Handling subscribe

I have a program that has been working fine, yet I continue to try and improve it. The program is used to monitor my garage doors, alert us when one opens and remind us of when one is left open (particularly important at night).

There are two garage doors (left and right) and I'm sensing opening/closing via YoLink devices. I then use IFTTT to send messages via webhooks to my particle device (Argon running 4.2.0). It used to work, but now I finding the application does not appear to be receiving the GDRC (garage door right closed) event. Using the command line particle subscribe GDRC I can see the event is coming into my account, but it isn't being seen by my program. I'm put in some extra Particle.publish() statements in an effort to localize the issue, but without success. Everything seems to work except receiving the GDRC event message. Thoughts? The logic is pretty simple although the enlightened might shudder at my coding.

Here's the evidence the events are coming into my account:

{"data":"somedata","ttl":60,"published_at":"2023-11-14T18:39:49.118Z","coreid":"api","name":"GDRC"}
{"data":"somedata","ttl":60,"published_at":"2023-11-14T18:46:21.654Z","coreid":"api","name":"GDRC"}
{"data":"somedata","ttl":60,"published_at":"2023-11-14T18:58:08.772Z","coreid":"api","name":"GDRC"}
{"data":"somedata","ttl":60,"published_at":"2023-11-14T19:00:04.944Z","coreid":"api","name":"GDRC"}
{"ttl":60,"published_at":"2023-11-14T19:04:30.266Z","coreid":"api","name":"GDRC"}
{"ttl":60,"published_at":"2023-11-14T19:05:37.264Z","coreid":"api","name":"GDRC"}
{"ttl":60,"published_at":"2023-11-14T19:23:47.307Z","coreid":"api","name":"GDRC"}
{"ttl":60,"published_at":"2023-11-14T19:24:29.732Z","coreid":"api","name":"GDRC"}

I'm including the program code in case someone is interested in seeing and advising me what I obviously can't see. Is there anyway the "GDRC" event is reserved for special use?

#include "Particle.h"
#include "neopixel.h"

SYSTEM_MODE(AUTOMATIC);

SerialLogHandler logHandler;

// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN D0
#define PIXEL_COUNT 24
//#define PIXEL_TYPE SK6812RGBW 
#define PIXEL_TYPE WS2812B //this is required for the neopixel ring I own
#define BRIGHTNESS 5 // 0 - 255
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define doorOpenTime 3600000 // 60 minutes * 60 seconds * 1000 ms
//#define doorOpenTime 60000 // 1 minutes * 60 seconds * 1000 ms  -- for testing!!!
#define closed 1
#define open 0

int solenoid = D8;

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

int hour = 0;
int minute = 0;
int second = 0;
uint32_t color = 0;
uint32_t lights[30];
int i=0;
uint32_t blue = 0xFF;
uint32_t red = 0xFF00;
uint32_t green = 0xFF0000;
char gDeviceInfo [200];
int gAction = 0;
int gDoorStillOpen = 0;
int gLGD = closed; 
int gRGD = closed;

boolean gChimeOn = true;

void doorOpenStill(); //prototype

Timer doorOpenLongTimer(doorOpenTime,doorOpenStill);

void setup() {
  pinMode(solenoid,OUTPUT);
  Time.zone(-5); //EST
  Particle.function("pulseSolenoid",pulseSolenoid);
  //Particle.function("setPixelsRed", setPixelsRed);
  //Particle.function("setPixelsGreen", setPixelsGreen);
  //Particle.function("setPixelsBlue", setPixelsBlue);
  //Particle.function("setPixelsOff",setPixelsOff);
  Particle.function("openRight", openRight);
  Particle.function("closeRight", closeRight);
  Particle.function("openLeft", openLeft);
  Particle.function("closeLeft", closeLeft);
  Particle.function("pReset",pReset);
  Particle.function("enableChime",enableChime);
  Particle.variable("deviceInfo",gDeviceInfo);
  Particle.variable("DoorStillOpen",gDoorStillOpen);
  Particle.variable("RGD",gRGD); //right garage door status
  Particle.variable("LGD",gLGD); //left garage door status
  Particle.variable("chimeStatus",gChimeOn);
  Particle.subscribe("waterLeak",waterLeak,MY_DEVICES);
  Particle.subscribe("GDLO",leftDoorOpened,MY_DEVICES); //Left garage door opened
  Particle.subscribe("GDLC",leftDoorClosed,MY_DEVICES); //Left garage door closed
  Particle.subscribe("GDRO",rightDoorOpened,MY_DEVICES); //Right garage door opened
  Particle.subscribe("GDRC",rightDoorClosed,MY_DEVICES); //Right garage door closed
  Particle.subscribe("GDC",bothDoorsClosed,MY_DEVICES); //Both doors closed
  strip.setBrightness(BRIGHTNESS);
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  gAction = 7;
  snprintf(gDeviceInfo, sizeof(gDeviceInfo)
             ,"Application: %s, Date: %s, Time: %s, System firmware: %s, SSID: %s, RSSI: %d"
             ,__FILENAME__
             ,__DATE__
             ,__TIME__
             ,(const char*)System.version()  // cast required for String
             ,(const char*)WiFi.SSID()       // cast not required but always safe when in doubt if String or char*
             ,WiFi.RSSI().rssi
             );
  Log.info("setup() completed");
}

