Waking up from RTC

I have a Boron that is on 4.0.0

I am trying to wake on RTC or 4 DI’s on Falling
I have not been able to make sense of the examples for sleep along with the RTC.

Does anyone have any examples that may help or tips on how to get this going ?

The goal is to have it wake at 0400 and 1200, publish the states of the sensors, stay awake for 15-20 minutes and go back to ULP mode, no cellular connection. Which will also makes digital output 2 and 3 go low, turning off the pressure transmitter and the LCD. Displaying my information to the LCD is working well except for the bug that scrambles or blanks the LCD after a indeterminate amount of time.

Nothing with this system needs to operate quickly, I am sending the water level and door status, along with bottle pressure and temperature 2x a day

Thank you !

/* SHOWER TRAILER MONITOR 
* Monitor door position, water level low and LOWLOW switches 
* Monitor temperature via I2C sendor and gas bottle pressure via analog input
* Send information to cloud 2 times a day at 0400, 1200
* Board to sleep mode between transmissions turning off LCD and cellular. Will wake on door switch or wake button 
* When board has been woken up it is to stay awake for 20 minutes, transmit status when woken up and prior to returning to sleep. 
* THINGS TO DO: Sleep mode, debounce DI's, 
* Sleep mode, will go to sleep after 20 minutes, can be woken up by wake button, low & low low water, and door switch.  
* Sleep mode ULP with only pins active will be the pins that can wake.  Cellular off.  
*/ 

#include "AnalogSmoother.h"
#include "Arduino.h"
#include "Debounce.h"
#include <Wire.h>
#include "LiquidCrystal_I2C_Spark.h"
#include <SparkFun_Qwiic_Humidity_AHT20.h>

AHT20 humiditySensor;

// WAKE BUTTON
const int wake_sw = 8;
int last_wake_sw;
bool wake_new_value = false;

// DOOR SWITCHES 
const int door_sw = 6;
int last_door_pos;
bool door_new_value = false;

// WATER LEVEL SWITCHES
const int water_low_sw = 4;    
const int water_LOWLOW_sw = 5;
int last_water_level;
bool water_low_new_value = false;
bool water_LOWLOW_new_value = false;

// LCD 
int lcd_power = D3;
LiquidCrystal_I2C *lcd;

// ON BOARD LED
int led = D7;

// BATTERY MONITORING
AnalogSmoother battery(A1, 10);
int batteryPin = A1;
int batteryValue = 0;
int batteryScaled = 0;

// GAS PRESSURE 
AnalogSmoother gas_pressure(A0, 10);
int gas_pressure_power = D2;
int gas_pressurePin = A0;
int gas_pressureValue = 0;
int gas_pressureScaled = 0;

// TEMPERATURE 

// CLOUD CONNECTION
int last_cloud_connected;
SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);

// SLEEP CONFIGURATION
SystemSleepConfiguration& gpio(pin_t pin, InterruptMode mode)
SystemSleepConfiguration config;
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
      .gpio(D4, FALLING);
      .gpio(D5, FALLING);
      .gpio(D6, FALLING);
      .gpio(D8, FALLING);

// TIME 
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)
unsigned long lastSync = millis();

