Boron breathing dark blue when waking up from ULP

When my Boron wakes up after being in ULP mode the LED will breath dark blue and it pretty much does not do anything. It is hung up in this weird not really awake mode.

I have a IOT publish that goes out right before it goes to sleep. It will eventually wake up just enough to sent that and it is back to sleep. The rest of the program is not running. It does not even bring the digital inputs/outputs back up to where they will function. I have a DO that controls a transistor to control the LCD, pressure transmitter, and temperature sensor, it is not pulling that back up enough to drive the transistor on all the way. Watching my Log.info my digital inputs are functional. It is almost like it forgets void setup when it starts back up and the output to fire up the other devices does not come back. Except for the cellular will turn on, cloud connect, start the sleep timer, publish that it is going to sleep, and back out it goes.

I have the sleep time set at 3600000 which is winding up being about 3 hours.

Now if I power cycle or push the reset button it will fire up and work just like expected the first time.

I did find a document about the device blinking dark blue when the setup flag is not done. I tried it anyway and that did not help. This LED is “breathing” and not “blinking”

Log.info is sending info out. This ones comes out every time my block of info logs and I am not sure what it is or if it is helpful in figuring this out.

Below is the last string of Log.info as it went to sleep. This time around it never connected back to the cloud. Only had dark blue breathing the entire time. I wish I could figure out how to have the serial monitor fire back up as soon as it wakes up. When it woke up this time it woke from the digital input wakebutton going low. It did not wake up on its own from time. I still have not figured out how to get it to wake at specific times.

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>
#include "Adafruit_IO_Client.h"
#include "Adafruit_IO_Particle.h"
#include "Particle.h"


AHT20 humiditySensor;

// WAKE BUTTON
int wake_sw = 8;
int last_wake_sw;
bool wake_new_value = true;
int wake_sw_value = digitalRead(wake_sw);
int go_to_sleep = 0;
// DOOR SWITCHES 
int door_sw = 6;
// WATER LEVEL SWITCHES
int water_low_sw = 4;    
int water_LOWLOW_sw = 5;
int last_water_level = 0;
// LCD 
int lcd_power = D3;
LiquidCrystal_I2C *lcd;
// ON BOARD LED
int idiot_LED = D7;
// BATTERY MONITORING
AnalogSmoother battery(A1, 10);
int batteryPin = A1;
int batteryValue = 0;
int batteryScaled = 0;
int last_batteryScaled = 0;
// GAS PRESSURE 
AnalogSmoother gas_pressure(A0, 10);
int transmitter_lcd_power = D2;
int gas_pressurePin = A0;
int gas_pressureValue = 0;
float gas_pressureScaled = 0;
int last_gas_pressureScaled = 0;
// TEMPERATURE 
float last_Ftemp = 0;
// CLOUD CONNECTION
int last_cloud_connected;
SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
// ADAFRUIT IO CONNECTIONS         ###### need to move username and key into seperate library ################
#define AIO_USERNAME 
#define AIO_KEY      
TCPClient client;                                        // TCP Client used by Adafruit IO library
 
// Create the AIO client object
Adafruit_IO_Client  AIOClient = Adafruit_IO_Client(client, AIO_KEY);
 
// Create a feed object to send and get data
Adafruit_IO_Feed    ST0000temperaturefeed = AIOClient.getFeed("ST-0000-temperature-feed");
Adafruit_IO_Feed    ST0000batteryfeed = AIOClient.getFeed("ST-0000-batteryfeed");
Adafruit_IO_Feed    ST0000waterlevelLOWfeed = AIOClient.getFeed("ST-0000-water-level-LOW-feed");
Adafruit_IO_Feed    ST0000waterlevelLOWLOWfeed = AIOClient.getFeed("ST-0000-water-level-LOWLOW-feed");
Adafruit_IO_Feed    ST0000gasPfeed = AIOClient.getFeed("ST-0000-gasPfeed");
Adafruit_IO_Feed    ST0000doorfeed = AIOClient.getFeed("ST-0000-doorfeed");
Adafruit_IO_Feed    ST0000sleepfeed = AIOClient.getFeed("ST-0000-sleepfeed");

// TIMER FOR PUBLISHING
int publish_data = 1;
unsigned int publish_timer_period = (1000 * 60 * 10);  // 10 minute interval
Timer publish_timer(publish_timer_period, time_to_publish);

void time_to_publish() {
  Log.info("time to publish=%d", publish_data++);
}

// System Sleep 
SystemSleepConfiguration config;
int sleep_duration = 0; 

