Help with spark.publish / spark.subscribe, please?

Hello! :blush:

I have received help elsewhere about this obstacle but unfortunately it's going over my head at the moment. Not...way over, about an inch, but, still - it's going over my head. I'm pretty good at "adapting" code, and adding to existing code, I've cooked up reasonably involved projects before on the Arduino platform, but I am not an experienced coder at all so would really welcome some help turning advice/help into actual code. I don't want to pester ScruffR anymore - who has been helpful and patient. Hence this new thread.

Background (skip if you're not interested!)
Basically I am trying to create something like the VoicePage checkout system for the small 'mom and pop' shop I work in. There are two checkouts, an office and a little workshop. It would be SO useful to send messages between these places with buttons and LEDs rather than picking up the internal telephone all the time. I had thought to do this with spark.subscribe/spark.publish and four particle cores. I got so excited when I saw the spark core (as was) being talked about online - it all sounded like it would be easy for someone who had been able to cobble arduino projects together by writing the odd bit of code but mainly patching in bits of code or adapting code examples. Especially as all I'm really doing is flicking LEDs/relays etc on and off in response to inputs. Things I've easily been able to do with the Arduino. It sounded like the Spark platform would enable me to do that but with a small family of devices due to its cloud based nature. However the more into the project I have got the more limitations/restrictions/bugs I have found which need to be overcome with programming nouse I don't yet(?) have! Things like adding a heartbeat to a particle project to overcome the bug (now fixed, it seems!) whereby if the particle dropped its WiFi connection, which mine seem to do a lot, it didn't automatically resubscribe. All concepts I can understand, and no problem for a real programmer, but an insurmountable hurdle for me so far. I'm also not complaining, merely explaining my difficulty.

So, my issue:-

I have code which is 'adapted' from Quantumly Entangled LEDs and it's working. I can make two separate digital pins go high or low on a particle core based on inputs on another particle core being pressed using spark subscribe and spark publish. I've been given some advice about how to adapt/extend this code but it is just a bit too abstract for me. Does anyone have time to hold my hand?

Code for Core I Code for Core II

This is the advice I have been given:-

If you wrap your sending device ID and any other useful info into your event name and data payload you can just get along with one handler.

There might me some doc reading to be done, but one hint is that the event name you are subscribing to is only a prefix filter (e.g. CoreToggle would fire for CoreToggle1 and CoreToggle2 and even CoreToggleAnyOtherSuffix).

Just check the contents of toggle along side onOff.

If you use your DeviceID rather than an arbitrary number you can even flash the same code to all of your Cores and each Core would know if it's receiving own events or one of the others without the need to set that number in code for each individual Core. [...]
when you subscribe to e.g. "myPrefix" your handler will be triggered for anything that starts with "myPrefix", so you can use do e.g. Particle.publish("myPrefixLED1", "ON") and Particle.publish("myPrefixLED2", "OFF") and both wil trigger the same handler. To check which LED is meant, you look at the first parameter you called *toggle.

Or you send something like "LED1=OFF"/LED2=ON", split this up and use the seperated values.

So as I say, I can understand this in principle, I just can't turn it into code. I would be happy to be pointed in the direction of a code example, and no doubt(!) I'll be able to strip it for parts and make it work for my situation. But I can't find one online and I can't write one from where I am now. Which is my failing.

The other thing is, I am a bit at sea re: button debouncing. The button debouncing in my above code examples on pastebin work a treat, but they mean introducing lots of new variables each time I add a button. Is there a library that will handle debouncing for me? Any recommendation there?

THANKS for your help in advance, much appreciated. :smile:

No problem, no pester :wink:

Try this one (tested and working :blush:)

// name all devices with a common prefix e.g ScruffyMaster, ScruffySlave, ...
#define COMMON_DEVICE_NAME_PREFIX "Scruffy"

const int ledPins[] = { D7, D1, D2, A3, RX, TX };
const int LED_CNT = sizeof(ledPins) / sizeof(int);

char devName[64];
int ledNr;
bool ledState;

void sparkEvents(const char *topic, const char *data) 
{
    Serial.printlnf("%s: %s", topic, data);
    if (strstr(topic, "spark/device/name"))
    {
        strcpy(devName, data);
        Serial.printlnf("devName = %s", devName);
    }
}

void devEvents(const char *topic, const char *data) 
{
    int pinIdx;
    int state;
    
    Serial.printlnf("%s received %s: %s", devName, topic, data);

    if (strstr(topic, devName))
    {
        Serial.println("Uhh, That's me!");
        
        if (strstr(data, "=") && strstr(data, ";")) // rudimentary sanity check
        {
            pinIdx = atoi(data);    
            state  = atoi(strstr(data, "=") + 1);
            digitalWrite(ledPins[pinIdx], state);
        }
    }
    else
        Serial.println("Meh, no one loves me!");
        
}

void setup() 
{
    Serial.begin(115200);

    for (int i=0; i < LED_CNT; i++)  // setup LED pins
        pinMode(ledPins[i], OUTPUT);
        
    Particle.subscribe("spark/", sparkEvents);
    Particle.subscribe(COMMON_DEVICE_NAME_PREFIX, devEvents);

    Particle.publish("spark/device/name"); // push cloud to send my device name
}

void loop() 
{
    static uint32_t msLastPublish;
    int devNr;

    ledState = !ledState;
    
    if (millis() - msLastPublish >= 1001)
    {
        ledNr++;
        ledNr %= LED_CNT; 
        
        switch (devNr = (millis() % LED_CNT)) // just some random result
        {
            case 0: // send myself a message to set a LED 
                Particle.publish(devName, String::format("%d=%d;", ledNr, ledState));
                break;
            default: // one of the other remote devices
                Particle.publish(String::format("%s%d", COMMON_DEVICE_NAME_PREFIX, devNr) 
                                ,String::format("%d=%d;", ledNr, ledState)
                                );
                break;
        }
        msLastPublish = millis();
    }
}

Any questions, don't hesitate to ask :sunglasses:

2 Likes

Here is my suggestion:

Use IFTTT and a few simple subscribe and publish buttons. I got this code working with a group of grade 11-12 students. They could push a button on their cell phones and make the other students photon light go on. See topic at

@christine @daneboomer

If you donā€™t want to use IFTTT, this bit of code works nice. Has a bit of security and works for Photons on different peoples accounts. You can probably tighten things up a bit if both photons are on the same particle.io account, then you should be able to use the private settings.

Here is the setup image, basically connect 3V3 to D0 using a switch. (I use SNAP circuits 750, awesome for teaching)

And here is the .ino file

STARTUP(WiFi.selectAntenna(ANT_INTERNAL)); // selects the CHIP antenna
//STARTUP(WiFi.selectAntenna(ANT_EXTERNAL)); // selects the u.FL antenna
//STARTUP(WiFi.selectAntenna(ANT_AUTO)); // continually switches at high speed between antennas


//PUT YOUR GLOBAL VARIABLES HERE
volatile bool myFlag1 = false;
          int myCount = 0;

Timer myTimer1(7000, my7sFunction);   // activate function every 7 seconds

  
void my7sFunction(){
    
    myFlag1 = true;

}  
  
    
 // Any general setup stuff goes here   
void setup(){
    
    pinMode(D0, INPUT_PULLDOWN);
    pinMode(D7, OUTPUT);
    
    myTimer1.start();
    // Particle.subscribe("my-lamp-on3456", myLampFunction, MY_DEVICES);   
    // ", MYDEVICES" is only for your photons think of it as private
    Particle.subscribe("my-lamp-on3456", myLampFunction);   // public
    // change the number for uniqueness. Change 3456 to some other number
    // for using the "DO" IFTTT button or the "IF" this then that
    
}


void myLampFunction(const char *event, const char *data){    

    if (String(data) == "SECRET-STUFF"){
       digitalWrite(D7, 1);   // flash D7 
       delay(7000);           // for 7 seconds
        
       digitalWrite(D7, 0);   // D7 Off
       data = "STOP";
    }
}


void loop(){
     
  // your looping stuff goes here

        if (digitalRead(D0) == 1 && myFlag1){
           //  Particle.publish("my-lamp-on3456", "SECRET-STUFF", 60, PRIVATE);  //
           // Private only for your photons
           Particle.publish("my-lamp-on3456", "SECRET-STUFF", 60, PUBLIC);  
           // to call a different photons lamb use the different number
        myFlag1 = false;      // reset timer variable
    }
  
}

Thanks @rocksetta

Basically what I want to do is something like the below. Iā€™ve sort of adapted your code into the below, but Iā€™m sure Iā€™ve turned it into pseudo code. I think the weakness in the below is that it would not be possible to have two buttons operating independently of each other because the const- ā€œdataā€ is shared between them?

Can you see what Iā€™m trying to do? Forgive me if Iā€™ve misunderstood your suggestion.

void setup() {
    
    pinMode(D0, INPUT_PULLDOWN);
    pinMode(D7, OUTPUT);
    Particle.subscribe("checkout", myLampFunction);   // public
}
    
void myLampFunction(const char *event, const char *data) {    

    if (String(data) == "lampone") {
       digitalWrite(D7, 1);   // flash D7         
       digitalWrite(D7, 0);   // D7 Off
       data = "STOP";
    }
    if (String(data) == "lamptwo") {
       digitalWrite(D6, 1);   // flash D6         
       digitalWrite(D6, 0);   // D6 Off
       data = "STOP";
    }
}
       
void loop() {
        if (digitalRead(D0) == 1) {
               Particle.publish("checkout", "lampone", 60, PUBLIC);  
        }  
        if (digitalRead(D1) == 1) {
               Particle.publish("checkout", "lamptwo", 60, PUBLIC);  
        }
}

For two publishes youā€™d get two calls for your handler which donā€™t share the same data since they donā€™t overlap in time.

But you will run into the rate limit for publishing in your loop()

And I would strongly recommend against the use of delay(7000); inside the event handler!
This is not good in any case, but if you expect asyncronous events from multiple devices it could severly impair your functionality.
But with the short succession of writing HIGH then LOW to the pins you would not see the LEDs turn on - itā€™s just too fast.

2 Likes

Hi @ScruffR, I wasnā€™t aware I was using a delay in my loop. I had removed this (or I thought I had) in my pseudo code above?

I know that as it stands the loop would be executing code far too fast, both in terms of publishing to the cloud and in terms of how fast itā€™s switching LEDs on and off. I should have clarified, but I was more interested in getting the publish/subscribe working in theory. And I removed the delay because I donā€™t want to be using delays.

Basically I just want a way to be able to say:-
ā€œCore 1? Make pin D5 go high!ā€ [sent from, for example, Core 4]
ā€œCore 3 & Core 2? Make pin D3 go high!ā€ [sent from, for example Core 1]
ā€œCores 2,3 and 4? Make pin D4 go low!ā€ [sent from, for example Core 1]

Iā€™ll keep plugging away. I havenā€™t fully explored your code example from 3 days ago, yet. The answer may be found there! :slight_smile:

No, the delay(7000) was not in your code, but in the suggested code by Rocksetta and a delay in loop is no problem but would rather help avoiding the rate violation.


Oh, are you intending to use my code? I just pulled it back since I got the impression it might have been confusing and youā€™d much prefere the other code.
Should I put it back then?
It would definetly allow for multi device and multi button/LED support.

1 Like

Hi Scruff, itā€™s OK, I have a copy locally (of the code you posted).

It was in a sense initially ā€œtoo complicatedā€ but I think you had built into the code a sort of automatic-demo, had you not? I am in the process of stripping it down to the bare essentials and seeing if I can use it for my purposes. :slight_smile: I thank you for your time and effort!

Actually it was tailor made for what I understood from your initial question and the other thread where we had discussed some options (e.g. filter own events from other devices events - hence the spark subscription).

This code is intended to be as portable as possible that you only have to maintain one project that you can flash to whatever device you want to tie into your setup with the device names as kind of abstraction layer.

And yes, it does contain a demo loop() for a one-device-self-test.

Iā€™ve got the code very slightly reworked and will put it back, after Iā€™ve retested it.

2 Likes

Itā€™s like ruddy Christmas. Thanks @ScruffR
Quick question: is the reworked code amended due to a better understanding of what Iā€™m trying to do? All best! :slight_smile:

The over all functionality has not actually changed, but Iā€™ve added some measures to take my code and adapt it for your needs a bit easier (e.g. one place to swap my device name prefix for your own, just add remove a pin to the led_pins[] list without any need for further code adaption, ā€¦)

BTW: It might not be obvious, but you can even shoot one event that gets recognized as ā€œfor meā€ by just naming the event as e.g. ā€œScruffy1,Scruffy2,Scruffy3ā€ with a max length of 63 characters (so choose short device names ;-))