void setup()
{
  // PINS AND PIN MODES 
  pinMode(door_sw, INPUT_PULLDOWN);
  pinMode(water_low_sw, INPUT_PULLDOWN);
  pinMode(water_LOWLOW_sw, INPUT_PULLDOWN);
  pinMode(wake_sw, INPUT_PULLDOWN);
  pinMode(led, OUTPUT);
  pinMode(lcd_power, OUTPUT);
  pinMode(gas_pressure_power, OUTPUT);
  digitalWrite(lcd_power, HIGH);
  digitalWrite(gas_pressure_power, HIGH);
 
  Serial.begin(115200);
  gas_pressure.fill();
  battery.fill();
  lcd = new LiquidCrystal_I2C(0x3f, 20, 4);
  lcd->init();
  lcd->backlight();
  lcd->clear();
  lcd->setCursor(0,0);
  lcd->print("SHOWER TRAILER 0000");
  lcd->setCursor(0,1);
  lcd->print("STARTING UP");
  delay(2000);

  Wire.begin();
    //Check if the AHT20 will acknowledge
  if (humiditySensor.begin() == false)
  {
    Serial.println("AHT20 not detected. Please check wiring. Freezing.");
    while (1);
  }
  Serial.println("AHT20 acknowledged.");
// Turning on cellular and connecting to the cloud
  if(Cellular.isOff() == true)
  {
    Cellular.on();
    waitFor(Cellular.isOn, 30000);
  }
  Cellular.connect();
  
  if(Particle.connected() == false)
  {
    Particle.connect();
    waitFor(Particle.connected, 30000);
    lcd->setCursor(0,3);
    lcd->print("CLOUD STATUS");
    lcd->print(Particle.connected());
    delay(10000);
  }
// CLOCK 
    if (millis() - lastSync > ONE_DAY_MILLIS) 
    {
    // Request time synchronization from the Particle Device Cloud
    Particle.syncTime();
    lastSync = millis();
    }
  // Send field status to cloud upon startup
  if(Particle.connected() == true)
  {
    Particle.publish("Ftemp","Ftemp()",60,PRIVATE);
    Particle.publish("batteryScaled","batteryScaled()",60,PRIVATE);
    Particle.publish("Water_Level","Water_Level()",60,PRIVATE);
    delay(30000);
    Particle.publish("door_pos","door_pos()",60,PRIVATE);
    Particle.publish("gas_pressureScaled","gas_pressureScaled()",60,PRIVATE);
    delay(30000);
    // LCD print successful transmission of data  {{{{{{{NEED TO COMPLETE}}}}}
    lcd->setCursor(0,4);
    lcd->print("SENSORS PUBLISHED");
    delay(30000); 
  }
  lcd->clear();
}


