Sleepy Boron - SOC Not Updating

Hi,

I am working on a Boron LTE that takes a differential air pressure reading every 60 seconds and spends the rest of the time in standby (SLEEP_NETWORK_STANDBY). For some reason soc seems to be “stuck” and is not updating. I have reviewed my code multiple times and tried adding short delays with no luck. The other variables posting to Ubidots are changing appropriately, so I do not think its the Ubidots connection. I left it overnight and it posted a soc of 55.36 the whole time, until I removed the lipo this morning for a moment then re-powered and it went down to 26.11. I started with 1.5.0-rc.1 and upgraded to 1.5.0-rc.2 and experienced the same behavior.

I am honestly hoping its something simple I am overlooking, and any help would be appreciated.

Link to code: https://build.particle.io/build/5e6632611aa1f50021c2fdc7

Thank you very much.

Your link is not working.
When you want to share your code you need to use the SHARE THIS REVISION feature to create a sharable link.

@ScruffR, my apologies:

https://go.particle.io/shared_apps/5e6632711aa1f50016c2ff67

Try adding fuel.quickStart() in setup() and after your sleep statement.

@ScruffR, here is some preliminary results after implementing your suggestion:

Hmm, then I’d have to defer to @rickkas7.

@ScruffR,

Is there a power consumption implication to this statement? Not much in the docs on this API.

Thanks, Chip

I have had the same issue on some Electrons that I was using 1.5.0 rc-1 on, they reported at 65 % the entire time until I downgraded the Device OS. Pushing the reset button and unplugging the battery did not change the SoC. I also use the SLEEP_NETWORK_STANDBY when the units are not reporting. They report fine when running on older Device OS.

FYI I reverted back to original code from the start of this post and downgraded to 1.4.4 and SOC is behaving correctly. At this point, I believe it could be an issue with 1.5.0-rc.x @rickkas7.

@ScruffR,

I am seeing the same issue and was hoping that moving to deviceOS@1.5.0 would fix it. Unfortunately it has not. Batter SoC levels are stuck and do not seem to reflect the current charge levels.

@kwhite, Have you seen the same after moving to the production release?

Thanks, Chip

1 Like

Yes I have seen the same issue continue. All of the units that I have tested the 1.5.0 release on have ready the same SoC level since they started reporting. Resetting the unit does not change the readings. They are not all reading the same level between units, I guess maybe it depends on what the battery level was the first time it looked at it.

1 Like

Too bad, something for @avtolstoy and/or to file a issue report about then, I fear.

@all,

I have done some additional testing and here are the symptoms across different code bases and with both the Electron and the Boron.

  1. Problem only occurs when the device is in low power mode - (58:30 in Stop mode sleep / 1:30 awake and connected).
  2. If I take a device that is in this state and take it out of low power mode (awake and connected), the problem persists until a System.reset() or power cycle.
  3. The value displayed seems to be the last one taken before going into low power mode.
  4. Downgrading to deviceOS@1.4.4 fixes this issue.

Here is what this looks like - this seems to be a serious bug for deviceOS@1.5.0 as this behavior:

Code base here: https://github.com/chipmc/Rwanda-Sense-and-Control

Any help would be appreciated.

Chip

1 Like

@ScruffR or @avtolstoy,

I think I am getting closer to figuring out what the issue is.

There are three things needed to get the device into this state:

  • The new Sleep 2.0 API
  • The new Power Management API
  • Sleep

I have simplified the code base to remove any hardware dependencies - all you need is a Boron and a Battery.

When the Boron boots up, it will publish the battery level every 30 minutes. You will see that the battery level changes as expected. However, when you put the device in to “lowPowerMode” by typing “Yes” at the console, the device will start to sleep at the next 30 minute interval and then wake to report the battery level every 30 minutes. Unless the device is reset, the battery level will not change.

/*
 * Project Sleep-Battery-Test
 * Description: Simple test for the Effect of Sleep on Battery SOC reporting
 * Author: Chip McClelland  
 * Date: 4-6-20
 *
 * When the Boron boots up, it will publish the battery level every 30 minutes.  
 * You will see that the battery level changes as expected.  
 * However, when you put the device in to "lowPowerMode" by typing "Yes" at the console, the device will start to sleep at the next 30 minute interval 
 * and then wake to report the battery level every 30 minutes.  Unless the device is reset, the battery level will not change.
 */

