Need help finding example code for sending mesh data from xenon to argon

Right to the point: I’m having trouble comprehending how to take a data point collected from a Xenon and then sending it to an Argon.

This seems like it should be Mesh 101 but I’m having a hard time understanding the Reference Documents regarding Mesh.publish, myHandler and then Mesh.subscribe. I’ve looked high and low for simple example code to work from by searching the Particle Community and beyond but I’m coming up short.

I have my Argon working perfectly collecting temp/humidity data and then outputting the data to a 4-digit display as well as the Particle Console but now I’d like to move to the next step and collect the same data with a Xenon and then sending it to the Argon for processing. This seems to be the next logical step in my learning about the Mesh environment.

Could someone help direct me to example code where someone is collecting data from a Xenon, sending it to an Argon and then processing it? I’m not asking anyone to code this, just to give me a nudge in the right direction.

Thanks!

1 Like

You can check out my mesh heartbeat code. The Argon sends a mesh.publish to which all the xenon nodes respond with an acknowledgement of that heartbeat. It wouldn’t be a stretch to add in some data collection and publish that data in the same manner.

4 Likes

Thanks, @ninjatill. Your heartbeat code was actually the very first code I loaded to my mesh devices. I had them sitting all over the house playing Marco-Polo. I spent quite a few hours tonight going through your code line by line trying to see how to apply it to my use case. I’ll try again tomorrow. Maybe a new day will lead to new discoveries.

2 Likes

These are the sections in the official reference docs that should explain everything
https://docs.particle.io/reference/device-os/firmware/xenon/#publish-
https://docs.particle.io/reference/device-os/firmware/xenon/#subscribe-

The samples won't get any simpler than shown in the docs

// receiver side - needs to setup once and will be called on demand by the system
void myHandler(const char *event, const char *data)
{
  Serial.printlnf("event=%s data=%s", event, data ? data : "NULL");
}

void setup()
{
  Serial.begin(9600);
  Mesh.subscribe("motion-sensor", myHandler);
}

  // sender side (anywhere in your code - e.g. inside `loop()`
  ...
  Mesh.publish("motion-sensor", "living room");
  ...

You setup your receiver and have it sit there minding its own busines till any sender publishes an event which will be delivered to all the subscribers where the subscription callback will be invoked and do its job.

So I think your main question is not about how to send/receive data inside your mesh but rather how to send your data - irrespectivce of scope (e.g. intra mesh vs. cloud), right?

If so, I think you shouldn't limit your search to mesh transport but rather investigate the fundamentals of wrapping your data into a publishable string.

In this regard this thread might be helpful

(make sure to read the entire thread or any other thread that explains the use of snprintf())

Once you have composed the event data string, the next step would be parsing the received data. For that you could either use sscanf() for any format string or a dedicated parser library (e.g. for JSON JsonParserGeneratorRK is a good one - this can also be used on the sender side to generate the data string)

3 Likes

Possibly, @ScruffR. Because my knowledge of how all this works is so limited, I'm not even sure what questions to ask (I don't know what I don't know, quite frankly.) But it does appear that first learning...

...will get me going in the right direction. That's exactly what I need. Thanks for the roadmap.

2 Likes

Here is a simple example of a Xenon publishing a message via mesh to an Argon every 5 seconds.

Xenon publishing message code:



int boardLed = D7; // This is the LED that is already on your device.

void setup() {
  // This part is mostly the same:
  pinMode(boardLed,OUTPUT); // Our on-board LED is output as well
  
}


// Now for the loop.

void loop() {
    
        Mesh.publish ("beam", "broken");
        digitalWrite(boardLed,HIGH);
        delay(500);
        digitalWrite(boardLed,LOW);
        Mesh.publish ("beam", "fixed");
        
        delay (5000);
}

and then on the Argon for the subscribe side :slight_smile:


int led1 = D7; // Instead of writing D0 over and over again, we'll write led1

void beamAction(const char *event, const char *data) {
    
   if (strcmp (data, "broken")==0) { digitalWrite (led1, HIGH);}
   if (strcmp (data, "fixed")==0) { digitalWrite (led1, LOW);}
    
    
    Serial.println (data);
}


