This setup allows for the Boron to go into ULP sleep mode and wake every 900 seconds (15 minutes) to read and publish data. It worked fine for about a week, but now it is waking up from the network at random intervals (15 s < t < 2 min). I could simply remove the .network(NETWORK_INTERFACE_CELLULAR) from the code, but I would like to be able to wake it up from my function call to change the sleep duration.
Here is the code that it has been running:
#include "Particle.h"
//Instances
SystemSleepConfiguration config; //Sleep Config instance to change sleep mode/wake options
// PINS
#define GROUND_MOISTURE_PIN A0 //Delcare analog pins for moisture readings
#define SURFACE_MOISTURE_PIN A1
#define HIGH_FREQ_BUTTON_PIN 5 //Declare digital pin for high frequency button pin
#define LOW_FREQ_BUTTON_PIN 6 //Declare digital pin for low frequency button pin
// VARIABLES
double ground_moisture, surface_moisture, ground_percent, surface_percent; //Declare variables to store moisture readings
int read_delay = 900; //Delay time variable (seconds) is passed to 'delayTimeSeconds()' or the 'config.mode(SLEEP_MODE).duration(ms)'
const unsigned long wait_time = 1800; //Time (seconds) until system goes to old reading state if user forgets to reset
unsigned long start_time, current_time; //Time variables to hold start time and current time after user initiates faster polling rate
const unsigned long high_freq_button_time = 300; //Set high_freq button time delay (seconds) (Faster)
const unsigned long low_freq_button_time = 900; //Set low_freq button time delay (seconds) (Slower)
int high_freq_button_state; //Variable to hold current button state
int last_high_freq_button_state = 0; //Variable to hold last button state (0 or 1)
int low_freq_button_state;
int last_low_freq_button_state = 0;
unsigned long last_high_freq_debounce_time = 0;
unsigned long last_low_freq_debounce_time = 0;
unsigned long debounce_delay = 50;
unsigned long time_pressed = 0; //Variable to store the time when the high_freq button is pressed
bool high_freq_button_is_pressed = false; //Boolean to check if the high_freq button is the last button pressed
int batteryLevel = 0; //Variable to store battery level
int32_t temp; //Variables to get and hold the die temperature
uint32_t res;
float tempC;
bool dataSent = false; //Boolean to check if data has been sent
// FUNCTIONS
int changeDelaySeconds(String seconds); //Function to change the read_delay
void delayTimeSeconds(int time_seconds); //Function to delay the read time and break if there is any change (NOT TO USE WITH ULP mode)
const char* getBatteryState(); //Function to get battery state (see documentation)
const char* getWakeupReason(); //Function to get wakeup readon from sleep (see documentation)
void checkFreqButtons(); //Function to check if the high_freq and low_freq buttons have been pressed and perform respective operations
void publishData(); //Function to publish all data to the particle cloud
void checkLastPressTime(); //Function to check how long the high_freq button has been pressed and to turn it off
void getDieTemp(); //Function to get the temperature of the die (nRF52840) on the Boron in degrees Celsius
void getMoisture(); //Function to read/calculate the ground and surface moistures
void firmwareUpdateHandler(system_event_t event, unsigned int param); //Function to handle if the firmware is being updated
// These are the states in the finite state machine, handled in loop()
enum State {
STATE_WAIT_CONNECTED = 0,
STATE_PUBLISH,
STATE_PRE_SLEEP,
STATE_SLEEP,
STATE_FIRMWARE_UPDATE
};
State state = STATE_WAIT_CONNECTED;
unsigned long stateTime;
bool firmwareUpdateInProgress = false;
const std::chrono::milliseconds publishMaxTime = 3min; //Time to wait for publish to the particle cloud
const std::chrono::milliseconds firmwareUpdateMaxTime = 5min; //Time to wait for firmware update
const std::chrono::milliseconds connectMaxTime = 6min; //Wait time for connection
const std::chrono::milliseconds cloudMinTime = 10s; //Time to wait for any particle cloud activity, this adds a delay of time specified
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);
void setup() {
System.on(firmware_update, firmwareUpdateHandler);
Cellular.on();
Particle.connect();
pinMode(GROUND_MOISTURE_PIN, INPUT); //Analog input pin for moisture readings
pinMode(SURFACE_MOISTURE_PIN, INPUT);
pinMode(HIGH_FREQ_BUTTON_PIN, INPUT); //High_Freq Button
pinMode(LOW_FREQ_BUTTON_PIN, INPUT); //Low_Freq Button
//Particle functions
Particle.function("Delay (s)", changeDelaySeconds); //Register delay function to the cloud to call using the cloud
//Particle variables
Particle.variable("Data Sent", dataSent);
Particle.variable("HF BTN", high_freq_button_is_pressed);
attachInterrupt(HIGH_FREQ_BUTTON_PIN, checkFreqButtons, CHANGE); //Interrupt for high_freq button when GPIO pin changes states
attachInterrupt(LOW_FREQ_BUTTON_PIN, checkFreqButtons, CHANGE); //Interrupt for low_freq button when GPIO pin changes states
Time.now(); //Initialize RTC time
stateTime = millis(); //Initialize stateTime start
}
void loop(){
checkLastPressTime();
switch(state){
case STATE_WAIT_CONNECTED: //Wait for connection
if(Particle.connected()){
state = STATE_PUBLISH; //Once connected chnage state to PUBLISH
stateTime = millis();
}
else if(millis() - stateTime >= connectMaxTime.count()){ //If it takes too long to connect, go to sleep
state = STATE_SLEEP;
}
break;
case STATE_PUBLISH:
getDieTemp();
getMoisture();
publishData();
if(millis() - stateTime < cloudMinTime.count()){ //Give it time to publish the data
state = STATE_PRE_SLEEP;
}
else{
state = STATE_SLEEP;
}
break;
case STATE_PRE_SLEEP:
if(millis() - stateTime >= cloudMinTime.count()){ //After buffer time and data published, change to sleep state
state = STATE_SLEEP;
}
break;
case STATE_SLEEP:
if(firmwareUpdateInProgress){ //Wait for any firware to be updated
state = STATE_FIRMWARE_UPDATE;
stateTime = millis();
break;
}
else{ //If no firware update in progress, configure sleep mode and go to sleep
config.mode(SystemSleepMode::ULTRA_LOW_POWER) //Set sleep mode to ULP to save power
.duration(read_delay * 1000) //Wake after t time in milliseconds (23 minutes is the maximum before losing connection)
.network(NETWORK_INTERFACE_CELLULAR) //Wake when we talk to the device through the particle cloud
.gpio(HIGH_FREQ_BUTTON_PIN, CHANGE) //Wake when one of the buttons is pressed
.gpio(LOW_FREQ_BUTTON_PIN, CHANGE)
Particle.publish("WAKEUP", getWakeupReason(), PRIVATE); //This is here for debugging, move 'System.sleep(config)' here found in 'getWakeUpReason()' if a wakeup reason isn't needed
state = STATE_WAIT_CONNECTED;
}
break;
case STATE_FIRMWARE_UPDATE:
if(!firmwareUpdateInProgress){ //If no firmware update change to sleep state
state = STATE_SLEEP;
}
else if (millis() - stateTime >= firmwareUpdateMaxTime.count()) { //If firmware update takes too long, go to sleep
state = STATE_SLEEP;
}
break;
}
}
// FUNCTION DEFINITIONS
int changeDelaySeconds(String seconds){
read_delay = seconds.toInt(); //Change delay based on cloud function input
return seconds.toInt(); //Particle function must return an integer to view from the cloud
}
void delayTimeSeconds(int time_seconds){
for(int i = 0; i < time_seconds; i++){
if(time_seconds != read_delay) //If delay ever changes due to button press or function call
break; //This way we can change the delay immediately instead of waiting for the delay() function
else
delay(1000); //Delay for one second
}
}
const char* getBatteryState() {
switch (System.batteryState()) {
case 0:
return "Unknown";
case 1:
return "Not Charging";
case 2:
return "Charging";
case 3:
return "Charged";
case 4:
return "Discharging";
case 5:
return "Fault";
case 6:
return "Disconnected";
default:
return "Error";
}
}
const char* getWakeupReason(){
SystemSleepResult result = System.sleep(config); //Go to sleep using the passed configuration
switch (result.wakeupReason()) {
case SystemSleepWakeupReason::UNKNOWN:
return "UNKNOWN";
case SystemSleepWakeupReason::BY_GPIO:
return "BUTTON";
case SystemSleepWakeupReason::BY_RTC:
return "RTC";
case SystemSleepWakeupReason::BY_NETWORK:
return "NETWORK";
default:
return "Error/DID NOT WAKE";
}
}
void checkFreqButtons(){ // Atached to the GPIO interrupts for immediate button recognition
//Debounce for high_freq button
if((Time.now() - last_high_freq_debounce_time/1000) > debounce_delay){
int high_freq_reading = digitalRead(HIGH_FREQ_BUTTON_PIN);
if(high_freq_reading != last_high_freq_button_state){
last_high_freq_debounce_time = Time.now();
last_high_freq_button_state = high_freq_reading;
if(high_freq_button_state == 0){
read_delay = high_freq_button_time; //Set to a faster polling rate
time_pressed = Time.now(); //Get time pressed to begin timing
high_freq_button_is_pressed = true; //High_freq button has been pressed
}
}
}
//Debounce for low_freq button
if((Time.now() - last_low_freq_debounce_time/1000) > debounce_delay){
int low_freq_reading = digitalRead(LOW_FREQ_BUTTON_PIN);
if(low_freq_reading != last_low_freq_button_state){
last_low_freq_debounce_time = Time.now();
last_low_freq_button_state = low_freq_reading;
if(low_freq_button_state == 0){
read_delay = low_freq_button_time; //Set to a slower polling rate
high_freq_button_is_pressed = true; //"Unpress" high_freq button
}
}
}
}
void publishData(){
Particle.publish("Europa", String(int(ground_percent)) + "," + String(int(surface_percent)) + "," + String(int(System.batteryCharge())), PRIVATE); //Publish data to the particle cloud
Particle.publish("Battery State", getBatteryState(), PRIVATE); //Publish battery state to the particle cloud
Particle.publish("Die Temp", String(tempC), PRIVATE); //Publish the temperature of the die to the particle cloud
dataSent = true;
}
void checkLastPressTime(){
//Check if it has been long enough to automatically go back to a slower polling rate
if( (Time.now() - time_pressed) >= wait_time & high_freq_button_is_pressed ){ // This will reset the frequency if the user forgets after pressing the high_freq button
read_delay = low_freq_button_time; //Go back to slow polling rate
high_freq_button_is_pressed = false; //Reset the high_freq button to not pressed
}
}
void getDieTemp(){
//Get die temperature in 0.25 degrees Celsius.
res = sd_temp_get(&temp);
if (res == NRF_SUCCESS) {
tempC = (float)temp / 4;
}
}
void getMoisture(){
//Get ground moisture
ground_moisture = analogRead(GROUND_MOISTURE_PIN); //Get raw moisture readings
surface_moisture = analogRead(SURFACE_MOISTURE_PIN);
ground_percent = (1.0-(ground_moisture/4095.0)) * 100; //Calculate readable moisture readings
surface_percent = (1.0-(surface_moisture/4095.0)) * 100;
}
void firmwareUpdateHandler(system_event_t event, unsigned int param) {
switch(param) {
case firmware_update_begin:
firmwareUpdateInProgress = true;
break;
case firmware_update_complete:
break;
case firmware_update_failed:
firmwareUpdateInProgress = false;
break;
}
}