SYSTEM_MODE(SEMI_AUTOMATIC);                        // This will enable user code to start executing automatically.
SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.
FuelGauge batteryMonitor;                           // Prototype for the fuel gauge (included in Particle core library)
SystemPowerConfiguration conf;                      // Initalize the new Power Management API to support changes in needed for Solar or DC Power below.
SystemSleepConfiguration config;                    

enum State { IDLE_STATE, NAPPING_STATE, REPORTING_STATE};
char stateNames[8][14] = {"Idle", "Napping", "Reporting"};
State state = IDLE_STATE;

//Program Variables
bool lowPowerMode = false;                    // Flag for wake / sleep cycle setting
float stateOfCharge = 0.00;

// Timing Variables
unsigned long reportingTimeStamp;
unsigned long reportingFrequencySec = 1800;             // How often do we report

void setup() {
    Particle.function("LowPowerMode",setLowPowerMode);
    Particle.publish("Status","Setup Complete",PRIVATE);

    // Comment out this section out and the problem goes away
    conf.powerSourceMaxCurrent(1500)                                   // default is 900mA this let's me charge faster
      .powerSourceMinVoltage(4208)                                     // This is the default value for the Boron
      .batteryChargeCurrent(1024)                                      // default is 2048mA (011000) = 512mA+1024mA+512mA)
      .batteryChargeVoltage(4112)                                      // default is 4.112V termination voltage
      .feature(SystemPowerFeature::PMIC_DETECTION)
      .feature(SystemPowerFeature::USE_VIN_SETTINGS_WITH_USB_HOST) ;
      System.setPowerConfiguration(conf);                               // These settings are for the DC power mode

    config.mode(SystemSleepMode::STOP)
      .duration(reportingFrequencySec * 1000)
      .flag(SystemSleepFlag::WAIT_CLOUD);
}

void loop() {
  switch(state) {

  case IDLE_STATE:                                                    // Where we spend most time - note, the order of these conditionals is important
    if (Time.now() - reportingTimeStamp > reportingFrequencySec) state = REPORTING_STATE;
    break;

  case REPORTING_STATE: {
    char data[64];
    if (!Particle.connected()) connectToParticle();                   // Only attempt to connect if not already New process to get connected
    if (Particle.connected()) {
      stateOfCharge = batteryMonitor.getSoC();                        // Percentage of full charge
      snprintf(data,sizeof(data),"State of Charge = %4.2f %%",stateOfCharge);
      waitUntil(meterParticlePublish);
      Particle.publish("Status", data, PRIVATE);
      reportingTimeStamp = Time.now();
      (lowPowerMode) ? state = NAPPING_STATE : state = IDLE_STATE;
    }
    } break;

  case NAPPING_STATE: {                                               // This state puts the device in low power mode quickly
    if (Particle.connected()) 
      waitUntil(meterParticlePublish);
      Particle.publish("Status","Napping",PRIVATE);
      disconnectFromParticle();                                       // If we are in connected mode we need to Disconnect from Particle
    System.sleep(config);                                             // Sensor will wake at the interval set by the reportingFrequencySec variable
    state = IDLE_STATE;                                               // Back to the IDLE_STATE after a nap - not enabling updates here as napping is typicallly disconnected
    } break;
  }
}

// Particle Function
bool setLowPowerMode(String command)                                   // This is where we can put the device into low power mode if needed
{
  if (command != "Yes" && command != "No") return false;               // Before we begin, let's make sure we have a valid input
  if (command == "Yes") {                                              // Command calls for setting lowPowerMode
    if (Particle.connected()) {
      waitUntil(meterParticlePublish);
      Particle.publish("Mode","Low Power", PRIVATE);
    }
    lowPowerMode = true;
  }
  else if (command == "No")  {                                          // Command calls for clearing lowPowerMode
    if (Particle.connected()) {
      waitUntil(meterParticlePublish);
      Particle.publish("Mode","Normal Operations", PRIVATE);
    }
    lowPowerMode = false;
  }
  return true;
}