void loop() 
{      
  if (humiditySensor.available() == true)
{  
  Serial.println("SHOWER TRAILER 0000");
  lcd->setCursor(0,0);
  lcd->print("SHOWER TRAILER 0000");
  // Temperature and Battery 
  float temperature = humiditySensor.getTemperature();
  float Ftemp = round(((temperature * 9/5)+32));
  Serial.print("Temp: ");
  Serial.print(Ftemp);
  Serial.print(" F\t");
  batteryValue = battery.read();
  float batteryScaled = map(batteryValue,1163,3721,5,16);
  Serial.print("BATT: ");
  Serial.print(batteryScaled);
  Serial.print(" V " );
  Serial.print("Battery AI:");
  Serial.print(batteryValue);
  Serial.println();
  lcd->setCursor(0,1);
  lcd->print("TEMP:");
  lcd->print(Ftemp, 0);
  lcd->print("F");
  lcd->print(" Batt:");
  lcd->print(batteryScaled);
  lcd->print("V");

 // WATER LEVEL SWITCHES
  int Water_Level = digitalRead(water_low_sw) + digitalRead(water_LOWLOW_sw);
  lcd->setCursor(0,2);
  {
    switch (Water_Level)
    {
    case 0:
      Serial.print("WTR:MIN! ");
      lcd->print("WTR:MIN!");
      break;
    case 1:
      Serial.print("WTR:LOW ");
      lcd->print("WTR:LOW ");
      break;
    case 2:
      Serial.print("WTR:GOOD ");
      lcd->print("WTR:GOOD");
      break;
    default:
      Serial.println("WTR:ERR! ");
      lcd->print("WTR:ERR!");
      break;
    }
    last_water_level = Water_Level;
  }

 // DOOR POSITION SWITCHES 
  int door_pos = digitalRead(door_sw);
  lcd->setCursor(10,2);
  {
    switch (door_pos)
    {
    case 0:
      Serial.print("DOOR:OPEN");
      lcd->print("DOOR:OPEN");
      break;
    case 1:
      Serial.print("DOOR:CLSD");
      lcd->print("DOOR:CLSD");
      break;
    }
    last_door_pos = door_pos;
    Serial.println();
  }

 // WAKE BUTTON 
    int wake_sw_lvl = digitalRead(wake_sw);
  {
    switch (wake_sw_lvl)
    {
    case 0:
      Serial.print("WAKE SWITCH:");
      Serial.print("NOT");
      break;
    case 1:
      Serial.print("WAKE SWITCH:");
      Serial.print("AWAKEN");
      break;
    }
    last_wake_sw = wake_sw_lvl;
  }

  // GAS PRESSURE TROUBLE and PRESSURE VALUES
    if(gas_pressureValue < 500)
  {
  Serial.println();
  Serial.print("GAS:");
  Serial.print("TRBL ");
  Serial.print("Gas P AI:");
  gas_pressureValue = gas_pressure.read();
  float gas_pressureScaled = map(gas_pressureValue,600,3000,1,3000);
  Serial.print(gas_pressureValue);
  lcd->setCursor(0,3);
  lcd->print("GAS:");
  lcd->print("TRBL ");
  }
    else
  {
  Serial.println();
  Serial.print("GAS:");
  Serial.print(gas_pressureScaled);
  Serial.print(" PSIG");
  Serial.print("Gas P AI:");
  Serial.print(gas_pressureValue);
  lcd->setCursor(0,3);
  lcd->print("GAS:");
  lcd->print(gas_pressureScaled);
  lcd->print("PSI");
  }

  Serial.println();
  // CLOUD CONNECTED 
    int cloud_connected = Particle.connected();
    lcd->setCursor(12,3);
  {
    switch (cloud_connected)
    {
    case 0:
      Serial.print("CLOUD NOT CONNECTED");
      lcd->print("CLD:NOT");
      break;
    case 1:
      Serial.print("CLOUD CONNECTED");
      lcd->print("CLD:CON");
      break;
    }
    last_cloud_connected = cloud_connected;
  }

// just a idiot light to see if it is running.
  digitalWrite(led, HIGH);
  delay(5000);
  digitalWrite(led, LOW);
  delay(1000);

  Serial.println();
    delay(5000);
}
 delay(2000);
}

I have not used deep sleep on a Boron.
I have used deep sleep on an ESP8266
but I have looked at a video about the photon, maybe some clues are there;
Particle Photon Sleep Modes Demystified - YouTube

@txrocks ,

Your use case is perfectly well suited to the Boron so you are on the right track.

At a glance, I can tell you that you need to allow for much more time than 30 seconds for cellular to connect. Ideally, you would give the cellular modem up to 10 minutes to connect. Your connect time will vary based on your signal strength / quality.

Also, it looks like you are using “FALLING” on the GPIO pins but you have set them with a INPUT_PULLDOWN pin mode, this may be exactly correct but when I see a pin name like water_low_sw, it makes me want to ask what way that switch asserts itself - not a bad thing to add in a comment.

I would suggest you use a consistent naming / numbering for your GPIO pins - either PIN number or pin name for both the definition of the name and for your sleep config. Here is what I use:

const pin_t WAKEUP_PIN        = D8;

Hope this helps

Chip

2 Likes

Thank you for the feedback ! I am working on improving my coding skills which are about Kindergarten level at the moment. I am glad I do not do this for a living as I would be starving.

I have extended the time and changed up a couple things. What I would like to accomplish there is allow for the longer time, print on the LCD that it is attempting to connect. If it can not connect in the 10 minutes then print on the LCD that it can not connect. Many of these are going to be scattered over west Texas and New Mexico and there will be some connectivity issues. I need to come up with an external antenna also.

