//************************************************************ // this is a simple example that uses the painlessMesh library // // 1. sends a silly message to every node on the mesh at a random time between 1 // and 5 seconds // 2. prints anything it receives to Serial.print // 3. has OTA support and can be updated remotely // //************************************************************ //needs these: //https://github.com/me-no-dev/AsyncTCP //https://github.com/me-no-dev/ESPAsyncTCP //https://github.com/me-no-dev/ESPAsyncWebServer //https://randomnerdtutorials.com/esp-mesh-esp32-esp8266-painlessmesh/ //Outpost Mesh OTA RECEIVER //This will let itself be updated over the air by ota sender //role type defines what ota update firmware applies to this device: // simplesensor - sends numeric data //simplerelay - accepts simple on/off actions // v.0.0.1 - initial // v.0.0.2 - version, ota receiver and ota sender, better serial info // v.0.0.3 - mesh.onReceive moved to setup, msg sent as : delimited strings (command:waterpump:switch:on , response:waterpump:switch_status:on) // v.0.0.4 - no more random chatter, basic switch actions with timer, main actions are now set in doactions() // v.0.0.5 - short commands: whoami, orders // v.0.0.6 - buffer and active commands fix //[to]:[node_name|all]:[on|off]:(secs) //[get]:[node_name|all]:[status] //todo: monospace responses for table view on reports. //bug: its unable to send orders to self, only to other nodes //??todo: nodeRole to be declared by a master node at runtime based on node name, keep simple node names line node1 node2 #include "namedMesh.h" #define NODE_VERSION "ota-receiver-0.0.6" //role and version number #define MESH_PREFIX "SensorSwarm" //Outpost Mesh, SwarmSense #define MESH_PASSWORD "1L.24edckin3487bH^g36g3ud91" #define MESH_PORT 5555 String nodeName = "lights"; // Name needs to be unique. waterbox, waterpump, rainsensor, String nodeRole = "simpleswitch"; //this name should be found in the ota bin file firmware__.bin (ex: firmware_ESP8266_simplerelay.bin) //analogread = sends numeric data //digitalread = sends numeric data //simpleswitch = accepts on/off commands //board specific const int relayPin = D1; unsigned long interval = 5000; // pause for two seconds int relayState = LOW; unsigned long previousMillis = 0; //---debugger functions for var_dump equiv const char* TypeOf(const bool&) { static const char type[] = "bool"; return type; } const char* TypeOf(const bool*) { static const char type[] = "bool*"; return type; } const char* TypeOf(const char&) { static const char type[] = "char"; return type; } const char* TypeOf(const char*) { static const char type[] = "char*"; return type; } const char* TypeOf(const double&) { static const char type[] = "double"; return type; } const char* TypeOf(const double*) { static const char type[] = "double*"; return type; } const char* TypeOf(const float&) { static const char type[] = "float"; return type; } const char* TypeOf(const float*) { static const char type[] = "float*"; return type; } const char* TypeOf(const int&) { static const char type[] = "int"; return type; } const char* TypeOf(const int*) { static const char type[] = "int*"; return type; } const char* TypeOf(const String&) { static const char type[] = "String"; return type; } const char* TypeOf(const String*) { static const char type[] = "String*"; return type; } //const char* const TypeOf(const char[5]) { static const char* type = "char[5]"; return type; } // Decays to char* so generates compiler redefinition warning. const char* const TypeOf(const char(*)[5]) { static const char* type = "char(*)[5]"; return type; } //-------------- //serial control const byte numChars = 80; char receivedSerialChars[numChars]; // an array to store the received data boolean newSerialData = false; boolean saidHello = false; //init, used only once boolean runningSwitchOn = false; //avoid timer init if a counter is already running //command it receives regardless if important String buffCmdType; String buffCmdTarget; String buffCmdKey; String buffCmdValue; //command it decides it should be active String actCmdType; String actCmdTarget; String actCmdKey; String actCmdValue; //periodic tasks Scheduler userScheduler; // to control your personal task namedMesh mesh; // User stub void userTaskFunction(); //todo: it runs when it starts so this makes the device to keep rebooting Task userTask(TASK_SECOND * 10, TASK_FOREVER, &userTaskFunction); //every hour, reboot 60 * 60 /* //sampke task function that resets the task to run again once done void sendReport(); //declair first Task taskSendMessage(TASK_SECOND * 1, TASK_FOREVER, &sendReport); void sendReport() { //do task stuff here //set the task again taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5)); } */ //say hello when node connects void userTaskFunction(){ //taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5)); //reboot device //Serial.println("Restarting node to heal fragmentated memory ..."); // a reset from time to time keeps the nodes healthy, but might interfere with ongoing activities //delay(1000); //ESP.restart(); //broadcast to all //String msg = ""; //msg.reserve(80); //msg += "hello:"; //msg += nodeName; //msg += ":"; //msg += nodeRole; //msg += "__"; //msg += NODE_VERSION; //Serial.println(msg); //THIS TASK CAN NOT RUN MESH RELATED BROADCASTS, its only for local relevant activities } //todo //command is 'read_pressure', value is 'null' //or for action devices command is 'switch' value is 'on' void reportValueToAll(String command, String value){ //broadcast to all String myvalue = String(mesh.getNodeId()); //"myval123"; String msg = ""; msg.reserve(80); //@@todo: :value: is the type of sensor value, for example 'pressure', and myvalue is '30psi' msg += "response:"; msg += nodeName; msg += ":"; msg += command; msg += ":"; msg += myvalue; //mesh.getNodeId(); mesh.sendBroadcast(msg); } //--- void setActiveCommand(String cmd, String targ, String k, String v){ actCmdType = cmd; actCmdTarget = targ; actCmdKey = k; actCmdValue = v; } //triggered when it receives mesh broadcast //can be overloaded so it supports also send to id void receivedCallback(String &from, String &msg) { //uint32_t from bool shouldBeActive = true; //first load in temp command, later if nothing interferes , move to active //Serial.println(TypeOf(msg)); parseCommandString(msg); //msg_char_arr //catch all non active functions if(buffCmdKey == "status"){ shouldBeActive = false; //do not make active, it will be handled in doactions before active command } if(msg.startsWith("response:")){ shouldBeActive = false; //do not make active, it will be handled in doactions before active command } //make sure its command if(buffCmdType == "to" || buffCmdType == "get"){ //if(strcmp(buffCmdType, String("command").c_str()) == 0 || strcmp(buffCmdType, String("to").c_str()) == 0 ){ //make sure its for me if(buffCmdTarget == nodeName || buffCmdTarget == "all"){ if(!msg.startsWith("response:")){ //reportValueToAll(buffCmdKey,buffCmdValue); //delivery confirmation String resp_from = from; String resp_msg = "response: message received by "; resp_msg += nodeName; resp_msg += " ("; resp_msg += msg; resp_msg += ")"; mesh.sendSingle(resp_from, resp_msg); }else{ //already a response, do not respond to a response } }else{ Serial.println("message not for me"); shouldBeActive = false; } } //if no other use was found for this command, assue its the one thats supposed to be active/run if(shouldBeActive){ setActiveCommand(buffCmdType, buffCmdTarget, buffCmdKey, buffCmdValue); } Serial.print("[From:"); Serial.print(from); Serial.print("] "); Serial.println(msg); } //void receivedCallback(uint32_t from, String &msg) { // Serial.println("addressing by node id is supported but not used in this mesh"); //} void newConnectionCallback(uint32_t nodeId) { Serial.printf("Detected new node, nodeId = %u", nodeId); Serial.println(); } void changedConnectionCallback() { Serial.printf("Changed connections"); Serial.println(); } void nodeTimeAdjustedCallback(int32_t offset) { //not important to view //Serial.printf("Adjusted time %u. Offset = %d", mesh.getNodeTime(), offset); //Serial.println(); } //receive serial command followed by enter (new line) void readSerialCommands() { static byte ndx = 0; char endMarker = '\n'; char rc; while (Serial.available() > 0 && newSerialData == false) { rc = Serial.read(); if (rc != endMarker) { receivedSerialChars[ndx] = rc; ndx++; if (ndx >= numChars) { ndx = numChars - 1; } } else { receivedSerialChars[ndx] = '\0'; // terminate the string ndx = 0; newSerialData = true; } } } // split command string into its parts //first cmd is loaded , then if a valid command is detected that should replace the current one, cmd is trasnfered to actCmd. This allows other intermediary commands to be run via parseCommandString, not just the active one void parseCommandString( String receivedcmd ) { String buffCmdType_tmp = getValue(receivedcmd,':',0); String buffCmdTarget_tmp = getValue(receivedcmd,':',1); String buffCmdKey_tmp = getValue(receivedcmd,':',2); String buffCmdValue_tmp = getValue(receivedcmd,':',3); if( (buffCmdType_tmp.length() >= 1) && (buffCmdTarget_tmp.length() >= 1) && (buffCmdKey_tmp.length() >= 1) ){ //Serial.println("OK: found at least cmd, target and key"); buffCmdType = buffCmdType_tmp; buffCmdTarget = buffCmdTarget_tmp; buffCmdKey = buffCmdKey_tmp; buffCmdValue = buffCmdValue_tmp; }else{ //this is not the right syntax for parsing } } String getValue(String data, char separator, int index) { int found = 0; int strIndex[] = { 0, -1 }; int maxIndex = data.length() - 1; for (int i = 0; i <= maxIndex && found <= index; i++) { if (data.charAt(i) == separator || i == maxIndex) { found++; strIndex[0] = strIndex[1] + 1; strIndex[1] = (i == maxIndex) ? i+1 : i; } } return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; } //commands are read in temp commands first (cmd), then moved to actCmd if they are worth it. //serial commands will just run local actions, and any important action will be sent to run here via the mesh (this means no locally available functions, to avoid redundant code) void actOnSerialCommand(){ bool shortcmd = false; //flag to detect //detect serial command if (newSerialData == true) { //Serial.print("Serial input: "); //Serial.println(receivedSerialChars); //snitch String msg = ""; msg.reserve(80); msg += "response: Received serial input ("; msg += receivedSerialChars; //mesh.getNodeId(); msg += ")"; mesh.sendBroadcast(msg); //----- String serialmsg = receivedSerialChars; if(serialmsg == "whoami"){ Serial.print("Node name:"); Serial.print(nodeName); Serial.print(" version:"); Serial.print(NODE_VERSION); Serial.print(" firmware role:"); Serial.print(nodeRole); Serial.println(); shortcmd = true; } if(serialmsg == "orders"){ Serial.print("[Active orders] cmd:"); Serial.print(actCmdType); Serial.print(" target:"); Serial.print(actCmdTarget); Serial.print(" key:"); Serial.print(actCmdKey); Serial.print(" val:"); Serial.print(actCmdValue); Serial.print(" [Command buffer] cmd:"); Serial.print(buffCmdType); Serial.print(" target:"); Serial.print(buffCmdTarget); Serial.print(" key:"); Serial.print(buffCmdKey); Serial.print(" val:"); Serial.print(buffCmdValue); Serial.println(); shortcmd = true; } //no short command detected, look for more complex if(!shortcmd){ //loads temp command with instructions parseCommandString(serialmsg); //buffCmdType, buffCmdTarget, buffCmdKey, buffCmdValue //"\0" //if command is "command" or "to", will send to target node //@@todo: to: should be replaced with self: and allow internal actions, because node can not broadcast to itself //Serial.print(" will check for buffCmdType ("); //Serial.print(buffCmdType); //Serial.print(") and buffCmdTarget is ("); //Serial.print(buffCmdTarget); //Serial.println(")"); if(buffCmdType == "to" || buffCmdType == "get" ){ //if(strcmp(buffCmdType, String("command").c_str()) == 0 || strcmp(buffCmdType, String("to").c_str()) == 0 ){ Serial.print("Sending to: "); Serial.print(buffCmdTarget); Serial.print(" Msg: "); Serial.print(serialmsg); Serial.println(); if(buffCmdTarget=="all"){ //String msg = receivedSerialChars; //msg.reserve(80); mesh.sendBroadcast(serialmsg); }else{ String to = buffCmdTarget; //String msg_command = receivedSerialChars; mesh.sendSingle(to, serialmsg); } }else{ //anything that is not detected as short command or does not have a valid command suntax, falls here Serial.print("Undefined serial command: ("); Serial.print(buffCmdType); Serial.print(") To: ("); Serial.print(buffCmdTarget); Serial.print(") "); Serial.print("received:"); Serial.println(serialmsg); //receivedSerialChars } }else{ //string was already interpreted as short command } newSerialData = false; //reset } } //erase current actCmd fields so it no longer consdiers it has to do any active (continous) command void releaseActiveCommand(){ actCmdType = ""; actCmdTarget = ""; actCmdKey = ""; actCmdValue = ""; } //release any command you just read from serial or received via broadcast void releaseTempCommand(){ buffCmdType = ""; buffCmdTarget = ""; buffCmdKey = ""; buffCmdValue = ""; } void doactions(){ unsigned long currentMillis = millis(); //String buffCmdType; //String buffCmdTarget; //String buffCmdKey; //String buffCmdValue; //process temporary commands, this will not change current active commands, and can not be something that runs continuously. Use for status reads if(buffCmdTarget == nodeName || buffCmdTarget == "all"){ if(buffCmdType == "get"){ if(actCmdKey == "status"){ String msg = ""; msg.reserve(80); msg += "response: status for node "; msg += nodeName; msg += " is "; if(nodeRole == "simpleswitch"){ if(relayState){ msg += "on"; }else{ msg += "off"; } mesh.sendBroadcast(msg); Serial.print("BROADCASTING: "); Serial.println(msg); releaseTempCommand(); //end this, but still go on with active comm } //--- if(nodeRole == "digitalread"){ //todo msg += "todo digitalread"; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); releaseTempCommand(); //end this, but still go on with active comm } if(nodeRole == "analogread"){ //todo msg += "todo analogread"; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); releaseTempCommand(); //end this, but still go on with active comm } } } } if(actCmdTarget == nodeName || actCmdTarget == "all"){ if(actCmdType == "to"){ if(actCmdKey == "on"){ //if off, start the timer and switch it on if(relayState == LOW){ //switch on relayState = HIGH; digitalWrite(relayPin, relayState); if(actCmdValue.toInt()>0){ //do not end, wait for interval //if(!runningSwitchOn){ previousMillis = currentMillis; //init timer runningSwitchOn = true; //} interval = actCmdValue.toInt()*1000; //seconds to milis String msg = ""; msg += "response: Switched ON for "; msg += actCmdValue; msg += " seconds"; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); }else{ String msg = ""; msg += "response: Switched ON indefinitely"; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); releaseActiveCommand(); //end } }else{ //if its already on if (runningSwitchOn && currentMillis - previousMillis >= interval) { // if enough millis have elapsed //end action relayState = LOW; digitalWrite(relayPin, relayState); String msg = ""; msg += "response: Switched OFF after "; msg += actCmdValue; msg += " seconds"; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); runningSwitchOn = false; //reset timer if any running releaseActiveCommand(); //end }else{ //do nothing, its still on and during the interval } } } //-------- if(actCmdKey == "off"){ //if on switch permanently off if(relayState == HIGH){ //switch off relayState = LOW; digitalWrite(relayPin, relayState); String msg = ""; msg += "response: Switched OFF"; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); runningSwitchOn = false; //reset timer if any running releaseActiveCommand(); //end }else{ //already off } } //------- }else{ //no action command } }else{ //not me } } //----------------------------------------- void setup() { Serial.begin(9600); //115200 //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on mesh.setDebugMsgTypes(ERROR | STARTUP | DEBUG); //// set before init() so that you can see startup messages mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); mesh.setName(nodeName); // This needs to be an unique name! //mesh.onReceive([](uint32_t from, String &msg) { // // int str_len = msg.length() + 1; // char msg_char_arr[str_len]; // msg.toCharArray(msg_char_arr, str_len); // // Serial.println(); // Serial.print("[Node id:"); // Serial.print(mesh.getNodeId()); // Serial.print(" name:"); // Serial.print(nodeName); // Serial.printf("] (todo1) Receive callback: Received from %u msg=%s\n", from, msg.c_str()); // // parseCommandString(msg_char_arr); // String to = buffCmdTarget; // String msg_command = receivedSerialChars; // mesh.sendSingle(to, msg_command); // //}); //second definition? //mesh.onReceive([](String &from, String &msg) { // Serial.printf(" (todo2) Received message by name from: %s, %s\n", from.c_str(), msg.c_str()); //}); mesh.onReceive(&receivedCallback); mesh.onNewConnection(&newConnectionCallback); mesh.onChangedConnections(&changedConnectionCallback); mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback); // if you want your node to accept OTA firmware, simply include this line // with whatever role you want your hardware to be. For instance, a // mesh network may have a thermometer, rain detector, and bridge. Each of // those may require different firmware, so different roles are preferrable. // // MAKE SURE YOUR UPLOADED OTA FIRMWARE INCLUDES OTA SUPPORT OR YOU WILL LOSE // THE ABILITY TO UPLOAD MORE FIRMWARE OVER OTA. YOU ALSO WANT TO MAKE SURE // THE ROLES ARE CORRECT mesh.initOTAReceive(nodeRole); userScheduler.addTask(userTask); userTask.enable(); //board specific pinMode(relayPin, OUTPUT); } void sayHello(){ if(!saidHello){ //broadcast hello to all String msg = ""; msg.reserve(80); msg += "hello:"; msg += nodeName; msg += ":"; msg += nodeRole; msg += "__"; msg += NODE_VERSION; Serial.print("BROADCASTING: "); Serial.println(msg); mesh.sendBroadcast(msg); saidHello = true; Serial.println("Node "+nodeName+" loaded"); }else{ } } void loop() { sayHello(); //broadcasts hello to entire mesh, once readSerialCommands(); //receive serial command actOnSerialCommand(); //do something about received serial commands doactions(); //meshtastic mesh.update();// it will run the user scheduler as well // It is necessary to run the userScheduler on each loop //userScheduler.execute(); //i think this runs already in mesh.update }