Electron: Collect data every minute, publish it every 15 minutes - with sleep. Better way to do this?

electron
Tags: #<Tag:0x00007f1c9e4e56d0>

#21

ScruffR,

That worked like a charm, thanks so much for all the assists!

/r,
Kolbi


#22

Latest and working well :wink:

///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\//
//     THIS CODE SNIPPET IS TO GATHER READINGS FROM SENSORS AT A USER SET TIME INTERVAL (SET IN     //
//     SECONDS) TO THEN BE PUBLISHED OUT ONCE THE DESIRED NUMBER OF SAMPLES HAS BEEN OBTAINED.      //
//     DURING THIS, IF AN EVENT IS TRIGGERED - A STATE WHERE IF ANY ONE OF THE SENSOR'S VALUE       //
//     DIFFERS MORE THEN THE LAST THREE VALUES AVERAGED TOGETHER + SENSOR ALERT SETPOINT (sa[X]),   //
//     THE SENSOR SAMPLE TIME INTERVAL IS CHANGED UNTIL THE DEFINED NUMBER OF PUBLISHED ALERT       //
//     EVENTS HAS OCCURRED. THESE SETTINGS ARE BELOW, UNDER *USER CONFIGURABLE PARAMETERS*          //
//                                                                                                  //
//     * * * NOTE THAT UNDER THE 'SENSOR VALUE EVENT CHECK' THE FOLLOWING TWO LINES WILL INSERT     //
//           A FALSE EVENT ON THE TWENTIETH SAMPLE TAKEN, ON SENSOR-3. DO NOT FORGET TO COMMENT     //
//           OUT THESE LINES * * *                                                                  //
//            if (sc == 20)        // ADD 100 TO SENSOR-3 VALUE TO SIMULATE EVENT                   //
//                sv[3][4] += 100; // ADD 100 TO SENSOR-3 VALUE TO SIMULATE EVENT                   //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\//
#include "application.h"
#include <math.h>

using namespace std;

// BEGIN USER CONFIGURABLE PARAMETERS
    const int v = 6;            // THE NUMBER IF VALUES TO COLLECT PER SAMPLE
    int samplei = 2;            // SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
    const int publishf = 15;    // WAIT UNTIL x NUMBER OF SAMPLES ARE GATHERED TO BURST PUBLISH
    int scasamplei = 1;         // ALERT SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
    const int scacr = 2;        // WHILE IN SENSOR ALERT - HOW MANY COMPLETE PUBLISH CYCLES TO LOOP THROUGH
    int sa[v] = {10, 11, 12, 13, 14, 15}; // SET ARRAY FOR SENSOR ALERT
// END OF USER CONFIGURABLE PARAMETERS


// DECLARE ITEMS USED AND SET INITIAL PARAMETERS
    int scac = ((publishf * scacr) + 1);    //HOW MANY SENSOR SAMPLES TO TAKE WHILE IN EVENT/ALERT MODE
    float storage[(publishf + 1)][v];   //DIMENSION THE STORAGE CONTAINER ARRAY
    int csamplei = samplei; // SET CURRENT SAMPLE INTERVAL TO NORMAL SAMPLE INTERVAL AS ABOVE
    char fullpublish[500];  //fullpublish[0] = '\0'; // CONTAINS THE STRING TO BE PUBLISHED
    char tempd[5];          // TEMPORY HOLDING STRING USED FOR FULLPUBLISH EVENT
    int satemp = 0;         // USED AS A TEMP CONTAINER IN SENSOR ALERT CHECKING
    int w = 0;              // THE NUMBER OF SAMPLES TO GATHER
    int x = 0;              // THE NUMBER OF SAMPLES COUNTER
    int xx = 0;             // THE NUMBER OF SAMPLES COUNTER in ALERT STATE
    int y = 0;              // THE SAMPLE PER COUNT GATHERED
    int z = 0;              // FOR TO LOOP TO SIMULATE 'VOID LOOP()'
    int pc = 0;             // COUNTER FOF ALL PUBLISHES PERFORMED
    int sc = 0;             // COUNTER FOF ALL SAMPLES PERFORMED
    int ec = 0;             // SENSOR SAMPLE EVENT CHECK
    int ecd = 0;            // SENSOR SAMPLE EVENT CHECK DEPTH
    int ecdiff = 0;         // SENSOR EVENT CHECK DIFFERENCE
    int scact;              // CYCLE MARK BASED ON 'SC' TO CONTINUE ALERT REPORTING
    int t2 = 0;             // EQUALS TIME(0) + SAMPLEI, USED TO DETERMINE WHEN TO TAKE SAMPLE
    int cias = 0;           // CHANGE IN ALERT STATE FLAG
    float sv[v][5];         // DIMENSION THE SENSOR VALUE ARRAY
// END OF DECLARATIONS AND INITIAL PARAMETERS

void setup()
{
Serial.begin(9600);
}