I will add comments in there to explain better. The inputs are pulled down, as when all the switches are in "good" values they are closed circuits and pulling the pins up. So if I have an open in the circuit like a switch harness unplugged they will indicate low water level. I may have been thinking about this wrong as the wake function would be on the pin falling.

This was the gpio() system sleep configuration example

// PROTOTYPE
SystemSleepConfiguration& gpio(pin_t pin, InterruptMode mode) 

// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::HIBERNATE)
      .gpio(WKP, RISING);

// EXAMPLE
SystemSleepConfiguration config;
config.mode(SystemSleepMode::STOP)
      .gpio(D2, RISING);
      .gpio(D3, FALLING);

So in the above code system sleep mode is "stop", and it will wake when D2 rises ? and/or D3 falling ?

// SLEEP CONFIGURATION
SystemSleepConfiguration& duration(system_tick_t ms)
SystemSleepConfiguration& duration(std::chrono::milliseconds ms)
SystemSleepConfiguration config;
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
      .gpio = (water_low_sw, FALLING);       // wake up if pin goes low
      .gpio = (water_LOWLOW_sw, FALLING);    // wake up if pin goes low
      .gpio = (door_sw, FALLING);            // wake up if pin goes low
      .gpio = (wake_sw, FALLING);            // wake up if pin goes low
      .gpio = (lcd_power, CHANGE);           // pin to go low during sleep is this handled as part of the sleep function ?
      .gpio = (gas_pressure_power, CHANGE);  // pin to go low during sleep is this handled as part of the sleep function ?
      .duration(8h);                         // sleep for up to 8 hours
      .network(NETWORK_INTERFACE_CELLULAR)  // I think I should handle this with Cellular.off(); command right before the two output pins above are turned off.  
// RTC wake up at 0400 and 1200 CENTRAL standard time

Now I need to figure out how to have it run for 30 minutes then go back to sleep.

Will the actual sleep command be in the setup or in the loop ? It seems
Setup will have the sleep mode, duration, and the wake pins
I do want it to wake on RTC at set times so I can check on things and have an expected time when they are to be awake.

I have been running through the docs trying to make sense of it. The video that was noted seems a bit dated. There is some good information in there, but its not really matching what is in the docs now. I will keep chugging away trying to get it going.

@txrocks ,

So in the above code system sleep mode is “stop”, and it will wake when D2 rises ? and/or D3 falling ?

Yes, you are correct.

It seems like you are making good progress. One more suggestion, when you have multiple potential sources to wake the device is to test the sleep result. For example:

			config.mode(SystemSleepMode::ULTRA_LOW_POWER)
				.gpio(BUTTON_PIN,CHANGE)
				.gpio(INT_PIN,RISING)
				.duration(wakeInSeconds * 1000L);
			ab1805.stopWDT();  												// No watchdogs interrupting our slumber
			SystemSleepResult result = System.sleep(config);              	// Put the device to sleep device continues operations from here
			ab1805.resumeWDT();                                             // Wakey Wakey - WDT can resume
			if (result.wakeupPin() == BUTTON_PIN) {                         // If the user woke the device we need to get up - device was sleeping so we need to reset opening hours
				Log.info("Woke with user button - LoRA State");
				state = LoRA_TRANSMISSION_STATE;
			}
			else if (result.wakeupPin() == INT_PIN) {
				Log.info("Woke with sensor interrupt");						// Will count at the bottom of the main loop
				if (secondsUntilNextEvent() <= 2 || secondsUntilNextEvent() >= 598) state = IDLE_STATE;		// If more or less than 2 seconds we may miss reporting
				else state = SLEEPING_STATE;								// This is the normal behavioud
			}
			else {
				Log.info("Time is up at %s with %li free memory", Time.format((Time.now()+wakeInSeconds), "%T").c_str(), System.freeMemory());
				state = IDLE_STATE;
			}

Let us know if you have more questions.

Thanks, Chip

I have gotten somewhat closer…

