Evaluating power consumption

Hi,

now that my project has been deployed for a few days, with some tweaks every day, one of the things I have been keeping an eye on is the power consumption of my Photons, as I want to make sure that both my batteries and my solar panels are adequately sized. Ultimately, I want the solar panels to be able to provide at least enough charging power to replace what a Photon consumes during the “non-charging” hours. In a perfect world, I would like the solar panels to be able to provide that amount of charging power even in non-optimal conditions (ie cloudy days). And I want the batteries to provide enough power to power the Photons continuously even when the solar panels are not charging optimally.
To do figure this out, I have been capturing the last 4 days worth of battery capacity to see if the power consumption is pretty stable. To my horror, it seems to be all over the place, making it really difficult to approximate anything. The picture below is the power consumption of one of my Photon over the last 3 days. Each data point is the delta between 2 subsequent battery capacity measurements - each point is Capacity@n-1 - Capacity@n - thus measuring how much power has been consumed between the 2 measurements.


As you can see, it is all over the place. Is this everyone’s experience? I would have thought, that my power consumption would be roughly the same every time as I am running the same code, but apparently not. The flat lines in the graph represent when I ran out of battery power. :frowning: I have bigger batteries coming shortly to avoid this, hopefully.
Is this everyone’s experience? Am I doing something wrong here?
I am still really new to this, hence the obvious question.

Thanks a lot,
B.

Since WiFi coverage is not static the demand to connect and transmit/receive varies with it.
We also don’t have any input about the connected devices and the graph doesn’t seem to be normalised for the variance in your solar input to the system.
Additionally even if (obviously) the same code is running, that doesn’t necessarily mean that always the same code path is taken and hence the current draw may vary dramatically with conditions being met or not. Without insight on the code we’d never know :wink:

I would pretty much go with what @ScruffR has said. We can’t really help you unless you at least describe what sort of cycle (sleep: wake) you are operating on.

I have a battery backup solution on one product using Photon - this isn’t solar powered and is there to gracefully stop controlled processes when the mains supply is removed.

One of the things I have done is monitor the RSSI and report this back to a web app. This highlights the times when the Photon will be thrashing/retrying to connect to WiFi and that the WiFi signal strength varies hugely (humans attenuate WiFi).

You could try monitoring the battery state (voltage?) and then increasing your sleep period if the power is being used up more on one day or the top-up from solar is less?

1 Like

@ScruffR and @armor : what you said about the wifi signal strength makes sense and I totally didn’t account for that. I guess, the weaker the signal, the more power the Photon would draw.
Here is my code. Please don’t judge the quality of it as I have never been a great coder:

#include <Ubidots.h>
#include <SparkFunBQ27441.h>

//STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));
//SYSTEM_THREAD(ENABLED);

#ifndef MYTOKEN
#define MYTOKEN "XXXXXX"
#endif

#define MAXSENSORS 4
#define WEATHERCOUNT 8
#define VERYDRY 40
#define DRY 50
#define MOIST 60
#define WET 70
#define VERYWET 80
#define STARTHOUR 4
#define ENDHOUR 9

Ubidots ubidots("webhook", UBI_PARTICLE);

//Sensors Pins
int sensor0 = A0;
int sensor1 = A1;
int sensor2 = A2;
int sensor3 = A3;

//H-Bridge Pins
int enablePin = D6;
int valve1Pin = A4;
int valve2Pin = A5;

//Transistor Pins controlling the Sensors
int tr0 = D2;
int tr1 = D3;
int tr2 = D4;
int tr3 = D5;

//unresponsive server LED
int trueLed = D7;

//step-down regulator down
int regulator = D7;

//Battery variables
//Battery babysitty Pins
int sda = D0;
int sdc = D1;

int numSensors = 0;
int valveStatus = 0;
int valveStatusAddr = 0;
int forceWatering = 0;

double moisture = 100;
int moistureAddr = 20;

int weatherRain = 0;
int weatherRainAddr = 40;
int sleepDuration = 300;

