Electron connecting but not logging to Cloud

Hi,

I have about 50 Electrons in the field. They turn on each hour, take a sensor reading, report that, then go back to sleep. They were working alright for 15 days, but now about 8 of the electrons stopped reporting data to the cloud.

I checked them out. They run the normal firmware, they breathe Cyan as expected, but nothing gets logged into the particle cloud. It looks like it’s really running the proper cycle, but publishing with those devices doesn’t work for some reason.

When we remove power, and do a hard reset, the device looks like it starts working again. So far the next couple hours it has worked as expected, but I’m wondering if anyone knows why this could be happening?

Thanks!

Are you using numerous String objects?
If so, long running devices will eventually run out of heap space.

There are other possible reasons but for those a code review may be necessary.

The only String object is what is being published. So, just one String.

I’m curious what is heap space? I have 50 devices running the same code, and only few have issues.

I’ll post my code.

I don’t think it’s a code issue… but I’m more than happy to be wrong.

The device appears to be working properly and running through the code cycle, the cloud just doesn’t show any published data.

I think it’s a cloud and device issue, that they aren’t working together properly because of something with the device maybe.

    /*----------------------------------------------------------------------------------------------------------------------
     *                                      INCLUDES
     */
    #include "Particle.h"
    #include "cellular_hal.h"
    #include <math.h>
    #include <HC_SR04.h>

    /*----------------------------------------------------------------------------------------------------------------------
     *                                      CONSTANTS
     */
    #define EVENT_NAME                  "M"
    #define MEASUREMENT_COUNT           100 
    #define MEASUREMENT_THRESHOLD        10

    #define BUFFER_SIZE                 256

    #define CELL_APN                    "hologram"
    #define CELL_USERNAME               ""
    #define CELL_PASSWORD               ""

    #define EEPROM_TIME_ADDRESS         0

    #define ELECTRON_PRODUCT_ID         ----
    #define ELECTRON_PRODUCT_VERSION    --

    #define DELAY_SETTLE                100          // Milliseconds
    #define DELAY_CLOUD                 20000         // Milliseconds
    #define DELAY_CONNECT_TIME          500          // Milliseconds
    #define UPDATE_TIME                 300000       // Milliseconds
    #define DELAY_MEASUREMENT           20           // Milliseconds
    #define NOT_CONNECTED_TIMEOUT       120000        // Milliseconds
    #define FAILURE_SLEEP_DURATION      3600         // Seconds

    #define PIN_ECHO                    D5          // Connect HC-SR04 Range finder as follows:
    #define PIN_RELAY                   D3          // GND - GND, 5V - VCC, D4 - Trig, D5 - VoltageDivider.
    #define PIN_TRIG                    D4          // VoltageDivider (470 OHM RESISTORS FROM D5 TO GND AND TO ECHO)

    #define RANGE_MAX                   400
    #define RANGE_MIN                   0.5

    #define DEBUG                       1
    #define BAUD_RATE                   9600

    /*----------------------------------------------------------------------------------------------------------------------
     *                                      MACROS
     */
    #if DEBUG
    # define SERIAL_DEBUG_BEGIN(x)      Serial.begin(x)
    # define DPRINTF(...)               Serial.printf(__VA_ARGS__)
    # define DPRINTLN(x)                Serial.println(x)
    #else // do nothing
    # define SERIAL_DEBUG_BEGIN(x)
    # define DPRINTF(...)
    # define DPRINTLN(x)                
    #endif

    /*----------------------------------------------------------------------------------------------------------------------
     *                                      CONFIGURATION
     */
    PRODUCT_ID(ELECTRON_PRODUCT_ID); // Product ID
    PRODUCT_VERSION(ELECTRON_PRODUCT_VERSION); 
    STARTUP(cellular_credentials_set(CELL_APN, CELL_USERNAME, CELL_PASSWORD, NULL)); 

    STARTUP(System.enableFeature(FEATURE_RESET_INFO)); // To enable reading why the system was reset.
    // SYSTEM_THREAD(ENABLED); // for allowing processes to run in parallel. Default is SYSTEM_THREAD disabled. 
    SYSTEM_MODE(SEMI_AUTOMATIC); // Cloud connecting at SEMI_AUTOMATIC can provide power savings.

    HC_SR04 rangefinder = HC_SR04(PIN_TRIG, PIN_ECHO, RANGE_MIN, RANGE_MAX); // initializes HC_SR04 object

    FuelGauge fuel;

    /*----------------------------------------------------------------------------------------------------------------------
     *                                      GLOBALS
     */
    int m_min = -1;
    int m_q1 = -1;
    int m_med = -1;
    int m_q3 = -1;
    int m_max = -1;
    float m_soc = 0.0;

    char publishString[BUFFER_SIZE];

    bool published = false;
    //bool connectSuccess = false; // used for connection timeout
    bool forceConnect = true;

    /*======================================================================================================================
     *                                      PARTICLE MAIN CYCLE ENTRY POINTS
     */
    void setup() 
    {
        initAll();
        DPRINTLN("+++ setup ====================================");
        takeMeasurements();
        slowClock();
        sprintf(publishString, "{\"m_min\":%d,\"m_q1\":%d,\"m_med\":%d,\"m_q3\":%d,\"m_max\":%d,\"m_soc\":%f}", m_min, m_q1, m_med, m_q3, m_max, m_soc);
        DPRINTF("forceConnect value = %d\n", forceConnect);
        if(!forceConnect) // go to sleep without reporting
        {
            DPRINTLN("setup branch 1; going to sleep without reporting");
            unsigned long t_now = Time.now();   // seconds since Jan 1 1970 (UTC)
            int Sleep_Duration = getSleepDelay(t_now);
            DPRINTF("Going to sleep for %d seconds\n", Sleep_Duration);
            DPRINTF("End of no connectivity cycle; time read: %s\n", Time.format(Time.now(), TIME_FORMAT_DEFAULT).c_str());
            System.sleep(SLEEP_MODE_SOFTPOWEROFF, Sleep_Duration);
        }
        Particle.connect();
        // connectSuccess = waitFor(Particle.connected, NOT_CONNECTED_TIMEOUT); // System threading needs to be enabled for this to work
        waitUntil(Particle.connected);
        DPRINTLN("--- setup ====================================");
    }

    void loop() 
    {
        DPRINTLN("+++ loop *************************************");
        // if (connectSuccess)
        // {
        if (published == false)
        {
            DPRINTLN("loop branch 1; publishing");
            published = Particle.publish(EVENT_NAME, publishString, PRIVATE);
            DPRINTF("Published with return value: %d (1 means success)\n", published);
        }
        else
        {
            DPRINTLN("loop branch 2; delay, disconnect, write to eeprom, go to sleep");
            delay(DELAY_CLOUD);
            Particle.disconnect();
            unsigned long t_now = Time.now();   // seconds since Jan 1 1970 (UTC)
            int Sleep_Duration = getSleepDelay(t_now);
            DPRINTF("End of connected cycle; time read: %s\n", Time.format(Time.now(), TIME_FORMAT_DEFAULT).c_str());
            DPRINTF("Going to sleep for %d seconds\n", Sleep_Duration);
            System.sleep(SLEEP_MODE_SOFTPOWEROFF, Sleep_Duration);
        }
        // }
        // else // connection time is greater than 2 minutes, go to sleep for 1 hour
        // {
        //     DPRINTLN("Going to sleep for one hour, failed to connect to cloud!");
        //     System.sleep(SLEEP_MODE_SOFTPOWEROFF, FAILURE_SLEEP_DURATION);
        // }
        DPRINTLN("--- loop *************************************");
    }

    /*======================================================================================================================
     *                                          HELPER FUNCTIONS
     */
     
    ///=====================================================================================================================
    /// <summary>This function called to initialize everything that is needed</summary>
    ///=====================================================================================================================
    inline void initAll()
    {
        SERIAL_DEBUG_BEGIN(BAUD_RATE);
        DPRINTF("Beginning of cycle; time read: %s\n", Time.format(Time.now(), TIME_FORMAT_DEFAULT).c_str());
        pinMode(PIN_RELAY, OUTPUT);
        m_soc = fuel.getSoC();
        if(!Time.isValid())
        {
            DPRINTLN("Time is not valid, force a connect.");
            forceConnect = true;
        }
        if(Time.hour(Time.now()+2*60) == 2) // 8pm daily report
        {
            DPRINTLN("Time is 8pm, force a connect.");
            forceConnect = true;
        }
    }

    ///=====================================================================================================================
    /// <summary>Helper function used in takeMeasurements to turn on the solid state relay</summary>
    ///=====================================================================================================================
    void turnOnRelay() {
        digitalWrite(PIN_RELAY,HIGH);
        delay(DELAY_SETTLE);
        DPRINTLN("The Relay turned on");
    }

    ///=====================================================================================================================
    /// <summary>Helper function used in takeMeasurements to turn off the solid state relay</summary>
    ///=====================================================================================================================
    void turnOffRelay() {
        digitalWrite(PIN_RELAY,LOW);
        delay(DELAY_SETTLE);
        DPRINTLN("The Relay turned off");
    }

    ///=====================================================================================================================
    /// <summary>
    ///  This function slows down the clock from 120MHz to 30MHz in order to be more power efficent.  This function was 
    ///  written by someone in the particle community (link is on the next line)
    ///  https://community.particle.io/t/reducing-photon-run-time-power-consumption-howto/18613
    /// </summary>
    ///=====================================================================================================================
    void slowClock() {
        RCC->CFGR &= ~0xfcf0;
        RCC->CFGR |= 0x0090;

        SystemCoreClockUpdate();
        SysTick_Configuration();

        FLASH->ACR &= ~FLASH_ACR_PRFTEN;
        DPRINTLN("Clock was slowed down to 30 MHz");
    }

    ///=====================================================================================================================
    /// <summary>
    ///  Helper function for takeMeasurements that sorts an array of length MEASUREMENT_COUNT using the insertion sort 
    ///  algorithm
    /// </summary>
    /// <param name="myReadings">
    ///  the array of integers that are being sorted (no need to pass by reference)
    /// </param>
    ///=====================================================================================================================
    void sortArray(int myReadings[])
    {
        int i, j, x;
        for(uint16_t i = 1; i < MEASUREMENT_COUNT; i++)
        {
            x = myReadings[i];
            j = i - 1;
            while(j >= 0 && (myReadings[j] > x))
            {
                myReadings[j + 1] = myReadings[j];
                j = j - 1;
            }
            myReadings[j + 1] = x;
        }
    }

    ///=====================================================================================================================
    /// <summary>
    ///  This function takes measurements from an ultrasonic sensor and stores the results in 5
    ///  global variables so that the results are ready to publish
    /// </summary>
    ///=====================================================================================================================
    void takeMeasurements()
    {
        DPRINTLN("+++ takeMeasurements");
        turnOnRelay(); // Turns on relay that allows power to flow to sensor
        int measurements[MEASUREMENT_COUNT];
        for(int i = 0; i < MEASUREMENT_COUNT; i++) // takes MEASUREMENT_COUNT measurements
        {
            measurements[i] = rangefinder.getDistanceCM();
            delay(DELAY_MEASUREMENT);
        }
        DPRINTLN("Finished Taking Measurements.");
        turnOffRelay(); // Stops power from leaking to sensor
        sortArray(measurements); // Sorts the measurements in ascending order
        // Populates global variables
        m_min = measurements[0];
        m_q1 =  measurements[MEASUREMENT_COUNT / 4 - 1];
        m_med = measurements[MEASUREMENT_COUNT / 2 - 1];
        m_q3 =  measurements[MEASUREMENT_COUNT * 3 / 4 - 1];
        m_max = measurements[MEASUREMENT_COUNT - 1];
        if(m_q3 <= MEASUREMENT_THRESHOLD) // Threshold reached
        {
            DPRINTLN("forceConnect set to true, MEASUREMENT_THRESHOLD condition met");
            forceConnect = true;
        }
        DPRINTLN("--- takeMeasurements");
    }

    ///=====================================================================================================================
    /// <summary>
    ///  This function uses the time of day to determine how long to sleep until another measurement is taken.  Currently it 
    ///  has been written to operate differently in 3 different time ranges:
    ///  8am - 5pm MDT : go to sleep until the next hour
    ///  6pm - 7pm MDT : go to sleep until 8pm MDT
    ///  8pm - 7am MDT : go to sleep until 8am MDT
    /// </summary>
    /// <returns>Seconds until wakeup</returns>
    ///=====================================================================================================================
    int getSleepDelay(unsigned long t_unix) 
    {
        DPRINTLN("+++ getSleepDelay");
        int t_hour = Time.hour(t_unix);          // hour (0-23) in UTC
        int r_sec;
        if(isInRange(t_hour, 14, 23)) // (14 - 23 UTC) or (8am - 5pm MDT)
        {
            DPRINTLN("getSleepDelay Branch 1");
            r_sec = secUntilNextHour(t_unix);
        }
        else if(isInRange(t_hour, 0, 1)) // (0 - 1 UTC) or (6pm - 7pm MDT)
        {
            DPRINTLN("getSleepDelay Branch 2");
            r_sec = secUntilNextHour(t_unix);
            int r_hours = hoursUntilStart(t_hour, 2); // (2 UTC) or (8pm MDT)
            r_sec += (r_hours - 1) * 3600;
        }
        else // (2 - 13) UTC or (8pm - 7am)
        {
            DPRINTLN("getSleepDelay Branch 3");
            r_sec = secUntilNextHour(t_unix);
            int r_hours = hoursUntilStart(t_hour, 14); // (14 UTC) or (8am MDT)
            r_sec += (r_hours - 1) * 3600;
        }
        DPRINTLN("--- getSleepDelay");
        return r_sec;
    }

    ///=====================================================================================================================
    /// <summary>
    ///  Helper function for getSleepDelay used to determine how many seconds are remaining until the beginning of the next 
    ///  hour.
    /// </summary>
    /// <param name="t_unix">unix time that holds how many seconds have elapsed since Jan 1 1970 in UTC timezone</param>
    /// <returns>Number of seconds until the beginning of the next hour</returns>
    ///=====================================================================================================================
    int secUntilNextHour(unsigned long t_unix) 
    {
        int t_minute = Time.minute(t_unix);
        int t_second = Time.second(t_unix);
        int r_minute = 59 - t_minute;
        int r_second = 59 - t_second;
        return r_minute * 60 + r_second;
    }

    ///=====================================================================================================================
    /// <summary>
    ///  Helper function for getSleepDelay that is used to determin hour many hours need to elapse until the target hour has
    ///  been reached.
    /// </summary>
    /// <param name="hour_now">The current hour - expected to be within the range (0 - 23)</param>
    /// <param name="hour_target">The target hour - expected to be within the range (0 - 23)</param>
    /// <returns>The number of hours needed to elapse until the target hour will be reached</returns>
    ///=====================================================================================================================
    int hoursUntilStart(int hour_now, int hour_target)
    {
        return (hour_target + 24 - hour_now) % 24;
    }

    ///=====================================================================================================================
    /// <summary>
    ///  Helper function for getSleepDelay that is used to determine if the current hour is within a specified range.
    /// </summary>
    /// <param name="hour_now">The current hour - expected to be within the range (0 - 23)</param>
    /// <param name="lower">The lower hour boundary</param>
    /// <param name="upper">The upper hour boundary</param>
    /// <returns>True if within range, false if outside of range</returns>
    ///=====================================================================================================================
    bool isInRange(int hour_now, int lower, int upper)
    {
        if(lower < upper)
        {
            return (hour_now >= lower) && (hour_now <= upper);
        }
        return (hour_now >= lower) || (hour_now <= upper); // in case the range wraps around the 23 - 0 hour boundary
    }