// SLEEP CONFIGURATION
// 
SystemSleepConfiguration config;
config.mode(SystemSleepMode::ULTRA_LOW_POWER).duration(8h);
      .gpio(water_low_sw, FALLING)    // wake up if water_low_sw opens on low water
      .gpio(water_LOWLOW_sw, FALLING)    // wake up if water_LOWLOW_sw opens on LOW LOW water level
      .gpio(door_sw, FALLING)    // wake up if door_sw opens on open door condition 
      .gpio(wake_sw, FALLING)    // wake up if wake_sw opens when depressed

This section of code is somewhat happy now.

This line which is the first ".gpio…

      .gpio(water_low_sw, FALLING)    // wake up if water_low_sw opens on low water

I get this problem for the first gpio line

expected primary-expression before ‘.’ token

I have not been able to quite figure that out.

Also for the timer to put it into sleep mode, I was originally thinking a software timer and a class member callback to send it into the sleep function which I need it to disconnect from particle, turn off the cellular, turn off the two pins that are driving other devices, then go to sleep. The docs state using the callback from the software timer is not really a good idea. Would the proper way to do this be a ISR ? I am going to get working on the log events and I eventually need to add the watchdog in there also.

Thank you again !

Move the semicolon from after duration(8h)

.gpio(wake_sw, FALLING) // wake up if wake_sw opens when depressed

to to after the last of the modifiers.

Thanks,

Chip

2 Likes

The little things. Thank you chipmc!

1 Like

another question.

Sleep mode is working. Not exactly how I intended it to work.

I originally had the

    config.mode(SystemSleepMode::ULTRA_LOW_POWER).duration(1h)
      .gpio(water_low_sw, FALLING)    // wake up if water_low_sw opens on low water
      .gpio(water_LOWLOW_sw, FALLING)    // wake up if water_LOWLOW_sw opens on LOW LOW water level
      .gpio(door_sw, FALLING)    // wake up if door_sw opens on open door condition 
      .gpio(wake_sw, FALLING);    // wake up if wake_sw opens when depressed

in void setup, when it is there it will go to sleep soon as it works through the actions above are completed.

I moved that section down into my loop, and it will work through the actions in the loop then go to sleep. I have not been able to get the count down timer to work yet. It will not wake back up on its own either. I thought the .duration(1h) would wake it back up after the one hour. Or did I miss there needing to be another timer that needs to be setup to handle that timing ? I originally thought the Boron had a built in RTC (real time clock) from research it looks like it has a RTC (real time counter). Which this application is really not time critical so I can deal with that later as an enhancement.

Do you have any tips on what needs to be done for it to automatically wake up when .duration(x time) has expired, or am I misunderstanding the .duration ?

Thank you again !

@txrocks, Your project is interesting to me, since you are measuring a gas pressure, water level, and a door switch.
Are you by chance working with a Chlorinator for potable water?

If so, I’d love to chat with you about your project. I have many clients that could use this, but I don’t have the time myself. But I’d be glad to help where I can. Feel free to DM me if you cant publicly share the details yet.

1 Like

No potable water system here. This is remote monitoring for safety trailers that are out in the field with pneumatically powered water pumps in them.

1 Like

I am getting it to go to sleep with no challenges now, I have the timer to go to sleep working well. The challenge is it never wakes up.
The below code is at the end of my loop, the timer kicks “going to sleep” to true

Is there another time action that is tied in with the .duration(60min) that I could be missing ? I did look through the log and nothing jumped out at me, would there possibly be a message in there that it was missing the timing function to wake back up ? Eventually I am going to figure out how to make it wake at a certain time, I would be happy for it to wake back up at all for now.

Thanks !!

    // SLEEP TIMER ACTIONS 