Meta-BTW: If you want to change a post you made, you donā€™t need to delete the old one and create a new one, but you just need to click the pencil icon left of the reply button of your own posts (might be hidden in the three dots ... ellipsis).

1 Like

Darn it, you are right @ScruffR. That 7 second delay shuts down the main loop. I want the light to go on for a while, but if I shut it down in the timer, then it will turn off at a variable rate, sometimes being half a second other times being about 7 seconds. Anyone see an easy solution. I probably have to make another timer and start it in the main loop.

1 Like

Canā€™t you use a millis calculation to achieve this? :slight_smile:

You could also use a high(er) frequency timer that checks for termination times which are set inside the event handler for each individual event (just as you would with millis()).

@ScruffR This seems to work. Thanks for the heads up about the bad delay(7000) in the main loop. Have solved it using timer.reset(). Had to activate it in both the sending and receiving functions so that the timer is in sync on both photons.


//PUT YOUR GLOBAL VARIABLES HERE
volatile bool myFlag1 = false;
          int myCount = 0;

Timer myTimerD7(7000, myD7Function);   
// slow down the number of published events sent to D7
// presently set to one event every 7 seconds
// after myTimerD7.reset() waits 7 seconds to activate myD7Function

  
void myD7Function(){
    
    myFlag1 = true;
    digitalWrite(D7, 0);   // D7 Off

}  
  
    
 // Any general setup stuff goes here   
