Particle Cloud "Cheat Sheet"

The EEPROM area is accessible from your code, reflecting it persistent nature. Like other MCU's re-flashing won't affect EEPROM; as it should be.

If it looks like a duck, and it quacks like a duck... well then, what else would you call it?

#define WHATEVER_THE_HECK_YOU_PREFER  EEPROM

:wink:

1 Like

So, re-FLASHing doesn’t clear “EEPROM”. Then if you reprogram a Photon to do something new you better first run the following:

void setup() { 
  EEPROM.clear(); // get rid of leftovers
}
void loop() {
  delay(1000);
}

That’s correct. Not a bad idea to have a “ClearEEPROM” app. You could actually have it clear the EEPROM and then reenter DFU/Safe Mode so it’s 100% ready for reflashing. (would also indicate that it was done clearing the flash)

or... simply initialize the EEPROM in the state(s) you want your project to begin...

// comment this block to initialize EEPROM
  EEPROM.get(0 * sizeof(DeviceTime), onTime_Weekdays);
  weekdayTimer.setStartTime(onTime_Weekdays.theHour, onTime_Weekdays.theMinute);
  EEPROM.get(1 * sizeof(DeviceTime), offTime_Weekdays);
  weekdayTimer.setEndTime(offTime_Weekdays.theHour, offTime_Weekdays.theMinute);
  EEPROM.get(2 * sizeof(DeviceTime), onTime_Weekends);
  weekendTimer.setStartTime(onTime_Weekends.theHour, onTime_Weekends.theMinute);
  EEPROM.get(3 * sizeof(DeviceTime), offTime_Weekends);
  weekendTimer.setEndTime(offTime_Weekends.theHour, offTime_Weekends.theMinute);
  EEPROM.get(4 * sizeof(DeviceTime), sunrise);
  EEPROM.get(5 * sizeof(DeviceTime), sunset);
  int timeZoneOffset = 0;
  EEPROM.get(6 * sizeof(DeviceTime), timeZoneOffset);
  Time.zone(timeZoneOffset);
  EEPROM.get(7 * sizeof(DeviceTime), mode);

  // uncomment this block to initialize EEPROM
  /*EEPROM.put(0 * sizeof(DeviceTime), onTime_Weekdays);
  EEPROM.put(1 * sizeof(DeviceTime), offTime_Weekdays);
  EEPROM.put(2 * sizeof(DeviceTime), onTime_Weekends);
  EEPROM.put(3 * sizeof(DeviceTime), offTime_Weekends);
  EEPROM.put(4 * sizeof(DeviceTime), sunrise);
  EEPROM.put(5 * sizeof(DeviceTime), sunset);
  int timeZoneOffset = -5;
  EEPROM.put(6 * sizeof(DeviceTime), timeZoneOffset);
  Time.zone(timeZoneOffset);
  Time.zone(-5);
  EEPROM.put(7 * sizeof(DeviceTime), mode);

in your setup() kind-of-thing...

I’m wondering if the reference to “only between Particle device” statement might be a bit misleading to the newbie. Yes, you do some clarification about capabilities via cURL, but there are other avenues, such as from PHP (or any other scripting language) through which data can be sent to Particle devices that have subscribed to a particular topic. Also, I don’t see a reference to webhooks which provide a convenient means of getting data out of the Particle cloud via the Publish function with an associated Subscribe receiving the response.

I offer these not as criticisms, but rather as food for thought to consider for revisions to your cheat sheet.

1 Like

@ctmorrison: How about examples? Esp. PHP. I’ve never come to terms with “webhooks”. And avoid javascript where possible.

@tommy_boy: re Raspberry Pi. No one would use a kludge like EEPROM on a Linux system. See my previous rant at FEEDBACK: Raspberry Pi particle-agent structure -- complaint

My original topic here was to improve my cheat-sheet – have the best info on 1 page. Have at it.

In regards to publish/subscribe; it's using an HTML standard 'Server Sent Events'. It's nothing Particle specific, and having to write specific examples for something general like that hasn't really got priority I can imagine. The docs have a description of the API which in most cases should suffice to get you started.
If I'm not mistaken, there are some PHP examples floating around the forums as well.
Any specific reason why you don't like Javascript?

Ask and you shall receive: :wink:

I’m usually quite tentative sharing code, since I’m a bit of a hack. However, I wouldn’t have gotten anywhere without code examples shared on this forum and others. So, with significant trepidation, I’ll offer the following example of how to use publish(), subscribe(), webhooks and PHP to allow a single code base to be utilized across multiple devices to allow them to get individualized configuration information.

This example would allow two devices to get specific configuration information to allow them to subsequently publish data to a Grovestreams account. As my device population grew along with the info I wanted to send, this approach became too cumbersome, so I re-wrote it to utilize a MySQL database. That approach is far more flexible and elegant. However, I hope this suffices to show how to utilize publish() and subscribe() in a fashion other than device to device communications as I alluded to in my earlier comment.

The following is hacked together from working code as an example, but has not been tested per se.

There are three chunks of code in the example: Particle device code, Particle webhook code and PHP code. You’ll need a web server somewhere to host the PHP code.

In a nutshell, each device calls the PHP script with its device ID as the “key” to its configuration information. The PHP script replies with the device specific Grovestreams configuration info ALONG with the device ID, so that the receiving devices know which messages to process and which to ignore. They should only process responses that include their device ID.

================= Start Particle code
//you'll need these libraries for the following example
#include "SparkJson.h"
#include "string.h"

//declare some globals for all for all of this to work
char gPublishString[200];     //declare string for multiple uses
char gDeviceID[25];           //holds device ID
bool gHasCredentials = false; //indicates if credentials have been received
char gGSAPIkey[50];           //GroveStreams API key
char gComponentID[20];        //GroveStreams component ID 
char gPeriod[10];             //holds the string version of the loop period in seconds
int  gPeriodi = 60001;         //holds the integer version of the loop period in milliseconds

void setup() {
//all your other setup() code goes in here
Particle.subscribe("hook-response/getGSconfig",getGSConfigHandler,MY_DEVICES); //directs the response from the PHP script
String myID = System.deviceID();
myID.toCharArray(gDeviceID,25);
sprintf(gPublishString,"{\"deviceID\":\"%s\"}",gDeviceID);
Particle.publish("getGSConfig",gPublishString,60,PRIVATE); //sends the configuration request via the webhook "getGSConfig"
}


void loop() {
//all your main loop code here
//I also use Particle.publish() to write data to GroveStreams via another webhook on a periodic basis in the main loop,
//but that's another example if desired!
}

void getGSconfigHandler(const char *name, const char *data){
  char _marvin[200];
  strncpy(_marvin,data,199);
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(_marvin);
  const char* deviceID = root["deviceID"];
  //check to see if the device ID in the message is the same as this device.  otherwise ignore the configuration
  if (strcmp(deviceID,gDeviceID)==0) {
    gHasCredentials = true;
    const char* period = root["period"];
    strcpy(gPeriod,period);
    gPeriodi = atoi(gPeriod)*1000;
    Serial.print("The period in milliseconds: ");
    Serial.println(gPeriodi);
    const char* gsAPIkey = root["gsAPIkey"];
    strcpy(gGSAPIkey,gsAPIkey);
    const char* componentID = root["componentID"];
    strcpy(gComponentID,componentID);
    //Blink the LED to indicate we've gotten credentials
    int led7 = D7;
    pinMode(led7, OUTPUT);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    delay(100);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
  }
  return;
}
================= End Particle code

================= Start Webhook code for webhook "getGSConfig"
{
    "event": "getGSconfig",
    "url": "http://www.yourwebsite.com/particlestuff/photonconfig.php",
    "requestType": "GET",
    "query" : {
       "deviceID" : "{{deviceID}}"
    },
    "mydevices": true,
    "noDefaults": true
}
================= End Webhook code

================= Start PHP code stored on your webserver in the file "photonconfig.php"
<?php
//particlegsconfig --> get GroveStreams configuration for Particle Photon/Electron
$deviceID = $_GET["deviceID"];
$foundID = "no";
//This version simply uses IF statements to select which response is appropriate and works for a small number of devices
//A more elegant solution is to utilize a MySQL database to hold the configuration data that should be sent back to the device
//This is a somewhat of a bare bones example
//All the devices 
//
//Photon1
if ($deviceID == "123456789012345678901234"){
    echo "{\"deviceID\":\"123456789012345678901234\",\"gsAPIkey\":\"12345678-1234-1234-1234-123456789012\",\"componentID\":\"photon1\",\"period\":\"300\"}";
	$foundID = "yes";}
//
//Photon2
if ($deviceID == "432109876543210987654321"){
    echo "{\"deviceID\":\"432109876543210987654321\",\"gsAPIkey\":\"12345678-1234-1234-1234-123456789012\",\"componentID\":\"photon2\",\"period\":\"60\"}";
	$foundID = "yes";}
//
if($foundID=="no") {
	echo "deviceID not found";}      
?>
================= End Webhook code
5 Likes

Hi, I was wondering if a Particle.variable() is accessible from a different particle device, or only from a laptop or other computer? The reason is because I was using publish/subscribe between two photons so that one can tell the other when to turn on/off some LEDs. But sometimes the subscriber receives the “turn ON” message then doesn’t get the “turn OFF” message, so it keeps the LED on longer than desired. My thinking is if I can use particle.variable(), maybe I can have the receiver periodically check the status of the variable, so if it misses one check, it will get it the next time?

No, that is not possible. I think you need to figure out why the receiver isn't getting the "turn off" message. Is it always getting the "turn on" message, but sometimes not the "turn off" message, or are both sometimes being missed? Are you publishing more often than the rate limit of 1 per second?

1 Like

Hi Ric,
Thanks for your reply. The receiver often misses the “turn off” as well, I just didn’t mention it because it’s less of a problem for my purposes. I think publishing too often may have been the culprit. I reprogrammed the sender to publish the desired state of the LED every second. So if the receiver misses a “turn off” it will hopefully get the next one. I can still notice that the receiver is missing a lot of messages but it’s sort of working now.
Thanks again!

If you slowed down publish rate to 1/sec, publishing too often was definitively the cause. Particle.publish() is rate limited to 1 event per second (with shout burst of 4 allowd).

https://docs.particle.io/reference/firmware/photon/#particle-publish-

@asp, the receiving Photon should not be missing events unless your code is interfering. Is your receiver running with system threading enabled? Do you have any blocking code in loop()?

Hi @peekay123,
I simplified my code as much as possible to make sure nothing in the loop was causing missed messages. I actually removed the loop entirely I am starting to think that maybe it’s a not-so-great wifi network. Not just because of missed messages but often the messages are not only missed, but the photon loses connection for a moment and starts blinking green before reconnecting. I’ll post all the code I’m using below.

Here are the sketches for both sender and receiver:

Sender:

String touchMsg;
String sqMsg;
String msg;

// Indicator LEDs so we know that the interrupts were called
const int touchLED = A0; // RED
const int sqLED = A1; // GREEN

// Input pins wired to interrupts
const int touch = D1;
const int squeeze = D2;

// Onboard LED used to indicate when messages are published
const int led = D7;

// The state of each input, modified by the interrupts
volatile bool touchState = false;
volatile bool sqState = false;

bool success;

long t; //time keeper

void setup() {
    // configure pins
    pinMode(touchLED, OUTPUT);
    pinMode(sqLED, OUTPUT);
    pinMode(touch, INPUT);
    pinMode(squeeze, INPUT);
    pinMode(led, OUTPUT);
    
    // attach the interrupts
    attachInterrupt(touch, touchDel, CHANGE);
    attachInterrupt(squeeze, sqDel, CHANGE);
    
    // illuminate onboard LED to indicate startup
    digitalWrite(led, HIGH);
    delay(1000);
    digitalWrite(led, LOW);
    
    // initialize timekeeper
    t = millis();
}

void loop() {
  
  // roughly every 2 seconds
  if (millis() - t > 2000) {
      
      // LED turns on to indicate we are trying to send a message
      digitalWrite(led, HIGH);
      
      // configure the two strings as "true" or "false" depending on respective input states
      // also set outputs of their indicator LEDs so we can tell whether hardware/interrupts are working
      if (touchState) {
          digitalWrite(touchLED, HIGH);
          touchMsg = "true";
      } else {
          digitalWrite(touchLED, LOW);
          touchMsg = "false";
      }
      if (sqState) {
          digitalWrite(sqLED, HIGH);
          sqMsg = "true";
      } else {
          digitalWrite(sqLED, LOW);
          sqMsg = "false";
      }
      
      // concatenate the two strings with a ',' to form the event data
      msg = touchMsg + "," + sqMsg;
      
      // finally, attempt to publish
      if ( (Particle.publish("STATE", msg, 60, PRIVATE)) ) {
          
          // if we get to here, publish was successful
          // a short delay allows a human eye to notice the LED blink
          delay(200);
          
          // if the LED stays on for more than a split second, publish failed
          digitalWrite(led, LOW);
      }
      
      // reset timer
      t = millis();
  }
}

void touchDel() {
    // Whenever the voltage on the touch sensors input pin changes, update the state of that input
    touchState = digitalRead(touch);
}

void sqDel() {
    // The "squeeze" (pressure) sensor is wired to a comparator 
    // that drives its output low when the sensor is squeezed, so negate it
    sqState = !(digitalRead(squeeze));
} 

Receiver:

//output pins
const int led = D7;
const int vib = D1;
const int heat = D0;

void setup() {
    pinMode(vib, OUTPUT);
    pinMode(led, OUTPUT);
    pinMode(heat, OUTPUT);
    
    Particle.subscribe("STATE", myHandler, MY_DEVICES);
    
    // Activate onboard LED and vibrating motor for 2 seconds when finishing setup
    digitalWrite(led, HIGH);
    digitalWrite(vib, HIGH);
    delay(2000);
    digitalWrite(led, LOW);
    digitalWrite(vib, LOW);
}

void loop() {
    
}

void myHandler(const char *event, const char *data)
{
    
    // parse the event data into 2 tokens, each representing the state of an output
    char *str = strdup(data);
    char *token[2];
    for (int i=0; i<2; i++) {
        token[i] = strsep(&str, ",");
    }
    free(str);
    
    /* 
     * Illuminate onboard LED to indicate a message was received.
     * The sender illuminates its onboard LED to indicate a message was sent.
     * The fact that the receiver often doesn't illuminate soon after indicates
     * to me that messages are not received.
    */
    digitalWrite(led, HIGH);
    
    // If the first token is "true", activate the vibrating motor
    // Else, turn it off
    if (strstr(token[0], "true")) {
        digitalWrite(vib, HIGH);
    } else {
        digitalWrite(vib, LOW);
    }
    
    // If the second token is "true", activate the heater
    // Else, turn it off
    if (strstr(token[1], "true")) {
        digitalWrite(heat, HIGH);
    } else {
        digitalWrite(heat, LOW);
    }
    
    delay(200);
    digitalWrite(led, LOW);
}
1 Like

