From 16f6f26aba72945c2ef220e685d8162fff63891e Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 2 Jul 2021 14:08:06 +0200 Subject: [PATCH] Add initial support for Tasmota Mesh Add initial support for Tasmota Mesh (TasMesh) providing node/broker communication using ESP-NOW (#11939) --- CHANGELOG.md | 6 + RELEASENOTES.md | 3 +- info/xdrv_57_tasmesh.md | 51 ++ tasmota/xdrv_57_1_tasmesh_support.ino | 391 +++++++++++ tasmota/xdrv_57_9_tasmesh.ino | 846 ++++++++++++++++++++++++ tools/mqtt-file/Config_demo_9.5.0.1.dmp | Bin 4096 -> 0 bytes tools/mqtt-file/download-settings.py | 7 +- tools/mqtt-file/upload-ota.py | 7 +- tools/mqtt-file/upload-settings.py | 9 +- 9 files changed, 1315 insertions(+), 5 deletions(-) create mode 100644 info/xdrv_57_tasmesh.md create mode 100644 tasmota/xdrv_57_1_tasmesh_support.ino create mode 100644 tasmota/xdrv_57_9_tasmesh.ino delete mode 100644 tools/mqtt-file/Config_demo_9.5.0.1.dmp diff --git a/CHANGELOG.md b/CHANGELOG.md index edcd160f1..9ecc9f106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - Development ## [9.5.0.2] +### Added +- Initial support for Tasmota Mesh (TasMesh) providing node/broker communication using ESP-NOW (#11939) + +### Changed +- ESP32 core library from v1.0.7 to v1.0.7.1 + ### Fixed - ESP32-C3 settings layout for configuration backup and restore diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b1cccab7d..e572f5df2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -98,10 +98,11 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo ## Changelog v9.5.0.2 ### Added - Enable UFILESYS, GUI_TRASH_FILE and GUI_EDIT_FILE for any device compiled with more than 1M flash size +- Initial support for Tasmota Mesh (TasMesh) providing node/broker communication using ESP-NOW [#11939](https://github.com/arendst/Tasmota/issues/11939) - Support for AM2320 Temperature and Humidity Sensor by Lars Wessels [#12485](https://github.com/arendst/Tasmota/issues/12485) ### Changed -- ESP32 core library from v1.0.6 to v1.0.7 +- ESP32 core library from v1.0.6 to v1.0.7.1 - Force ESP32 defines USE_UFILESYS, GUI_TRASH_FILE and #define GUI_EDIT_FILE - Speed up initial GUI console refresh - Simplified configuration for ir-full and removal of tasmota-ircustom [#12428](https://github.com/arendst/Tasmota/issues/12428) diff --git a/info/xdrv_57_tasmesh.md b/info/xdrv_57_tasmesh.md new file mode 100644 index 000000000..47dc918ed --- /dev/null +++ b/info/xdrv_57_tasmesh.md @@ -0,0 +1,51 @@ +# TASMESH + +This driver provides the ability to move TASMOTA-devices out of the WLAN by using ESP-NOW to communicate bidirectional with an internal protocol. + +Thus the workload for the WLAN-router is reduced and with the reduced overhead the local 2.4-GHz-band will be freed of some traffic. Power consumption of the nodes will be reduced significantly allowing better battery powered projects with TASMOTA. +Automatic payload encryption is applied using the WiFi-password1 as the key. A maximum of 32 bytes of this password is used for the ChaCha20Poly1305 authenticated encryption as the key. + +As ACK/NACK messages seem to be not reliable on both ESP-platforms, the method "send-and-pray" is used. + + +## Working priciple + +An ESP32 is needed as gateway/broker to connect the nodes (typically an ESP8266) to the WLAN. The ESP32 will receive the MQTT-topic of every node and subscribe to it as a proxy. +If a MQTT-message in the form of 'cmnd/node_topic/...' is received, the broker will automatically send this to the referring node via ESP-NOW. +The broker will automatically send time messages to all nodes. + +The nodes will send their MQTT-messages back to the broker via ESP-NOW. + +## Enabling the driver + +Add ``#define USE_TASMESH`` to your file ``user_config_override.h`` before compilation. + +## Commands + +``MeshBroker`` - starts the broker on the ESP32, printing out the MAC and used WiFi-channel to the log. Must be called after WiFi is initialized!! Example 'Rule1 on system#boot do meshbroker endon' + +``MeshChannel 1..13`` - changes the WiFi-channel (on the node) to n (1-13) according to the channel of the (ESP32-)broker. + +``MeshNode AA:BB:CC:DD:EE:FF`` - starts a node and connects the the broker with the given MAC-address, will automatically send MQTT-topic to the broker + +``MeshPeer AA:BB:CC:DD:EE:FF`` - usable to add a known node to another node to be able to send data via the mesh to the broker, that may be out of reach + +``MeshInterval 2..200`` - changes the interval between mesh messages default set to 50 ms + +## Rules + +Rules examples: + +- The broker must be started after wifi is up!!
``rule1 on system#boot do meshbroker endon`` +- The node may be started as soon as possible. Once started wifi and webserver are disabled by design
``rule1 on system#init do meshnode 98:F4:AB:6D:2D:B5 endon`` +- Add a known peer (another node in the mesh) after the node has initialized
``rule3 on mesh#node=1 do meshpeer 2cf4323cdb33 endon`` + +## Limitations + +The following limitations apply: +- An ESP32 is only supported as a broker +- An ESP8266 is only supported as a node +- No command persistence is implemented so use rules to start a broker or a node +- Although node send queues are implemented there is no node receive queue so MQTT commands send to the node need to be as small as possible limited to a maximum of around 160 characters including the topic +- Although broker receive queues are implemented there is no broker send queue so MQTT commands send to the node need to be as small as possible limited to a maximum of around 160 characters including the topic +- As there is no direct connection from the node to the MQTT broker it will signal the node as LWT Offline diff --git a/tasmota/xdrv_57_1_tasmesh_support.ino b/tasmota/xdrv_57_1_tasmesh_support.ino new file mode 100644 index 000000000..81949af0f --- /dev/null +++ b/tasmota/xdrv_57_1_tasmesh_support.ino @@ -0,0 +1,391 @@ +/* + xdrv_57_1_tasmesh_support.ino - Mesh via ESP-Now support for Tasmota + + Copyright (C) 2021 Christian Baars and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_TASMESH + +#include +#include + +#ifdef ESP32 +#include +#include +#else +#include +#endif //ESP32 + +/*********************************************************************************************\ + * constants +\*********************************************************************************************/ + +#define D_CMND_MESH "MESH" + +#define MESH_PAYLOAD_SIZE 160 // Default 160 - with header of 20 bytes and 16 bytes tag, stays below 200 bytes, which is reported to work with ESP8266 +#define MESH_TOPICSZ 64 // Max supported topic size +#define MESH_BUFFERS 26 // (6) Max buffers number for splitted messages +#define MESH_MAX_PACKETS 3 // (3) Max number of packets +#define MESH_REFRESH 50 // Number of ms + +// ------------------------------------------------------------------------------------------------------------ +// | MAC Header | Category Code | Organization Identifier | Random Values | Vendor Specific Content | FCS | +// ------------------------------------------------------------------------------------------------------------ +// 24 bytes 1 byte 3 bytes 4 bytes 7~255 bytes 4 bytes +// ------------------------------------------------------------------------------- +// | Element ID | Length | Organization Identifier | Type | Version | Body | +// ------------------------------------------------------------------------------- +// 1 byte 1 byte 3 bytes 1 byte 1 byte 0~250 bytes + +struct mesh_packet_t { + uint8_t sender[6]; // MAC + uint8_t receiver[6]; // MAC + uint32_t counter:4; // Rolling counter to identify a packet + uint32_t type:6; // Command, Mqtt, ... + uint32_t chunks:6; // Number of chunks + uint32_t chunk:6; // Chunk number + uint32_t chunkSize:8; // Chunk size + uint32_t TTL:2; // Time to live, counting down + union { + uint32_t senderTime; // UTC-timestamp from every sender in the MESH + uint32_t peerIndex; // Only for resending in the MESH + }; + uint8_t tag[16]; // Tag for de/encryption + uint8_t payload[MESH_PAYLOAD_SIZE]; +} __attribute__((packed)); + +struct mesh_packet_header_t { // ToDo: Maybe, we do not need this + uint8_t sender[6]; // MAC + uint8_t receiver[6]; // MAC + uint32_t counter:4; // Rolling counter to identify a packet + uint32_t type:6; // Command, Mqtt, ... + uint32_t chunks:6; // Number of chunks + uint32_t chunk:6; // Chunk number + uint32_t chunkSize:8; // Chunk size + uint32_t TTL:2; // Time to live, counting down + union { + uint32_t senderTime; // UTC-timestamp from every sender in the MESH + uint32_t peerIndex; // Only for resending in the MESH + }; + uint8_t tag[16]; // Tag for de/encryption +} __attribute__((packed)); + +struct mesh_peer_t { + uint8_t MAC[6]; + uint32_t lastMessageFromPeer; // Time of last message from peer +#ifdef ESP32 + char topic[MESH_TOPICSZ]; +#endif //ESP32 +}; + +struct mesh_flags_t { + uint8_t brokerNeedsTopic:1; + uint8_t nodeGotTime:1; + uint8_t nodeWantsTime:1; + uint8_t nodeWantsTimeASAP:1; +}; + +struct mesh_packet_combined_t { + mesh_packet_header_t header; + uint32_t receivedChunks; // Bitmask for up to 32 chunks + char raw[MESH_PAYLOAD_SIZE * MESH_BUFFERS]; +}; + +struct mesh_first_header_bytes { // TODO: evaluate random 4-byte-value of pre-packet + uint8_t raw[15]; +} __attribute__((packed));; + +struct { + uint32_t lastMessageFromBroker; // Time of last message from broker + uint32_t lmfap; // Yime of last message from any peer + uint8_t broker[6] = { 0 }; + uint8_t key[32]; + uint8_t role; + uint8_t channel; // Wifi channel + uint8_t interval; + uint8_t currentTopicSize; + mesh_flags_t flags; + mesh_packet_t sendPacket; + std::vector peers; + std::queue packetToResend; + std::queue packetToConsume; + std::vector packetsAlreadySended; + std::vector packetsAlreadyReceived; + std::vector multiPackets; +#ifdef ESP32 + std::vector lastTeleMsgs; +#endif //ESP32 +} MESH; + +/*********************************************************************************************\ + * Declarations for functions with custom types +\*********************************************************************************************/ + +void MESHsendPacket(mesh_packet_t *_packet); +bool MESHencryptPayload(mesh_packet_t *_packet, int _encrypt); // 1 encryption, 0 decryption + +/*********************************************************************************************\ + * enumerations +\*********************************************************************************************/ + +enum MESH_Commands { // commands useable in console or rules + CMND_MESH_BROKER, // start broker on ESP32 + CMND_MESH_NODE, // start node and connect to broker based on MAC address + CMND_MESH_PEER, // add node to peer list of a broker or node + CMND_MESH_CHANNEL}; // set wifi channel on node (the broker gets it automatically from the AP) + +enum MESH_Role { + ROLE_NONE = 0, // not initialized + ROLE_BROKER, // ESP32 will connect mesh to WLAN + ROLE_NODE_FULL, // Node will listen and resend every message for MESH functionality + ROLE_NODE_SMALL // Node will only talk to the broker +}; + +enum MESH_Packet_Type { // Type of packet + PACKET_TYPE_TIME = 0, // + PACKET_TYPE_PEERLIST, // send all kown peers, broker is always 0 + PACKET_TYPE_COMMAND, // not used yet + PACKET_TYPE_REGISTER_NODE, // register a node with encrypted broker-MAC, announce mqtt topic to ESP32-proxy - broker will send time ASAP + PACKET_TYPE_REFRESH_NODE, // refresh node infos with encrypted broker-MAC, announce mqtt topic to ESP32-proxy - broker will send time slightly delayed + PACKET_TYPE_MQTT, // send regular mqtt messages, single or multipackets + PACKET_TYPE_WANTTOPIC // the broker has no topic for this peer/node +}; + +/*********************************************************************************************\ + * +\*********************************************************************************************/ + +#ifdef ESP32 + +void MESHsendTime(void) { // Only from broker to nodes + MESH.sendPacket.counter++; + MESH.sendPacket.type = PACKET_TYPE_TIME; + MESH.sendPacket.TTL = 1; + // memcpy(MESH.sendPacket.receiver,MESH.peers[_peerNumber].MAC,6); + MESH.sendPacket.senderTime = Rtc.utc_time; + MESH.sendPacket.payload[0] = 0; + // mesh_flags_t *_flags = (mesh_flags_t *)MESH.sendPacket.payload; + // if(MESH.peers[_peerNumber].topic[0]==0){ + // AddLog(LOG_LEVEL_INFO, PSTR("MSH: Broker wants topic from peer: %u"), _peerNumber); + // _flags->brokerNeedsTopic = 1; + // } + MESH.sendPacket.chunkSize = 0; + MESH.sendPacket.chunks = 0; + MESHsendPacket(&MESH.sendPacket); +} + +void MESHdemandTopic(uint32_t _peerNumber) { + MESH.sendPacket.counter++; + MESH.sendPacket.type = PACKET_TYPE_WANTTOPIC; + MESH.sendPacket.TTL = 2; + MESH.sendPacket.payload[0] = 0; + MESH.sendPacket.chunkSize = 0; + MESH.sendPacket.chunks = 0; + memcpy(MESH.sendPacket.receiver,MESH.peers[_peerNumber].MAC,6); + MESHsendPacket(&MESH.sendPacket); +} + +#endif //ESP32 + +void MESHsendPeerList(void) { // We send this list only to the peers, that can directly receive it + MESH.sendPacket.counter++; + MESH.sendPacket.type = PACKET_TYPE_PEERLIST; + MESH.sendPacket.senderTime = Rtc.utc_time; + uint32_t _idx = 0; + for (auto &_peer : MESH.peers) { + memcpy(MESH.sendPacket.payload + _idx, _peer.MAC, 6); + _idx += 6; + } + if (0 == _idx) { return; } + + MESH.sendPacket.chunk = 0; + MESH.sendPacket.chunks = 1; + MESH.sendPacket.chunkSize = _idx; + MESH.sendPacket.TTL = 1; +// AddLogBuffer(LOG_LEVEL_INFO, MESH.sendPacket.payload, MESH.sendPacket.chunkSize); + MESHsendPacket(&MESH.sendPacket); +} + +bool MESHcheckPeerList(const uint8_t *MAC) { + bool success = false; + for (auto &_peer : MESH.peers) { + if (memcmp(_peer.MAC, MAC, 6) == 0) { + _peer.lastMessageFromPeer = millis(); + return true; + } + } + return false; +} + +uint8_t MESHcountPeers(void) { +#ifdef ESP32 + esp_now_peer_num_t _peernum; + esp_now_get_peer_num(&_peernum); + uint8_t _num = _peernum.total_num; +#else + uint8_t _num; + uint8_t _numEnc; // wo don't care + esp_now_get_cnt_info(&_num, &_numEnc); +#endif +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Peers %u"), _num); + return _num; +} + +int MESHaddPeer(uint8_t *_MAC ) { + mesh_peer_t _newPeer; + memcpy(_newPeer.MAC, _MAC, 6); + _newPeer.lastMessageFromPeer = millis(); +#ifdef ESP32 + _newPeer.topic[0] = 0; +#endif + MESH.peers.push_back(_newPeer); +#ifdef ESP32 + std::string _msg = "{\"Init\":1}"; // Init with a simple JSON only while developing + MESH.lastTeleMsgs.push_back(_msg); // We must keep this vector in sync with the peers-struct on the broker regarding the indices +#endif //ESP32 + int err; +#ifdef ESP32 + esp_now_peer_info_t _peer; + _peer.channel = MESH.channel; + _peer.encrypt = false; + _peer.ifidx = (wifi_interface_t)ESP_IF_WIFI_AP; + memcpy(_peer.peer_addr, _MAC, 6); + err = esp_now_add_peer(&_peer); +#else + err = esp_now_add_peer(_MAC, ESP_NOW_ROLE_COMBO, MESH.channel, NULL, 0); +#endif + if (0 == err) { + char _peerMAC[18]; + ToHex_P(_MAC, 6, _peerMAC, 18, ':'); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Peer %s added successful"), _peerMAC); + #ifdef ESP32 + if (ROLE_BROKER == MESH.role) { MESHsendTime(); } + #endif //ESP32 + Response_P(PSTR("{\"%s\":{\"Peers\":%u}}"), D_CMND_MESH, MESH.peers.size()); + XdrvRulesProcess(0); + return err; + } else { + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Failed to add peer %d"), err); + } + return err; +} + +//helper functions +void MESHstripColon(char* _string) { + uint32_t _length = strlen(_string); + uint32_t _index = 0; + while (_index < _length) { + char c = _string[_index]; + if (c == ':') { + memmove(_string + _index, _string + _index +1, _length - _index); + } + _index++; + } + _string[_index] = 0; +} + +void MESHHexStringToBytes(char* _string, uint8_t _MAC[]) { //uppercase + MESHstripColon(_string); + UpperCase(_string, _string); + uint32_t index = 0; + uint32_t _end = strlen(_string); + memset(_MAC, 0, _end / 2); + while (index < _end) { + char c = _string[index]; + uint8_t value = 0; + if ((c >= '0') && (c <= '9')) { + value = (c - '0'); + } + else if ((c >= 'A') && (c <= 'F')) { + value = (10 + (c - 'A')); + } + _MAC[(index / 2)] += value << (((index + 1) % 2) * 4); + index++; + } +} + +void MESHsendPacket(mesh_packet_t *_packet) { + MESHencryptPayload(_packet, 1); +// esp_now_send(_packet->receiver, (uint8_t *)_packet, sizeof(MESH.sendPacket) - MESH_PAYLOAD_SIZE + _packet->chunkSize); + esp_now_send(NULL, (uint8_t *)_packet, sizeof(MESH.sendPacket) - MESH_PAYLOAD_SIZE + _packet->chunkSize); //NULL -> broadcast +} + +void MESHsetKey(uint8_t* _key) { // Must be 32 bytes!!! + char* _pw = SettingsText(SET_STAPWD1 + Settings->sta_active); + size_t _length = strlen(_pw); + memset(_key, 0, 32); + if (_length > 32) { _length = 32; } + memcpy(_key, _pw, _length); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: set crypto key to PASSWORD1")); +} + +bool MESHencryptPayload(mesh_packet_t *_packet, int _encrypt) { + +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: will encrypt: %u"), _encrypt); + + size_t _size = _packet->chunkSize; + char _tag[16]; + +// AddLog(LOG_LEVEL_DEBUG, PSTR("cc: %u, _size: %u"), _counter,_size); +// AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*)_tag,16); + + br_chacha20_run bc = br_chacha20_ct_run; + + br_poly1305_ctmul32_run((void*)MESH.key, (const void *)_packet, + (void *)_packet->payload, _size, _packet->receiver+6, 2, + _tag, bc, _encrypt); + +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: encryption done ")); + + if (_encrypt==1) { + memcpy(_packet->tag, _tag, 16); +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: payload encrypted")); + return true; + } + if (memcmp(_packet->tag, _tag, 16) == 0) { +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: payload decrypted")); + return true; + } + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: payload decryption error")); + return false; +} + +void MESHsetSleep(void) { + if (MESH.role && (Settings->sleep > MESH.interval)) { + Settings->sleep = MESH.interval; + TasmotaGlobal.sleep = MESH.interval; + } +} + +void MESHsetWifi(bool state) { +#ifdef ESP8266 // Only ESP8266 as ESP32 is a broker and needs Wifi + if (state) { // Wifi On + Settings->flag4.network_wifi = 1; // (Re-)enable wifi as long as Mesh is not enabled +// TasmotaGlobal.global_state.wifi_down = 0; + Settings->flag.global_state = 0; // (Wifi, MQTT) Control link led blinking (1) + } else { // Wifi Off and use ESP-NOW + Settings->flag4.network_wifi = 0; // The "old" wifi off command + TasmotaGlobal.global_state.wifi_down = 1; + Settings->flag.global_state = 1; // (Wifi, MQTT) Control link led blinking (1) + } +#endif // ESP8266 +} + +uint32_t MESHmaxPayloadSize(void) { + return MESH_PAYLOAD_SIZE - MESH.currentTopicSize -1; +} + +#endif //USE_TASMESH diff --git a/tasmota/xdrv_57_9_tasmesh.ino b/tasmota/xdrv_57_9_tasmesh.ino new file mode 100644 index 000000000..a56b2f4c4 --- /dev/null +++ b/tasmota/xdrv_57_9_tasmesh.ino @@ -0,0 +1,846 @@ +/* + xdrv_57_tasmesh.ino - Mesh support for Tasmota using ESP-Now + + Copyright (C) 2021 Christian Baars, Federico Leoni and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + -------------------------------------------------------------------------------------------- + Version yyyymmdd Action Description + -------------------------------------------------------------------------------------------- + 0.9.5.1 20210622 integrate Expand number of chunks to satisfy larger MQTT messages + Refactor to latest Tasmota standards + --- + 0.9.4.1 20210503 integrate Add some minor tweak for channel management by Federico Leoni + --- + 0.9.0.0 20200927 started From scratch by Christian Baars +*/ + +#ifdef USE_TASMESH + +/*********************************************************************************************\ +* Build a mesh of nodes using ESP-Now +* Connect it through an ESP32-broker to WLAN +\*********************************************************************************************/ + +#define XDRV_57 57 + +/*********************************************************************************************\ + * Callbacks +\*********************************************************************************************/ + +#ifdef ESP32 + +void CB_MESHDataSent(const uint8_t *MAC, esp_now_send_status_t sendStatus); +void CB_MESHDataSent(const uint8_t *MAC, esp_now_send_status_t sendStatus) { + char _destMAC[18]; + ToHex_P(MAC, 6, _destMAC, 18, ':'); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Sent to %s status %d"), _destMAC, sendStatus); +} + +void CB_MESHDataReceived(const uint8_t *MAC, const uint8_t *packet, int len) { + static bool _locked = false; + if (_locked) { return; } + + _locked = true; + char _srcMAC[18]; + ToHex_P(MAC, 6, _srcMAC, 18, ':'); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Rcvd from %s"), _srcMAC); + mesh_packet_t *_recvPacket = (mesh_packet_t*)packet; + if ((_recvPacket->type == PACKET_TYPE_REGISTER_NODE) || (_recvPacket->type == PACKET_TYPE_REFRESH_NODE)) { + if (MESHcheckPeerList((const uint8_t *)MAC) == false) { + MESHencryptPayload(_recvPacket, 0); //decrypt it and check + if (memcmp(_recvPacket->payload, MESH.broker, 6) == 0) { + MESHaddPeer((uint8_t*)MAC); +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Rcvd topic %s"), (char*)_recvPacket->payload + 6); +// AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)&MESH.packetToConsume.front().payload,MESH.packetToConsume.front().chunkSize+5); + for (auto &_peer : MESH.peers) { + if (memcmp(_peer.MAC, _recvPacket->sender, 6) == 0) { + strcpy(_peer.topic, (char*)_recvPacket->payload + 6); + MESHsubscribe((char*)&_peer.topic); + _locked = false; + return; + } + } + } else { + char _cryptMAC[18]; + ToHex_P(_recvPacket->payload, 6, _cryptMAC, 18, ':'); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Peer %s denied, wrong MAC %s"), _srcMAC, _cryptMAC); + _locked = false; + return; + } + } else { + if (_recvPacket->type == PACKET_TYPE_REGISTER_NODE) { + MESH.flags.nodeWantsTimeASAP = 1; //this could happen after wake from deepsleep on battery powered device + } else { + MESH.flags.nodeWantsTime = 1; + } + } + } + MESH.lmfap = millis(); + if (MESHcheckPeerList(MAC) == true){ + MESH.packetToConsume.push(*_recvPacket); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Packet %d from %s to queue"), MESH.packetToConsume.size(), _srcMAC); + } + _locked = false; +} + +#else // ESP8266 + +void CB_MESHDataSent(uint8_t *MAC, uint8_t sendStatus) { + char _destMAC[18]; + ToHex_P(MAC, 6, _destMAC, 18, ':'); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Sent to %s status %d"), _destMAC, sendStatus); +} + +void CB_MESHDataReceived(uint8_t *MAC, uint8_t *packet, uint8_t len) { + MESH.lmfap = millis(); //any peer + if (memcmp(MAC, MESH.broker, 6) == 0) { + MESH.lastMessageFromBroker = millis(); //directly from the broker + } + mesh_packet_t *_recvPacket = (mesh_packet_t*)packet; + switch (_recvPacket->type) { + case PACKET_TYPE_TIME: + Rtc.utc_time = _recvPacket->senderTime; + Rtc.user_time_entry = true; + MESH.lastMessageFromBroker = millis(); + if (MESH.flags.nodeGotTime == 0) { + RtcSync(); + TasmotaGlobal.rules_flag.system_boot = 1; // for now we consider the node booted and let trigger system#boot on RULES + } + MESH.flags.nodeGotTime = 1; + //Wifi.retry = 0; + // Response_P(PSTR("{\"%s\":{\"Time\":1}}"), D_CMND_MESH); //got the time, now we can publish some sensor data + // XdrvRulesProcess(); + break; + case PACKET_TYPE_PEERLIST: + MESH.packetToConsume.push(*_recvPacket); + return; + break; + default: + // nothing for now; + break; + } + if (memcmp(_recvPacket->receiver, MESH.sendPacket.sender, 6) != 0) { //MESH.sendPacket.sender simply stores the MAC of the node + if (ROLE_NODE_SMALL == MESH.role) { + return; // a 'small node' does not perform mesh functions + } + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Packet to resend ...")); + MESH.packetToResend.push(*_recvPacket); + return; + } else { + if (_recvPacket->type == PACKET_TYPE_WANTTOPIC) { + MESH.flags.brokerNeedsTopic = 1; + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Broker needs topic ...")); + return; //nothing left to be done + } + // for(auto &_message : MESH.packetsAlreadyReceived){ + // if(memcmp(_recvPacket,_message,15==0)){ + // AddLog(LOG_LEVEL_INFO, PSTR("MSH: Packet already received")); + // return; + // } + // } + // MESH.packetsAlreadyReceived.push_back((mesh_packet_header_t*) _recvPacket); + // AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Packet to consume ...")); + MESH.packetToConsume.push(*_recvPacket); + } +} + +#endif // ESP32 + +/*********************************************************************************************\ + * init driver +\*********************************************************************************************/ + +void MESHInit(void) { + MESH.interval = MESH_REFRESH; + MESH.role = ROLE_NONE; + MESH.packetsAlreadyReceived.reserve(5); + MESH.peers.reserve(10); + MESH.multiPackets.reserve(2); + + MESH.sendPacket.counter = 0; + MESH.sendPacket.chunks = 1; + MESH.sendPacket.chunk = 0; + MESH.sendPacket.type = PACKET_TYPE_TIME; + MESH.sendPacket.TTL = 2; + + MESHsetWifi(1); // (Re-)enable wifi as long as Mesh is not enabled + + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Initialized")); +} + +void MESHdeInit(void) { +#ifdef ESP8266 // only ESP8266, ESP32 as a broker should not use deepsleep + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Stopping")); + // TODO: degister from the broker, so he can stop MQTT-proxy + esp_now_deinit(); +#endif // ESP8266 +} + +/*********************************************************************************************\ + * MQTT proxy functions +\*********************************************************************************************/ + +#ifdef ESP32 + +/** + * @brief Subscribes as a proxy + * + * @param topic - received from the referring node + */ +void MESHsubscribe(char *topic) { + char stopic[TOPSZ]; + GetTopic_P(stopic, CMND, topic, PSTR("#")); + MqttSubscribe(stopic); +} + +void MESHunsubscribe(char *topic) { + char stopic[TOPSZ]; + GetTopic_P(stopic, CMND, topic, PSTR("#")); + MqttUnsubscribe(stopic); +} + +void MESHconnectMQTT(void){ + for (auto &_peer : MESH.peers) { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Reconnect topic %s"), _peer.topic); + if (_peer.topic[0] != 0) { + MESHsubscribe(_peer.topic); + } + } +} + +/** + * @brief Intercepts mqtt message, that the broker (ESP32) subscribes to as a proxy for a node. + * Is called from xdrv_02_mqtt.ino. Will send the message in the payload via ESP-NOW. + * + * @param _topic + * @param _data + * @param data_len + * @return true + * @return false + */ +bool MESHinterceptMQTTonBroker(char* _topic, uint8_t* _data, unsigned int data_len) { + if (MESH.role != ROLE_BROKER) { return false; } + + char stopic[TOPSZ]; +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Intercept topic %s"), _topic); + for (auto &_peer : MESH.peers) { + GetTopic_P(stopic, CMND, _peer.topic, PSTR("")); //cmnd/topic/ + if (strlen(_topic) != strlen(_topic)) { + return false; // prevent false result when _topic is the leading substring of stopic + } + if (memcmp(_topic, stopic, strlen(stopic)) == 0) { + MESH.sendPacket.chunkSize = strlen(_topic) +1; + + if (MESH.sendPacket.chunkSize + data_len > MESH_PAYLOAD_SIZE) { + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Intercept payload oversized %d"), data_len); + return false; + } + + memcpy(MESH.sendPacket.receiver, _peer.MAC, 6); + memcpy(MESH.sendPacket.payload, _topic, MESH.sendPacket.chunkSize); + memcpy(MESH.sendPacket.payload + MESH.sendPacket.chunkSize, _data, data_len); + MESH.sendPacket.chunkSize += data_len; + MESH.sendPacket.chunks = 1; + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Intercept payload '%s'"), MESH.sendPacket.payload); + MESH.sendPacket.type = PACKET_TYPE_MQTT; + MESH.sendPacket.senderTime = Rtc.utc_time; + MESHsendPacket(&MESH.sendPacket); + // int result = esp_now_send(MESH.sendPacket.receiver, (uint8_t *)&MESH.sendPacket, (sizeof(MESH.sendPacket))-(MESH_PAYLOAD_SIZE-MESH.sendPacket.chunkSize)); + //send to Node + return true; + } + } + return false; +} + +#else // ESP8266 + +void MESHreceiveMQTT(mesh_packet_t *_packet); +void MESHreceiveMQTT(mesh_packet_t *_packet){ + uint32_t _slength = strlen((char*)_packet->payload); + if (_packet->chunks == 1) { //single chunk message + MqttDataHandler((char*)_packet->payload, (uint8_t*)(_packet->payload)+_slength+1, (_packet->chunkSize)-_slength); + } else { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Multiple chunks %u not supported yet"), _packet->chunks); + // TODO: reconstruct message in buffer or only handle short messages + } +} + +#endif // ESP32 + +bool MESHroleNode(void) { + return (MESH.role > ROLE_BROKER); +} + +/** + * @brief Redirects the mqtt message on the node just before it would have been sended to + * the broker via ESP-NOW + * + * @param _topic + * @param _data + * @param _retained - currently unused + * @return true + * @return false + */ +bool MESHrouteMQTTtoMESH(const char* _topic, char* _data, bool _retained) { + if (!MESHroleNode()) { return false; } + + size_t _bytesLeft = strlen(_topic) + strlen(_data) +2; + MESH.sendPacket.counter++; + MESH.sendPacket.chunk = 0; + MESH.sendPacket.chunks = ((_bytesLeft+2) / MESH_PAYLOAD_SIZE) +1; + memcpy(MESH.sendPacket.receiver, MESH.broker, 6); + MESH.sendPacket.type = PACKET_TYPE_MQTT; + MESH.sendPacket.chunkSize = MESH_PAYLOAD_SIZE; + MESH.sendPacket.peerIndex = 0; +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Chunks %u, Counter %u"), MESH.sendPacket.chunks, MESH.sendPacket.counter); + size_t _topicSize = strlen(_topic) +1; + size_t _offsetData = 0; + while (_bytesLeft > 0) { + size_t _byteLeftInChunk = MESH_PAYLOAD_SIZE; + // MESH.sendPacket.chunkSize = MESH_PAYLOAD_SIZE; + if (MESH.sendPacket.chunk == 0) { + memcpy(MESH.sendPacket.payload, _topic, _topicSize); + MESH.sendPacket.chunkSize = _topicSize; + + MESH.currentTopicSize = MESH.sendPacket.chunkSize; + + _bytesLeft -= _topicSize; + _byteLeftInChunk -= _topicSize; + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Topic in payload '%s'"), (char*)MESH.sendPacket.payload); +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: After topic -> chunk:%u, pre-size: %u"),MESH.sendPacket.chunk,MESH.sendPacket.chunkSize); + } + if (_byteLeftInChunk > 0) { + if (_byteLeftInChunk > _bytesLeft) { +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: only last chunk bL:%u bLiC:%u oSD:%u"),_bytesLeft,_byteLeftInChunk,_offsetData); + _byteLeftInChunk = _bytesLeft; +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: only last chunk after correction -> chunk:%u, pre-size: %u"),MESH.sendPacket.chunk,MESH.sendPacket.chunkSize); + } + if (MESH.sendPacket.chunk > 0) { _topicSize = 0; } +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: %u"),_topicSize); + memcpy(MESH.sendPacket.payload + _topicSize, _data + _offsetData, _byteLeftInChunk); + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Data in payload '%s'"), (char*)MESH.sendPacket.payload + _topicSize); + _offsetData += _byteLeftInChunk; + _bytesLeft -= _byteLeftInChunk; + } + MESH.sendPacket.chunkSize += _byteLeftInChunk; + MESH.packetToResend.push(MESH.sendPacket); +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: chunk:%u, size: %u"),MESH.sendPacket.chunk,MESH.sendPacket.chunkSize); +// AddLogBuffer(LOG_LEVEL_INFO, (uint8_t*)MESH.sendPacket.payload, MESH.sendPacket.chunkSize); + if (MESH.sendPacket.chunk == MESH.sendPacket.chunks) { +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Too many chunks %u"), MESH.sendPacket.chunk +1); + } + + SHOW_FREE_MEM(PSTR("MESHrouteMQTTtoMESH")); + + MESH.sendPacket.chunk++; + MESH.sendPacket.chunkSize = 0; + } + return true; +} + +/** + * @brief The node sends its mqtt topic to the broker + * + */ +void MESHregisterNode(uint8_t mode){ + memcpy(MESH.sendPacket.receiver, MESH.broker, 6); // First 6 bytes -> MAC of broker + strcpy((char*)MESH.sendPacket.payload +6, TasmotaGlobal.mqtt_topic); // Remaining bytes -> topic of node + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Register node with topic '%s'"), (char*)MESH.sendPacket.payload +6); + MESH.sendPacket.TTL = 2; + MESH.sendPacket.chunks = 1; + MESH.sendPacket.chunk = 0; + MESH.sendPacket.chunkSize = strlen(TasmotaGlobal.mqtt_topic) + 1 + 6; + memcpy(MESH.sendPacket.payload, MESH.broker, 6); + MESH.sendPacket.type = (mode == 0) ? PACKET_TYPE_REGISTER_NODE : PACKET_TYPE_REFRESH_NODE; + MESHsendPacket(&MESH.sendPacket); +} + +/*********************************************************************************************\ + * Generic functions +\*********************************************************************************************/ + +void MESHstartNode(int32_t _channel, uint8_t _role){ //we need a running broker with a known channel at that moment +#ifdef ESP8266 // for now only ESP8266, might be added for the ESP32 later + MESH.channel = _channel; + WiFi.mode(WIFI_STA); + WiFi.begin("", "", MESH.channel, nullptr, false); //fake connection attempt to set channel + wifi_promiscuous_enable(1); + wifi_set_channel(MESH.channel); + wifi_promiscuous_enable(0); + WiFi.disconnect(); + MESHsetWifi(0); + if (esp_now_init() != 0) { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Node init failed")); + return; + } + +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Node initialized, channel: %u"),wifi_get_channel()); //check if we succesfully set the + Response_P(PSTR("{\"%s\":{\"Node\":1,\"Channel\":%u,\"Role\":%u}}"), D_CMND_MESH, wifi_get_channel(), _role); + XdrvRulesProcess(0); + + esp_now_set_self_role(ESP_NOW_ROLE_COMBO); + esp_now_register_send_cb(CB_MESHDataSent); + esp_now_register_recv_cb(CB_MESHDataReceived); + + MESHsetKey(MESH.key); + memcpy(MESH.sendPacket.receiver, MESH.broker, 6); + WiFi.macAddress(MESH.sendPacket.sender); + MESHaddPeer(MESH.broker); //must always be peer 0!! -return code -7 for peer list full + MESHcountPeers(); + MESH.lastMessageFromBroker = millis(); // Init + MESH.role = (0 == _role) ? ROLE_NODE_SMALL : ROLE_NODE_FULL; + MESHsetSleep(); + MESHregisterNode(0); +#endif // ESP8266 +} + +void MESHstartBroker(void) { // Must be called after WiFi is initialized!! Rule - on system#boot do meshbroker endon +#ifdef ESP32 + WiFi.mode(WIFI_AP_STA); + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Broker MAC %s"), WiFi.softAPmacAddress().c_str()); + WiFi.softAPmacAddress(MESH.broker); //set MESH.broker to the needed MAC + uint32_t _channel = WiFi.channel(); + + if (esp_now_init() != 0) { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Broker init failed")); + return; + } + + Response_P(PSTR("{\"%s\":{\"Broker\":1,\"Channel\":%u}}"), D_CMND_MESH, _channel); + XdrvRulesProcess(0); +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Broker initialized on channel %u"), _channel); + esp_now_register_send_cb(CB_MESHDataSent); + esp_now_register_recv_cb(CB_MESHDataReceived); + MESHsetKey(MESH.key); + MESHcountPeers(); + memcpy(MESH.sendPacket.sender, MESH.broker, 6); + MESH.role = ROLE_BROKER; + MESHsetSleep(); +#endif // ESP32 +} + +/*********************************************************************************************\ + * Main loops +\*********************************************************************************************/ + +#ifdef ESP32 + +void MESHevery50MSecond(void) { + // if (MESH.packetToResend.size() > 0) { + // // pass the packets + // } + if (MESH.packetToConsume.size() > 0) { +// AddLog(LOG_LEVEL_DEBUG, PSTR("_")); +// AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t *)&MESH.packetToConsume.front(), 15); + for (auto &_headerBytes : MESH.packetsAlreadyReceived) { +// AddLog(LOG_LEVEL_DEBUG, PSTR(".")); +// AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t *)_headerBytes.raw, 15); + if (memcmp(MESH.packetToConsume.front().sender, _headerBytes.raw, 15) == 0) { + MESH.packetToConsume.pop(); + return; + } + } + mesh_first_header_bytes _bytes; + memcpy(_bytes.raw, &MESH.packetToConsume.front(), 15); + MESH.packetsAlreadyReceived.push_back(_bytes); +// AddLog(LOG_LEVEL_DEBUG, PSTR("...")); +// AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t *)_bytes.raw, 15); + if (MESH.packetsAlreadyReceived.size() > MESH_MAX_PACKETS) { + MESH.packetsAlreadyReceived.erase(MESH.packetsAlreadyReceived.begin()); +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Erase received data")); + } + + // do something on the node + // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t *)&MESH.packetToConsume.front(), 30); + + MESHencryptPayload(&MESH.packetToConsume.front(), 0); + switch (MESH.packetToConsume.front().type) { + // case PACKET_TYPE_REGISTER_NODE: + // AddLog(LOG_LEVEL_INFO, PSTR("MSH: received topic: %s"), (char*)MESH.packetToConsume.front().payload + 6); + // // AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)&MESH.packetToConsume.front().payload,MESH.packetToConsume.front().chunkSize+5); + // for(auto &_peer : MESH.peers){ + // if(memcmp(_peer.MAC,MESH.packetToConsume.front().sender,6)==0){ + // strcpy(_peer.topic,(char*)MESH.packetToConsume.front().payload+6); + // MESHsubscribe((char*)&_peer.topic); + // } + // } + // break; + case PACKET_TYPE_PEERLIST: + for (uint32_t i = 0; i < MESH.packetToConsume.front().chunkSize; i += 6) { + if (memcmp(MESH.packetToConsume.front().payload +i, MESH.sendPacket.sender, 6) == 0) { + continue; // Do not add myself + } + if (MESHcheckPeerList(MESH.packetToConsume.front().payload +i) == false) { + MESHaddPeer(MESH.packetToConsume.front().payload +i); + } + } + break; + case PACKET_TYPE_MQTT: // Redirected MQTT from node in packet [char* _space_ char*] +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Received node output '%s'"), (char*)MESH.packetToConsume.front().payload); + if (MESH.packetToConsume.front().chunks > 1) { + bool _foundMultiPacket = false; + for (auto &_packet_combined : MESH.multiPackets) { +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Append to multipacket")); + if (memcmp(_packet_combined.header.sender, MESH.packetToConsume.front().sender, 12) == 0) { + if (_packet_combined.header.counter == MESH.packetToConsume.front().counter) { + memcpy(_packet_combined.raw + (MESH.packetToConsume.front().chunk * MESH_PAYLOAD_SIZE), MESH.packetToConsume.front().payload, MESH.packetToConsume.front().chunkSize); + bitSet(_packet_combined.receivedChunks, MESH.packetToConsume.front().chunk); + _foundMultiPacket = true; +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: Multipacket rcvd chunk mask 0x%08X"), _packet_combined.receivedChunks); + } + } + uint32_t _temp = (1 << (uint8_t)MESH.packetToConsume.front().chunks) -1; //example: 1+2+4 == (2^3)-1 +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: _temp: %u = %u"),_temp,_packet_combined.receivedChunks); + if (_packet_combined.receivedChunks == _temp) { + char * _data = (char*)_packet_combined.raw + strlen((char*)_packet_combined.raw) + 1; +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Publish multipacket")); + MqttPublishPayload((char*)_packet_combined.raw, _data); + MESH.multiPackets.erase(MESH.multiPackets.begin()); + break; + } + } + if (!_foundMultiPacket) { + mesh_packet_combined_t _packet; + memcpy(_packet.header.sender, MESH.packetToConsume.front().sender, sizeof(_packet.header)); + memcpy(_packet.raw + (MESH.packetToConsume.front().chunk * MESH_PAYLOAD_SIZE), MESH.packetToConsume.front().payload, MESH.packetToConsume.front().chunkSize); + _packet.receivedChunks = 0; + bitSet(_packet.receivedChunks, MESH.packetToConsume.front().chunk); + MESH.multiPackets.push_back(_packet); +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: New multipacket with chunks %u"), _packet.header.chunks); + } + } else { +// AddLog(LOG_LEVEL_INFO, PSTR("MSH: chunk: %u size: %u"), MESH.packetToConsume.front().chunk, MESH.packetToConsume.front().chunkSize); +// if (MESH.packetToConsume.front().chunk==0) AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)&MESH.packetToConsume.front().payload,MESH.packetToConsume.front().chunkSize); + char * _data = (char*)MESH.packetToConsume.front().payload + strlen((char*)MESH.packetToConsume.front().payload) +1; +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Publish packet")); + MqttPublishPayload((char*)MESH.packetToConsume.front().payload, _data); + + uint32_t idx = 0; + for (auto &_peer : MESH.peers){ + if (memcmp(_peer.MAC, MESH.packetToConsume.front().sender, 6) == 0) { + _peer.lastMessageFromPeer = millis(); + MESH.lastTeleMsgs[idx] = std::string(_data); + break; + } + idx++; + } +// AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)&MESH.packetToConsume.front().payload,MESH.packetToConsume.front().chunkSize); + } + break; + default: + AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t *)&MESH.packetToConsume.front(), MESH.packetToConsume.front().chunkSize +5); + break; + } + MESH.packetToConsume.pop(); +// AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Consumed one packet %u"), (char*)MESH.packetToConsume.size()); + } +} + +void MESHEverySecond(void) { + static uint32_t _second = 0; + _second++; + // send a time packet every x seconds + if (MESH.flags.nodeWantsTimeASAP) { + MESHsendTime(); + MESH.flags.nodeWantsTimeASAP = 0; + return; + } + if (_second % 5 == 0) { + if ((MESH.flags.nodeWantsTime == 1) || (_second % 30 == 0)) { //every 5 seconds on demand or every 30 seconds anyway + MESHsendTime(); + MESH.flags.nodeWantsTime = 0; + return; + } + } + uint32_t _peerNumber = _second%45; + if (_peerNumber < MESH.peers.size()) { + if (MESH.peers[_peerNumber].topic[0] == 0) { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Broker wants topic from peer %u"), _peerNumber); + MESHdemandTopic(_peerNumber); + } + } + if (MESH.multiPackets.size() > 3) { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Multi packets in buffer %u"), MESH.multiPackets.size()); + MESH.multiPackets.erase(MESH.multiPackets.begin()); + } +} + +#else // ESP8266 + +void MESHevery50MSecond(void) { + if (ROLE_NONE == MESH.role) { return; } + + if (MESH.packetToResend.size() > 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Next packet %d to resend of type %u, TTL %u"), + MESH.packetToResend.size(), MESH.packetToResend.front().type, MESH.packetToResend.front().TTL); + if (MESH.packetToResend.front().TTL > 0) { + MESH.packetToResend.front().TTL--; + if (memcmp(MESH.packetToResend.front().sender, MESH.broker, 6) != 0) { //do not send back the packet to the broker + MESHsendPacket(&MESH.packetToResend.front()); + } + } else { + MESH.packetToResend.pop(); + } + // pass the packets + } + + if (MESH.packetToConsume.size() > 0) { + MESHencryptPayload(&MESH.packetToConsume.front(), 0); + switch (MESH.packetToConsume.front().type) { + case PACKET_TYPE_MQTT: + if (memcmp(MESH.packetToConsume.front().sender, MESH.sendPacket.sender, 6) == 0) { + //discard echo + break; + } + // AddLog(LOG_LEVEL_INFO, PSTR("MSH: node received topic: %s"), (char*)MESH.packetToConsume.front().payload); + MESHreceiveMQTT(&MESH.packetToConsume.front()); + break; + case PACKET_TYPE_PEERLIST: + for (uint32_t i = 0; i < MESH.packetToConsume.front().chunkSize; i += 6) { + if (memcmp(MESH.packetToConsume.front().payload +i, MESH.sendPacket.sender, 6) == 0) { + continue; //do not add myself + } + if (MESHcheckPeerList(MESH.packetToConsume.front().payload +i) == false) { + MESHaddPeer(MESH.packetToConsume.front().payload +i); + } + } + break; + default: + break; + } + MESH.packetToConsume.pop(); + } +} + +void MESHEverySecond(void) { + if (MESH.role > ROLE_BROKER) { + if (MESH.flags.brokerNeedsTopic == 1) { + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Broker wants topic")); + MESHregisterNode(1); //refresh info + MESH.flags.brokerNeedsTopic = 0; + } + if (millis() - MESH.lastMessageFromBroker > 31000) { + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Broker not seen for >30 secs")); + MESHregisterNode(1); //refresh info + } + if (millis() - MESH.lastMessageFromBroker > 70000) { + AddLog(LOG_LEVEL_DEBUG, PSTR("MSH: Broker not seen for 70 secs, try to re-launch wifi")); + MESH.role = ROLE_NONE; + MESHsetWifi(1); + WifiBegin(3, MESH.channel); + } + } +} + +#endif // ESP8266 + +/*********************************************************************************************\ + * Presentation +\*********************************************************************************************/ + +void MESHshow(bool json) { + if (json) { + if (ROLE_BROKER == MESH.role) { + ResponseAppend_P(PSTR(",\"MESH\":{\"channel\":%u"), MESH.channel); + ResponseAppend_P(PSTR(",\"nodes\":%u"),MESH.peers.size()); + if (MESH.peers.size() > 0) { + ResponseAppend_P(PSTR(",\"MAC\":[")); + bool comma = false; + for (auto &_peer : MESH.peers) { + char _MAC[18]; + ToHex_P(_peer.MAC, 6, _MAC,18, ':'); + ResponseAppend_P(PSTR("%s\"%s\""), (comma)?",":"", _MAC); + comma = true; + } + ResponseAppend_P(PSTR("]")); + } + ResponseJsonEnd(); + } + } else { +#ifdef ESP32 //web UI only on the the broker = ESP32 + if (ROLE_BROKER == MESH.role) { +// WSContentSend_PD(PSTR("TAS-MESH:
")); + WSContentSend_PD(PSTR("Broker MAC %s
"), WiFi.softAPmacAddress().c_str()); + WSContentSend_PD(PSTR("Broker Channel %u
"), WiFi.channel()); + uint32_t idx = 0; + for (auto &_peer : MESH.peers) { + char _MAC[18]; + ToHex_P(_peer.MAC, 6, _MAC, 18, ':'); + WSContentSend_PD(PSTR("Node MAC %s
"), _MAC); + WSContentSend_PD(PSTR("Node last message %u ms
"), millis() - _peer.lastMessageFromPeer); + WSContentSend_PD(PSTR("Node MQTT topic %s"), _peer.topic); +/* + WSContentSend_PD(PSTR("Node MQTT topic: %s
"), _peer.topic); + if (MESH.lastTeleMsgs.size() > idx) { + char json_buffer[MESH.lastTeleMsgs[idx].length() +1]; + strcpy(json_buffer, (char*)MESH.lastTeleMsgs[idx].c_str()); + JsonParser parser(json_buffer); + JsonParserObject root = parser.getRootObject(); + for (auto key : root) { + JsonParserObject subObj = key.getValue().getObject(); + if (subObj) { + WSContentSend_PD(PSTR("
    %s:"), key.getStr()); + for (auto subkey : subObj) { + WSContentSend_PD(PSTR("
      %s: %s
    "), subkey.getStr(), subkey.getValue().getStr()); + } + WSContentSend_PD(PSTR("
")); + } else { + WSContentSend_PD(PSTR("
    %s: %s
"), key.getStr(), key.getValue().getStr()); + } + } +// AddLog(LOG_LEVEL_INFO,PSTR("MSH: teleJSON %s"), (char*)MESH.lastTeleMsgs[idx].c_str()); +// AddLog(LOG_LEVEL_INFO,PSTR("MSH: stringsize: %u"),MESH.lastTeleMsgs[idx].length()); + } else { +// AddLog(LOG_LEVEL_INFO,PSTR("MSH: telemsgSize: %u"),MESH.lastTeleMsgs.size()); + } +*/ + WSContentSend_PD(PSTR("
")); + idx++; + } + } +#endif // ESP32 + } +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +const char kMeshCommands[] PROGMEM = "Mesh|" // Prefix + "Broker|Node|Peer|Channel|Interval"; + +void (* const MeshCommand[])(void) PROGMEM = { + &CmndMeshBroker, &CmndMeshNode, &CmndMeshPeer, &CmndMeshChannel, &CmndMeshInterval }; + +void CmndMeshBroker(void) { + MESH.channel = WiFi.channel(); // The Broker gets the channel from the router, no need to declare it with MESHCHANNEL (will be mandatory set it when ETH will be implemented) + MESHstartBroker(); + ResponseCmndNumber(MESH.channel); +} + +void CmndMeshNode(void) { + if (XdrvMailbox.data_len > 0) { + MESHHexStringToBytes(XdrvMailbox.data, MESH.broker); + if (XdrvMailbox.index != 0) { XdrvMailbox.index = 1; } // Everything not 0 is a full node + // meshnode FA:KE:AD:DR:ES:S1 + bool broker = false; + char EspSsid[11]; + String mac_address = XdrvMailbox.data; + snprintf_P(EspSsid, sizeof(EspSsid), PSTR("ESP_%s"), mac_address.substring(6).c_str()); + int32_t getWiFiChannel(const char *EspSsid); + if (int32_t ch = WiFi.scanNetworks()) { + for (uint8_t i = 0; i < ch; i++) { + if (!strcmp(EspSsid, WiFi.SSID(i).c_str())) { + MESH.channel = WiFi.channel(i); + broker = true; + AddLog(LOG_LEVEL_INFO, PSTR("MSH: Successfully connected to Mesh Broker using MAC %s as %s on channel %d"), + XdrvMailbox.data, EspSsid, MESH.channel); + MESHstartNode(MESH.channel, XdrvMailbox.index); + ResponseCmndNumber(MESH.channel); + } + } + } + if (!broker) { + AddLog(LOG_LEVEL_INFO, PSTR("MSH: No Mesh Broker found using MAC %s"), XdrvMailbox.data); + } + } +} + +void CmndMeshPeer(void) { + if (XdrvMailbox.data_len > 0) { + uint8_t _MAC[6]; + MESHHexStringToBytes(XdrvMailbox.data, _MAC); + char _peerMAC[18]; + ToHex_P(_MAC, 6, _peerMAC, 18, ':'); + AddLog(LOG_LEVEL_DEBUG,PSTR("MSH: MAC-string %s (%s)"), XdrvMailbox.data, _peerMAC); + MESHaddPeer(_MAC); + MESHcountPeers(); + ResponseCmndChar(_peerMAC); + } +} + +void CmndMeshChannel(void) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 14)) { + MESH.channel = XdrvMailbox.payload; + } + ResponseCmndNumber(MESH.channel); +} + +void CmndMeshInterval(void) { + if ((XdrvMailbox.payload > 1) && (XdrvMailbox.payload < 201)) { + MESH.interval = XdrvMailbox.payload; // 2 to 200 ms + MESHsetSleep(); + } + ResponseCmndNumber(MESH.interval); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv57(uint8_t function) { + bool result = false; + + switch (function) { + case FUNC_COMMAND: + result = DecodeCommand(kMeshCommands, MeshCommand); + break; + case FUNC_PRE_INIT: + MESHInit(); // TODO: save state + break; + } + if (MESH.role) { + switch (function) { + case FUNC_LOOP: + static uint32_t mesh_transceive_msecond = 0; // State 50msecond timer + if (TimeReached(mesh_transceive_msecond)) { + SetNextTimeInterval(mesh_transceive_msecond, MESH.interval); + MESHevery50MSecond(); + } + break; + case FUNC_EVERY_SECOND: + MESHEverySecond(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MESHshow(0); + break; +#endif + case FUNC_JSON_APPEND: + MESHshow(1); + break; +#ifdef ESP32 + case FUNC_MQTT_SUBSCRIBE: + MESHconnectMQTT(); + break; +#endif // ESP32 + case FUNC_SHOW_SENSOR: + MESHsendPeerList(); // Sync this to the Teleperiod with a delay + break; +#ifdef USE_DEEPSLEEP + case FUNC_SAVE_BEFORE_RESTART: + MESHdeInit(); + break; +#endif // USE_DEEPSLEEP + } + } + return result; +} + +#endif // USE_TASMESH diff --git a/tools/mqtt-file/Config_demo_9.5.0.1.dmp b/tools/mqtt-file/Config_demo_9.5.0.1.dmp deleted file mode 100644 index b265d202924829477a7d5c88c89f77bb313f015a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeH~i8~Z{9L5<(ZbfWDlw0GNYKV%JEE1V%9777T70Oj@&QOV=sc0q2kt0_bQ;u;T zF)8{WF%)^xQ!mw8fkE+Vc%PP}S zuo!H5e0N1vQGRbJ=4MPZVURL(Ej% z{sVgY-x?e=JY@8pv5Dy?`t6T9oH*&|^e6f+2L*>*xq2=1dRX|X{+qXMM@8TH@jt5{ z2vn2RF*G(XHZs_&vrjicUPwLq;K%xdpfFevGzxBSZ^zQFd-fOoFjtYY?jQ62jDAGY z=lG-Js%rpAbvVvbFJD>sG&j>vx8t!N@gE^CviOrmXIA+yPEC=Rv&$_00cC6XALbPh z^bm{^{XKuCjI10$@fZG)@4lRWQC4wAX?l5T<-?Gqs|lg^!{U&!kuj({x1yqM{m%cN z(f^VEVR@OK^M9Xan>qp5p}Ok-p1$r+{ZE?`6yWFMoujk-&tR;4k^kSU`oHrZ^nYA9 zDgt?fCwi^_(X03G%%5Qox3$T3<3JbjTrc}q?!PPiN7Q=vFRTO72L)`^`0w5S;@{4{ zT9B;JmQLS)A^&bfMj*po4r~+IA|Oug92*+{H2)&bU+}6dDaN-=vhv^0H{iKzd0|05 zbfEo*)?mI78q&;`#JxigqhX+f4{$NP5xI4^smW( zE=~>rJKGAAvAneSVPSr5c4nH6NR;;e`Tj>oe#`$HO?d@j^}n9~U~nK91l%mlA;fM3 z;4zVq;br5L-e3vf7Gbjk$ViJ@2}=m^oqRM;o}B7W%cKs|21z%U#tNU#Gu|&Q++)l$ zXXs!5zyI<7FD4eqD=DgQL{%GPV{>RfP-&lvzQ#TUIqhSby4LIx{ATv>o%*{w>oU`7 z8lS&>k&VMWdEHpsS>97$lbK%7)C+kAK8 zecY8K6bhA)H#bM8Gbw}P@yWFLi52=2dhGq^`1tO2NDLT6+Q=*T=2ffM7Oxp?X?ZU8 z4o3;;HozmEMlUDM`alX>t#5G%mnNrz9K_V${hcpMZ4El_=k4t%B!Jk+#s@gXzHM7jusgpPH$X~S z65t~x?k^}U>>g~SEvEsJH8fUJQkUOhzJ0r;g0?=$R!>gN%*0lC?`{oTdo2-9tj%nC zk;9;_ROJ=cxA)bwYz-cznq0@Z7d^e*T|L!JMeW3rrk<9(%F?-h+T1`$7-@(~p`b|pw-+eGSFXp>>64?g zboyx0((QqmtFd!Kq$FfmQp5-?GU;w&;@AHBwg0cTe~@ebzm1#3zhwU??2uDbQdCe~ zZ~qK$NI=V|@R<6DE@Zw{J@%FqYXXPZiNdS|E$yf6e%(JVfj5ul24}6a|NgB1p8n(g zSNV_ZKYm_e-ap!Zue*s|ogM82!-@lt_4Z#$F}8@+Po6!+