void setup(){
    
    pinMode(D7, OUTPUT);
    pinMode(D0, INPUT_PULLDOWN);
    //  INPUT_PULLDOWN resets D0 to LOW if no power applied to it

    myTimerD7.start(); 
    // activate the timer for lamp D7
    // Will need another timer for each lamp you activate
     
    Particle.subscribe("my-lamp-on3456", myLampFunction);   // public
    
    // Particle.subscribe("my-lamp-on3456", myLampFunction, MY_DEVICES);   
    // ", MYDEVICES" is only for your photons, think of it as private
    
    // change the number for uniqueness. Change 3456 to some other number
    // Can be used with IFTTT website
    
}


void myLampFunction(const char *event, const char *data){    

    if (String(data) == "SECRET-STUFF"){
        // an attempt at security
       digitalWrite(D7, 1);   // flash D7 
       
       myTimerD7.reset();   // reset timer to start fresh
       data = "STOP";
       // change the security so that the function does not fire again
    }
}


void loop(){
     
  // your looping stuff goes here

        if (digitalRead(D0) == 1 && myFlag1){
  
           Particle.publish("my-lamp-on3456", "SECRET-STUFF", 60, PUBLIC); 
           //  Particle.publish("my-lamp-on3456", "SECRET-STUFF", 60, PRIVATE);  
           // PRIVATE only works for your photons using the same login
           // to call a different photon's lamb use a different number
           
           myTimerD7.reset();   // reset timer to start fresh
           myFlag1 = false;      // reset timer loop variable
    }
  
}

1 Like

THANK YOU! Finally got time to test this.

I did notice it seems to trigger an overflow bug with the Spark Dashboard Log. But otherwise Iā€™ll have a tinker with it and see if I can get it to do what I want. Thanks again.

P.S. @Dave should I log a bug re: dashboard log?

1 Like

Hi @daneboomer,

Glad you got it going! Iā€™ll pass this overflow bug onto the dashboard team. :slight_smile:

Thanks!
David