@asp1020, looking at your code I would suggest to not use String vars in the Sender and use char[] strings instead. This will prevent any heap usage creep.

You may want to consider adding debouncing for your touch and squeeze sensors to prevent “chatter” and multiple publish events. Also, using a CHANGE interrupt will cause the interrupt to fire twice on sensor activity -going active then going inactive!

You may also want to consider running in SYSTEM_MODE(SEMI_AUTOMATIC) so you can control connectivity, especially if it is intermittent. You should wrap your cloud commands with if (Particle.connected()) so you don’t attempt the publish if you are not connected. :wink:

1 Like

Re peekay123 … “use char[] instead”. Can you use the C form for Particle.function? Below from the Reference:
// EXAMPLE USAGE

int brewCoffee(String command); //i.e., … brewCoffee(char *command) ???

void setup()
{
// register the cloud function
Particle.function(“brew”, brewCoffee);
}

@rch, no in that case you need String because the function is cast that way. One thing with Strings is that you canstring.reserve(bytes) a fixed number of bytes for the string in order to prevent storage expansion via dynamic allocation, possibly creating heap fragmentation. Using char[] requires you to (ideally) pre-allocate a buffer so it is not dissimilar. The difference is one is pre-allocated at link time and the other is dynamically allocated at runtime.

Hi @peekay123,
Thanks for the advice! I have the sensors hooked up to comparators and/or mosfets that debounce the signals pretty well, so I’m not sure if that’s necessary in the software but I’ll keep it in mind. I will definitely implement the semi automatic mode and wrap the cloud commands. And very true about the String vars.