void setup() {

  pinMode(led1, OUTPUT);
  Serial.begin();
  Mesh.subscribe ("beam", beamAction);
  
}


void loop() {

  // Wait 1 second...
  delay(1000);

}

This is just a simple example that I set up to get me going. The time to set up both devices from scratch, configure the mesh network and get this “hello world” test up and running took about 30 minutes.

All in all a great experience for me.

3 Likes

Thanks, @Grenello, every little bit helps. But I think what I need to learn right now is how to publish two (2) variables in a string from the Xenon (temp & humidity), subscribe to those same two (2) variables with my Argon, and then use the variables to execute a task with the Argon (publish to the Console and output to a 4-digit display.) It's all out there starting with the links @ScruffR suggested, I just have to figure out what it all means (hopefully before my brain explodes!)

1 Like

Sorry, I misunderstood the question.

As ScruffR advises, the first thing is to prepare a meaningful string to transport your message and data. To achieve this, a JSON message is the way to go. It gives you a structured ability to send multiple data fields in a single message. In essense it is nothing more than a text string, but its brilliance is in its structure.

So the JSON message to send your two data points would look like this:

{“humidity”:“75.5”,“temperature”:“24.0”}

The words “humidity” and “temperature” are the key, and the values represent your data.

Here is a simple program for the Xenon to prepare the message and send it via mesh to the Argon.

int boardLed = D7; // This is the LED that is already on your device.

float temperature = 23.6;
float humidity = 75.1;

char msg [100];

void setup() {

  pinMode(boardLed,OUTPUT); // Our on-board LED is output as well
  
  Serial.begin();
  
}


// Now for the loop.

void loop() {
    
        temperature += 0.1;
        humidity += 0.1;

        snprintf(msg, sizeof(msg), "{\"humidity\":\"%.1f\",\"temperature\":\"%.1f\"}", humidity, temperature);        
        
        Serial.println (msg);
        
        Mesh.publish ("beam", msg);
        Mesh.publish ("beam", "broken");
        digitalWrite(boardLed,HIGH);
        delay(500);
        digitalWrite(boardLed,LOW);
        Mesh.publish ("beam", "fixed");
        
        delay (5000);
}

The Argon program shown in an earlier post above will now println the message on a terminal. This JSON can now be deserialised and converted to new variables in your Argon program.

Let me know if you would like an example of this.

2 Likes

If you have the Grove Kit try this:

SYSTEM_MODE(MANUAL); // Will controll manually for mesh connections

#include <Seeed_DHT11.h>

#define DHTPIN D2
#define SENDLED D7

DHT dht(DHTPIN);

unsigned long previousTime;
int LEDSTATE = LOW;

const long interval = 60 * 1000 * 30; // Delay Amount: 30 Minutes
const char* device = "XPX-Xenon-01";

void setup() {
    pinMode(SENDLED, OUTPUT);
    dht.begin();
}

void loop() {
    
    unsigned long currentTime = millis();
    
    if ( currentTime - previousTime >= interval)
    {
        previousTime = currentTime;
        
        Mesh.on();
        Mesh.connect();
        
        while ( Mesh.connecting() )
        {
            // pause for connection
        }
        
        if ( Mesh.ready() )
        {
            char data[128];
            sprintf(data, "{\"Temperature\":\%.2f\, \"Humidity\":\%.2f\, \"Battery Voltage\":\%.2f\, \"Device\":\"%s\"}", checkTemp(), checkHumidity(), checkBattery(), device);
            Mesh.publish("Environment Read", data);
            digitalWrite(SENDLED, HIGH);
            delay(2000);
            digitalWrite(SENDLED, LOW);
            Mesh.off();
        }

    }
    
}

float checkHumidity()
{
    return dht.getHumidity();
}

float checkTemp()
{
    return dht.getTempFarenheit();
}

float checkBattery()
{
    return analogRead(BATT) * 0.0011224;
}

Thanks, @Grenello and @callen. I spent quite a bit of time last night trying to understand how JSONs work but I'm still not there yet. Your examples will help. I'll give them a try soon.