//Battery Variables
const unsigned int BATTERY_CAPACITY = 1800;
double voltage = 0.0;
int soc = 0;
int current = 0;
int fullCapacity = 0;
int capacity = 0;
int power = 0;
int health = 0;
int connectedBattery = 0;
String deviceID;
char msg[128];

void setup() {

  Particle.variable("moisture", moisture);
  Particle.variable("weatherRain", weatherRain);
  Particle.variable("valveStatus", valveStatus);
  Particle.variable("forcedWatering", forceWatering);
  Particle.variable("sleepDuration", sleepDuration);
  Particle.function("setMoisture", setMoisture);
  Particle.function("setWeatherRain", setWeatherRain);
  Particle.function("setValveStatus", setValveStatus);
  Particle.function("setForcedWatering", setForceWatering);
  Particle.function("onDemandWatering", onDemandWatering);
  Particle.function("setSleepDuration", setSleepDuration);

  Particle.subscribe("hook-response/Ubidots", ubidotsHandler, MY_DEVICES);
  Particle.subscribe("hook-response/openWeather", weatherHandler, MY_DEVICES);

  // Initialize serial port
  Serial.begin(115200);

  ubidots.setDebug(false);

  //Initializing sensors
  pinMode(sensor0, INPUT);
  pinMode(sensor1, INPUT);
  pinMode(sensor2, INPUT);
  pinMode(sensor3, INPUT);

  // Initializing H-Bridge Pins
  pinMode(enablePin, OUTPUT);
  pinMode(valve1Pin, OUTPUT);
  pinMode(valve2Pin, OUTPUT);

  //Initializing Transistor Pins

  pinMode(tr0, OUTPUT);
  pinMode(tr1, OUTPUT);
  pinMode(tr2, OUTPUT);
  pinMode(tr3, OUTPUT);

  pinMode(trueLed, OUTPUT);
  pinMode(regulator, OUTPUT);

  deviceID = System.deviceID();
  if(deviceID == "19002f001547343339383037") {
    numSensors = 2;
  } else if(deviceID == "2a0033001647363335343834") {
    numSensors = 2;
  } else if(deviceID == "39002b000f47363336383437") {
    numSensors = 3;
  } else {
    numSensors = 4;
  }
  EEPROM.get(valveStatusAddr, valveStatus);
  if(valveStatus < 0) {
    valveStatus = 0;
    EEPROM.put(valveStatusAddr, valveStatus);
  }
  setupBQ27441();
  //EEPROM.get(moistureAddr, moisture);
  //EEPROM.get(weatherRainAddr, weatherRain);
  Time.zone(-6);

}

void loop() {
  sprintf(msg, "Waking up from sleep");
  Particle.publish("Loop", msg, PRIVATE);
  delay(10000);
  Serial.println("main: Turning regulator on");
  digitalWrite(regulator, HIGH);
  Particle.publish("Loop", "Turning regulator on", PRIVATE);
  delay(500);
  sprintf(msg, "valveStatus = %i, moisture = %f, weatherRain = %i", valveStatus, moisture, weatherRain);
  Particle.publish("Loop", msg, PRIVATE);
  delay(500);
  Serial.println("main>\tStarting Loop");
  Serial.printlnf("main>\tvalveStatus = %i, moisture = %f, weatherRain = %i", valveStatus, moisture, weatherRain);
  Serial.println("main>\tTaking measurements");
  takeMeasurements();
  Serial.println("main>\tRunning water routine");
  water();
  //if battery %charge dips below 30%, then sleepDuration should be increased to 10mins.
  if(soc <= 30) {
    if(valveStatus == 0) {
      sleepDuration = 900;
    } else {
      //In case the water is on, we don't want to take a chance and run out of battery with the water on. so we turn it off
      turnWaterOff();
      sleepDuration = 300;
    }
  }
  Particle.publish("Loop", "20 seconds delay", PRIVATE);
  digitalWrite(regulator, LOW);
  delay(20000);
  sprintf(msg, "Work done, sleeping for %i seconds", sleepDuration);
  Particle.publish("Loop", msg, PRIVATE);
  System.sleep(SLEEP_MODE_DEEP, sleepDuration);
  //delay(30000);
}

