Boron Solar Charging with 1.5.0-rc1

Finally got around to implementing the cloud control of solar charging (along with other configuration parameters). Here was my approach in case anyone has some other suggestion or can use it. There is quite a bit here but figured I’d share for any feedback in my approach and maybe someone can use it as well.

Within the Boron:

Initialize values:

//Configuration parameters: -Initialized here
//After initialization, this should be set via PUB/SUB method on some frequency
int16_t config_mode = 1; //Mode - 1:Cellular All-in-on only, 2:Cellular+Lora Gateway, 3:Remote Lora Node
int16_t config_slpInterval_Min = 5;  //Time between taking sensor readings when powered (sec)
int16_t config_rptTime_Min = 20;  //Time between reporting readings to the cloud when not powered
int16_t config_vitalsTime_Min = 120; //Time between publishing vitals
int16_t config_stayAwake_flag = 1; //Duration of time to stay awake. 1 = forever, 0 = immediatly sleep
int16_t config_powerSourceMaxCurrent = 900; //Set maximum current the power source can provide. The default is 900 mA.
int16_t config_powerSourceMinVoltage = 3880; //Set minimum voltage required for VIN to be used. The default is 3880 (3.88 volts).
int16_t config_batteryChargeCurrent = 896; //Sets the battery charge current.The default is 896 mA.
int16_t config_batteryChargeVoltage = 4112; //Sets the battery charge termination voltage. The default is 4112 (4.112V).
int16_t config_indexNum = 0; //Index number of current configuration (0-255)

#define configPtID_mode "1"
#define configPtID_slpInterval_Min "2"
#define configPtID_rptTime_Min "3"
#define configPtID_vitalsTime_Min "4"
#define configPtID_stayAwake_flag "5"
#define configPtID_powerSourceMaxCurrent "6"
#define configPtID_powerSourceMinVoltage "7"
#define configPtID_batteryChargeCurrent "8"
#define configPtID_batteryChargeVoltage "9"
#define configPtID_indexNum "255"

In setup() load the default upon first poweron (we don’t know if it’s solar or mains power yet):

  //Initalize default power configuration for USB charging
  System.setPowerConfiguration(SystemPowerConfiguration());  // To restore the default configuration

Handle the subscription back from each publish event:

void subscriptionHandler(const char *event, const char *data)
{
  Log.info("New Subscription Event: %s",event);
  Log.info("With Data: %s", data);

  jsonParser.clear();
  jsonParser.addString(data);
  jsonParser.parse();

  bool newPmicCongig_rx = false;
  int intValue;

  //Update device sleep, wake, vitals, stayawayke configuration
  if (jsonParser.getOuterValueByKey(configPtID_mode, intValue)) config_mode = intValue;
  if (jsonParser.getOuterValueByKey(configPtID_slpInterval_Min, intValue)) config_slpInterval_Min = intValue;
  if (jsonParser.getOuterValueByKey(configPtID_rptTime_Min, intValue)) config_rptTime_Min = intValue;
  if (jsonParser.getOuterValueByKey(configPtID_vitalsTime_Min, intValue)) config_vitalsTime_Min = intValue;
  if (jsonParser.getOuterValueByKey(configPtID_stayAwake_flag, intValue)) config_stayAwake_flag = intValue;

  //Update PMIC Configuration:
  if (jsonParser.getOuterValueByKey(configPtID_powerSourceMaxCurrent, intValue)) {
      if (config_powerSourceMaxCurrent != intValue){
        config_powerSourceMaxCurrent = intValue;
        newPmicCongig_rx = true;
      } 
  }
  if (jsonParser.getOuterValueByKey(configPtID_powerSourceMinVoltage, intValue)) {
      if (config_powerSourceMinVoltage != intValue){
        config_powerSourceMinVoltage = intValue;
        newPmicCongig_rx = true;
      }
  }
  if (jsonParser.getOuterValueByKey(configPtID_batteryChargeCurrent, intValue)) {
      if (config_batteryChargeCurrent != intValue){
        config_batteryChargeCurrent = intValue;
        newPmicCongig_rx = true;
      }
  }
  if (jsonParser.getOuterValueByKey(configPtID_batteryChargeVoltage, intValue)) {
    if (config_batteryChargeVoltage != intValue){
      config_batteryChargeVoltage = intValue;
      newPmicCongig_rx = true;
    }
  }

  //Update Configuration index Number so cloud knows if it needs to send a new config
  if (jsonParser.getOuterValueByKey(configPtID_indexNum, intValue)) config_indexNum = intValue;

  //update PMIC Config if needed:
  if(newPmicCongig_rx) {
    Log.info("new PMIC Config Rx - Call function");
    pmicConfig();
  }
  pubSub_ResponseRx = true;
}

