Particle.function does not work two times in a row

Dear all,
here is part of my main source file :

// Cloud function
int remoteCtrl(String command);

void setup() {
 
    // Register the cloud function
    Particle.function("remoteCtrl", remoteCtrl);
}

int remoteCtrl(String command) {
    if (command == "up") {
        handleUp();
        return 1;
    } else if (command == "down") {
        handleDown();
        return 1;
    } else if (command == "left") {
        handleLeft();
        return 1;
    } else if (command == "right") {
        handleRight();
        return 1;
    } else { 
        return -1;
    }
}

Calling the cloud function from curl works well just the first time.
Second call falls into a time out.

Any idea would be much appreciated.

Thanks,

Sylvain

Could you show us the rest of the code? We don’t know what’s going on in the ‘handle’ functions, and I’m guessing they’re blocking for too long. Set a flag in the function, and react to that in the loop. Try to keep function calls as short as possible.

2 Likes

Hi and thanks for your response.
My code is actually controlling the led strip using SPI which perfectly works when I use the gesture sensor but not when the same code is executed remotely using curl it worked only just the first time.
Anyway I followed your advice and simply set a flag in my cloud function. See code below. But this time behavior is weird. Either I get a timeout, or an error saying remoteCtrl function does not exists or sometimes it works. Seems there is some inconsistency on the cloud side when I flash my firmware. I assume that cloud functions get created/updated/deleted each time firmware is flashed or am I wrong?

Please advice.

Thanks

// Cloud function
int remoteCtrl(String command);

// Command set by the cloud function
String remoteCommand = NULL;

void setup() {
    // Init code skipped for clarity...

    // Register the cloud function
    Particle.function("remoteCtrl", remoteCtrl);
}

void loop() {
    if (remoteCommand != NULL) {
        handleRemoteCommand();
        remoteCommand = NULL;
    }
}

int remoteCtrl(String command) {
    if ((command == "up") || (command == "down") || (command == "left") || (command == "right")) {
        remoteCommand = command;
        return 1;
    } else { 
        return -1;
    }
}

Refreshing again the firmware made the “remoteCtrl function” error disappear.
But I still have the first call succeed and timeout on subsequent calls with the later code (setting the flag only).
Any idea?

You still haven't shown us what happens inside your handler function.
If you are blocking in there cloud features will become unresponsive.

2 Likes

Indeed, I would never thought that loop() would impact the response of the cloud function. I was expecting there was 2 threads: one for the loop() and another one for cloud functions.
See below the complete code for the main file. Please note that it works well when I use the gesture sensor. LedManager is my code and comes after. RGBConverter is a utility class for converting RGB to HSV and back. SparkFun_APDS9960 is a library I found from the Particle IDE.

Thank you in advance.

#include "RGBConverter.h"
#include "LedManager.h"
#include "SparkFun_APDS9960.h"

/*
  Soft SPI wires (LED strip)
  --------------------------
  SCK  => D4 green wire
  MOSI => D5 white wire
  GND  => GND blue wire
  +5V  => VIN red wire

  I2C wires (APDS - Gesture sensor)
  --------------------------
  SDA   => D0 orange wire
  SCL   => D1 yellow wire
  INT   => D3 white wire
  GND   => GND blue wire
  +3.3V => 3V3 red wire
*/

// APDS interrupt pin
#define APDS9960_INT D3

// Create APDS
SparkFun_APDS9960 apds = SparkFun_APDS9960();

// Flag set to true when interrupt trigger
bool interrupt = false;

// Led manager configuration
#define DATAPIN  D4
#define CLOCKPIN D5
#define NUMPIXELS 36

// Create LedManager
LedManager ledManager = LedManager();

// Cloud function
int remoteCtrl(String command);

// Command set by the cloud function
String remoteCommand = NULL;

