Sleeping mode and collect data

Hi All,

just need help about the feasibility to make the Boron sleep and gather information while sleeping and send it when it wake to save the battery.

i am using this function to make the boron sleep:

SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
.gpio(WKP, RISING)
.duration(10min);
System.sleep(config);

so every 10 min it is wake and send me a signal from distance detector, however now I’m missing a lot of information during these 10 min, so is there away to make it gather information every 1 minutes while sleeping and wake after 10 min to send me these past information?

Many thanks

@majj_11 ,

Interesting question. Here are a couple thoughts. Key to both is that you need to separate wake / sleep from connected / disconnected by changing the System mode to semi-automatic or manual. Then, you can try these approaches:

  1. Yes, you can certainly have the device wake from sleep, collect the data and then go back to sleep. Then, after a few of these sleep / wake / measure cycles, you can connect to Particle and send the accumulated data at one go. This can be done two ways (that I can think of):
  • Use a library like PublishQueuePOSIX to publish your data each minute. Then, when the device connects, the queue will be emptied and sent to Particle. Simple but multiple data operations.

  • Store the measurements in an array and then build a single payload that is published when you connect. Has the advantage of fewer data operations but is less resilient than the first option.

  1. Another thought is to use an “adaptive” approach to distance measurement. If, for example, distance changes infrequently but irregularly, you could use a distance measurement device that would operate even when the Particle device is asleep. When the distance changes over a certain amount or outside a set limit, it then wakes the Particle device with an interrupt which then connects and publishes. The advantage of this is that you don’t miss a significant change in distance and you don’t waste data operations.

I am sure there are other smart ways of doing this but I hope this gets you started.

Thanks,

Chip

4 Likes

As Chip stated, “changing the System mode to semi-automatic or manual” is the key for your project.
Very little Power is consumed by the Boron to quickly wake and record a measurement every minute, if you are not powering the Cellular Modem during measurement cycles.

How you record and report the data depends on what your needs are.
For instance, you may only really need the Min, Max, and Average distances over the 10 min reporting period. Or, if the data hasn’t substantially changed- you may want to skip the reporting/publishing for multiple 10 minute cycles.

Your Power Budget takes a hit (as well as Data Operations) when you power the modem to send data.
So generally speaking, you want to cram as much data into each scheduled Publish that you can (but only what you actually need), and maximize the time between Publishes with an adaptive approach(based on the rate of change of your data).

I’ve found that the driving factors are usually:

  • How sophisticated you want your backend to be.

  • How much data do you actually need to accomplish your goal.

  • Does the project prioritize Battery Life, or the volume of data

And remember, you can use Function Calls to make changes to your Adaptive Publish Schedule on the fly during testing to eliminate the need for re-flashing as you drill down on the best reporting approach.
An example would be to modify (w/ a Particle.Function) your threshold for Publishing from a 1" Min/Max/Average distance change over the 10 minutes to a 2" change to trigger a Publish. You just skip the next 10 minute Reporting Cycle if your threshold hasn’t been met, saving power and data operations. Keep the Modem Off for as long as you can.

4 Likes

There will be an application note for this use case, and a library to simplify a number of the minor but tricky details, in a few weeks.

The key to making your case work is that you need to disconnect from the cloud and turn off the cellular modem before going to sleep in the cases where you want the modem to stay off on wake. The reason is that upon wake the modem is restored to the previous state. If the cellular modem is not wakened, you can wake, take some measurements and save to the flash file system, in under a second. The sample code is currently taking 800 milliseconds. This is ideal for waking briefly once a minute.

3 Likes

This is a sample app using this library (not released yet). It grabs a temperature reading every 2 minutes and every 15 minutes it connects to the cloud and uploads the data. It automatically saves the samples and aggregates as many as will fit into a single publish, splitting them across multiple events as necessary.

#include "SleepHelper.h"

Serial1LogHandler logHandler(115200, LOG_LEVEL_INFO);

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);

const pin_t TMP36_SENSE_PIN = A0;
const pin_t TMP36_POWER_PIN = A1;

float readTempC();

void setup() {
    pinMode(TMP36_POWER_PIN, OUTPUT);
    digitalWrite(TMP36_POWER_PIN, LOW);

    SleepHelper::instance()
        .withShouldConnectMinimumSoC(9.0)
        .withMinimumCellularOffTime(5min)
        .withMaximumTimeToConnect(11min)
        .withDataCaptureFunction([](SleepHelper::AppCallbackState &state) {
            if (Time.isValid()) {
                SleepHelper::instance().addEvent([](JSONWriter &writer) {
                    writer.name("t").value((int) Time.now());
                    writer.name("c").value(readTempC());
                });
            }
            return false;
        })
        .withTimeConfig("EST5EDT,M3.2.0/02:00:00,M11.1.0/02:00:00")
        .withEventHistory("/usr/events.txt", "eh");

    // Full wake and publish every 15 minutes
    SleepHelper::instance().getScheduleFull()
        .withMinuteOfHour(15);

    // Data capture every 2 minutes
    SleepHelper::instance().getScheduleDataCapture()
        .withMinuteOfHour(2);

    SleepHelper::instance().setup();
}