1 Like

You could also write your own confirmation system, where the publisher is also subscribed to a confirmation message that the subscriber transmits every time it gets data. Bit of extra redundancy, but would get you very high certainty and allow you to resend if you don’t get confirmation.

Particle.publish will confirm that a message was transmitted successfully, but can’t tell if any subscribers got it. Might be a good first line of defense check.

There’s a feature that I’m very excited to get built which absolutely handles this case, but it’s far down the current priority list so I won’t tease you too much with it.

2 Likes

Re Dick post:
Here’s a stripped down sub/pub pair of programs. I tested a longer version – but not this exact code (there might be a bug):

// Raspberry Pi end of pub/sub loop
#include <stdio.h>
#include <unistd.h>
#include <time.h>

#define SAFE_UID 1000 // i.e., the "pi" user built into RPi Linux
#define MY_PATH "/home/pi"
char Msg[100];;
int Ct = 0;
int Loops = 0;
int Reply = 0;
time_t SecStart;

void uid_fix() {
    setuid(SAFE_UID); // don't be ROOT!
    chdir(MY_PATH);
}

STARTUP( uid_fix() );

void setup() {
    Particle.subscribe("??-RX", getPub); // test
}

void getPub(const char *event, const char *data) {
    char txt[10];
    int elapsed;
    int count;
    
    sscanf(data, "%s: %d", txt, &count);
    elapsed = (int)(time(NULL) - SecStart); // pub to sub time
    if(count != Loops) {
        // missed message, do something
    }
    Reply = 1;
    // ...
}

void loop() {
    delay(180000); // every 3 minutes
    while(Reply == 0) {
        // delay loop
    }
    ++Loops;
    sprintf(Msg, "??-TX: %d, %d:%02d.%02d.%02d\n", Loops,Time.month(),Time.day(),Time.hour(),Time.minute());
    Reply = 0;
    Particle.publish("??-TX", Msg);
    SecStart = time(NULL);
}

// Photon-end of a publish/subscribe conversation with my RPi
char Msg[100];

void getPub(const char *event, const char *data) {
    sprintf(Msg, "RET: %s", data);
    Particle.publish("??-RX", Msg);
}

void setup() {
    Particle.subscribe("??-TX", getPub);
}

void loop() { 
    delay(25);
}

Comments?

1 Like