switch (going_to_sleep == true)  {
case 0:
  digitalWrite(idiot_LED, HIGH);
  lcd->clear();                           // clear LCD
  lcd->print("GOING TO SLEEP");           // write on LCD going to sleep
  Log.info("GOING TO SLEEP");             // log entry for sleep
  ST0000sleepfeed.send(1);
  delay(30000);
  Particle.disconnect();                  // disconnect from cloud
  delay(30000);
  Cellular.off();                         // turn cellular off 
  delay(450000);
  digitalWrite(gas_pressure_power, LOW);  // turn off gas pressure transmitter
  digitalWrite(lcd_power, LOW);           // turn off lcd
  digitalWrite(idiot_LED, LOW);
  config.mode(SystemSleepMode::ULTRA_LOW_POWER).duration(60min)   // sleep for 1 hour (will eventually be set for 0400 and 1200 -6 ZULU)
    .gpio(water_low_sw, FALLING)       // wake up if water_low_sw opens on low water
    .gpio(water_LOWLOW_sw, FALLING)    // wake up if water_LOWLOW_sw opens on LOW LOW water level
    .gpio(door_sw, FALLING)            // wake up if door_sw opens on open door condition 
    .gpio(wake_sw, FALLING);            // wake up if wake_sw opens when depressed
       
  System.sleep(config);                   // going to sleep  
  break;
case 1:
  break;
}

one more question for everyone.

in the .duration in sleep if there is not a time value name (min, h) in the .duration () what does it default to ?

I have the below function that I am trying to get to work to set the duration based on what time it is so I can wake at certain times. Or would a formatted string be a better way to do it ?

Thanks!

Log.info("SLEEP STATUS:=%d", go_to_sleep);
    // SLEEP TIMER ACTIONS 
switch (go_to_sleep == 1)  {
case 0:
  break;
case 1:
digitalWrite(idiot_LED, HIGH);
  lcd->clear();                           // clear LCD
  lcd->print("GOING TO SLEEP");           // write on LCD going to sleep
  Log.info("GOING TO SLEEP");             // log entry for sleep
  ST0000sleepfeed.send(1);
  delay(30000);
  Particle.disconnect();                  // disconnect from cloud
  delay(6000);
  Cellular.off();                         // turn cellular off 
  delay(6000);
  digitalWrite(transmitter_lcd_power, LOW);  // turn off gas pressure transmitter
  digitalWrite(lcd_power, LOW);           // turn off lcd
  digitalWrite(idiot_LED, LOW);
  sleep_timer.reset();
  go_to_sleep = 0;
    // setting sleep duration to wake at 0400, 1200, 1800
  if ((Time.hour() > 18) & (Time.hour() < 4)) {
    sleep_duration_minutes = (600 - ((Time.hour()*60) + Time.minute()));   
}
else {
        
}
if ((Time.hour() > 4) & (Time.hour() < 12)) {
    sleep_duration_minutes = (480 - ((Time.hour()*60) + Time.minute()));
}
else {

}
if ((Time.hour() > 12) & (Time.hour() < 18)) {
    sleep_duration_minutes = (360 - ((Time.hour()*60) + Time.minute()));
}
else {
    
}
  config.mode(SystemSleepMode::ULTRA_LOW_POWER).duration(sleep_duration_minutes)   // wakeup at 0400, 1200, 1800 CST
    .gpio(water_low_sw, FALLING)       // wake up if water_low_sw opens on low water
    .gpio(water_LOWLOW_sw, FALLING)    // wake up if water_LOWLOW_sw opens on LOW LOW water level
    .gpio(door_sw, FALLING)            // wake up if door_sw opens on open door condition 
    .gpio(wake_sw, FALLING);            // wake up if wake_sw opens when depressed   
  System.sleep(config);                   // going to sleep   
  break;
}

milliseconds

Hi @Rftop,
I just came across your message by chance, perhaps my own experience would be interesting to you, I have developed a remote monitoring solution for Waste Water Treatment Plants based on an Electron, I’m happy to have a chat if this sounds interesting.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.