void loop() {
    
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
//                                START OF SENSOR VALUE GATHERING                                 //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///





// * * *    THIS PLACES STATIC VALUES IN THE SV[ARRAY] FOR TESTING,    * * *
// * * *            REPLACE THIS WITH SENSOR GATHERING CODE            * * * 

// SENSOR VALUES GO INTO THE SV[X][0] ARRAY WHERE 'X' IS THE SENSOR NUMBER
// sv[1][0] = SENSOR 1, SENSOR VALUE
// sv[1][1] = SENSOR 1, HISTORICAL SENSOR VALUE - ONE OF THREE
// sv[1][2] = SENSOR 1, HISTORICAL SENSOR VALUE - TWO OF THREE
// sv[1][3] = SENSOR 1, HISTORICAL SENSOR VALUE - THREE OF THREE
// sv[1][4] = SENSOR 1, HISTORICAL SENSOR VALUE AVERAGE OF PREVIOUS THREE

// BEGIN TO COLLECT SENSOR VALUES AND PLACE IN SV[ARRAY]
    sv[0][0] = 440;
    sv[1][0] = 441;
    sv[2][0] = 422;
    sv[3][0] = 463;
    sv[4][0] = 444;
    sv[5][0] = 445;
// END OF COLLECTING SENSOR VALUES THAT WERE PLACED IN SV[ARRAY]






///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
//     GATHER, EVENT CHECK, AND PUBLISH COLLECTED SENSOR VALUES BASED ON csamplei AND publishf    //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///



///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// BEGIN SENSOR VALUE EVENT CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (sc > ec) {
        ec = sc;
        if (ecd == 3) {
            ecd = 1;
        }
        else {
            ecd++;
        }
        for (w = 0; w < v; w++) {
            sv[w][ecd] = sv[w][0];
            sv[w][4] = ((sv[w][1] + sv[w][2] + sv[w][3]) / 3);
            if (sc == 20)        // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT
                sv[3][4] += 100; // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT
            if (sc > 3) {
                satemp = fabs(sv[w][0] - sv[w][4]);
                if (satemp > sa[w]) {
                    Serial.printf("Out of SA change on sensor %d, ", w);
                    Serial.printlnf("difference of %d, threshold set at %d.", satemp, sa[w]);
                    // DO SOMETHING HERE LIKE ALTER REPORTING TIMES OR SEND ALERT
                    cias = 1; // SIGNIFIES FRESH CHANGE INTO ALERT STATE SO PUBLISHER WILL PUBLISH STORAGE AND THEN START ALERT REPORTING
                    csamplei = scasamplei;
                    scact = sc + scac;
                }
            }
        }
    }
    if (sc > scact) // CHECK IF WE ARE IN AN ALERT REPORT CONDITION AND SEE IF WE SHOULD COME OUT OF IT
        csamplei = samplei;
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// END SENSOR VALUR EVENT CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//



///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// START OF SAMPLING TIME CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (Time.now() > t2) {
        for (w = 0; w < v; w++) {
            storage[x][w] = sv[w][0];
        }
        x++;
        t2 = Time.now() + csamplei;
        if (scact > sc) {
            Serial.printf("EVENT STATE: Saved readings in Storage: %d \n", x); // sc, scac, scact
            Serial.printf("Current sampling count (sc) = %d / Alert sample count (scac) = %d / Current alert sample count (scact) = %d \n", sc, scac, scact);
        }
        else {
            Serial.printf("Saved readings in Storage: %d \n", x);
        }
        sc++;
    }
///\/\/\/\/\/\/\/\/\/\/\/\/\/\//    
// END OF SAMPLING TIME CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\//



//\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// ALERT STATE PUBLISH FLUSH //
//\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (cias == 1 && scact > sc) {
        for (xx = 0; xx < x; xx++) {
            for (y = 0; y < v; y++) {
                snprintf(tempd, sizeof(tempd), "%.0f%s", storage[x][y], (y == 5) ? "^" : ",");
                strncat(fullpublish, tempd, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
            }
        } // BUILT FULLPUBLISH STRING
        // DO EVENT WITH FULLPUBLISH SAMPLE - PARTICLE.PUBLISH EVENT
        x = 0;
        xx = 0;
        cias = 0;
        Serial.println("Published readings. ");
        Serial.println(fullpublish);
        string str(fullpublish);
        Serial.printf("The size of published string is %d bytes. \n", str.length());
        fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
        pc++;
    }
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//  
// END OF ALERT STATE PUBLISH FLUSH //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//



//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// START OF SAMPLES TAKEN CHECK, IF MET THEN PUBLISH //
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (x == publishf) {
        for (x = 0; x < publishf; x++) {
            for (y = 0; y < v; y++) {
                snprintf(tempd, sizeof(tempd), "%.0f%s", storage[x][y], (y == 5) ? (scact > sc ? "!" : "^") : ",");
                strncat(fullpublish, tempd, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
            }
        } // BUILT FULLPUBLISH STRING
        // DO EVENT WITH FULLPUBLISH SAMPLE - ONCE ON ELECTRON, THIS WILL BE PARTICLE.PUBLISH EVENT
        x = 0;
        Serial.println("Published readings. ");
        Serial.println(fullpublish);
        Serial.printf("The size of published string is %d bytes. \n", strlen(fullpublish));
        fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
        pc++;
    }
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// END OF SAMPLES TAKEN CHECK, IF MET THEN PUBLISH //
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//



///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
// END OF GATHER, EVENT CHECK, AND PUBLISH COLLECTED SENSOR VALUES BASED ON csamplei AND publishf //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
}

#23

Giving power savings some thought…