/********************************************/
/*                                          */
/*     Main Functions                       */
/*                                          */
/********************************************/

void setupBQ27441() {
  if(!lipo.begin()) {
    Serial.println("Error: Unable to communicate with BQ27441.");
    Serial.println("\tCheck wiring and try again.");
    Serial.println("\t(Battery must be plugging into Battery Babysitter!)");
    connectedBattery = 0;
    return;
  }
  Serial.println("Connected to BQ27441!");
  lipo.setCapacity(BATTERY_CAPACITY);
  connectedBattery = 1;
}

void takeMeasurements() {
  bool sent;
  Particle.publish("Measurements", "Starting", PRIVATE);
  delay(500);
  Serial.println("takeMeasurements>\tStarting taking measurements");
  getSensorsData();
  getWifiData();
  Particle.publish("openWeather", deviceID, PUBLIC);
  ubidots.add("valve-status", valveStatus);
  Serial.printlnf("takeMeasurements>\tmoisture = %f", moisture);
  sent = sendMeasurements();
  if(sent) {
    Serial.println("main>\tMeasurements successfully sent");
  }
  delay(500);
  getBatteryData();
  Serial.println("main>\tSending measurements");
  sent = sendMeasurements();
  if(sent) {
    Serial.println("main>\tMeasurements successfully sent");
  }
  Particle.publish("Measurements", "Done", PRIVATE);
  delay(500);
  Serial.println("takeMeasurements>\tDone taking measurements");
  return;
}

bool sendMeasurements() {
  //Serial.println("sendMeasurements>\tConnecting to Wifi to send data and get weather data");
  //Particle.connect();
  bool bufferSent = ubidots.send("Ubidots", PUBLIC);
  if(bufferSent) {
    Serial.println("sendMeasurements>\tMeasurements sent to Ubidots");
  }
  //Particle.disconnect();
  //Serial.println("sendMeasurements>\tDisconnecting from Wifi");
  return(true);
}

void getBatteryData() {
  Serial.println("getBatteryData>\tGetting battery data");
  //Particle.publish("Reading Battery data", PRIVATE);
  voltage = lipo.voltage() / 1000.0;
  soc = lipo.soc();
  current = lipo.current();
  fullCapacity = lipo.capacity(FULL);
  capacity = lipo.capacity(REMAIN);
  power = lipo.power();
  health = lipo.soh();
  Serial.printlnf("getBatteryData>\tvoltage = %f, current = %d, capacity = %i, charge = %i, health = %i", voltage, current, capacity, soc, health);
  ubidots.add("battery-voltage", voltage);
  ubidots.add("battery-current", current);
  ubidots.add("battery-capacity", capacity);
  ubidots.add("battery-charge", soc);
  ubidots.add("battery-health", health);
  Serial.println("getBatteryData>\tGetting battery data done");
}

void getWifiData() {
  Serial.println("getWifiData>\Getting Wifi data");
  //Particle.publish("Reading Wifi data", PRIVATE);
  int wifiRSSI = WiFi.RSSI();
  if(wifiRSSI > 0) {
    Serial.printlnf("Error getting WIFI Signal, RSSI = %i", wifiRSSI);
    return;
  } else {
    ubidots.add("wifi-signal", wifiRSSI);
  }
  Serial.printlnf("getWifiData>\twifiSignal = %i", wifiRSSI);
  return;
}

double readSensor(int sensor) {
  Serial.printlnf("readSensor: Reading sensor data for sensor #%i", sensor);
  //Particle.publish("Reading Sensor data", PRIVATE);
  double value;
  switch (sensor) {
    case 0:
      digitalWrite(tr0, HIGH);
      delay(1200);
      value = analogRead(sensor0);
      break;
    case 1:
      digitalWrite(tr1, HIGH);
      delay(1200);
      value = analogRead(sensor1);
      break;
    case 2:
      digitalWrite(tr2, HIGH);
      delay(1200);
      value = analogRead(sensor2);
      break;
    case 3:
      digitalWrite(tr3, HIGH);
      delay(1200);
      value = analogRead(sensor3);
      break;
  }
  return value;
}