Just to make this post complete so others like me (hobbyist) might benefit, what I'm trying to do is take my code that works great on a standalone Argon and essentially split it into two (2) codes, one for a Xenon (data collection) and one for an Argon (which will take the data and output the data to the Particle Console and to a 4-digit display.) The temp/humidity sensor and 4-digit display were included in the Grove Starter Kit for Particle Mesh. My standalone code is:

// This #include statement was automatically added by the Particle IDE.
#include <Seeed_DHT11.h>

#include "TM1637.h"

#define CLK D2//pins definitions for TM1637
#define DIO D3

TM1637 tm1637(CLK,DIO);

#define DHTPIN D4//set pin for DHT

DHT dht(DHTPIN);

int tempF = 0;
int humidity = 0;

int lasttempF = 0;//I'll use these to get rid of spikes in the data
int tempFdelta;
int lasthumidity = 0;
int humiditydelta;

unsigned long lastPubMillis = 0;
unsigned long pubInterval = 5000;

//Global variables to store messages.
char *message = "Room Environment";
char msg[128];

void setup()

{
  tm1637.init();
  tm1637.set(BRIGHT_TYPICAL);//BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
  tm1637.point(POINT_ON);
  
  dht.begin(); //initialize the sensor
  // variable name max length is 12 characters long
  Particle.variable("tempF", tempF);
  Particle.variable("humidity", humidity);
  
  delay(1500);

}
void loop()
{
        //Use a millis timer for non-blocking code design.
    if (millis() - lastPubMillis > pubInterval) 
    {
        humidity = dht.getHumidity();
        tempF = dht.getTempFarenheit();
        tempFdelta = tempF-lasttempF;
        tempFdelta = abs(tempFdelta);
        humiditydelta = humidity-lasthumidity;
        humiditydelta = abs(humiditydelta);
        
            if(tempFdelta <2 && humiditydelta <2)
            {
                    //Create your message to publish and load into the message buffer.
                    snprintf(msg,arraySize(msg)-1,"T = %d;H = %d",tempF,humidity);
            
                    //Send your data.
                    Particle.publish("Room Environment",msg);
                
                    int digit1 = tempF/10;
                    int digit2 = tempF % 10;
                    int digit3 = humidity/10;
                    int digit4 = humidity % 10;
                    tm1637.display(0,digit1);
                    tm1637.display(1,digit2);
                    tm1637.display(2,digit3);
                    tm1637.display(3,digit4);
            }

        //Update your pub millis timer.
        lastPubMillis = millis();
                            
        lasttempF = tempF;
        lasthumidity = humidity;
    }
}

Here’s where I’m at right now. While both codes compile, I only get a single temp/humidity reading of 0/0 so obviously I’m doing something wrong, probably something basic. I’ll have to look at it more tomorrow.

Xenon code:

// This #include statement was automatically added by the Particle IDE.
#include <Seeed_DHT11.h>

#include "Particle.h"

#define DHTPIN D4//set pin for DHT

DHT dht(DHTPIN);

int tempF = 0;
int humidity = 0;

int lasttempF = 0;//I'll use these to get rid of spikes in the data
int tempFdelta;
int lasthumidity = 0;
int humiditydelta;

unsigned long lastPubMillis = 0;
unsigned long pubInterval = 5000;

//Global variables to store messages.
char *message = "Xenon1";
char msg[128];

void setup()

{
    Particle.variable("tempF", tempF);
    Particle.variable("humidity", humidity);
    
    dht.begin(); //initialize the sensor
}
void loop()
{
        //Use a millis timer for non-blocking code design.
    if (millis() - lastPubMillis > pubInterval) 
    {
        humidity = dht.getHumidity();
        tempF = dht.getTempFarenheit();
        tempFdelta = tempF-lasttempF;
        tempFdelta = abs(tempFdelta);
        humiditydelta = humidity-lasthumidity;
        humiditydelta = abs(humiditydelta);
        
            if(tempFdelta <2 && humiditydelta <2)
            {
                    //Create your message to publish and load into the message buffer.
                    char data[128];
                    sprintf(data, "{\"Temperature\":\%d\, \"Humidity\":\%d\}", tempF, humidity);
                    
                    //Send your data.
                    Particle.publish("Xenon1",msg);
             }

        //Update your pub millis timer.
        lastPubMillis = millis();
                            
        lasttempF = tempF;
        lasthumidity = humidity;
    }
}

