@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.
I hope this makes sense.
Thanks again for the help.
B.