├── EspNowUsb ├── EspNowUsb.ino └── README.md ├── LICENSE ├── README.md ├── arduinoSlaveNode └── main │ └── main.ino ├── gateway ├── config.js ├── index.js ├── package.json ├── parser.js ├── serialInterface.js └── simpleMqtt.js ├── pictures ├── ArduinoAdditionalURLs.png ├── ArduinoBoardManager.png ├── ArduinoSetupForEsp2866.png ├── DIYMESH.PNG ├── messageRelay.gif └── testSetup.png └── tools └── randomKey.js /EspNowUsb/EspNowUsb.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | Commands cmd; 4 | int bsid = 0x112233; 5 | 6 | #ifdef ESP32 7 | #include 8 | #else 9 | #include 10 | #endif 11 | 12 | #define ESP_NOW_CHANNEL 1 13 | //AES 128bit 14 | unsigned char secredKey[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; 15 | unsigned char iv[16] = {0xb2, 0x4b, 0xf2, 0xf7, 0x7a, 0xc5, 0xec, 0x0c, 0x5e, 0x1f, 0x4d, 0xc1, 0xae, 0x46, 0x5e, 0x75}; 16 | 17 | void espNowFloodingMeshRecv(const uint8_t *data, int len, uint32_t replyPrt) { 18 | char replyPrtStr[15]; 19 | sprintf(replyPrtStr, "%lu", replyPrt); 20 | cmd.send("REC", replyPrtStr, data, len); 21 | } 22 | 23 | void setup() { 24 | Serial.begin(115200); 25 | cmd.begin(Serial); 26 | delay(1000); 27 | //Set device in AP mode to begin with 28 | espNowFloodingMesh_RecvCB(espNowFloodingMeshRecv); 29 | 30 | espNowFloodingMesh_ErrorDebugCB([](int level, const char *str) { 31 | if (level == 0) { 32 | cmd.send("ERROR", str); 33 | } 34 | if (level == 1) { 35 | cmd.send("WRN", str); 36 | } 37 | if (level == 2) { 38 | cmd.send("INFO", str); 39 | } 40 | }); 41 | Serial.println(); 42 | Serial.println(); 43 | Serial.println(); 44 | cmd.send("READY"); 45 | } 46 | 47 | int channel = 1; 48 | char buf[20]; 49 | bool initialized = false; 50 | void loop() { 51 | espNowFloodingMesh_loop(); 52 | cmd.handleInputCommands([](const char* cmdName, const char*p1, const char*p2, const char*p3, const unsigned char*binary, int size) { 53 | 54 | if (strcmp(cmdName, "PING") == 0) { 55 | cmd.send("ACK"); 56 | } else if (strcmp(cmdName, "CHANNEL") == 0) { 57 | if (strcmp(p1, "SET") == 0) { 58 | channel = atoi(p2); 59 | cmd.send("ACK", itoa(channel, buf, 10)); 60 | } else if (strcmp(p1, "GET") == 0) { 61 | cmd.send("ACK", itoa(channel, buf, 10)); 62 | } else { 63 | cmd.send("NACK", "PARAM"); 64 | } 65 | } else if (strcmp(cmdName, "ROLE") == 0) { 66 | if (strcmp(p1, "MASTER") == 0) { 67 | int ttl = (p2 == NULL ? 0 : atoi(p2)); 68 | espNowFloodingMesh_setToMasterRole(true, ttl); 69 | cmd.send("ACK", p2); 70 | } else if (strcmp(p1, "SLAVE") == 0) { 71 | espNowFloodingMesh_setToMasterRole(false); 72 | cmd.send("ACK"); 73 | } else { 74 | cmd.send("NACK", "INVALID ROLE"); 75 | } 76 | } else if (strcmp(cmdName, "SEND") == 0) { 77 | int ttl = 0; 78 | if (p1 != 0) { 79 | ttl = atoi(p1); 80 | } 81 | espNowFloodingMesh_send((uint8_t*)binary, size, ttl); 82 | cmd.send("ACK"); 83 | } else if (strcmp(cmdName, "REQ") == 0) { 84 | int ttl = 0; 85 | if (p1 != 0) { 86 | ttl = atoi(p1); 87 | } 88 | uint32_t replyptr = espNowFloodingMesh_sendAndHandleReply((uint8_t*)binary, size, ttl, NULL); 89 | sprintf(buf, "%lu", replyptr); 90 | cmd.send("ACK", buf); 91 | } else if (strcmp(cmdName, "REPLY") == 0) { 92 | int ttl = 0; 93 | uint32_t replyPrt; 94 | if (p1 == NULL || p2 == NULL) { 95 | cmd.send("ACK", "INVALID PARAM"); 96 | } else { 97 | ttl = atoi(p1); 98 | replyPrt = Commands::sTolUint(p2); 99 | espNowFloodingMesh_sendReply((uint8_t*)binary, size, ttl, replyPrt); 100 | cmd.send("ACK", buf); 101 | } 102 | } else if (strcmp(cmdName, "STOP") == 0) { 103 | espNowFloodingMesh_end(); 104 | cmd.send("ACK"); 105 | } else if (strcmp(cmdName, "REBOOT") == 0) { 106 | cmd.send("ACK", "Rebooting"); 107 | Serial.flush(); 108 | ESP.restart(); 109 | while (1); 110 | } else if (strcmp(cmdName, "INIT") == 0) { 111 | if (initialized == false) { 112 | initialized = true; 113 | espNowFloodingMesh_secredkey(secredKey); 114 | espNowFloodingMesh_setAesInitializationVector(iv); 115 | espNowFloodingMesh_begin(channel, bsid); 116 | cmd.send("ACK"); 117 | } else { 118 | cmd.send("NACK", "REBOOT NEEDED"); 119 | } 120 | } else if (strcmp(cmdName, "RTC") == 0) { 121 | if (strcmp(p1, "GET") == 0) { 122 | time_t t = espNowFloodingMesh_getRTCTime(); 123 | sprintf(buf, "%lu", t); 124 | cmd.send("ACK", buf); 125 | } else if (strcmp(p1, "SET") == 0) { 126 | time_t t = Commands::sTolUint(p2); 127 | espNowFloodingMesh_setRTCTime(t); 128 | sprintf(buf, "%lu", t); 129 | cmd.send("ACK", buf); 130 | } else { 131 | cmd.send("NACK", "INVALID PARAMETER"); 132 | } 133 | } else if (strcmp(cmdName, "KEY") == 0) { 134 | if (strcmp(p1, "SET") == 0) { 135 | if (size == sizeof(secredKey)) { 136 | memcpy(secredKey, binary, sizeof(secredKey)); 137 | espNowFloodingMesh_secredkey(secredKey); 138 | cmd.send("ACK"); 139 | } else { 140 | cmd.send("NACK", "SIZE!=16"); 141 | } 142 | } else if (strcmp(p1, "GET") == 0) { 143 | cmd.send("ACK", secredKey, sizeof(secredKey)); 144 | } else { 145 | cmd.send("NACK", "PARAM"); 146 | } 147 | } 148 | else if (strcmp(cmdName, "MEM") == 0) { 149 | sprintf(buf, "%d", ESP.getFreeHeap()); 150 | cmd.send("ACK", buf); 151 | 152 | } 153 | else if (strcmp(cmdName, "IV") == 0) { 154 | if (strcmp(p1, "SET") == 0) { 155 | if (size == sizeof(secredKey)) { 156 | memcpy(iv, binary, sizeof(iv)); 157 | espNowFloodingMesh_setAesInitializationVector(iv); 158 | cmd.send("ACK"); 159 | } else { 160 | cmd.send("NACK", "SIZE!=16"); 161 | } 162 | } else if (strcmp(p1, "GET") == 0) { 163 | cmd.send("ACK", secredKey, sizeof(secredKey)); 164 | } else { 165 | cmd.send("NACK", "PARAM"); 166 | } 167 | } 168 | else if (strcmp(cmdName, "BSID") == 0) { 169 | if (strcmp(p1, "SET") == 0) { 170 | if (strlen(p2)>0) { 171 | bsid = atoi(p2); 172 | cmd.send("ACK"); 173 | } else { 174 | cmd.send("NACK", "INVALID SIZE"); 175 | } 176 | } else if (strcmp(p1, "GET") == 0) { 177 | sprintf(buf, "%ud", bsid); 178 | cmd.send("ACK", buf); 179 | } else { 180 | cmd.send("NACK", "PARAM"); 181 | } 182 | } else if (strcmp(cmdName, "MAC") == 0) { 183 | String mac = WiFi.macAddress(); 184 | cmd.send("ACK", (const unsigned char *)mac.c_str(), 6); 185 | } 186 | else { 187 | cmd.send("NACK", "INVALID COMMAND"); //Handle invalid command 188 | } 189 | }); 190 | } 191 | -------------------------------------------------------------------------------- /EspNowUsb/README.md: -------------------------------------------------------------------------------- 1 | # Arduino EspNow flooding mesh network UsbAdapter 2 | 3 | 4 | ###### Arduino libraries: 5 | - https://github.com/arttupii/espNowFloodingMeshLibrary 6 | - https://github.com/arttupii/ArduinoCommands 7 | - https://github.com/arttupii/SimpleMqttLibrary 8 | - https://github.com/kakopappa/arduino-esp8266-aes-lib (Only ESP2866) 9 | 10 | 11 | ##### Installation 12 | 0. Install Arduino and following dependencies: 13 | - https://github.com/arttupii/espNowFloodingMeshLibrary 14 | - https://github.com/arttupii/ArduinoCommands 15 | - https://github.com/arttupii/SimpleMqttLibrary 16 | - https://github.com/kakopappa/arduino-esp8266-aes-lib (Only ESP2866) 17 | 1. Install esp8266 dev module. Use git version. 18 | - Instructions: https://github.com/esp8266/Arduino 19 | 2. Check Espressif FW version!!!. It should be nonos-sdk 2.2.1+100(testing). 20 | ![alt text](https://github.com/arttupii/EspNowFloodingMesh/blob/master/pictures/ArduinoSetupForEsp2866.png) 21 | 3. Add "https://dl.espressif.com/dl/package_esp32_dev_index.json" into the Additional Board Manager URLs field. 22 | ![alt text](https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/master/pictures/ArduinoAdditionalURLs.png) 23 | 4. Install esp32 dev module 1.0.3-rc1 from Arduino's Boards Manager. 24 | ![alt text](https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/master/pictures/ArduinoBoardManager.png) 25 | 5. Flash Usb adapter software (EspNowUsb/EspNowUsb.ino) on esp32/esp2866 (esp32 is the best choice.). (You don't need to change any parameters) 26 | - https://github.com/arttupii/EspNowFloodingMesh/tree/master/EspNowUsb 27 | 28 | 29 | #### Example messages (USBAdapter) 30 | ##### Initialize mesh network 31 | ``` 32 | ROLE MASTER; 34 | IV SET [10,31,42,53,46,15,36,57,83,19,11,55,14,33,24,51]; 36 | KEY SET [00,11,22,33,44,55,66,77,88,99,AA,BB,CC,DD,EE,FF]; 38 | CHANNEL SET 1; 40 | INIT; 42 | ROLE MASTER 3; 48 | REBOOT; 53 | SEND 3 [11,22,33,44,55,66]; 59 | REQ 3 [11,22,33,44,55,66]; 64 | REPLY 3 2314 [11,22,33,44,55,66]; 69 | REC 2314 [53,4C,41,56,45,20,48,45,4C,4C,4F,20,4D,45,53,53,41,47,45,0]; 74 | ``` 75 | ##### Message received 76 | ``` 77 | NACK INVALID COMMAND; 83 | ``` 84 | ##### RTC time SET command (EPOC) 85 | ``` 86 | ACK 23456; 88 | ``` 89 | ##### RTC time GET command (EPOC) 90 | ``` 91 | ACK 243495; 93 | ``` 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino EspNow managed flooding mesh network with mqtt 2 | 3 | Includes: 4 | - Mesh usb adapter codes (for esp32/esp2866). 5 | - Mesh gateway codes (Convert messages between mesh network and MQTT broker) 6 | - Slave node example codes (Slave node can read sensors, control switches/lights or something else) 7 | 8 | ##### Features: 9 | - Works on EspNow broadcast 10 | - Nearly instant connection after poweron 11 | - Maximum number of slave nodes: unlimited 12 | - Flooding mesh support 13 | - a message cache. If a received packet is already found in the cache --> it will not be retransmitted or handled again 14 | - Mesh nodes use MQTT service (subscribe/publish) 15 | - Master node (USBAdapter=ESP32 or ESP2866) is connected to RaspberryPi's USB port 16 | - Each Nodes can communicate with each other 17 | - ESP32, ESP2866, ESP01 18 | - Ping about 40-60ms 19 | - ttl support 20 | - Battery node support 21 | - AES128 22 | - Retransmission support 23 | - Request/Reply support 24 | - Send and pray support (Send a message to all nodes without reply/ack) 25 | - Easy to configure (just set channel, secure keys and bsid) 26 | - Simple mqqt interface. All nodes can use mqtt services via master node (subsribe,unsubscribe,publish,get). 27 | - MQTT local cache on raspberry 28 | - Arduino 29 | 30 | ``` 31 | ____________________________________ 32 | ( ) 33 | | | 34 | ( Internet ) 35 | | | 36 | (____________________________________) 37 | ^ 38 | |MQTT 39 | | 40 | +--------------------|-------+ 41 | | RaspberryPi V | 42 | |-------------+ +--+-------| 43 | | MeshGateway |<->| MQTT | +-------------------------------------+ 44 | | | | broker | | ESPNOW mesh network | 45 | +-----+-------+---+----------+ | Node6 | 46 | ^ | Node1 Node3 | 47 | | USB(SerialData) | +------------+ Node3 Node5 | 48 | +------------------------------>| USBAdapter | Node4 | 49 | | | (Master) | NodeX Node7 | 50 | | +------------+ | 51 | +-------------------------------------+ 52 | ``` 53 | 54 | ## Flooding mesh network 55 | In this network example ttl must be >= 5 56 | ``` 57 | SlaveNode 58 | | 59 | | Message from master to BatteryNode 60 | | ---------------------------+ 61 | | ttl=5 ttl=4 | 62 | SlaveNode-------MasterNode-------------SlaveNode | 63 | | | | 64 | | | | 65 | | | | 66 | | | | 67 | SlaveNode | | 68 | | | | 69 | | | | 70 | | | +------------------------------------------------> 71 | | | ttl=3 ttl=2 ttl=1 72 | SlaveNode-------SlaveNode-------------SlaveNode-------SlaveNode-------------SlaveNode---------BatteryNode 73 | | | | 74 | | | | 75 | | | | 76 | | | | 77 | +-----------SlaveNode-----------------+ 78 | ``` 79 | ###### Message relay 80 | The Master sends a request to the farthest node and the farthest node replies. 81 | ![alt text](https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/master/pictures/messageRelay.gif) 82 | 83 | ###### Arduino libraries: 84 | - https://github.com/arttupii/espNowFloodingMeshLibrary 85 | - https://github.com/arttupii/ArduinoCommands 86 | - https://github.com/arttupii/SimpleMqttLibrary 87 | - https://github.com/kakopappa/arduino-esp8266-aes-lib (Only ESP2866) 88 | 89 | ###### Slave Node examples 90 | - PIR-sensor node: https://github.com/arttupii/PirSensorNode 91 | - Relay switch node: https://github.com/arttupii/mesh-relay 92 | - Temperature sensor node: https://github.com/arttupii/mesh-dallas 93 | - Four channel relay (+reed switch) node: https://github.com/arttupii/FourChannelRelay 94 | ###### Early demo video 95 | - https://youtu.be/tXgNWhqPE14 96 | 97 | ###### Mesh usb adapter 98 | - Esp32/Esp2866 99 | - https://github.com/arttupii/EspNowUsb/tree/master/EspNowUsb 100 | 101 | ###### Mesh slave node codes 102 | - Esp32/Esp2866/Esp-01 103 | - https://github.com/arttupii/EspNowUsb/tree/master/arduinoSlaveNode/main 104 | 105 | ###### MeshGateway software for RaspberryPi (conversation between mesh and mqtt broker) 106 | - https://github.com/arttupii/EspNowFloodingMesh/tree/master/gateway 107 | - See config.js file (https://github.com/arttupii/EspNowUsb/blob/master/RaspberryPiServer/config.js) 108 | 109 | 110 | ##### Installation 111 | 0. Install Arduino and following dependencies: 112 | - https://github.com/arttupii/espNowFloodingMeshLibrary 113 | - https://github.com/arttupii/ArduinoCommands 114 | - https://github.com/arttupii/SimpleMqttLibrary 115 | - https://github.com/kakopappa/arduino-esp8266-aes-lib (Only ESP2866) 116 | 1. Install esp8266 dev module. Use git version. 117 | - Instructions: https://github.com/esp8266/Arduino 118 | 2. Check Espressif FW version!!!. It should be nonos-sdk 2.2.1+100(testing). 119 | ![alt text](https://github.com/arttupii/EspNowFloodingMesh/blob/master/pictures/ArduinoSetupForEsp2866.png) 120 | 3. Add "https://dl.espressif.com/dl/package_esp32_dev_index.json" into the Additional Board Manager URLs field. 121 | ![alt text](https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/master/pictures/ArduinoAdditionalURLs.png) 122 | 4. Install esp32 dev module 1.0.3-rc1 from Arduino's Boards Manager. 123 | ![alt text](https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/master/pictures/ArduinoBoardManager.png) 124 | 5. Flash Usb adapter software (EspNowUsb/EspNowUsb.ino) on esp32/esp2866 (esp32 is the best choice.). (You don't need to change any parameters) 125 | - https://github.com/arttupii/EspNowFloodingMesh/tree/master/EspNowUsb 126 | 6. Install mqtt broker, nodejs and npm on RaspberryPi 127 | ``` 128 | curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - 129 | sudo apt-get install mosquitto nodejs npm 130 | ``` 131 | 7. Get gateway and install npm modules 132 | * https://github.com/arttupii/EspNowFloodingMesh/tree/master/gateway 133 | ``` 134 | git clone git@github.com:arttupii/EspNowFloodingMesh.git 135 | cd EspNowFloodingMesh 136 | cd gateway 137 | npm install 138 | ``` 139 | 8. Modify gateway/config.js file: 140 | - set secredKey parameter (16 bytes) 141 | - set initializationVector parameter (16 bytes). 142 | - set bsid 143 | 9. Start gateway software on RaspberryPi. . 144 | ``` 145 | a@labra:~/git/EspNowUsb/gateway/node index.js 146 | begin /dev/ttyUSB0 115200 147 | Subscribe topic device1/led/value from cache 148 | Subscribe topic device1/led/set from cache 149 | reboot 150 | Role MASTER, ttl=3 151 | MAC GET 152 | InitializationVector [178,75,242,247,122,197,236,12,94,31,77,193,174,70,94,117] 153 | key [0,17,34,51,68,85,102,119,136,153,170,187,204,221,238,255] 154 | Channel 1; 155 | Init 156 | RTC 1563876153 157 | 158 | ``` 159 | 10. Open slave node code (arduinoSlaveNode/main/main.ino) and modify deviceName, secredKey, iv and ESP_NOW_CHANNEL paramaters. 160 | * https://github.com/arttupii/EspNowFloodingMesh/tree/master/arduinoSlaveNode/main 161 | * deviceName should be unique 162 | * secredKey, iv, bsid and ESP_NOW_CHANNEL must be match to config.js file on raspberryPi. Otherwise mesh network won't work. 163 | --> Flash slave node 164 | 165 | 166 | ###### Slave node code example 167 | Slave node updates the button's state to topic device1/switch/led/value. The led state can be controlled with topic device1/switch/led/set on/off. 168 | ```c++ 169 | #include 170 | #include 171 | 172 | /********NODE SETUP********/ 173 | #define ESP_NOW_CHANNEL 1 174 | const char deviceName[] = "device1"; 175 | unsigned char secredKey[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; 176 | unsigned char iv[16] = {0xb2, 0x4b, 0xf2, 0xf7, 0x7a, 0xc5, 0xec, 0x0c, 0x5e, 0x1f, 0x4d, 0xc1, 0xae, 0x46, 0x5e, 0x75}; 177 | const int ttl = 3; 178 | const int bsid = 0x112233; 179 | /*****************************/ 180 | 181 | #define LED 1 182 | #define BUTTON_PIN 2 183 | 184 | SimpleMQTT simpleMqtt = SimpleMQTT(ttl, deviceName); 185 | 186 | bool setLed; 187 | bool ledValue; 188 | 189 | void setup() { 190 | Serial.begin(115200); 191 | 192 | pinMode(LED, OUTPUT); 193 | pinMode(BUTTON_PIN, INPUT_PULLUP); 194 | 195 | espNowFloodingMesh_secredkey(secredKey); 196 | espNowFloodingMesh_setAesInitializationVector(iv); 197 | espNowFloodingMesh_setToMasterRole(false, ttl); 198 | espNowFloodingMesh_begin(ESP_NOW_CHANNEL, bsid); 199 | 200 | espNowFloodingMesh_ErrorDebugCB([](int level, const char *str) { 201 | Serial.print(level); Serial.println(str); //If you want print some debug prints 202 | }); 203 | 204 | 205 | if (!espNowFloodingMesh_syncWithMasterAndWait()) { 206 | //Sync failed??? No connection to master???? 207 | Serial.println("No connection to master!!! Reboot"); 208 | ESP.restart(); 209 | } 210 | 211 | //Handle MQTT events from master. Do not call publish() inside of call back. --> Endless event loop and crash 212 | simpleMqtt.handleEvents([](const char *topic, const char* value) { 213 | simpleMqtt._ifSwitch(VALUE, "led", [](MQTT_switch value){ //<--> Listening topic switch/led/value/value 214 | if(value==SWITCH_ON) { 215 | ledValue = true; 216 | } 217 | if(value==SWITCH_OFF) { 218 | ledValue = false; 219 | } 220 | }); 221 | simpleMqtt._ifSwitch(SET, "led", [](MQTT_switch set){ //<-->Listening topic device1/switch/led/set 222 | if(set==SWITCH_ON) { 223 | setLed = true; 224 | } 225 | if(set==SWITCH_OFF) { 226 | setLed = false; 227 | } 228 | }); 229 | }); 230 | 231 | if (!simpleMqtt._switch(SUBSCRIBE, "led")) { //Subscribe topic device1/switch/led/set and get topic device1/switch/led/value from cache 232 | Serial.println("MQTT operation failed. No connection to gateway"); 233 | } 234 | } 235 | 236 | bool buttonStatechange = false; 237 | 238 | void loop() { 239 | espNowFloodingMesh_loop(); 240 | 241 | int p = digitalRead(BUTTON_PIN); 242 | 243 | if (p == HIGH && buttonStatechange == false) { 244 | buttonStatechange = true; 245 | setLed = true; 246 | } 247 | if (p == LOW && buttonStatechange == true) { 248 | buttonStatechange = false; 249 | setLed = false; 250 | } 251 | 252 | if (ledValue == true && setLed == false) { 253 | ledValue = false; 254 | Serial.println("LED_OFF"); 255 | //digitalWrite(LED,HIGH); 256 | if (!simpleMqtt._switch(PUBLISH, "led", SWITCH_OFF)) { //publish topic device1/switch/led/value off 257 | Serial.println("Publish failed... Reboot"); 258 | Serial.println(ESP.getFreeHeap()); 259 | ESP.restart(); 260 | } 261 | } 262 | 263 | if (ledValue == false && setLed == true) { 264 | ledValue = true; 265 | Serial.println("LED_ON"); 266 | if (!simpleMqtt._switch(PUBLISH, "led", SWITCH_ON)) { //publish topic device1/switch/led/value on 267 | Serial.println("Publish failed... Reboot"); 268 | Serial.println(ESP.getFreeHeap()); 269 | ESP.restart(); 270 | } 271 | } 272 | 273 | delay(100); 274 | } 275 | ``` 276 | #### Config file for MeshGateway on RasperryPi 277 | ```javascript 278 | module.exports = { 279 | "usbPort": "/dev/ttyUSB0", 280 | "mesh": { 281 | "secredKey": [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], 282 | "ttl": 3, 283 | "channel": 1 284 | }, 285 | "dbCacheFile":"./cache.json", 286 | "mqtt": { 287 | "host": "mqtt://localhost", 288 | "root": "mesh/" 289 | } 290 | } 291 | ``` 292 | 293 | 294 | ### Recommended topics for switches, sensors and so on (just because of compatibility) 295 | ``` 296 | [nodename]/[type]/[sensorOrSwitchName]/[value or set] value 297 | 298 | ***SWITCH*** 299 | device1/switch/lamp1/value on 300 | device1/switch/lamp1/value off <--Current lamp state (only node should change this!) 301 | device1/switch/lamp1/set on <--Request set lamp on from outside 302 | device1/switch/lamp1/set off <--Request set lamp off from outside 303 | device1/switch/alarm/value off <--Current lamp state (only node should change this!) 304 | device1/switch/alarm/value on <--Current lamp state (only node should change this!) 305 | device1/switch/alarm/set on <--Request set lamp on from outside 306 | 307 | ***TEMPERATURE SENSORS*** 308 | device1/temp/outside/value 24.8 <--Celsius 309 | device1/humidity/bedroom/value 55 <--percentage 310 | 311 | device1/temp/thermostat1/set 21.2 <--Set thermostat 312 | 313 | ***TRIGGER*** 314 | device1/trigger/pirSensor1/value "triggered" <--Trigger to outside. For example pulse from pir-sensor. 315 | 316 | ***CONTACT*** 317 | device1/contact/switch1/value open 318 | device1/contact/switch1/value closed 319 | 320 | ***DIMMER*** 321 | device1/dimmer/myDimmer1/value 0 <--min value==off 322 | device1/dimmer/myDimmer1/value 255 <--max value==on 323 | device1/dimmer/myDimmer1/set 0 <--min value==off 324 | device1/dimmer/myDimmer1/set 255 <--max value==on 325 | 326 | ***STRING*** 327 | device1/string/message/value HelloWorld! <--(only node should change this!) 328 | device1/string/screen/set HelloWorld! <--(short string message from outside) 329 | 330 | ***NUMBER*** 331 | device1/number/thing/value min,max,step <--(number message to outside) 332 | device1/number/thing/set min,max,step <--(number message from outside) 333 | 334 | ***FLOAT*** 335 | device1/float/thing1/value 343.23 <--(number message to outside) 336 | device1/float/thing1/set 123.32 <--(number message from outside) 337 | 338 | ***INT*** 339 | device1/int/thing1/value 123 <--(int message to outside) 340 | device1/int/thing1/set 456 <--(int message from outside) 341 | 342 | ***ROLLERSHUTTER*** 343 | device1/shutter/myrollershutter/set open 344 | device1/shutter/myrollershutter/set close 345 | device1/shutter/myrollershutter/set stop 346 | device1/shutter/myrollershutter/value open 347 | device1/shutter/myrollershutter/value close 348 | device1/shutter/myrollershutter/value stop 349 | 350 | ***Counter*** 351 | device1/counter/gasMeter/value 23412343252 <--(gasMeter value to outside) 352 | device1/counter/gasMeter/value 23412343252 <--(gasMeter value to outside) 353 | 354 | ***Binary*** 355 | device1/bin/data1/value dGVzdA== <--(Binary data base64) 356 | device1/bin/data/set dGVzdA== <--(Binary data base64) 357 | ``` 358 | 359 | -------------------------------------------------------------------------------- /arduinoSlaveNode/main/main.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /********NODE SETUP********/ 5 | const char deviceName[] = "device2"; 6 | #if 0 7 | #define ESP_NOW_CHANNEL 1 8 | int bsid = 0x112233; 9 | unsigned char secredKey[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; 10 | unsigned char iv[16] = {0xb2, 0x4b, 0xf2, 0xf7, 0x7a, 0xc5, 0xec, 0x0c, 0x5e, 0x1f, 0x4d, 0xc1, 0xae, 0x46, 0x5e, 0x75};; 11 | const int ttl = 3; 12 | #else 13 | #include "/home/arttu/git/myEspNowMeshConfig.h" //My secred mesh setup... 14 | #endif 15 | /*****************************/ 16 | 17 | #define LED 1 /*LED pin*/ 18 | #define BUTTON_PIN 0 19 | 20 | SimpleMQTT simpleMqtt = SimpleMQTT(ttl, deviceName); 21 | 22 | bool setLed; 23 | bool ledValue; 24 | void setup() { 25 | Serial.begin(115200); 26 | 27 | //pinMode(LED, OUTPUT); 28 | //pinMode(BUTTON_PIN, INPUT_PULLUP); 29 | 30 | espNowFloodingMesh_secredkey(secredKey); 31 | espNowFloodingMesh_setAesInitializationVector(iv); 32 | espNowFloodingMesh_setToMasterRole(false, ttl); 33 | espNowFloodingMesh_begin(ESP_NOW_CHANNEL, bsid); 34 | 35 | espNowFloodingMesh_ErrorDebugCB([](int level, const char *str) { 36 | Serial.print(level); Serial.println(str); //If you want print some debug prints 37 | }); 38 | 39 | 40 | if (!espNowFloodingMesh_syncWithMasterAndWait()) { 41 | //Sync failed??? No connection to master???? 42 | Serial.println("No connection to master!!! Reboot"); 43 | // ESP.restart(); 44 | } 45 | 46 | //Handle MQTT events from master. Do not call publish inside of call back. --> Endless event loop and crash 47 | simpleMqtt.handleEvents([](const char *topic, const char* value) { 48 | /*if (simpleMqtt.compareTopic(topic, deviceName, "/switch/led/value")) { //subscribed initial value for led. 49 | if (strcmp("on", value) == 0) { //check value and set led 50 | ledValue = true; 51 | } 52 | if (strcmp("off", value) == 0) { 53 | ledValue = false; 54 | } 55 | } 56 | if (simpleMqtt.compareTopic(topic, deviceName, "/switch/led/set")) { 57 | if (strcmp("on", value) == 0) { //check value and set led 58 | setLed = true; 59 | } 60 | if (strcmp("off", value) == 0) { 61 | setLed = false; 62 | } 63 | }*/ 64 | 65 | simpleMqtt._ifSwitch(VALUE, "led", [](MQTT_switch value){ //<--> topic switch/led/value 66 | if(value==SWITCH_ON) { 67 | ledValue = true; 68 | } 69 | if(value==SWITCH_OFF) { 70 | ledValue = false; 71 | } 72 | }); 73 | simpleMqtt._ifSwitch(SET, "led", [](MQTT_switch value){ //<-->topic switch/led/set 74 | if(value==SWITCH_ON) { 75 | setLed = true; 76 | } 77 | if(value==SWITCH_OFF) { 78 | setLed = false; 79 | } 80 | }); 81 | }); 82 | 83 | /* 84 | bool success = simpleMqtt.subscribeTopic(deviceName, "/switch/led/set"); //Subscribe the led state from MQTT server device1/switch/led/set 85 | success = simpleMqtt.getTopic(deviceName, "/led/switch/value"); //Get the led state from MQTT server (topic is device1/switch/led/value) 86 | */ 87 | if (!simpleMqtt._switch(SUBSCRIBE, "led")) { //Same as the upper, but the smarter way 88 | Serial.println("MQTT operation failed. No connection to gateway"); 89 | } 90 | } 91 | 92 | bool buttonStatechange = false; 93 | 94 | void loop() { 95 | espNowFloodingMesh_loop(); 96 | 97 | int p = Serial.read();//digitalRead(BUTTON_PIN); 98 | 99 | if (p == '0' && buttonStatechange == false) { 100 | buttonStatechange = true; 101 | setLed = true; 102 | } 103 | if (p == '1' && buttonStatechange == true) { 104 | buttonStatechange = false; 105 | setLed = false; 106 | } 107 | 108 | if (ledValue == true && setLed == false) { 109 | ledValue = false; 110 | Serial.println("LED_OFF"); 111 | //digitalWrite(LED,HIGH); 112 | /*if (!simpleMqtt.publish(deviceName, "/led/value", "off")) { 113 | Serial.println("Publish failed... Reboot"); 114 | Serial.println(ESP.getFreeHeap()); 115 | ESP.restart(); 116 | }*/ 117 | //The better way to publish 118 | if (!simpleMqtt._switch(PUBLISH, "led", SWITCH_OFF)) { 119 | Serial.println("Publish failed... Reboot"); 120 | Serial.println(ESP.getFreeHeap()); 121 | ESP.restart(); 122 | } 123 | } 124 | if (ledValue == false && setLed == true) { 125 | ledValue = true; 126 | Serial.println("LED_ON"); 127 | 128 | //digitalWrite(LED,HIGH); 129 | /*if (!simpleMqtt.publish(deviceName, "/led/value", "on")) { 130 | Serial.println("Publish failed... Reboot"); 131 | ESP.restart(); 132 | }*/ 133 | //The better way to publish 134 | if (!simpleMqtt._switch(PUBLISH, "led", SWITCH_ON)) { 135 | Serial.println("Publish failed... Reboot"); 136 | Serial.println(ESP.getFreeHeap()); 137 | ESP.restart(); 138 | } 139 | } 140 | 141 | delay(100); 142 | 143 | } 144 | -------------------------------------------------------------------------------- /gateway/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "usbPort": "/dev/ttyUSB0", 3 | "mesh": { 4 | "secredKey": [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], 5 | "initializationVector": [0xb2, 0x4b, 0xf2, 0xf7, 0x7a, 0xc5, 0xec, 0x0c, 0x5e, 0x1f, 0x4d, 0xc1, 0xae, 0x46, 0x5e, 0x75], 6 | "ttl": 3, 7 | "channel": 1, 8 | "bsid": 0x112233 9 | }, 10 | "dbCacheFile":"./cache.json", 11 | "mqtt": { 12 | "host": "mqtt://localhost", 13 | "root": "mesh/" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gateway/index.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const si = require('./serialInterface'); 3 | var _=require("underscore"); 4 | var simpleMqtt=require("./simpleMqtt"); 5 | const config = require("./config.js") 6 | let polycrc = require('polycrc') 7 | var initialized = false; 8 | si.begin(config.usbPort); 9 | si.receiveCallback(function(replyId, data, err){ 10 | if(err==="REBOOT") { 11 | if(initialized) { 12 | setup(); 13 | } 14 | return; 15 | } 16 | console.info("Received: %j", data); 17 | simpleMqtt.parse(replyId, data); 18 | }); 19 | var rtcInterval=false; 20 | function setup() { 21 | initialized = false; 22 | 23 | if(rtcInterval===false) { 24 | setInterval(function(){ 25 | var epoch = (new Date).getTime()/1000; 26 | si.setRTC(epoch); 27 | }, 5*60*1000); 28 | rtcInterval=true; 29 | } 30 | 31 | return Promise.delay(1000) 32 | .then(function(){ 33 | return si.reboot().delay(3000); 34 | }) 35 | .then(function(){ 36 | return si.role("master"); 37 | }) 38 | .then(function(){ 39 | return si.getMAC(); 40 | }) 41 | .then(function(mac){ 42 | var crc24 = parseInt(polycrc.crc24(new Buffer(mac)))&0xffffff; 43 | 44 | if(config.mesh.bsid!==crc24 && config.mesh.bsid===0x112233) { 45 | console.info("(HOX!!! SET THIS VALUE TO ALL YOUR NODES --> \"const int bsid = 0x%s;\"). Update also config.js!!!", crc24.toString(16)); 46 | console.info("Default Bsid is used!!!"); 47 | } 48 | return si.setBSID(config.mesh.bsid); 49 | 50 | }) 51 | .then(function(){ 52 | return si.setInitializationVector(config.mesh.initializationVector); 53 | }) 54 | .then(function(){ 55 | return si.setKey(config.mesh.secredKey); 56 | }) 57 | .then(function(){ 58 | return si.setChannel(config.mesh.channel); 59 | }) 60 | .then(function(){ 61 | return si.init(); 62 | }) 63 | .then(function(){ 64 | initialized=true; 65 | return si.setRTC((new Date).getTime()/1000).delay(3000); 66 | }).catch(function(e){ 67 | console.info(e); 68 | }); 69 | } 70 | 71 | setup(); 72 | -------------------------------------------------------------------------------- /gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "bluebird": "^3.5.5", 4 | "mqtt": "^3.0.0", 5 | "nodejs": "0.0.0", 6 | "polycrc": "^0.1.0", 7 | "serialport": "^9.0.2", 8 | "underscore": "^1.9.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gateway/parser.js: -------------------------------------------------------------------------------- 1 | var _ = require("underscore"); 2 | 3 | function parse(line) { 4 | var buffer=[]; 5 | var paramIndex=0; 6 | var binaryData=[]; 7 | 8 | var commentStarted=false; 9 | var controlMark=false; 10 | var index=0; 11 | 12 | var parameters =[]; 13 | 14 | console.info(" Parse: \"%s\"",line); 15 | 16 | function p(a){ 17 | if(a=='\\' && controlMark==false) { 18 | controlMark = true; 19 | return; 20 | } 21 | if(controlMark) { 22 | controlMark = false; 23 | buffer.push(a); 24 | return; 25 | } 26 | 27 | if(a=='#') { 28 | commentStarted = true; 29 | return; 30 | } 31 | 32 | if(commentStarted) { 33 | if (a == '\n' || a == '\r') { 34 | commentStarted=false; 35 | } 36 | return; 37 | } 38 | 39 | if (a == ' ' || a == '\t' || a == ';') { 40 | //Handle params 41 | parameters.push(buffer.join("")); 42 | buffer =[]; 43 | return; 44 | } else if (a == '[') { 45 | buffer = []; 46 | binaryDataStarted = true; 47 | return; 48 | } else if (a == '\n' || a == '\r') { 49 | } else if (a == ']' /*|| a == ','*/) { 50 | parameters.push(_.map(buffer.join("").split(","), function(a){ 51 | return parseInt(a,16); 52 | })); 53 | buffer = []; 54 | return; 55 | } else { 56 | 57 | } 58 | if (a == ';') { 59 | parameters = parameters.pop(); 60 | } 61 | buffer.push(a); 62 | 63 | } 64 | 65 | for(var i=0;i 0x%s", bsid.toString(16)); 229 | 230 | return new Promise(function(resolve, reject){ 231 | var ret = waitAckNack() 232 | .then(function(p){ 233 | if(p[0]==="ACK") { 234 | resolve(); 235 | } { 236 | reject("bsid FAILED"); 237 | } 238 | }); 239 | port.write("BSID SET " + bsid + ";"); 240 | return ret; 241 | }).timeout( 242 | 5000, "Bsid operation timed out").then(function(){ 243 | mutex(false); 244 | }).error(function(e){ 245 | return Promise.reject(e); 246 | }); 247 | }); 248 | } 249 | 250 | function setInitializationVector(iv) { 251 | return mutex(true).then(function(done){ 252 | console.info("InitializationVector %j", iv); 253 | 254 | if(iv.length!==16) return Promise.reject("Invalid key size"); 255 | 256 | 257 | return new Promise(function(resolve, reject){ 258 | var ret = waitAckNack() 259 | .then(function(p){ 260 | if(p[0]==="ACK") { 261 | resolve(); 262 | } { 263 | reject("KEY FAILED"); 264 | } 265 | }); 266 | port.write("IV SET [" + _.map(iv,function(a){ 267 | return a.toString(16).toUpperCase(); 268 | }).join(",")+"];"); 269 | 270 | return ret; 271 | }).timeout( 272 | 5000, "InitializationVector operation timed out").then(function(){ 273 | mutex(false); 274 | }).error(function(e){ 275 | return Promise.reject(e); 276 | }); 277 | }); 278 | } 279 | function convertToBinaryArray(a){ 280 | function f(c) { 281 | return _.map(c, function(b){ 282 | return b.toString(16).toUpperCase(); 283 | }).join(","); 284 | } 285 | if(Array.isArray(a)){ 286 | return f(a); 287 | } else { 288 | return f(_.map(a.toString().split(""), function(a){ 289 | return a.charCodeAt(0); 290 | })) + ",0"; 291 | } 292 | } 293 | 294 | function send(message, ttl=0) { 295 | return mutex(true).then(function(){ 296 | var j = convertToBinaryArray(message); 297 | console.info("send ttl=%d, %s", ttl, j); 298 | 299 | return new Promise(function(resolve, reject){ 300 | var ret = waitAckNack() 301 | .then(function(p){ 302 | if(p[0]==="ACK") { 303 | resolve(); 304 | } { 305 | reject("SEND FAILED"); 306 | } 307 | }); 308 | port.write("SEND " + ttl +" [" + j +"];"); 309 | return ret; 310 | }).timeout( 311 | 5000, "Send operation timed out").then(function(){ 312 | mutex(false); 313 | }).error(function(e){ 314 | return Promise.reject(e); 315 | }); 316 | }); 317 | } 318 | 319 | function reply(message, replyPrt, ttl=0) { 320 | return mutex(true).then(function(){ 321 | var j = convertToBinaryArray(message); 322 | console.info("send ttl=%d, %s", ttl, j); 323 | 324 | return new Promise(function(resolve, reject){ 325 | var ret = waitAckNack() 326 | .then(function(p){ 327 | if(p[0]==="ACK") { 328 | resolve(); 329 | } { 330 | reject("SEND FAILED"); 331 | } 332 | }); 333 | port.write("REPLY " + ttl + " " + +replyPrt + " [" + j +"];"); 334 | return ret; 335 | }).timeout( 336 | 5000, "Reply operation timed out").then(function(){ 337 | mutex(false); 338 | }).error(function(e){ 339 | return Promise.reject(e); 340 | }); 341 | }); 342 | } 343 | 344 | function request(message, ttl=0, timeout=3000, wantedReplyCount=1) { 345 | function reqSend(){ 346 | var ret; 347 | return mutex(true).then(function(){ 348 | console.info("request ttl=%d, %j", ttl, message); 349 | var replyId; 350 | return new Promise(function(resolve, reject){ 351 | waitAckNack() 352 | .error(function(){}) 353 | .then(function(r){ 354 | mutex(false); 355 | return r; 356 | }) 357 | .then(function(p){ 358 | replyId = p[1]; 359 | 360 | if(p[0]==="ACK") { 361 | replyIdDB[replyId] = { 362 | data: [], 363 | add: function(a){ 364 | this.data.push(a); 365 | wantedReplyCount--; 366 | ret = this.data; 367 | if(wantedReplyCount<=0){ 368 | resolve(this.data); 369 | } 370 | } 371 | }; 372 | console.info("Wait all Replies with replyId: " + p[1]); 373 | } else { 374 | reject("SEND FAILED"); 375 | } 376 | }); 377 | port.write("REQ " + ttl +" [" + convertToBinaryArray(message) +"];"); 378 | }).timeout( 379 | 3000, "REQ operation timed out") 380 | .error(function(e) { 381 | console.info("MESSAGE!!!:", message); 382 | console.info("Timeout!!", e); 383 | }).then(function(){ 384 | console.info("Request handled"); 385 | }); 386 | }).then(function(){ 387 | return ret; 388 | }); 389 | } 390 | return reqSend(); 391 | } 392 | 393 | function getRTC(message) { 394 | return mutex(true).then(function(done){ 395 | console.info("RTC GET"); 396 | 397 | return new Promise(function(resolve, reject){ 398 | var ret = waitAckNack() 399 | .then(function(p){ 400 | if(p[0]==="ACK") { 401 | resolve(parseInt(p[1])); 402 | } { 403 | reject("RTC GET FAILED"); 404 | } 405 | }); 406 | port.write("RTC GET;"); 407 | 408 | return ret; 409 | }).timeout( 410 | 5000, "RTC GET operation timed out").then(function(){ 411 | mutex(false); 412 | }).error(function(e){ 413 | return Promise.reject(e); 414 | }); 415 | }); 416 | } 417 | function getMAC() { 418 | return mutex(true).then(function(done){ 419 | console.info("MAC GET"); 420 | 421 | return new Promise(function(resolve, reject){ 422 | var ret = waitAckNack() 423 | .then(function(p){ 424 | if(p[0]==="ACK") { 425 | resolve(p[1]); 426 | } { 427 | reject("MAC GET FAILED"); 428 | } 429 | }); 430 | port.write("MAC;"); 431 | return ret; 432 | }).timeout( 433 | 5000, "MAC GET operation timed out").then(function(ret){ 434 | mutex(false); 435 | return ret; 436 | }).error(function(e){ 437 | return Promise.reject(e); 438 | }); 439 | }); 440 | } 441 | 442 | function setRTC(epoch) { 443 | return mutex(true).then(function(done){ 444 | epoch = parseInt(epoch); 445 | console.info("RTC %d", epoch); 446 | 447 | return new Promise(function(resolve, reject){ 448 | var ret = waitAckNack() 449 | .then(function(p){ 450 | if(p[0]==="ACK") { 451 | resolve(parseInt(p[1])); 452 | } { 453 | reject("RTC SET FAILED"); 454 | } 455 | }); 456 | port.write("RTC SET " + epoch +";"); 457 | return ret; 458 | }).timeout( 459 | 5000, "RTC SET operation timed out").then(function(){ 460 | mutex(false); 461 | }).error(function(e){ 462 | return Promise.reject(e); 463 | }); 464 | }); 465 | } 466 | 467 | module.exports.begin = begin; 468 | module.exports.receiveCallback = function(cb) { 469 | callback = cb; 470 | } 471 | module.exports.init = init; 472 | module.exports.role = role; 473 | module.exports.setKey = setKey; 474 | module.exports.ping = ping; 475 | module.exports.setChannel = setChannel; 476 | module.exports.reboot = reboot; 477 | module.exports.getRTC = getRTC; 478 | module.exports.setRTC = setRTC; 479 | module.exports.setInitializationVector = setInitializationVector; 480 | module.exports.setBSID = setBSID; 481 | module.exports.getMAC = getMAC; 482 | module.exports.send = send; 483 | module.exports.req = request; 484 | module.exports.reply = reply; 485 | -------------------------------------------------------------------------------- /gateway/simpleMqtt.js: -------------------------------------------------------------------------------- 1 | const _ = require("underscore"); 2 | var mqtt = require('mqtt') 3 | const config = require("./config.js") 4 | const si = require('./serialInterface'); 5 | const fs = require('fs'); 6 | 7 | var client = mqtt.connect(config.mqtt.host) 8 | 9 | var mqttCache = {}; 10 | 11 | var writeDelayed = 0; 12 | function writeChacheFile() { 13 | if(writeDelayed===0) { 14 | setTimeout(function(){ 15 | if(writeDelayed>1) { 16 | writeDelayed=0; 17 | writeChacheFile(); 18 | } 19 | writeDelayed=0; 20 | },15000); 21 | fs.writeFileSync(config.dbCacheFile, JSON.stringify(mqttCache,0,3)); 22 | console.info("Update cache file..."); 23 | } 24 | writeDelayed++; 25 | } 26 | 27 | function readCacheFileFromDisk(){ 28 | var rawdata; 29 | try { 30 | rawdata = fs.readFileSync(config.dbCacheFile); 31 | } catch(e){} 32 | if(rawdata) { 33 | mqttCache = JSON.parse(rawdata); 34 | } else { 35 | mqttCache = {}; 36 | } 37 | } 38 | 39 | readCacheFileFromDisk(); 40 | 41 | client.on('connect', function () { 42 | _.forEach(_.keys(mqttCache), function(topic){ 43 | console.info("Subscribe topic %s from cache", topic); 44 | client.subscribe(config.mqtt.root+topic); 45 | }); 46 | }) 47 | 48 | function initMqttCacheObject(shortTopic){ 49 | if(mqttCache[shortTopic]===undefined) { 50 | mqttCache[shortTopic] = { 51 | value:"", 52 | lastUpdate:"", 53 | subscribedBy:[], 54 | status:{ 55 | errorStatus:"", 56 | lastUpdate:"" 57 | } 58 | } 59 | } 60 | } 61 | 62 | function updateValueMqttCache(shortTopic, value) { 63 | console.info("updateValueMqttCache shortTopic:%s, value=\"%s\"",shortTopic, value); 64 | initMqttCacheObject(shortTopic); 65 | mqttCache[shortTopic].value = value; 66 | mqttCache[shortTopic].lastUpdate = new Date(Date.now()).toString(); 67 | writeChacheFile(); 68 | } 69 | function isSubscribedMqttCache(shortTopic) { 70 | console.info("isSubscribedMqttCache shortTopic:%s",shortTopic); 71 | 72 | initMqttCacheObject(shortTopic); 73 | return mqttCache[shortTopic].subscribedBy.length>0; 74 | } 75 | function addSubscriber(shortTopic, deviceName) { 76 | console.info("addSubscriber shortTopic:%s",shortTopic); 77 | initMqttCacheObject(shortTopic); 78 | mqttCache[shortTopic].subscribedBy.push(deviceName); 79 | mqttCache[shortTopic].subscribedBy = _.uniq(mqttCache[shortTopic].subscribedBy); 80 | } 81 | function deleteSubscriber(shortTopic, deviceName) { 82 | if(mqttCache.hasOwnProperty(shortTopic)){ 83 | mqttCache[shortTopic].subscribedBy = _.filter(mqttCache[shortTopic].subscribedBy, function(s){ 84 | return s!==deviceName; 85 | }); 86 | } 87 | } 88 | function updateErrorMqttCache(shortTopic, status) { 89 | initMqttCacheObject(shortTopic); 90 | mqttCache[shortTopic].status.errorStatus = status; 91 | mqttCache[shortTopic].status.lastUpdate = new Date(Date.now()).toString(); 92 | writeChacheFile(); 93 | } 94 | 95 | function generateDataUpdateMsg(shortTopic, value, buffer) { 96 | if(buffer!==undefined) { 97 | if(buffer=="") { 98 | buffer+="MQTT MASTER\n"; 99 | return buffer+="P:"+shortTopic+" "+value+"\n"; 100 | } 101 | } 102 | return "MQTT MASTER\nP:"+shortTopic+" "+value+"\n"; 103 | } 104 | 105 | client.on('message', function (topic, message) { 106 | // message is Buffer 107 | console.log("From MQTT broker:"+message.toString()) 108 | console.info(" --->", topic); 109 | 110 | var shortTopic=topic.replace(config.mqtt.root,""); 111 | var payload = message.toJSON().data; 112 | var v = _.map(payload,function(c){ 113 | return String.fromCharCode(c); 114 | }).join(""); 115 | 116 | updateValueMqttCache(shortTopic, v); 117 | 118 | if(isSubscribedMqttCache(shortTopic)) { 119 | console.info("Topic has been subscribed --> publish it to mesh network"); 120 | var msg = generateDataUpdateMsg(shortTopic,v); 121 | var tryCnt=3; 122 | function send() { 123 | return si.req(msg, config.mesh.ttl) 124 | .then(function(){ 125 | updateErrorMqttCache(shortTopic, "OK"); 126 | }) 127 | .catch(function (e) { 128 | console.info("No reply from node..."); 129 | tryCnt--; 130 | if(tryCnt>0) { 131 | console.info("Try again..."); 132 | return send(); 133 | } else { 134 | updateErrorMqttCache(shortTopic, "NoReceiver"); 135 | } 136 | }); 137 | } 138 | send(); 139 | }else { 140 | console.info("No subscriber for topic %s", shortTopic); 141 | } 142 | }); 143 | 144 | 145 | function parse(replyId, data) { 146 | function decompressTopic(topic) { 147 | if(topic[0]!==".") { 148 | this.t = topic; 149 | return topic; 150 | } 151 | var pCount=0; 152 | for(pCount;topic[pCount]==='.';pCount++); 153 | var slitIndex = 0; 154 | 155 | for(var i=0;i0) { 222 | //Ack required 223 | console.info("MQTT: Ack requested by client..."); 224 | var msg = ""; 225 | _.forEach(subscribedTopics, function(t){ 226 | if(!t.match(/.*\/trigger\/.*\/value$/)){ 227 | if(mqttCache.hasOwnProperty(t)) { 228 | var value = mqttCache[t].value; 229 | msg=generateDataUpdateMsg(t, value, msg); 230 | } 231 | } 232 | }); 233 | if(msg==="") { 234 | msg = "MQTT MASTER\n"; 235 | } 236 | console.info("Send ack to client. ReplyId=%d, msg\"%s\"", replyId, msg); 237 | 238 | si.reply(msg, replyId, config.mesh.ttl); 239 | } 240 | } 241 | } 242 | 243 | module.exports.parse=parse; 244 | -------------------------------------------------------------------------------- /pictures/ArduinoAdditionalURLs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/4fd7eda4478a3ec0142134b16f0c75491c5c8a70/pictures/ArduinoAdditionalURLs.png -------------------------------------------------------------------------------- /pictures/ArduinoBoardManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/4fd7eda4478a3ec0142134b16f0c75491c5c8a70/pictures/ArduinoBoardManager.png -------------------------------------------------------------------------------- /pictures/ArduinoSetupForEsp2866.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/4fd7eda4478a3ec0142134b16f0c75491c5c8a70/pictures/ArduinoSetupForEsp2866.png -------------------------------------------------------------------------------- /pictures/DIYMESH.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/4fd7eda4478a3ec0142134b16f0c75491c5c8a70/pictures/DIYMESH.PNG -------------------------------------------------------------------------------- /pictures/messageRelay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/4fd7eda4478a3ec0142134b16f0c75491c5c8a70/pictures/messageRelay.gif -------------------------------------------------------------------------------- /pictures/testSetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arttupii/EspNowFloodingMesh/4fd7eda4478a3ec0142134b16f0c75491c5c8a70/pictures/testSetup.png -------------------------------------------------------------------------------- /tools/randomKey.js: -------------------------------------------------------------------------------- 1 | function randomIntFromInterval(min,max) // min and max included 2 | { 3 | return Math.floor(Math.random()*(max-min+1)+min); 4 | } 5 | 6 | tmp="const char key[]={"; 7 | for(var i=0;i<16;i++) { 8 | console.info("LOOP"); 9 | if(i===0) { 10 | tmp+="0x"+randomIntFromInterval(0,255).toString(16); 11 | }else { 12 | tmp+=", "+"0x"+randomIntFromInterval(0,255).toString(16); 13 | } 14 | } 15 | 16 | tmp+="};" 17 | console.info(tmp) 18 | --------------------------------------------------------------------------------