Since publishes would normally occur every 15-20 minutes, and samples were taken about once per minute - it seems that System.sleep(BTN, FALLING, (sampling interval seconds - 4 seconds), SLEEP_NETWORK_STANDBY); followed by delay (1500); would work?

From what I have read, it usually takes about 1 second to fully wake, but perhaps up to two seconds to actually fall asleep? That is why I subtracted 4 seconds from the sample interval time, and then added a delay of 1.5 seconds when waking to ensure no hiccups with immediately publishing data.

If this logic is flawed to any extend please let me know - by reading the docs and also the forums, some of this with sleep settings and the whole process is a bit fuzzy to me, and it seems that some trial and error are always required based on the individual application.

:thinking:Question: Is this the best sleep option to use in this case? Additionally, given the sleep setting chosen - should there be a delay after a particle.publish?

/r,
Kolbi

///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// START OF SAMPLING TIME CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// 08APR19 - ADDED POWER SAVINGS EVENT
    if (Time.now() > t2) {
        for (w = 0; w < v; w++) {
            storage[x][w] = sv[w][0];  //PLACE SENSOR VALUES IN STORAGE ARRAY
        }
        x++;
        t2 = Time.now() + csamplei;
        if (scact > sc) {
            Serial.printf("EVENT STATE: Saved readings in Storage: %d \n", x); // sc, scac, scact
            Serial.printf("Current sampling count (sc) = %d / Alert sample count (scac) = %d / Current alert sample count (scact) = %d \n", sc, scac, scact);
          if (scasamplei > 18) { // IF SAMPLE TIME < 18 SECONDS THEN SKIP SLEEP     // POWER SAVINGS EVENT
            System.sleep(BTN, FALLING, (scasamplei - 4), SLEEP_NETWORK_STANDBY);    // POWER SAVINGS EVENT
            delay (1500);                                                           // POWER SAVINGS EVENT
          }                                                                         // POWER SAVINGS EVENT
        }
        else {
            Serial.printf("Saved readings in Storage: %d \n", x);
          if (samplei > 18) {    // IF SAMPLE TIME < 18 SECONDS THEN SKIP SLEEP     // POWER SAVINGS EVENT
            System.sleep(BTN, FALLING, (samplei - 4), SLEEP_NETWORK_STANDBY);       // POWER SAVINGS EVENT
            delay (1500);                                                           // POWER SAVINGS EVENT
          }                                                                         // POWER SAVINGS EVENT
        }
        sc++;
    }
///\/\/\/\/\/\/\/\/\/\/\/\/\/\//    
// END OF SAMPLING TIME CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\//

#24

Additionally, I have added the application watchdog and the sample rate of the sensor reading to the end of the data line to be published.
This allows for less cellular data, not sending a date/time stamp for each, where the PHP on the web server can calculate the time of sensor reading from the particle.publish time.

During normal reporting, the code publishes a ^ between each sensor set, an E at the end of the line, followed by the time (in seconds) between each sensor value recorded, and lastly vCell and vSoC - it looks like this:

440,441,422,463,444,445^440,441,422,463,444,445^440,441,422,463,444,445^440,441,422,463,444,445^440,441,422,463,444,445E804038448

While in alert reporting, the code publishes a ! between each sensor set, an E at the end of the line, followed by the time (in seconds) between each sensor value recorded - and lastly vCell and vSoC - it looks like this:

440,441,422,463,444,445!440,441,422,463,444,445!440,441,422,463,444,445!440,441,422,463,444,445!440,441,422,463,444,445E104038448

See full code in Particle Wed IDE HERE.

/r,
Kolbi


#25

Trying to use SLEEP_NETWORK_STANDBY with wake-on-ring but so far, no joy.
When the void loop starts, the following is executed to set set AT+URING to 1.

if (smpls_performed < 1) Cellular.command("AT+URING=1\r\n"); // ENABLE WAKE ON RING - Enable wake on all URCs //

