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

ScruffR,

Thanks much for your testing. I used your settings of

SYSTEM_MODE(AUTOMATIC)
SYSTEM_THREAD(ENABLED)

and

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

in my code but it still acts the same. Please do correct me if I’m wrong, but I should see no different between the two sleep settings correct?

That is to say, if I understand correctly, between using either this

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

or

System.sleep(BTN, FALLING,(seconds of sleep), SLEEP_NETWORK_STANDBY);

the sleep should act exactly the same, only difference should be a delayed sleep on first run and if the device gets pinged it will wake up in addition to pressing the button?

/r,
Kolbi

Looking at some stats, maybe I should put some work into automatically going into a deep sleep mode based on sample rate desire. Of course this changes the scope of this thread’s title but still in the same ball park-ish…
publish-consideration

Can you explain this statement?

The docs do a better job
https://docs.particle.io/reference/device-os/firmware/electron/#particle-function-

As the name wake suggests, you can call that function to wake the device, but the function isn't expected to do anything (hence dmy).

You won't be able to sleep longer than 23 minutes without losing contact to the device due to the closing UDP window after that (see Particle.keepAlive())

Have you tested my code?
Check if your device behaves as expected with that exact code and then try to work forward to see when the unexpected behaviour starts to appear.

Got it working!

By keeping eye (and cursing) at the blue breathing led I noticed that the on/off cycle began to have a flip/flop lighting effect - where it would be off for a sleep cycle and then appear to be on for a cycle, this could only happened if it overslept and missed a sensor reading so I increased ‘secs_less_intrvl_sleep’ to 3 and it started to work as expected. Very odd but for some reason, it needs a little bit extra offset when using wake-on-ping.

The random waking problem was, at least I think - but too lazy to prove, because the signal quality was so poor in where I physically had the device in my house, making it keep trying to regain cell connection? Now that I moved my testing to another location, downstairs next to the window, it seems to not suffer from the previous nightmares.

1 Like

Thought up ways to cut back on data being published, came up and implemented this:

Follows is the firmware code. Webhook code and PHP receiver file are in the firmware code, at the bottom.

Updated 16 April, two minor calculation issues corrected, added padding for sensor values to ensure the length is as defined in preamble.

//////////////////////////////////////////////////////////////////////////////////////////////////////
//     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 TWO LINES WILL INSERT      //
//          A FALSE EVENT ON THE TWENTIETH SAMPLE TAKEN, ON SENSOR-3 AND AGAIN AT SAMPLE            //
//          TWENTY-THREE. DO NOT FORGET TO REMOVE THESE LINES * * *                                 //
//          if (smpls_performed == 21) sensor_value[3][5] += 100; // SIMULATE EVENT                 //
//          if (smpls_performed == 23) sensor_value[3][5] += 100; // SIMULATE EVENT                 //
//                                                                                                  //
//////////////////////////////////////////////////////////////////////////////////////////////////////

#include "Particle.h"

#include <math.h>

void setup();
void loop();

SYSTEM_MODE(AUTOMATIC)
SYSTEM_THREAD(ENABLED)

///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ 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

// SENSOR REPORT CONFIG
const int no_of_sensors = 6;     // THE NUMBER OF SENSOR READINGS TO COLLECT PER SAMPLE
int sensorlen[no_of_sensors] = {3, 3, 3, 3, 3, 3}; // THE LENGTH OF EACH SENSOR'S VALUE

// ALAERT SETTING //
int alert_smpl_intvl = 30;       // ALERT SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
const int alrt_publish_rnds = 2; // 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 enable_wop = 1;              // ENABLE WAKE-ON-PING
int sleep = 1;                   // SLEEP MODE ON = 1 / SLEEP MODE OFF = 0
int sleep_wait = 1;              // TIME TO WAIT AFTER PUBLISH TO FALL ASLEEP
int secs_less_intrvl_sleep = 3;  // SECONDS TO DELAY FROM SAMPLE INTERVAL FOR SLEEP TIME
int app_watchdog = 90000;        // 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 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
int a = 0;                                                          // GP TIMER USE
int published_norm1_or_alert2 = 0;                                  // TYPE OF PUBLISH LAST PERFORMED
int sla = 0;                                                        // USED IN CREATION OF PREAMBLE
int led1 = D7;                                                      // ONBOARD BLUE LED
// END OF DECLARATIONS AND INITIAL PARAMETERS

void setup()
{
  Serial.begin(9600);
  pinMode(led1, OUTPUT);
  // SOLAR SETTINGS //
  //PMIC pmic; //INITIALIZE THE PMIC CLASS TO CALL THE POWER MANAGEMENT FUNCTIONS BELOW
  //pmic.setChargeCurrent(0,0,1,0,0,0); 	//SET CHARGING CURRENT TO 1024MA (512 + 512 OFFSET)
  //pmic.setInputVoltageLimit(4840);     	//SET THE LOWEST INPUT VOLTAGE TO 4.84 VOLTS, FOR 5V SOLAR PANEL
  //pmic.setInputVoltageLimit(5080);     	//SET THE LOWEST INPUT VOLTAGE TO 5.08 VOLTS, FOR 6V SOLAR PANEL
}

ApplicationWatchdog wd(app_watchdog, System.reset);