// Utility Functions
bool connectToParticle() {                                              // Highly reliable way to get connected - and not block crucial functions while doing so
  Cellular.on();
  Particle.connect();
  // wait for *up to* 5 minutes
  for (int retry = 0; retry < 300 && !waitFor(Particle.connected,1000); retry++) {
    // You can check on things here
    Particle.process();
  }
  if (Particle.connected()) return true;
  else return false;                                                    // return based on our connection status
}

bool disconnectFromParticle()                                           // Ensures we disconnect cleanly from Particle
{
  Particle.disconnect();
  waitFor(notConnected, 15000);                                         // make sure before turning off the cellular modem
  Cellular.off();
  delay(2000);                                                          // Bummer but only should happen once an hour
  return true;
}

bool notConnected() {                                             // Companion function for disconnectFromParticle
  return !Particle.connected();
}


bool meterParticlePublish(void)
{
  static unsigned long lastPublish=0;                                   // Initialize and store value here
  if(millis() - lastPublish >= 1000) {                                  // Particle rate limits at 1 publish per second
    lastPublish = millis();
    return true;
  }
  else return false;
}

Something about calling the power management API is causing battery state of charge to not be updated.

Thanks,

Chip

3 Likes

Hi @chipmc ,
I don’t have a workable sim card that can make my Boron LTE connect to the Particle cloud. Just created an application that most likely behaves as your example, except connecting to the cloud and publish events.

#include "Particle.h"

SYSTEM_MODE(MANUAL);

SYSTEM_THREAD(ENABLED);

FuelGauge batteryMonitor;
SystemPowerConfiguration conf;

Serial1LogHandler log(LOG_LEVEL_ALL);

void setup() {
    // Comment out this section out and the problem goes away
    conf.powerSourceMaxCurrent(1500)                                     // default is 900mA this let's me charge faster
        .powerSourceMinVoltage(4208)                                     // This is the default value for the Boron
        .batteryChargeCurrent(1024)                                      // default is 2048mA (011000) = 512mA+1024mA+512mA)
        .batteryChargeVoltage(4112)                                      // default is 4.112V termination voltage
        .feature(SystemPowerFeature::PMIC_DETECTION)
        .feature(SystemPowerFeature::USE_VIN_SETTINGS_WITH_USB_HOST) ;
    System.setPowerConfiguration(conf);                                  // These settings are for the DC power mode
}

void loop() {
    static system_tick_t startTime = millis();
    if (Serial1.available()) {
        while (Serial1.available()) {
            Serial1.read();
        }
        LOG(INFO, "Device enters stop mode. It wakes up after 3 seconds.");
        SystemSleepConfiguration config = {};
        config.mode(SystemSleepMode::STOP).duration(3s);
        System.sleep(config);
    }
    else if (millis() - startTime > 1000) {
        startTime = millis();
        LOG(TRACE, "SoC: %4.2f %%", batteryMonitor.getSoC());
    }
}

In this application, the SoC is printed to Serial1 every second and once you type character on Serial1 terminal, device will enter sleep mode(STOP) for 3 seconds and then wakes up to keep printing the SoC.

I ran this application on my Boron and the SoC can update as expected after waking up. Could you help verify on your Boron? If it also worked for you, then it must be something wrong with other stuff.

Best regards,
Guohui

1 Like

@ielec,

Thank you for pitching in here. I have run your test code and it does appear to work. However, I am not sure if the way it is written will illustrate the error as you would not expect the state of charge to change over a 3 second period. So, when it sleeps, it always comes back to the level that was there before sleep. I am going to play with your code a bit with longer sleep times and see if I still see an issue.

Also, I don’t have a Serial1 setup and when I move to Serial for the LogHandler, it supplies power which defeats the purpose so, I will likely switch back to Particle.publish().

One thing your code has highlighted. While the batteryMonitor.getSOC() allows for two digits to the right of the decimal point, the readings are “chunky” with the smallest I have seen being 0.14%. This fact along with the low power consumption of the Boron may explain this issue. Will need to test to see.

Chip

