Particle board entering panic mode every couple minutes


#1

General device info:

name/channel: spark/device/last_reset
msg: panic, assert_failed
SOS blink pattern: 10 blinks between SOS -> Assertion Failure
Frequency of occurrence: approximately every 1-2 minutes
Device OS version(s): v0.7.0 and v0.8.0-rc11
Devices: Photon

Does anyone have any suggestions for what might cause an assertion failure? Or where to begin debugging? The message is rather cryptic as to what ‘failed to assert’?

I can flash another program that achieves the same objective and not have this issue, so the board(s) seem to be O.K.

The BaseComponents.hpp/cpp is the same as with the prior version. The main difference is that this version makes extensive use of std::queue, std::vector, std::string, and std::array. I was hoping to simplify the code by relying on the standard libraries, but that may not be the case? Although, on a cortex-m3, I feel like my code shouldn’t be to complicated for it to execute every couple seconds?

Here is the main code body (AlphaMonitor.ino):

/*
 * Project AlphaMonitor
 * Description:
 * Author:
 * Date:
 */

#include <queue>       // Using for queues
#include <string>       // Using for std::string
#include <array>        // Using for easy array management
#include <vector>

#include "BaseComponents.hpp"

char HW_VERSION[] = "v0.0.1";
char USE_CLASS[] = "Heater_Monitor";
char MSG_PROTOCOL[] = "v0.2";
unsigned long lastTime = 0UL;
char UPTIME[12];
unsigned long lastTimeThermocouple = 0UL;
unsigned long lastTimeRTD = 0UL;
unsigned long lastTimeLevel = 0UL;
unsigned long lastTimeModbus = 0UL;
unsigned long lastTimeGPIO = 0UL;

std::array<TemperatureSensor, 2> tempArr;
std::array<LevelSensor, 2> levelArr;

std::queue<std::string> statusQueue;
std::queue<std::string> alarmQueue;    // Not used in program yet

// reset the system after 10 minutes if the application is unresponsive
ApplicationWatchdog wd(600000, System.reset, 512);

// Verify if device has a cloud connection, uses data each time called
void checkConnection() {
  // reset device after this many seconds pass without a connection
  static const int reconnectTimeSecs = 120;
  static long lastFailTime = 0;

  long currentTime = millis();
  bool connected = Particle.connected();

  if(connected) {
    // connected, all is well
    lastFailTime = 0;
  }
  else if(!connected && lastFailTime == 0) {
    // connection has just gone down, note the time
    lastFailTime = currentTime;
  }
  else if(!connected && ((currentTime-lastFailTime) >= (reconnectTimeSecs*1000))) {
    // connection has been down for too long
    System.reset();
  }
}

// Obtains device name (human readable) from the Particle Cloud
//  *********************************WARNING*********************************
//  **********************************NOTES**********************************
// Particle.subscribe("particle/device/name", setName)
// Particle.publish("particle/device/name");
char myName[255];
void setName(const char *topic, const char *name){  // Ignoring topic
  strcpy(myName, name);
}

// setup() runs once, when the device is first turned on.
void setup() {
  bool success = false;
  for(uint8_t i = 0; i < tempArr.size(); i++){
    tempArr[i].setHysteresis(5);
  }
  for(uint8_t i = 0; i < tempArr.size(); i++){
    levelArr[i].setHysteresis(5);
  }
  Particle.subscribe("particle/device/name", setName);
  Particle.publish("particle/device/name"); // Asking to have name sent
  delay(1000);
}

