Example Code: Xenon collects temp/humidity data & sends to Argon which then outputs to 4-digit display and Particle Console

I am a programming novice but have been fascinated with Particle devices for the last couple years. When Particle announced they were creating Mesh devices I put in my order as soon as possible. I also ordered the Grove Starter Kit for Particle Mesh.

When my package arrived I was excited to get started. Unfortunately (or fortunately!) I found this wasn't a plug-and-play environment. I searched for example codes or tutorials on using the Starter Kit with Mesh devices and discovered nothing much was out there. And while there was good information available to make the various sensors in the Starter Kit work in a standalone environment...

(see: https://github.com/particle-iot/docs/blob/master/src/content/datasheets/accessories/mesh-accessories.md)

...I wasn't able to find anything that was of much help for people at my level of programming experience to collect sensor data from one Mesh device and then send it directly to another Mesh device for processing. This seems to me to be the essence of using Mesh!

I decided to see if I could figure how to:

  1. Collect temperature and humidity data using the DHT11 sensor included in the Starter Kit connected to a Xenon, and then:
  2. Send the data to an Argon and have the Argon output the sensor data to the 4-digit display included in the Starter Kit as well as publish the data to the Particle Console.

I floundered around for awhile and was ready to give up when, through the help of @ScruffR and others on this forum, I was able to get everything working. To make things easier for those of you looking for examples to use when playing around with the Starter Kit, I figured it would make sense to post the code in this forum. Good luck and have fun!

Xenon Code (collects data from a DHT11 temp/humidity sensor and sends the data to an Argon)(NOTE: Edited 19 Jan 2018 to stop publishing and make display show all 0's if Xenon stops sending data):

//This code allows a Particle Xenon to collect temperature and humidity data and then send it to a Particle Argon
//for processing. The Argon then outputs the data to a 4-digit display and, concurrently, to the Particle Console.
//Matching code is available for the Argon to make all this work.
//Special thanks to @ScruffR and others on the Particle Forum. If any code doesn't seem right, that's on me.
//For any code that works especially well, that's because of @ScruffR.

#include <Seeed_DHT11.h>

const int 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 lastSensorRead = 0;
unsigned long sensorInterval = 5000;//I choose to read the sensor every 5 seconds

//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

  Serial.begin(9600);
}

void loop()
{
  //Use a millis timer for non-blocking code design.
  if (millis() - lastSensorRead > sensorInterval)
  {
    humidity = dht.getHumidity();
    tempF = dht.getTempFarenheit();
    tempFdelta = tempF - lasttempF;
    tempFdelta = abs(tempFdelta);
    humiditydelta = humidity - lasthumidity;
    humiditydelta = abs(humiditydelta);

    if (tempFdelta < 2 && humiditydelta < 2)//if temp difference is more than 1 degree F in 5 seconds
                                            //it's probably a spike in the data
    {
      snprintf(msg, sizeof(msg), "{\"tempF\":\"%.d\",\"humidity\":\"%.d\"}", tempF, humidity);
      Serial.println(msg);//for debugging

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

    //Update the sensor read millis timer.
    lastSensorRead = millis();

    lasttempF = tempF;
    lasthumidity = humidity;
  }
}

Argon code (receives temp/humidity data from Xenon and then outputs the data to a 4-digit display and publishes the data to the Particle Console):

//This code allows a Particle Argon to receive temperature and humidity data from a Particle Xenon
//and then output the data to a 4-digit display connected to the Argon and, concurrently, to the Particle Console.
//Matching code is available for the Xenon to make all this work.
//Special thanks to @ScruffR and others on the Particle Forum. If any code doesn't seem right, that's on me.
//For any code that works especially well, that's because of @ScruffR.

#include <JsonParserGeneratorRK.h>

#include "TM1637.h""

//pins definitions for TM1637
const int CLK = D2;
const int 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 = "Xenon1 T / H";//A temp/humidity sensor is attached to Xenon1
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
  strncpy(msg, data, sizeof(msg)-1); // copy the incoming data to the global variable msg (with boundary limit)

  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 'tempF'
    // 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);//I am collecting data from Xenon1
}

void loop()
{
  //Using a millis timer for non-blocking code design.
  if (millis() - lastPubMillis > pubInterval)
  {
    //Send your data.
    Serial.printlnf("RoomEnv: '%s'", msg);//for debugging
    Particle.publish("Xenon1 T / H", msg, PRIVATE);

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

To see the process and reasoning behind the individual steps that posed some obstacles in the development of this code, go here:

4 Likes

Do you really want to reset the display after 5 seconds, just because you didn't get an update in time?
It's unlikely that your readings get completely outdated in just 5 seconds.
You could maybe use the decimal dot of your displays to indicate whether the data is current or potentially outdated.

It's also not considered best practice to repeat a conditions (e.g. millis() - lastPubMillis > pubInterval) when you have the option to unify it into one (e.g. by nesting the other part(s) of your entire condition).

When you said

I thought that you didn't want the Particle.publish() to happen every 5 seconds, so I'd rather or at least also put the needPublish check in the Argon code.
Why is it now on the Xenon side and not on the Argon's?

I found this to be true last night when I expanded the program to multiple Xenons, all sending data back to the Argon. After I had four (4) sensors (one attached to the Argon itself) outputting to the cloud and the displays, I found not everything was getting published to the cloud. All the data was outputting to the monitor so I figured it had to be something to do with my publishing frequency resulting from the multiple timer loops.

I spend quite a few restless hours last night pondering the situation and realized I probably needed a way to separate the display output from the publishing. I just wasn't sure where to start. Lo and behold, I wake up this morning to find you gave me some direction on how to answer my own question, before I even asked!

Back to the drawing board!

1 Like