1 Like

You can always try with a USB cable that does not connect the Vcc line.

@ScruffR,

Yes, I had one of those once and lost it. Need to get a replacement for this purpose.

@ielec,

I modified your code to allow for cellular monitoring. Thank you for simplifying this further so that we can eliminate any other potential errors. With this code, over a 24 hour period, I am not seeing any change in the state of charge even to the 0.01% level.

/*
 * Project Sleep-Battery-Test-2
 * Description: Another test for SOC
 * Author: Guohui modifications by Chip McClelland
 * Date: 4-10-20
 * In this application, the SoC is printed to Serial1 every second and once you type character on Serial1 terminal, 
 * device will enter sleep mode(STOP) for 3 seconds and then wakes up to keep printing the SoC.
 */

#include "Particle.h"

SYSTEM_MODE(SEMI_AUTOMATIC);

SYSTEM_THREAD(ENABLED);

FuelGauge batteryMonitor;
SystemPowerConfiguration conf;
SystemSleepConfiguration config;

char batteryString[32];

void setup() {
    // Comment out this section out and the problem goes away
    conf.powerSourceMaxCurrent(1500)                                     // default is 900mA this let's me charge faster
        .powerSourceMinVoltage(4208)                                     // This is the default value for the Boron
        .batteryChargeCurrent(1024)                                      // default is 2048mA (011000) = 512mA+1024mA+512mA)
        .batteryChargeVoltage(4112)                                      // default is 4.112V termination voltage
        .feature(SystemPowerFeature::PMIC_DETECTION)
        .feature(SystemPowerFeature::USE_VIN_SETTINGS_WITH_USB_HOST) ;
    System.setPowerConfiguration(conf);                                  // These settings are for the DC power mode

    config.mode(SystemSleepMode::STOP)
        .duration(30min)
        .network(NETWORK_INTERFACE_CELLULAR)
        .flag(SystemSleepFlag::WAIT_CLOUD);
            
    Particle.connect();
    waitUntil(Particle.connected);

    Particle.publish("Status","Starting Sleep / Wake Cycles",PRIVATE);
    delay(1000);
}

void loop() {
    Particle.connect();
    waitUntil(Particle.connected);
    snprintf(batteryString,sizeof(batteryString), "SoC: %4.2f %%", batteryMonitor.getSoC());
    Particle.publish("Charge",batteryString,PRIVATE);
    System.sleep(config);
}

Perhaps one one with access to a connected Boron could test this. Also, I confirmed that this issue is also present in the 2G/3G Boron.

Here is a sample of the data I collected:

Thanks,

Chip

1 Like

I'm just bouncing ideas here:
"IF" Device OS 1.5.0.x and/or Sleep 2.0 actually SLEEPS the FuelGauge, then it wouldn't have a chance to determine an accurate value at a low duty cycle (Sleepy Boron).

Has anyone tried fuel.getVCell() instead of SOC ? That's a direct measurement and the only main external influence would be if charging is occurring at the time of the measurement. You could disable charging for 30 seconds after the Wake Event and then read the Li-Po Voltage after the Cellular Connection is established and the current demand settles down. Or w/ Manual Mode, read the Voltage before the Cellular Connection.

Tracking Voltage instead of SOC should be less sensitive to future changes to Device OS & Sleep also, for Sleepy Borons ?

2 Likes

@Rftop,

I like the way you think.

To keep things simple, I will test this without charging as I don’t want to mix the old and the new power management regimes (PMIC and SystemPowerConfiguration) in order to disable charging. Also, I moved the snprintf statement up before the device connects to Particle and included both SOC and vCell so I don’t have to guess when the cellular connection demands level out. I will now run this for a while and see if vCell changes relative to SOC.

At the same time, I have to point out that prior to 1.5.0 the getSOC() command seemed to work perfectly well on my sleepy devices. I hope that this functionality can be brought back while still enjoying the benefits of Sleep 2.0.

Last question. I have see the discharge curves of LiPO batteries and they are fairly flat over much of their range. If all we can get is vCell, can I make a reasonable conversion back to SOC?

Will let you know what the data looks like over the next few hours.

Chip

2 Likes