// temporarily - this triggers warning and shutdown - and eventual reboot
// - Added optional, programmable warning beeper (Piezo)
// - Added serial JSON (optional) monitor
+// - Watchdog timeout from 1 minute through 255 minutes - change of state pin WATCHDOG
// In normal operation shows total on time and total off time
// Use 328-based board similar regulator (pref low loss) and no USB chip. If possible
// Blog has more - as does this video https://www.youtube.com/watch?v=44Lvdf7o4GQ
// Now set up for the 32-pixel displays - got everything down to 4 lines
- uint8_t warningTimeout;
+ uint8_t warningTimeout;
// Device address for the SSD1306 display
#define POWER 12 // inverted - ON - power available for my cheap version
#define WARNING 5 // inverted
#define TONE 13 // optional piezo beeper on warning
+#define WATCHDOG 11 // watchdog in
uint16_t longpressedUp = 0 ;
uint16_t longpressedDown = 0;
-uint8_t triggerShutdown =0;
+uint8_t triggerShutdown = 0;
+uint8_t watchdogTrigger = 0;
+uint8_t watchdogTimer = 0;
+uint8_t watchdogState = 0;
+uint8_t secondsCounter = 0;
void LcdP(char *fmt, ...)
- char buf[128]; // resulting string limited to 128 chars
- vsnprintf(buf, 128, fmt, args);
+ char buf[128]; // resulting string limited to 128 chars
+ vsnprintf(buf, 128, fmt, args);
- char buf[128]; // resulting string limited to 128 chars
- vsnprintf(buf, 128, fmt, args);
+ char buf[128]; // resulting string limited to 128 chars
+ vsnprintf(buf, 128, fmt, args);
void gotoXY(int x, int y)
//------------------------------------------------------------------------------
- analogReference(INTERNAL);
- if (eeSave.check != 0x52d3)
- eeSave.highV = SWITCHON;
- eeSave.lowV = SWITCHOFF;
- eeSave.startTimeout = 5;
- eeSave.goodTimeout = 5;
- eeSave.warningTimeout = 25;
- eeSave.offTimeout = 20;
- eeSave.batteryOffset=400;
- oled.begin(&Adafruit128x32, I2C_ADDRESS);
- oled.setFont(Adafruit5x7);
- Serial.begin(115200); // serial JSON data out if set
- #define WARNINGOFF HIGH
- digitalWrite(POWER, POWEROFF); pinMode(POWER, OUTPUT);
- digitalWrite(WARNING, WARNINGOFF); pinMode(WARNING, OUTPUT);
- digitalWrite(TONE,0); pinMode(TONE,OUTPUT);
- pinMode(SET, INPUT_PULLUP);
- pinMode(UP, INPUT_PULLUP);
- pinMode(DOWN, INPUT_PULLUP);
- average = analogRead(VOLTAGE); // we start off with read value;
- my100millis = millis() + 1000;
- stateCounter = eeSave.startTimeout;
+ analogReference(INTERNAL);
+ if (eeSave.check != 0x52d5)
+ eeSave.highV = SWITCHON;
+ eeSave.lowV = SWITCHOFF;
+ eeSave.startTimeout = 5;
+ eeSave.goodTimeout = 5;
+ eeSave.warningTimeout = 25;
+ eeSave.offTimeout = 20;
+ eeSave.batteryOffset = 400;
+ oled.begin(&Adafruit128x32, I2C_ADDRESS);
+ oled.setFont(Adafruit5x7);
+ Serial.begin(115200); // serial JSON data out if set
+ digitalWrite(POWER, POWEROFF); pinMode(POWER, OUTPUT);
+ digitalWrite(WARNING, WARNINGOFF); pinMode(WARNING, OUTPUT);
+ digitalWrite(TONE, 0); pinMode(TONE, OUTPUT);
+ pinMode(SET, INPUT_PULLUP);
+ pinMode(UP, INPUT_PULLUP);
+ pinMode(DOWN, INPUT_PULLUP);
+ pinMode(WATCHDOG, INPUT_PULLUP);
+ average = analogRead(VOLTAGE); // we start off with read value;
+ my100millis = millis() + 1000;
+ stateCounter = eeSave.startTimeout;
+ watchdogState = digitalRead(WATCHDOG);
-// Float to ASCII conversion
-char *ftoa(char *a, double f, int precision)
+ // Float to ASCII conversion
+ char *ftoa(char *a, double f, int precision)
long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};
long desimal = abs((long)((f - heiltal) * p[precision]));
- LcdP("%d.%02dv", int(vol), ((int(vol * 100)) % 100)); oled.clearToEOL();
+ LcdP("%d.%02dv", int(vol), ((int(vol * 100)) % 100)); oled.clearToEOL();
//------------------------------------------------------------------------------
- if (my100millis <= millis()) // 100ms for key check
- if (digitalRead(SET) == 0)
- if (digitalRead(UP) == 0) // special case long press is reset...
- case 1 : if (eeSave.highV < 4.3) eeSave.highV += 0.1; break;
- case 2 : if (eeSave.lowV < 4.2) eeSave.lowV += 0.1; break;
- case 3 : eeSave.startTimeout++; break;
- case 4 : eeSave.goodTimeout++; break;
- case 5 : eeSave.warningTimeout++; break;
- case 6 : eeSave.offTimeout++; break;
- case 7 : eeSave.batteryOffset++; break;
- case 8 : if (eeSave.tone<5000) eeSave.tone+=500; else eeSave.tone=0; break;
- case 9 : eeSave.smon=1; break;
- if (++longpressedUp==LONGPRESS_UP) triggerShutdown=1;
- { uKeypressed = 0; longpressedUp = 0; }
- if (digitalRead(DOWN) == 0)
- case 1 : if (eeSave.highV > 2.3) eeSave.highV -= 0.1; break;
- case 2 : if (eeSave.lowV > 2.2) eeSave.lowV -= 0.1; break;
- case 3 : eeSave.startTimeout--; break;
- case 4 : eeSave.goodTimeout--; break;
- case 5 : eeSave.warningTimeout--; break;
- case 6 : eeSave.offTimeout--; break;
- case 7 : eeSave.batteryOffset--; break;
- case 8 : if (eeSave.tone>=500) eeSave.tone-=500; break;
- case 9 : eeSave.smon=0; break;
- if (++longpressedDown==LONGPRESS_DOWN) average=0; // temporarily drop the voltage and hence trigger warning
- { dKeypressed = 0; longpressedDown = 0; }
- if (eeSave.highV <= eeSave.lowV) eeSave.highV = eeSave.lowV + 0.1;
- if (eeSave.lowV >= eeSave.highV) eeSave.lowV = eeSave.highV - 0.1;
- case 0: if (updated == 0)
- case 1: if (updated == 0)
- LcdP("SET: High V=%d.%d ", int(eeSave.highV), ((int(eeSave.highV * 100)) % 100) / 10); oled.clearToEOL();
- case 2: if (updated == 0)
- LcdP("SET: Low V=%d.%d ", int(eeSave.lowV), ((int(eeSave.lowV * 100)) % 100) / 10); oled.clearToEOL();
- case 3: if (updated == 0)
- LcdP("SET: Start %d secs",eeSave.startTimeout); oled.clearToEOL();
- case 4: if (updated == 0)
- LcdP("SET: Good %d secs",eeSave.goodTimeout); oled.clearToEOL();
- case 5: if (updated == 0)
- LcdP("SET: Warn %d secs",eeSave.warningTimeout); oled.clearToEOL();
- case 6: if (updated == 0)
- LcdP("SET: Off %d secs",eeSave.offTimeout); oled.clearToEOL();
- case 7: if (updated == 0)
- LcdP("SET: Battery %d",eeSave.batteryOffset-400); oled.clearToEOL();
- case 8: if (updated == 0)
- LcdP("Warning tone: %d",eeSave.tone); oled.clearToEOL();
- case 9: if (updated == 0)
- LcdP("Serial monitor: %d",eeSave.smon); oled.clearToEOL();
- case 10: if (updated == 0)
- LcdP("Version: %s",VER); oled.clearToEOL();
- case 11: updated = 0; instate = 0; break;
- if (--updateTimeout == 0)
- // main 1 second loop here for determining where we are
- if (mymillis <= millis())
- mymillis += 1000; // every second we do this
- average = ((average * 7) + analogRead(VOLTAGE)) / 8; //i.e. average over 8 seconds 0-1023 = 0-5v - might make testing longer in the end
- vol = (((float)average) * 5.0 / 824.0) * (float)eeSave.batteryOffset/400.0; //10k/2k2
- if (triggerShutdown) { triggerShutdown=0; state=SHUTDOWN_STATE; stateCounter=SHUTDOWN_PERIOD; }
- ftoa(stringStore,vol,2);
- sprintf(bString,"{\"state\": %d,\"battery\": %s,\"upsecs\":%ld,\"downsecs\":%ld}",state,stringStore,upSecs,downSecs);
- if (eeSave.smon) Serial.println(bString);
- case POWERUP_STATE : // powered up everything off
- if (stateCounter == eeSave.startTimeout) { gotoXY(0, 0); LcdP("Starting up"); oled.clearToEOL(); }
- gotoXY(0, 2); LcdP("Battery: "); showBattery();
- gotoXY(0, 3); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
- digitalWrite(WARNING, WARNINGON); // no warning
- digitalWrite(POWER, POWEROFF);
- if (vol > eeSave.highV)
- if (stateCounter == 0) {
- stateCounter = eeSave.goodTimeout;
- stateCounter = eeSave.offTimeout;
- case GOOD_STATE : // battery is above upper threshold
- if (stateCounter == eeSave.goodTimeout) { gotoXY(0, 0); LcdP("** Pi Power %s **",VER); oled.clearToEOL(); }
- gotoXY(0, 1); LcdP("Ok. Battery: "); showBattery();
- gotoXY(0, 2); LcdP("High: %d.%dv Low: %d.%dv", int(eeSave.highV), ((int(eeSave.highV * 100)) % 100) / 10, int(eeSave.lowV), ((int(eeSave.lowV * 100)) % 100) / 10); oled.clearToEOL();
- digitalWrite(WARNING, WARNINGOFF); // no warning
- digitalWrite(POWER, POWERON);
- gotoXY(0, 3); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
- if (stateCounter == 0) {
- stateCounter = eeSave.warningTimeout;
- stateCounter = eeSave.goodTimeout-1;
- if ((instate==0) && (vol >= eeSave.lowV))
- if (mySecs&2) { LcdP("On: %04ldd %02ld:%02ld:%02ld", upSecs / 86400, (upSecs / 3600) % 24, (upSecs % 3600) / 60, upSecs % 60); oled.clearToEOL(); }
- else { LcdP("Off: %04ldd %02ld:%02ld:%02ld", downSecs / 86400, (downSecs / 3600) % 24 , (downSecs % 3600) / 60, downSecs % 60); oled.clearToEOL(); }
- case TIMEOUT_STATE : // battery has dropped below upper threshold - ALWAYS timeout to OFF_STATE
- if (stateCounter == eeSave.warningTimeout) oled.clear();
- gotoXY(0, 0); LcdP("Timing out. B: "); showBattery();
- gotoXY(0, 1); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
- digitalWrite(WARNING, WARNINGON); // warning
- digitalWrite(POWER, POWERON);
- if (eeSave.tone) tone(TONE,eeSave.tone,100);
- if (stateCounter == 0 ) {
- stateCounter=eeSave.offTimeout;
- case OFF_STATE : // everything off
- if (stateCounter == eeSave.offTimeout) oled.clear();
- if (vol <= eeSave.highV) { gotoXY(0, 0); LcdP("Stdby. V="); showBattery(); }
- digitalWrite(WARNING, WARNINGON); // LEAVE ON ie LOW
- digitalWrite(POWER, POWEROFF);
- if (vol > eeSave.highV)
- gotoXY(0, 0); LcdP("Warming up. V="); showBattery();
- gotoXY(0, 1); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
- if (stateCounter == 0) {
- stateCounter = eeSave.goodTimeout;
- stateCounter = eeSave.offTimeout-1; // avoid clearscreen
- gotoXY(0, 3); oled.clearToEOL();
- case SHUTDOWN_STATE: // everything off and stays off... hold the SET button.... set state to SHUTDOWN_STATE and stateCounter to SHUTDOWN_PERIOD
- if (stateCounter==SHUTDOWN_PERIOD)
- gotoXY(0, 2); LcdP("Shutdown commencing.."); oled.clearToEOL();
- digitalWrite(WARNING, WARNINGON); // warning
- gotoXY(0, 2); LcdP("Shutdown COMPLETE"); oled.clearToEOL();
- digitalWrite(WARNING, WARNINGON); // LEAVE ON ie LOW
- digitalWrite(POWER, POWEROFF);
- else if (digitalRead(DOWN) == 0) { oled.clear(); state=POWERUP_STATE; stateCounter = eeSave.startTimeout; }
+ if (my100millis <= millis()) // 100ms for key check
+ if ((eeSave.watchdog) && (watchdogTrigger == 0))
+ if (watchdogState != digitalRead(WATCHDOG)) {
+ watchdogTimer = eeSave.watchdog;
+ watchdogState = digitalRead(WATCHDOG);
+ if (digitalRead(SET) == 0)
+ if (digitalRead(UP) == 0) // special case long press is reset...
+ case 1 : if (eeSave.highV < 4.3) eeSave.highV += 0.1; break;
+ case 2 : if (eeSave.lowV < 4.2) eeSave.lowV += 0.1; break;
+ case 3 : eeSave.startTimeout++; break;
+ case 4 : eeSave.goodTimeout++; break;
+ case 5 : eeSave.warningTimeout++; break;
+ case 6 : eeSave.offTimeout++; break;
+ case 7 : eeSave.batteryOffset++; break;
+ case 8 : if (eeSave.tone < 5000) eeSave.tone += 500; else eeSave.tone = 0; break;
+ case 9 : eeSave.smon = 1; break;
+ case 10 : eeSave.watchdog++;
+ watchdogTimer = eeSave.watchdog; watchdogTrigger = 0; break;
+ if (++longpressedUp == LONGPRESS_UP) triggerShutdown = 1;
+ if (digitalRead(DOWN) == 0)
+ case 1 : if (eeSave.highV > 2.3) eeSave.highV -= 0.1; break;
+ case 2 : if (eeSave.lowV > 2.2) eeSave.lowV -= 0.1; break;
+ case 3 : eeSave.startTimeout--; break;
+ case 4 : eeSave.goodTimeout--; break;
+ case 5 : eeSave.warningTimeout--; break;
+ case 6 : eeSave.offTimeout--; break;
+ case 7 : eeSave.batteryOffset--; break;
+ case 8 : if (eeSave.tone >= 500) eeSave.tone -= 500; break;
+ case 9 : eeSave.smon = 0; break;
+ case 10 : if (eeSave.watchdog) eeSave.watchdog--;
+ watchdogTimer = eeSave.watchdog; watchdogTrigger = 0; break;
+ if (++longpressedDown == LONGPRESS_DOWN) average = 0; // temporarily drop the voltage and hence trigger warning
+ if (eeSave.highV <= eeSave.lowV) eeSave.highV = eeSave.lowV + 0.1;
+ if (eeSave.lowV >= eeSave.highV) eeSave.lowV = eeSave.highV - 0.1;
+ case 0: if (updated == 0)
+ case 1: if (updated == 0)
+ LcdP("SET: High V=%d.%d ", int(eeSave.highV), ((int(eeSave.highV * 100)) % 100) / 10); oled.clearToEOL();
+ case 2: if (updated == 0)
+ LcdP("SET: Low V=%d.%d ", int(eeSave.lowV), ((int(eeSave.lowV * 100)) % 100) / 10); oled.clearToEOL();
+ case 3: if (updated == 0)
+ LcdP("SET: Start %d secs", eeSave.startTimeout); oled.clearToEOL();
+ case 4: if (updated == 0)
+ LcdP("SET: Good %d secs", eeSave.goodTimeout); oled.clearToEOL();
+ case 5: if (updated == 0)
+ LcdP("SET: Warn %d secs", eeSave.warningTimeout); oled.clearToEOL();
+ case 6: if (updated == 0)
+ LcdP("SET: Off %d secs", eeSave.offTimeout); oled.clearToEOL();
+ case 7: if (updated == 0)
+ LcdP("SET: Battery %d", eeSave.batteryOffset - 400); oled.clearToEOL();
+ case 8: if (updated == 0)
+ LcdP("Warning tone: %d", eeSave.tone); oled.clearToEOL();
+ case 9: if (updated == 0)
+ LcdP("Serial monitor: %d", eeSave.smon); oled.clearToEOL();
+ case 10: if (updated == 0)
+ LcdP("Watchdog: %d mins", eeSave.watchdog); oled.clearToEOL();
+ case 11: if (updated == 0)
+ LcdP("Version: %s", VER); oled.clearToEOL();
+ case 12: updated = 0; instate = 0; break;
+ if (--updateTimeout == 0)
+ // main 1 second loop here for determining where we are
+ if (mymillis <= millis())
+ mymillis += 1000; // every second we do this
+ average = ((average * 7) + analogRead(VOLTAGE)) / 8; //i.e. average over 8 seconds 0-1023 = 0-5v - might make testing longer in the end
+ vol = (((float)average) * 5.0 / 824.0) * (float)eeSave.batteryOffset / 400.0; //10k/2k2
+ state = SHUTDOWN_STATE;
+ stateCounter = SHUTDOWN_PERIOD;
+ sprintf(bString, "{\"state\": %d,\"battery\": %d.%02d,\"upsecs\":%ld,\"downsecs\":%ld}", state, int(vol), (int)(vol * 100) % 100, upSecs, downSecs);
+ if (eeSave.smon) Serial.println(bString);
+ case POWERUP_STATE : // powered up everything off
+ if (stateCounter == eeSave.startTimeout) {
+ gotoXY(0, 2); LcdP("Battery: "); showBattery();
+ gotoXY(0, 3); LcdP("Wait %03d secs", stateCounter); oled.clearToEOL();
+ digitalWrite(WARNING, WARNINGON); // no warning
+ digitalWrite(POWER, POWEROFF);
+ if (vol > eeSave.highV)
+ if (stateCounter == 0) {
+ stateCounter = eeSave.goodTimeout;
+ if (stateCounter == 0) {
+ stateCounter = eeSave.offTimeout;
+ case GOOD_STATE : // battery is above upper threshold
+ if (stateCounter == eeSave.goodTimeout) {
+ LcdP("** Pi Power %s **", VER);
+ watchdogTimer = eeSave.watchdog;
+ if (++secondsCounter == 60)
+ watchdogTimer--; // only do this when battery is ok
+ if (watchdogTimer == 0) watchdogTrigger = 1;
+ watchdogTrigger = 0; // reboot
+ stateCounter = eeSave.warningTimeout;
+ gotoXY(0, 1); LcdP("Ok. Battery: "); showBattery();
+ gotoXY(0, 2); LcdP("High: %d.%dv Low: %d.%dv", int(eeSave.highV), ((int(eeSave.highV * 100)) % 100) / 10, int(eeSave.lowV), ((int(eeSave.lowV * 100)) % 100) / 10); oled.clearToEOL();
+ digitalWrite(WARNING, WARNINGOFF); // no warning
+ digitalWrite(POWER, POWERON);
+ gotoXY(0, 3); LcdP("Wait %03d secs", stateCounter); oled.clearToEOL();
+ if (stateCounter == 0) {
+ stateCounter = eeSave.warningTimeout;
+ stateCounter = eeSave.goodTimeout - 1;
+ if ((instate == 0) && (vol >= eeSave.lowV))
+ LcdP("On: %04ldd %02ld:%02ld:%02ld", upSecs / 86400, (upSecs / 3600) % 24, (upSecs % 3600) / 60, upSecs % 60);
+ LcdP("Off: %04ldd %02ld:%02ld:%02ld", downSecs / 86400, (downSecs / 3600) % 24 , (downSecs % 3600) / 60, downSecs % 60);
+ case TIMEOUT_STATE : // battery has dropped below upper threshold - ALWAYS timeout to OFF_STATE
+ if (stateCounter == eeSave.warningTimeout) oled.clear();
+ gotoXY(0, 0); LcdP("Timing out. B: "); showBattery();
+ gotoXY(0, 1); LcdP("Wait %03d secs", stateCounter); oled.clearToEOL();
+ digitalWrite(WARNING, WARNINGON); // warning
+ digitalWrite(POWER, POWERON);
+ if (eeSave.tone) tone(TONE, eeSave.tone, 100);
+ if (stateCounter == 0 ) {
+ stateCounter = eeSave.offTimeout;
+ case OFF_STATE : // everything off
+ if (stateCounter == eeSave.offTimeout) oled.clear();
+ if (vol <= eeSave.highV) {
+ digitalWrite(WARNING, WARNINGON); // LEAVE ON ie LOW
+ digitalWrite(POWER, POWEROFF);
+ if (vol > eeSave.highV)
+ gotoXY(0, 0); LcdP("Warming up. V="); showBattery();
+ gotoXY(0, 1); LcdP("Wait %03d secs", stateCounter); oled.clearToEOL();
+ if (stateCounter == 0) {
+ stateCounter = eeSave.goodTimeout;
+ stateCounter = eeSave.offTimeout - 1; // avoid clearscreen
+ gotoXY(0, 3); oled.clearToEOL();
+ case SHUTDOWN_STATE: // everything off and stays off... hold the SET button.... set state to SHUTDOWN_STATE and stateCounter to SHUTDOWN_PERIOD
+ if (stateCounter == SHUTDOWN_PERIOD)
+ gotoXY(0, 2); LcdP("Shutdown commencing.."); oled.clearToEOL();
+ digitalWrite(WARNING, WARNINGON); // warning
+ gotoXY(0, 2); LcdP("Shutdown COMPLETE"); oled.clearToEOL();
+ digitalWrite(WARNING, WARNINGON); // LEAVE ON ie LOW
+ digitalWrite(POWER, POWEROFF);
+ else if (digitalRead(DOWN) == 0) {
+ stateCounter = eeSave.startTimeout;