void getSensorsData() {
  int i;
  moisture = 100;
  double value = 0;
  double vwc = 0;
  Serial.println("getSensorData>\tGetting sensor data");
  for(i = 0; i < numSensors; i++) {
    value = (3.3 * readSensor(i)) / 4095;
    vwc = convertVoltageToVWC(value);
    Serial.printlnf("getSensorData>\tvalue = %f, vwc = %f", value, vwc);
    switch(i) {
      case 0:
        ubidots.add("sensor-0", value);
        ubidots.add("sensor-0-vwc", vwc);
        break;
      case 1:
        ubidots.add("sensor-1", value);
        ubidots.add("sensor-1-vwc", vwc);
        break;
      case 2:
        ubidots.add("sensor-2", value);
        ubidots.add("sensor-2-vwc", vwc);
        break;
      case 3:
        ubidots.add("sensor-3", value);
        ubidots.add("sensor-3-vwc", vwc);
        break;
    }
    if(vwc > 15) { //removing noise from unpopulated sensors
      if(vwc < moisture) {
        moisture = vwc;
      }
    }
  }
  sprintf(msg, "moisture = %f", moisture);
  Particle.publish("getSensorsData", msg, PRIVATE);
  Serial.printlnf("getSensorsData>\tAfter checking sensors, moisture = %f", moisture);
}



void water() {
  //if the valve is open, we need to check the soil moisture,
  //if the soil moisture is moist or wet, we close the valve
  //if it is still dry, then we do nothing and keep watering
  Serial.printlnf("water>\tvalveStatus = %i", valveStatus);
  if(valveStatus == 1) {
    Serial.println("water>\tWater is on!!");
    Serial.printlnf("water>\tmoisture = %f", moisture);
    if(moisture >= MOIST) {
      sprintf(msg, "Water is on and moisture = %f, turning water off", moisture);
      Particle.publish("Water", msg, PRIVATE);
      Serial.println("water>\tSoil is at least moist, turning water off");
      turnWaterOff();
    } else {
      sprintf(msg, "Water is on and moisture = %f, keep watering", moisture);
      Particle.publish("Water", msg, PRIVATE);
      Serial.println("water>\tSoil is still dry, keep watering");
    }
  } else {
    //if the valve is closed, we need to check the time of the day it is
    //if it is between 4am and 10am, then we need to check the moisture of the soil
    //if the soil moisture is very dry, we water no matter the weather
    //if the soil is dry, and there is no rain, then we start watering
    //if the soil is dry and there is rain in the forecast, then we do nothing
    Serial.println("water>\tWater is off, checking time of day");
    int currentHour = Time.hour();
    Serial.printlnf("water>\thour of the day is %i", currentHour);
    if(currentHour >= STARTHOUR && currentHour <= ENDHOUR) {
      Serial.println("water>\tWithin the watering period");
      if(moisture <= VERYDRY) {
        sprintf(msg, "Water is off but moisture = %f, turning water on", moisture);
        Particle.publish("Water", msg, PRIVATE);
        Serial.println("water>\tSoil is very dry, turning water on");
        turnWaterOn();
      } else if(moisture > VERYDRY && moisture < DRY) {
        Serial.println("water>\tSoil is dry, checking weather forecast");
        if(weatherRain == 0) {
          sprintf(msg, "Water is off but moisture = %f and weatherRain = %i, turning water on", moisture, weatherRain);
          Particle.publish("Water", msg, PRIVATE);
          Serial.printlnf("water>\tNo rain in the forecast, turning water on");
          turnWaterOn();
        } else {
          sprintf(msg, "Water is off but moisture = %f and weatherRain = %i, not turning water on", moisture, weatherRain);
          Particle.publish("Water", msg, PRIVATE);
          Serial.println("water>\tRain in the forecast, nothing to do");
        }
      } else {
        sprintf(msg, "Water is off but moisture = %f and weatherRain = %i, nothing to do", moisture, weatherRain);
        Particle.publish("Water", msg, PRIVATE);
        Serial.println("water>\tSoil is moist, nothing to do");
      }
    }
  }
}