// LOG handler 
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
// TIME  
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)
unsigned long lastSync = millis();
int lastSecond = 0;

unsigned int sleep_timer_period = (1000 * 60 * 30);  // 30 minutes 
Timer sleep_timer(sleep_timer_period, time_to_go_to_sleep);   // set at 30 minutes

// SLEEP TIMER ACTIONS 
void time_to_go_to_sleep() {
  Log.info("going to sleep=%d", go_to_sleep++);
}

void setup()                                                  //  SETUP ############################################
{
  // PINS AND PIN MODES 
  pinMode(door_sw, INPUT_PULLDOWN); // pin low when door open
  pinMode(water_low_sw, INPUT_PULLDOWN); // pin low when water level LOW
  pinMode(water_LOWLOW_sw, INPUT_PULLDOWN); // pin low when water level LOW LOW
  pinMode(wake_sw, INPUT_PULLUP); // pin low when wake switch pressed
  pinMode(idiot_LED, OUTPUT); // just an idiot light
  pinMode(transmitter_lcd_power, OUTPUT); 
  digitalWrite(transmitter_lcd_power, HIGH);  // lcd powered when awake, will turn off when in sleep mode
  (void)logHandler; // just to eliminate the unused variable warning
  AIOClient.begin();  // Adafruit IO client 
  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");
  sleep_timer.start();  //  timer for system to go to sleep after 30 minutes
  publish_timer.start();  // timer for publishing to cloud
  delay(2000);
  Wire.setSpeed(20000);
  Wire.begin();
    //Check if the AHT20 will acknowledge
  if (humiditySensor.begin() == false) {
    Log.info("AHT20 not detected. Please check wiring. Freezing.");
    }
    else{
      }
  Log.info("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();
    Log.info("Connecting to skynet");
    lcd->setCursor(0,1);
    lcd->print("CLOUD: CONNECTING");
    waitFor(Particle.connected, (90000));
    lcd->setCursor(0,3);
    switch (Particle.connected())
    {
    case 0:
      Log.info("CLOUD: NOT CONNECTED");
      lcd->print("CLOUD: NOT CONNECTED");
      break;
    case 1:
      Log.info("CLOUD: CONNECTED");
      lcd->print("CLOUD: CONNECTED");
      break;
    }
  }
// CLOCK 
    if (millis() - lastSync > ONE_DAY_MILLIS) {
    // Request time synchronization from the Particle Device Cloud ###### need to change to get local time ######
    Particle.syncTime();
    lastSync = millis();
    }

lcd->clear();
}


