From 90ee6f3995bcd468ef6e7f6c21451b24238fad39 Mon Sep 17 00:00:00 2001 From: Staars Date: Mon, 19 Oct 2020 19:03:47 +0200 Subject: [PATCH] tasmota EPS-Now mesh driver --- tasmota/support_tasmesh.ino | 283 +++++++++++++++++ tasmota/xdrv_02_mqtt.ino | 15 + tasmota/xdrv_44_tasmesh.ino | 613 ++++++++++++++++++++++++++++++++++++ 3 files changed, 911 insertions(+) create mode 100644 tasmota/support_tasmesh.ino create mode 100644 tasmota/xdrv_44_tasmesh.ino diff --git a/tasmota/support_tasmesh.ino b/tasmota/support_tasmesh.ino new file mode 100644 index 000000000..5b6336068 --- /dev/null +++ b/tasmota/support_tasmesh.ino @@ -0,0 +1,283 @@ +/* + support_tasmesh.ino - mesh via ESP-Now support for Sonoff-Tasmota + + Copyright (C) 2020 Theo Arends & Christian Baars + + 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 Date Action Description + -------------------------------------------------------------------------------------------- +*/ + +#define USE_TASMESH +#ifdef USE_TASMESH + +#include +#include + +#ifdef ESP32 +#include +#include +#else +#include //ESP8266 ... why Espressif, why?? +#endif //ESP32 + +#define MESH_PAYLOAD_SIZE 160 // default 180 - with header of 20 bytes, stays at 200 bytes, which is reported to work with ESP8266 +#define MESH_TOPICSZ 64 // max supported topic size +#define MESH_BUFFERS 4 // max buffers number for splitted messages + +struct mesh_packet_t{ + uint8_t sender[6]; //MAC + uint8_t receiver[6]; //MAC + uint32_t counter:4; //endless 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{ + uint8_t sender[6]; //MAC + uint8_t receiver[6]; //MAC + uint32_t counter:4; //endless 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 lmfp; //time of last message from peer +#ifdef ESP32 + char topic[MESH_TOPICSZ]; +#endif //ESP32 +}; + +struct mesh_broker_flags_t{ + uint8_t brokerNeedsTopic:1; +}; + +struct mesh_packet_combined_t{ + mesh_packet_header_t header; + uint8_t receivedChunks; + char raw[MESH_PAYLOAD_SIZE*6]; +}; + +struct{ + uint8_t broker[6]; + uint8_t role; + uint8_t channel; //Wifi channel + uint8_t counter; //for every message + uint32_t lmfb; //time of last message from broker + uint32_t lmfap; //time of last message from any peer + uint8_t pmk[32]; + mesh_broker_flags_t flags; + mesh_packet_t sendPacket; + std::vector peers; + std::queue packetToResend; + std::queue packetToConsume; + std::vector packetsAlreadySended; + std::vector multiPackets; +}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_Role { + ROLE_NONE = 0, // not initialized + ROLE_BROKER, // ESP32 will connect mesh to WLAN + ROLE_NODE_SMALL, // Node will only talk to the broker + ROLE_NODE_FULL // Node will listen and resend every message for MESH functionality +}; + +enum MESH_Packet_Type { // Type of packet + PACKET_TYPE_TIME = 0, // + PACKET_TYPE_SENSOR, // + PACKET_TYPE_COMMAND, // + PACKET_TYPE_TOPIC, // announce mqtt topic to ESP32-proxy + PACKET_TYPE_MQTT // +}; + +/*********************************************************************************************\ + * +\*********************************************************************************************/ +#ifdef ESP32 +void MESHsendTime(uint32_t _peerNumber){ //only from broker to nodes + MESH.sendPacket.counter++; + MESH.sendPacket.type = PACKET_TYPE_TIME; + MESH.sendPacket.TTL = 3; + memcpy(MESH.sendPacket.receiver,MESH.peers[_peerNumber].MAC,6); + MESH.sendPacket.senderTime = Rtc.utc_time; + MESH.sendPacket.payload[0] = 0; + mesh_broker_flags_t *_flags = (mesh_broker_flags_t *)MESH.sendPacket.payload; + if(MESH.peers[_peerNumber].topic[0]==0){ + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: broker wants topic from peer: %u"), _peerNumber); + _flags->brokerNeedsTopic = 1; + } + MESH.sendPacket.chunkSize = 0; + MESHsendPacket(&MESH.sendPacket); + // esp_now_send(MESH.sendPacket.receiver, (uint8_t *)&MESH.sendPacket, sizeof(MESH.sendPacket)-MESH_PAYLOAD_SIZE+1); +} +#endif //ESP32 + +void MESHcheckPeerList(const uint8_t *MAC){ + for(auto &_peer : MESH.peers){ + if(memcmp(_peer.MAC,MAC,6)==0){ + _peer.lmfp = millis(); + return; + } + } + MESHaddPeer((uint8_t *)MAC); +} + + +void MESHcountPeers(void){ + #ifdef ESP32 + esp_now_peer_num_t _num; + esp_now_get_peer_num(&_num); + AddLog_P2(LOG_LEVEL_INFO, PSTR("TAS-MESH peers: %u"),_num.total_num); +#else + uint8_t _num; + uint8_t _numEnc; + esp_now_get_cnt_info(&_num,&_numEnc); + AddLog_P2(LOG_LEVEL_INFO, PSTR("TAS-MESH peers: %u"),_num); +#endif +} + + +void MESHaddPeer(uint8_t *_MAC ){ + mesh_peer_t _newPeer; + memcpy(_newPeer.MAC,_MAC,6); + _newPeer.lmfp = millis(); +#ifdef ESP32 + _newPeer.topic[0] = 0; +#endif + MESH.peers.push_back(_newPeer); + int err; +#ifdef ESP32 + esp_now_peer_info_t _peer; + _peer.channel = WiFi.channel(); + _peer.encrypt = false; + _peer.ifidx = 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 + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: peer added, err: %d"), err); + AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t *)_MAC,6); +#ifdef ESP32 + if(MESH.role == ROLE_BROKER) MESHsendTime(MESH.peers.size()-1); +#endif //ESP32 +} + +//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 MESHMACStringToBytes(char* _string,uint8_t _MAC[]) { //uppercase + uint32_t index = 0; + uint32_t _end = 12; + 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); +} + +void MESHsetPMK(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_P2(LOG_LEVEL_DEBUG, PSTR("MESH: set crypto key to PASSWORD1")); +} + + +bool MESHencryptPayload(mesh_packet_t *_packet, int _encrypt){ + +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: will encrypt: %u"), _encrypt); + +size_t _size = _packet->chunkSize; +char _tag[16]; + +// AddLog_P2(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.pmk, (const void *)_packet, +(void *)_packet->payload, _size, (void*)&_packet->senderTime, 4, +_tag, bc, _encrypt); + +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: encryption done ")); + +if(_encrypt==1){ + memcpy(_packet->tag,_tag,16); + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: payload encrypted")); + return true; +} +if(memcmp(_packet->tag,_tag,16)==0){ + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: payload decrypted")); + return true; +} +AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: payload decryption error")); +return false; +} + +#endif //USE_TASMESH diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index 58fd04c2c..70d83df77 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -214,6 +214,13 @@ bool MqttPublishLib(const char* topic, bool retained) mqtt_cmnd_blocked++; } } +#ifdef USE_TASMESH +if(MESH.role == ROLE_NODE_SMALL){ + MESHredirectMQTT(topic, mqtt_data, retained); + yield(); + return true; +} +#endif //USE_TASMESH bool result = MqttClient.publish(topic, mqtt_data, retained); yield(); // #3313 @@ -245,6 +252,14 @@ void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len char data[data_len +1]; memcpy(data, mqtt_data, sizeof(data)); +#ifdef USE_TASMESH +#ifdef ESP32 +if(MESH.role == ROLE_BROKER){ + if (MESHinterceptMQTT(topic, (uint8_t*)data, data_len+1)) return; +} +#endif //ESP32 +#endif //USE_TASMESH + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_MQTT D_RECEIVED_TOPIC " \"%s\", " D_DATA_SIZE " %d, " D_DATA " \"%s\""), topic, data_len, data); // if (LOG_LEVEL_DEBUG_MORE <= seriallog_level) { Serial.println(data); } diff --git a/tasmota/xdrv_44_tasmesh.ino b/tasmota/xdrv_44_tasmesh.ino new file mode 100644 index 000000000..c88cfce31 --- /dev/null +++ b/tasmota/xdrv_44_tasmesh.ino @@ -0,0 +1,613 @@ +/* + xdrv_44_tasmesh.ino - Mesh support for Tasmota using ESP-Now + + Copyright (C) 2020 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 . + + -------------------------------------------------------------------------------------------- + Version yyyymmdd Action Description + -------------------------------------------------------------------------------------------- + 0.9.0.0 20200927 started - from scratch + +*/ + + +#ifdef USE_TASMESH + +/*********************************************************************************************\ +* Build a mesh of nodes using ESP-Now +* Connect it through an ESP32-broker to WLAN +\*********************************************************************************************/ + +#define XDRV_44 44 + +/*********************************************************************************************\ + * constants +\*********************************************************************************************/ + +#define D_CMND_MESH "MESH" + +const char S_JSON_MESH_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MESH "%s\":%d}"; +const char S_JSON_MESH_COMMAND[] PROGMEM = "{\"" D_CMND_MESH "%s\"}"; +const char kMESH_Commands[] PROGMEM = "Broker|Node|Peer|Channel"; + +/*********************************************************************************************\ + * 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 + +/*********************************************************************************************\ + * 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) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BROKER sended packet")); +} + +void CB_MESHDataReceived(const uint8_t *MAC, const uint8_t *packet, int len) { + MESH.lmfap = millis(); + MESHcheckPeerList((const uint8_t *)MAC); + mesh_packet_t *_recvPacket = (mesh_packet_t*)packet; + MESH.packetToConsume.push(*_recvPacket); +} +#else //ESP8266 +void CB_MESHDataSent( uint8_t *MAC, uint8_t 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.lmfb = millis(); //directly from the broker + mesh_packet_t *_recvPacket = (mesh_packet_t*)packet; + if(memcmp(_recvPacket->receiver,MESH.sendPacket.sender,6)!=0){ //MESH.sendPacket.sender simply stores the MAC of the node + //pass packet back to the MESH + _recvPacket->peerIndex = 0; + MESH.packetToResend.push(*_recvPacket); + return; + } + switch(_recvPacket->type){ + case PACKET_TYPE_TIME: + Rtc.utc_time = _recvPacket->senderTime; + Rtc.user_time_entry = true; + memcpy((uint8_t*)&MESH.flags,_recvPacket->payload,1); + break; + default: + MESH.packetToConsume.push(*_recvPacket); + break; + } +} +#endif //ESP32 + +/*********************************************************************************************\ + * init driver +\*********************************************************************************************/ + +void MESHInit(void) { + MESH.role == ROLE_NONE; + AddLog_P2(LOG_LEVEL_INFO, PSTR("TAS-MESH initialized: %u"),Settings.tele_period); + + MESH.sendPacket.counter = 0; + MESH.sendPacket.chunks = 1; + MESH.sendPacket.chunk = 0; + MESH.sendPacket.type = PACKET_TYPE_TIME; + MESH.sendPacket.TTL = 2; +} + +/*********************************************************************************************\ + * 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_P2(LOG_LEVEL_INFO, PSTR("MESH: 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 MESHinterceptMQTT(char* _topic, uint8_t* _data, unsigned int data_len){ + char stopic[TOPSZ]; + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: 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; + memcpy(MESH.sendPacket.payload,_topic,MESH.sendPacket.chunkSize); + memcpy(MESH.sendPacket.payload+MESH.sendPacket.chunkSize,_data,data_len); + MESH.sendPacket.chunkSize += data_len; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MESH: 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_P2(LOG_LEVEL_INFO, PSTR("MESH: multiple chunks: %u"),_packet->chunks); + // TODO: reconstruct message in buffer + } +} +#endif //ESP32 + +/** + * @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 MESHredirectMQTT(const char* _topic, char* _data, bool _retained){ + 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_P2(LOG_LEVEL_INFO, PSTR("MESH: 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; + _bytesLeft -= _topicSize; + _byteLeftInChunk -= _topicSize; + // AddLog_P2(LOG_LEVEL_INFO, PSTR("topic in payload %s"),(char*)MESH.sendPacket.payload); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: after topic -> chunk:%u, pre-size: %u"),MESH.sendPacket.chunk,MESH.sendPacket.chunkSize); + } + if(_byteLeftInChunk>0){ + if(_byteLeftInChunk>_bytesLeft){ + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: only last chunk bL:%u bLiC:%u oSD:%u"),_bytesLeft,_byteLeftInChunk,_offsetData); + _byteLeftInChunk = _bytesLeft; + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: only last chunk after correction -> chunk:%u, pre-size: %u"),MESH.sendPacket.chunk,MESH.sendPacket.chunkSize); + } + if(MESH.sendPacket.chunk>0) _topicSize = 0; + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: %u"),_offsetPayload); + memcpy(MESH.sendPacket.payload + _topicSize, _data + _offsetData,_byteLeftInChunk); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("data in payload %s"),(char*)MESH.sendPacket.payload + _offsetPayload); + _offsetData += _byteLeftInChunk; + _bytesLeft -= _byteLeftInChunk; + } + MESH.sendPacket.chunkSize += _byteLeftInChunk; + MESH.packetToResend.push(MESH.sendPacket); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: 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_P2(LOG_LEVEL_INFO, PSTR("MESH: too many chunks: %u"),MESH.sendPacket.chunk+1); + } + MESH.sendPacket.chunk++; + MESH.sendPacket.chunkSize = 0; + } + //send to pipeline + return true; +} + +/** + * @brief The node sends its mqtt topic to the broker + * + */ +void MESHanounceTopic(){ + memset(MESH.sendPacket.payload,0,MESH_PAYLOAD_SIZE); + strcpy((char*)MESH.sendPacket.payload,mqtt_topic); + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: topic: %s"),(char*)MESH.sendPacket.payload); + MESH.sendPacket.chunkSize = strlen((char*)MESH.sendPacket.payload) + 1; + MESH.sendPacket.type = PACKET_TYPE_TOPIC; + MESHsendPacket(&MESH.sendPacket); + // int result = esp_now_send(MESH.sendPacket.receiver, (uint8_t *)&MESH.sendPacket, (sizeof(MESH.sendPacket))-(MESH_PAYLOAD_SIZE-MESH.sendPacket.chunkSize-1)); +} + +/*********************************************************************************************\ + * generic functions +\*********************************************************************************************/ + +void MESHstartNode(int32_t _channel){ + MESH.channel = _channel; + WiFi.mode(WIFI_STA); + WiFi.begin("","",MESH.channel, nullptr, false); //fake connection attempt to set channel + WiFi.disconnect(); + if (esp_now_init() != 0) { + return; + } + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: Node initialized, channel: %u"),MESH.channel); +#ifdef ESP8266 + esp_now_set_self_role(ESP_NOW_ROLE_COMBO); +#endif //ESP8266 + + esp_now_register_send_cb(CB_MESHDataSent); + esp_now_register_recv_cb(CB_MESHDataReceived); + MESHsetPMK(MESH.pmk); + memcpy(MESH.sendPacket.receiver,MESH.broker,6); + WiFi.macAddress(MESH.sendPacket.sender); + MESHaddPeer(MESH.broker); //must always be peer 0!! + MESHcountPeers(); + MESH.role = ROLE_NODE_SMALL; + MESHanounceTopic(); +} + +void MESHstartBroker(){ +#ifdef ESP32 + WiFi.mode(WIFI_AP_STA); + // WiFi.softAP("SSID_NOW","PASSWORD_NOW",9,1); + // AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)WiFi.softAPmacAddress(),6); + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: Broker MAC: %s"),WiFi.softAPmacAddress().c_str()); + WiFi.softAPmacAddress(MESH.broker); + + uint32_t _channel = WiFi.channel(); + esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE); + esp_wifi_set_promiscuous(false); + + if (esp_now_init() != 0) { + return; + } + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: Broker initialized on channel: %u"), _channel); + esp_now_register_send_cb(CB_MESHDataSent); + esp_now_register_recv_cb(CB_MESHDataReceived); + MESHsetPMK(MESH.pmk); + MESHcountPeers(); + memcpy(MESH.sendPacket.sender,MESH.broker,6); + MESH.role = ROLE_BROKER; +#endif //ESP32 +} + +/*********************************************************************************************\ + * main loops +\*********************************************************************************************/ +#ifdef ESP32 + +void MESHevery50MSecond(){ + // if(MESH.packetToResend.size()>0){ + // // pass the packets + // } + if(MESH.packetToConsume.size()>0){ + // do something on the node + MESHencryptPayload(&MESH.packetToConsume.front(),0); + switch(MESH.packetToConsume.front().type){ + case PACKET_TYPE_TOPIC: + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: received topic: %s"), (char*)MESH.packetToConsume.front().payload); + 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); + MESHsubscribe((char*)&_peer.topic); + } + } + break; + // case PACKET_TYPE_SENSOR: + // for(auto &_peer : MESH.peers){ + // if(memcmp(_peer.MAC,MESH.packetToConsume.front().sender,6)==0){ + // // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: received sensor output: %s"), (char*)MESH.packetToConsume.front().payload); + // char stopic[MESH_TOPICSZ]; + // GetTopic_P(stopic, TELE, _peer.topic, PSTR("SENSOR")); + // MqttClient.publish(stopic, (char*)MESH.packetToConsume.front().payload); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: topic: %s output: %s"), stopic, (char*)MESH.packetToConsume.front().payload); + // yield(); // #3313 + // break; + // } + // } + case PACKET_TYPE_MQTT: // redirected MQTT from node in packet [char* _space_ char*] + { + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: 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_P2(LOG_LEVEL_INFO, PSTR("MESH: 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_P2(LOG_LEVEL_INFO, PSTR("MESH: recChunks= %u"),_packet_combined.receivedChunks); + } + } + uint32_t _temp = (1 << (uint8_t)MESH.packetToConsume.front().chunks)-1 ; //example: 1+2+4 == (2^3)-1 + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: _temp: %u = %u"),_temp,_packet_combined.receivedChunks); + if(_packet_combined.receivedChunks==_temp){ + char * _data = (char*)_packet_combined.raw + strlen((char*)_packet_combined.raw) + 1; + MqttClient.publish((char*)_packet_combined.raw, _data); + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: combined done: %s = %s"),(char*)_packet_combined.raw,_data); + // AddLogBuffer(LOG_LEVEL_INFO,(uint8_t*)_packet_combined.raw,50); + } + } + 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_P2(LOG_LEVEL_INFO, PSTR("new multipacket with chunks: %u"),_packet.header.chunks); + } + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: no support yet for multiple chunks: %u"),MESH.packetToConsume.front().chunks); + break; + } + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: 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; + MqttClient.publish((char*)MESH.packetToConsume.front().payload, _data); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: topic: %s output: %s"), (char*)MESH.packetToConsume.front().payload, _data); + // AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)&MESH.packetToConsume.front().payload,MESH.packetToConsume.front().chunkSize); + yield(); // #3313 + } + break; + default: + AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)&MESH.packetToConsume.front(),MESH.packetToConsume.front().chunkSize+5); + break; + } + MESH.packetToConsume.pop(); + } +} + +void MESHEverySecond(){ + static uint32_t _second = 0; + _second++; + // send a time packet every x seconds + uint32_t _peerNumber = _second%60; + if(_peerNumber3){ + AddLog_P2(LOG_LEVEL_INFO, PSTR("MESH: multi packets in buffer: %u"),MESH.multiPackets.size()); + MESH.multiPackets.erase(MESH.multiPackets.begin()); + } +} + +#else //ESP8266 +void MESHevery50MSecond(){ + if(MESH.packetToResend.size()>0){ + uint32_t _tempIndex = MESH.packetToResend.front().peerIndex; + if(MESH.peers.size()>_tempIndex){ + MESH.packetToResend.front().senderTime = Rtc.utc_time; + 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 TODO: guarantee that peer[0] is always the broker + MESHsendPacket(&MESH.packetToResend.front()); + // int result = esp_now_send(MESH.packetToResend.front().receiver, (uint8_t *)&MESH.packetToResend.front(), (sizeof(MESH.sendPacket))-(MESH_PAYLOAD_SIZE-MESH.packetToResend.front().chunkSize)); + } + } + MESH.packetToResend.front().peerIndex = _tempIndex + 1; + } + 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_P2(LOG_LEVEL_INFO, PSTR("MESH: node received topic: %s"), (char*)MESH.packetToConsume.front().payload); + MESHreceiveMQTT(&MESH.packetToConsume.front()); + break; + default: + break; + } + MESH.packetToConsume.pop(); + } +} + + +void MESHEverySecond(){ + static uint32_t _second = 0; + static uint32_t _tele_period = Settings.tele_period; + if (MESH.role == ROLE_NODE_SMALL){ + _tele_period--; + if(_tele_period == 0){ + // uint8_t broadcastAddress[6] = {0x30,0xAE,0xA4,0x26,0xE7,0x29}; + // memcpy(MESH.sendPacket.receiver,MESH.broker,6); + // _tele_period = Settings.tele_period; + // mqtt_data[0] = 0; + + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("SENSOR: %s %u"), mqtt_data, strlen(mqtt_data)); + // MESH.sendPacket.chunkSize = strlen(mqtt_data); + // memcpy(MESH.sendPacket.payload,mqtt_data,MESH.sendPacket.chunkSize); + // MESH.sendPacket.type = PACKET_TYPE_SENSOR; + // MESH.sendPacket.senderTime = Rtc.utc_time; + // int result = esp_now_send(MESH.sendPacket.receiver, (uint8_t *)&MESH.sendPacket, (sizeof(MESH.sendPacket))-(MESH_PAYLOAD_SIZE-MESH.sendPacket.chunkSize)); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("send error: %d, tele: %u"), result, tele_period); + } + if(MESH.flags.brokerNeedsTopic == 1){ + MESHanounceTopic(); + MESH.flags.brokerNeedsTopic = 0; + } + } +} +#endif //ESP8266 + +/*********************************************************************************************\ + * presentation +\*********************************************************************************************/ +void MESHshow(bool json){ + if (json) { + if(MESH.role != ROLE_NONE){ + if(MESH.role != ROLE_BROKER) ResponseAppend_P(PSTR(",\"MESH\":{\"broker\":%u"),MESH.channel); + else ResponseAppend_P(PSTR(",\"MESH\":{\"node\":%u"),MESH.channel); + ResponseJsonEnd(); + } + } else { +#ifdef ESP32 + if(MESH.role == ROLE_BROKER){ + WSContentSend_PD(PSTR("TAS-MESH:
")); + WSContentSend_PD(PSTR("Broker MAC: %s
"),WiFi.softAPmacAddress().c_str()); + WSContentSend_PD(PSTR("Broker Channel: %u
"),WiFi.channel()); + 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
"),_peer.lmfp); + WSContentSend_PD(PSTR("Node MQTT topic: %s
"),_peer.topic); + } + } +#endif //ESP32 + } +} + + +/*********************************************************************************************\ + * check the MESH commands +\*********************************************************************************************/ + +bool MESHCmd(void) { + char command[CMDSZ]; + bool serviced = true; + uint8_t disp_len = strlen(D_CMND_MESH); + + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_MESH), disp_len)) { // prefix + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kMESH_Commands); + + switch (command_code) { + case CMND_MESH_BROKER: + MESHstartBroker(); + Response_P(S_JSON_MESH_COMMAND_NVALUE, command, MESH.channel); + break; + case CMND_MESH_NODE: + if (XdrvMailbox.data_len > 0) { + MESHstripColon(XdrvMailbox.data); + MESHMACStringToBytes(XdrvMailbox.data,MESH.broker); + MESHstartNode(MESH.channel); + Response_P(S_JSON_MESH_COMMAND_NVALUE, command, MESH.channel); + } + break; + case CMND_MESH_CHANNEL: + if (XdrvMailbox.data_len > 0) { + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("channel: %u"), XdrvMailbox.payload); + MESH.channel = XdrvMailbox.payload; + } + break; + case CMND_MESH_PEER: + if (XdrvMailbox.data_len > 0) { + uint8_t _MAC[6] = {0}; + MESHstripColon(XdrvMailbox.data); + MESHMACStringToBytes(XdrvMailbox.data,_MAC); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("MAC-string: %s"), XdrvMailbox.data); + // AddLogBuffer(LOG_LEVEL_INFO,(uint8_t *)_MAC,6); + MESHaddPeer(_MAC); + MESHcountPeers(); + } + break; + default: + // else for Unknown command + serviced = false; + break; + } + } else { + return false; + } + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv44(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_PRE_INIT: + MESHInit(); // TODO: save state + break; + case FUNC_EVERY_50_MSECOND: + MESHevery50MSecond(); + break; + case FUNC_EVERY_SECOND: + MESHEverySecond(); + break; + case FUNC_COMMAND: + result = MESHCmd(); + break; + case FUNC_WEB_SENSOR: +#ifdef USE_WEBSERVER + MESHshow(0); +#endif + break; + case FUNC_JSON_APPEND: + MESHshow(1); + break; +#ifdef ESP32 + case FUNC_MQTT_SUBSCRIBE: + MESHconnectMQTT(); + break; +#endif //ESP32 + // case FUNC_SHOW_SENSOR: + // if(MESH.role == ROLE_NODE_SMALL) MESHsendTeleSensor(); + // break; + } +return result; +} + +#endif // USE_TASMESH