/* River level Alert. ESP32 - 1.54" e-Paper display - PushSafer push notification */
#include <GxEPD.h>
#include <GxGDEH0154D67/GxGDEH0154D67.h> // 1.54" b/w epaper display
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino_JSON.h> // https://github.com/arduino-libraries/Arduino_JSON
#include <UrlEncode.h>
#define VOLTS_PIN 32
// Waveshare e-paper connections to ESP32
// Vcc -> 3v3
// GND -> GND
// DIN -> MOSI (GPIO 23)
// CLK -> CLK (GPIO 18)
// CS -> SS (GPIO 5)
// DC -> GPIO 17
// RST -> GPIO 16
// BUSY-> GPIO 4
GxIO_Class io(SPI, /*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16); // arbitrary selection of 17, 16
GxEPD_Class display(io, /*RST=*/ 16, /*BUSY=*/ 4); // arbitrary selection of 16, 4
String flood_data_url = "http://environment.data.gov.uk/flood-monitoring/id/stations/";
/*-*-*-*-*-* Edit values between these lines *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
String station = "685517"; // Change station ID to river of interest. Station ID can be found at...
// https://riverlevels.uk/levels
const float plotMax = 2.0; // Maximum value shown on UK Government's flood-risk graph.
const float high_level = 1.2; // Threshold at which to request PushSafer notification & lockout further requests.
const float low_level = 0.7; // Reset notifications lockout when level drops to this level.
const char* ssid = "*******"; // Your router's WiFi SSID
const char* password = "**********"; // Your router's Wifi Password
String pushSaferKey = "********************"; // PushSaferKey and DeviceID are obtained from..
String pushSafer_deviceID = "*****"; // ..your PushSafer Account.
String title = "Arrowebrook Level Monitor"; // Monitored river's name for notification header.
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
String High_level_icon = "48"; // Rising graph Icon for notification.
String Low_battery_icon = "49";
String pushSaferURL = "http://www.pushsafer.com/api"; // PushSafer server URL.
String pushSaferFullPath; // pushSafer full path will include your pushsafer account ID and river height.
// These values are retained through a wakeup from deep sleep.
RTC_NOINIT_ATTR bool high_level_flag = false; // true = lockout further pushsafer high river level notifications.
RTC_NOINIT_ATTR bool low_battery_flag = false; // true = lockout further pushsafer low battery notifications.
RTC_NOINIT_ATTR byte arrayPointer = 0; // Pointer to next data array element.
RTC_NOINIT_ATTR byte plotArray[200]; // Save histogram data in plotArray.
RTC_NOINIT_ATTR bool first_run = true;
int sleep_interval = 15; // minutes to wait between river height requests. Do not reduce this value.
int retry_interval = 1; // Minutes to wait before retrying to connect WiFi.
double height = 0.0; // Initial river height.
float mVolts = 0.0;
const float battery_low = 3300.0; // Battery volatge low threshold to trigger notification.
const float battery_charging = 3900.0; // Battery charging. Reset low battery lockout flag.
long rssi; // WiFi strength for display purposes only.
String jsonBuffer; // Buffer to store JSON river-level data received from Government website.
void setup() {
Serial.begin(9600);
if (first_run) { // No valid data in array so clear all array.
first_run = false;
arrayPointer = 0;
for (int i = 0; i < 200; i++) {
plotArray[i] = 0;
}
}
int vref = 1140;
uint16_t v = analogRead(VOLTS_PIN); // Read adc value at R1/R2 junction for battery voltage.
float mVolts = ((float)v / 4095.0) * 2 * 3.2 * vref; // Convert value to mVolts.
WiFi.begin(ssid, password); // Start the WiFi connection to your routerr
int wifi_timeout = 0; // Attempt to connect
while (WiFi.status() != WL_CONNECTED) {
delay(10);
wifi_timeout++;
if (wifi_timeout > 1000) { // If connect to WiFi fails - deep sleep...
deepSleep(retry_interval); // .. for the retry interval.
}
}
if (WiFi.status() == WL_CONNECTED) { // WiFi connected to router.
rssi = WiFi.RSSI(); // Get WiFi signal strength for epaper display.
HTTPClient http1;
flood_data_url = flood_data_url + station + "/measures";
Serial.println(flood_data_url);
http1.begin(flood_data_url);
if (http1.GET() > 0) { // Data successfully received from Government website.
String jsonBuffer = http1.getString();
Serial.println(jsonBuffer);
JSONVar doc = JSON.parse(jsonBuffer);
height = doc["items"][0]["latestReading"]["value"]; // Extract river height from JSON buffer.
Serial.print("Height: "); Serial.println(height, 3);
updateHistogramData(); // Update the plotArray data.
if (height <= low_level) high_level_flag = false; // Reset high level flag to allow new notifications.
if (mVolts >= battery_charging) low_battery_flag = false; // Reset low battery flag to allow new notifications.
}
http1.end();
if ((high_level_flag == false) && (height >= high_level)) {
request_push_notification("HIGH LEVEL: " + String(height, 2) + "m", High_level_icon); // Request a push notification.
}
if ((low_battery_flag == false) && (mVolts <= battery_low)) {
request_push_notification("BATTERY LOW: " + String(mVolts/1000, 2) + "v", Low_battery_icon); // Request a push notification
}
}
display.init();
display.setRotation(45);
display.setTextColor(GxEPD_BLACK);
display.fillScreen(GxEPD_WHITE);
display.setTextSize(2);
display.setCursor(25, 0);
display.print("RIVER HEIGHT");
display.setCursor(45, 16);
display.print("(metres) ");
display.drawLine(0, 34, 199, 34, GxEPD_BLACK);
display.drawLine(0, 174, 199, 174, GxEPD_BLACK);
display.setCursor(0, 186); // Show battery volts, high-level preset and WiFi strength..
display.print(mVolts / 1000, 1); // .. on Status bar at bottom of display.
display.print("v");
display.setCursor(60, 186);
display.print(high_level, 1); display.print("m");
display.setCursor(140, 186);
display.print(rssi);
display.print("dB");
display.setFont(&FreeSansBold18pt7b); // Show measured river height in large bold text.
display.setCursor(25, 100);
display.print(height, 2);
display.setFont(); // Reset font to standard small.
plotHistogram(); // Plot the river height histogram.
display.update(); // Update display and put epaper display in very low power mode.
deepSleep(sleep_interval); // Sleep the ESP32. epaper sleeps automatically.
}
void updateHistogramData() {
if (arrayPointer >= 199) { // If array has been filled, move all values down one.
for (int i = 0; i < 199; i++) {
plotArray[i] = plotArray[i+1];
}
arrayPointer = 199;
}
plotArray[arrayPointer] = round(height * (80 / plotMax)); //
if (arrayPointer < 199) arrayPointer++; // increment pointer to plot Array.
}
void plotHistogram() { // Function to re-draw the histogram.
// (strictly an "area chart" but let's not quibble).
for (int i = 0; i <= 199; i++){
display.drawLine(i, 173 - plotArray[i], i, 173, GxEPD_BLACK); // plot vertical histogram line.
}
int y = round(175 - (high_level * (80 / plotMax))); // Calculate vertical position of alert level.
for (int x = 0; x < 190; x+= 20) { // Draw alert level line (threshold level).
display.setCursor(x, y-6);
display.print("- ");
}
display.fillTriangle(198, y-5, 198, y+5, 188, y, GxEPD_BLACK); // Display triangle ◄ at alert level.
}
void request_push_notification(String message, String icon) {
// title.replace(" ", "%20"); // Replace spaces with %20 for use in PushSafer URL
// message.replace(" ", "%20");
title = urlEncode(title);
message = urlEncode(message);
pushSaferFullPath = pushSaferURL + // String to send to PushSafer server.
"?d=" + pushSafer_deviceID +
"&k=" + pushSaferKey +
"&i=" + icon +
"&t=" + title +
"&m=" + message;
Serial.println(pushSaferFullPath);
HTTPClient http2;
// String http2_request = pushSaferPath;
http2.begin(pushSaferFullPath);
int http2Code = http2.GET();
if (http2Code > 0) { // PushSafer has received push notificstion request.
String response_str = http2.getString();
Serial.println(response_str);
if (response_str.indexOf("success") > 1) { // If successful response from PushSafer, set flags..
if (icon == High_level_icon) high_level_flag = true; // .. to stop further requests until measured values allow..
if (icon == Low_battery_icon) low_battery_flag = true; // .. flags to be reset.
}
}
http2.end();
}
void deepSleep(int interval) {
esp_sleep_enable_timer_wakeup(interval * 60 *1000 * 1000);
esp_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_deep_sleep_start();
}
void loop() {
// Nothing to see here.
}