Just some minor hints to start with

Try this instead

 HC_SR04 rangefinder(PIN_TRIG, PIN_ECHO, RANGE_MIN, RANGE_MAX); // initializes HC_SR04 object

You are doing some extra time calculations on Time.now(), did you know about Time.zone() and Time.local() to rid you of the need to convert UTC/local yourself.

There is also an easier way to find the seconds till next full hour

  secTillNextHour = 3600 - Time.local() % 3600;

As I see it forceConnect will never be false so what’s it for?

Also slowing down the system clock may well have some impact on the cloud timing. I don’t know whether there are any minimum time-used checks (=negative/invers timeout) in the framework, but if there are the slow clock might trigger that.

I also had mixed experiences in non-AUTOMATIC modes with Particle.connect() without calling WiFi.on()/Cellular.on() first. To be on the safe side just add it anyway.

Also don’t rely on the connection being valid before calling Particle.publish() and guard it with if(Particle.connected()).
BTW, even a return value true of the publish instruction does not actually tell you that the event was successfully published - just successfully queued for publishing.
And in case you are not actually targeting 0.6.2+ you would need to add a 5sec delay between publish and sleep - here counts the set target for the build process and not what system is installed on your device (this has confused others before - double check).

3 Likes

Thanks for these suggestions ScruffR.

However, it’s still not working after I tried these suggestions (issue of it not logging data to the cloud all the time). I had a fleet of 9 devices and they all reported each day for 4 days, but on the 5th day, one didn’t report. It seems to get worse as time goes on and almost every device will eventually have this problem.

The forceConnect was for something we were trying in the past to only connect and send data if it passed a threshold measurement, but doesn’t matter anymore. I removed it.