void loop() {
  switch (gAction)
  {
    case 0: //do nothing
      gAction = 0;
      break;

    case 1: // door opened
      //turnLEDSoff();
      chime();
      //gAction = 5; //set LEDs red
      gAction = 0;
      break;
    
    case 2: // doorOpenStill
      gDoorStillOpen = 1;
      // turnLEDSoff();
      chime();
      delay(750);
      chime();
      delay(750);
      chime();
      gAction = 0; //set LEDs red
      break;

    case 3: // bothDoorsClosed
      gDoorStillOpen = 0;
      for (uint16_t i=0;i<PIXEL_COUNT;i++){
        strip.setPixelColor(i,0,128,0); //green
      }
      strip.show();
      gAction = 0;
      doorOpenLongTimer.stop();
      break;

    case 4: // pulse solenoid
      chime();
      gAction = 0;
      break;

    case 5: // set pixels red
      for (uint16_t i=0;i<PIXEL_COUNT;i++){
        strip.setPixelColor(i,128,0,0); //red
      }
      strip.show();
      gAction = 0;
      break;

    case 6: //set pixels green
      for (uint16_t i=0;i<PIXEL_COUNT;i++){
        strip.setPixelColor(i,0,128,0); //green
      }
      strip.show();
      gAction = 0;
      break;

    case 7: //set pixels purple
      for (uint16_t i=0;i<PIXEL_COUNT;i++){
        strip.setPixelColor(i,128,0,128); //blue
      }
      strip.show();
      gAction = 0;
      break;

    case 8: // set pixels off
      turnLEDSoff();
      gAction = 0;
      break;

    case 9: // water leak - chime and set LEDs to blue
      turnLEDSoff();
      chime();
      delay(750);
      chime();
      delay(750);
      Particle.process();
      chime();
      delay(750);
      chime();
      delay(750);
      Particle.process();
      chime();
      delay(750);
      chime();
      delay(750);
      Particle.process();
      chime();
      delay(750);
      chime();
      delay(750);
      Particle.process();
      chime();
      gAction = 10;
      break;

    case 10:  // set pixels blue
      for (uint16_t i=0;i<PIXEL_COUNT;i=i+2){
        strip.setPixelColor(i,0,0,128); //blue
      }
      strip.show();
      gAction = 0;
      break;

    case 11:  // left garage door open
      Particle.publish("case11");
      for (uint16_t i=PIXEL_COUNT/2+1;i<PIXEL_COUNT;i=i+1){
        strip.setPixelColor(i,255,0,0); //red
      }
      strip.setPixelColor(0,255,0,0);
      strip.show();
      chime();
      gAction = 0;
      gLGD = open;
      doorOpenLongTimer.start();
      break;

    case 12:  // right garage door open
      Particle.publish("case12");
      for (uint16_t i=1;i<PIXEL_COUNT/2+1;i=i+1){
        strip.setPixelColor(i,255,0,0); //red
      }
      strip.show();
      chime();
      gAction = 0;
      gRGD = open;
      doorOpenLongTimer.start();
      break;

    case 13:  // left garage door closed
      Particle.publish("case13");
      for (uint16_t i=PIXEL_COUNT/2+1;i<PIXEL_COUNT;i=i+1){
        strip.setPixelColor(i,0,128,0); //green
      }
      strip.setPixelColor(0,0,128,0); //green
      strip.show();
      gAction = 0;
      gLGD = closed;
      if (gRGD==closed) {
        doorOpenLongTimer.stop();
      }
      break;

    case 14:  // right garage door closed
      Particle.publish("case14");
      for (uint16_t i=1;i<PIXEL_COUNT/2+1;i=i+1){
        strip.setPixelColor(i,0,128,0); //green
      }
      strip.show(); 
      gAction = 0;
      gRGD = closed;
      if (gLGD==closed) {
        doorOpenLongTimer.stop();
      }
      break;
      
    default:
      gAction = 0;
  }
}