Then after each sensor sample is recorded, the following sleep commands are run. The first one listens for ring/ping and does wake up the Electron when received, but the problem is that the board won’t sleep correctly, it seems like it takes a long time to go to sleep… The uncommented line works perfectly, putting the Electron to sleep for about 75 seconds - but the wake-on-lan version only gets about 30-40 seconds of sleep, and it varies for some reason also.

           //System.sleep({RI_UC, BTN}, {RISING, FALLING},((alert_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY); // * N * E * W *
        System.sleep(BTN, FALLING,((alert_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY);

Full code is HERE.
Any ideas?

/r,
Kolbi


#26

Not sure why 30-40 seconds of sleep would suggest a problem
This are the values I see in your current “Full code is HERE” link

    // ALAERT SETTING //
    int alert_smpl_intvl        = 30;        // ALERT SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
    const int alrt_publish_rnds = 3;        // WHILE IN SENSOR ALERT - HOW MANY COMPLETE PUBLISH CYCLES TO LOOP THROUGH
    int sensor_alert_thrshld[no_of_sensors] = {10, 11, 12, 13, 14, 15}; // SET ARRAY FOR SENSOR ALERT

    // SLEEP SETTINGS //
    int sleep                   = 1;        // SLEEP MODE ON = 1 / SLEEP MODE OFF = 0
    int delay_befor_publish     = 1000;     // DELAY IN MS BEFORE SLEEP
    int delay_after_publish     = 1000;     // DELAY IN MS AFTER SLEEP
    int secs_less_intrvl_sleep  = 2;        // SECONDS TO DELAY FROM SAMPLE INTERVAL FOR SLEEP TIME

plugging these into your sleep time calculation

((alert_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000))

gives me

((30 - 2) - ((1000+1000)/1000)) = 26

Consequently I’d expect 26 (plus a few for reconnect) seconds to be the correct sleep periode but 75 seems wrong IMO.



Just a general thought:
I always have conceptual problems with the terminology “commented” vs. “uncommented” since there seems to be a lot of confusion/inconsistency across the coder’s community as of which is what.
From the semantic perspecitve I’d deduce a line with a leading “line comment marker” (//) should be seen as “commented (out)” (aka inactive) and without the “line comment marker” as “not commented (out)” (aka active) or if you will “uncommented” (which isn’t a good term IMO at all - as it could also mean an active code line without discriptive comment).


#27

ScruffR,

Yeah, guess my statement about the uncommented line was a bit odd, but you knew what I was stepping in :wink:

True that the alert interval is 30, but it was in norm interval which is 80. So I should have been seeing about 75ish. I can’t figure it out because both lines use the same time parameter, but the wake-on-ping line isn’t working so good. Could it have something to do with Cellular.command("AT+URING=1\r\n"); command? Also, I didn’t place the Cellular.command("AT+URING=0\r\n"); in the script - is that needed?

           //System.sleep({RI_UC, BTN}, {RISING, FALLING},((norm_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY); // * N * E * W *
        System.sleep(BTN, FALLING,((norm_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY);

As always, thanks much for all your assistance - I do very much appreciate!

/r,
Kolbi


#28

I have never used it, but that doesn’t mean it wasn’t needed for other providers.


#29

Ok, but to set it on, to 1, is required correct?
I run this directly after the void loop, since first starting smpls_performed will be 0 so it will set AT+URING to 1. Just running this once at the start should be sufficient?

if (smpls_performed < 1) Cellular.command("AT+URING=1\r\n"); // ENABLE WAKE ON RING 

I also read that this is required, which I have:

#include "Particle.h"

Only other thing I could possibly think of would be geographical location / provider??? I am in Japan - maybe it’s a network thing?


#30

I have never used either of these settings (0 or 1)

This is what I was refering to when saying …


#31

Thanks ScruffR,

So if I understand all correctly, you successfully use the following:

System.sleep({RI_UC, BTN}, {RISING, FALLING},(seconds of sleep), SLEEP_NETWORK_STANDBY);

without the need for any statements such as:

Cellular.command("AT+URING=1\r\n"); -OR- Cellular.command("AT+URING=0\r\n");

Is #include "Particle.h" required?

To get the wake-on-ping functionality the only things I added to my code (3) was;
1 - #include "Particle.h"
2 - if (smpls_performed < 1) Cellular.command("AT+URING=1\r\n");
3 - System.sleep({RI_UC, BTN}, {RISING, FALLING},(seconds of sleep), SLEEP_NETWORK_STANDBY);

Am I missing something?

/r,
Kolbi


#32

…I’m pretty sure it’s the network. Oh, I forgot to say that I am using the Electron 2G/3G Europe/Asia/Africa - SARA-U270.


#33

Well, all the functions appear to be working well - still waiting for sensors to come in the mail…
The only thing I can’t get working is the SLEEP with WAKE-ON-PING, I think it is due to the network in my area but I’ll keep digging and see what I can find.

Just in case the APP within the Particle Wed IDE is removed and renamed, here is the full project. Please pass any thoughts, findings, and critiques to me, I always appreciate and grow from it.

///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\//
//     THIS CODE SNIPPET IS TO GATHER READINGS FROM SENSORS AT A USER SET TIME INTERVAL (SET IN     //
//     SECONDS) TO THEN BE PUBLISHED OUT ONCE THE DESIRED NUMBER OF SAMPLES HAS BEEN OBTAINED.      //
//     DURING THIS, IF AN EVENT IS TRIGGERED - A STATE WHERE IF ANY ONE OF THE SENSOR'S VALUE       //
//     DIFFERS MORE THEN THE LAST FOUR VALUES AVERAGED TOGETHER + SENSOR ALERT SETPOINT             //
//     (sensor_alert_thrshld[X]), THE SENSOR SAMPLE TIME INTERVAL IS CHANGED UNTIL THE DEFINED      //
//     NUMBER OF PUBLISHED ALERT EVENTS HAS OCCURRED. THESE SETTINGS ARE BELOW, UNDER               //
//     *USER CONFIGURABLE PARAMETERS*                                                               //
//                                                                                                  //
//    * * * NOTE THAT UNDER THE 'SENSOR VALUE EVENT CHECK' THE FOLLOWING FOUR LINES WILL INSERT     //
//          A FALSE EVENT ON THE TWENTIETH SAMPLE TAKEN, ON SENSOR-3 AND AGAIN AT SAMPLE            //
//          TWENTY-THREE. DO NOT FORGET TO COMMENT OUT THESE LINES * * *                            //
//          if (smpls_performed == 21)      // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT        //
//              sensor_value[3][5] += 100;  // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT        //
//          if (smpls_performed == 23)      // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT        //
//              sensor_value[3][5] += 100;  // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT        //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\//

#include "application.h"
#include <math.h>
#include "Particle.h"
using namespace std;

// BEGIN USER CONFIGURABLE PARAMETERS

    // SAMPLE & PUBLISH SETTINGS //
    int norm_smpl_intvl         = 88;       // SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
    const int rnds_to_publish   = 15;       // WAIT UNTIL x NUMBER OF SAMPLES ARE GATHERED TO BURST PUBLISH
    int do_publish              = 1;        // PERFORM PARTICLE.PUBLISH EVENTS
    const int no_of_sensors     = 6;        // THE NUMBER OF SENSOR READINGS TO COLLECT PER SAMPLE

    // ALAERT SETTING //
    int alert_smpl_intvl        = 40;       // ALERT SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
    const int alrt_publish_rnds = 3;        // WHILE IN SENSOR ALERT - HOW MANY COMPLETE PUBLISH CYCLES TO LOOP THROUGH
    int sensor_alert_thrshld[no_of_sensors] = {10, 11, 12, 13, 14, 15}; // SET ARRAY FOR SENSOR ALERT

    // SLEEP SETTINGS //
    int sleep                   = 1;        // SLEEP MODE ON = 1 / SLEEP MODE OFF = 0
    int delay_befor_publish     = 1000;     // DELAY IN MS BEFORE SLEEP
    int delay_after_publish     = 1000;     // DELAY IN MS AFTER SLEEP
    int secs_less_intrvl_sleep  = 2;        // SECONDS TO DELAY FROM SAMPLE INTERVAL FOR SLEEP TIME
    int app_watchdog            = 30000;    // APPLICATION WATCHDOG TIME TRIGGER IS MS - SLEEP TIME SHOULD NOT BE COUNTED, 
                                            // BUT THE ABOVE THREE VARIABLES SHOULD BE BUT NOT LESS THEN 30000
// END OF USER CONFIGURABLE PARAMETERS


// DECLARE ITEMS USED AND SET INITIAL PARAMETERS
    int alert_sample_qty = ((rnds_to_publish * alrt_publish_rnds) + 1); //HOW MANY SENSOR SAMPLES TO TAKE WHILE IN EVENT/ALERT MODE
    float storage[(rnds_to_publish + 1)][no_of_sensors]; //DIMENSION THE STORAGE CONTAINER ARRAY
    int current_smpl_intvl = norm_smpl_intvl; // SET CURRENT SAMPLE INTERVAL TO NORMAL SAMPLE INTERVAL AS ABOVE
    char fullpublish[500];                  // CONTAINS THE STRING TO BE PUBLISHED
    char fullpub_temp1[14];                 // TEMPORY HOLDING STRING USED FOR FULLPUBLISH EVENT
    char fullpub_temp2[4];                  // TEMPORY HOLDING STRING USED FOR FULLPUBLISH EVENT
    int alrt_ckng_tmp = 0;                  // USED AS A TEMP CONTAINER IN SENSOR ALERT CHECKING
    int w = 0;                              // THE NUMBER OF SAMPLES TO GATHER
    int x = 0;                              // THE NUMBER OF SAMPLES COUNTER
    int xa = 0;                             // THE NUMBER OF SAMPLES COUNTER in ALERT STATE
    int y = 0;                              // THE SAMPLE PER COUNT GATHERED
    int pubs_performs = 0;                  // COUNTER FOF ALL PUBLISHES PERFORMED
    int smpls_performed = 0;                // COUNTER FOF ALL SAMPLES PERFORMED
    int sensor_event_chk = 0;               // SENSOR SAMPLE EVENT CHECK
    int sensor_hist_depth = 0;              // SENSOR SAMPLE EVENT CHECK DEPTH
    int alert_cycl_mark = 0;                // CYCLE MARK BASED ON 'smpls_performed' TO CONTINUE ALERT REPORTING
    int t2 = 0;                             // EQUALS TIME(0) + norm_smpl_intvl, USED TO DETERMINE WHEN TO TAKE SAMPLE
    int alrt_state_chng = 0;                // CHANGE IN ALERT STATE FLAG
    float sensor_value[no_of_sensors][6];   // DIMENSION THE SENSOR VALUE ARRAY
    int vcell;                              // BATTERY INFO
    int vsoc;                               // BATTERY INFO
// END OF DECLARATIONS AND INITIAL PARAMETERS


void setup() { Serial.begin(9600); }

ApplicationWatchdog wd(app_watchdog, System.reset);

void loop() {
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
//                                START OF SENSOR VALUE GATHERING                                 //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///



// * * *    THIS PLACES STATIC VALUES IN THE sensor_value[ARRAY][0] FOR TESTING,    * * *
// * * *            REPLACE THIS WITH SENSOR GATHERING CODE                         * * * 

// SENSOR VALUES GO INTO THE sensor_value[X][0] ARRAY WHERE 'X' IS THE SENSOR NUMBER
// sensor_value[1][0] = SENSOR 1, SENSOR VALUE
// sensor_value[1][1] = SENSOR 1, HISTORICAL SENSOR VALUE - ONE OF FOUR
// sensor_value[1][2] = SENSOR 1, HISTORICAL SENSOR VALUE - TWO OF FOUR
// sensor_value[1][3] = SENSOR 1, HISTORICAL SENSOR VALUE - THREE OF FOUR
// sensor_value[1][4] = SENSOR 1, HISTORICAL SENSOR VALUE - FOUR OF FOUR
// sensor_value[1][5] = SENSOR 1, HISTORICAL SENSOR VALUE AVERAGE OF ALL FOUR

// BEGIN TO COLLECT SENSOR VALUES AND PLACE IN sensor_value[ARRAY]
	sensor_value[0][0] = random(440, 450);
	sensor_value[1][0] = random(441, 450);
	sensor_value[2][0] = random(422, 435);
	sensor_value[3][0] = random(463, 475);
	sensor_value[4][0] = random(444, 450);
	sensor_value[5][0] = random(445, 455);
// END OF COLLECTING SENSOR VALUES THAT WERE PLACED IN sensor_value[ARRAY]






///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\/\\//\/\\//\\//\\//\\///
//  GATHER, EVENT CHECK, AND PUBLISH COLLECTED SENSOR VALUES BASED ON current_smpl_intvl AND rnds_to_publish  //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\/\\//\/\\//\\//\\//\\///



///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// BEGIN SENSOR VALUE EVENT CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (smpls_performed > sensor_event_chk) {
        sensor_event_chk = smpls_performed;
        if (sensor_hist_depth == 4) { sensor_hist_depth = 1; } else { sensor_hist_depth++; } // CYCLE THROUGH 1 - 4 FOR HISTORY / AVERAGING
        for (w = 0; w < no_of_sensors; w++) {
           sensor_value[w][5] = ((sensor_value[w][1] + sensor_value[w][2] + sensor_value[w][3] + sensor_value[w][4]) / 4);    
            sensor_value[w][sensor_hist_depth] = sensor_value[w][0];
            if (smpls_performed == 21)      // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT * * * *
                sensor_value[3][5] += 100;  // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT * * * *
            if (smpls_performed == 23)      // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT * * * *
                sensor_value[3][5] += 100;  // ADD 100 TO SENSOR-3 AVERAGE TO SIMULATE EVENT * * * *
            if (smpls_performed > 4) {
                alrt_ckng_tmp = fabs(sensor_value[w][0] - sensor_value[w][5]);
                if (alrt_ckng_tmp > sensor_alert_thrshld[w]) {
                    Serial.printf("OOS Sensor %d, ", w);
                    Serial.printlnf("Diff: %d, Threshold: %d.", alrt_ckng_tmp, sensor_alert_thrshld[w]);
                    // DO SOMETHING HERE LIKE ALTER REPORTING TIMES OR SEND ALERT
                    if  (alrt_state_chng != 2) alrt_state_chng = 1; // SIGNIFIES FRESH CHANGE INTO ALERT STATE SO PUBLISHER WILL PUBLISH STORAGE AND THEN START ALERT REPORTING
                    current_smpl_intvl = alert_smpl_intvl;
                    alert_cycl_mark = smpls_performed + alert_sample_qty;
                }
            }
        }
    }
    if (smpls_performed > alert_cycl_mark) current_smpl_intvl = norm_smpl_intvl; // CHECK IF WE ARE IN AN ALERT REPORT CONDITION AND SEE IF WE SHOULD COME OUT OF IT
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// END SENSOR VALUR EVENT CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//



///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// START OF SAMPLING TIME CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (Time.now() > t2) {
        for (w = 0; w < no_of_sensors; w++) { storage[x][w] = sensor_value[w][0]; } //PLACE SENSOR VALUES IN STORAGE ARRAY
        x++;
        t2 = Time.now() + current_smpl_intvl;
        if (alert_cycl_mark > smpls_performed) {
         Serial.println("!");    
         if (sleep) {                   // IS SLEEP ENABLED IN SETTINGS?
          if (alert_smpl_intvl > 18) {  // IF ALERT SAMPLE TIME < 18 SECONDS THEN SKIP SLEEP
        //System.sleep({RI_UC, BTN}, {RISING, FALLING}, ((alert_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY); // * N * E * W *
        System.sleep(BTN, FALLING,((alert_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY);
         } }
        }
        else {
         Serial.println(".");  
         if (sleep) {                   // IS SLEEP ENABLED IN SETTINGS?
          if (norm_smpl_intvl > 18) {   // IF NORMAL SAMPLE TIME < 18 SECONDS THEN SKIP SLEEP
        //System.sleep({RI_UC, BTN}, {RISING, FALLING}, ((norm_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY); // * N * E * W *
        System.sleep(BTN, FALLING,((norm_smpl_intvl - secs_less_intrvl_sleep) - ((delay_after_publish+delay_befor_publish)/1000)), SLEEP_NETWORK_STANDBY);
         } }
        }
        smpls_performed++;
        wd.checkin(); // SOFTWARE WATCHDOG CHECK-IN
    if (alert_cycl_mark == (smpls_performed + 1)) alrt_state_chng = 3; 
    }
///\/\/\/\/\/\/\/\/\/\/\/\/\/\//    
// END OF SAMPLING TIME CHECK //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\//



//\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// ALERT STATE PUBLISH FLUSH //
//\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if ((alrt_state_chng == 3) || (alrt_state_chng == 1 && alert_cycl_mark > smpls_performed)) {
        for (xa = 0; xa < x; xa++) {
            for (y = 0; y < no_of_sensors; y++) {
                snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%.0f%s", storage[x][y], (xa == (x-1) and (y == (no_of_sensors -1)) ? "E" : (y == (no_of_sensors -1)) ? (alert_cycl_mark > smpls_performed ? "!" : "^") : ","));
                strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
            } 
        } // BUILT FULLPUBLISH STRING
        x = 0;
        xa = 0;
        if (alrt_state_chng = 1 ) alrt_state_chng = 2;
        if (strlen(fullpublish) != 0 )
          {
            FuelGauge fuel; // GET BATTERY INFO
            fullpub_temp1[0] = 0;
            snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d%d%d", current_smpl_intvl, ((int) (fuel.getVCell()*100)), ((int) (fuel.getSoC()*100))); // ADDING SAMPLE RATE, BATTERY INFO
            strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
            Serial.println(fullpublish);
            Serial.printf("ASP: The size of published string is %d bytes. \n", strlen(fullpublish));
            delay (delay_befor_publish);
            if (do_publish) Particle.publish("a", fullpublish, 60, PRIVATE, NO_ACK); //- ! ! ! ! PARTICLE.PUBLISH EVENT ! ! ! ! 
            delay (delay_after_publish);
          }
        fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
        pubs_performs++;
	if (alrt_state_chng == 3) alrt_state_chng = 0;
    }
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//  
// END OF ALERT STATE PUBLISH FLUSH //
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//



//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// START OF SAMPLES TAKEN CHECK, IF MET THEN PUBLISH //
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
    if (x == rnds_to_publish) {
        for (x = 0; x < rnds_to_publish; x++) {
            for (y = 0; y < no_of_sensors; y++) {
                snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%.0f%s", storage[x][y], (x == (rnds_to_publish-1) and (y == (no_of_sensors -1)) ? "E" : (y == (no_of_sensors -1)) ? (alert_cycl_mark > smpls_performed ? "!" : "^") : ","));
                strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // check the boundaries of fullpublish
            }
        } // BUILT FULLPUBLISH STRING
        x = 0;
        if (strlen(fullpublish) != 0 )
          {
            FuelGauge fuel; // GET BATTERY INFO
            fullpub_temp1[0] = 0;
            snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d%d%d", current_smpl_intvl, ((int) (fuel.getVCell()*100)), ((int) (fuel.getSoC()*100))); // ADDING SAMPLE RATE, BATTERY INFO
            strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
            Serial.println(fullpublish);
            Serial.printf("The size of published string is %d bytes. \n", strlen(fullpublish));
            delay (delay_befor_publish);
            if (do_publish) Particle.publish("a", fullpublish, 60, PRIVATE, NO_ACK); //- ! ! ! ! PARTICLE.PUBLISH EVENT ! ! ! ! 
            delay (delay_after_publish);
          }
        fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
        pubs_performs++;
    }
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
// END OF SAMPLES TAKEN CHECK, IF MET THEN PUBLISH //
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//



///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
//  END OF GATHER, EVENT CHECK, AND PUBLISH COLLECTED SENSOR VALUES BASED ON current_smpl_intvl AND rnds_to_publish   //
///\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\///
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//    WEBHOOK
// 
//
//--       {
//--            "event": "a",
//--            "url": "http://WEB-ADDRESS/RCVR.php",
//--            "requestType": "GET",
//--            "noDefaults": false,
//--            "rejectUnauthorized": false,
//--            "query": {
//--                "id": "{{SPARK_EVENT_VALUE}}",
//--                "location": "{{SPARK_CORE_ID}}"
//--            }
//--        }
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//   PHP PUBLISHER RECEIVER - RCVR.php
//
//
//-- <?php
//--
//-- if(!isset($_GET["data"])){echo "404"; die();}
//-- 
//-- $vp = htmlspecialchars($_GET["data"]); //GET THE PUBLISHED INFORMATION SENT
//-- 
//-- $published = htmlspecialchars($_GET["published_at"]); //GET THE TIME SENT
//-- $published = str_replace("-", "/", $published);
//-- 
//-- $date = substr($published, 0, 10);	// CLEAN UP THE DATE / TIME RVCD SO WE CAN USE IT BETTER IN CSV FILE LATER
//-- $time = substr($published, 11, 8);	// CLEAN UP THE DATE / TIME RVCD SO WE CAN USE IT BETTER IN CSV FILE LATER
//-- $datetime = $date." ".$time;		// CLEAN UP THE DATE / TIME RVCD SO WE CAN USE IT BETTER IN CSV FILE LATER
//-- 
//-- $vpc = str_replace(chr(34), '', $vp);	// CLEAN UP THE PUBLISHED INFO
//-- $data = str_replace(' ', '', $vpc);		// CLEAN UP THE PUBLISHED INFO
//-- $incoming = htmlspecialchars($data);	// CLEAN UP THE PUBLISHED INFO
//-- $inlength - strlen($incoming);			// DETERMINE STRING LENGTH OF PUBLISHED DATA
//-- $involts = substr($incoming, -4);		// GET THE VOLTAGE
//-- $insoc = substr($incoming, -7, 3);		// GET THE SOC
//-- $intrvl = substr($incoming, strpos($incoming, 'E') + 1, ($length-7)); // GET TIME INTERVAL BETWEEN SENSOR READINGS
//-- $striplen = strlen($intrvl)+8;			// CALCULATE WHERE TO CUT THE EXTRA DATA FROM SENSOR DATA
//-- $process = substr($incoming, 0, ($length - $striplen)); // CUT OFF EXTRA DATA - JUST LEAVE SENSOR DATA
//-- 
//-- $delin = substr_count($process, '!');	// DETERMINE HOW MANY SETS OF SENSOR DATA WAS RCVD, IF IT WAS ALERT OR NORMAL
//-- if ($delin == 0) {						// AND BUST SENSOR DATA SETS INTO ARRAY IF DATA WAS SENT AS NORMAL REPORTING
//-- 	$inArray = explode('^', $process);
//-- 	$delin = substr_count($process, '^');	
//--   } else {
//-- 	$inArray = explode('!', $process); 
//--   }
//-- $sectodel = $intrvl * ($delin+1);
//-- 
//-- $timestamp = new DateTime($datetime);	// BASED OFF TIME PUBLISHED AND SAMPLING INTERVAL, GIVE EACH SAMPLE IT'S TIME
//-- $timestamp->modify('- '.$sectodel.' seconds');
//-- $datetime = $timestamp->format('Y/m/d H:i:s');
//-- for ( $i = 0; $i < ($delin+1); $i++ ) {
//-- 	$sectoadd = ($intrvl * ($i + 1));
//-- 	$timestamp = new DateTime($datetime);
//-- 	$timestamp->modify('+ '.$sectoadd.' seconds');
//-- 	$datetimeN = $timestamp->format('Y/m/d H:i:s');	
//-- 	$inArray[$i] = $datetimeN . "," . $inArray[$i] . "\n";
//--   }
//-- 
//-- $fp = fopen('ark/lf.csv', 'a');		// WRITE SENSOR DATA TO CSV FILE NAMED lf.csv LOCATED IN ark FOLDER
//-- for ( $i = 0; $i < ($delin+1); $i++ ) { fwrite($fp, $inArray[$i]); }
//-- fclose($fp);
//-- 
//-- $fp = fopen('ark/p1f.csv', 'a');	// WRITE SOC DATA TO CSV FILE NAMED p1f.csv LOCATED IN ark FOLDER
//--  fwrite($fp, $datetime . "," . ($insoc / 100) . "\n");
//--  fclose($fp); 
//-- 
//-- $fp = fopen('ark/p2f.csv', 'a');	// WRITE VOLTS DATA TO CSV FILE NAMED p2f.csv LOCATED IN ark FOLDER
//--  fwrite($fp, $datetime . "," . ($involts / 100) . "\n");
//--  fclose($fp); 
//-- 
//-- echo "Received particle publish marked with time: " . $datetime . "!"; // LET WEBHOOK KNOW WE RCVD AND AT WHAT TIME
//-- 
//-- ?>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Or this can get looked at on the Particle Web IDE HERE.

Thanks much,
Kolbi


#34

Not exactly, I use System.sleep(RI_UC, RISING, (seconds of sleep), SLEEP_NETWORK_STANDBY); successfully (with 0.7.0 - not tested since).

In an .ino you won’t need it since the pre-processor will add it. In .h and .cpp files you will need it, but it replaces any #include "application.h" (application.h was the predecessor of Particle.h)


#35

ScruffR,

Thanks for the info! Earlier I did try just waking on RI_UC as you stated above, but that didn’t work so good. It seems like when I use RI_UC, it takes longer to sleep and also as if it gets random pings so that the sleep cycle isn’t fully realized.

Ah, so that Particle.h replaced application.h - should I just use #include “Particle.h” and forget about the application.h?

Thanks as always,
Kolbi


#36

Yup, that’s what I’d do.


#37

For that example, did you also use SYSTEM_THREAD(ENABLED)?


#38

I usually do.


#39

Funny, maybe it’s just a coincidence but the unit seems to go to sleep quicker with SYSTEM_THREAD(ENABLED). I was thinking about the connection health itself - maybe because I get poor reception at my house, it interrupts sleep to hand-shake network and ensures the connection alive?
42%20PM

Also, I have read some on SYSTEM_THREAD(ENABLED) - are there any ill effects or situations where it is not advisable to use such?

/r,
Kolbi


#40

I have now tested with my 3G Electron with 1.0.1 and I (now) do have to set AT+URING=1.

SYSTEM_MODE(AUTOMATIC)
SYSTEM_THREAD(ENABLED)

void setup() {
  pinMode(D7, OUTPUT);
  digitalWrite(D7, HIGH);
  
  Particle.function("wake", dmy);
}

void loop() {
  char msg[64];

  digitalWrite(D7, !digitalRead(D7));

  Cellular.command("AT+URING=1\r\n");
  for (uint32_t ms = millis(); millis() - ms < 1000; Particle.process());
  
  strcpy(msg, (const char*)Time.format("%H:%M:%S"));
  System.sleep({ RI_UC, BTN }, { RISING, FALLING }, 20*60, SLEEP_NETWORK_STANDBY);
  snprintf(msg, sizeof(msg), "%s - %s", msg, (const char*)Time.format("%H:%M:%S"));
  Particle.publish("Slept", msg, PRIVATE);

}

int dmy(const char*) { // function does nothing and ignores the parameter
  return 1;
}

I see a delayed sleep on first run, but after that everything seems to be as expected.