void loop()                                              //  LOOP  #######################################
{  
    if(Cellular.isOff() == true) {
    Cellular.on();
    Log.info("Cellular turning back on");
    lcd->clear();
    lcd->print("CELLULAR TURNING BACK ON");
    waitFor(Cellular.isOn, 30000);
    Cellular.connect();
  }
  else {
  }
// if (humiditySensor.available() == true)   
Log.info("ST 0000");
lcd->setCursor(0,0);
lcd->print("ST 0000");
// TIME on LCD
if (Time.second() != lastSecond) {
Log.info(Time.timeStr() );
lcd->setCursor(8,0);
Time.zone(-6);
lcd->print(Time.hour() < 10? "   0" : "    ");
lcd->print(Time.hour());
lcd->print(Time.minute() < 10? ":0": ":");
lcd->print(Time.minute());
lastSecond = Time.second();
}
  // Temperature and Battery 
  float temperature = humiditySensor.getTemperature();
  float Ftemp = round(((temperature * 9/5)+32));
  Log.info("Temp:=%f", Ftemp);
  batteryValue = battery.read();
  float batteryScaled = map(batteryValue,1125,4000,5,16);
  Log.info("BATT:=%f ",batteryScaled);
  Log.info("Battery AI:=%d", batteryValue);
  lcd->setCursor(0,1);
  lcd->print("TEMP:");
  lcd->print(Ftemp, 0);
  lcd->print("F");
  lcd->print(" Batt:");
  lcd->print(batteryScaled, 2);
  lcd->print("V");

 // WATER LEVEL SWITCHES
  int Water_Level = digitalRead(water_low_sw) + digitalRead(water_LOWLOW_sw);
  int Water_level_low = digitalRead(water_low_sw);
  int Water_level_LOWLOW = digitalRead(water_LOWLOW_sw);
  lcd->setCursor(0,2);
  {
    switch (Water_Level)
    {
    case 0:
      Log.info("WTR:MIN! ");
      lcd->print("WTR:MIN!");
      break;
    case 1:
      Log.info("WTR:LOW ");
      lcd->print("WTR:LOW ");
      break;
    case 2:
      Log.info("WTR:GOOD ");
      lcd->print("WTR:GOOD");
      break;
    default:
      Log.info("WTR:ERR! ");
      lcd->print("WTR:ERR!");
      break;
    }
    last_water_level = Water_Level;
    Log.info("WATER LOW:%d", Water_level_low);
    Log.info("WATER LEVEL LOWLOW:%d", Water_level_LOWLOW);
    delay(1000);
  }

 // DOOR POSITION SWITCHES 
  int door_pos = digitalRead(door_sw);
  lcd->setCursor(10,2);
  {
    switch (door_pos)
    {
    case 0:
      Log.info("DOOR:OPEN ");
      lcd->print("DOOR:OPEN");
      break;
    case 1:
      Log.info("DOOR:CLSD ");
      lcd->print("DOOR:CLSD");
      break;
    }
    Log.info("door_pos:=%d", door_pos);
     delay(1000);
  }

 // WAKE BUTTON 
  int wake_sw_lvl = digitalRead(wake_sw);
  {
    switch (wake_sw_lvl)
    {
    case 0:
      Log.info("WAKE SWITCH: AWAKEN");
      sleep_timer.reset();          // Sleep timer reset
      digitalWrite(idiot_LED, LOW);

      break;
    case 1:
      Log.info("WAKE SWITCH: NOT");
      break;
    }
    last_wake_sw = wake_sw_lvl;
    Log.info("wake_sw_lvl:=%d", wake_sw_lvl);
    delay(1000);
  }
  // GAS PRESSURE TROUBLE and PRESSURE VALUES
    if(gas_pressureValue < 500) {
  Log.info("Gas P AI:=%d", gas_pressureValue);
  gas_pressureValue = gas_pressure.read();
  gas_pressureScaled = map(gas_pressureValue,600,3000,1,3000);
  lcd->setCursor(0,3);
  lcd->print("GAS:");
  lcd->print("TRBL ");
  }
    else
  {
  Log.info("GAS_pressure_Value:=%d",gas_pressureValue);
  lcd->setCursor(0,3);
  lcd->print("GAS:");
  lcd->print(gas_pressureScaled);
  lcd->print("PSI");
  delay(1000);
  }
    if(gas_pressureScaled < 1) {
      Log.info("GAS PRESSURE BAD");
    }
      else
      Log.info("GAS PRESSURE SCALED:%f", gas_pressureScaled);

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

// PUBLISHING DATA TO CLOUD  NEED TO MAKE PUBLISH ONE TIME, then only if there is a change. Publish limit once per minute
  // Trigger the integration
// Log info for bool troubleshooting
  if ((Particle.connected() == true) & (publish_data == 1)) {
  String Stemp = String::format("%.2f", Ftemp);
    ST0000temperaturefeed.send(Stemp);
      delay(30000);
  String Sbatt = String::format("%.2f", batteryScaled);
    ST0000batteryfeed.send(Sbatt);
      delay(30000);
  String SgasP = String::format("%.f", gas_pressureScaled);
    ST0000gasPfeed.send(SgasP);
      delay(30000);
    ST0000waterlevelLOWfeed.send(Water_level_low);
      delay(30000);
    ST0000waterlevelLOWLOWfeed.send(Water_level_LOWLOW);
      delay(30000);
    ST0000doorfeed.send(door_pos);
      delay(30000);
    publish_data = 0;
    publish_timer.reset();
  }
  else {
        
  }
Log.info("PUBLISH DATA:%d", publish_data);
      // setting sleep duration to wake at 0400, 1200, 1800
sleep_duration = (3600000*1);
Log.info("Sleep Duration TIME:%d", sleep_duration / 3600000);
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;
  config.mode(SystemSleepMode::ULTRA_LOW_POWER).duration(sleep_duration)   // 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;
}
    delay(6000);
}

Log.info

0036814780 [app] INFO: CLOUD NOT CONNECTED

0036818994 [app] INFO: PUBLISH DATA:26

0036818995 [app] INFO: Sleep Duration TIME:1

0036818995 [app] INFO: SLEEP STATUS:=1

0036828027 [app] INFO: GOING TO SLEEP

0036846187 [ncp.at] TRACE: > AT+CIMI

0036846214 [ncp.at] TRACE: < 234104099952405

0036846214 [ncp.at] TRACE: < OK

0036871435 [system.nm] TRACE: Request to power off the interface

0036871435 [net.pppncp] TRACE: NCP event 3