/********************************************/
/*                                          */
/*      Water Control Functions             */
/*                                          */
/********************************************/

void turnWaterOn() {
  sprintf(msg, "Turning water on, moisture = %f", moisture, weatherRain);
  Particle.publish("Watering", msg, PRIVATE);
  Serial.println("TurnWaterOn\tOpening water valve");
  digitalWrite(regulator, HIGH);
  digitalWrite(enablePin, HIGH);
  analogWrite(valve1Pin, 255);
  analogWrite(valve2Pin, 0);
  delay(50);
  digitalWrite(enablePin, LOW);
  Serial.printlnf("turnWaterOn>\tvalveStatus = %i, setting it to 1", valveStatus);
  valveStatus = 1;
  EEPROM.put(valveStatusAddr, valveStatus);
  ubidots.add("valve-status", valveStatus);
  Serial.printlnf("turnWaterOn>\tvalveStatus = %i" ,valveStatus);
}

void turnWaterOff() {
  sprintf(msg, "Turning water off, moisture = %f", moisture, weatherRain);
  Particle.publish("Watering", msg, PRIVATE);
  Serial.println("TurnWaterOff`\tClosing water valve");
  digitalWrite(regulator, HIGH);
  digitalWrite(enablePin, HIGH);
  analogWrite(valve1Pin, 0);
  analogWrite(valve2Pin, 255);
  delay(50);
  digitalWrite(enablePin, LOW);
  Serial.printlnf("turnWaterOff>\tvalveStatus = %i, setting it to 0", valveStatus);
  valveStatus = 0;
  EEPROM.put(valveStatusAddr, valveStatus);
  ubidots.add("valve-status", valveStatus);
  Serial.printlnf("turnWaterOff>\tvalveStatus = %i" ,valveStatus);
}


/********************************************/
/*                                          */
/*      Webhook Handler Functions           */
/*                                          */
/********************************************/


void ubidotsHandler(const char *event, const char *data) {
  //Serial.printlnf("ubidotsHandler: data = %s", data);
  if(!data) {
    Particle.publish("ubidotsResp", "No data");
    return;
  }
  int respCode = atoi(data);
  if((respCode == 200 || respCode == 201)) {
    Particle.publish("ubidotsHook", "Success");
  } else {
    Particle.publish("ubidotsHook", data);
  }
}

void weatherHandler(const char *event, const char *data) {
  int i;
  Serial.println("weatherHandler: Starting weather data");
  Serial.printlnf("weatherHandler: data = %s", data);
  if(!data) {
    Particle.publish("weatherResp", "No data");
    return;
  }
  int weatherDecision = 0;
  int weather[WEATHERCOUNT];
  int index = 0;
  char dataCopy[strlen(data) + 1];
  strcpy(dataCopy, data);
  char *ptr = strtok(dataCopy, "~");
  while(ptr != NULL) {
    if(index < WEATHERCOUNT) {
      weather[index] = atoi(ptr);
      index ++;
      ptr = strtok(NULL, "~");
    }
  }
  for(i = 0; i < WEATHERCOUNT; i++) {
    if(weather[i] >= 200 && weather[i] <= 299) {
      weatherDecision ++;
    } else {
      if(weather[i] >= 300 && weather[i] <= 399) {
        weatherDecision --;
      } else {
        if(weather[i] >= 500 && weather[i] <= 599) {
          weatherDecision --;
        } else {
          if(weather[i] >= 600 && weather[i] <= 699) {
            weatherDecision --;
          } else {
            weatherDecision ++;
          }
        }
      }
    }
  }
  //Serial.printlnf("weatherHandler: weatherDecision = %i", weatherDecision);
  sprintf(msg, "weatherDecision = %i", weatherDecision);
  Particle.publish("weatherHandler: weatherDecision", msg, PRIVATE);
  if(weatherDecision < 6) {
    weatherRain = 1;
  } else {
    weatherRain = 0;
  }
}