void setup() {
    // Print system version
    Serial.printlnf("System version: %s", System.version().c_str());

    // Set APDS interrupt pin as input
    pinMode(APDS9960_INT, INPUT);

    // Initialize interrupt service routine
    attachInterrupt(APDS9960_INT, interruptRoutine, FALLING);
    
    // Initialize APDS
    if (apds.init()) {
        Serial.println("Gesture Sensor initialized.");
    } else {
        Serial.println("Gesture Sensor failed.");
    }
    
    // Start running the APDS-9960 gesture sensor (interrupts)
    if (apds.enableGestureSensor(true)) {
        Serial.println("Gesture sensor is now running");
    } else {
        Serial.println("Something went wrong during gesture sensor init!");
    }
    
    // Initialize LedManager
    ledManager.init(NUMPIXELS, DATAPIN, CLOCKPIN);
    
    // Register the cloud function
    Particle.function("remoteCtrl", remoteCtrl);
}

void loop() {
    if (interrupt) {
        detachInterrupt(APDS9960_INT);
        if (apds.isGestureAvailable()) {
            handleGesture();
        } 
        interrupt = false;
        attachInterrupt(APDS9960_INT, interruptRoutine, FALLING);
    }
    
    if (remoteCommand != NULL) {
        handleRemoteCommand();
        remoteCommand = NULL;
    }
}

void interruptRoutine() {
    interrupt = true;
}

void handleGesture() {
    String eventName = NULL;

    switch (apds.readGesture()) {
        case DIR_UP:            
            eventName = "UP";
            handleUp();
        break;
        case DIR_DOWN:
            eventName = "DOWN";
            handleDown();
        break;
        case DIR_LEFT:
            eventName = "LEFT";
            handleLeft();
        break;
        case DIR_RIGHT:
            eventName = "RIGHT";
            handleRight();
        break;
        case DIR_NEAR:
            eventName = "NEAR";
            handleNear();
        break;
        case DIR_FAR:
            eventName = "FAR";
            handleFar();
        break;
    }
    
    if (eventName != NULL) {
        Serial.println(eventName);
        Particle.publish("GESTURE-EVENT", eventName, 60, PRIVATE);
    }
}

void handleRemoteCommand() {
    if (remoteCommand == "up") {
        handleUp();
    } else if (remoteCommand == "down") {
        handleDown();
    } else if (remoteCommand == "left") {
        handleLeft();
    } else if (remoteCommand == "right") {
        handleRight();
    }
}

void handleUp() {
    ledManager.increaseBrightness();
}

void handleDown() {
    ledManager.decreaseBrightness();
}

void handleLeft() {
    ledManager.previousColor();
}

void handleRight() {
    ledManager.nextColor();
}

void handleNear() {
    // TODO get color from the cloud. Use a CloudManager class if needed
}

void handleFar() {
    // TODO send color to the cloud. Use a CloudManager class if needed
}

int remoteCtrl(String command) {
    if ((command == "up") || (command == "down") || (command == "left") || (command == "right")) {
        remoteCommand = command;
        return 1;
    } else { 
        return -1;
    }
}

Here is the LedManager cpp code:

#include "LedManager.h"

// Possible brightness values
double LedManager::BRIGHTNESSES[] = {1, 0.8, 0.6, 0.4, 0.2, 0};

// Possible color values
// white, red, orange, yellow, light green, green, light blue, cyan, navy blue, blue, purple, pink, fushia
byte LedManager::COLORS[][3] = {    {255, 255, 255}, {255, 0, 0}, {255, 127, 0}, {255, 255, 0}, {127, 255, 0}, 
                                    {0, 255, 0}, {0, 255, 127}, {0, 255, 255}, {0, 127, 255}, {0, 0, 255}, 
                                    {127, 0, 255}, {255, 0, 255}, {255, 0, 127} };

LedManager::LedManager() {
}

LedManager::~LedManager() {
}

