I don’t see the log info, as I don’t have a serial connection that would be charging the battery and put it above the threshold. Is there a means to see the log.info()
when I don’ have a serial connection?
I call qualify_battery_and_hibernate()
from my procedure that’s called periodically from loop()
. Essentially, every minute I check to see if any inputs have changed sufficiently. If they have, I pack up the readings and send the changed ones to Ubidots. Every 15 minutes, I send all readings to Ubidots. It’s after the Ubidots send that I call qualify_battery_and_hibernate()
. It only does anything if there’s no incoming power and the battery voltage is sufficiently low.
As I said above, I do see the PHASE_1 publish in the console just prior to SOS
Here’s the entire app please don’t chuckle too much! 
//HVAC StarPlus
//StarGazer - Electron - sending analog (temperature) and digital (dry contact closure) inputs to Ubidots
//20191026 - Tom Morrison
// - modified:
//
//
#define dReadPeriod 10000 //read inputs every 10 seconds by default
#define dPublishPeriod 60000 //publish to Ubidots every 60 seconds by default
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define dMaxDelta 0.5 //number of tenths of a degree that forces an Ubidots update
#include "thermistorLU.h"
#include "SparkJson.h"
#include "string.h"
#include "math.h"
#include "Particle.h"
#include "Ubidots.h"
SerialLogHandler logHandler;
const char* WEBHOOK_NAME = "ubidots";
Ubidots ubidots("webhook", UBI_PARTICLE);
//used for timing loops
unsigned long gNow = 0;
double gMaxDelta = dMaxDelta;
char gPublishString[200];
char gDeviceID[25];
char gS[100];
char gT[200];
char gB[50];
char gSlastSent[100];
unsigned long gLastPublish = 0; //timestamp of last push to Grovestreams
unsigned long gLastRead = 0; //timestamp of last input sensor reading
int gPublishPeriod = dPublishPeriod;
char gDeviceInfo [300] = "~";
bool gHasCredentials = false;
bool gSendValues = true; // indicates if values should be sent to Grovestreams
char gFred[200]; //used for debugging
////////////Battery declarations
#define PHASE1_BATTERY_LEVEL 3.8 //~40% capacity
#define PHASE2_BATTERY_LEVEL 3.7 //~10% capacity
//used to estimate amount of battery charge left
double gBattV;
double gBattVLastSent=0;
char* lBattV = "battV";
////////////Battery declarations
SystemSleepConfiguration config; //used to define sleep parameters
//The following are labels for the parameters sent to Ubidots
char* lT1 = "T1";char* lT2 = "T2";char* lT3 = "T3";char* lT4 = "T4";char* lT5 = "T5";char* lT6 = "T6";
char* lS1 = "S1";char* lS2 = "S2";char* lS3 = "S3";char* lS4 = "S4";char* lS5 = "S5";char* lS6 = "S6";char* lS7 = "S7";char* lS8 = "S8";
//define input analog and digital ports
//the numbering is the same as on the StarGazer PCB (1-12)
thermistorLU gT1(A5);
thermistorLU gT2(A4);
thermistorLU gT3(A3);
thermistorLU gT4(A2);
thermistorLU gT5(A1);
thermistorLU gT6(A0);
int S1 = D0;
int S2 = D1;
int S3 = D2;
int S4 = D3;
int S5 = D4;
int S6 = D5;
int S7 = D6;
int S8 = D8;
int led7 = D7;
//used to hold the most recent digital inputs
int gS1, gS2, gS3, gS4, gS5, gS6, gS7, gS8;
//used to hold the most recent reading instead of redeclaring in the read function or forcing excessive AI reads
double gT1f, gT2f, gT3f, gT4f, gT5f, gT6f;
//last sent values intialized to force a send on the first loop
double gT1LastSent=0, gT2LastSent=0, gT3LastSent=0, gT4LastSent=0, gT5LastSent=0, gT6LastSent=0; //last T value sent to Grovestreams
int gS1LastSent=-1, gS2LastSent=-1, gS3LastSent=-1, gS4LastSent=-1, gS5LastSent=-1, gS6LastSent=-1, gS7LastSent=-1, gS8LastSent=-1;//last S value sent to Ubidots
void printMAC();
void setup() {
delay(100);//brief pause to let serial interface start
//The following lines are to ensure the system clock is set accurately
printMAC();
Log.info("we're in setup");
Log.info("we're trying to sync the device's time");
while(Time.year() <= 1970) Particle.process();
Log.info("Time is sync'd");
Particle.variable("gPubPeriod",gPublishPeriod);
Particle.variable("gS",gS);
Particle.variable("gT",gT);
Particle.variable("Battery",gBattV);
Particle.function("pReset",pReset);
Particle.function("setPeriod",setPeriod);
Particle.subscribe("particle/device/name",updateDeviceInfo);
//Publishes info about the device
Particle.variable("deviceInfo",gDeviceInfo);
//
pinMode(led7, OUTPUT);
for(int i=0;i<5;i++) {
Log.info("waiting " + String(5-i) + " seconds before we publish");
digitalWrite(led7, HIGH);
delay(100);
digitalWrite(led7, LOW);
delay(100);
}
pinMode(S1,INPUT);
pinMode(S2,INPUT);
pinMode(S3,INPUT);
pinMode(S4,INPUT);
pinMode(S5,INPUT);
pinMode(S6,INPUT);
pinMode(S7,INPUT);
pinMode(S8,INPUT);
gLastRead = (60-Time.second())*1000;//need to contemplate this to get readings on the minute
}
void loop() {
//The loop involves periodically reading the sensors to update published variables
//and periodically pushing updates to Grovestreams. These are two different periods
gNow = millis();
//check to see if inputs should be read
if (gNow < gLastRead) { //handle roll over
gLastRead = 0;
}
if ((gNow - gLastRead) >= dReadPeriod){
gLastRead = gNow;
//Particle.publish("read the damn sensors");
readSensors();
if (gDeviceInfo[0]=='~') {
Particle.publish("particle/device/name",PRIVATE);
}
}
//check to see if readings should be sent to Grovestreams
if (gNow < gLastPublish) { //handle roll over
gLastPublish = 0;
}
if ((gNow - gLastPublish) >= gPublishPeriod) {
sendToUbidots();
}
}
void updateDeviceInfo(const char *topic,const char *data) {
char deviceName [40] = "";
byte mac[6];
WiFi.macAddress(mac);
strncpy(deviceName,data,sizeof(deviceName)-1);
snprintf(gDeviceInfo, sizeof(gDeviceInfo)
,"Device: %s, Application: %s, Date: %s, Time: %s, System firmware: %s, SSID: %s,mac: %02x:%02x:%02x:%02x:%02x:%02x"
,(const char*)deviceName
,__FILENAME__
,__DATE__
,__TIME__
,(const char*)System.version() // cast required for String
,(const char*)WiFi.SSID()
,mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]
);
Log.info("mac: %02x:%02x:%02x:%02x:%02x:%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
}
void printMAC() {
byte mac[6];
WiFi.macAddress(mac);
Log.info("mac: %02x:%02x:%02x:%02x:%02x:%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
}
void readSensors() {
gLastRead = gNow;
//read input sensors and store in global variables
gT1f = gT1.temperature();
gT2f = gT2.temperature();
gT3f = gT3.temperature();
gT4f = gT4.temperature();
gT5f = gT5.temperature();
gT6f = gT6.temperature();
snprintf(gT, sizeof(gT),"%4.2f,%4.2f,%4.2f,%4.2f,%4.2f,%4.2f",gT1f,gT2f,gT3f,gT4f,gT5f,gT6f);
Log.info("temperatures: %s",gT);
//read the digital inputs and format output string
gS1 = digitalFilter(S1);
gS3 = digitalFilter(S3);
gS2 = digitalFilter(S2);
gS4 = digitalFilter(S4);
gS5 = digitalFilter(S5);
gS6 = digitalFilter(S6);
gS7 = digitalFilter(S7);
gS8 = digitalFilter(S8);
snprintf(gS,sizeof(gS),"%u%u%u%u%u%u%u%u",gS1,gS2,gS3,gS4,gS5,gS6,gS7,gS8);
Log.info("switch inputs: %s",gS);
//gBatt = gBattVoltage;
gBattV = analogRead(BATT) * 0.0011224; //this is uniquely how we read battery voltage on an Argon
Log.info("Battery voltage: %2.1f",gBattV);
///Battery logic
}
void sendToUbidots() {
//send readings to Ubidots if they have changed sufficiently
gLastPublish = gNow; //set timestamp
if (Time.second()!=0){ //align the time sent with even minutes as much as possible
gLastPublish = gLastPublish - Time.second()*1000;
}
if (Time.minute() % 15 == 0) {//we'll send all values every at hh:00, hh:15, hh: 30 and hh:45
gS1LastSent=gS2LastSent=gS3LastSent=gS4LastSent=gS5LastSent=gS6LastSent=gS7LastSent=gS8LastSent=-1;
gT1LastSent=gT2LastSent=gT3LastSent=gT4LastSent=gT5LastSent=gT6LastSent=0;
gBattVLastSent = -1;
}
gSendValues = false;
//Let's check and send any digital inputs that have changed in group 1
if (gS1 != gS1LastSent) {gS1LastSent = gS1; ubidots.add(lS1,gS1); gSendValues = true;}
if (gS2 != gS2LastSent) {gS2LastSent = gS2; ubidots.add(lS2,gS2); gSendValues = true;}
if (gS3 != gS3LastSent) {gS3LastSent = gS3; ubidots.add(lS3,gS3); gSendValues = true;}
if (gS4 != gS4LastSent) {gS4LastSent = gS4; ubidots.add(lS4,gS4); gSendValues = true;}
if (gS5 != gS5LastSent) {gS5LastSent = gS5; ubidots.add(lS5,gS5); gSendValues = true;}
if (gS6 != gS6LastSent) {gS6LastSent = gS6; ubidots.add(lS6,gS6); gSendValues = true;}
if (gS7 != gS7LastSent) {gS7LastSent = gS7; ubidots.add(lS7,gS7); gSendValues = true;}
if (gS8 != gS8LastSent) {gS8LastSent = gS8; ubidots.add(lS8,gS8); gSendValues = true;}
if (abs(gT1f-gT1LastSent)>gMaxDelta) {gT1LastSent = gT1f; ubidots.add(lT1,gT1f); gSendValues = true;}
if (abs(gT2f-gT2LastSent)>gMaxDelta) {gT2LastSent = gT2f; ubidots.add(lT2,gT2f); gSendValues = true;}
if (abs(gT3f-gT3LastSent)>gMaxDelta) {gT3LastSent = gT3f; ubidots.add(lT3,gT3f); gSendValues = true;}
if (abs(gT4f-gT4LastSent)>gMaxDelta) {gT4LastSent = gT4f; ubidots.add(lT4,gT4f); gSendValues = true;}
if (abs(gT5f-gT5LastSent)>gMaxDelta) {gT5LastSent = gT5f; ubidots.add(lT5,gT5f); gSendValues = true;}
if (abs(gT6f-gT6LastSent)>gMaxDelta) {gT6LastSent = gT6f; ubidots.add(lT6,gT6f); gSendValues = true;}
if (abs(gBattV-gBattVLastSent)>gMaxDelta) {gBattVLastSent = gBattV; ubidots.add(lBattV,gBattV); gSendValues = true;}
if(gSendValues){
bool bufferSent = false;
bufferSent = ubidots.send(WEBHOOK_NAME,PUBLIC); //will use Particle webhook to send
if (bufferSent) {
Log.info("Values sent to Ubidots successfully");
}
}
qualify_battery_and_hibernate();
}
int digitalFilter(int port) {
//an input needs to be on for 3 successive checks every 10 msec
int holdInput;
holdInput = digitalRead(port);
delay(10);
holdInput += digitalRead(port);
delay(10);
holdInput += digitalRead(port);
if (holdInput==3){
return 1;
}
else {
return 0;
}
}
int pReset(String command) {
//function to call to remotely reset device
System.reset();
return 1;
}
int setPeriod(String command) {
//change the Grovestreams update period
Log.info("Setting publish period");
int _command;
_command = command.toInt();
if (_command>=60 && _command<=900) {//in seconds - between 1 and 15 minutes)
gPublishPeriod = _command * 1000;
return 1;
}
else {
return -1;
}
}
/*
* checks to see if there's incoming power on VIN
* @return 'true' if there's incoming power or 'false' if there is not
* the assumption is that the power sensing relay has been installed and connected to S8!
*/
bool incoming_power() {
return (gS8==1 ? 1 : 0);
}
/*
* @param capacity The value to compare current battery to.
* @return If battery is lower than `capacity`, return `true`.
*/
bool battery_lower_than(float volts)
{
return (gBattV < volts) ? 1 : 0;
}
void qualify_battery_and_hibernate() {
//Let's make sure any cloud communications are completed
for (int i=0;i<5;i++) {
Particle.process();
delay(1000);
}
//PHASE 2 TEST
if (!incoming_power() && battery_lower_than(PHASE2_BATTERY_LEVEL)) {
Particle.publish("PHASE_2",PRIVATE);
delay(1000);//wait for the publish to complete
Log.info("No incoming power, but sufficient battery -- Phase 1 sleep");
//System.sleep(SLEEP_MODE_DEEP, PHASE1_SLEEP_DURATION, SLEEP_DISABLE_WKP_PIN);
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
.duration(8* 60 * 60 * 1000) //8 hours * 60min/hr * 60sec/min * 1000 millis/sec
.flag(SystemSleepFlag::WAIT_CLOUD);
System.sleep(config);
}
//PHASE 1 TEST
else if (!incoming_power() && battery_lower_than(PHASE1_BATTERY_LEVEL)) {
Particle.publish("PHASE_1",PRIVATE);
delay(1000);//wait for the publish to complete
Log.info("No incoming power, but sufficient battery -- Phase 1 sleep");
//System.sleep(SLEEP_MODE_DEEP, PHASE1_SLEEP_DURATION, SLEEP_DISABLE_WKP_PIN);
config.mode(SystemSleepMode::ULTRA_LOW_POWER)
.duration(2* 60 * 60 * 1000) //2 hours * 60min/hr * 60sec/min * 1000 millis/sec
.flag(SystemSleepFlag::WAIT_CLOUD);
System.sleep(config);
}
}