Node to Node communication



  • Hello, I see few discussions about establishing Node to Node communication, probably the most complete is here:
    https://forum.mysensors.org/topic/8941/mysensors-get-temperature-value-from-another-node-through-the-gateway/27
    Unfortunately, there are still few things which are not clear to me

    From the link above, here is what I see as working example:

    Send sketch is:

    send(pcMsg.setDestination(1).setSensor(101).set(temperature,1));

    and receive sketch is

    void receive(const MyMessage &message) {
    if (message.type==V_VAR1) {
    if (message.sensor==101) {
    // Write some debug info
    Serial.print("Incoming change for sensor:");
    Serial.print(message.sensor);
    Serial.print("\n");
    Serial.print(", New status: ");
    Serial.println(message.getFloat());
    }
    }
    I am missing here explanation of some text used in the example above, for example, in send sketch what is pcMsg, why Destination has 1 as argument, why setSensor has 101 as argument, what is temperature and 1 arguments in the last set?
    send(pcMsg.setDestination(1).setSensor(101).set(temperature,1));



  • Hey, APL2017. In this example, pcMsg is the arbitrarily choosen name of a MyMessage class instance (apidocs), like

    #define CHILD_ID 0
    #define CHILD_ID_TEMP 42
    MyMessage pcMsg(CHILD_ID, V_VAR1);
    MyMessage temperatureMessage(CHILD_ID_TEMP, V_TEMP);
    

    setDestination(destinationId) (apidocs) defines with which node you want to communicate. destinationId is the ID of the target node.

    Accordingly, setSensor(sensorId) (apidocs) defines to which sensor ID on the target node this message should be mapped to.

    set(floatValue, decimals) (apidocs) defines which float value should be sent with the given number of decimal places. Note that the set() function has a bunch of overloads for different value types - only floats accept the decimals parameter, for obvious reasons.

    Summarized, send(pcMsg.setDestination(1).setSensor(101).set(temperature,1)) attempts to send a MyMessage of type V_VAR1 with the value of the temperature variable, trimmed to one decimal place, to sensor ID 101 on node ID 1.

    The conditions in the receive() function on the destination node then checks, that the incoming message is of type V_VAR1 and its sensor ID is 101 before it prints the received variable.

    Here's a compact list of all the relevant getters and setters to manipulate a MyMessage object. For a more detailed overview, refer to the apidocs page for the MyMessage class.



  • @BearWithBeard Thank you! Will go through information you provided and try to implement shortly



  • This post is deleted!


  • This post is deleted!


  • @BearWithBeard I took my typical water leak detection node, which sends leak flag to controller and added request to also send leak flag to node 15, sensor 10 (the last set below) , but immediately this node stopped reporting leak to controller. What am I doing wrong? Do I need to define dedicated MyMessage statement for sharing data with node?

    #define MY_DEBUG 
    #define MY_RADIO_RF24
    #include <MySensors.h>
    
    #define LEAK_1_CHILD_ID 1
    #define LEAK_1_PIN  5
    
    bool LEAK_1;
    bool last_LEAK_1;
    
    MyMessage msg_LEAK_1(LEAK_1_CHILD_ID,V_TRIPPED);
    
    void setup()  
    {  
      pinMode(LEAK_1_PIN,INPUT_PULLUP);
    }
    void presentation() {
      sendSketchInfo("Water Leak", "1.0");
      present(LEAK_1_CHILD_ID, S_DOOR); 
    }
    
    void loop() 
    {
     LEAK_1 =digitalRead(LEAK_1_PIN);
     if (LEAK_1 != last_LEAK_1) 
     {
     send(msg_LEAK_1.set(LEAK_1));
     send(msg_LEAK_1.setDestination(15).setSensor(10).set(LEAK_1));
     last_LEAK_1 = LEAK_1;
     }
    }
    


  • The destination is a state inside the MyMessage object. You need to reset it every time you change destination, from controller to the other node and back:

     send(msg_LEAK_1.setDestination(0).setSensor(LEAK_1_CHILD_ID).set(LEAK_1));
     send(msg_LEAK_1.setDestination(15).setSensor(10).set(LEAK_1));
    
    

    Or you could create a second message object for the node to node communication:

    MyMessage msg_LEAK_1(LEAK_1_CHILD_ID,V_TRIPPED);
    MyMessage msg_LEAK_to_15(10,V_TRIPPED);
    
    void setup()  
    {  
      pinMode(LEAK_1_PIN,INPUT_PULLUP);
    
      // Set destination only once, sensor is set on constructor above
      msg_LEAK_to_15.setDestination(15);
    }
    //[...]
     if (LEAK_1 != last_LEAK_1) 
     {
       send(msg_LEAK_1.set(LEAK_1));
       send(msg_LEAK_to_15.set(LEAK_1));
       last_LEAK_1 = LEAK_1;
     }
    


  • @boum said in Node to Node communication:

    MyMessage msg_LEAK_to_15(10,V_TRIPPED);

    Thank you, here is what I have on receiving node (simplified). I am not getting the leak flag on receiving node. Is this correct? Elimination of sleeping did not help. I've noticed that there is no receiving commands within the LOOP, but it was advised as not required in previously referenced discussion.

    #define MY_DEBUG 
    #define MY_RADIO_RF24
    #include <MySensors.h>
    #define LEAK_RECEIVED_CHILD_ID 10
    bool LEAK_RECEIVED;
    uint32_t SLEEP_TIME = 1000; // Sleep time between reads (in milliseconds)
    MyMessage msg_LEAK_RECEIVED(LEAK_RECEIVED_CHILD_ID,V_TRIPPED);
    
    void setup()  
    {  
     }
    void presentation() {
      sendSketchInfo("Basement Water", "1.0");
      present(LEAK_RECEIVED_CHILD_ID, S_DOOR);
    }
    void receive(const MyMessage &message)
     {if (message.type==V_TRIPPED)
      {if (message.sensor==15)
        { Serial.print(message.sensor);
          Serial.print("\n");
          Serial.print(", New status: ");
          Serial.print(message.getBool());
          LEAK_RECEIVED=message.getBool();
        }
        }
     }
    void loop() 
    {
        sleep(SLEEP_TIME);
     }
    


  • 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();
        }
        }
     }
    


  • sensor or sensor ID is synonymous for child ID, one of the many sensors a single node can have. sender is 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 node
    

    On 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==10 works 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. Using message.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.



  • @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 msgToGw as well as the the node with the ID 15 (LEAK_TARGET_NODE_ID) via msgToNode about the updated leakState.

    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:

    1. Is it critical to have void receive after void loop?
    2. 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!

    1. 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 the mysensors.h inclusion if you wish.

    2. 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, like getSender() 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!!!



  • @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 sensor and sender in 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 in receive(), 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 20 and 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 the destination. Both sender and destination enable 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_SOFT on that node whenever it receives a new value? You have to test that the incoming message is of the expected type and that it concerns the right sensor. If you also want to make sure that only the controller or GW can cause an update of RESET_SOFT, you must validate that sender - 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!



  • This post is deleted!

Log in to reply
 

Suggested Topics

  • 1
  • 10
  • 1
  • 3
  • 6
  • 2

1
Online

11.4k
Users

11.1k
Topics

112.7k
Posts