/********************************************/
/*                                          */
/*      Various  Functions                  */
/*                                          */
/********************************************/

float convertVoltageToVWC(float voltage) {
  if(voltage >= 2.2 && voltage <= 3.0) {
    return((voltage * 62.5) - 87.5);
  }
  if(voltage >= 1.82 && voltage < 2.2) {
    return((voltage * 26.32) - 7.89);
  }
  if(voltage >= 1.3 && voltage < 1.82) {
    return((voltage * 48.08) - 47.5);
  }
  if(voltage >= 1.1 && voltage < 1.3) {
    return((voltage * 25.0) - 17.5);
  }
  if(voltage >= 0.0 && voltage < 1.1) {
    return((voltage * 10.0) - 1.0);
  }
  return(-1);
}

int setMoisture(String command) {
  moisture = atoi(command);
  EEPROM.put(moistureAddr, moisture);
  return moisture;
}

int setWeatherRain(String command) {
  weatherRain = atoi(command);
  EEPROM.put(weatherRainAddr, weatherRain);
  return weatherRain;
}

int setValveStatus(String command) {
  valveStatus = atoi(command);
  EEPROM.put(valveStatusAddr, valveStatus);
  return valveStatus;
}

int setForceWatering(String command) {
  forceWatering = atoi(command);
  return forceWatering;
}

int setSleepDuration(String command) {
  sleepDuration = atoi(command);
  return sleepDuration;
}

int onDemandWatering(String command) {
  if(command == "on") {
    turnWaterOn();
    return 1;
  } else if(command == "off") {
    turnWaterOff();
    return 0;
  }
}

As you can see, the code execution for the most part is pretty constant, but I guess that the Vegetronix Soil Moisture sensors that I am using might be different amount of power each time I do a reading, hence again the variation each time. All of this makes sense to me, but makes it really difficult to estimate what battery capacity is needed. Looking at the events console, it takes about 40 seconds for the entire code to be executed. If you wonder why the couple of delay in the main loop, it is to give me a chance to be able to flash the firmware when I need to, otherwise, the code executes too quickly. :smiley: I hope this makes sense.

Thanks again for the help.
B.

Just some notes on the code:

  • you already are using EEPROM, so why would you hardcode numSensors depending on the device ID? Wouldn't that be better stored in EEPROM and set once via a Particle.function()?
  • are you typically requesting the Particle.variables() individually or in a burst? If the latter, you could place all your readings in one string variable which would safe calls.
  • to allow for reflashing the devices you could add some other means than unconditional long delay() call
  • to get less of the insignificant decimal places with your float values in sprintf() try %.2f as format place holder
  • to save power you could get rid of some publishes and only upate cloud values when they have significantly changed (plus less frequent regular updates). This way you could keep WiFi off unless needed.
  • you can also collect all your data and only after that publish from one place packing as much info into the publish as possible. This would help getting rid of many of these delay() calls and keep wake time shorter.
  • instead of sleeping for a fixed amount of time I'd rather sleep till some time. This ensures a more predictable wake time for the devices (e.g. every full 5, 10, 15 minutes)
  • in readSensor() I'd also add a "read-all" case which would cut the delay time by four (again keeping the wake time shorter) - especially since you are "always" reding all in the same for() loop (this might require you to return all four results at once tho' - e.g. via a by-reference parameter to place the results in an array)
  • If you use else if instead of else { if you can keep the indentation level (e.g. in weatherHandler() less deep - although I'm not quite sure why you have so many conditions but only two possible actions (++ vs. --) this could be doen in a single combined if statement or (my preference) mathematically.
  • this can be reduced into a one-liner

like this

  weatherRain = (weatherDecision < 6); // true == 1; false == 0