void loop() {
  unsigned long now = millis();
  if (now-lastTime>4000UL) {
    lastTime = now;
    // now is in milliseconds
    unsigned nowSec = now/1000UL;
    unsigned sec = nowSec%60;
    unsigned min = (nowSec%3600)/60;
    unsigned hours = (nowSec%86400)/3600;
    sprintf(UPTIME,"%u:%u:%u",hours,min,sec);
  }

  // Update all sensor readings every 3 seconds (simulated)
  if(now-lastTimeGPIO > (3*1000UL)){
    for(uint8_t i = 0; i < tempArr.size(); i++){
      tempArr[i].setValue(rand() % 500 + 1);
    }
    for(uint8_t i = 0; i < levelArr.size(); i++){
      levelArr[i].setValue(rand() % 375 + 1);
    }
  }

  // Determine what messages need to be sent
  // We want to send a thermocouple reading at least every 5 min.
  // This section is not currently implemented
  if(now-lastTimeThermocouple>(5*60*1000UL)){
    // Queue all
    lastTimeThermocouple = now;
  }
  // We want to send a RTD reading at least every 5 min.
  if(now-lastTimeRTD>(5*60*1000UL)){
    // Queue all
    lastTimeRTD = now;
  }
  // We want to send a level reading at least every 5 min.
  if(now-lastTimeLevel>(5*60*1000UL)){
    // Queue all
    lastTimeLevel = now;
  }
  // We want to send a modbus reading at least every 10 min.
  if(now-lastTimeModbus>(10*60*1000UL)){
    // Queue all
    lastTimeModbus = now;
  }
  delay(100);
  // Check if sensors have new data that needs to be sent
  std::queue<std::string> tempMSGs;
  for(uint8_t i = 0; i < tempArr.size(); i++){
    char buf[15];
    if(tempArr[i].newData()){
      // Queue for publish
      std::string msg;
      msg += "{\"i\":";
      snprintf(buf, 15, "%d", i);
      msg += buf;
      msg += ",\"v\":";
      snprintf(buf, 15, "%.1f", tempArr[i].getReading());
      msg += buf;
      msg += "}";
      tempMSGs.push(msg);
    }
  }
  std::string tempBranch;
  if(tempMSGs.size() > 0){
    tempBranch += "\"THERMOCOUPLE\":[";
    for(uint8_t i = 0; i < tempArr.size(); i++){
      tempBranch += tempMSGs.front();
      tempMSGs.pop();
      if(tempMSGs.size() > 0){
        tempBranch += ",";
      }
    }
    tempBranch += "]";
  }
  delay(100);
  //Particle.publish("STATUS", tempBranch.c_str(), PRIVATE);
  std::queue<std::string> levelMSGs;
  for(uint8_t i = 0; i < levelArr.size(); i++){
    char buf[15];
    if(levelArr[i].newData()){
      // Queue for publish
      std::string msg;
      msg += "{\"i\":";
      snprintf(buf, 15, "%d", i);
      msg += buf;
      msg += ",\"v\":";
      snprintf(buf, 15, "%.1f", levelArr[i].getReading());
      msg += buf;
      msg += "}";
      levelMSGs.push(msg);
    }
  }
  std::string levelBranch;
  if(levelMSGs.size() > 0){
    levelBranch += "\"LEVEL\":[";
    for(uint8_t i = 0; i < levelArr.size(); i++){
      levelBranch += levelMSGs.front();
      levelMSGs.pop();
      if(levelMSGs.size() > 0){
        levelBranch += ",";
      }
    }
    levelBranch += "]";
  }

  // Aggregate MSGs
  if((tempBranch.size() + levelBranch.size()) > 5){
    std::string outboundPacket;
    outboundPacket += "{";
    outboundPacket += tempBranch;
    outboundPacket += ",";
    outboundPacket += levelBranch;
    outboundPacket += "}";
    Particle.publish("STATUS", outboundPacket.c_str(), PRIVATE);
  }


  delay(500);
}

BaseComponents.hpp

#ifndef BASECOMPONENTS_HPP
#define BASECOMPONENTS_HPP

/*
class BaseSensor {
  public:
    virtual bool newData(void){};
    virtual bool updateSensor(void){};
    virtual float getReading(void){};
    virtual void setHysteresis(float hysteresis){};
    virtual float getHysteresis(void){};
};
*/

class TemperatureSensor {
    //enum sensorCatType {J, K, T, N, E, B, R, S, RTD, Transmitter, unused};
    enum sensorCatType {J = 'J', K = 'K', T = 'T', N = 'N', E = 'E', B = 'B', R = 'R', S = 'S', unused = 'u'};    // Currently does not support using a RTD or Transmitter
    private:
      sensorCatType _sensorType;
      float _temperatureValue_C;
      bool  _newData;
      //float _hysteresisLow;       // Low side hysteresis
      //float _hysteresisHigh;      // High side hysteresis
      float _hysteresis;
      //float _highAlarm;
      //float _lowAlarm;
      //String _deviceName          // Used to set what the device is used for