void LedManager::init(int NUMPIXELS, int DATAPIN, int CLOCKPIN) {
    // Create Adafruit DotStar strip
    strip = Adafruit_DotStar(NUMPIXELS, DATAPIN, CLOCKPIN);

    // Initialize pins for output
    strip.begin();
    
    // Turn all LEDs off ASAP
    strip.show();

    // Create RGBConverter
    rgbConverter = RGBConverter();
    
    // Set brightnessIndex and nbBrightnesses
    brightnessIndex = 0;
    nbBrightnesses = sizeof(BRIGHTNESSES)/sizeof(double);

    // Set colorIndex and nbColors
    colorIndex = 0;
    nbColors = sizeof(COLORS)/(3*sizeof(byte));
    
    // TODO fade up brightness to max (switch on the light to black to max white)
    setStripBrightness(0, BRIGHTNESSES[brightnessIndex], 40, 50);
}

void LedManager::increaseBrightness() {
    // Keep current brightness
    double currentBrightness = BRIGHTNESSES[brightnessIndex];
    if (brightnessIndex > 0) {
        brightnessIndex --;
    } else {
        brightnessIndex = nbBrightnesses - 1;
    }
    setStripBrightness(currentBrightness, BRIGHTNESSES[brightnessIndex], 10, 10);
}

void LedManager::decreaseBrightness() {
    // Keep current brightness
    double currentBrightness = BRIGHTNESSES[brightnessIndex];
    if (brightnessIndex < nbBrightnesses - 1) {
        brightnessIndex ++;
    } else {
        brightnessIndex = 0;   
    }
    setStripBrightness(currentBrightness, BRIGHTNESSES[brightnessIndex], 10, 10);
}

void LedManager::nextColor() {
   if (colorIndex < nbColors - 1) {
        colorIndex ++;
    } else {
        colorIndex = 0;
    }
    setStripColor(COLORS[colorIndex]);
}
    
void LedManager::previousColor() {
    if (colorIndex > 0) {
        colorIndex --;
    } else {
        colorIndex = nbColors - 1;
    }
    setStripColor(COLORS[colorIndex]);    
}

void LedManager::setStripColor(byte color[3]) {
    // TODO Fade brightness down

    // Take into account the current brightness
    // Convert rgb to hsv and set the brightness
    double hsv[] = {0, 0, 0};
    rgbConverter.rgbToHsv(color[0], color[1], color[2], hsv);
    hsv[2] = BRIGHTNESSES[brightnessIndex];

    showStrip(hsv[0], hsv[1], hsv[2]);
   
    // TODO fade brightness up
}

void LedManager::setStripBrightness(double sourceBrightness, double targetBrightness, int nbStep, int sleep) {
    // Convert rgb to hsv and set the brightness
    double hsv[] = {0, 0, 0};
    rgbConverter.rgbToHsv(COLORS[colorIndex][0], COLORS[colorIndex][1], COLORS[colorIndex][2], hsv);

	double vIncrement = (targetBrightness - sourceBrightness) / nbStep;
	double tmpV = sourceBrightness;

	for (int i = 0; i < nbStep; i ++) {
		tmpV = tmpV + vIncrement;
		if (tmpV >= 0 && tmpV <= 1) {
            // Show led strip and wait
            showStrip(hsv[0], hsv[1], tmpV);
            delay(sleep);
		}
	}

    // Set target brightness because previous v is double and previous calculation may not have reach the exact v value
    // Show led strip and wait
    showStrip(hsv[0], hsv[1], targetBrightness);
    delay(sleep);
}

void LedManager::showStrip(double h, double s, double v) {
    // Convert hsv to rgb
    byte color[] = {0, 0, 0};
    rgbConverter.hsvToRgb(h, s, v, color);

    // Set pixels color
    for (int i = 0; i < strip.numPixels(); i ++) {
        strip.setPixelColor(i, color[0], color[2], color[1]); // this method accepts r, b and g
    }
    strip.show();
}

