Code to keep Electrons functioning while Off-line - Learning a lesson from this week's outage

All,

If there is one good thing that comes from an outrage like this, it is the possibility to explore a new error mode. Today, when I cannot get Particle.connected(), the device just tries over and over again with soft and hard resets at certain intervals. Looking at the forums, I can see that I should have planned for this happening and can still have some functionality while disconnected. I thought I would share what seems to be working and ask for help on some parts that are problematic. This is drawing on posts that already exist for the Photon but I did not see anything like this for the Electron in my searches.

Step 1 - Make sure we have System Thread Enabled and Connect to Semi-Automatic. This separates execution of my code from the Particle processes and it allows me some control over the connection process.

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.

Step 2 - Create a function for connecting to Particle and avoid calling Particle.connect() repeatedly as this resets the connection attempt. Here is what I have, suggestions welcome. Do I need to throw some Particle.prcoess() lines in here?

bool connectToParticle()
{
  if (!Cellular.ready())
  {
    Cellular.on();                                           // turn on the Modem
    Cellular.connect();                                      // Connect to the cellular network
    if(!waitFor(Cellular.ready,90000)) return false;         // Connect to cellular - give it 90 seconds
  }
  Particle.connect();                                      // Connect to Particle
  if(!waitFor(Particle.connected,30000)) return false;     // Connect to Particle - give it 30 seconds
  return true;
}

Step 3 - Here is where I could use some advice. I can let the Particle.subscribe(), Particle.variable() and Particle.function() calls timeout but, it seems to take a long time. Should I pull all that out of Setup() and execute it once connection succeed? Had planned to retry connection hourly.

Suggestions?

Chip

A different side to this - I have an electron, solar powered, very remote, that I was carefully managing the power consumption on, but had not yet implemented code to have it deep sleep and check for enough battery power to continue before reconnecting.

The constant attempt at connection used a lot more power than typical, so overnight I’ve gone down to 4% SOC. Fingers crossed that the sun shines brightly there today! I’ll have to implement something to ensure I don’t use up all the battery power in another outage of this type.

J

@jimbol,

I think we are on the same page. My dev unit spent the night connecting and rand the battery down to 1%. My plan is to change the code so there is a limited number of attempts to connect. Then, the device will work off-line and sleep as per normal until the next hourly check.

Make sense? Chip

Yep, I think that’s the answer for this issue.
Just have to find the time to implement it.
J

In your code you have a waitFor line on cellularReady and another one on particleConnected.

My guess is that since you have threading enabled, you don’t need to wait for any of those… (i might be wrong). it was my assumption that calling them in that threading mode will make those lines not blocking calls.

Am I wrong?

Also is it only necessary to Particle.connect() or do I need to call Cellular.connect()

I thought that the cellular one is only necessary when you want to go online but not to the particle cloud. Again! I might be completely wrong!

I have some code I shared that will keep your battery SOC from going much below whatever you set it at. I usually set the bottom floor at 30%. It worked for me for like 3 months last year. Combine that with the watchdog circuit and you should be good.

All,

At the risk of sharing something that is blindingly obvious, I wanted to close the loop on what happened with this week’s outage and how I had not prepared my battery powered device to cope.

My electron sits out in a park about a mile from the parking lot and the nearest outlet. When the telecom provider mishap occurred, it got stuck in an endless attempt to connect (flashing green) and drained the battery overnight. My usual error corrections (soft and hard reset) did not take it out of this state.

What I realized is that I should rewrite my code so that the device will function correctly (counting people) and only attempt to connect for a few tries every hour. The first step in this was to rewrite the Setup() of my code so, there is no connection to Particle. Here is what I came up with:

