Safe way to pause and resume a loop?

I’m building a device that reads a sensor via a MODBUS converter that is connected to Serial1. Currently, I read the sensor every 5 seconds with some working code in void loop() which essentially serializes a hex command, sends it to the sensor and reads a response frame.

I would like to add a way to calibrate the sensor using a particle.function() call and the API. This would require me to pause the main loop for a while (maybe half a minute), execute the code in the function and then resume the loop to continue taking readings.

My code looks likes this. My idea is to use a while loop in the main loop to see if a command has been issued - if it has, the loop should stop and execute the function which would clear the command, resuming the while loop. I’m a C++ newbie, so I’m not sure if this makes sense at all? Is there a better way?

int calibrateZero(string command);

void setup()
{
    Particle.function("zero", calibrateZero);
}

void loop()
{
    while(command.empty())
    {
     //Read sensor code goes here
     Particle.publish("DEBUG", "Looping...")
     delay(5000);
    }
}

int calibrateZero(String command)
{
    if(command == "zerosensor")
    {
        //Calibration code goes here
        Particle.publish("DEBUG", "Sensor calibration successful!")      
        command.clear()
        return 1;
    }
    else
    {
        Particle.publish("DEBUG", "Sensor calibration failed.")
        command.clear()
        return -1;
    } 
    
}

Welcome to the community!

A really important thing to do with real-time OS like the device OS is to keep the loop() running as fast as possible. You should never stop or block the loop() function. High loop() cadence is really important.

A much better approach than a delay(5000); is the following

As global variable
unsigned long loop_update;

At the end of setup()

loop_update = millis();

In loop()

if (millis() - loop_update >= 5000UL)
{
  loop_update = millis();
  //whatever you want to do every 5000 mSec
}

Another tip is to try and avoid String object use. It is all too easy for these to grow in terms of memory use. C-string i.e. char cstring[10]; is better although it takes a while to understand how to use C strlib. Instead of ‘==’ you use strncmp/strcmp although the n version is safer.

In addition to @armor’s hints you may want to consider using an FSM approach for what you are doing, where you only trigger the state change from normal operation to calibration but have the actual work done in loop().

That would also better fit the guideline to have Particle.function() handlers return as fast as possible to provide timely feedback to the calling application - particularly since we don’t know how long you calibration would take (anything over 3 seconds will result in a timeout condition on the cloud side).

1 Like

@armor thank you! That’s really helpful - I’ll try out this approach.

@ScruffR thanks for the clarification. In the documentation, the Particle.function() is called outside of loop() but your explanation makes sense and I’ll try it that way.

1 Like

Hi @west2788,
Just want to bring up to you a point which might interest you.
The docs indicated that the loop() and Particle.function() are called by the system within the same thread. If my understanding is correct, this mean there is definite sequence in which they are called, one is called after another. But what is not clear is the order which function is called first. You might want to consider this in your approach to ensure the integrity of your sensor reading.

One way I could think of is the extension of @ScruffR idea of FSM. You might want to send the particle function in two pass. The first pass is to indicate to the loop that calibration is coming. This to ensure the loop() enter into state ready of calibration (i.e. idle - return directly from loop() in your case). Then on the second pass, you send cloud function to do the calibration. This way you can ensure that the new publish data after calibration cloud function is indeed from the calibrated sensor.

Anyway, maybe putting delay(5000) in your code is not a good idea, remember, Particle.function() shared the same thread as loop(). If you implement this, you might starve the Particle.function() of resources.

This might appear so, but is not (always) the case.
Whenever your code calls Particle.process() or any function that may do that implicitly (e.g. delay()) that logic is broken.

It is a first come first serve strategy. While you may not be able to influence the order at which concurrent calls from the cloud arrive at the device, once the device got the call requests that is the order in which functions will be executed one-by-one each time the cloud management task is executed.

This would not require two discrete function calls but may be executed by the FSM by itself. The function call just sets the “START_CAL” state once that state has been dealt with the FSM can move on to its next state (e.g. “PERFORM_CAL”) and when done with that revert back to the default “RUN” state.

Yes this makes sense, but the basic idea remained, there has to be some kind of coordination and synchronization between the timing of cloud function called vs the calibration should be started. But having one pass should be a better approach :slight_smile:

Is delay() a blocking call anyway? Or it can yield to other thread?

With SYSTEM_THREAD(ENABLED) it will yield to another thread, but without it it will block the application but internally call Particle.process() every 1000ms of accumulated waiting time. If that wouldn’t happen (as it didn’t in very early device OS versions) a delay(20000) would inevitably break the cloud connection, hence that behaviour was added.

1 Like