This is true when you use SYSTEM_THREAD(ENABLED), but even then user code including Particle.function()s and Particle.subscribe() handlers have to run in the application thread and not in the elevated system thread. So these would still become unresponsive if your other code hogs the application thread.

One difference between the two is, that while handling the gestures, you have your interrupt detached, but while handling the remote call, you have the pin interrupt active.
This might not be the root of your issue, but is an obvious difference to investigate further.


You might want to add some rate limiting to your Particle.publish() inside handleGesture() to obey the max 1/sec limit.


BTW:

// Create APDS
SparkFun_APDS9960 apds; // you won't need this: = SparkFun_APDS9960();
                        // default constructor() will be called this way anyhow
                        // otherwise you'll construct two objects just to immediately destroy one again
...
// ditto
// Create LedManager
LedManager ledManager = LedManager();

Thanks for your message.
I tried to detach interrupt and attach it again and in between call my handleRemoteCommand but it did not help.
Thanks for the programming advices for Particle.publish and default constructor being called.
I commented out a couple of lines and found out that the blocking code could come from the call to strip.show() in my LedManager class:

void LedManager::showStrip(double h, double s, double v) {
    // Convert hsv to rgb
    byte color[] = {0, 0, 0};
    rgbConverter.hsvToRgb(h, s, v, color);

    // Set pixels color
    for (int i = 0; i < strip.numPixels(); i ++) {
        strip.setPixelColor(i, color[0], color[2], color[1]); // this method accepts r, b and g
    }
    strip.show();
}

The strip.show() calls the DOTSTAR library I got from the Particle IDE and this here is the code:

void Adafruit_DotStar::show(void) {

  if(!pixels) return;

  uint8_t *ptr = pixels, i;            // -> LED data
  uint16_t n   = numLEDs;              // Counter
  uint16_t b16 = (uint16_t)brightness; // Type-convert for fixed-point math

  //__disable_irq(); // If 100% focus on SPI clocking required

  if(dataPin == USE_HW_SPI) {

    for(i=0; i<4; i++) spi_out(0x00);    // 4 byte start-frame marker
    if(brightness) {                     // Scale pixel brightness on output
      do {                               // For each pixel...
        spi_out(0xFF);                   //  Pixel start
        for(i=0; i<3; i++) spi_out((*ptr++ * b16) >> 8); // Scale, write RGB
      } while(--n);
    } else {                             // Full brightness (no scaling)
      do {                               // For each pixel...
        spi_out(0xFF);                   //  Pixel start
        for(i=0; i<3; i++) spi_out(*ptr++); // Write R,G,B
      } while(--n);
    }

    // Four end-frame bytes are seemingly indistinguishable from a white
    // pixel, and empirical testing suggests it can be left out...but it's
    // always a good idea to follow the datasheet, in case future hardware
    // revisions are more strict (e.g. might mandate use of end-frame
    // before start-frame marker).  i.e. let's not remove this.
    for(i=0; i<4; i++) spi_out(0xFF);

  } else {                               // Soft (bitbang) SPI

    for(i=0; i<4; i++) sw_spi_out(0);    // Start-frame marker
    if(brightness) {                     // Scale pixel brightness on output
      do {                               // For each pixel...
        sw_spi_out(0xFF);                //  Pixel start
        for(i=0; i<3; i++) sw_spi_out((*ptr++ * b16) >> 8); // Scale, write
      } while(--n);
    } else {                             // Full brightness (no scaling)
      do {                               // For each pixel...
        sw_spi_out(0xFF);                //  Pixel start
        for(i=0; i<3; i++) sw_spi_out(*ptr++); // R,G,B
      } while(--n);
    }
    for(i=0; i<4; i++) sw_spi_out(0xFF); // End-frame marker (see note above)
  }

  //__enable_irq();
}

Not an expert here but it seems to “only” write to SPI.
Is there any limitation in writing to SPI from a cloud function?
And by the way, any idea why disabling and enabling IRQ has been commented?

Thanks.