void loop() {
    SleepHelper::instance().loop();
}
float readTempC() {
    digitalWrite(TMP36_POWER_PIN, HIGH);
    delay(2);

    int adcValue = analogRead(TMP36_SENSE_PIN);
    digitalWrite(TMP36_POWER_PIN, LOW);

    // Analog inputs have values from 0-4095, or
    // 12-bit precision. 0 = 0V, 4095 = 3.3V, 0.0008 volts (0.8 mV) per unit
    // The temperature sensor docs use millivolts (mV), so use 3300 as the factor instead of 3.3.
    float mV = ((float)adcValue) * 3300 / 4095;

    // According to the TMP36 docs:
    // Offset voltage 500 mV, scaling 10 mV/deg C, output voltage at 25C = 750 mV (77F)
    // The offset voltage is subtracted from the actual voltage, allowing negative temperatures
    // with positive voltages.

    // Example value=969 mV=780.7 tempC=28.06884765625 tempF=82.52392578125

    // With the TMP36, with the flat side facing you, the pins are:
    // Vcc | Analog Out | Ground
    // You must put a 0.1 uF capacitor between the analog output and ground or you'll get crazy
    // inaccurate values!

    // As configured above, connect VCC to A1 and Analog Out to A0.

    float tempC = (mV - 500) / 10;

    return tempC;
}

This is the event that is generated. “ttc” is the time to connect to cellular. You can turn this off. “wr” is the wake reason. The “eh” (event history) array contains the samples. “t” is the timestamp, “c” is the temperature in °C. It’s automatically split across events to minimize data operations.

{"ttc":17381,"wr":4,"eh":[{"t":1649010722,"c":23.4139},{"t":1649010841,"c":23.5751},{"t":1649010960,"c":23.6557},{"t":1649011081,"c":23.1722},{"t":1649011201,"c":22.9304},{"t":1649011320,"c":23.2527},{"t":1649011441,"c":23.1722},{"t":1649011518,"c":22.6886}]}
6 Likes

@rickkas7 ,

From the sample code you provided, I can see that this library, once released, will make a big difference for this “sleepy” use case. I look forward to testing it. Thank you for your continued significant contributions to the community!

Chip

3 Likes

@rickkas7, I’m also excited about this !
Having a standardized method of Sleeping will be a blessing for testing, especially identifying those pesky edge cases.
I love that TimeToConnect will be a built-in feature of the library.
Would you consider adding VCell and SoC to the Optional Payload (measured once during Full Wake cycle, before the Cell Radio)?

Thank you for the contribution.
Ryan

If you wanted to record the battery SoC and voltage on every boot or wake, you just add a wakeOrBootFunction like this:

    SleepHelper::instance()
        .withShouldConnectMinimumSoC(9.0)
        .withMinimumCellularOffTime(5min)
        .withMaximumTimeToConnect(11min)
        .withDataCaptureFunction([](SleepHelper::AppCallbackState &state) {
            if (Time.isValid()) {
                SleepHelper::instance().addEvent([](JSONWriter &writer) {
                    writer.name("t").value((int) Time.now());
                    writer.name("c").value(readTempC());
                });
            }
            return false;
        })
        .withWakeOrBootFunction([](int reason) {
            SleepHelper::instance().addEvent([](JSONWriter &writer) {
                FuelGauge fuel;
                writer.name("volt").value(fuel.getVCell());
                writer.name("soc").value(fuel.getSoC());
            });    
            return false;
        })
        .withTimeConfig("EST5EDT,M3.2.0/02:00:00,M11.1.0/02:00:00")
        .withEventHistory("/usr/events.txt", "eh");

The new parameters are added to the event history (eh), automatically combining to maximize the use of the event, and splitting if necessary if the event size limit is exceeded.

{"ttc":21638,"rr":40,"eh":[{"volt":3.98625,"soc":96.3945},{"t":1649605593,"c":23.6557}]}
5 Likes

Or you could use a wakeEventFunction instead. This only generates data on full wake and publish cycles, and the data goes in the outer event instead of inside the “eh” array. The items are added to the event, highest priority (1-100) first. If you set the priority < 50, then if the data overflows the event, your data will be discarded instead of publishing another event.

        .withWakeEventFunction([](JSONWriter &writer, int &priority) {
            FuelGauge fuel;
            writer.name("volt").value(fuel.getVCell(), 2);
            writer.name("soc").value(fuel.getSoC(),  1);
            priority = 50;
            return false;
        })        
3 Likes

@rickkas7 ,

I know this “Sleep Helper” is under development. Any idea when we can get our hands on it?

Thanks, Chip