void setup()                                                      // Note: Disconnected Setup()
{
  pinMode(intPin,INPUT);                                          // PIR Sensor Interrupt pin
  pinMode(wakeUpPin,INPUT);                                       // This pin is active HIGH
  pinMode(blueLED, OUTPUT);                                       // declare the Blue LED Pin as an output
  pinMode(tmp36Shutdwn,OUTPUT);                                   // Supports shutting down the TMP-36 to save juice
  digitalWrite(tmp36Shutdwn, HIGH);                               // Turns on the temp sensor
  pinMode(donePin,OUTPUT);                                        // Allows us to pet the watchdog
  pinMode(hardResetPin,OUTPUT);                                   // For a hard reset active HIGH

  power.begin();                                                  // Settings for Solar powered power management
  power.disableWatchdog();
  power.disableDPDM();
  power.setInputVoltageLimit(4840);     //Set the lowest input voltage to 4.84 volts. This keeps my 5v solar panel from operating below 4.84 volts (defauly 4360)
  power.setInputCurrentLimit(900);                                // default is 900mA
  power.setChargeCurrent(0,0,0,0,0,0);                            // default is 512mA matches my 3W panel
  power.setChargeVoltage(4112);                                   // default is 4.112V termination voltage
  power.enableDPDM();

  attachInterrupt(wakeUpPin, watchdogISR, RISING);   // The watchdog timer will signal us and we have to respond
  attachInterrupt(intPin,sensorISR,RISING);   // Will know when the PIR sensor is triggered

  char responseTopic[125];
  String deviceID = System.deviceID();                                // Multiple Electrons share the same hook - keeps things straight
  deviceID.toCharArray(responseTopic,125);
  Particle.subscribe(responseTopic, UbidotsHandler, MY_DEVICES);      // Subscribe to the integration response event

  Particle.variable("HourlyCount", hourlyPersonCount);                // Define my Particle variables
  Particle.variable("DailyCount", dailyPersonCount);                  // Note: Don't have to be connected for any of this!!!
  Particle.variable("Signal", Signal);
  Particle.variable("ResetCount", resetCount);
  Particle.variable("Temperature",temperatureF);
  Particle.variable("Release",releaseNumber);
  Particle.variable("stateOfChg", stateOfCharge);

  Particle.function("startStop", startStop);                          // Define my Particle functions
  Particle.function("resetFRAM", resetFRAM);
  Particle.function("resetCounts",resetCounts);
  Particle.function("Reset",resetNow);
  Particle.function("HardReset",hardResetNow);
  Particle.function("SleepInFive",sleepInFive);
  Particle.function("SendNow",sendNow);

  if (!fram.begin()) {                                                  // You can stick the new i2c addr in here, e.g. begin(0x51);
    snprintf(Status,13,"Missing FRAM");                                 // Can't communicate with FRAM - fatal error
    state = ERROR_STATE;
  }
  else if (FRAMread8(VERSIONADDR) != VERSIONNUMBER) {                   // Check to see if the memory map in the sketch matches the data on the chip
    snprintf(Status,13,"Erasing FRAM");
    ResetFRAM();                                                        // Reset the FRAM to correct the issue
    if (FRAMread8(VERSIONADDR) != VERSIONNUMBER) state = ERROR_STATE;   // Resetting did not fix the issue
  }

  resetCount = FRAMread8(RESETCOUNT);                                   // Retrive system recount data from FRAMwrite8
  if (System.resetReason() == RESET_REASON_PIN_RESET)                   // Check to see if we are starting from a pin reset
  {
    resetCount++;
    FRAMwrite8(RESETCOUNT,static_cast<uint8_t>(resetCount));            // If so, store incremented number - watchdog must have done This
  }

  Time.zone(-5);                                                        // Set time zone to Eastern USA daylight saving time
  takeMeasurements();
  StartStopTest(1);                                                     // Default action is for the test to be running
  timeTillSleep = sleepDelay;                                           // Set initial delay for 60 seconds
  lastEvent = millis();

  if (state != ERROR_STATE) state = IDLE_STATE;                         // IDLE unless error from above code
}

Now, if Particle’s telecommunications partners drop the ball in the future, my device will just keep counting and not drain the battery.

Hope this helps and please let me know if I missed anything.

Chip

2 Likes

In the event that you get multiple failures to connect, perhaps there needs to be additional code that issues an AT command to reset the modem? Because I don’t think the modem will reset if the watchdog resets the micro.

@fishmastaflex,

Agree. I think some sort of reset for the modem is required.

In my code,I count the number of attempts and only do three soft resets. On the fourth, I have built a carrier board for the Electron that can power cycle the system for a hard reset. This has worked well so far.

Chip

Ah that’s right! I remember that you said that from another thread now. :slight_smile:

I implemented your connectToParticle code snippet from the top to test an issue I’ve been having. Tried to add some printing out to the serial but I guess that doesn’t work when a bool is called because all it’s looking for is the true or false return:

bool connectToParticle() {
    if (!Cellular.ready()) {
        Serial.println("Cellular not ready, turning on module and connecting.");
        Cellular.on();                                           // turn on the Modem
        Cellular.connect();                                      // Connect to the cellular network
        if(!waitFor(Cellular.ready,90000)) {
            Serial.println("Connection timed out.");
            return false;         // Connect to cellular - give it 90 seconds
        }
    }
    Serial.println("Connecting to Particle cloud");
    startTime = millis();
    Particle.connect();
    if (waitFor(Particle.connected, 60000)) {
        Serial.printlnf("Connected: connection process took %d seconds.", (millis() - startTime)/1000);
        return true;
    }
    else {
        Serial.println("WARNING: connection failed. Timed out after 60 seconds");
        return false;
    }
}

Hmm, that should be OK. One issue I have with debug statements in this code is the need to add delays so the print buffer can be emptied before the device goes to sleep. Also, when the device sleeps it breaks the serial connection. I am away so I can’t test myself but perhaps you can give it a try.

Chip

Thanks Chip. No need to test though, I already did! I moved the debug statements out of the bool method into a different loop where I print them to the serial port:

    int numFailures;
    for (int i = 0; i < 3; i++) {
        if (!connectToParticle) {
            Serial.println("Failed to connect to Particle");
            numFailures++;
        }
        else {
            Serial.println("Connected to Particle. Publishing now...");
            Particle.publish("fb1", payload, PRIVATE, NO_ACK);
            numFailures = 0;
            break;
        }
        
        if (numFailures > 3) {
            Serial.println("Failed 3 times...resetting the system.");
            delay(5000);
            System.reset();
        }
    }
1 Like