void loop()
{

  FuelGauge fuel;

  if (a == 0 && fuel.getSoC() > 20)
  {
    Cellular.command("AT+URING=1\r\n");
    for (uint32_t ms = millis(); millis() - ms < 2000; Particle.process())
      a = 1;
  }

  if (fuel.getSoC() < 20) //LOW BATTERY DEEP SLEEP
  {
    if (a != 0)
      Particle.publish("a", "9 - DEVICE SHUTTING DOWN FOR 8 HOURS, BATTERY SoC < 20!", 60, PRIVATE, NO_ACK); // LET SERVER KNOW WE ARE GOING TO SLEEP
    delay(1000);
    System.sleep(SLEEP_MODE_DEEP, 14400); // SLEEP 8 HOURS IF SoC < 20
  }

  if (a == 1)
  {
    Particle.publish("a", "9 - DEVICE ONLINE!", 60, PRIVATE, NO_ACK); // LET SERVER KNOW ONLINE!
    a = 2;
  }

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ START SENSOR VALUE GATHERING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  // 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-4] = SENSOR 1, HISTORICAL SENSOR VALUES
  // sensor_value[1][5] = SENSOR 1, HISTORICAL SENSOR VALUE AVERAGE OF ALL FOUR

  sensor_value[0][0] = random(441, 450); // PLACES RANDOM VALUES FOR TESTING
  sensor_value[1][0] = random(461, 470); // PLACES RANDOM VALUES FOR TESTING
  sensor_value[2][0] = random(471, 480); // PLACES RANDOM VALUES FOR TESTING
  sensor_value[3][0] = random(481, 490); // PLACES RANDOM VALUES FOR TESTING
  sensor_value[4][0] = random(491, 500); // PLACES RANDOM VALUES FOR TESTING
  sensor_value[5][0] = random(501, 510); // PLACES RANDOM VALUES FOR TESTING

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END SENSOR VALUE GATHERING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/GATHER, EVENT CHECK, AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  //-- BEGIN SENSOR 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)
        sensor_value[3][5] += 100; // SIMULATE EVENT * * * *
      if (smpls_performed == 23)
        sensor_value[3][5] += 100; // 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 EVENT CHECK \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ START 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("!");
      published_norm1_or_alert2 = 2;
    }
    else
    {
      Serial.println(".");
      published_norm1_or_alert2 = 1;
    }
    smpls_performed++;
    wd.checkin(); // SOFTWARE WATCHDOG CHECK-IN
    if (alert_cycl_mark == (smpls_performed + 1))
      alrt_state_chng = 3;
  }
  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END SAMPLING TIME CHECK \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ START BEDTIME CHECK \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
  if (sleep == 1 && published_norm1_or_alert2 == 1 && norm_smpl_intvl > 18 && t2 >= (Time.now() + sleep_wait))
  {
    if (enable_wop)
    {
      System.sleep({RI_UC, BTN}, {RISING, FALLING}, (((norm_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 2), SLEEP_NETWORK_STANDBY);
      Cellular.command("AT+URING=0\r\n");
      Cellular.command("AT+URING=1\r\n");
    }
    else
    {
      System.sleep(BTN, FALLING, ((norm_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait), SLEEP_NETWORK_STANDBY);
    }
    published_norm1_or_alert2 = 0;
  }

  if (sleep == 1 && published_norm1_or_alert2 == 2 && alert_smpl_intvl > 18 && t2 >= (Time.now() + sleep_wait))
  {
    if (enable_wop)
    {
      System.sleep({RI_UC, BTN}, {RISING, FALLING}, (((alert_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 2), SLEEP_NETWORK_STANDBY);
      Cellular.command("AT+URING=0\r\n");
      Cellular.command("AT+URING=1\r\n");
    }
    else
    {
      System.sleep(BTN, FALLING, ((alert_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait), SLEEP_NETWORK_STANDBY);
    }
    published_norm1_or_alert2 = 0;
  }
  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END BEDTIME CHECK \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ BEGIN ALERT STATE PUBLISH FLUSH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
  if ((alrt_state_chng == 3) || (alrt_state_chng == 1 && alert_cycl_mark > smpls_performed))
  {
    fullpub_temp1[0] = 0;
    fullpublish[0] = 0; // THIS IS NEW STUFF TO PUT PREAMBLE ON DATA
    snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d%d", alrt_state_chng, no_of_sensors);
    strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
    for (sla = 0; sla < no_of_sensors; sla++)
    {
      snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d", sensorlen[sla]);
      strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
    } // END OF NEW STUFF TO PUT PREAMBLE ON DATA
    for (xa = 0; xa < x; xa++)
    {
      for (y = 0; y < no_of_sensors; y++)
      {
        snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f", sensorlen[y], storage[x][y]);
        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), "E%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));
      if (do_publish)
      {
        Particle.publish("a", fullpublish, 60, PRIVATE, NO_ACK); // PARTICLE.PUBLISH EVENT ! ! ! !
        digitalWrite(led1, HIGH);
        delay(200);
        digitalWrite(led1, LOW);
      }
    }
    fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
    pubs_performs++;
    if (alrt_state_chng == 3)
      alrt_state_chng = 0;
  }
  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END ALERT STATE PUBLISH FLUSH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ START SAMPLES TAKEN CHECK AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
  if (x == rnds_to_publish)
  {
    fullpub_temp1[0] = 0;
    fullpublish[0] = 0; // THIS IS NEW STUFF TO PUT PREAMBLE ON DATA
    snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d%d", alrt_state_chng, no_of_sensors);
    strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
    for (sla = 0; sla < no_of_sensors; sla++)
    {
      snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d", sensorlen[sla]);
      strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
    } // END OF NEW STUFF TO PUT PREAMBLE ON DATA
    for (x = 0; x < rnds_to_publish; x++)
    {
      for (y = 0; y < no_of_sensors; y++)
      {
        snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f", sensorlen[y], storage[x][y]);
        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), "E%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));
      if (do_publish)
      {
        Particle.publish("a", fullpublish, 60, PRIVATE, NO_ACK); // PARTICLE.PUBLISH EVENT ! ! ! !
        digitalWrite(led1, HIGH);
        delay(200);
        digitalWrite(led1, LOW);
      }
    }
    fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
    pubs_performs++;
  }
  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END SAMPLES TAKEN CHECK AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

  ///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END GATHER, EVENT CHECK, AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
}
///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END OF SKETCH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 	WEBHOOK, DO SEARCH AND REPLACE AS "//--" TO "" TO MAKE USABLE, THEN PASTE INTO CUSTOM TEMPLATE WEBHOOK AREA
//--{
//--"event": "a",
//--"url": "http://BLAHBLAHBLAH/RECEIVER.php",
//--"requestType": "GET",
//--"noDefaults": false,
//--"rejectUnauthorized": false,
//--"query": {
//--	"id": "{{SPARK_EVENT_VALUE}}",
//--	"location": "{{SPARK_CORE_ID}}"
//--	}
//--}
//  END OF CUSTOM WEBHOOK
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//--  RECEIVER.php FILE, DO SEARCH AND REPLACE AS "//--" TO "" TO MAKE USABLE
//--
//--  <?php
//--
//--  if (!isset($_GET["data"]))
//--  	{
//--  	echo "404";
//--  	die();
//--  	}
//--
//--  $incoming = htmlspecialchars($_GET["data"]); // GET THE PUBLISHED INFORMATION SENT
//--  $coreid = htmlspecialchars($_GET["coreid"]);
//--  $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);
//--  $datetime = $date . " " . $time;
//--  $datetime2 = $datetime;
//--
//--  if ((substr($incoming, 0, 1)) == "9")
//--  	{
//--  	echo "* * * " . $incoming . " * * *";
//--  	$fp = fopen('ark/system.csv', 'a');
//--  	fwrite($fp, $datetime . "," . substr($incoming,4,99) . "\n");
//--  	fclose($fp);
//--  	die();
//--  	}
//--
//--  echo "Publish string: \n" . $incoming . "\n\n";
//--  $inlength = strlen($incoming);
//--
//--  $involts = substr($incoming, -4); // GET THE VOLTAGE
//--  $insoc = substr($incoming, -7, 3); // GET THE SOC
//--  $intrvl = substr($incoming, strpos($incoming, 'E') + 1, ($length - 7));
//--  $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
//--  $isalert = substr($incoming, 0, 1);
//--  $number_sensors = substr($incoming, 1, 1);
//--
//--  echo "Length: " . $inlength . ", Battery SoC: " . ($involts / 100) . ", Battery Volts: " . ($insoc / 100) . "\n";
//--  echo "Alert Flag: " . $isalert . ", Number of sensors: " . $number_sensors . ".\n";
//--
//--  for ($i = 0; $i < $number_sensors; $i++)
//--  	{
//--  	echo "Sensor " . ($i + 1) . ": " . substr($incoming, ($i + 2) , 1) . " chars    ";
//--  	}
//--
//--  $preamble = $number_sensors + 2;
//--  $rawdata = substr($process, $preamble);
//--
//--  for ($a = 0; $a < $number_sensors; $a++)
//--  	{
//--  	$sensetln = $sensetln + (substr($incoming, ($a + 2) , 1));
//--  	}
//--
//--  $setspublished = strlen($rawdata) / $sensetln;
//--
//--  $roundmin = ($intrvl * $setspublished) / 60;
//--
//--  $roundtime = number_format((float)$roundmin, 1, '.', '');
//--
//--  echo "Sample Interval: " . $intrvl . " - Sample Rounds: " . $setspublished . ", with a publish interval time of " . $roundtime. " minutes.\n";
//--
//--  $delin = $setspublished;
//--  $b = 0;
//--  $build2 = "";
//--
//--  for ($x = 0; $x < $setspublished; $x++)
//--  	{
//--  	for ($a = 0; $a < $number_sensors; $a++)
//--  		{
//--  		$build = substr($rawdata, $b, (substr($incoming, ($a + 2) , 1)));
//--  		$b = $b + (substr($incoming, ($a + 2) , 1));
//--  		$build2.= $build . ",";
//--  		}
//--
//--  	$build3 = substr($build2, 0, (strlen($build2) - 1));
//--  	$inArray[$x] = $build3;
//--  	$build1 = ""; $build2 = ""; $build3 = "";
//--  	}
//--
//--  $sectodel = $intrvl * $setspublished;
//--  $timestamp = date('Y/m/d H:i:s', strtotime('-' . $sectodel . ' seconds', strtotime($datetime2)));
//--  $datetime = $timestamp;
//--
//--  for ($i = 0; $i < $setspublished; $i++)
//--  	{
//--  	$sectoadd = ($intrvl * ($i + 1));
//--  	$datetimeN = date('Y/m/d H:i:s', strtotime('+' . $sectoadd . ' seconds', strtotime($timestamp)));
//--  	$inArray[$i] = $datetimeN . "," . $inArray[$i] . "\n";
//--  	}
//--
//--  $fp = fopen('ark/lf.csv', 'a'); // WRITE SENSOR DATA TO CSV FILE NAMED lf.csv LOCATED IN THE ark FOLDER
//--
//--  for ($i = 0; $i < ($delin); $i++)
//--  	{
//--  	fwrite($fp, $inArray[$i]);
//--  	}
//--
//--  fclose($fp);
//--
//--  $fp = fopen('ark/p1f.csv', 'a'); // WRITE SOC DATA TO CSV FILE NAMED p1f.csv
//--  fwrite($fp, $datetime2 . "," . ($insoc / 100) . "\n");
//--  fclose($fp);
//--
//--  $fp = fopen('ark/p2f.csv', 'a'); // WRITE VOLTS DATA TO CSV FILE NAMED p2f.csv
//--  fwrite($fp, $datetime2 . "," . ($involts / 100) . "\n");
//--  fclose($fp);
//--
//--  echo "Received particle publish marked with time: " . $datetime2 . ", calculated time: " . $datetimeN . "!"; // LET THE WEBHOOK KNOW WE RCVD AND AT WHAT TIME
//--
//--  ?>
//--
//-- 	END OF RECEIVER.php FILE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Suggestions, corrections, or critiques are warmly welcomed!

/r,
Kolbi

Question about deep_sleep.

In reading forum posts and available tutorials, it seems that for going longer publishing intervals, at least ones that are greater than 60 minutes, that using deep_sleep and cell modem off would be the way to go and save battery life?

Here’s where my wonder and confusion is. If I only wanted one report per hour and used 144 seconds interval between taking sensor samples, that would yield me a published length of 600 characters which is fine, and samples would be just a tad over every two minutes.
But obviously the electron couldn’t be asleep for an hour; the cell chip could, but the electron would only be able to sleep for 135 seconds before waking up to take a sensor reading. Give that it would have to wake so often, would deep sleep, cell and particle disconnect, and data/energy usage by worth implementing the deep_sleep at all?

/r,
Kolbi

ScruffR,
As I am currently performing a battery drop test until 20% SoC to get a feel for worst case scenario, I noticed a few drops in transmissions. I believe this correlates to the waking problem where the modem tries to reestablish connection.

/r,
Kolbi

1 Like

Files can be found on GitHub https://github.com/rkolbi/sensor_reporter.

/r,
Kolbi

Some interesting battery performance stats using a 3.7v / 6600 mAh battery. These drop tests were conducted with a fully charged battery, sketch using sleep/network standby with wake on ping, and any source of charging removed, ending when SoC was at 20%. Note that no external sensor were connected to the device during these drop tests.

Taking samples every 88 seconds / published every 15 rounds (publish every 22 minutes): 36 hours
Taking samples every 180 seconds / published every 7 rounds (publish every 21 minutes): 50 hours

Also to note; with this battery at 20% SoC it took approximately 3.5 hours to achieve full charge.

I received some of my sensors (BNO055, ADS1115, and MCP9808) and incorporated them into my sketch, granted it is a bit rough and needs some (or lots of) cleaning up but it is working…

Of course, any feedback, suggestions, or criticism is very much appreciated.

/r,
Kolbi

*Updated 3 May: Moved a lot of functions around and made the sketch much more reactive to alert state detections. It is a bit repetitive in some parts, and I’m sure it can be cleaned up some - but currently, my 3 weeks of c++ knowledge has been stretched past the extents :wink: If it wasn’t for the excellent and helpful people here, I couldn’t have achieved this in a year’s duration.

GitHub link
Particle IDE

//////////////////////////////////////////////////////////////////////////////////////////////////////
//     THIS SKETCH IS TO GATHER READINGS FROM SENSORS, IN THIS CASE THE BNO055, ADS1115, AND        //
//     MCP9808, 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.                                               //
//     CONFIGURABLE SETTINGS ARE BELOW, UNDER *USER CONFIGURABLE PARAMETERS*                        //
//////////////////////////////////////////////////////////////////////////////////////////////////////

#include "Particle.h"
#include "particle-BNO055.h"
#include <Wire.h>
#include <ADS1115.h>
#include "MCP9808.h"
#include <math.h>

SYSTEM_MODE(AUTOMATIC)
SYSTEM_THREAD(ENABLED)

ADS1115 ads;
MCP9808 mcp = MCP9808();
Adafruit_BNO055 bno = Adafruit_BNO055(55);

///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ BEGIN USER CONFIGURABLE PARAMETERS \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

// SAMPLE & PUBLISH SETTINGS //
int norm_smpl_intvl = 120;		// SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
const int rnds_to_publish = 11; // WAIT UNTIL x NUMBER OF SAMPLES ARE GATHERED TO BURST PUBLISH
int do_publish = 1;				// PERFORM PARTICLE.PUBLISH EVENTS

// SENSOR REPORT CONFIG
const int no_of_sensors = 8; // THE NUMBER OF SENSOR READINGS TO COLLECT PER SAMPLE
// 								▼ THE LENGTH OF EACH SENSOR'S VALUE
int sensorlen[no_of_sensors] = {4, 4, 4, 4, 3, 3, 3, 3};

// ALAERT SETTING //
int alert_smpl_intvl = 60;		 // ALERT SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
const int alrt_publish_rnds = 2; // WHILE IN SENSOR ALERT - HOW MANY COMPLETE PUBLISH CYCLES TO LOOP THROUGH
// 					 					   ▼ SET ARRAY FOR SENSOR ALERT
int sensor_alert_thrshld[no_of_sensors] = {999, 999, 999, 999, 2, 15, 15, 15};

// POWER SETTINGS //
int solar_opt = 0;				// SOLAR OPTIMIZATION, 0 = SOLAR OPT OFF, 5 = 5V SOLAR PANEL, 6 = 6V SOLAR PANEL
int enable_wop = 0;				// ENABLE WAKE-ON-PING
int sleep = 1;					// SLEEP MODE ON = 1 / SLEEP MODE OFF = 0
int sleep_wait = 1;				// TIME TO WAIT AFTER PUBLISH TO FALL ASLEEP
int secs_less_intrvl_sleep = 0; // ADDITIONAL SECONDS TO DELAY FROM SAMPLE INTERVAL FOR SLEEP TIME, 5 SECONDS ARE ALREADY SUBTRACTED
int app_watchdog = 360000;		// APPLICATION WATCHDOG TIME TRIGGER IS MS - SLEEP TIME SHOULD NOT BE COUNTED

///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END 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
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][7];								// DIMENSION THE SENSOR VALUE ARRAY
int vcell;															// BATTERY INFO
int vsoc;															// BATTERY INFO
int a = 0;															// GP TIMER USE
int published_norm1_or_alert2 = 0;									// TYPE OF PUBLISH LAST PERFORMED
int sla = 0;														// USED IN CREATION OF PREAMBLE
int led1 = D7;														// ONBOARD BLUE LED
int16_t adc0, adc1, adc2, adc3;										// IMU
float adcx0, adcx1, adcx2, adcx3;									// IMU
// END OF DECLARATIONS AND INITIAL PARAMETERS

void i2cWake()
{
	//WAKE UP i2c DEVICES
	ads.setMode(MODE_CONTIN);
	mcp.setPowerMode(MCP9808_CONTINUOUS);
	Wire.beginTransmission(0x28);
	Wire.write(0x3E);
	Wire.write(0x00);
	Wire.endTransmission();
}

void i2cSleep()
{
	//PUT i2c DEVICES TO SUSPEND
	ads.setMode(MODE_SINGLE);
	mcp.setPowerMode(MCP9808_LOW_POWER);
	Wire.beginTransmission(0x28);
	Wire.write(0x3E);
	Wire.write(0x02);
	Wire.endTransmission();
}

void preamble()
{
	// PUT PREAMBLE ON DATA
	fullpub_temp1[0] = 0;
	fullpublish[0] = 0;

	snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d%d", alrt_state_chng, no_of_sensors);
	strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);

	for (sla = 0; sla < no_of_sensors; sla++)
	{
		snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d", sensorlen[sla]);
		strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
	}
}

void fullpublishout()
{
	Particle.publish("a", fullpublish, 60, PRIVATE, NO_ACK); // PARTICLE.PUBLISH EVENT ! ! ! !

	digitalWrite(led1, HIGH);

	for (uint32_t ms = millis(); millis() - ms < 500; Particle.process())
		;
	digitalWrite(led1, LOW);
}

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

	pinMode(led1, OUTPUT);

	// SOLAR SETTINGS //
	if (solar_opt)
	{
		PMIC pmic; //INITIALIZE THE PMIC CLASS TO CALL THE POWER MANAGEMENT FUNCTIONS BELOW

		pmic.setChargeCurrent(0, 0, 1, 0, 0, 0); //SET CHARGING CURRENT TO 1024MA (512 + 512 OFFSET)

		if (solar_opt == 5)
			pmic.setInputVoltageLimit(4840); //SET THE LOWEST INPUT VOLTAGE TO 4.84 VOLTS, FOR 5V SOLAR PANEL

		if (solar_opt == 6)
			pmic.setInputVoltageLimit(5080); //SET THE LOWEST INPUT VOLTAGE TO 5.08 VOLTS, FOR 6V SOLAR PANEL
	}
	Wire.begin(); // INITIALIZE I2C
	for (uint32_t ms = millis(); millis() - ms < 500;)
		;

	bno.begin(); // INITIALIZE IMU
	for (uint32_t ms = millis(); millis() - ms < 500;)
		;

	bno.setExtCrystalUse(true);
	ads.getAddr_ADS1115(ADS1115_DEFAULT_ADDRESS); // (ADDR = GND)
	ads.setGain(GAIN_TWOTHIRDS);				  // 2/3x gain +/- 6.144V  1 bit = 0.1875mV
	ads.setMode(MODE_CONTIN);					  // ads.setMode(MODE_CONTIN) = Continuous conversion mode - ads.setMode(MODE_SINGLE); Power-down single-shot mode (default)
	ads.setRate(RATE_128);						  // 128SPS (default) 8,16,32,64,128,250,475,860
	ads.setOSMode(OSMODE_SINGLE);				  // Set to start a single-conversion
	ads.begin();
	mcp.setResolution(MCP9808_SLOWEST);

	i2cWake();
}

ApplicationWatchdog wd(app_watchdog, System.reset);

void loop()
{
	FuelGauge fuel;

	if (a == 0 && fuel.getSoC() > 20)
	{
		if (enable_wop)
			Cellular.command("AT+URING=1\r\n");

		for (uint32_t ms = millis(); millis() - ms < 1000; Particle.process())
			Particle.publish("a", "9 - DEVICE ONLINE!", 60, PRIVATE, NO_ACK); // LET SERVER KNOW ONLINE!

		digitalWrite(led1, HIGH);

		for (uint32_t ms = millis(); millis() - ms < 1000; Particle.process())
			;

		digitalWrite(led1, LOW);
		a = 2;
	}

	if (fuel.getSoC() < 20) //LOW BATTERY DEEP SLEEP
	{
		if (a != 0)
			Particle.publish("a", "9 - DEVICE SHUTTING DOWN, BATTERY SoC < 20!", 60, PRIVATE, NO_ACK); // LET SERVER KNOW WE ARE GOING TO SLEEP

		for (uint32_t ms = millis(); millis() - ms < 5000; Particle.process()) //EXTRA TIME BEFORE DEEP SLEEP
			;

		System.sleep(SLEEP_MODE_DEEP, 7200); // SLEEP 2 HOURS IF SoC < 20
	}

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ START SENSOR VALUE GATHERING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

	// 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-4] = SENSOR 1, HISTORICAL SENSOR VALUES
	// sensor_value[1][5] = SENSOR 1, HISTORICAL SENSOR VALUE AVERAGE OF ALL FOUR

	adc0 = ads.Measure_SingleEnded(0); //ADS1115 VOLTAGE READING 1
	adcx0 = ((0.1875 * adc0) / 1000);
	sensor_value[0][0] = adcx0;

	adc1 = ads.Measure_SingleEnded(1); //ADS1115 VOLTAGE READING 2
	adcx1 = ((0.1875 * adc1) / 1000);
	sensor_value[1][0] = adcx1;

	adc2 = ads.Measure_SingleEnded(2); //ADS1115 VOLTAGE READING 3
	adcx2 = ((0.1875 * adc2) / 1000);
	sensor_value[2][0] = adcx2;

	adc3 = ads.Measure_SingleEnded(3); //ADS1115 VOLTAGE READING 4
	adcx3 = ((0.1875 * adc3) / 1000);
	sensor_value[3][0] = adcx3;

	int tempget = fabs(10 * mcp.getTemperature()); //MCP9808 TEMP READING
	sensor_value[4][0] = tempget;

	sensors_event_t event; //BNO055 SAMPLE GATHERING
	bno.getEvent(&event);

	float orx = event.orientation.x; // BNO055 ORIENTATION X
	// MAKE ORX RELATIVE TO 180'
	int orxt1 = int(orx) - 180;
	orxt1 = abs((orxt1 + 180) % 360 - 180);
	int orxt2 = 180 - int(orx);
	orxt2 = abs((orxt2 + 180) % 360 - 180);
	if (orxt1 > orxt2)
		sensor_value[5][0] = float(orxt2);
	else
		sensor_value[5][0] = float(orxt1);

	float ory = event.orientation.y; // BNO055 ORIENTATION Y
	ory = ory + 180;
	sensor_value[6][0] = ory;

	float orz = event.orientation.z; // BNO055 ORIENTATION Z
	orz = orz + 180;
	sensor_value[7][0] = orz;

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END SENSOR VALUE GATHERING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/GATHER, EVENT CHECK, AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

	//-- ALERT CHECK --
	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 > 4)
		{
			alrt_ckng_tmp = fabs(sensor_value[w][0] - sensor_value[w][5]);
			sensor_value[w][6] = alrt_ckng_tmp;

			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]);

				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
				}

				alert_cycl_mark = smpls_performed + alert_sample_qty;
			}
		}
	}
	//-- END OF ALERT CHECK

	//-- START 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("!");
			published_norm1_or_alert2 = 2;
		}
		else
		{
			Serial.println(".");
			published_norm1_or_alert2 = 1;
		}

		smpls_performed++;

		if (smpls_performed > alert_cycl_mark)
			current_smpl_intvl = norm_smpl_intvl;

		wd.checkin(); // SOFTWARE WATCHDOG CHECK-IN

		if (alert_cycl_mark == (smpls_performed + 1))
			alrt_state_chng = 3;
	}
	//-- END SAMPLING TIME CHECK --

	//-- BEGIN ALERT STATE PUBLISH FLUSH --
	if ((alrt_state_chng == 3) || (alrt_state_chng == 1))
	{
		preamble();
		for (xa = 0; xa < x; xa++)
		{
			for (y = 0; y < no_of_sensors; y++)
			{
				if (y < 4)
				{ //THIS IS TO LET THE FIRST FOUR READINGS HAVE TWO DECIMAL PLACES
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.2f", sensorlen[y], storage[xa][y]);
				}
				else
				{
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f", sensorlen[y], storage[xa][y]);
				}
				strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
			}
		} // BUILT FULLPUBLISH STRING
		x = 0;
		xa = 0;

		FuelGauge fuel; // GET BATTERY INFO
		fullpub_temp1[0] = 0;

		if (alrt_state_chng == 1) //FIX ISSUE WHERE NORM COLLECTED BUFFER REPORTING INTERVAL IS PUBLISHED AS ALERT REPORT INTERVAL
		{
			snprintf(fullpub_temp1, sizeof(fullpub_temp1), "E%d%d%d", norm_smpl_intvl, ((int)(fuel.getVCell() * 100)), ((int)(fuel.getSoC() * 100))); // ADDING SAMPLE RATE, BATTERY INFO
		}
		else
		{
			snprintf(fullpub_temp1, sizeof(fullpub_temp1), "E%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));

		if (do_publish)
			fullpublishout();

		if (alrt_state_chng == 1)
		{
			current_smpl_intvl = alert_smpl_intvl;
			t2 = Time.now() + current_smpl_intvl;
		}

		pubs_performs++;

		if (alrt_state_chng == 3)
		{
			alrt_state_chng = 0;
			current_smpl_intvl = norm_smpl_intvl;
			t2 = Time.now() + current_smpl_intvl;
		}

		//-- SEND OUT SYSTEM NOTE TO ALERT OOS
		if (alrt_state_chng == 1)
		{
			fullpub_temp1[0] = 0;
			fullpublish[0] = 0;

			snprintf(fullpub_temp1, sizeof(fullpub_temp1), "9 - OOS: ");
			strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);

			for (y = 0; y < no_of_sensors; y++)
			{
				if (y < 4)
				{ //THIS IS TO LET THE FIRST FOUR READINGS HAVE TWO DECIMAL PLACES
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.2f|%.0f>%d ", sensorlen[y], sensor_value[y][0], sensor_value[y][6], sensor_alert_thrshld[y]);
				}
				else
				{
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f|%.0f>%d ", sensorlen[y], sensor_value[y][0], sensor_value[y][6], sensor_alert_thrshld[y]);
				}
				strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
			}																						// BUILT FULLPUBLISH STRING
			FuelGauge fuel;																			// GET BATTERY INFO
			fullpub_temp1[0] = 0;

			Serial.println(fullpublish);
			Serial.printf("ASP: The size of published string is %d bytes. \n", strlen(fullpublish));

			if (do_publish)
				fullpublishout();

			alrt_state_chng = 2;
		}
		//-- END SEND OUT SYSTEM NOTE TO ALERT OOS
	}
	//-- END ALERT STATE PUBLISH FLUSH --
	else

	//-- START NORMAL CHECK AND PUBLISH --
	{
		if (x == rnds_to_publish)
		{
			preamble();
			for (x = 0; x < rnds_to_publish; x++)
			{
				for (y = 0; y < no_of_sensors; y++)
				{
					if (y < 4)
					{ //THIS IS TO LET THE FIRST FOUR READINGS HAVE TWO DECIMAL PLACES
						snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.2f", sensorlen[y], storage[x][y]);
					}
					else
					{
						snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f", sensorlen[y], storage[x][y]);
					}
					strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
				}
			} // BUILT FULLPUBLISH STRING
			x = 0;
			FuelGauge fuel; // GET BATTERY INFO
			fullpub_temp1[0] = 0;

			snprintf(fullpub_temp1, sizeof(fullpub_temp1), "E%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));

			if (do_publish)
				fullpublishout();

			fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
			pubs_performs++;
		}
		//-- END SAMPLES TAKEN CHECK AND PUBLISH --
	}

	//-- START BEDTIME CHECK FOR NORMAL MODE --
	if (sleep == 1 && published_norm1_or_alert2 == 1 && norm_smpl_intvl > 23 && t2 >= (Time.now() + sleep_wait))
	{
		i2cSleep();

		if (enable_wop)
		{
			System.sleep({RI_UC, BTN}, {RISING, FALLING}, (((norm_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 5), SLEEP_NETWORK_STANDBY);

			Cellular.command("AT+URING=0\r\n");
			Cellular.command("AT+URING=1\r\n");
		}
		else
		{
			System.sleep(BTN, FALLING, (((norm_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 5), SLEEP_NETWORK_STANDBY);
		}

		i2cWake();

		published_norm1_or_alert2 = 0;
	}

	//-- START BEDTIME CHECK FOR ALERT MODE --
	if (sleep == 1 && published_norm1_or_alert2 == 2 && alert_smpl_intvl > 23 && t2 >= (Time.now() + sleep_wait))
	{
		i2cSleep();

		if (enable_wop)
		{
			System.sleep({RI_UC, BTN}, {RISING, FALLING}, (((alert_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 5), SLEEP_NETWORK_STANDBY);

			Cellular.command("AT+URING=0\r\n");
			Cellular.command("AT+URING=1\r\n");
		}
		else
		{
			System.sleep(BTN, FALLING, (((alert_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 5), SLEEP_NETWORK_STANDBY);
		}

		i2cWake();

		published_norm1_or_alert2 = 0;
	}
	//-- END BEDTIME CHECK --

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END GATHER, EVENT CHECK, AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
	for (uint32_t ms = millis(); millis() - ms < 700;)
		;
}
2 Likes

One problem I ran into is finding the angular difference between events, whereas at time1 is could be 350’ and then at time2 it could be 003’. Best I could come up with is this:

	float orx = event.orientation.x; // BNO055 ORIENTATION X
	// MAKE ORX RELATIVE TO 180'
	orxt1 = int(orx) - 180;
	orxt1 = abs((orxt1 + 180) % 360 - 180);
	orxt2 = 180 - int(orx);
	orxt2 = abs((orxt2 + 180) % 360 - 180);
	if (orxt1 > orxt2)
		sensor_value[5][0] = float(orxt2);
	else
		sensor_value[5][0] = float(orxt1);

If anyone has a better way to do this, please share :wink:

/r,
Kolbi

Latest working…

Particle IDE Share

//////////////////////////////////////////////////////////////////////////////////////////////////////
//     THIS SKETCH IS TO GATHER READINGS FROM SENSORS, IN THIS CASE THE BNO055, ADS1115, AND        //
//     MCP9808, 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.                                               //
//     CONFIGURABLE SETTINGS ARE BELOW, UNDER *USER CONFIGURABLE PARAMETERS*			    //
//												    //
//	Following components are used:								    //
//	   -Pololu #2562, 5V Step-Up Voltage Regulator, https://www.pololu.com/product/2562/   	    //
//	    (100k onboard pullup removed, replaced with external 10k pulldown) 			    //
//	   -ADS1115 16-Bit ADC - 4 Channel, https://www.adafruit.com/product/1085		    //
//	   -MCP9808 High Accuracy I2C Temperature Sensor, https://www.adafruit.com/product/1782	    //
//	   -BNO055 9-DOF Absolute Orientation IMU Fusion, https://www.adafruit.com/product/2472	    //
//////////////////////////////////////////////////////////////////////////////////////////////////////

#include "Particle.h"
#include "particle-BNO055.h"
#include <Wire.h>
#include <ADS1115.h>
#include "MCP9808.h"
#include <math.h>

SYSTEM_MODE(AUTOMATIC)
SYSTEM_THREAD(ENABLED)

ADS1115 ads;
MCP9808 mcp = MCP9808();
Adafruit_BNO055 bno = Adafruit_BNO055(55);

///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ BEGIN USER CONFIGURABLE PARAMETERS \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

// SAMPLE & PUBLISH SETTINGS //
int norm_smpl_intvl = 60;	// SAMPLE INTERVAL, HOW MANY SECONDS TO WAIT BETWEEN EACH
const int rnds_to_publish = 5;	// WAIT UNTIL x NUMBER OF SAMPLES ARE GATHERED TO BURST PUBLISH
int do_publish = 1;		// PERFORM PARTICLE.PUBLISH EVENTS

// SENSOR REPORT CONFIG
const int no_of_sensors = 8; 	// THE NUMBER OF SENSOR READINGS TO COLLECT PER SAMPLE
// 				                   ▼ THE LENGTH OF EACH SENSOR'S VALUE
int sensorlen[no_of_sensors] = {4, 4, 4, 4, 3, 3, 3, 3};

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

// POWER SETTINGS //
int solar_opt = 0;		// SOLAR OPTIMIZATION, 0 = SOLAR OPT OFF, 5 = 5V SOLAR PANEL, 6 = 6V SOLAR PANEL
int enable_wop = 0;		// ENABLE WAKE-ON-PING
int sleep = 1;			// SLEEP MODE ON = 1 / SLEEP MODE OFF = 0
int sleep_wait = 1;		// TIME TO WAIT AFTER PUBLISH TO FALL ASLEEP
int secs_less_intrvl_sleep = 0; // ADDITIONAL SECONDS TO DELAY FROM SAMPLE INTERVAL FOR SLEEP TIME, 5 SECONDS ARE ALREADY SUBTRACTED
int app_watchdog = 360000;	// APPLICATION WATCHDOG TIME TRIGGER IS MS - SLEEP TIME SHOULD NOT BE COUNTED

///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END 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[16];					// TEMPORARY 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 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][7];			// DIMENSION THE SENSOR VALUE ARRAY
int vcell;						// BATTERY INFO
int vsoc;						// BATTERY INFO
int a = 0;						// GP TIMER USE
int published_norm1_or_alert2 = 0;			// TYPE OF PUBLISH LAST PERFORMED
int sla = 0;						// USED IN CREATION OF PREAMBLE
int led1 = D7;						// ONBOARD BLUE LED
int volt5 = D6;						// 5V STEP-UP VOLTAGE REGULATOR ON/OFF CONTROL (SAME CONTROL AS LED) 
int16_t adc0, adc1, adc2, adc3;				// IMU
float adcx0, adcx1, adcx2, adcx3;			// IMU
int sleeptime = 0;					// SLEEP DURATION
// END OF DECLARATIONS AND INITIAL PARAMETERS

void i2cWake()
{
	//WAKE UP i2c DEVICES
	digitalWrite(volt5, HIGH);			// TURN ON 5 VOLT REGULATOR
	ads.setMode(MODE_CONTIN);			// SET ADC SAMPLING MODE AS CONTINUOUS
	mcp.setPowerMode(MCP9808_CONTINUOUS);		// SET TEMP SENSOR MODE AS CONTINUOUS 
	Wire.beginTransmission(0x28);			// TURN IMU ON
	Wire.write(0x3E);				// TURN IMU ON
	Wire.write(0x00);				// TURN IMU ON
	Wire.endTransmission();				// TURN IMU ON
}

void i2cSleep()
{
	//PUT i2c DEVICES TO SUSPEND
	ads.setMode(MODE_SINGLE);			// SET ADC SAMPLING MODE AS CONTINUOUS
	mcp.setPowerMode(MCP9808_LOW_POWER);		// SET TEMP SENSOR MODE AS CONTINUOUS
	Wire.beginTransmission(0x28);			// TURN IMU ON
	Wire.write(0x3E);				// TURN IMU ON
	Wire.write(0x02);				// TURN IMU ON
	Wire.endTransmission();				// TURN IMU ON
	digitalWrite(volt5, LOW);			// TURN OFF 5 VOLT REGULATOR
}

void preamble()
{
	// PUT PREAMBLE ON DATA
	fullpub_temp1[0] = 0;
	fullpublish[0] = 0;

	snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d%d", alrt_state_chng, no_of_sensors);
	strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);

	for (sla = 0; sla < no_of_sensors; sla++)
	{
		snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%d", sensorlen[sla]);
		strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);
	}
}

void fullpublishout()
{
	Particle.publish("a", fullpublish, 60, PRIVATE, NO_ACK); // PARTICLE.PUBLISH EVENT ! ! ! !

	digitalWrite(led1, HIGH);

	for (uint32_t ms = millis(); millis() - ms < 500; Particle.process())
		;
	digitalWrite(led1, LOW);
}

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

	pinMode(led1, OUTPUT);
	pinMode(volt5, OUTPUT);

	digitalWrite(volt5, HIGH);

	// SOLAR SETTINGS //
	if (solar_opt)
	{
		PMIC pmic; //INITIALIZE THE PMIC CLASS TO CALL THE POWER MANAGEMENT FUNCTIONS BELOW

		pmic.setChargeCurrent(0, 0, 1, 0, 0, 0); //SET CHARGING CURRENT TO 1024MA (512 + 512 OFFSET)

		if (solar_opt == 5)
			pmic.setInputVoltageLimit(4840); //SET THE LOWEST INPUT VOLTAGE TO 4.84 VOLTS, FOR 5V SOLAR PANEL

		if (solar_opt == 6)
			pmic.setInputVoltageLimit(5080); //SET THE LOWEST INPUT VOLTAGE TO 5.08 VOLTS, FOR 6V SOLAR PANEL
	}

	Wire.begin(); // INITIALIZE I2C
	for (uint32_t ms = millis(); millis() - ms < 500;)
		;

	bno.begin(); // INITIALIZE IMU
	for (uint32_t ms = millis(); millis() - ms < 500;)
		;

	bno.setExtCrystalUse(true);
	ads.getAddr_ADS1115(ADS1115_DEFAULT_ADDRESS); // (ADDR = GND)
	ads.setGain(GAIN_TWOTHIRDS);	// 2/3x gain +/- 6.144V  1 bit = 0.1875mV
	ads.setMode(MODE_CONTIN);	// ads.setMode(MODE_CONTIN) = Continuous conversion mode - ads.setMode(MODE_SINGLE); Power-down single-shot mode (default)
	ads.setRate(RATE_128);		// 128SPS (default) 8,16,32,64,128,250,475,860
	ads.setOSMode(OSMODE_SINGLE);	// Set to start a single-conversion
	ads.begin();
	mcp.setResolution(MCP9808_SLOWEST);

	i2cWake();
}

ApplicationWatchdog wd(app_watchdog, System.reset);

void loop()
{
	FuelGauge fuel;

	if (a == 0 && fuel.getSoC() > 20)
	{
		if (enable_wop)
			Cellular.command("AT+URING=1\r\n");

		for (uint32_t ms = millis(); millis() - ms < 1000; Particle.process())
			Particle.publish("a", "9 - DEVICE ONLINE!", 60, PRIVATE, NO_ACK); // LET SERVER KNOW ONLINE!

		digitalWrite(led1, HIGH);

		for (uint32_t ms = millis(); millis() - ms < 1000; Particle.process())
			;

		digitalWrite(led1, LOW);
		a = 2;
	}

	if (fuel.getSoC() < 20) //LOW BATTERY DEEP SLEEP
	{
		if (a != 0)
			Particle.publish("a", "9 - DEVICE SHUTTING DOWN, BATTERY SoC < 20!", 60, PRIVATE, NO_ACK); // LET SERVER KNOW WE ARE GOING TO SLEEP

		for (uint32_t ms = millis(); millis() - ms < 5000; Particle.process()) //EXTRA TIME BEFORE DEEP SLEEP
			;

		System.sleep(SLEEP_MODE_DEEP, 7200); // SLEEP 2 HOURS IF SoC < 20
	}

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ START SENSOR VALUE GATHERING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

	// 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-4] = SENSOR 1, HISTORICAL SENSOR VALUES
	// sensor_value[1][5] = SENSOR 1, HISTORICAL SENSOR VALUE AVERAGE OF ALL FOUR

	adc0 = ads.Measure_SingleEnded(0); //ADS1115 VOLTAGE READING 1
	adcx0 = ((0.1875 * adc0) / 1000);
	sensor_value[0][0] = adcx0;

	adc1 = ads.Measure_SingleEnded(1); //ADS1115 VOLTAGE READING 2
	adcx1 = ((0.1875 * adc1) / 1000);
	sensor_value[1][0] = adcx1;

	adc2 = ads.Measure_SingleEnded(2); //ADS1115 VOLTAGE READING 3
	adcx2 = ((0.1875 * adc2) / 1000);
	sensor_value[2][0] = adcx2;

	adc3 = ads.Measure_SingleEnded(3); //ADS1115 VOLTAGE READING 4
	adcx3 = ((0.1875 * adc3) / 1000);
	sensor_value[3][0] = adcx3;

	int tempget = fabs(10 * mcp.getTemperature()); //MCP9808 TEMP READING
	sensor_value[4][0] = tempget;

	sensors_event_t event; //BNO055 SAMPLE GATHERING
	bno.getEvent(&event);

	float orx = event.orientation.x; // BNO055 ORIENTATION X
	// MAKE ORX RELATIVE TO 180'
	int orxt1 = int(orx) - 180;
	orxt1 = abs((orxt1 + 180) % 360 - 180);
	int orxt2 = 180 - int(orx);
	orxt2 = abs((orxt2 + 180) % 360 - 180);
	if (orxt1 > orxt2)
		sensor_value[5][0] = float(orxt2);
	else
		sensor_value[5][0] = float(orxt1);

	float ory = event.orientation.y; // BNO055 ORIENTATION Y
	ory = ory + 180;
	sensor_value[6][0] = ory;

	float orz = event.orientation.z; // BNO055 ORIENTATION Z
	orz = orz + 180;
	sensor_value[7][0] = orz;

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END SENSOR VALUE GATHERING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/GATHER, EVENT CHECK, AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

	//-- ALERT CHECK --
	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 > 4)
		{
			alrt_ckng_tmp = fabs(sensor_value[w][0] - sensor_value[w][5]);
			sensor_value[w][6] = alrt_ckng_tmp;

			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]);

				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
				}

				alert_cycl_mark = smpls_performed + alert_sample_qty;
			}
		}
	}
	//-- END OF ALERT CHECK

	//-- START 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("!");
			published_norm1_or_alert2 = 2;
		}
		else
		{
			Serial.println(".");
			published_norm1_or_alert2 = 1;
		}

		smpls_performed++;

		if (smpls_performed > alert_cycl_mark)
			current_smpl_intvl = norm_smpl_intvl;

		wd.checkin(); // SOFTWARE WATCHDOG CHECK-IN

		if (alert_cycl_mark == (smpls_performed + 1))
			alrt_state_chng = 3;
	}
	//-- END SAMPLING TIME CHECK --

	//-- BEGIN ALERT STATE PUBLISH FLUSH --
	if ((alrt_state_chng == 3) || (alrt_state_chng == 1))
	{
		preamble();
		for (xa = 0; xa < x; xa++)
		{
			for (y = 0; y < no_of_sensors; y++)
			{
				if (y < 4)
				{ //THIS IS TO LET THE FIRST FOUR READINGS HAVE TWO DECIMAL PLACES
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.2f", sensorlen[y], storage[xa][y]);
				}
				else
				{
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f", sensorlen[y], storage[xa][y]);
				}
				strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
			}
		} // BUILT FULLPUBLISH STRING
		x = 0;
		xa = 0;

		FuelGauge fuel; // GET BATTERY INFO
		fullpub_temp1[0] = 0;

		snprintf(fullpub_temp1, sizeof(fullpub_temp1), "E%d%d%dT%ld", current_smpl_intvl, ((int)(fuel.getVCell() * 100)), ((int)(fuel.getSoC() * 100)), (t2 - Time.now())); // 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));

		if (do_publish)
			fullpublishout();

		if (alrt_state_chng == 1)
		{
			current_smpl_intvl = alert_smpl_intvl;
			t2 = Time.now() + current_smpl_intvl;
		}

		if (alrt_state_chng == 3)
		{
			alrt_state_chng = 0;
			current_smpl_intvl = norm_smpl_intvl;
			t2 = Time.now() + current_smpl_intvl;
		}

		//-- SEND OUT SYSTEM NOTE TO ALERT OOS AND FOR ON THE SPOT SENSOR VALUE REPORTING
		if (alrt_state_chng == 1)
		{
			fullpub_temp1[0] = 0;
			fullpublish[0] = 0;

			snprintf(fullpub_temp1, sizeof(fullpub_temp1), "9 - OOS: ");
			strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1);

			for (y = 0; y < no_of_sensors; y++)
			{
				if (y < 4)
				{ //THIS IS TO LET THE FIRST FOUR READINGS HAVE TWO DECIMAL PLACES
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "  '%0*.2f'%s%.0f>%d", sensorlen[y], sensor_value[y][0], ((sensor_value[y][6] > sensor_alert_thrshld[y]) ? "▲" : ":"), sensor_value[y][6], sensor_alert_thrshld[y]);
				}
				else
				{
					snprintf(fullpub_temp1, sizeof(fullpub_temp1), "  '%0*.0f'%s%.0f>%d", sensorlen[y], sensor_value[y][0], ((sensor_value[y][6] > sensor_alert_thrshld[y]) ? "▲" : ":"), sensor_value[y][6], sensor_alert_thrshld[y]);
				}
				strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
			}

			FuelGauge fuel; // GET BATTERY INFO

			fullpub_temp1[0] = 0;

			Serial.println(fullpublish);
			Serial.printf("The size of published string is %d bytes. \n", strlen(fullpublish));

			if (do_publish)
				fullpublishout();

			alrt_state_chng = 2;
		}
		//-- END SEND OUT SYSTEM NOTE TO ALERT OOS
	}
	//-- END ALERT STATE PUBLISH FLUSH --

	else

	//-- START NORMAL CHECK AND PUBLISH --
	{
		if (x == rnds_to_publish)
		{
			preamble();
			for (x = 0; x < rnds_to_publish; x++)
			{
				for (y = 0; y < no_of_sensors; y++)
				{
					if (y < 4)
					{ //THIS IS TO LET THE FIRST FOUR READINGS HAVE TWO DECIMAL PLACES
						snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.2f", sensorlen[y], storage[x][y]);
					}
					else
					{
						snprintf(fullpub_temp1, sizeof(fullpub_temp1), "%0*.0f", sensorlen[y], storage[x][y]);
					}
					strncat(fullpublish, fullpub_temp1, sizeof(fullpublish) - strlen(fullpublish) - 1); // better to check the boundaries
				}
			} // BUILT FULLPUBLISH STRING
			x = 0;
			FuelGauge fuel; // GET BATTERY INFO
			fullpub_temp1[0] = 0;

			snprintf(fullpub_temp1, sizeof(fullpub_temp1), "E%d%d%dT%ld", current_smpl_intvl, ((int)(fuel.getVCell() * 100)), ((int)(fuel.getSoC() * 100)), (t2 - Time.now())); // 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));

			if (do_publish)
				fullpublishout();

			fullpublish[0] = 0; // CLEAR THE FULLPUBLISH STRING
		}
		//-- END SAMPLES TAKEN CHECK AND PUBLISH --
	}

	//-- BEDTIME CHECK --
	if (sleep == 1)
	{
		if (published_norm1_or_alert2 == 1 && norm_smpl_intvl > 23 && t2 >= (Time.now() + sleep_wait))
			sleeptime = (((norm_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 5);

		if (published_norm1_or_alert2 == 2 && alert_smpl_intvl > 23 && t2 >= (Time.now() + sleep_wait))
			sleeptime = (((alert_smpl_intvl - secs_less_intrvl_sleep) - sleep_wait) - 5);

		if (enable_wop == 1 && sleeptime)
		{
			i2cSleep();
			System.sleep({RI_UC, BTN}, {RISING, FALLING}, sleeptime, SLEEP_NETWORK_STANDBY);
			i2cWake();
			Cellular.command("AT+URING=0\r\n");
			Cellular.command("AT+URING=1\r\n");
		}

		if (enable_wop == 0 && sleeptime)
		{
			i2cSleep();
			System.sleep(BTN, FALLING, sleeptime, SLEEP_NETWORK_STANDBY);
			i2cWake();
		}

		sleeptime = 0;
		published_norm1_or_alert2 = 0;
	}
	//-- END BEDTIME CHECK --

	///\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ END GATHER, EVENT CHECK, AND PUBLISH \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
	for (uint32_t ms = millis(); millis() - ms < 700;)
		;
}