Argon Code:


// This #include statement was automatically added by the Particle IDE.
#include "TM1637.h""

#define CLK D2//pins definitions for TM1637
#define DIO D3

TM1637 tm1637(CLK,DIO);

unsigned long lastPubMillis = 0;
unsigned long pubInterval = 5000;

int tempF;
int humidity;

//Global variables to store messages.
char *message = "Room Environment";
char msg[128];

void myHandler(const char *event, const char *data){
}

void setup()

{
  tm1637.init();
  tm1637.set(BRIGHT_TYPICAL);//BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
  tm1637.point(POINT_ON);

  delay(1500);
  
  Mesh.subscribe("Xenon1",myHandler);
}
void loop()
{
        //Use a millis timer for non-blocking code design.
    if (millis() - lastPubMillis > pubInterval) 
            {
                     //Create your message to publish and load into the message buffer.
                    snprintf(msg,arraySize(msg)-1,"T = %d;H = %d",tempF,humidity);
            
                    //Send your data.
                    Particle.publish("Room Environment",msg);
                
                    int digit1 = tempF/10;
                    int digit2 = tempF % 10;
                    int digit3 = humidity/10;
                    int digit4 = humidity % 10;
                    tm1637.display(0,digit1);
                    tm1637.display(1,digit2);
                    tm1637.display(2,digit3);
                    tm1637.display(3,digit4);
            }

        //Update your pub millis timer.
        lastPubMillis = millis();
}

Can you provide some more background of your intended setup.
Currently I guess your Argon is directly talking with the sensors and the display and you want to separate this into Xenon talking with the sensors, sending the collected data to the Argon and the Argon will receive that and for one display that on the 4-digit display and publish to the cloud, right?

If so, the first thing to change in your Argon code is this

An empty subscribe handler won't do any parsing of the received data, but without that received data you haven't got anything to display or publish.

To start with, you can just print out the received event data

void myHandler(const char *event, const char *data){
  Serial.println(data);
}

to see whether your device actually gets any data from the Xenon.

Once you have established that, I'd (again) recommend to look at the JsonParserGeneratorRK library - especially this example which illustrates how to subscribe to an event and then parse the received JSON string.

Correct!

I've looked at it (again and again and again.) I'm just not understanding it by way of the example. At this point my plan is to search around for some other examples in order to try and grasp the concept. Once I get a better understanding, I'll work it into my code and see what happens. If I get stuck again, I'll come back and ask for some more guidance.

Ultimately, I hope to have some working code to come back and post. From what I can see, there's not a lot to work from yet.

I'm sure there will be plenty of examples to work from before long. It seems to me that in order to use the Grove Starter Kit (or any sensors for that matter) the most basic task needed to work when using mesh is how to collect variables using one device and then send them to another device.

Thanks again, @ScruffR.

I agree, the examples in that library are showing a lot of stuff in one go.
But the main points for your needs shouldn’t be any more complicated than this

void myHandler(const char *event, const char *data){
  Serial.println(data);
  jsonParser.clear();
  jsonParser.addString(data);
  if (jsonParser.parse()) {
    if (!jsonParser.getOuterValueByKey("Temperature", tempF)) {
      Serial.println("failed to get Temperature");
    }
    if (!jsonParser.getOuterValueByKey("Humidity", humidity)) {
      Serial.println("failed to get Humidity");
    }
  }
}

I really appreciate all your help on this, @ScruffR. I don't know if you have a day job but you sure spend a lot of time helping people on this forum!

I've tried to make this work but now I'm resorting to cutting and pasting crap hoping something will start working, with no understanding of what I'm doing. I'm in over my head. Rather than waste people's time I'm going to wait on the sidelines until someone posts a similar project or tutorial. In the meantime I'll play around with all this some more and see if I start to grasp what's going on.

