Issues with a BRN404X not reconnecting

Hi everyone. I have a Boron transmitting data for a weather station at a remote paragliding site. The Boron operated continuously from May 25th 2024 to August 9th 2024, when it went offline. I went to the site and inspected it - it was still on and running. I swapped the Boron for a new one because I assumed that something was wrong with it. The new Boron transmitted data for 3.5 hours before going offline as well.

I am unsure of how to troubleshoot this issue. I don't think my Boron is banned from the network because it is only transmitting data every 8 minutes. What I want to get feedback on first is whether there's a problem in the code I wrote for handling reconnections. Here it is:

#include "Particle.h"
#include <algorithm>

SYSTEM_MODE(SEMI_AUTOMATIC);

SYSTEM_THREAD(ENABLED);

const int ULP_SLEEP_TIME_MS = 8 * 60 * 1000;

void ulpSleep(int durationInMs) {
    systemSleepConfig.mode(SystemSleepMode::ULTRA_LOW_POWER)
            .duration(durationInMs)
            .network(NETWORK_INTERFACE_CELLULAR, SystemSleepNetworkFlag::INACTIVE_STANDBY);
        
    System.sleep(systemSleepConfig);
}

void sleepIfBatteryChargeIsLow() {
    while (true) {
        batteryStateOfCharge = System.batteryCharge();

        Log.info("Battery: %2f", batteryStateOfCharge);

        if (batteryStateOfCharge > 10 || batteryStateOfCharge < 0) {
            // We have enough battery to operate.
            // System.batteryCharge() returns -1 when battery state cannot be determined.
            return;
        } else {
            Log.info("Battery low. Going to sleep.");

            ulpSleep(ULP_SLEEP_TIME_MS);
        }
    }
}

void setup()
{
    sleepIfBatteryChargeIsLow();

    // ... Setup things for sampling the weather sensors.

    // Connect to the Particle cloud so the Boron's clock is accurate.
    if (Particle.connected() == false) {
        Log.info("Connecting to Particle Cloud.");
        Particle.connect();
        if (waitFor(Particle.connected, 100000)) {
            Log.info("Connected to Particle Cloud.");
        } else {
            Log.info("Failed to connect to the Particle Cloud.");
        }
    }
}

void loop()
{
    sleepIfBatteryChargeIsLow();

    // Sample weather sensors for 8 minutes.
    // Then we transmit the data:
    
    if (Particle.connected() == false) {
        Log.info("Connecting to Particle Cloud.");
        Particle.connect();
        if (waitFor(Particle.connected, 100000)) {
            Log.info("Connected to Particle Cloud.");
        } else {
            Log.info("Failed to connect to the Particle Cloud.");
            // We don't try to send the data since the connect failed.
            return;
        }
    }
    
    // kd = Kaaikop Data
    bool success = Particle.publish("kd", jsonArrayOfWeatherReadings, PRIVATE, WITH_ACK);
    Log.info("Publish result: %d", success);
}

The code basically does the following:

  • During setup, connects to the particle network. It's ok if we fail to connect.
  • During the loop, we sample the weather sensors for 8 minutes, then we try to transmit the data. It's ok if we fail to transmit the data, we just move on to the next 8 minute block of sampling and try again.

Am I making a mistake that could cause my Boron to end up stuck and not try to reconnect?

Thank you all for your time.

Since you are using SYSTEM_MODE(AUTOMATIC), the connection will be managed automatically and you don't need to do anything to connect. Even if SEMI_AUTOMATIC, once you start the connection process once, you don't need to do anything else.

The check for Particle.connected() should be before the Particle.publish so it will only attempt to publish when connected, and at the rate you desire.

Your wait for connection is 100 seconds, and that's insufficient. We recommend 11 minutes, because if connection failure occurs for more than 10 minutes, the cellular modem hardware will be reset by completely powering it down, which can sometimes allow a reconnection to occur. You aren't disconnecting from cellular on connection failure, so the modem reset will eventually occur with your code, but it's something to be aware of if you do.

You're using ULP with cellular active, which still uses a significant amount of power. However your sampling interval of 8 minutes is higher than recommended for reconnecting with cellular off. The recommendation in your case is to use SEMI_AUTOMATIC mode and disconnect from cellular when the battery is low. Keep track of the last time you woke and reconnect only when there is sufficient battery power and a minimum of 10 minutes has elapsed. If the battery is high, could continue to use cellular on while sleeping.

Thanks for the reply, rickkas7. Just a minor correction, I am using SYSTEM_MODE(SEMI_AUTOMATIC), not SYSTEM_MODE(AUTOMATIC) (see line 4 of the code snippet I attached). I chose SEMI_AUTOMATIC to implement the idea suggested here:

One common use case for this is checking the battery charge before connecting to cellular on the Boron, B-Series SoM, Electron, and E-Series.

In setup I initiate a sleep if the battery charge is low before attempting to connect.

Thanks for pointing out the missing Particle.connected() check that should be wrapping my publish. I will tweak the code as follows:

if (Particle.connected()) {
    bool success = Particle.publish("kd", jsonArrayOfWeatherReadings, PRIVATE, WITH_ACK);
    Log.info("Publish result: %d", success);
}

What I want to understand better is the behaviour of Particle.publish() when not connected, since that's something my code currently allows. Can calling publish while not connected make the device end up stuck in the publish and fail to reconnect?

Regarding the 100 second wait period - as you mention, the modem reset should eventually be happening. For my use-case, I don't want to wait 11 minutes, I'd rather have the device move on to sample the next window of data and simply discard the window that it could not send.

Regarding ULP sleep with cellular active - I have a large battery and solar panel powering the device, so power consumption is not a concern. Before my device went offline, it was reporting that the battery charge was at 100%.

Thanks for the help.

In SEMI_AUTOMATIC you should do the battery check in setup() then call Particle.connect() once, then it will automatically reconnect if necessary. This is how Tracker Edge and Monitor Edge firmware works.

If you are still in the process of connecting when you call Particle.publish, the publish call will block for up to 10 minutes until the modem is reset, or until cloud connected.