Node to Node communication
-
Yes, you should replace the sleep with a wait. If you module is sleeping, it won't receive any message.
While prototyping/debugging, you should put more Serial.print around your blocks, not only in the innermost block.I guess you are mixing different things in the message object. In the receive() function, you should test message.sensor against 10, the sensor value you put in the sent message, 15 is the node ID destination, that is the current node.
@boum Thank you! So, in my receiving function i should have the following, where 10 is sending node?
void receive(const MyMessage &message) {if (message.type==V_TRIPPED) {if (message.sensor==10) { Serial.print(message.sensor); Serial.print("\n"); Serial.print(", New status: "); Serial.print(message.getBool()); LEAK_RECEIVED=message.getBool(); } } } -
sensoror sensor ID is synonymous for child ID, one of the many sensors a single node can have.senderis the ID of the node which sent the message.On the sending node:
#define MY_NODE_ID 7 // ^ sender / node ID [...] MyMessage msg_LEAK_to_15( 10, V_TRIPPED ); // ^^ sensor / child ID [...] msg_LEAK_to_15.setDestination(15); // ^^ ID of the destination / receiving nodeOn the receiving node:
// returns sender / node ID (7) msg.getSender(); // returns sensor / child ID (10) msg.getSensor();So yes, with the changes in your latest code snipped, you should start seeing some serial output.
On a different note: Not that it would change anything here, but l'd like to advise to use the availabe getter and setter functions, whenever possible.
message.sensor==10works perfectly fine if you want to compare the current value of the variable against 10. But if you accidently omit one of the equal signs, you assign 10 to the variable instead. Bugs like these can be hard to spot - the if-condition would always evaluate true in this case. Usingmessage.getSensor()prevents such mistakes. -
sensoror sensor ID is synonymous for child ID, one of the many sensors a single node can have.senderis the ID of the node which sent the message.On the sending node:
#define MY_NODE_ID 7 // ^ sender / node ID [...] MyMessage msg_LEAK_to_15( 10, V_TRIPPED ); // ^^ sensor / child ID [...] msg_LEAK_to_15.setDestination(15); // ^^ ID of the destination / receiving nodeOn the receiving node:
// returns sender / node ID (7) msg.getSender(); // returns sensor / child ID (10) msg.getSensor();So yes, with the changes in your latest code snipped, you should start seeing some serial output.
On a different note: Not that it would change anything here, but l'd like to advise to use the availabe getter and setter functions, whenever possible.
message.sensor==10works perfectly fine if you want to compare the current value of the variable against 10. But if you accidently omit one of the equal signs, you assign 10 to the variable instead. Bugs like these can be hard to spot - the if-condition would always evaluate true in this case. Usingmessage.getSensor()prevents such mistakes.@BearWithBeard Thank you. I do not define my nodes ID, they are being assigned automatically (by controller? gateway?), but I do see nodes ID in my controller configuration. Is this a problem?
-
I'm not sure if a node's automatically assigned ID can change in special circumstances (apart from clearing the EEPROM), but as long as you don't care about which specific node sent a message in a node-to-node relationship, this shouldn't be a problem. So it's best to avoid using
msg.getSender()on the receiving node.I just added a static node ID in the example above to better illustrate what each variable means, since you seem to mix up sender and sensor.
-
I'm not sure if a node's automatically assigned ID can change in special circumstances (apart from clearing the EEPROM), but as long as you don't care about which specific node sent a message in a node-to-node relationship, this shouldn't be a problem. So it's best to avoid using
msg.getSender()on the receiving node.I just added a static node ID in the example above to better illustrate what each variable means, since you seem to mix up sender and sensor.
@BearWithBeard Sorry, I am still confused. Is it possible to publish a complete example of sending Boolean from one node to another to be used in control logic? Thank you.
-
Unfortunately, I don't have a test setup running right now, otherwise I would quickly whip up and test two minimal example sketches.
But here are some snippets that should include everything related to node-to-node communication you need. My best advice at this point, if it still confuses you, is to get rid of all the magic numbers in the code and define macros / constants.
On the leak detector node, you need the following bits:
bool leakState = false; bool previousLeakState = false; #define LEAK_CHILD_ID 0 // 0 to 254 #define LEAK_CHILD_ID_TO_NODE 100 // 0 to 254 #define LEAK_TARGET_NODE_ID 15 // The ID of the node you want to report to // Setup two separate messages. One reports to the GW, the other to the target node MyMessage msgToGw(LEAK_CHILD_ID, V_TRIPPED); MyMessage msgToNode(LEAK_CHILD_ID_TO_NODE, V_TRIPPED); void presentation() { // Register and present sketch and sensors sendSketchInfo("Leak Detector", "1.0"); present(LEAK_CHILD_ID, S_WATER_LEAK); } void setup() { // Set the destination for msgToNode permanently to the target node's ID // msgToGw doesn't need that; it defaults to 0 (=GW) msgToNode.setDestination(LEAK_TARGET_NODE_ID); } void loop() { // Check if things have changed if (leakState != previousLeakState) { // Report new state to GW and target node send(msgToGw.set(leakState)); wait(100); // Optional, but a short pause inbetween can't hurt send(msgToNode.set(leakState)); // Update state previousLeakState = leakState; } }This will inform the GW via
msgToGwas well as the the node with the ID 15 (LEAK_TARGET_NODE_ID) viamsgToNodeabout the updatedleakState.The GW will receive this message with the child ID 0 (
LEAK_CHILD_ID), node 15 will receive it with the child ID 100 (LEAK_CHILD_ID_TO_NODE). You do not need to change the child ID you send to the destination node - you can keep using the same as for the GW. Just note that you can change it.On the target node, you need this:
bool leakState = false; bool previousLeakState = false; #define LEAK_CHILD_ID_INCOMING 100 void loop() { // Check if leakState has changed if (leakState != previousLeakState) { // Do something // Update state previousLeakState = leakState; } } void receive(const MyMessage & msg) { Serial.print("Incoming message... "); // Filter out our message if (msg.getType() == V_TRIPPED && msg.getSensor() == LEAK_CHILD_ID_INCOMING) { // Update the local leakState variable leakState = msg.getBool(); // Print some infos Serial.println("is a new leak state!"); Serial.print("From (Node ID):"); Serial.println(msg.getSender()); Serial.print("Child ID: "); Serial.println(msg.getSensor()); Serial.print("State: "); Serial.println(leakState); } else { Serial.println("is something else. :("); } }Hope I didn't miss anything.
Once you got it working, it's best to remove most if not all serial prints from the receive function, as it's generally bad practice and can cause various problems.
-
Unfortunately, I don't have a test setup running right now, otherwise I would quickly whip up and test two minimal example sketches.
But here are some snippets that should include everything related to node-to-node communication you need. My best advice at this point, if it still confuses you, is to get rid of all the magic numbers in the code and define macros / constants.
On the leak detector node, you need the following bits:
bool leakState = false; bool previousLeakState = false; #define LEAK_CHILD_ID 0 // 0 to 254 #define LEAK_CHILD_ID_TO_NODE 100 // 0 to 254 #define LEAK_TARGET_NODE_ID 15 // The ID of the node you want to report to // Setup two separate messages. One reports to the GW, the other to the target node MyMessage msgToGw(LEAK_CHILD_ID, V_TRIPPED); MyMessage msgToNode(LEAK_CHILD_ID_TO_NODE, V_TRIPPED); void presentation() { // Register and present sketch and sensors sendSketchInfo("Leak Detector", "1.0"); present(LEAK_CHILD_ID, S_WATER_LEAK); } void setup() { // Set the destination for msgToNode permanently to the target node's ID // msgToGw doesn't need that; it defaults to 0 (=GW) msgToNode.setDestination(LEAK_TARGET_NODE_ID); } void loop() { // Check if things have changed if (leakState != previousLeakState) { // Report new state to GW and target node send(msgToGw.set(leakState)); wait(100); // Optional, but a short pause inbetween can't hurt send(msgToNode.set(leakState)); // Update state previousLeakState = leakState; } }This will inform the GW via
msgToGwas well as the the node with the ID 15 (LEAK_TARGET_NODE_ID) viamsgToNodeabout the updatedleakState.The GW will receive this message with the child ID 0 (
LEAK_CHILD_ID), node 15 will receive it with the child ID 100 (LEAK_CHILD_ID_TO_NODE). You do not need to change the child ID you send to the destination node - you can keep using the same as for the GW. Just note that you can change it.On the target node, you need this:
bool leakState = false; bool previousLeakState = false; #define LEAK_CHILD_ID_INCOMING 100 void loop() { // Check if leakState has changed if (leakState != previousLeakState) { // Do something // Update state previousLeakState = leakState; } } void receive(const MyMessage & msg) { Serial.print("Incoming message... "); // Filter out our message if (msg.getType() == V_TRIPPED && msg.getSensor() == LEAK_CHILD_ID_INCOMING) { // Update the local leakState variable leakState = msg.getBool(); // Print some infos Serial.println("is a new leak state!"); Serial.print("From (Node ID):"); Serial.println(msg.getSender()); Serial.print("Child ID: "); Serial.println(msg.getSensor()); Serial.print("State: "); Serial.println(leakState); } else { Serial.println("is something else. :("); } }Hope I didn't miss anything.
Once you got it working, it's best to remove most if not all serial prints from the receive function, as it's generally bad practice and can cause various problems.
@BearWithBeard THANK YOU! It works fine. The only change I had to make is to replace msg with message in all commands. Couple of questions, if you don't mind:
- Is it critical to have void receive after void loop?
- How receiving node distinguishes between same sensor ID coming from different nodes? Or, I need to assign unique sensor ID throughout complete sensors pool?
It will be useful to include working example into mySensors library.
-
Glad you got it working!
-
The order of the functions in the sketch doesn't determine their execution order, which is managed behind the scenes by the framework. You could place
receive()right below themysensors.hinclusion if you wish. -
They don't. That's why I showed you how to assign a different child ID. You could assing a unique ID per node-to-node-message to make them identifiable.
If multiple node-to-node-message end up having the same child ID, you would have to factor in other variables, likegetSender()to tell them apart. Let's say you have three nodes (IDs 1, 2, 3) sending a boolean to a fourth target node and all messages have the same child ID of 0, you could tell them apart like this:#define LEAK_CHILD_ID_INCOMING 0 // [...] void receive(const MyMessage & msg) { // Message is what we're looking for if (msg.getType() == V_TRIPPED && msg.getSensor() == LEAK_CHILD_ID_INCOMING) { // Find out where it's from switch (msg.getSender()) { case 1: // From node ID 1 leakStateNode1 = msg.getBool(); break; case 2: // From node ID 2 leakStateNode2 = msg.getBool(); break; case 3: // From node ID 3 leakStateNode3 = msg.getBool(); break; default: // From GW or 4 - 254 break; } } }But again, since you're using automatic ID assignment - I'm not sure if node IDs can change under specific circumstances. So If you make use of
getSender()you may want to consider assigning static node IDs.
-
-
Glad you got it working!
-
The order of the functions in the sketch doesn't determine their execution order, which is managed behind the scenes by the framework. You could place
receive()right below themysensors.hinclusion if you wish. -
They don't. That's why I showed you how to assign a different child ID. You could assing a unique ID per node-to-node-message to make them identifiable.
If multiple node-to-node-message end up having the same child ID, you would have to factor in other variables, likegetSender()to tell them apart. Let's say you have three nodes (IDs 1, 2, 3) sending a boolean to a fourth target node and all messages have the same child ID of 0, you could tell them apart like this:#define LEAK_CHILD_ID_INCOMING 0 // [...] void receive(const MyMessage & msg) { // Message is what we're looking for if (msg.getType() == V_TRIPPED && msg.getSensor() == LEAK_CHILD_ID_INCOMING) { // Find out where it's from switch (msg.getSender()) { case 1: // From node ID 1 leakStateNode1 = msg.getBool(); break; case 2: // From node ID 2 leakStateNode2 = msg.getBool(); break; case 3: // From node ID 3 leakStateNode3 = msg.getBool(); break; default: // From GW or 4 - 254 break; } } }But again, since you're using automatic ID assignment - I'm not sure if node IDs can change under specific circumstances. So If you make use of
getSender()you may want to consider assigning static node IDs.
@BearWithBeard Thank you!!!
-
-
Glad you got it working!
-
The order of the functions in the sketch doesn't determine their execution order, which is managed behind the scenes by the framework. You could place
receive()right below themysensors.hinclusion if you wish. -
They don't. That's why I showed you how to assign a different child ID. You could assing a unique ID per node-to-node-message to make them identifiable.
If multiple node-to-node-message end up having the same child ID, you would have to factor in other variables, likegetSender()to tell them apart. Let's say you have three nodes (IDs 1, 2, 3) sending a boolean to a fourth target node and all messages have the same child ID of 0, you could tell them apart like this:#define LEAK_CHILD_ID_INCOMING 0 // [...] void receive(const MyMessage & msg) { // Message is what we're looking for if (msg.getType() == V_TRIPPED && msg.getSensor() == LEAK_CHILD_ID_INCOMING) { // Find out where it's from switch (msg.getSender()) { case 1: // From node ID 1 leakStateNode1 = msg.getBool(); break; case 2: // From node ID 2 leakStateNode2 = msg.getBool(); break; case 3: // From node ID 3 leakStateNode3 = msg.getBool(); break; default: // From GW or 4 - 254 break; } } }But again, since you're using automatic ID assignment - I'm not sure if node IDs can change under specific circumstances. So If you make use of
getSender()you may want to consider assigning static node IDs.
@BearWithBeard Thanks to you my water leak detection system works fine. My next challenge is to let node receive Boolean (RESET_SOFT) from GW - directly from controller logic. I am looking at example RelayActuator and trying to make sense of it, see below simplified version of it. The idea is to get message from GW (node 0) and received it as sensor ID 20 on the receiving node. Is this close to what is should be?
#define MY_DEBUG #define MY_RADIO_RF24 #include <MySensors.h> bool RESET_SOFT; void setup() { } void presentation() { sendSketchInfo("Basement Water", "1.0"); present(20, S_BINARY); } void loop() { } void receive(const MyMessage &message) { if (message.getType()==S_BINARY && message.getSensor()==0) {RESET_SOFT=message.getBool(); } } -
-
Well, it looks like you are still mixing up the meaning of
sensorandsenderin the code. If the controller sends a message to the sensor you have set up in the sketch above (20), while you are comparing against 0 inreceive(), you will never detect that message. Compare against 20.Remember how I advised to get rid of magic numbers and use constants instead? If you would add something like
#define SENSOR_ID 20and use that variable instead of 0 and 20, you might be able to avoid such confusions, because you give those arguments a meaningful name.Let's try to explain it another way, so that you can adapt it to any situation in the future.
Sensor: In the context of a MySensors sketch, stop thinking of a sensor being a (physical) device. It is just a unique identifier for one of many different data points a device (MySensors calls this device a node) wants to share with others. Think of a sensor (or also often called child) as one of up to 255 wires going from one node to any other, whereby each wire represents a single type of data, like a temperature, a string, voltage level, a boolean value.
Sender: When a node sends a message, it includes a reference to itself - the node ID - as the
sender, as well as a reference to the target node as thedestination. Bothsenderanddestinationenable MySensors to route messages through the network, no matter if it is a direct A-to-B connection or if the message needs to be forwarded by multiple repeaters.The MyMessage class is used to manage those messages. It stores all kinds of information neccessary to share data between nodes, send and request commands independently from the selected transport method (RF24, Sub-GHz, wired) and controller connection (Serial, MQTT, WiFi, Ethernet).
Imagine a simplified MyMessage instance as a collection of variables and a bunch of helper functions to make your life easier. When the controller (via the GW) sends the message to the node, as you described above, the message would look like this on the receiving node:
MyMessage msg { sender = 0; // Node ID of the message's origin (the GW) destination = 7; // Node ID of this device (I assumed this number!) sensor = 20; // Child ID / data point that this message wants to update type = 3; // S_BINARY == 3 [...] getBool(); // Returns the payload as a boolean getSensor(); // Returns the value of sensor setSensor(n); // Changes the value of sensor getDestination(); // Returns the value of destination [...] }So what do you have to do if you want to update the local variable
RESET_SOFTon that node whenever it receives a new value? You have to test that the incoming message is of the expectedtypeand that it concerns the rightsensor. If you also want to make sure that only the controller or GW can cause an update ofRESET_SOFT, you must validate thatsender- in other words, the origin of this message - is valid as well.I really hope this makes sense to you, as I'm running out of ideas how to explain what is going on behind the scenes.
Maybe a look at the Serial API introduction can also help you further.
-
Well, it looks like you are still mixing up the meaning of
sensorandsenderin the code. If the controller sends a message to the sensor you have set up in the sketch above (20), while you are comparing against 0 inreceive(), you will never detect that message. Compare against 20.Remember how I advised to get rid of magic numbers and use constants instead? If you would add something like
#define SENSOR_ID 20and use that variable instead of 0 and 20, you might be able to avoid such confusions, because you give those arguments a meaningful name.Let's try to explain it another way, so that you can adapt it to any situation in the future.
Sensor: In the context of a MySensors sketch, stop thinking of a sensor being a (physical) device. It is just a unique identifier for one of many different data points a device (MySensors calls this device a node) wants to share with others. Think of a sensor (or also often called child) as one of up to 255 wires going from one node to any other, whereby each wire represents a single type of data, like a temperature, a string, voltage level, a boolean value.
Sender: When a node sends a message, it includes a reference to itself - the node ID - as the
sender, as well as a reference to the target node as thedestination. Bothsenderanddestinationenable MySensors to route messages through the network, no matter if it is a direct A-to-B connection or if the message needs to be forwarded by multiple repeaters.The MyMessage class is used to manage those messages. It stores all kinds of information neccessary to share data between nodes, send and request commands independently from the selected transport method (RF24, Sub-GHz, wired) and controller connection (Serial, MQTT, WiFi, Ethernet).
Imagine a simplified MyMessage instance as a collection of variables and a bunch of helper functions to make your life easier. When the controller (via the GW) sends the message to the node, as you described above, the message would look like this on the receiving node:
MyMessage msg { sender = 0; // Node ID of the message's origin (the GW) destination = 7; // Node ID of this device (I assumed this number!) sensor = 20; // Child ID / data point that this message wants to update type = 3; // S_BINARY == 3 [...] getBool(); // Returns the payload as a boolean getSensor(); // Returns the value of sensor setSensor(n); // Changes the value of sensor getDestination(); // Returns the value of destination [...] }So what do you have to do if you want to update the local variable
RESET_SOFTon that node whenever it receives a new value? You have to test that the incoming message is of the expectedtypeand that it concerns the rightsensor. If you also want to make sure that only the controller or GW can cause an update ofRESET_SOFT, you must validate thatsender- in other words, the origin of this message - is valid as well.I really hope this makes sense to you, as I'm running out of ideas how to explain what is going on behind the scenes.
Maybe a look at the Serial API introduction can also help you further.
@BearWithBeard Thank you! I am really embarrassed with my, let's say, luck of understanding. But you made it clear again. Everything works as required!
-
Well, it looks like you are still mixing up the meaning of
sensorandsenderin the code. If the controller sends a message to the sensor you have set up in the sketch above (20), while you are comparing against 0 inreceive(), you will never detect that message. Compare against 20.Remember how I advised to get rid of magic numbers and use constants instead? If you would add something like
#define SENSOR_ID 20and use that variable instead of 0 and 20, you might be able to avoid such confusions, because you give those arguments a meaningful name.Let's try to explain it another way, so that you can adapt it to any situation in the future.
Sensor: In the context of a MySensors sketch, stop thinking of a sensor being a (physical) device. It is just a unique identifier for one of many different data points a device (MySensors calls this device a node) wants to share with others. Think of a sensor (or also often called child) as one of up to 255 wires going from one node to any other, whereby each wire represents a single type of data, like a temperature, a string, voltage level, a boolean value.
Sender: When a node sends a message, it includes a reference to itself - the node ID - as the
sender, as well as a reference to the target node as thedestination. Bothsenderanddestinationenable MySensors to route messages through the network, no matter if it is a direct A-to-B connection or if the message needs to be forwarded by multiple repeaters.The MyMessage class is used to manage those messages. It stores all kinds of information neccessary to share data between nodes, send and request commands independently from the selected transport method (RF24, Sub-GHz, wired) and controller connection (Serial, MQTT, WiFi, Ethernet).
Imagine a simplified MyMessage instance as a collection of variables and a bunch of helper functions to make your life easier. When the controller (via the GW) sends the message to the node, as you described above, the message would look like this on the receiving node:
MyMessage msg { sender = 0; // Node ID of the message's origin (the GW) destination = 7; // Node ID of this device (I assumed this number!) sensor = 20; // Child ID / data point that this message wants to update type = 3; // S_BINARY == 3 [...] getBool(); // Returns the payload as a boolean getSensor(); // Returns the value of sensor setSensor(n); // Changes the value of sensor getDestination(); // Returns the value of destination [...] }So what do you have to do if you want to update the local variable
RESET_SOFTon that node whenever it receives a new value? You have to test that the incoming message is of the expectedtypeand that it concerns the rightsensor. If you also want to make sure that only the controller or GW can cause an update ofRESET_SOFT, you must validate thatsender- in other words, the origin of this message - is valid as well.I really hope this makes sense to you, as I'm running out of ideas how to explain what is going on behind the scenes.
Maybe a look at the Serial API introduction can also help you further.