Thanks again!

Yes, I do have a day job too.

Does my suggested code work? I've just written it off the top of my head without acctually trying it out :blush:

But if it does, it's not that complicated (as long you don't want to care how the JSON parser works internally).

I'll add some comments to the code

// this will be called whenever an event arrives we subscribed to
void myHandler(const char *event, const char *data){
  Serial.println(data);       // print out the data as it comes in for debugging

  jsonParser.clear();         // make sure the parser buffer is fresh and empty
  jsonParser.addString(data); // copy the received data into the parser buffer for it to work with
  if (jsonParser.parse()) {   // let the parser do its job and split up the data internally
    // first ask the parser for the value connected with the key 'Temperature' 
    // if this is successful pop that value into the provided variable tempF
    // if not, the function will return false which - in turn - triggers the error output
    if (jsonParser.getOuterValueByKey("Temperature", tempF) == false) {
      Serial.println("failed to get Temperature");
    }
    // do the same for 'Humidity'
    // if(!someBoolean) is short hand for if(someBoolean == false) 
    if (!jsonParser.getOuterValueByKey("Humidity", humidity)) {
      Serial.println("failed to get Humidity");
    }
  }
}

Does this make things clearer?

2 Likes

Yes, it does! But I'm still not understanding some of the basics for getting the data from the Xenon to the Argon.

After trying so many things, my code got a bit messy. I went back to each device, cleaned the code up, and then added code to print and make sure the Xenon was still collecting and outputting the correct data. It is.

Things are not so great on the Argon side. On the Particle Console I get "null" for Room Environment. If I comment out the print associated with the json parser and let it try to print to the monitor, I get nothing except carriage returns every 5 seconds. If I uncomment the json print and comment out the serial monitor print I only get the "failed to get..." comments. So it appears I'm not getting the data from the Xenon to the Argon.

At the risk of continued public embarrassment :roll_eyes:, my current code on the Xenon:

// This #include statement was automatically added by the Particle IDE.
#include <JsonParserGeneratorRK.h>

#include <Seeed_DHT11.h>

#define DHTPIN D4//set pin for DHT

DHT dht(DHTPIN);

int tempF = 0;
int humidity = 0;

int lasttempF = 0;//I'll use these to get rid of spikes in the data
int tempFdelta;
int lasthumidity = 0;
int humiditydelta;

unsigned long lastPubMillis = 0;
unsigned long pubInterval = 5000;

//Global variables to store messages.
char *message = "Xenon1";
char msg[128];

// This creates a buffer to hold up to 256 bytes of JSON data (good for Particle.publish)
JsonWriterStatic<256> jw;

void setup()

{
    Particle.variable("tempF", tempF);
    Particle.variable("humidity", humidity);
    
    dht.begin(); //initialize the sensor
    
    Serial.begin(9600);
}
void loop()
{
    //Use a millis timer for non-blocking code design.
    if (millis() - lastPubMillis > pubInterval) 
    {
        humidity = dht.getHumidity();
        tempF = dht.getTempFarenheit();
        tempFdelta = tempF-lasttempF;
        tempFdelta = abs(tempFdelta);
        humiditydelta = humidity-lasthumidity;
        humiditydelta = abs(humiditydelta);

            if(tempFdelta <2 && humiditydelta <2)
            {

                //print to make sure data is actually there
                Serial.print("tempF:  ");
                Serial.println(tempF);
                Serial.print("humidity:  ");
                Serial.println(humidity);
                Serial.println();
                
                {
                JsonWriterAutoObject obj(&jw);

		        // Add various types of data
	        	jw.insertKeyValue("tempF", tempF);
		        jw.insertKeyValue("humidity", humidity);
                }

	        	//Send the data.
                Mesh.publish("Xenon1",msg);
                
             }

        //Update the pub millis timer.
        lastPubMillis = millis();
                            
        lasttempF = tempF;
        lasthumidity = humidity;
    }
}

My current code on the Argon:

// This #include statement was automatically added by the Particle IDE.
#include <JsonParserGeneratorRK.h>

