/* OpenWeather weather monitor (with second server option) */
#include <GxEPD.h> // https://github.com/ZinggJM/GxEPD
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
#include <GxGDEW042T2/GxGDEW042T2.h> // display class 4.2" b/w
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>
// FreeFonts from Adafruit_GFX
#include <Fonts/FreeMonoBold9pt7b.h>
#include "myFonts/FreeMonoBold7pt7b.h"
#include "myFonts/DSEG7Classic_Bold20pt7b.h" // https://github.com/keshikan/DSEG/releases/tag/v0.46
#include "myFonts/DSEG7Classic_Bold8pt7b.h"
#include "bitmaps/bitmaps.h" // Weather symbols bitmaps
#include <TimeLib.h> // https://github.com/geekfactory/TimeLib
#include <Arduino_JSON.h> // https://github.com/arduino-libraries/Arduino_JSON
#include <HTTPClient.h>
// for SPI pin definitions see e.g.:
// C:\Users\xxx\Documents\Arduino\hardware\espressif\esp32\variants\lolin32\pins_arduino.h
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
#define WEATHER_SERVER_PIN 32 // Select Local server or OpenWeathermap (Option)
#define ADC_PIN 35 // ADC Pin for battery voltage
#define FLAT_BATT 3.3 // Battery at lowest safe discharge level - sleep forever.
#define LOW_BATT 3.5 // Battery considered 'low' below this value
#define READS 20 // How many times to read the ADC pin.
#define WIFI_TIMEOUT 10000 // 10 seconds in milliseconds
#define DEEP_SLEEP_TIME 15 // Sleep time in Minutes (will double if battery is < LOW_BATT volts)
/*------------------------ Only change values between these lines -------------------- */
#define DEBUGGING false // Set to false when sketch is finished.
#define SHOW_LOCAL_TIME true // Set to false to display GMT (UTC)
#define CONV_FACTOR 1.76 // ADC value conversion factor (tweak for accurate volts).
#define DST "BST" // Define local name for your daylight saving time
// ( https://www.timeanddate.com/time/zones/ )
const char* ssid = "*******"; // Your WiFi router SSID
const char* password = "**********"; // Your WiFi router password
String openWeatherMapApiKey = "********************************"; // Your openweathermap Api Key
// ( https://home.openweathermap.org/users/sign_up )
String lat = "53.4"; // Your latitude & longitude (Google maps)
String lon = "-3.1";
String serverPathLocal = ""; // Leave this line as an empty String
// **SEE TEXT**
/*------------------------------------------------------------------------------------*/
String OpenWeather = "http://api.openweathermap.org/data/2.5/weather";
String serverPathOpenWeather = OpenWeather + "?lat=" + lat + "&lon=" + lon + "&appid=" + openWeatherMapApiKey;
String serverPath;
String jsonBuffer;
bool useLocalServer = false;
double volts ;
unsigned long runtime = millis(); // Used for debugging to minimize time esp32 is active
void setup() {
setCpuFrequencyMhz(80); // Slow CPU should use less current.
Serial.begin(115200);
pinMode(WEATHER_SERVER_PIN, INPUT_PULLUP); // Input to select optional weather server **SEE TEXT**
useLocalServer = digitalRead(WEATHER_SERVER_PIN) == LOW; // If Jumper JP1 is closed, use local server. **SEE TEXT**
if (useLocalServer) {
serverPath = serverPathLocal;
} else
serverPath = serverPathOpenWeather;
WiFi.begin(ssid, password);
if (DEBUGGING) {
Serial.println();
Serial.print("Free heap: ");
Serial.println(ESP.getFreeHeap()); // Check on memeory useage
Serial.print("Connecting to ");
Serial.println(serverPath);
}
unsigned long startAttemptTime = millis();
volts = getBatteryVolts();
while (WiFi.status() != WL_CONNECTED &&
millis() - startAttemptTime < WIFI_TIMEOUT){
delay(10);
}
// Make sure that we're actually connected, otherwise go to deep sleep
if(WiFi.status() != WL_CONNECTED){
Serial.println("FAILED");
goToDeepSleep(volts);
}
if (DEBUGGING) {
Serial.println("");
Serial.print("Connecting time:");
Serial.print((millis() - startAttemptTime) / 1000);
Serial.println(" sec");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
display.init();
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeMonoBold9pt7b);
display.setRotation(0);
display.fillScreen(GxEPD_WHITE);
if(WiFi.status()== WL_CONNECTED){
int rssi = WiFi.RSSI();
jsonBuffer = httpGETRequest(serverPath.c_str()); // Get raw JSON data from server
if (DEBUGGING) {
Serial.println(jsonBuffer);
}
JSONVar doc = JSON.parse(jsonBuffer); // Parse the JSON data into JSONVar 'doc'
// JSON.typeof(jsonVar) can be used to get the type of the var
if (JSON.typeof(doc) == "undefined") {
Serial.println("Parsing input failed!");
goToDeepSleep(volts);
}
if (DEBUGGING) {
Serial.print("JSON object = ");
Serial.println(doc);
Serial.println();
}
double latitude = doc["coord"]["lat"]; // Get 'doc' values into variables.
double longitude = doc["coord"]["lon"];
double temp = doc["main"]["temp"];
double windspeed = doc["wind"]["speed"];
int wind_dir = doc["wind"]["deg"];
int pressure = doc["main"]["pressure"];
int humidity = doc["main"]["humidity"];
int visibility = doc["visibility"];
long time_now = doc["dt"]; // Times are Unix times
long sunrise = doc["sys"]["sunrise"];
long sunset = doc["sys"]["sunset"];
long timezone = doc["timezone"];
const char* location = doc["name"];
JSONVar weather = doc["weather"][0];
int id = weather["id"];
const char* wx = weather["main"];
const char* description = weather["description"];
double tempC = temp - 273.15; // Convert Kelvin to Celcius
double windspeed_mph = windspeed * 2.237; // Convert m/s to mph
if (SHOW_LOCAL_TIME) { // Add timezone if Local time is requested
time_now = time_now + timezone;
sunrise = sunrise + timezone;
sunset = sunset + timezone;
}
display.drawRoundRect(25, 5, 170, 95, 5, GxEPD_BLACK); // Draw rectangles for each weather value on diplay
display.setCursor(45, 22);
display.print("TEMPERATURE c");
display.drawRoundRect(205, 5, 170,95, 5, GxEPD_BLACK);
display.setCursor(230, 22);
display.print("HUMIDITY %");
display.drawRoundRect(25, 104, 170,95, 5, GxEPD_BLACK); // weather
display.drawRoundRect(205, 104, 170,95, 5, GxEPD_BLACK);
display.setCursor(220, 123);
display.print("PRESSURE mb");
display.drawRoundRect(25, 203, 170, 95, 5, GxEPD_BLACK); // sunrise etc
display.drawRoundRect(205, 203, 170,95, 5, GxEPD_BLACK);
display.setCursor(290, 220);
display.print("WIND");
display.setFont(&FreeMonoBold7pt7b);
display.setCursor(333, 250);
display.print("mph");
display.drawCircle(333, 270, 2, GxEPD_BLACK);
display.setCursor(134, 118);
display.print("vis:(m)");
display.setCursor(134, 135);
if (visibility == 0) {
display.print("n/a");
}
else
display.print(visibility);
display.setCursor(30, 184);
display.print(wx);
display.setCursor(30, 194);
display.print(description);
display.setCursor(143, 218);
if ((SHOW_LOCAL_TIME ) && (timezone != 0)){
display.print(DST);
} else
display.print("GMT");
display.setCursor(135, 244);
display.print(rssi); // Display WiFi signal strength
display.print(" dB");
display.setFont(&DSEG7Classic_Bold20pt7b);
int tempX;
tempC < 10 ? tempX = 80 : tempX = 50;
display.setCursor(tempX, 80);
display.print(tempC, 1);
display.setCursor(250, 80);
display.print(humidity);
if (pressure <1000) {
display.setCursor(220, 180);
}
else
display.setCursor(210, 180);
display.print(pressure);
// Display "BBC Style" weather direction indicator
display.setFont(&FreeMonoBold9pt7b);
display.setCursor(227, 290);
switch (wind_dir) {
case 0 ... 11: display.drawBitmap(gImage_n, 210, 205, 79, 73, GxEPD_BLACK);
display.print( " N");
break;
case 12 ... 34: display.drawBitmap(gImage_nne, 210, 205, 79, 73, GxEPD_BLACK);
display.print("NNE");
break;
case 35 ... 56: display.drawBitmap(gImage_ne, 210, 205, 79, 73, GxEPD_BLACK);
display.print("NE");
break;
case 57 ... 79: display.drawBitmap(gImage_ene, 210, 205, 79, 73, GxEPD_BLACK);
display.print("ENE");
break;
case 80 ... 101: display.drawBitmap(gImage_e, 210, 205, 79, 73, GxEPD_BLACK);
display.print(" E");
break;
case 102 ... 123: display.drawBitmap(gImage_ese, 210, 205, 79, 73, GxEPD_BLACK);
display.print("ESE");
break;
case 124 ... 146: display.drawBitmap(gImage_se, 210, 205, 79, 73, GxEPD_BLACK);
display.print("SE");
break;
case 147 ... 169: display.drawBitmap(gImage_sse, 210, 205, 79, 73, GxEPD_BLACK);
display.print("SSE");
break;
case 170 ... 192: display.drawBitmap(gImage_s, 210, 205, 79, 73, GxEPD_BLACK);
display.print(" S");
break;
case 193 ... 215: display.drawBitmap(gImage_ssw, 210, 205, 79, 73, GxEPD_BLACK);
display.print("SSW");
break;
case 216 ... 238: display.drawBitmap(gImage_sw, 210, 205, 79, 73, GxEPD_BLACK);
display.print("SW");
break;
case 239 ... 261: display.drawBitmap(gImage_wsw, 210, 205, 79, 73, GxEPD_BLACK);
display.print("WSW");
break;
case 262 ... 283: display.drawBitmap(gImage_w, 210, 205, 79, 73, GxEPD_BLACK);
display.print(" W");
break;
case 284 ... 306: display.drawBitmap(gImage_wnw, 210, 205, 79, 73, GxEPD_BLACK);
display.print("WNW");
break;
case 307 ... 329: display.drawBitmap(gImage_nw, 210, 205, 79, 73, GxEPD_BLACK);
display.print("NW");
break;
case 330 ... 352: display.drawBitmap(gImage_nnw, 210, 205, 79, 73, GxEPD_BLACK);
display.print("NNW");
break;
case 353 ... 360: display.drawBitmap(gImage_n, 210, 205, 79, 73, GxEPD_BLACK);
display.print(" N");
break;
default : display.drawBitmap(gImage_n, 210, 205, 79, 73, GxEPD_BLACK);
}
display.setFont(&DSEG7Classic_Bold8pt7b); // Switch to small 7-segment font
display.setCursor(290, 252);
display.println(windspeed_mph, 1);
display.setCursor(290, 285);
display.print(wind_dir);
display.drawBitmap(gImage_sunrise, 30, 205, 25, 25, GxEPD_BLACK);
display.drawBitmap(gImage_sunset, 30, 233, 25, 25, GxEPD_BLACK);
display.setCursor(60, 230);
char buff[32];
if (sunrise != sunset) {
sprintf(buff, "%02d:%02d", hour(sunrise), minute(sunrise)); // Convert unix to date time
display.println(buff);
display.setCursor(60, 258);
sprintf(buff, "%02d:%02d", hour(sunset), minute(sunset)); // Convert unix to date time
display.println(buff);
}
else {
display.print("-:-");
display.setCursor(60, 258);
display.print("-:-");
}
display.drawBitmap(gImage_clock, 30, 265, 25, 25, GxEPD_BLACK);
display.setCursor(60, 285);
sprintf(buff, "%02d:%02d", hour(time_now), minute(time_now)); // Convert unix to date time
display.println(buff);
display.drawBitmap(gImage_battery, 140, 250, 45, 15, GxEPD_BLACK);
display.setCursor(136, 285);
display.print(volts);
display.setFont(&FreeMonoBold9pt7b);
display.print("v");
float w = constrain(mapf(volts, 3.3, 4.2, 10, 40), 0, 40); // Convert volts into width value and
display.fillRect(142, 253, w, 9, GxEPD_BLACK); // 'fill' battery image by 'w' amount.
bool daytime = (time_now >= sunrise) && (time_now < sunset); // Work out if it's daytime or night time
if (sunrise == sunset) daytime = true; // Line to handle 2nd (home) server
int x = 50;
int y = 108;
// Display cloud conditions image based on the id sent from the server
switch (id) {
case 200 ... 232:
display.drawBitmap(gImage_11d_n, x, y, 77, 58, GxEPD_BLACK); // Thunderstorm - day & night
break;
case 300 ... 321:
display.drawBitmap(gImage_09d_n, x, y,81, 69, GxEPD_BLACK); // Drizzle - day & night
break;
case 500 ... 504:
if (daytime) {
display.drawBitmap(gImage_10d, x, y,73, 69, GxEPD_BLACK); // Light rain - daytime
} else {
display.drawBitmap(gImage_10n, x, y,76, 61, GxEPD_BLACK); // Light rain - night
}
break;
case 511:
display.drawBitmap(gImage_13d_n, x, y,60, 68, GxEPD_BLACK); // freezing rain
break;
case 520 ... 531:
display.drawBitmap(gImage_09d_n, x, y,81, 69, GxEPD_BLACK); // Drizzle - day & night
break;
case 600 ... 622:
display.drawBitmap(gImage_13d_n, x, y,60, 68, GxEPD_BLACK); // Frost - day & night
break;
case 701 ... 781:
display.drawBitmap(gImage_50d_n, x, y,43, 33, GxEPD_BLACK); // Mist, fog - day & night
break;
case 800:
if (daytime) {
display.drawBitmap(gImage_01d, x, y, 51, 58, GxEPD_BLACK); // claer sky - day
} else {
display.drawBitmap(gImage_01n, x, y,39, 39, GxEPD_BLACK); // clear sky - night
}
break;
case 801 ... 802:
if (daytime) {
display.drawBitmap(gImage_02d, x, y, 75, 62, GxEPD_BLACK); // few clouds - day
} else {
display.drawBitmap(gImage_02n, x, y,83, 56, GxEPD_BLACK); // few clouds - night
}
break;
case 803 ... 804:
display.drawBitmap(gImage_03d_n, x, y,77, 42, GxEPD_BLACK); // cloud - day and night
break;
default:
display.drawBitmap(gImage_09d_n, x, y,81, 69, GxEPD_BLACK); // Drizzle - day & night
}
if (DEBUGGING) {
Serial.print("Latitude: ");
Serial.print(abs(latitude));
if (latitude > 0) {
Serial.println("N");
}
else Serial.println("S");
Serial.print("Longitude: ");
Serial.print(abs(longitude));
if (longitude > 0) {
Serial.println("E");
}
else Serial.println("W");
Serial.print("Location: ");
Serial.println(location);
Serial.print("Weather id: ");
Serial.println(id);
Serial.print("Weather: ");
Serial.print(wx);
Serial.print(" - ");
Serial.println(description);
Serial.print("Temperature: ");
Serial.print(tempC, 2);
Serial.println(" C");
Serial.print("Pressure: ");
Serial.print(pressure);
Serial.println(" mbar");
Serial.print("Humidity: ");
Serial.print(humidity);
Serial.println(" %");
Serial.print("Wind Speed: ");
Serial.print(windspeed_mph, 1);
Serial.println(" mph");
Serial.print("Wind Direction: ");
Serial.println(wind_dir);
sprintf(buff, "%02d:%02d:%02d", hour(time_now), minute(time_now), second(time_now));
Serial.print("Data time: ");
Serial.println(buff);
if (sunrise != sunset) {
Serial.print("Sunrise; ");
sprintf(buff, "%02d:%02d", hour(sunrise), minute(sunrise));
Serial.println(buff);
Serial.print("Sunset; ");
sprintf(buff, "%02d:%02d", hour(sunset), minute(sunset));
Serial.println(buff);
}
else {
Serial.print("Sunrise: ");
Serial.println("n/a");
Serial.print("Sunset: ");
Serial.println("n/a");
}
Serial.println("");
}
}
else {
Serial.println("WiFi Disconnected");
}
display.update();
goToDeepSleep(volts); // Tell DeepSleep the battery voltage so it can set the sleep time accordingly.
}
void goToDeepSleep(double volts){
if (DEBUGGING) {
Serial.print("Total round-trip time: ");
Serial.print((millis() - runtime) / 1000);
Serial.println(" seconds");
Serial.print("Batt: ");
Serial.println(volts);
}
// Configure the timer to wake up!
if (volts <= FLAT_BATT) {
display.setFont(&FreeMonoBold9pt7b);
display.fillScreen(GxEPD_WHITE);
display.setCursor(130, 150);
display.print("CHARGE BATTERY");
display.update();
esp_sleep_enable_timer_wakeup(8760ULL * 60 * 60 * 1000 * 1000); // Battery flat. Set a 1 year interval
}
else if (volts < LOW_BATT) {
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME * 120L * 1000 * 1000); // Battery low. Set 30 minute inteval
} else {
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME * 60L * 1000 * 1000); // Battery good. Set 15 mins interval.
}
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_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
// esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
Serial.flush();
esp_deep_sleep_start();
}
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your IP address with path or Domain name with URL path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "{}";
if (httpResponseCode>0) {
if (DEBUGGING) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
}
payload = http.getString();
}
else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
}
double mapf(double val, double in_min, double in_max, double out_min, double out_max) {
return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
double getBatteryVolts() {
double totalVal = 0;
double averageVal = 0;
for (int i = 0; i < READS; i++ ) {
totalVal += analogRead(ADC_PIN);
}
averageVal = totalVal / READS;
volts = averageVal * CONV_FACTOR / 1000;
return volts;
}
void loop() {
//
}