    protected:
      void updateData(float data){
        if(data > (_temperatureValue_C + _hysteresis) || data < (_temperatureValue_C - _hysteresis)){
          _newData = true;
          _temperatureValue_C = data;
        }
        else{
          // Ignoring write b/c of hysteresis
        }
      };

    public:
      //TemperatureSensor()
      void setSensorType(char type);                      // Sets the type of sensor used
      char getSensorType(void){return(_sensorType);};     // Returns the type of sensor used
      bool newData(void){return(_newData);};              // Returns true if new data is available
      bool updateSensor(void);                            // Reads the physical sensor, and if outside of previous value with hysteresis, records
      float getReading(void);                             // Returns the stored reading
      //void setHysteresisLow(float hLow){_hysteresisLow = hLow;};
      //void setHysteresisHigh(float hHigh){_hysteresisHigh = hHigh;};
      //void setHysteresis(float hysteresis){_hysteresisLow = hysteresis; _hysteresisHigh = hysteresis;};
      void setHysteresis(float hysteresis){_hysteresis = hysteresis;};
      float getHysteresis(void){return(_hysteresis);};

      // Testing/Debug functions, remove once physical hardware available
      void setValue(float val){updateData(val);};
};

class LevelSensor {
  private:
    float _level_m;
    float _hysteresis_m;
    bool  _newData;

  protected:
    void updateData(float data){
      if(data > (_level_m + _hysteresis_m) || data < (_level_m - _hysteresis_m)){
        _newData = true;
        _level_m = data;
      }
      else{
        // Ignoring write b/c of hysteresis
      }
    };

  public:
    bool newData(void){return(_newData);};
    bool updateSensor(void);
    float getReading(void);
    void setHysteresis(float hysteresis){_hysteresis_m = hysteresis;};
    float getHysteresis(void){return(_hysteresis_m);};

    // Testing/Debug functions, remove once physical hardware available
    void setValue(float val){updateData(val);};
};

#endif /* BASECOMPONENTS_H */

BaseComponents.cpp

#include "BaseComponents.hpp"

void TemperatureSensor::setSensorType(char type){
    switch(type) {
        case 'J': {
            _sensorType = J;
            // Program thermocouple sensor to use type J
            break;
        }
        case 'K': {
            _sensorType = K;
            // Program thermocouple sensor to use type K
            break;
        }
        case 'T': {
            _sensorType = T;
            // Program thermocouple sensor to use type T
            break;
        }
        case 'N': {
            _sensorType = N;
            // Program thermocouple sensor to use type N
            break;
        }
        case 'E': {
            _sensorType = E;
            // Program thermocouple sensor to use type E
            break;
        }
        case 'B': {
            _sensorType = B;
            // Program thermocouple sensor to use type B
            break;
        }
        case 'R': {
            _sensorType = R;
            // Program thermocouple sensor to use type R
            break;
        }
        case 'S': {
            _sensorType = S;
            // Program thermocouple sensor to use type S
            break;
        }
        /*
        case '1': {
            _sensorType = RTD;
            // Configure system to read/use a RTD
            break;
        }
        case '2': {
            _sensorType = Transmitter;
            // Configure system to read/use a Transmitter
            break;
        }
        */
        default: {
            _sensorType = unused;
            break;
        }
    }
}

float TemperatureSensor::getReading(void){
  _newData = false;
  return(_temperatureValue_C);
}

float LevelSensor::getReading(void){
  _newData = false;
  return(_level_m);
}

#2

Extensive use of dynamic memory allocation (which all of your std:: objects do) will over time lead to heap fragmentation - especially when you keep creating & destroying objects of varying size.

BTW, since all your sensorCatType values map to the character you use in switch(type) you can directly assign them as

  switch(type) {
    case J: // fall through for all valid cases
    case K:
    ...
    case S:
       _sensorType = (sensorCatType)type;
      break;

    default:
      _sensorType = unused;
      break;
  }

#3

Ok, that makes sense, especially considering FreeRTOS is running at the same time. I’ll have to roll back to a more C like approach. Also, thank you for the code suggestion.