Here you go. As noted, the Pushover code is based on ESP8266 NodeMCU: Send Pushover Notifications (Arduino IDE) | Random Nerd Tutorials
/*
deployedB.ino
192.168.1.xxx
garage root cellar ("inside box") w/ DS18B20 temp sensors
if temp is too low -> push notification using Pushover
Compiled with:
Adafruit Feather ESP8266 Huzzah (even tho' just Adafruit ESP8266 breakout)
4 MB (FS:2MB OTA:~1019kB)
LwIP: v2 Higher bandwidth, no features
"Power on the controller then the servo." <-- from Customer Service Hitec RCD/Multiplex USA
*/
#define DEBUG true
#define Serial \
if (DEBUG) Serial
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoOTA.h>
#include "ThingSpeak.h"
#include <OneWire.h>
#include <DallasTemperature.h>
DeviceAddress insideT, outsideT;
//for Pushover
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
//************
// pins
#define ONE_WIRE_BUS 12 // for Huzzah ESP8266
const uint8_t pinForFan = 14;
const uint8_t pinForServo = 5;
const uint8_t pinForLED = 0; // write 0 to turn on red LED (low=on)
//************
// servo
bool valveOpen;
uint16_t angleUS; // valve angle in microseconds
//D85MG 850us to 2350us, 145 deg max range, 0.095 deg / us
//angles in degrees:
const uint8_t angleCloseClean = 18; // over closed...for cleaning the pot
const uint8_t angleCloseNormal = 31; // normal closed
const uint8_t angleOpenNormal = 135; // normal open
const uint8_t angleOpenClean = 150; // over open ...for cleaning the pot
//******************
// DS18B20 and temps
float TempCurrentInside, TempCurrentOutside, TempPrevious, TempPredicted;
float TempInMin = 99, TempOutMin = 99; // not used anymore...could delete
float TempInMax = -99, TempOutMax = -99; // not used anymore...could delete
uint32_t lastTempRequestTime;
const uint8_t setpointHi = 36; // turn on fan if above this
float setpointLo = 35.0; // shut off if below this; changed on the fly depending on outside air temp
//******************
// ThingSpeak
// the below also used for outside cold box
unsigned long myChannelNumber = 123;
const char* myWriteAPIKey = "abc";
//************
// for tracking duration
uint32_t timeValveClosed; // time valve closed (msec)
//************
// wifi
const char* ssid = "abc";
const char* password = "xyz";
//************
// objects
ESP8266WebServer server(80);
WiFiClient client;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
//************
// PushOver
const char* apiToken = "abc";
const char* userToken = "abc";
const char* pushoverApiEndpoint = "https://api.pushover.net/1/messages.json";
// Pushover root certificate (valid from 11/10/2006 to 15/01/2038)
const char* PUSHOVER_ROOT_CA =
"-----BEGIN CERTIFICATE-----\n"
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
"MrY=\n"
"-----END CERTIFICATE-----\n";
// Create a list of certificates with the server certificate
X509List cert(PUSHOVER_ROOT_CA);
void setup() {
Serial.begin(115200);
pinMode(pinForServo, OUTPUT);
pinMode(pinForFan, OUTPUT);
pinMode(pinForLED, OUTPUT);
digitalWrite(pinForLED, HIGH); // turn LED on w/ LOW, off w/ HIGH
//************
// wifi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(); Serial.print("IP:"); Serial.println(WiFi.localIP());
//************
// ThingSpeak
ThingSpeak.begin(client);
//************
// DS18B20
sensors.begin();
sensors.getAddress(insideT,0);
sensors.getAddress(outsideT,1);
sensors.requestTemperatures();
// using getTempF with address is supposedly faster than using getTempByIndex()
TempCurrentInside = sensors.getTempF(insideT);
TempCurrentOutside = sensors.getTempF(outsideT);
Serial.println(TempCurrentInside,1);
Serial.println(TempCurrentOutside,1);
//sensors.setWaitForConversion(false); // no need to use the non-blocking way
// doing two .getTempF in sequence, followed by requestTemperatures
// takes about 27 ms
// requestTemperatures alone takes 2 ms
//************
// exercise the valves and turn on LED while doing so
digitalWrite(pinForLED, LOW);
Serial.println("opening valve");
openValves();
delay(5000);
Serial.println("closing valve");
closeValves();
delay(5000);
Serial.println("cleaning servo");
cleanServos();
delay(1000);
digitalWrite(pinForLED, HIGH);
//************
// webserver
server.on("/", handleRoot);
server.on("/clear", handleClear); // clear TS max and min ... not used anymore
server.on("/tmpnstate", handleTemps); // show current in and out temps, and valve state
server.on("/force", handleForce); // force a new temp reading
server.on("/cycle", handleCycle); // cycle the valve
server.on("/push", handlePush); // send push via Pushover, for testing
server.onNotFound(handleNotFound);
server.begin();
//************
// For PushOver, set time via NTP, as required for x.509 validation
configTime(3 *3600,0,"pool.ntp.org","time.nist.gov");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("");
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print("Current time: ");
Serial.print(asctime(&timeinfo));
ArduinoOTA.begin();
}
//----------------------------------------------------------------------------
void loop() {
ArduinoOTA.handle();
server.handleClient();
uint32_t currentTime = millis();
//************
// poke servo...doesn't appear to be needed...could delete
const uint16_t intervalServo = 10000; // every 10 secs
static uint32_t prevTmServo = currentTime; // drifts, but so what
if (currentTime - prevTmServo > intervalServo) {
digitalWrite(pinForLED, LOW);
prevTmServo = currentTime;
delay(20);
if (valveOpen) // if was open, rewrite open normal angle
{
servoWrite(angleOpenNormal);
} else // if was closed, rewrite closed normal angle
{
servoWrite(angleCloseNormal);
}
digitalWrite(pinForLED, HIGH);
}
//************
// get temps and take action
const uint32_t interval = 120000;
static uint32_t prevTime = currentTime;
if (currentTime - prevTime > interval) {
prevTime = currentTime;
checkWiFi();
getTemps();
moveServo();
}
//************
// clean servo pot
const uint32_t cleanInterval = 172800000; // every two days...2x24x60x60x1000
static uint32_t prevTmClean = currentTime;
if (currentTime - prevTmClean > cleanInterval) {
prevTmClean = currentTime;
cleanServos();
}
} // end of loop
//----------------------------------------------------------------------------
void handleRoot() {
server.send(200,"text/plain","garage root cellar esp8266 .xxx");
}
//----------------------------------------------------------------------------
void handleClear() { //not needed...
server.send(200,"text/plain","Clearing mx/mn tmps in ESP8266...");
TempInMin = 99;
TempInMax = -99;
TempOutMin = 99;
TempOutMax = -99;
}
//----------------------------------------------------------------------------
void handleTemps() { // /tmpnstate
char myCstr[40];
if (valveOpen) {
sprintf(myCstr,"inside=%.1f outside=%.1f valve open", TempCurrentInside, TempCurrentOutside);
} else {
sprintf(myCstr,"inside=%.1f outside=%.1f valve closed", TempCurrentInside, TempCurrentOutside);
}
server.send(200,"text/plain", myCstr);
}
//----------------------------------------------------------------------------
void handleForce() {
server.send(404,"text/plain","getting temps");
getTemps();
}
//----------------------------------------------------------------------------
void handleCycle() {
server.send(404,"text/plain","cycling valve");
if (valveOpen) // if was open, turn off fan and close
{
GPOC = (1 << pinForFan); // turn fan off
servoWrite(angleCloseNormal);
} else // if was closed, open (but don't turn fan on)
{
servoWrite(angleOpenNormal);
}
delay(1000);
if (valveOpen) // if was open, open and turn fan back
{
servoWrite(angleOpenNormal);
GPOS = (1 << pinForFan); // turn fan on
} else // if was closed, close
{
servoWrite(angleCloseNormal);
}
}
//----------------------------------------------------------------------------
void handlePush() {
server.send(200,"text/plain","sending test push... inside cellar too cold");
sendNotification();
}
//----------------------------------------------------------------------------
void handleNotFound() {
server.send(404,"text/plain","404: Not found");
}
//----------------------------------------------------------------------------
// check wifi and retry
void checkWiFi() {
if (WiFi.status() != WL_CONNECTED) {
while (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, password);
delay(5000);
}
}
}
//----------------------------------------------------------------------------
void getTemps() {
sensors.requestTemperatures(); //blocks for about 750 ms
TempCurrentOutside = sensors.getTempF(outsideT);
TempCurrentOutside += 1.03; // calibrated at 32 deg F w/ mercury thermo
Serial.print("outside = ");
Serial.print(TempCurrentOutside);
Serial.println("ºF");
if (TempCurrentOutside < TempOutMin) TempOutMin = TempCurrentOutside;
if (TempCurrentOutside > TempOutMax) TempOutMax = TempCurrentOutside;
// if quite cold outside, then need warmer low setpoint, so fans shut off sooner
if (TempCurrentOutside < 15) {
setpointLo = 35.6;
} else if (TempCurrentOutside > 18) {
setpointLo = 35.0;
}
TempCurrentInside = sensors.getTempF(insideT);
TempCurrentInside += 1.14; // cal at 32 deg F w/ mercury thermo
Serial.print("inside = ");
Serial.print(TempCurrentInside);
Serial.println("ºF");
if (TempCurrentInside < TempInMin) TempInMin = TempCurrentInside;
if (TempCurrentInside > TempInMax) TempInMax = TempCurrentInside;
if (TempCurrentInside <= 32.0) sendNotification();
//If fan is on (& valve open), then predict next temperature.
//If predicted temp is less than low setpoint, turn motor off now (and close valve).
if (valveOpen) {
TempPredicted = 2 * TempCurrentInside - TempPrevious;
//the above is based on the fact that if the change is linear,
//the next change will be a decrease of (TempPrevious - TempCurrentInside)
//so the predicted temp is the TempCurrentInside-(TempPrevious-TempCurrentInside)
//or 2*TempCurrentInside-TempPrevious
//E.g.: if prev=36.29 and current=35.72, then predict 35.17 and keep fan going
//Or, if prev=36.29 and current=35.5, then predict 34.71 and shut off fan
if (TempPredicted < setpointLo) {
closeValves();
} else {
TempPrevious = TempCurrentInside;
}
}
//ThingSpeak post takes 0.48 to 0.62 seconds
ThingSpeak.setField(2, TempCurrentInside);
ThingSpeak.setField(3, TempCurrentOutside);
//ThingSpeak.setField(3, TempInMin); // not using max and min anymore
//ThingSpeak.setField(4, TempInMax);
//ThingSpeak.setField(5, TempOutMin);
//ThingSpeak.setField(6, TempOutMax);
//ThingSpeak.setField(7, valveOpen);
ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
//uint32_t endCount = ESP.getCycleCount();
//float deltaMS = (endCount - startCount) / 80000; // entire routine takes 1.3 secs, incl printing temps
//Serial.println(deltaMS);
Serial.println();
}
//----------------------------------------------------------------------------
void moveServo() {
// http://www.ee.calpoly.edu/media/uploads/resources/KarnaughExplorer_1.html
bool A = TempCurrentInside > setpointHi; // too warm
bool B = TempCurrentInside < setpointLo; // too cold
bool C = valveOpen; // valves open
bool D = TempCurrentOutside > TempCurrentInside; // outside is warmer than inside
bool E = (millis() - timeValveClosed) > (20 * 60 * 1000); // more than 20 min since valves closed
if ((C && D) || (B && C)) {
closeValves();
} else if ((A && !C && !D) || (A && !C && E)) {
openValves();
}
}
//----------------------------------------------------------------------------
void
openValves() {
servoWrite(angleOpenNormal); // set servo
GPOS = (1 << pinForFan); // turn fan on
TempPrevious = TempCurrentInside;
valveOpen = true;
}
//----------------------------------------------------------------------------
void
closeValves() {
GPOC = (1 << pinForFan); // turn fan off
servoWrite(angleCloseNormal); // set servo
valveOpen = false;
timeValveClosed = millis();
}
//----------------------------------------------------------------------------
void cleanServos() {
servoWrite(angleCloseClean);
delay(500);
servoWrite(angleOpenClean);
delay(500);
if (valveOpen) // if was open, leave open at normal angle
{
servoWrite(angleOpenNormal);
} else // and was closed, leave closed at normal angle
{
servoWrite(angleCloseNormal);
}
}
//----------------------------------------------------------------------------
void servoWrite(uint8_t angle) {
uint16_t angleUS = 850 + (angle * (2350 - 850)) / 180; // 2350 - 850 = 1,500 1,500 x 150 max angle = 225,000 fits in ESP8266 int32_twon't fit in uint16_t
GPOS = (1 << pinForServo); //set pin high; takes 0.06 uS or so, vs 1.5 uS for digitalWrite (not worth doing...but fun!)
delayMicroseconds(angleUS); // not rec for delays > 20 mS (doesn't yield...); no problem here
GPOC = (1 << pinForServo); // clear pin (set low) after appropriate pulse width
}
//----------------------------------------------------------------------------
// send PushOver notification
void sendNotification() {
// Based on https://randomnerdtutorials.com/esp8266-nodemcu-pushover-arduino/
// Make HTTPS POST request to send notification
if (WiFi.status() == WL_CONNECTED) {
// Create a JSON object with notification details
// Check the API parameters: https://pushover.net/api
StaticJsonDocument<512> notification;
notification["token"] = apiToken;
notification["user"] = userToken;
notification["message"] ="blueFoamInsideIsCold";
notification["title"] ="ESP8266 Notification";
notification["url"] ="";
notification["url_title"] ="";
notification["html"] ="";
notification["priority"] ="";
notification["sound"] ="cosmic";
notification["timestamp"] ="";
// Serialize the JSON object to a string
String jsonStringNotification;
serializeJson(notification, jsonStringNotification);
// Create a WiFiClientSecure object
WiFiClientSecure client;
// Set the certificate
client.setTrustAnchors(&cert);
// Create an HTTPClient object
HTTPClient http;
// Specify the target URL
http.begin(client, pushoverApiEndpoint);
// Add headers
http.addHeader("Content-Type","application/json");
// Send the POST request with the JSON data
int httpResponseCode = http.POST(jsonStringNotification);
// Check the response
if (httpResponseCode > 0) {
Serial.printf("HTTP response code: %d\n", httpResponseCode);
String response = http.getString();
Serial.println("Response:");
Serial.println(response);
} else {
Serial.printf("HTTP response code: %d\n", httpResponseCode);
}
// Close the connection
http.end();
}
}