/* *************************************************************************************************
* Description : Small Weather forecast device based on Dutch weather advice
* Author : Rudi Zengers
* Date : 11 Mar 2019
* Hardware : https://wiki.wemos.cc/products:retired:d1_mini_v3.0.0
* Arduino Settings : Board : LOLIN(WEMOS) D1 RA & Mini
* Upload Speed : 115200
* CPU freq : 80 MHz
* Flash Size : 4M (No SPIFFS) V2 Lower Memory Disabled
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* *************************************************************************************************/
/* *************************************************************************************************
* Include the necessary libraries here. Thanks to all the folks spending so much time
* to develop all this stuff to make my hobby a bit easier.
* *************************************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* *************************************************************************************************
* Defines, to ensure our code is easier to maintain
* *************************************************************************************************/
// Define TFT screen text messages here so we can easily translate them
#define MSG_WE_ARE_IN_STATION_MODE "Station mode; My ip:"
#define MSG_SSID "SSID: "
#define MSG_STARTING "Start Weerstation"
#define MSG_CONNECT_FAILURE "Verbinding mislukt"
#define MSG_CONNECTED_WITH_IP "Verbonden. IP adres:"
#define MSG_LOCAL_DAYNAMES {"Zondag","Maandag","Dinsdag","Woensdag","Donderdag","Vrijdag","Zaterdag"}
#define MSG_RAIN "Regen " // Make both 6 long to align nicely
#define MSG_SUN "Zon "
#define MSG_NO_RAIN_1 "Geen regen"
#define MSG_NO_RAIN_2 "voorzien"
#define MSG_RAIN_SCREEN "Regen komende 2 uur"
// Clock defines
#define SUMMERTIME_DIFF 1
#define WINTERTIME_DIFF 0
#define DIFF_FROM_UTC 1
#define WAITING_TIME_FORECAST 600 // Seconds
#define WAITING_TIME_RAIN 300 // Seconds
#define OFF_HOUR 23 // Time to black the screen
#define ON_HOUR 7 // Time to wake up
// json size
#define JSON_MAX_SIZE 2048
// ip based
#define ACCESSPOINT_NAME "Weerstation" // Name in station mode
#define HTTPADRESS "http://weerlive.nl/api/json-10min.php?locatie=Waalre"
#define RAINFORECAST "https://gpsgadget.buienradar.nl/data/raintext?lat=51.3&lon=5.45"
// Pin mapping
#define PIN_D0 16
#define PIN_D1 5
#define PIN_D2 4
#define PIN_D3 0
#define PIN_D4 2
#define PIN_D5 14
#define PIN_D6 12
#define PIN_D7 13
#define PIN_D8 15
#define PIN_D9 3
#define PIN_D10 1
// TFT Screen orientation and other
#define TFT_ROTATION_FROM_PINS_RIGHT 1
#define TFT_ROTATION_FROM_PINS_UP 2
#define TFT_ROTATION_FROM_PINS_LEFT 3
#define TFT_ROTATION_FROM_PINS_DOWN 4
#define SYMBOL_WIDTH 50
#define SYMBOL_HEIGTH 50
#define LV_COLOR_DEPTH 1
#define TEXT_SIZE_SMALL 0
#define TEXT_SIZE_MEDIUM 1
#define TEXT_SIZE_LARGE 2
#define TFT_CS PIN_D4 // TFT CSS select (CS) ** Define the TFT screen interface, which is a SPI interface.
#define TFT_DC PIN_D3 // TFT A0 display/cmd (DC) ** To prevent issues with SPI we stick to the default pins, this
#define TFT_RST PIN_D2 // TFT RES reset (RST) ** ensures compatibility with other libraries. Also have a look at
#define TFT_SCLK PIN_D5 // TFT SCK (SLCK) ** http://wiki.microduinoinc.com/index.php/TFT_ST7735_Syntax_Manual
#define TFT_MOSI PIN_D7 // TFT SDA (MOSI) ** and http://www.barth-dev.de/online/rgb565-color-picker/
#define BACKLIGHT PIN_D8 // TFT LED
#define SCREEN_CHANGE_TIMEOUT 20000 // ms to wait before showing another screen
/* *************************************************************************************************
* Global variables out of scope of functions
* *************************************************************************************************/
HTTPClient http; // Needed for our server communication
const char * headerKeys[] = {"date", "server"};
const size_t numberOfHeaders = 2;
DynamicJsonDocument doc(JSON_MAX_SIZE);
// Needed for the tft screen
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC,TFT_MOSI , TFT_SCLK, TFT_RST);
Ticker getWeather; // Timer for fetching weather info
Ticker getRain; // Timer for fetching rain precip info for next 2 hours
String Weather_temp, // Storing the most recent weather message
Weather_summary,
Weather_forecast,
Weather_winddir,
Weather_windkmh,
Weather_sup,
Weather_sunder,
Weather_image,
Weather_d0tmax,
Weather_d0tmin,
Weather_d0rain,
Weather_d0sun,
Weather_d1tmax,
Weather_d1tmin,
Weather_d1rain,
Weather_d1sun,
Weather_place,
Weather_error,
Rain_error;
float Rain[24]; // Rain forecast in mm/hour
float Rainmax;
int Weather_Daynr; // Sunday = 0
int TheHour; // Current hour for determining on/off cycli
String Date_Of_Today; // Some other vars
bool Display_Power; // Is backlight needed
#include "WeatherSymbols.h" // Our pictures in a C array; http://www.rinkydinkelectronics.com/_t_doimageconverter565.php
/* *************************************************************************************************
* Wifi Connect Manager
*
* From the WifiManager library. Will be called when in station mode. Gives is
* the opportunity to inform the user or do other things
* *************************************************************************************************/
void configModeCallback (WiFiManager *myWiFiManager) {
tft.setCursor( 0,12); tft.print(MSG_WE_ARE_IN_STATION_MODE);
tft.setCursor( 0,24); tft.print(WiFi.softAPIP());
tft.setCursor( 0,36); tft.print(MSG_SSID);
tft.print(myWiFiManager->getConfigPortalSSID());
}
/* *************************************************************************************************
* Setup
*
* We will run this code only once, when powered on
* *************************************************************************************************/
void setup() {
Serial.begin(9600); // Init serial port
Serial.println(MSG_STARTING);
pinMode(BACKLIGHT, OUTPUT); // Backlight on
digitalWrite(BACKLIGHT, HIGH);
Display_Power = true;
tft.initR(INITR_BLACKTAB); //InitST7735, 1.8" TFT BLACKTAB -> Manufacturer origin probably; Has black label on screen protector
tft.fillScreen(ST7735_BLACK);
tft.setRotation(TFT_ROTATION_FROM_PINS_RIGHT);
tft.setTextWrap(false);
/* Display has room for 26 char * 14 lines in size_small. Character heigth 9 and with 6
* Size 1 is nice for degree etc and will fit approx 13 chars I guess
*/
tft.setCursor(0, 0);
tft.setTextSize(TEXT_SIZE_SMALL);
tft.setTextColor(ST7735_YELLOW);
tft.print(MSG_STARTING);
delay(500); // Wait a bit to give the system time to polish the bits
tft.setCursor(0, 48);
WiFiManager wifiManager; /* Local intialization in the setup routine instead of global
* Once its business is done, there is no need to keep it around */
wifiManager.setAPCallback(configModeCallback); // set callback that gets called when connecting
wifiManager.setDebugOutput(false); // to previous WiFi fails, and enters Access Point mode
/* Fetches ssid and pass and tries to connect. If it does not connect it starts an access point
* with the specified name and goes into a blocking loop awaiting configuration */
if(!wifiManager.autoConnect(ACCESSPOINT_NAME)) {
tft.print(MSG_CONNECT_FAILURE);
Serial.println(F("Connection to accesspoint did not work. Will reset myself now"));
delay(3000); // wait a bit so user can read the message
ESP.reset(); //reset and try again
}
// If we arrive here we have a network connection
tft.print(MSG_CONNECTED_WITH_IP);
tft.setCursor(0,60);
tft.print(WiFi.localIP().toString());
Serial.println("Connected to IP " + WiFi.localIP().toString());
delay(2000); // wait a bit to get eveything finished and cleaned and read the message
// Get the info for the first time and install the timer for next period
Get_The_Weather_Now();
getWeather.attach(WAITING_TIME_FORECAST, Get_The_Weather_Now); // Timer for next time
Get_Rain_Forecast();
getRain.attach(WAITING_TIME_RAIN, Get_Rain_Forecast); // Timer for next time
}
/* *************************************************************************************************
* Align
*
* Right aligns the input with the filler and the positions as requested
* *************************************************************************************************/
String Align(int positions,String filler,String what) {
String giveback = what;
int mylen = what.length();
for (int i=mylen;i 10260159)) or
((jaar == 2020) and (md < 3290300 or md > 10250159)) or
((jaar == 2021) and (md < 3280300 or md > 10310159)) or
((jaar == 2022) and (md < 3270300 or md > 10300159)) or
((jaar == 2023) and (md < 3260300 or md > 10290159)))
return uur + + DIFF_FROM_UTC + WINTERTIME_DIFF;
if (((jaar == 2019) and (md > 3310259 and md < 10260200)) or
((jaar == 2020) and (md > 3290259 and md < 10250200)) or
((jaar == 2021) and (md > 3280259 and md < 10310200)) or
((jaar == 2022) and (md > 3270259 and md < 10300200)) or
((jaar == 2023) and (md > 3260259 and md < 10290200)))
return uur + + DIFF_FROM_UTC + SUMMERTIME_DIFF;
// if we arrive here, this program runs longer than expected
Serial.println("No more DST info in logic");
}
/* *************************************************************************************************
* TranslateDayName
*
* Converts dayname as seen in the header to local language
* *************************************************************************************************/
String TranslateDayName(String DateDay) {
String days = "___SunMonTueWedThuFriSat";
String localdays[] = MSG_LOCAL_DAYNAMES;
Weather_Daynr = days.indexOf(DateDay)/3 - 1; //Starting at zero for Sunday
return localdays[Weather_Daynr];
}
/* *************************************************************************************************
* Tomorrow
*
* Returns first tree letters of tomorrows day name for use on screen
* *************************************************************************************************/
String Tomorrow() {
String localdays[] = MSG_LOCAL_DAYNAMES;
if (Weather_Daynr == 6)
return localdays[0].substring(0,3);
else
return localdays[Weather_Daynr + 1].substring(0,3);
}
/* *************************************************************************************************
* MonthNr
*
* Convert monthname (3 char) from http header date to int 1..12
* *************************************************************************************************/
int MonthNr(String Mon) {
String maanden = "___JanFebMarAprMayJunJulAugSepOctNovDec";
return maanden.indexOf(Mon)/3;
}
/* *************************************************************************************************
* Parse_Header_Date
*
* Get the header date (http RFC) and the place from the json and transform this in a nice
* day, place hh:mm message to display on the screen
* *************************************************************************************************/
String Parse_Header_Date(String weerdatum,String plaats) {
// Sat, 23 Feb 2019 19:47:09 GMT
String DateDay = weerdatum.substring(0,weerdatum.indexOf(',')); // Sat
weerdatum = weerdatum.substring(weerdatum.indexOf(',')+2);
int DateDom = weerdatum.substring(0,weerdatum.indexOf(' ')).toInt(); // 23
weerdatum = weerdatum.substring(weerdatum.indexOf(' ')+1);
String DateMon = weerdatum.substring(0,weerdatum.indexOf(' ')); // Feb
weerdatum = weerdatum.substring(weerdatum.indexOf(' ')+1);
int DateYr = weerdatum.substring(0,weerdatum.indexOf(' ')).toInt(); // 2019
weerdatum = weerdatum.substring(weerdatum.indexOf(' ')+1);
int DateHr = weerdatum.substring(0,weerdatum.indexOf(':')).toInt(); // 19
weerdatum = weerdatum.substring(weerdatum.indexOf(':')+1);
int DateMin = weerdatum.substring(0,weerdatum.indexOf(':')).toInt(); // 47
weerdatum = weerdatum.substring(weerdatum.indexOf(':')+1);
DateHr = CorrectTimeZoneDST(DateYr, DateMon, DateDom, DateHr, DateMin);
Display_Power = ((DateHr <= OFF_HOUR) and (DateHr >= ON_HOUR));
return TranslateDayName(DateDay) + ", " + plaats + " " + Align(2,"0",String(DateHr)) + ":" + Align(2,"0",String(DateMin));
}
/* *************************************************************************************************
* Parse_Rain_Message
*
* Get the plain text rain forecast data for next 2 hours. Format will be like
* 087|13:20 -> Consists of rain level 0-255, vertical bar, and time in hh:mm format
* 099|13:25 -> rain formula:
* 092|13:30
* (etc, every 5 minutes for the next 2 hours, so 24 forecast points)
* *************************************************************************************************/
void Parse_Rain_Message(String payload) {
/* Loop thru all lines, End of line is shown by the 0x0a character. toggle between buffer and
* not to buffer, depending on if we have seen the vertical bar
*/
String buf = "";
bool tobuf = true;
int tel = 0;
Rainmax = 0;
for(int i=0;i Rainmax)
Rainmax = Rain[tel];
tel++;
buf = "";
tobuf = true;
} else {
if (payload.charAt(i) == 0x7C) tobuf = false; // vertical bar
if (tobuf) buf = buf + payload.charAt(i);
}
}
}
/* *************************************************************************************************
* Parse_Weather_Message
*
* Get the JSON weather string and the date from the http header and create global var's from it
* *************************************************************************************************/
void Parse_Weather_Message(String headerdate, String payload) {
DeserializationError err = deserializeJson(doc, payload);
Serial.print("DeserializeJson() ended with code ");
Serial.println(err.c_str());
if (err) {
Weather_error = "J" +String(err.c_str());
return; // Do not continue with this message
}
JsonObject obj = doc.as(); // http://weerlive.nl/api/json-10min.php?locatie=Valkenswaard
Weather_error = "W200"; // dummy // { "liveweer":
// [{
Weather_place = obj["liveweer"][0]["plaats"].as(); // "plaats": "Valkenswaard",
Weather_temp = obj["liveweer"][0]["temp"].as(); // "temp": "8.1",
// "gtemp": "6.2",
Weather_summary = obj["liveweer"][0]["samenv"].as(); // "samenv": "Onbewolkt",
// "lv": "56",
Weather_winddir = obj["liveweer"][0]["windr"].as(); // "windr": "ZO",
// "windms": "3",
// "winds": "2",
// "windk": "5.8",
Weather_windkmh = obj["liveweer"][0]["windkmh"].as(); // "windkmh": "10.8",
// "luchtd": "1036.3",
// "ldmmhg": "777",
// "dauwp": "-0",
// "zicht": "33",
Weather_forecast = obj["liveweer"][0]["verw"].as(); // "verw": "Helder, morgen zonnig en zacht",
Weather_sup = obj["liveweer"][0]["sup"].as(); // "sup": "07:33",
Weather_sunder = obj["liveweer"][0]["sunder"].as(); // "sunder": "18:09",
Weather_image = obj["liveweer"][0]["image"].as(); // "image": "helderenacht",
// "d0weer": "halfbewolkt",
Weather_d0tmax = obj["liveweer"][0]["d0tmax"].as(); // "d0tmax": "13",
Weather_d0tmin = obj["liveweer"][0]["d0tmin"].as(); // "d0tmin": "1",
// "d0windk": "2",
// "d0windknp": "4",
// "d0windms": "2",
// "d0windkmh": "7",
// "d0windr": "ZO",
Weather_d0rain = obj["liveweer"][0]["d0neerslag"].as(); // "d0neerslag": "0",
Weather_d0sun = obj["liveweer"][0]["d0zon"].as(); // "d0zon": "73",
// "d1weer": "halfbewolkt",
Weather_d1tmax = obj["liveweer"][0]["d1tmax"].as(); // "d1tmax": "13",
Weather_d1tmin = obj["liveweer"][0]["d1tmin"].as(); // "d1tmin": "1",
// "d1windk": "2",
// "d1windknp": "4",
// "d1windms": "2",
// "d1windkmh": "7",
// "d1windr": "ZO",
Weather_d1rain = obj["liveweer"][0]["d1neerslag"].as(); // "d1neerslag": "0",
Weather_d1sun = obj["liveweer"][0]["d1zon"].as(); // "d1zon": "73",
// "alarm": "0"
// }]
// }
Date_Of_Today = Parse_Header_Date(headerdate,Weather_place);
}
/* *************************************************************************************************
* Get_The_Weather_Now
*
* Time to retrieve the weather info again
* *************************************************************************************************/
void Get_The_Weather_Now() {
http.begin(HTTPADRESS);
delay(2000); // wait to ensure all went well
http.collectHeaders(headerKeys, numberOfHeaders);
int httpCode = http.GET();
Serial.println("Weather check at " + String(HTTPADRESS) + " ended with http code: " + String(httpCode));
Weather_error = "W" + String(httpCode);
if (httpCode == 200) {
String headerdate = http.header("date"); // Sat, 23 Feb 2019 19:47:09 GMT
String payload = http.getString();
http.end(); // end comm. first
Serial.println("=> " + payload);
Parse_Weather_Message(headerdate,payload);
} else {
http.end(); // try again next time
}
}
/* *************************************************************************************************
* Get_Rain_Forecast
*
* Time to retrieve the rain forecast
* *************************************************************************************************/
void Get_Rain_Forecast() {
http.begin(RAINFORECAST);
int httpCode = http.GET();
delay(2000); // wait to ensure all went well
Serial.println("Rain forecast at " + String(RAINFORECAST) + " ended with http code: " + String(httpCode));
Rain_error = "R" + String(httpCode);
if (httpCode == 200) {
String payload = http.getString();
http.end(); // end comm. first
Serial.println("=> " + payload);
Parse_Rain_Message(payload);
} else {
http.end(); // try again next time
}
}
/* *************************************************************************************************
* loop
*
* Keep doing this until we loose power
* *************************************************************************************************/
void loop() {
if (Display_Power) {
digitalWrite(BACKLIGHT, HIGH);
Show_Weather();
delay(SCREEN_CHANGE_TIMEOUT);
Show_Rain();
delay(SCREEN_CHANGE_TIMEOUT);
}
else {
Serial.println("Zzzzzz....");
digitalWrite(BACKLIGHT, LOW);
delay(SCREEN_CHANGE_TIMEOUT * 2);
}
}
/* *************************************************************************************************
* FINISH
* *************************************************************************************************/