Update battery configuration if it changes. Only allow going to Solar Config if battery source is greater than 70 percent.

void pmicConfig(){

  Log.info("Check PMIC Config");
  PMIC power(true);
  if (power.getInputCurrentLimit() != config_powerSourceMaxCurrent || power.getInputVoltageLimit() != config_powerSourceMinVoltage || power.getChargeCurrentValue() != config_batteryChargeCurrent || power.getChargeVoltageValue() != config_batteryChargeVoltage) 
  {
    
    Log.info("Current PMIC settings is Different than Config");
    
    //If power source min voltag is greater than 3880 only allow setting it to a higher value if current battery charge level is greater than 70%. This is to ensure the device is adequatly charged before switching to Solar functionality and no longer chargable via USB. 
    if (System.batteryCharge() > 70 || config_powerSourceMinVoltage <= 4000 )
    { 
      Log.info("Battery is greater than 70 Perct or power source min voltage <= 4000 accept new config");
      SystemPowerConfiguration conf;
      conf.powerSourceMaxCurrent(config_powerSourceMaxCurrent)
          .powerSourceMinVoltage(config_powerSourceMinVoltage) //3880 for USB, 5080 for Solar
          .batteryChargeCurrent(config_batteryChargeCurrent)
          .batteryChargeVoltage(config_batteryChargeVoltage)
          .feature(SystemPowerFeature::USE_VIN_SETTINGS_WITH_USB_HOST);
      int res = System.setPowerConfiguration(conf);
      Log.info("setPowerConfiguration1=%d", res);
    } 
  }

  //If power source min voltag is greater than 3880 and we are low on battery charge, then set powerSourceMinVoltage to 3880 to ensure the device is capable of USB charging.
  if(power.getInputVoltageLimit() > 3880 && System.batteryCharge() < 10) 
  {
    Log.info("Battery is less than 10 perc and currently configured for greater than 3.880 volts. Use Defaults");
    int res = System.setPowerConfiguration(SystemPowerConfiguration());  // To restore the default configuration
    Log.info("setPowerConfiguration2=%d", res);
  }
}

Something I don’t show here is when publishing an event, I also include what the current “config_indexNum”.

Within the Python function that I use to processes the data from the publish event, IF the config_indexNum does not equal the config_indexNum reported by the device, it assembles the JSON response of all configuration parameters to send as a response that is handled by the subscriptionHandler()

    response_msg = {"0":1}

    if "configIndex" in msg['data']:
        if (int(msg['data']['configIndex']) != device.device_settings.config_indexNum):
            response_msg = {
                str(configPtID.mode):device.device_settings.config_mode,
                str(configPtID.slpInterval_Min):device.device_settings.config_slpInterval_Min,
                str(configPtID.rptTime_Min):device.device_settings.config_rptTime_Min,
                str(configPtID.vitalsTime_Min):device.device_settings.config_vitalsTime_Min,
                str(configPtID.stayAwake_flag):device.device_settings.config_stayAwake_flag,
                str(configPtID.powerSourceMaxCurrent):device.device_settings.config_powerSourceMaxCurrent,
                str(configPtID.powerSourceMinVoltage):device.device_settings.config_powerSourceMinVoltage,
                str(configPtID.batteryChargeCurrent):device.device_settings.config_batteryChargeCurrent,
                str(configPtID.batteryChargeVoltage):device.device_settings.config_batteryChargeVoltage,
                str(configPtID.indexNum):device.device_settings.config_indexNum
            }

    return func.HttpResponse(json.dumps(response_msg))

(Something not shown here is the webhook configuration within the Particle Console but that’s pretty straightforward).

So now, each time I want to send a new configuration to the device, I just update the values and increment the config_indexNum in SQL. The next time the device publishes, it receives a response with the new configuration and settings are updated.

I’m sure I could clean this up a bit or maybe there are prettier ways to write this but it’s functional for me. :slight_smile: What I like about this, is I an also use the “stayAwake” flag, change the slpInterval, reportInterval, vitalsInterval at anytime and those settings are updated the next time the device wakes up and connects to the cloud.

@chipmc - This might be an alternative to consider rather than having a park ranger having to be at the device at a specific time if they need to wake it up for you. Although it does require more of a personalized backend to process the particle webhooks as well as a SQL (or some other DB). I did still use your approach of waking up the device at a specific Time of Day rather than just sleeping for x minutes. This way I know when to expect new data from the device. I just combined both concepts into one.

1 Like