0036871436 [net.pppncp] TRACE: NCP power state changed: IF_POWER_STATE_POWERING_DOWN

0036871436 [ncp.client] TRACE: Try powering modem off using AT command

0036871437 [ncp.at] TRACE: > AT+CPWROFF

0036871438 [system.nm] INFO: State changed: IFACE_DOWN -> DISABLED

0036874259 [ncp.at] TRACE: < OK

0036874260 [ncp.client] TRACE: Waiting the modem to be turned off...

0036874300 [net.pppncp] TRACE: NCP event 3

0036874300 [net.pppncp] TRACE: NCP power state changed: IF_POWER_STATE_DOWN

0036874300 [system.nm] TRACE: Interface 4 power state changed: 1

0036874301 [ncp.client] TRACE: It takes 41 ms to power off the modem.

0036874301 [mux] INFO: Stopping GSM07.10 muxer

0036874302 [mux] INFO: Gracefully stopping GSM07.10 muxer

0036874302 [mux] INFO: Closing all muxed channels

0036874302 [mux] INFO: Closing mux channel 1

0036874303 [mux] INFO: Muxed channel 2 already closed

0036874303 [mux] INFO: Muxed channel 3 already closed

0036874303 [mux] INFO: Muxed channel 4 already closed

0036874304 [mux] INFO: GSM07.10 muxer thread exiting

0036874304 [mux] INFO: GSM07.10 muxer stopped

0036874305 [ncp.client] TRACE: Setting UART voltage translator state 0

0036874305 [ncp.client] TRACE: Soft power off modem successfully

0036874305 [ncp.client] TRACE: Deinit modem serial.

0036874306 [ncp.client] TRACE: Modem already off

0036874306 [ncp.client] TRACE: Setting UART voltage translator state 0

0036874307 [ncp.client] TRACE: Soft power off modem successfully

0036874307 [ncp.client] TRACE: Deinit modem serial.

0036874307 [ncp.client] TRACE: NCP state changed: 0

0036874308 [net.pppncp] TRACE: NCP event 1

0036877435 [system.sleep] TRACE: Entering system_sleep_ext()

0036877436 [system.sleep] TRACE: Interface 4 is off already

0036877436 [system.sleep] TRACE: Interface 3 is off already

If you disconnect from the cloud before going to sleep, when the device wakes up, it stays disconnected from cellular and the cloud. Since you are using ULP mode, when the device wakes it resumes execution of loop() and does not go through setup() again.

It appears that your reconnect to the connect to cellular and the cloud logic is in setup(), so it won’t get executed again and will stay disconnected. You may want to add a flag and do it from loop instead from the first run at boot, and wake from sleep.

Breathing dark blue is cellular on, disconnected. I’m guessing you’re getting that instead of breathing white (cellular off) because it’s taking more than 6 seconds to turn off. Since it was still technically on before going to sleep, upon wake it turns back on again (but does not connect), so breathing dark blue.

Thanks@rickkas7 !!

That makes sense, and nothing that will be to much of a challenge to add the flags in the loop.

What threw me for a loop is that it would send out to the cloud that it was going back to sleep, somehow it was firing the timer back up to go back to sleep, which the timer start is in setup() I will add that flag in there to start the timer if it is not running also.

It would be nice if the dark blue breathing was in the status LED page. During shutdown it would go white cellular off when it shuts the cellular down.

With using ULP and trying to keep power consumption as low as possible while it is there is it necessary for me to command the cellular off or will that happen in the background as a function of ULP ?

Now just to get the PCB done and figure out a solution to deal with the cellular noise on the I2C bus.

If you plan on waking up every time and connecting to the cloud, it’s not necessary to disconnect from the cloud or cellular before going to sleep. That will be handled automatically for you. This didn’t work properly in old versions of Device OS, but you shouldn’t be using versions prior to 2.0.0 anyway.

The only time you need to disconnect first is if you have cycles where you wake without connecting to the cloud. For those you need to disconnect before going to sleep.

In some cases, you may want to manually disconnect if you need relatively precise control over the wake time, because the amount of time to disconnect is variable, and is not included in the duration specified in the sleep call.

You don’t need a fixed delay after Cellular.off() as there is a Cellular.isOff() API call.

1 Like

Perfect, I will go that route. There is no reason for these to wake up and not connect to the cloud. My long term goal is to have it wake up at specified times (at least within an hour). Many of these will be deployed close to the time zone break and we are not going to mess with DST. So if I can get them to wake around 0400, 1200, 1600 is the plan.

Thank you again for the help!