// This #include statement was automatically added by the Particle IDE.
#include "TM1637.h""

#define CLK D2//pins definitions for TM1637
#define DIO D3

TM1637 tm1637(CLK,DIO);

unsigned long lastPubMillis = 0;
unsigned long pubInterval = 5000;

int tempF;
int humidity;

//Global variables to store messages.
char *message = "Room Environment";
char msg[128];

void printJson(JsonParser &jp);

// Create a parser to handle 2K of data and 100 tokens
JsonParserStatic<2048, 100> jsonParser;

// this will be called whenever an event arrives we subscribed to
void myHandler(const char *event, const char *data){
  Serial.println(data);       // print out the data as it comes in for debugging

  jsonParser.clear();         // make sure the parser buffer is fresh and empty
  jsonParser.addString(data); // copy the received data into the parser buffer for it to work with
  if (jsonParser.parse()) {   // let the parser do its job and split up the data internally
    // first ask the parser for the value connected with the key 'Temperature' 
    // if this is successful pop that value into the provided variable tempF
    // if not, the function will return false which - in turn - triggers the error output
    if (jsonParser.getOuterValueByKey("tempF", tempF) == false) {
      Serial.println("failed to get Temperature");
    }
    // do the same for 'Humidity'
    // if(!someBoolean) is short hand for if(someBoolean == false) 
    if (!jsonParser.getOuterValueByKey("humidity", humidity)) {
      Serial.println("failed to get Humidity");
    }
  }
}

void setup()

{
  tm1637.init();
  tm1637.set(BRIGHT_TYPICAL);//BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
  tm1637.point(POINT_ON);
  
  Serial.begin(9600);

  delay(1500);
  
  Mesh.subscribe("Xenon1",myHandler);
}
void loop()
{
        //Use a millis timer for non-blocking code design.
    if (millis() - lastPubMillis > pubInterval) 
            {
                /*Serial.print("tempF:  ");
                Serial.println(tempF);
                Serial.print("humidity:  ");
                Serial.println(humidity);
                Serial.println();*/
            
                //Send your data.
                Particle.publish("Room Environment",msg);
                
                int digit1 = tempF/10;
                int digit2 = tempF % 10;
                int digit3 = humidity/10;
                int digit4 = humidity % 10;
                tm1637.display(0,digit1);
                tm1637.display(1,digit2);
                tm1637.display(2,digit3);
                tm1637.display(3,digit4);
            }

        //Update your pub millis timer.
        lastPubMillis = millis();
}

In your Xenon code you are not actually populating msg, so no surprise that you don't get any data on the Argon side :wink:

You only feed the data into your jw object, but never take the data from that object to insert it into msg. How should that magically get from one place to the other?

That sends the data that is in msg which is nowhere changed in your code, so it is still at its initial state of being an empty string.

Because it's magic and magicians never reveal their secrets? Or it could be because I needed to add this line to my Xenon:

 snprintf(msg, sizeof(msg), "{\"tempF\":\"%.d\",\"humidity\":\"%.d\"}",tempF,humidity);

Which (to me) is magical because I can now see the data on the monitor for both the Xenon and Argon!

{"tempF":"71","humidity":"22"}

So I'm getting closer! And now I know I've sent the data from the Xenon to the Argon. Yay! Unfortunately, I'm still getting "null" on the console and my 4-digit display only displays 00:00.

1 Like

The answer is the same: You don't set msg.

But the solution is slightly different

// this will be called whenever an event arrives we subscribed to
void myHandler(const char *event, const char *data){
  Serial.println(data);              // print out the data as it comes in for debugging
  strncpy(msg, data, sizeof(msg)-1); // copy the incoming data to the global variable msg (with boundary limit)
  ...
}

For that you should check whether your variables tempF and humidity actually get set in the subscription handler.

BTW, since you are creating the JSON string on the Xenon via snprintf() you won't need the jw JSON generator.
Or if you want to keep using it, don't us snprintf() but rather

  strncpy(msg, jw.getBuffer(), sizeof(msg)-1);  // copy the created JSON string to global variable msg
1 Like