Add initial support for Tasmota Mesh

Add initial support for Tasmota Mesh (TasMesh) providing node/broker communication using ESP-NOW (#11939)
This commit is contained in:
Theo Arends 2021-07-02 14:08:06 +02:00
parent f62f86aeb7
commit 16f6f26aba
9 changed files with 1315 additions and 5 deletions

View File

@ -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

View File

@ -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)

51
info/xdrv_57_tasmesh.md Normal file
View File

@ -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!!</br>``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</br>``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</br>``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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_TASMESH
#include <queue>
#include <t_bearssl_block.h>
#ifdef ESP32
#include <esp_now.h>
#include <esp_wifi.h>
#else
#include <espnow.h>
#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<mesh_peer_t> peers;
std::queue<mesh_packet_t> packetToResend;
std::queue<mesh_packet_t> packetToConsume;
std::vector<mesh_packet_header_t> packetsAlreadySended;
std::vector<mesh_first_header_bytes> packetsAlreadyReceived;
std::vector<mesh_packet_combined_t> multiPackets;
#ifdef ESP32
std::vector<std::string> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/*
--------------------------------------------------------------------------------------------
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:<br>"));
WSContentSend_PD(PSTR("<b>Broker MAC</b> %s<br>"), WiFi.softAPmacAddress().c_str());
WSContentSend_PD(PSTR("<b>Broker Channel</b> %u<hr>"), WiFi.channel());
uint32_t idx = 0;
for (auto &_peer : MESH.peers) {
char _MAC[18];
ToHex_P(_peer.MAC, 6, _MAC, 18, ':');
WSContentSend_PD(PSTR("<b>Node MAC</b> %s<br>"), _MAC);
WSContentSend_PD(PSTR("<b>Node last message</b> %u ms<br>"), millis() - _peer.lastMessageFromPeer);
WSContentSend_PD(PSTR("<b>Node MQTT topic</b> %s"), _peer.topic);
/*
WSContentSend_PD(PSTR("Node MQTT topic: %s <br>"), _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("<ul>%s:"), key.getStr());
for (auto subkey : subObj) {
WSContentSend_PD(PSTR("<ul>%s: %s</ul>"), subkey.getStr(), subkey.getValue().getStr());
}
WSContentSend_PD(PSTR("</ul>"));
} else {
WSContentSend_PD(PSTR("<ul>%s: %s</ul>"), 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("<hr>"));
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

View File

@ -94,7 +94,12 @@ def on_message(client, userdata, msg):
if "Started" in rcv_code:
return
if "Error" in rcv_code:
print("Error: "+rcv_code)
if "1" in rcv_code: print("Error: Wrong password")
else:
if "2" in rcv_code: print("Error: Bad chunk size")
else:
if "3" in rcv_code: print("Error: Invalid file type")
else: print("Error: "+rcv_code)
Err_flag = True
return
if "Command" in root:

View File

@ -87,7 +87,12 @@ def on_message(client, userdata, msg):
if "Started" in rcv_code:
return
if "Error" in rcv_code:
print("Error: "+rcv_code)
if "1" in rcv_code: print("Error: Wrong password")
else:
if "2" in rcv_code: print("Error: Bad chunk size")
else:
if "3" in rcv_code: print("Error: Invalid file type")
else: print("Error: "+rcv_code)
Err_flag = True
return
if "Command" in root:

View File

@ -44,7 +44,7 @@ broker_port = 1883 # MQTT broker port
mypassword = "" # Tasmota MQTT password
mytopic = "demo" # Tasmota MQTT topic
myfile = "Config_demo_9.5.0.1.dmp" # Tasmota Settings file name
myfile = "Config_demo_9.5.0.2.dmp" # Tasmota Settings file name
myfiletype = 2 # Tasmota Settings file type
# **** End of User Configuration Section
@ -86,7 +86,12 @@ def on_message(client, userdata, msg):
if "Started" in rcv_code:
return
if "Error" in rcv_code:
print("Error: "+rcv_code)
if "1" in rcv_code: print("Error: Wrong password")
else:
if "2" in rcv_code: print("Error: Bad chunk size")
else:
if "3" in rcv_code: print("Error: Invalid file type")
else: print("Error: "+rcv_code)
Err_flag = True
return
if "Command" in root: