Improve electron's connection-reliability to the Particle Cloud

Dear Particle Team

I love my Electron (SARA-U270) on the Asset Tracker (v 2.0.1) and would like to track things like vehicles or people.
The idea is to publish (Particle.publish) a location update every 5 seconds and forward that event to an own backend via Particle Cloud’s webhook.
To improve the GPS positioning, I attached the external Adafruit antenna and for debugging a single chainable LED to the Grove port.
I use my own SIM card, set the carrier’s APN and use a keepAlive of 30s.
Unfortunately, I see that the electron quite often loses connectivity to the Particle Cloud, i.e. the electron’s LED is blinking cyan (not green).
It’s not breathing but also not that rapidly blinking like I see it just before a cloud connection is established. If I then restart the electron manually (power off/on), the setup works and the electron is connected again to the cloud.
With that in mind, I checked out blessed @rickkas7 Github libraries and tried to aggressively restart the electron if anything is not working as expected.
However, this doesn’t solve the issues and I would like to know whether there is an even quicker and more reliable way to reconnect to the cloud? Or is my code doing something stupid and I could improve it there?

I appreciate any feedback, thank you!

Please find below my used code:

#include <AssetTrackerRK.h>
#include <PublishQueueAsyncRK.h>
#include <electronsample.h>
#include "cellular_hal.h"
#include "Particle.h"
#include "AppWatchdogWrapper.h"
#include "ConnectionCheck.h"
#include "ConnectionEvents.h"
#include "SessionCheck.h"
#include <Grove_ChainableLED.h>

STARTUP(cellular_credentials_set("gprs.swisscom.ch", "", "", NULL));
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(SEMI_AUTOMATIC);

retained uint8_t publishQueueRetainedBuffer[2048];
PublishQueueAsync publishQueue(publishQueueRetainedBuffer, sizeof(publishQueueRetainedBuffer));

ConnectionEvents connectionEvents("connEventStats");
SessionCheck sessionCheck(300);
ConnectionCheck connectionCheck;
AppWatchdogWrapper watchdog(60000);

int transmittingData = 1;
long lastPublish = 0;
float minSpeed = 1.7; // in knots
int delayMilliSeconds = 5000;

ChainableLED leds(D1, D0, 1);

AssetTracker t = AssetTracker();
FuelGauge fuel;

void setup() {
   // t.begin();
    t.gpsOn();
  	t.startThreadedMode();
    t.antennaExternal();
    
    Serial.begin(9600);

	connectionEvents.setup();
	sessionCheck.setup();
	connectionCheck.setup();
	publishQueue.setup();

    Particle.keepAlive(60);
	Particle.connect();

    leds.init();

    if (waitUntil(Particle.connected)) {
        Particle.function("tmode", transmitMode);
        Particle.function("minSpeed", setMinSpeed);
        Particle.function("delaySeconds", setDelayMilliSeconds);
        Particle.function("batt", batteryStatus);
        Particle.function("gps", gpsPublish); 
        Particle.function("jsonLocation", publishJsonLocation);
        Particle.publish("startup", "electron", PRIVATE);
    }
    
    Serial.println("setup completed");
}

void loop() {

	sessionCheck.loop();
	connectionCheck.loop();
	connectionEvents.loop();
	
	colorLed();
	
    if (
            transmittingData && 
            t.getFixQuality() && 
            t.getSatellites() > 4 &&
            (lastPublish == 0 || (millis()-lastPublish > delayMilliSeconds && t.getSpeed() > minSpeed))
        ) {
    //    if (waitFor(Particle.connected, 2000)) {
            lastPublish = millis();
            publishJsonLocation("");
    //    } 
    }
    
}

void colorLed() {
   if (t.getSatellites() > 4) {
       leds.setColorRGB(0, 0, 255, 0);
   } else if (t.getSatellites() > 0) {
       leds.setColorRGB(0, 0, 0, 255);
   } else {
       leds.setColorRGB(0, 255, 0, 0);
   }
}

long setDelayMilliSeconds(String command) {
    delayMilliSeconds = atol(command);
    return delayMilliSeconds;
}

int transmitMode(String command) {
    transmittingData = atoi(command);
    return transmittingData;
}

float setMinSpeed(String command) {
    minSpeed = atof(command);
    return minSpeed;
}

int gpsPublish(String command) {
    if (t.getFixQuality()) {
        Particle.publish("G", t.readLatLon(), 60, PRIVATE);
    }
    return t.getSatellites();
}

int publishJsonLocation(String command) {
    
    publishQueue.publish("location", String::format("{\"lat\":%f, \"lng\":%f, \"speed\":%.3f, \"satellites\":%d, \"timestamp\": \"20%d-%02d-%02dT%02d:%02d:%02d.%03dZ\", \"rssiStrength\":%.2f, \"rssiQuality\":%.2f, \"gpsFix\":%d, \"gpsFixQuality\":%d}", 
        t.readLatDeg(), 
        t.readLonDeg(), 
        t.getSpeed(), 
        t.getSatellites(), 
        t.getYear(), t.getMonth(), t.getDay(), t.getHour(), t.getMinute(), t.getSeconds(), t.getMilliseconds(),
        Cellular.RSSI().getStrength(),
        Cellular.RSSI().getQuality(),
        t.gpsFix(),
        t.getFixQuality()
        ), PRIVATE);
    
    return 1;
}

int batteryStatus(String command){
    
    Particle.publish("B",
          "v:" + String::format("%.2f",fuel.getVCell()) +
          ",c:" + String::format("%.2f",fuel.getSoC()),
          60, PRIVATE
    );
    // if there's more than 10% of the battery left, then return 1
    if (fuel.getSoC()>10){ return 1;}
    // if you're running out of battery, return 0
    else { return 0;}
}

THe first thing to check would be if the Electron can be stable at all without any of your code.
After that you could gradually add feature by feature to your project to locate any potential cause for the behaviour you see.

One thing I always preach is: "Don't use String!"

Over time this may lead to instabilities which you won't get with good ol' C-strings.

I even use an alternative function signature for Particle.function() callbacks

int myCallback(const char* arg);

BTW, you are using retained variables but you are not enabling the feature via

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

Just a note of inconsistency :wink:

but then

I'd also reorder this condition

    if (
            transmittingData && 
            t.getFixQuality() && 
            t.getSatellites() > 4 &&
            (lastPublish == 0 || (millis()-lastPublish > delayMilliSeconds && t.getSpeed() > minSpeed))
        ) {

in a way that the t.xxx() functions aren't called quite as rapidly as they are currently.
When you put the millis() restriction first in the ANDed list, you will only hit the GPS module when you actually need to and not permanently.

1 Like

Thank you very much ScruffR, I’m really grateful for your feedback.
I changed the signature to your recommendation, added the STARTUP method and re-arranged the conditions (I even removed the t.getFixQuality() as I believe the satellite-count covers that one, too)
As the code is my single source of truth, I remembered the wrong value, thanks for pointing it out. :slight_smile:

Two follow-up questions:

  1. Can I have multiple STARTUP-calls?
    STARTUP(cellular_credentials_set("gprs.swisscom.ch", "", "", NULL));
    STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));
    
  2. Does String::format() also fall under “Don’t use String!”? If so, what would be an equivalent function to publish a json?

@rbach, you can create a single function will all the calls you need and STARTUP and call that single function instead of multiple STARTUP call.

@ScruffR will recommend the use of snprintf() to “print” a formatted string into a char array. There are lots of (his) examples for json publishing in the forum you can draw from.

1 Like

You can do this? Cool! Abandoning strings!

1 Like