void chime() {
  if (gChimeOn) {
    digitalWrite(solenoid,HIGH);
    delay(50);
    digitalWrite(solenoid,LOW);
  }
}

void turnLEDSoff () {
  for (uint16_t i=0;i<PIXEL_COUNT;i++){
        strip.setPixelColor(i,0,0,0); //off
      }
      strip.show();
}
void doorOpened(const char *event, const char *data) {
  gAction = 1;
  doorOpenLongTimer.start();
}

void leftDoorOpened(const char *event, const char *data) {
  gAction = 11;
  // doorOpenLongTimer.start();
}

int openLeft(String command) {
  leftDoorOpened(" ","");
  return 1;
}

void rightDoorOpened(const char *event, const char *data) {
  Particle.publish("receivedGDRO");
  gAction = 12;
  // doorOpenLongTimer.start();
}

int openRight(String command) {
  rightDoorOpened(" ","");
  return 1;
}

void leftDoorClosed(const char *event, const char *data) {
  gAction = 13;
}

int closeLeft(String command) {
  leftDoorClosed(" ","");
  return 1;
}

void rightDoorClosed(const char *event, const char *data) {
  Particle.publish("receivedGDRC");
  gAction = 14;
}

int closeRight(String command) {
  rightDoorClosed(" ","");
  return 1;
}

void bothDoorsClosed(const char *event, const char *data) {
  Particle.publish("receivedGDC");
  gAction = 3;
}

void doorOpenStill() {
  gAction = 2;
}

void waterLeak (const char *event, const char *data) {
  gAction = 9;
}

int pulseSolenoid (String command) {
  gAction = 4;
  return 1;
}

int setPixelsRed (String command) {
  gAction = 5;
  return 1;
}

int setPixelsGreen (String command) {
  gAction = 6;
  return 1;
}

int setPixelsBlue (String command) {
  gAction = 7;
  return 1;
}

int setPixelsOff (String command) {
  gAction = 8;
  return 1;
}

int enableChime(String command) {
  if (command=="on") {
    gChimeOn = true;
    return 1;
  }
  else {
    gChimeOn = false;
    return 0;
  }
}

int pReset(String command) {
  //function to call to remotely reset device
  System.reset();
  return 1;
}

Are you getting other publishes? At one point in time you were limited to 4 subscriptions. I'm not positive that is still true, but it's potentially an issue. Fortunately this is easy to fix. Just subscribe once to "GD" and the inside the subscription handler handle the event based on the eventName parameter.

The thing about subscribe is that it's best effort delivery, at least once, not in order. Also if multiple events arrive at the device in rapid succession, it's possible that the later ones will be lost, especially if the subscription handler performs lengthy operations.

If you need reliable delivery, using functions is better because the function calls returns an error if the device does not acknowledge receiving the call.

Hi @rickkas7 , it seems I'm getting all of the other calls. Can you point me to example code where I can detect the actual event from the eventName parameter? In my case I think I always use the pointer *event as in void rightDoorClosed(const char *event, const char *data). I'm a bit rusty on how I would check *event to get the actual. I can take it from there. BTW, thank you for your idea. I was going a bit whack-o trying to see why it stopped working.

Since it's a const char *event and not a String you can't use == but you can use:

if (strcmp(event, "GDLO") == 0) {
  // Handle GLDO
}
else
if (strcmp(event, "GDLC") == 0) {
  // ...

Just remember that strcmp returns 0 if they are equal.

@rickkas7 -- Thank you! I'll update my code to call one function that will determine what the actual event was. Much appreciated!

@rickkas7 , So this is lazy, but I wanted to test prior to making it a bit more elegant. I have the following subscribe:

Particle.subscribe("GD",determineEvent,MY_DEVICES);

And it's call the determineEvent function, but the following code is not working.

void determineEvent (const char *event, const char *data) {
  Particle.publish("determineEvent entered");
  if (strcmp(event,"GDLO") == 0) {
    openLeft(" ");
  }
  if (strcmp(event,"GDLC") == 0) {
    closeLeft(" ");
  }
  if (strcmp(event,"GDRO") == 0) {
    openRight(" ");
  }
  if (strcmp(event,"GDRC") == 0) {
    closeRight(" ");
  }
  if (strcmp(event,"GDC") == 0) {
    bothDoorsClosed(" ", " ");
  }

}```

It enters the `determineEvent` function, but does not call any of the specified functions.

You can't call Particle.publish from a subscribe handler. They share a buffer and the publish data replaces the subscribe data so event and data will be corrupted.

2 Likes

ahhh...I recall that now. Sorry, I've been away for quite a while.

OK. Working now. Duh. Thanks, @rickkas7 for your guidance and patience!

BTW, the documentation does state a max of 4 subscribes.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.