├── ESP32_OWM_WX_Server_v3.1.ino ├── ESP8266_OWM_WX_Server_v3.ino ├── ESPweather_v3_es.JPG ├── README.md ├── WX_V2-1.jpg ├── credentials_EN.h ├── credentials_ES.h └── owm_credentials.h /ESP32_OWM_WX_Server_v3.1.ino: -------------------------------------------------------------------------------- 1 | /* ESP32 Weather Server, gets data from Open Weather Map, decodes and then displays it on a webpage. 2 | Uses mDNS service to allow a logical name to be used e.g. http://Wxserver.local instead of http://192.168.0.16/ 3 | #################################################################################################################################### 4 | This software, the ideas and concepts is Copyright (c) David Bird 2021. All rights to this software are reserved. 5 | 6 | Any redistribution or reproduction of any part or all of the contents in any form is prohibited other than the following: 7 | 1. You may print or download to a local hard disk extracts for your personal and non-commercial use only. 8 | 2. You may copy the content to individual third parties for their personal use, but only if you acknowledge the author David Bird as the source of the material. 9 | 3. You may not, except with my express written permission, distribute or commercially exploit the content. 10 | 4. You may not transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes. 11 | 12 | The above copyright ('as annotated') notice and this permission notice shall be included in all copies or substantial portions of the Software and where the 13 | software use is visible to an end-user. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT. FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY 16 | OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | See more at http://www.dsbird.org.uk 20 | */ 21 | 22 | 23 | //LANGUAGES 24 | #include "credentials_EN.h" // See "credentials_EN.h" tab and enter your OWM API key and set the Wifi SSID and PASSWORD 25 | //#include "credentials_ES.h" // o la pestaña "credentials_ES.h" e introduce tu propia clave de OWM API y ajusta Wifi SSID y PASSWORD#include "credentials_ES_PRIVADA.h" 26 | 27 | 28 | #include // https://github.com/bblanchon/ArduinoJson Install by: Sketch/ Include Library / Manage Libaries / enter 'JSON', find, select then install 29 | #include // Built-in 30 | #include // Built-in 31 | #include "time.h" // Built-in 32 | #include "ESPmDNS.h" // Built-in 33 | #include // Built-in 34 | #include // Built-in 35 | //######################################################################################## 36 | //Languagetranslation values, adjust "text" values for your language 37 | 38 | //Temperature, Dewpoint, Humidity, Windchill, HeatIndex, Windspeed, Pressure, Rainfall, Snowfall 39 | String TXT_FORECAST_GRAPHS = "5 day / 3 hour forecast"; 40 | String TXT_TEMPDEWPOINT = "Temperature / Dewpoint"; 41 | String TXT_TEMPERATURE = "Temperature"; 42 | String TXT_FEELSLIKE = "Feelslike"; 43 | String TXT_HUMIDITY = "Humidity"; 44 | String TXT_DEWPOINT = "Dewpoint"; 45 | String TXT_WINDCHILL = "Windchill"; 46 | String TXT_WCHILL_HINDEX = "Windchill / Heat Index"; 47 | String TXT_HEATINDEX = "Heat Index"; 48 | String TXT_WINDSPEED = "Windspeed"; 49 | String TXT_PRESSURE = "Pressure"; 50 | String TXT_CLOUDCOVER = "Cloud Cover"; 51 | String TXT_VISIBILITY = "Visibility"; 52 | String TXT_PRECIPITATION = "Precipitation"; 53 | String TXT_RAINFALL = "Rainfall"; 54 | String TXT_RAINRATE = "Rainrate"; 55 | String TXT_SNOWFALL = "Snowfall"; 56 | String TXT_SNOWRATE = "Snowrate"; 57 | String TXT_UVINDEX = "UV Index"; 58 | //Moon phases 59 | String TXT_MOON_PHASE1 = "New"; 60 | String TXT_MOON_PHASE2 = "Waxing Crescent"; 61 | String TXT_MOON_PHASE3 = "First Quarter"; 62 | String TXT_MOON_PHASE4 = "Waxing Gibbous"; 63 | String TXT_MOON_PHASE5 = "Full"; 64 | String TXT_MOON_PHASE6 = "Waning Gibbous"; 65 | String TXT_MOON_PHASE7 = "Third Quarter"; 66 | String TXT_MOON_PHASE8 = "Waning Crescent"; 67 | //General 68 | const char* TXT_UPDATED = ""; // or "Updated:" 69 | //Wind 70 | String TXT_N = "N"; 71 | String TXT_NE = "NE"; 72 | String TXT_E = "E"; 73 | String TXT_SE = "SE"; 74 | String TXT_S = "S"; 75 | String TXT_SW = "SW"; 76 | String TXT_W = "W"; 77 | String TXT_NW = "NW"; 78 | enum WindspdType {MPS, KPH, MPH, KTS}; // metres-per-second, kilometres-per-hour, miles-per-hour, knots 79 | WindspdType WindspeedUnits = MPH; // Change to your desired display format e.g. for KPH 'MetricWindspeed = KPH'; 80 | String TXT_WINDUNITS = "mph"; // Ensure text here matahces units chosen above! Use a leading space 81 | //Windspeed warning colours based on Beaufort scale in kph *** NOTE these are HTML colours, don't translate! 82 | String Calm = "white"; // <1 km/hr Force 0 83 | String LightAir = "lightcyan"; // 1-5 km/hr Force 1 84 | String LightBreeze = "paleturquoise"; // 6-11 km/hr Force 2 85 | String GentleBreeze = "aquamarine"; // 12-19 km/hr Force 3 86 | String ModerateBreeze = "turquoise"; // 20-28 km/hr Force 4 87 | String FreshBreeze = "mediumturquoise"; // 29-38 km/hr Force 5 88 | String StrongBreeze = "darkturquoise"; // 39-49 km/hr Force 6 89 | String HighWind = "lightseagreen"; // 50-61 km/hr Force 7 90 | String Gale = "yellow"; // 62-74 km/hr Force 8 91 | String SevereGale = "gold"; // 75-88 km/hr Force 9 92 | String Storm = "orange"; // 89-102 km/hr Force 10 93 | String ViolentStorm = "red"; // 103-117 km/hr Force 11 94 | String Hurricane = "crimson"; // >118 km/hr Force 12 95 | String TXT_BFS0 = "Calm"; 96 | String TXT_BFS1 = "Light Air"; 97 | String TXT_BFS2 = "Light Breeze"; 98 | String TXT_BFS3 = "Gentle Breeze"; 99 | String TXT_BFS4 = "Moderate Breeze"; 100 | String TXT_BFS5 = "Fresh Breeze"; 101 | String TXT_BFS6 = "Strong Breeze"; 102 | String TXT_BFS7 = "High Wind"; 103 | String TXT_BFS8 = "Gale"; 104 | String TXT_BFS9 = "Severe Gale"; 105 | String TXT_BFS10 = "Storm"; 106 | String TXT_BFS11 = "Violent Storm"; 107 | String TXT_BFS12 = "Hurricane"; 108 | //Days of the week 109 | String TXT_SUN = "Sun"; 110 | String TXT_MON = "Mon"; 111 | String TXT_TUE = "Tue"; 112 | String TXT_WED = "Wed"; 113 | String TXT_THU = "Thu"; 114 | String TXT_FRI = "Fri"; 115 | String TXT_SAT = "Sat"; 116 | const char* weekday_D[] = { TXT_SUN.c_str(), TXT_MON.c_str(), TXT_TUE.c_str(), TXT_WED.c_str(), TXT_THU.c_str(), TXT_FRI.c_str(), TXT_SAT.c_str() }; 117 | //Months 118 | String TXT_JAN = "Jan"; 119 | String TXT_FEB = "Feb"; 120 | String TXT_MAR = "Mar"; 121 | String TXT_APR = "Apr"; 122 | String TXT_MAY = "May"; 123 | String TXT_JUN = "Jun"; 124 | String TXT_JUL = "Jul"; 125 | String TXT_AUG = "Aug"; 126 | String TXT_SEP = "Sep"; 127 | String TXT_OCT = "Oct"; 128 | String TXT_NOV = "Nov"; 129 | String TXT_DEC = "Dec"; 130 | const char* month_M[] = { TXT_JAN.c_str(), TXT_FEB.c_str(), TXT_MAR.c_str(), TXT_APR.c_str(), TXT_MAY.c_str(), TXT_JUN.c_str(), TXT_JUL.c_str(), TXT_AUG.c_str(), TXT_SEP.c_str(), TXT_OCT.c_str(), TXT_NOV.c_str(), TXT_DEC.c_str() }; 131 | //Graph Colours 132 | const String legendColour = "black"; // Only use HTML colour names 133 | const String titleColour = "purple"; 134 | const String backgrndColour = "gainsboro"; 135 | const String data1Colour = "blue"; 136 | const String data2Colour = "red"; 137 | // High Low and Relative Humidity 138 | String TXT_HI = "Hi"; 139 | String TXT_LO = "Lo"; 140 | String TXT_RH = "RH"; 141 | // Menu items 142 | String TXT_HOME = "Home"; 143 | String TXT_WEATHERMAP = "Weather Map"; 144 | String TXT_SYSTEMSETUP = "System Setup"; 145 | String TXT_PREFILLSETTINGS = "Pre-Fill Settings"; 146 | String TXT_HELP = "Help"; 147 | String TXT_LANGUAGE_TABLE = "Language Translation Table"; 148 | 149 | int PageWidth = 1024; // Adjust for desired webpage width 150 | int ForecastPeriods = 9; // Adjust number of forecast periods to display, maximum 96hrs (96/3 / 8 = 4-days 151 | //################ VERSION ########################################### 152 | String version = "3.0"; // Programme version, see change log at end 153 | 154 | //################ PROGRAM VARIABLES and OBJECTS ################ 155 | String Time_str, Date_str, webpage, Wx_Description, WindUnits = " mps", DataFile = "params.txt"; // strings for Time, Date and webpage, etc 156 | 157 | #define max_readings 40 // OWM provides 5-days of every 3-hours data 158 | #define max_periods 9 // maximum 3-hour display items 159 | 160 | typedef struct { // For current Day and then Day 1, 2, 3, etc 161 | int Dt; 162 | String Icon; 163 | String Description; 164 | String Description2; 165 | String Time; 166 | String Trend; 167 | float uvIndex; 168 | float Temperature; 169 | float Feelslike; 170 | float Humidity; 171 | float High; 172 | float Low; 173 | float Winddir; 174 | float Windspeed; 175 | float Gust; 176 | float Rainfall; 177 | float Rainrate; 178 | float Snowfall; 179 | float Snowrate; 180 | float Pressure; 181 | float Pop; 182 | int Cloudcover; 183 | int Visibility; 184 | int Sunrise; 185 | int Sunset; 186 | } Forecast_record_type; 187 | 188 | Forecast_record_type WxConditions[1]; 189 | Forecast_record_type WxForecast[max_readings]; 190 | 191 | float pressure_readings[max_readings] = {0}; 192 | float temperature_readings[max_readings] = {0}; 193 | float humidity_readings[max_readings] = {0}; 194 | float dewpoint_readings[max_readings] = {0}; 195 | float windspeed_readings[max_readings] = {0}; 196 | float windgust_readings[max_readings] = {0}; 197 | float windchill_readings[max_readings] = {0}; 198 | float heatindex_readings[max_readings] = {0}; 199 | float rain_readings[max_readings] = {0}; 200 | float snow_readings[max_readings] = {0}; 201 | int wait = 0; 202 | int update_time = 300000; // Every 5-mins in milli-seconds 203 | bool Refresh_WXData = true; 204 | 205 | WebServer webserver(80); // Set the port you wish to use, a browser default is 80, but any port can be used, if you set it to 5555 then connect with http://nn.nn.nn.nn:5555/ 206 | WiFiClient client; // wifi client object 207 | 208 | const char* ServerName = "wxserver"; // Connect to the server with http://wxserver.local/ e.g. name = "myserver" then use address http://myserver.local/ 209 | 210 | //######################################################################################## 211 | void setup() { 212 | Serial.begin(115200); 213 | delay(200); 214 | Serial.println(__FILE__); 215 | Serial.println("Starting..."); 216 | StartSPIFFS(); 217 | RecoverSettings(); // NOTE: After programming SPIFFS is erased 218 | //SPIFFS.remove("/"+DataFile); // In-case SPIFFS file gets corrupted, it can happen! 219 | if (StartWiFi() == WL_CONNECTED) { // Start the WiFi service 220 | Serial.println("WiFi Started..."); 221 | } 222 | SetupDeviceName(ServerName); // Set up the logical name 223 | if (SetupTime() == true) { 224 | Serial.println("Time started..."); // Start the time services 225 | } 226 | webserver.on("/", Homepage); // Process a homepage request 227 | webserver.on("/wxmap", Weathermap); // Process a weather map request 228 | webserver.on("/setup", SystemSetup); // Process a setup request 229 | webserver.on("/prefillEN", PreFill); // Process a prefill data request 230 | webserver.on("/handleinput", HandleInput); // Process inputs from the client 231 | webserver.on("/help", Help); // Process a help request 232 | webserver.onNotFound(handleNotFound); // Handle page not found errors 233 | webserver.begin(); // Start the webserver 234 | Refresh_WXData = true; // Get the current weather data 235 | } 236 | //######################################################################################### 237 | void loop() { 238 | webserver.handleClient(); // Wait for a client to connect and when they do process their requests 239 | if (millis() > update_time + wait || Refresh_WXData == true) { // Wait for time-out or request for data 240 | UpdateData(); 241 | } 242 | } 243 | //######################################################################################### 244 | void UpdateData() { 245 | Refresh_WXData = false; 246 | byte Attempts = 1; 247 | bool RxWeather = false, 248 | RxForecast = false, 249 | RxUVindex = false; 250 | WiFiClient client; // WiFi client object 251 | while ((RxWeather == false || RxForecast == false || RxUVindex == false) && Attempts <= 2) { // Try up-to 2 times for Weather and Forecast data 252 | if (RxWeather == false) RxWeather = obtain_wx_data(client, "weather"); 253 | if (RxForecast == false) RxForecast = obtain_wx_data(client, "forecast"); 254 | if (RxUVindex == false) RxUVindex = obtain_wx_data(client, "onecall"); 255 | Attempts++; 256 | } 257 | int r = 0; 258 | do { 259 | if (Units == "I") pressure_readings[r] = WxForecast[r].Pressure * 0.02953; else pressure_readings[r] = WxForecast[r].Pressure; 260 | temperature_readings[r] = WxForecast[r].Temperature; 261 | humidity_readings[r] = WxForecast[r].Humidity; 262 | dewpoint_readings[r] = DewPoint(temperature_readings[r], humidity_readings[r]); 263 | windspeed_readings[r] = Convert_to_WS_Units(WxForecast[r].Windspeed); 264 | windgust_readings[r] = Convert_to_WS_Units(WxForecast[r].Gust); 265 | windchill_readings[r] = calc_windchill(temperature_readings[r], windspeed_readings[r]); 266 | heatindex_readings[r] = calc_heatindex(temperature_readings[r], humidity_readings[r]); 267 | if (Units == "I") rain_readings[r] = WxForecast[r].Rainfall * 0.0393701; else rain_readings[r] = WxForecast[r].Rainfall; 268 | if (Units == "I") snow_readings[r] = WxForecast[r].Snowfall * 0.0393701; else snow_readings[r] = WxForecast[r].Snowfall; 269 | r++; 270 | } while (r < max_readings); 271 | WxConditions[0].Windspeed = Convert_to_WS_Units(WxConditions[0].Windspeed); 272 | WxConditions[0].Gust = Convert_to_WS_Units(WxConditions[0].Gust); 273 | int HeatIndexAverage = 0; 274 | r = 0; do { 275 | HeatIndexAverage += heatindex_readings[r]; 276 | r++; 277 | } while (r < max_readings); 278 | HeatIndexAverage /= max_readings; 279 | if ((Units == "M" && HeatIndexAverage <= 28) || (Units == "I" && HeatIndexAverage <= 82)) { // Heatindex only applies if Temperature >= 28°C or >=82°F 280 | r = 0; do { 281 | heatindex_readings[r] = 0; // Don't display Heat Index when average temperature is out of bounds 282 | r++; 283 | } while (r < max_readings); 284 | } 285 | UpdateLocalTime(); 286 | wait = millis(); 287 | } 288 | //######################################################################################### 289 | void Homepage() { 290 | String WDirArrow = ""; 291 | Wx_Description = TitleCase(WxConditions[0].Description); 292 | if (WxConditions[0].Description2 != "") Wx_Description += ", " + TitleCase(WxConditions[0].Description2); 293 | append_page_header(true); 294 | webpage += "

" + Wx_Description + "

"; 295 | webpage += "
"; 296 | webpage += "
"; 297 | webpage += " wx"; 298 | webpage += "
"; 299 | webpage += "
"; 300 | webpage += " "; // Opened table-1 301 | webpage += " "; 302 | webpage += " "; 303 | webpage += " "; 304 | webpage += " "; 305 | webpage += " "; 306 | webpage += " "; 307 | webpage += " "; 308 | webpage += " "; 309 | webpage += " "; 310 | webpage += " "; 311 | webpage += " "; 312 | webpage += "
" + String(WxConditions[0].Temperature, 0) + "°
" + TXT_HI + "" + TXT_LO + "" + TXT_RH + "
" + String(WxConditions[0].High, 0) + "°" + String(WxConditions[0].Low, 0) + "°" + String(WxConditions[0].Humidity, 0) + "%
"; // Closed table-1 313 | webpage += "
"; 314 | webpage += "
"; 315 | webpage += " ";// Opened table-2 316 | webpage += " "; 317 | webpage += " "; 318 | webpage += " "; 319 | webpage += " "; 320 | webpage += " "; 321 | webpage += " "; 322 | webpage += " "; 323 | webpage += " "; 324 | webpage += " "; 325 | webpage += " "; 326 | webpage += " "; 327 | webpage += " "; 328 | webpage += " "; 329 | webpage += " "; 330 | webpage += " "; 331 | webpage += " "; 332 | webpage += " "; 333 | webpage += " "; 334 | webpage += " "; 335 | webpage += " "; 336 | if (WxConditions[0].Rainrate > 0) { 337 | webpage += " "; 338 | webpage += " "; 339 | webpage += " "; 340 | webpage += " "; 341 | } 342 | if (WxConditions[0].Snowrate > 0) { 343 | webpage += " "; 344 | webpage += " "; 345 | webpage += " "; 346 | webpage += " "; 347 | } 348 | webpage += " "; 349 | webpage += " "; 350 | webpage += " "; 351 | webpage += " "; 352 | webpage += "
" + TXT_PRESSURE + "" + String(WxConditions[0].Pressure, (Units == "M" ? 0 : 1)) + WxConditions[0].Trend + "
" + TXT_FEELSLIKE + "" + String(WxConditions[0].Feelslike, 0) + "°
" + TXT_DEWPOINT + "" + String(DewPoint(WxConditions[0].Temperature, WxConditions[0].Humidity), 0) + "°
" + TXT_CLOUDCOVER + "" + String(WxConditions[0].Cloudcover) + "%
" + TXT_VISIBILITY + "" + String(WxConditions[0].Visibility * (Units == "M" ? 1 : 1.0936133), 0) + String(Units == "M" ? " M" : " Yds") + "
" + TXT_RAINRATE + "" + String(WxConditions[0].Rainrate * (Units == "M" ? 1 : 1.0936133), 1) + String(Units == "M" ? " mm/Hr" : " in/Hr") + "
" + TXT_SNOWRATE + "" + String(WxConditions[0].Snowrate * (Units == "M" ? 1 : 1.0936133), 1) + String(Units == "M" ? " mm/Hr" : " in/Hr") + "
" + TXT_UVINDEX + "" + String(round(WxConditions[0].uvIndex), 0) + " (" + UVIndexLevel(round(WxConditions[0].uvIndex)) + ")
"; // Closed table-2 353 | webpage += "
"; 354 | webpage += "
"; 355 | webpage += " "; // Opened table-3 356 | webpage += " "; 357 | webpage += " "; 358 | webpage += " "; 359 | webpage += " "; 360 | webpage += " "; 362 | webpage += " "; 363 | webpage += " "; 364 | webpage += " "; 365 | webpage += " "; 366 | webpage += "
" + WindDegToDir(WxConditions[0].Winddir, &WDirArrow) + " / " + String(WxConditions[0].Winddir, 0) + "°" + WDirArrow + "
"; 361 | webpage += String(WxConditions[0].Windspeed, WindspeedUnits == MPS ? 1 : 0) + " / " + String(WxConditions[0].Gust, WindspeedUnits == MPS ? 1 : 0) + " " + TXT_WINDUNITS + "
" + WindToName(WxConditions[0].Windspeed) + "
"; // Closed table-3 367 | webpage += "
"; 368 | webpage += "
"; 369 | webpage += ""; // Opened table-4 370 | webpage += " "; 371 | webpage += " "; 372 | webpage += " "; 373 | webpage += " "; 374 | webpage += " "; 375 | webpage += "
"; SunrisesetIcon(); webpage += "↑ " + ConvertUnixTime(WxConditions[0].Sunrise).substring(0, 5) + ""; SunrisesetIcon(); webpage += "↓ " + ConvertUnixTime(WxConditions[0].Sunset).substring(0, 5) + ""; webpage += GetMoonImage(); webpage += " " + GetMoonPhase() + "
"; // Closed table-4 376 | /// Next section every 3-hours 377 | webpage += "
"; 378 | webpage += "
"; 379 | webpage += " "; // Opened table-5 380 | webpage += " "; 381 | int r = 0; 382 | r = 0; do { 383 | if (r < max_periods - 1) webpage += ""; 384 | else webpage += ""; 385 | r++; 386 | } while (r < max_periods); 387 | webpage += " "; 388 | webpage += " "; 389 | r = 0; do { 390 | if (r < max_periods - 1) webpage += ""; 391 | else webpage += ""; 392 | r++; 393 | } while (r < max_periods); 394 | webpage += " "; 395 | webpage += " "; 396 | r = 0; do { 397 | if (r < max_periods - 1) webpage += ""; 398 | else webpage += ""; 399 | r++; 400 | } while (r < max_periods); 401 | webpage += " "; 402 | webpage += " "; 403 | r = 0; do { 404 | if (r < max_periods - 1) webpage += ""; 405 | else webpage += ""; 406 | r++; 407 | } while (r < max_periods); 408 | webpage += " "; 409 | webpage += " "; 410 | r = 0; do { 411 | webpage += " "; 412 | webpage += " "; 413 | if (r < max_periods - 1) webpage += ""; 414 | else webpage += ""; 415 | r++; 416 | } while (r < max_periods); 417 | webpage += " "; 418 | webpage += " "; 419 | r = 0; do { 420 | WindDegToDir(WxForecast[r].Winddir, &WDirArrow); 421 | webpage += ""; 423 | r++; 424 | } while (r < max_periods); 425 | webpage += " "; 426 | webpage += "
" + DayOfWeek(WxForecast[r].Dt) + " (" + HourMinute(WxForecast[r].Dt) + ")" + DayOfWeek(WxForecast[r].Dt) + " (" + HourMinute(WxForecast[r].Dt) + ")
Forecast SymbolForecast Symbol
" + String(WxForecast[r].Temperature, 0) + "°" + String(WxForecast[r].Temperature, 0) + "°
" + RainDrop() + "" + String(WxForecast[r].Pop * 100, 0) + "%" + RainDrop() + "" + String(WxForecast[r].Pop * 100, 0) + "%
" + String(WxForecast[r].High, 0) + "°" + String(WxForecast[r].Low, 0) + "°" + String(WxForecast[r].Humidity, 0) + "%" + String(WxForecast[r].Humidity, 0) + "%
"; 422 | webpage += String(windspeed_readings[r], WindspeedUnits == MPS ? 1 : 0) + WDirArrow + "
"; // Closed table-5 427 | webpage += "

"; 428 | // Next section Beaufort table display 429 | webpage += ""; 430 | webpage += " "; 431 | webpage += " "; 432 | webpage += " "; 433 | webpage += " "; 434 | webpage += " "; 435 | webpage += " "; 436 | webpage += " "; 437 | webpage += " "; 438 | webpage += " "; 439 | webpage += " "; 440 | webpage += " "; 441 | webpage += " "; 442 | webpage += " "; 443 | webpage += " "; 444 | webpage += " "; 445 | webpage += " "; 446 | webpage += "
Beaufort Scale0123456789101112
"; 447 | add_graphs(); 448 | append_page_footer(); 449 | webserver.send(200, "text/html", webpage); 450 | webpage = ""; 451 | } 452 | //######################################################################################### 453 | void Weathermap() { 454 | append_page_header(true); 455 | webpage += "

Weather Map

"; 456 | webpage += ""; 458 | append_page_footer(); 459 | webserver.send(200, "text/html", webpage); 460 | webpage = ""; 461 | } 462 | //######################################################################################### 463 | void Help() { 464 | append_page_header(true); 465 | webpage += "
"; 466 | webpage += "

System Set-up

"; 467 | webpage += "1. Enter your City/Location name, choose any name.
"; 468 | webpage += "2. Enter your location Latitude and Longitude, e.g. 51.38, -2.33
"; 469 | webpage += "   - Use negative numbers for South of the Equator and East of the Meridian.
"; 470 | webpage += "3. Enter your Language e.g. 'en', see below for OWM supported languages.
"; 471 | webpage += "4. Enter your locations Hemisphere, North or South.
"; 472 | webpage += "5. Enter your desired wind units either MPS (Metres-per-Second), MPH (Miles-per-hour), KPH (Kilometres-per-hour) or KTS (Knots)
"; 473 | webpage += "6. Enter your desired Units either Metric or Imperial.
"; 474 | webpage += "7. Enter the text for each item in the Language Translation Table. e.g. for 'de' 'Pressure' enter 'Druck'
"; 475 | webpage += "    - For months you could use '01' for JAN, etc.
"; 476 | webpage += "8. Click on ENTER to save
"; 477 | webpage += "

Open Weather Map supports the following languages:

"; 478 | webpage += "
"; 479 | webpage += "af Afrikaans, al Albanian, ar Arabic, az Azerbaijani
"; 480 | webpage += "bg Bulgarian
"; 481 | webpage += "ca Catalan, cz Czech
"; 482 | webpage += "da Danish, de German
"; 483 | webpage += "el Greek, en English, eu Basque
"; 484 | webpage += "fa Persian (Farsi), fi Finnish, fr French
"; 485 | webpage += "gl Galician
"; 486 | webpage += "he Hebrew, hi Hindi, hr Croatian, hu Hungarian
"; 487 | webpage += "id Indonesian, it Italian
"; 488 | webpage += "ja Japanese
"; 489 | webpage += "kr Korean
"; 490 | webpage += "
"; 502 | webpage += "    - Example: German set lauguage = 'de'
"; 503 | webpage += "

Weather descriptions used and translated by OpenWeatherMap:

"; 504 | webpage += "Clear sky, Few clouds, Scattered clouds, Broken clouds, Shower rain, Rain, Heavy Intensity Rain, Moderate Rain, Light Rain, Thunderstorm, Snow, Mist
"; 505 | webpage += "

UV Index

"; 506 | webpage += "The UV index (or ultraviolet index) is an international standard for measuring the ultraviolet radiation at a particular place and time.
"; 507 | webpage += "    - Note: OWM issues the UV index at 1200 / Midday for the next 24-hours
"; 508 | webpage += "

Moon Phases

"; 509 | webpage += ""; 510 | webpage += ""; 511 | webpage += ""; 512 | webpage += ""; 513 | webpage += ""; 514 | webpage += ""; 515 | webpage += ""; 516 | webpage += ""; 517 | webpage += ""; 518 | webpage += "
Phase-1New
Phase-2Waxing Crescent
Phase-3First Quarter
Phase-4Waxing Gibbous
Phase-5Full
Phase-6Waning Gibbous
Phase-7Last Quarter
Phase-8Waning Crescent
"; 519 | webpage += "

Beafort Scale wind speeds

"; 520 | webpage += ""; 521 | webpage += ""; 522 | webpage += ""; 523 | webpage += ""; 524 | webpage += ""; 525 | webpage += ""; 526 | webpage += ""; 527 | webpage += ""; 528 | webpage += ""; 529 | webpage += ""; 530 | webpage += ""; 531 | webpage += ""; 532 | webpage += ""; 533 | webpage += ""; 534 | webpage += "
Force-0'Calm'<1 knot <1 mph <2 km/h <0.5 m/s
Force-1'Light Air'1-3 knots 1-3 mph 2-5 km/h 0.5-1.5 m/s
Force-2'Light Breeze'4-6 knots 4-7 mph 6-11 km/h 1.6-3.3 m/s
Force-3'Gentle Breeze'7-10 knots 8-12 mph 12-19 km/h 3.4-5.5 m/s
Force-4'Moderate Breeze'11-16 knots 13-18 mph 20-28 km/h 5.6-7.9 m/s
Force-5'Fresh Breeze'17-21 knots 19-24 mph 29-38 km/h 8.0-10.7 m/s
Force-6'Strong Breeze'22-27 knots 25-31 mph 39-48 km/h 10.8-13.8 m/s
Force-7'High Wind'28-33 knots 32-38 mph 50-61 km/h 13.9-17.1 m/s
Force-8'Gale'34-40 knots 39-46 mph 62-74 km/h 17.2-20.7 m/s
Force-9'Severe Gale'41-47 knots 47-54 mph 75-88 km/h 20.8-24.4 m/s
Force-10'Storm'48-55 knots 55-63 mph 89-102 km/h 24.5-28.4 m/s
Force-11'Violent Storm'56-63 knots 64-72 mph 103-117 km/h 28.5-32.6 m/s
Force-12'Hurricane'>64 knots >73 mph >118 km/h > 32.6 m/s
"; 535 | webpage += "

OWM API Documentation

"; 536 | webpage += "

See the OpenWeatherMap site for more details of their API data: Here

"; 537 | webpage += "
"; 538 | append_page_footer(); 539 | webserver.send(200, "text/html", webpage); 540 | webpage = ""; 541 | } 542 | //######################################################################################### 543 | void SystemSetup() { 544 | String Windunits = ""; 545 | if (WindspeedUnits == MPS) Windunits = "mps"; 546 | if (WindspeedUnits == MPH) Windunits = "mph"; 547 | if (WindspeedUnits == KPH) Windunits = "kph"; 548 | if (WindspeedUnits == KTS) Windunits = "kts"; 549 | append_page_header(true); 550 | webpage += "

System Setup, change entries then Enter

"; 551 | webpage += "
"; 552 | webpage += "
"; 553 | webpage += " System Setup Values: "; 554 | webpage += " "; 555 | webpage += " "; 556 | webpage += " "; 557 | webpage += " "; 558 | webpage += " "; 559 | webpage += " "; 560 | webpage += " "; 561 | webpage += " "; 562 | webpage += " "; 563 | webpage += " "; 567 | webpage += " "; 568 | webpage += " "; 574 | webpage += " "; 575 | webpage += " "; 579 | webpage += "
City:Lat:Lon:
Language:Hemisphere:Wind Units:Units:
"; 580 | webpage += "
"; 581 | // Language translations 582 | webpage += "
"; 583 | //webpage += ""; 584 | webpage += "
"; 585 | webpage += " " + TXT_LANGUAGE_TABLE + ":"; 586 | webpage += " "; 587 | webpage += " "; 588 | webpage += " "; 589 | webpage += " "; 590 | webpage += " "; 591 | webpage += " "; 592 | webpage += " "; 593 | webpage += " "; 594 | webpage += " "; 595 | webpage += " "; 596 | webpage += " "; 597 | webpage += " "; 598 | webpage += " "; 599 | webpage += " "; 600 | webpage += " "; 601 | webpage += " "; 602 | webpage += " "; 603 | webpage += " "; 604 | webpage += " "; 605 | webpage += " "; 606 | webpage += " "; 607 | webpage += " "; 608 | webpage += " "; 609 | webpage += " "; 610 | webpage += " "; 611 | webpage += " "; 612 | webpage += " "; 613 | webpage += " "; 614 | webpage += " "; 615 | webpage += " "; 616 | webpage += " "; 617 | webpage += " "; 618 | webpage += " "; 619 | webpage += " "; 620 | webpage += " "; 621 | webpage += " "; 622 | webpage += " "; 623 | webpage += " "; 624 | webpage += " "; 625 | webpage += "
" + TXT_FORECAST_GRAPHS + ":
" + TXT_TEMPERATURE + ":" + TXT_FEELSLIKE + ":
" + TXT_HEATINDEX + ":" + TXT_TEMPDEWPOINT + ":
" + TXT_HUMIDITY + ":" + TXT_DEWPOINT + ":
" + TXT_WINDSPEED + ":" + TXT_WINDCHILL + ":
" + TXT_WCHILL_HINDEX + ":" + TXT_PRESSURE + ":
" + TXT_CLOUDCOVER + ":" + TXT_VISIBILITY + ":
" + TXT_UVINDEX + ":" + TXT_PRECIPITATION + ":
" + TXT_RAINFALL + ":" + TXT_RAINFALL + ":
" + TXT_SNOWFALL + ":" + TXT_SNOWRATE + ":
"; 626 | webpage += "
"; 627 | webpage += "
"; 628 | webpage += "
"; 629 | webpage += ""; 630 | webpage += " "; 631 | webpage += " "; 632 | webpage += " "; 633 | webpage += " "; 634 | webpage += " "; 635 | webpage += " "; 636 | webpage += " "; 637 | webpage += " "; 638 | webpage += "
" + TXT_MOON_PHASE1 + ":
" + TXT_MOON_PHASE2 + ":
" + TXT_MOON_PHASE3 + ":
" + TXT_MOON_PHASE4 + ":
" + TXT_MOON_PHASE5 + ":
" + TXT_MOON_PHASE6 + ":
" + TXT_MOON_PHASE7 + ":
" + TXT_MOON_PHASE8 + ":
"; 639 | webpage += ""; 640 | webpage += " "; 641 | webpage += " "; 642 | webpage += " "; 643 | webpage += " "; 644 | webpage += " "; 645 | webpage += " "; 646 | webpage += " "; 647 | webpage += " "; 648 | webpage += "
" + TXT_N + ":
" + TXT_NE + ":
" + TXT_E + ":
" + TXT_SE + ":
" + TXT_S + ":
" + TXT_SW + ":
" + TXT_W + ":
" + TXT_NW + ":
"; 649 | webpage += ""; 650 | webpage += " "; 651 | webpage += " "; 652 | webpage += " "; 653 | webpage += "
" + TXT_HI + ":
" + TXT_LO + ":
" + TXT_RH + ":
"; 654 | // Days of Week 655 | webpage += ""; 656 | webpage += " "; 657 | webpage += " "; 658 | webpage += " "; 659 | webpage += " "; 660 | webpage += " "; 661 | webpage += " "; 662 | webpage += " "; 663 | webpage += "
" + TXT_SUN + ":
" + TXT_MON + ":
" + TXT_TUE + ":
" + TXT_WED + ":
" + TXT_THU + ":
" + TXT_FRI + ":
" + TXT_SAT + ":
"; 664 | // Months 665 | webpage += "
"; 666 | webpage += "
"; 667 | webpage += ""; 668 | webpage += " "; 669 | webpage += " "; 670 | webpage += " "; 671 | webpage += " "; 672 | webpage += " "; 673 | webpage += " "; 674 | webpage += " "; 675 | webpage += " "; 676 | webpage += " "; 677 | webpage += " "; 678 | webpage += " "; 679 | webpage += " "; 680 | webpage += "
" + TXT_JAN + ":
" + TXT_FEB + ":
" + TXT_MAR + ":
" + TXT_APR + ":
" + TXT_MAY + ":
" + TXT_JUN + ":
" + TXT_JUL + ":
" + TXT_AUG + ":
" + TXT_SEP + ":
" + TXT_OCT + ":
" + TXT_NOV + ":
" + TXT_DEC + ":
"; 681 | // Beaufort scale 682 | webpage += "
"; 683 | webpage += ""; 684 | webpage += " "; 685 | webpage += " "; 686 | webpage += " "; 687 | webpage += " "; 688 | webpage += " "; 689 | webpage += " "; 690 | webpage += " "; 691 | webpage += " "; 692 | webpage += " "; 693 | webpage += " "; 694 | webpage += " "; 695 | webpage += " "; 696 | webpage += " "; 697 | webpage += "
" + DEF_TXT_MBFS0 + ":
" + DEF_TXT_MBFS1 + ":
" + DEF_TXT_MBFS2 + ":
" + DEF_TXT_MBFS3 + ":
" + DEF_TXT_MBFS4 + ":
" + DEF_TXT_MBFS5 + ":
" + DEF_TXT_MBFS6 + ":
" + DEF_TXT_MBFS7 + ":
" + DEF_TXT_MBFS8 + ":
" + DEF_TXT_MBFS9 + ":
" + DEF_TXT_MBFS10 + ":
" + DEF_TXT_MBFS11 + ":
" + DEF_TXT_MBFS12 + ":
"; 698 | // Mernu items 699 | webpage += "
"; 700 | webpage += ""; 701 | webpage += " "; 702 | webpage += " "; 703 | webpage += " "; 704 | webpage += " "; 705 | webpage += " "; 706 | webpage += "
" + TXT_HOME + "
" + TXT_WEATHERMAP + "
" + TXT_SYSTEMSETUP + "
" + TXT_PREFILLSETTINGS + "
" + TXT_HELP + "
"; 707 | webpage += "
"; 708 | webpage += "


"; 709 | webpage += "
"; 710 | append_page_footer(); 711 | webserver.send(200, "text/html", webpage); 712 | webpage = ""; 713 | } 714 | //######################################################################################### 715 | void HandleInput() { 716 | City = webserver.arg("city"); City.trim(); 717 | Latitude = webserver.arg("latitude"); Latitude.trim(); 718 | Longitude = webserver.arg("longitude"); Longitude.trim(); 719 | Language = webserver.arg("language"); Language.trim(); 720 | Hemisphere = webserver.arg("hemisphere"); Hemisphere.trim(); Hemisphere.toLowerCase(); 721 | WindUnits = webserver.arg("windunits"); WindUnits.trim(); WindUnits.toLowerCase(); 722 | Units = webserver.arg("units"); Units.trim(); 723 | TXT_UVINDEX = webserver.arg("TXT_UVINDEX"); TXT_UVINDEX.trim(); 724 | TXT_FORECAST_GRAPHS = webserver.arg("TXT_FORECAST_GRAPHS"); TXT_FORECAST_GRAPHS.trim(); 725 | TXT_TEMPERATURE = webserver.arg("TXT_TEMPERATURE"); TXT_TEMPERATURE.trim(); 726 | TXT_FEELSLIKE = webserver.arg("TXT_FEELSLIKE"); TXT_FEELSLIKE.trim(); 727 | TXT_TEMPDEWPOINT = webserver.arg("TXT_TEMPDEWPOINT"); TXT_TEMPDEWPOINT.trim(); 728 | TXT_HUMIDITY = webserver.arg("TXT_HUMIDITY"); TXT_HUMIDITY.trim(); 729 | TXT_DEWPOINT = webserver.arg("TXT_DEWPOINT"); TXT_DEWPOINT.trim(); 730 | TXT_HEATINDEX = webserver.arg("TXT_HEATINDEX"); TXT_HEATINDEX.trim(); 731 | TXT_WINDSPEED = webserver.arg("TXT_WINDSPEED"); TXT_WINDSPEED.trim(); 732 | TXT_WINDCHILL = webserver.arg("TXT_WINDCHILL"); TXT_WINDCHILL.trim(); 733 | TXT_WCHILL_HINDEX = webserver.arg("TXT_WCHILL_HINDEX"); TXT_WCHILL_HINDEX.trim(); 734 | TXT_PRESSURE = webserver.arg("TXT_PRESSURE"); TXT_PRESSURE.trim(); 735 | TXT_CLOUDCOVER = webserver.arg("TXT_CLOUDCOVER"); TXT_CLOUDCOVER.trim(); 736 | TXT_VISIBILITY = webserver.arg("TXT_VISIBILITY"); TXT_VISIBILITY.trim(); 737 | TXT_PRECIPITATION = webserver.arg("TXT_PRECIPITATION"); TXT_PRECIPITATION.trim(); 738 | TXT_RAINFALL = webserver.arg("TXT_RAINFALL"); TXT_RAINFALL.trim(); 739 | TXT_RAINRATE = webserver.arg("TXT_RAINRATE"); TXT_RAINRATE.trim(); 740 | TXT_SNOWFALL = webserver.arg("TXT_SNOWFALL"); TXT_SNOWFALL.trim(); 741 | TXT_SNOWRATE = webserver.arg("TXT_SNOWRATE"); TXT_SNOWRATE.trim(); 742 | TXT_MOON_PHASE1 = webserver.arg("TXT_MOON_PHASE1"); TXT_MOON_PHASE1.trim(); 743 | TXT_MOON_PHASE2 = webserver.arg("TXT_MOON_PHASE2"); TXT_MOON_PHASE2.trim(); 744 | TXT_MOON_PHASE3 = webserver.arg("TXT_MOON_PHASE3"); TXT_MOON_PHASE3.trim(); 745 | TXT_MOON_PHASE4 = webserver.arg("TXT_MOON_PHASE4"); TXT_MOON_PHASE4.trim(); 746 | TXT_MOON_PHASE5 = webserver.arg("TXT_MOON_PHASE5"); TXT_MOON_PHASE5.trim(); 747 | TXT_MOON_PHASE6 = webserver.arg("TXT_MOON_PHASE6"); TXT_MOON_PHASE6.trim(); 748 | TXT_MOON_PHASE7 = webserver.arg("TXT_MOON_PHASE7"); TXT_MOON_PHASE7.trim(); 749 | TXT_MOON_PHASE8 = webserver.arg("TXT_MOON_PHASE8"); TXT_MOON_PHASE8.trim(); 750 | TXT_HI = webserver.arg("TXT_HI"); TXT_HI.trim(); 751 | TXT_LO = webserver.arg("TXT_LO"); TXT_LO.trim(); 752 | TXT_RH = webserver.arg("TXT_RH"); TXT_RH.trim(); 753 | TXT_SUN = webserver.arg("TXT_SUN"); TXT_SUN.trim(); 754 | TXT_MON = webserver.arg("TXT_MON"); TXT_MON.trim(); 755 | TXT_TUE = webserver.arg("TXT_TUE"); TXT_TUE.trim(); 756 | TXT_WED = webserver.arg("TXT_WED"); TXT_WED.trim(); 757 | TXT_THU = webserver.arg("TXT_THU"); TXT_THU.trim(); 758 | TXT_FRI = webserver.arg("TXT_FRI"); TXT_FRI.trim(); 759 | TXT_SAT = webserver.arg("TXT_SAT"); TXT_SAT.trim(); 760 | TXT_JAN = webserver.arg("TXT_JAN"); TXT_JAN.trim(); 761 | TXT_FEB = webserver.arg("TXT_FEB"); TXT_FEB.trim(); 762 | TXT_MAR = webserver.arg("TXT_MAR"); TXT_MAR.trim(); 763 | TXT_APR = webserver.arg("TXT_APR"); TXT_APR.trim(); 764 | TXT_MAY = webserver.arg("TXT_MAY"); TXT_MAY.trim(); 765 | TXT_JUN = webserver.arg("TXT_JUN"); TXT_JUN.trim(); 766 | TXT_JUL = webserver.arg("TXT_JUL"); TXT_JUL.trim(); 767 | TXT_AUG = webserver.arg("TXT_AUG"); TXT_AUG.trim(); 768 | TXT_SEP = webserver.arg("TXT_SEP"); TXT_SEP.trim(); 769 | TXT_OCT = webserver.arg("TXT_OCT"); TXT_OCT.trim(); 770 | TXT_NOV = webserver.arg("TXT_NOV"); TXT_NOV.trim(); 771 | TXT_DEC = webserver.arg("TXT_DEC"); TXT_DEC.trim(); 772 | TXT_N = webserver.arg("TXT_N"); TXT_N.trim(); 773 | TXT_NE = webserver.arg("TXT_NE"); TXT_NE.trim(); 774 | TXT_E = webserver.arg("TXT_E"); TXT_E.trim(); 775 | TXT_SE = webserver.arg("TXT_SE"); TXT_SE.trim(); 776 | TXT_S = webserver.arg("TXT_S"); TXT_S.trim(); 777 | TXT_SW = webserver.arg("TXT_SW"); TXT_SW.trim(); 778 | TXT_W = webserver.arg("TXT_W"); TXT_W.trim(); 779 | TXT_NW = webserver.arg("TXT_NW"); TXT_NW.trim(); 780 | TXT_BFS0 = webserver.arg("TXT_BFS0"); TXT_BFS0.trim(); 781 | TXT_BFS1 = webserver.arg("TXT_BFS1"); TXT_BFS1.trim(); 782 | TXT_BFS2 = webserver.arg("TXT_BFS2"); TXT_BFS2.trim(); 783 | TXT_BFS3 = webserver.arg("TXT_BFS3"); TXT_BFS3.trim(); 784 | TXT_BFS4 = webserver.arg("TXT_BFS4"); TXT_BFS4.trim(); 785 | TXT_BFS5 = webserver.arg("TXT_BFS5"); TXT_BFS5.trim(); 786 | TXT_BFS6 = webserver.arg("TXT_BFS6"); TXT_BFS6.trim(); 787 | TXT_BFS7 = webserver.arg("TXT_BFS7"); TXT_BFS7.trim(); 788 | TXT_BFS8 = webserver.arg("TXT_BFS8"); TXT_BFS8.trim(); 789 | TXT_BFS9 = webserver.arg("TXT_BFS9"); TXT_BFS9.trim(); 790 | TXT_BFS10 = webserver.arg("TXT_BFS10"); TXT_BFS10.trim(); 791 | TXT_BFS11 = webserver.arg("TXT_BFS11"); TXT_BFS11.trim(); 792 | TXT_BFS12 = webserver.arg("TXT_BFS12"); TXT_BFS12.trim(); 793 | TXT_HOME = webserver.arg("TXT_HOME"); TXT_HOME.trim(); 794 | TXT_WEATHERMAP = webserver.arg("TXT_WEATHERMAP"); TXT_WEATHERMAP.trim(); 795 | TXT_SYSTEMSETUP = webserver.arg("TXT_SYSTEMSETUP"); TXT_SYSTEMSETUP.trim(); 796 | TXT_PREFILLSETTINGS = webserver.arg("TXT_PREFILLSETTINGS"); TXT_PREFILLSETTINGS.trim(); 797 | TXT_HELP = webserver.arg("TXT_HELP"); TXT_HELP.trim(); 798 | TXT_LANGUAGE_TABLE = webserver.arg("TXT_LANGUAGE_TABLE"); TXT_LANGUAGE_TABLE.trim(); 799 | Units.toUpperCase(); 800 | if (WindUnits == "mps") { 801 | WindspeedUnits = MPS; 802 | TXT_WINDUNITS = "mps"; 803 | } 804 | if (WindUnits == "mph") { 805 | WindspeedUnits = MPH; 806 | TXT_WINDUNITS = "mph"; 807 | } 808 | if (WindUnits == "kph") { 809 | WindspeedUnits = KPH; 810 | TXT_WINDUNITS = "kph"; 811 | } 812 | if (WindUnits == "kts") { 813 | WindspeedUnits = KTS; 814 | TXT_WINDUNITS = "kts"; 815 | } 816 | SaveSettings(); 817 | UpdateData(); 818 | Homepage(); 819 | } 820 | //######################################################################################### 821 | void PreFill() { 822 | City = "Bath"; 823 | Latitude = "51.3704"; 824 | Longitude = "-2.1376"; 825 | Language = "en"; 826 | Hemisphere = "north"; 827 | Units = "M"; 828 | TXT_FORECAST_GRAPHS = DEF_TXT_FORECAST_GRAPHS; 829 | TXT_TEMPDEWPOINT = DEF_TXT_TEMPDEWPOINT; 830 | TXT_TEMPERATURE = DEF_TXT_TEMPERATURE; 831 | TXT_FEELSLIKE = DEF_TXT_FEELSLIKE; 832 | TXT_HUMIDITY = DEF_TXT_HUMIDITY; 833 | TXT_DEWPOINT = DEF_TXT_DEWPOINT; 834 | TXT_WINDCHILL = DEF_TXT_WINDCHILL; 835 | TXT_WCHILL_HINDEX = DEF_TXT_WCHILL_HINDEX; 836 | TXT_HEATINDEX = DEF_TXT_HEATINDEX; 837 | TXT_WINDSPEED = DEF_TXT_WINDSPEED; 838 | TXT_PRESSURE = DEF_TXT_PRESSURE; 839 | TXT_CLOUDCOVER = DEF_TXT_CLOUDCOVER; 840 | TXT_VISIBILITY = DEF_TXT_VISIBILITY; 841 | TXT_PRECIPITATION = DEF_TXT_PRECIPITATION; 842 | TXT_RAINFALL = DEF_TXT_RAINFALL; 843 | TXT_RAINRATE = DEF_TXT_RAINRATE; 844 | TXT_SNOWFALL = DEF_TXT_SNOWFALL; 845 | TXT_SNOWRATE = DEF_TXT_SNOWRATE; 846 | TXT_UVINDEX = DEF_TXT_UVINDEX; 847 | //Moon phases 848 | TXT_MOON_PHASE1 = DEF_TXT_MOON_PHASE1; 849 | TXT_MOON_PHASE2 = DEF_TXT_MOON_PHASE2; 850 | TXT_MOON_PHASE3 = DEF_TXT_MOON_PHASE3; 851 | TXT_MOON_PHASE4 = DEF_TXT_MOON_PHASE4; 852 | TXT_MOON_PHASE5 = DEF_TXT_MOON_PHASE5; 853 | TXT_MOON_PHASE6 = DEF_TXT_MOON_PHASE6; 854 | TXT_MOON_PHASE7 = DEF_TXT_MOON_PHASE7; 855 | TXT_MOON_PHASE8 = DEF_TXT_MOON_PHASE8; 856 | //Wind 857 | TXT_N = DEF_TXT_N; 858 | TXT_NE = DEF_TXT_NE; 859 | TXT_E = DEF_TXT_E; 860 | TXT_SE = DEF_TXT_SE; 861 | TXT_S = DEF_TXT_S; 862 | TXT_SW = DEF_TXT_SW; 863 | TXT_W = DEF_TXT_W; 864 | TXT_NW = DEF_TXT_NW; 865 | WindspeedUnits = MPH; // Change to your desired display format e.g. for KPH 'MetricWindspeed = KPH'; 866 | TXT_WINDUNITS = DEF_TXT_WINDUNITS; // Ensure text here matahces units chosen above! Use a leading space 867 | //Windspeed warning colours based on Beaufort scale in kph *** NOTE these are HTML colours, don't translate! 868 | TXT_BFS0 = DEF_TXT_BFS0; 869 | TXT_BFS1 = DEF_TXT_BFS1; 870 | TXT_BFS2 = DEF_TXT_BFS2; 871 | TXT_BFS3 = DEF_TXT_BFS3; 872 | TXT_BFS4 = DEF_TXT_BFS4; 873 | TXT_BFS5 = DEF_TXT_BFS5; 874 | TXT_BFS6 = DEF_TXT_BFS6; 875 | TXT_BFS7 = DEF_TXT_BFS7; 876 | TXT_BFS8 = DEF_TXT_BFS8; 877 | TXT_BFS9 = DEF_TXT_BFS9; 878 | TXT_BFS10 = DEF_TXT_BFS10; 879 | TXT_BFS11 = DEF_TXT_BFS11; 880 | TXT_BFS12 = DEF_TXT_BFS12; 881 | //Days of the week 882 | TXT_SUN = DEF_TXT_SUN; 883 | TXT_MON = DEF_TXT_MON; 884 | TXT_TUE = DEF_TXT_TUE; 885 | TXT_WED = DEF_TXT_WED; 886 | TXT_THU = DEF_TXT_THU; 887 | TXT_FRI = DEF_TXT_FRI; 888 | TXT_SAT = DEF_TXT_SAT; 889 | //Months 890 | TXT_JAN = DEF_TXT_JAN; 891 | TXT_FEB = DEF_TXT_FEB; 892 | TXT_MAR = DEF_TXT_MAR; 893 | TXT_APR = DEF_TXT_APR; 894 | TXT_MAY = DEF_TXT_MAY; 895 | TXT_JUN = DEF_TXT_JUN; 896 | TXT_JUL = DEF_TXT_JUL; 897 | TXT_AUG = DEF_TXT_AUG; 898 | TXT_SEP = DEF_TXT_SEP; 899 | TXT_OCT = DEF_TXT_OCT; 900 | TXT_NOV = DEF_TXT_NOV; 901 | TXT_DEC = DEF_TXT_DEC; 902 | TXT_HI = DEF_TXT_HI; 903 | TXT_LO = DEF_TXT_LO; 904 | TXT_RH = DEF_TXT_RH; 905 | TXT_HOME = DEF_TXT_HOME; 906 | TXT_WEATHERMAP = DEF_TXT_WEATHERMAP; 907 | TXT_SYSTEMSETUP = DEF_TXT_SYSTEMSETUP; 908 | TXT_PREFILLSETTINGS = DEF_TXT_PREFILLSETTINGS; 909 | TXT_HELP = DEF_TXT_HELP; 910 | TXT_LANGUAGE_TABLE = DEF_TXT_LANGUAGE_TABLE; 911 | SaveSettings(); 912 | UpdateData(); 913 | Homepage(); 914 | } 915 | //######################################################################################### 916 | void append_page_header(bool Header) { 917 | webpage = ""; 918 | webpage += ""; 919 | webpage += ""; 920 | webpage += ""; // 180-sec screen refresh time 921 | webpage += ""; 922 | webpage += "Weather Webserver"; 923 | webpage += ""; 964 | webpage += ""; 965 | webpage += ""; 966 | UpdateLocalTime(); 967 | webpage += "

ESP Weather Server (" + Time_str + " / " + Date_str + " )

"; 968 | if (Header) { 969 | webpage += "

" + City + "

"; 970 | } 971 | } 972 | //######################################################################################### 973 | void append_page_footer() { // Saves repeating many lines of code for HTML page footers 974 | webpage += "
"; 980 | webpage += "

© Open Weather Map Data and Icons 2021 / "; 981 | webpage += "© " + String(char(byte(0x40 >> 1))) + String(char(byte(0x88 >> 1))) + String(char(byte(0x5c >> 1))) + String(char(byte(0x98 >> 1))) + String(char(byte(0x5c >> 1))); 982 | webpage += String(char((0x84 >> 1))) + String(char(byte(0xd2 >> 1))) + String(char(0xe4 >> 1)) + String(char(0xc8 >> 1)) + String(char(byte(0x40 >> 1))); 983 | webpage += String(char(byte(0x64 / 2))) + String(char(byte(0x60 >> 1))) + String(char(byte(0x64 >> 1))) + String(char(0x62 >> 1)) + "

"; 984 | webpage += "
"; 985 | } 986 | //######################################################################################### 987 | void add_graphs() { 988 | int r = 0; 989 | String WDirArrow = ""; 990 | //webpage += ""; 991 | webpage += ""; 992 | webpage += ""; 1185 | // Start of main HTML page content 1186 | webpage += "
"; 1187 | webpage += "

" + TXT_FORECAST_GRAPHS + "

"; 1188 | webpage += "
"; 1189 | webpage += ""; 1190 | webpage += ""; 1191 | webpage += " "; 1192 | webpage += " "; 1193 | webpage += " "; 1194 | webpage += " "; 1195 | webpage += " "; 1196 | webpage += " "; 1197 | webpage += " "; 1198 | webpage += " "; 1199 | webpage += " "; 1200 | webpage += "
"; 1201 | webpage += "
"; 1202 | } 1203 | //######################################################################################### 1204 | // Handle file not found situation 1205 | void handleNotFound() { 1206 | String message; 1207 | message += "URI: "; 1208 | message += webserver.uri(); 1209 | message += "\nMethod: "; 1210 | message += (webserver.method() == HTTP_GET) ? "GET" : "POST"; 1211 | message += "\nArguments: "; 1212 | message += webserver.args(); 1213 | message += "\n"; 1214 | for (uint8_t i = 0; i < webserver.args(); i++) { 1215 | message += " NAME:" + webserver.argName(i) + "\n VALUE:" + webserver.arg(i) + "\n"; 1216 | } 1217 | webserver.send(404, "text/plain", message); 1218 | Serial.print(message); 1219 | } 1220 | //######################################################################################### 1221 | uint8_t StartWiFi() { 1222 | Serial.print("\r\nConnecting to: "); Serial.println(String(ssid)); 1223 | IPAddress dns(8, 8, 8, 8); // Google DNS 1224 | WiFi.disconnect(); 1225 | delay(1200); 1226 | WiFi.mode(WIFI_STA); // switch off AP 1227 | WiFi.setAutoConnect(true); 1228 | WiFi.begin(ssid, password); 1229 | unsigned long start = millis(); 1230 | uint8_t connectionStatus; 1231 | bool AttemptConnection = true; 1232 | while (AttemptConnection) { 1233 | connectionStatus = WiFi.status(); 1234 | if (millis() > start + 15000) { // Wait 15-secs maximum 1235 | AttemptConnection = false; 1236 | } 1237 | if (connectionStatus == WL_CONNECTED || connectionStatus == WL_CONNECT_FAILED) { 1238 | AttemptConnection = false; 1239 | } 1240 | delay(50); 1241 | } 1242 | if (connectionStatus == WL_CONNECTED) { 1243 | Serial.println("WiFi connected at: " + WiFi.localIP().toString()); 1244 | } 1245 | return connectionStatus; 1246 | } 1247 | //######################################################################################### 1248 | boolean SetupTime() { 1249 | configTime(gmtOffset_sec, daylightOffset_sec, ntpServer, "time.nist.gov"); //(gmtOffset_sec, daylightOffset_sec, ntpServer) 1250 | setenv("TZ", Timezone, 1); //setenv()adds the "TZ" variable to the environment with a value TimeZone, only used if set to 1, 0 means no change 1251 | delay(200); 1252 | bool TimeStatus = UpdateLocalTime(); 1253 | return TimeStatus; 1254 | } 1255 | //######################################################################################### 1256 | boolean UpdateLocalTime() { 1257 | struct tm timeinfo; 1258 | char time_output[30], day_output[30], update_time[30]; 1259 | while (!getLocalTime(&timeinfo, 15000)) { // Wait for up to 15-sec for time to synchronise 1260 | return false; 1261 | } 1262 | //See http://www.cplusplus.com/reference/ctime/strftime/ 1263 | Serial.println(&timeinfo, "%a %b %d %Y %H:%M:%S"); // Displays: Saturday, June 24 2017 14:05:49 1264 | if (Units == "M") { 1265 | sprintf(day_output, "%s %02u-%s-%04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); 1266 | strftime(update_time, sizeof(update_time), "%H:%M:%S", &timeinfo); // Creates: '14:05:49' 1267 | sprintf(time_output, "%s %s", TXT_UPDATED, update_time); 1268 | } 1269 | else 1270 | { 1271 | strftime(day_output, sizeof(day_output), "%a %b-%d-%Y", &timeinfo); // Creates 'Sat May-31-2019' 1272 | strftime(update_time, sizeof(update_time), "%r", &timeinfo); // Creates: '02:05:49pm' 1273 | sprintf(time_output, "%s %s", TXT_UPDATED, update_time); 1274 | } 1275 | Date_str = day_output; 1276 | Time_str = time_output; 1277 | return true; 1278 | } 1279 | //######################################################################################### 1280 | String HourMinute(int unix_time) { 1281 | // Returns either '21:12 ' or ' 09:12pm' depending on Units mode 1282 | // See http://www.cplusplus.com/reference/ctime/strftime/ 1283 | time_t tm = unix_time; 1284 | struct tm *now_tm = localtime(&tm); 1285 | char output[40]; 1286 | if (Units == "M") { 1287 | strftime(output, sizeof(output), "%H:%M", now_tm); 1288 | } 1289 | else 1290 | { 1291 | strftime(output, sizeof(output), "%I:%M%P", now_tm); 1292 | } 1293 | return String(output); 1294 | } 1295 | //######################################################################################### 1296 | void SetupDeviceName(const char *DeviceName) { 1297 | if (MDNS.begin(DeviceName)) { // The name that will identify your device on the network 1298 | Serial.println("mDNS responder started"); 1299 | Serial.print("Device name: "); 1300 | Serial.println(DeviceName); 1301 | MDNS.addService("n8i-mlp", "tcp", 23); // Add service 1302 | } 1303 | else { 1304 | Serial.println("Error setting up MDNS responder"); 1305 | } 1306 | } 1307 | //######################################################################################### 1308 | String WindDegToDir(float winddirection, String * WDirArrow) { 1309 | if (winddirection >= 337.5 || winddirection < 22.5) { 1310 | *WDirArrow = "⇑"; 1311 | return TXT_N; 1312 | } 1313 | if (winddirection >= 22.5 && winddirection < 67.5) { 1314 | *WDirArrow = "⇗"; 1315 | return TXT_NE; 1316 | } 1317 | if (winddirection >= 67.5 && winddirection < 112.5) { 1318 | *WDirArrow = "⇒"; 1319 | return TXT_E; 1320 | } 1321 | if (winddirection >= 112.5 && winddirection < 157.5) { 1322 | *WDirArrow = "⇘"; 1323 | return TXT_SE; 1324 | } 1325 | if (winddirection >= 157.5 && winddirection < 202.5) { 1326 | *WDirArrow = "⇓"; 1327 | return TXT_S; 1328 | } 1329 | if (winddirection >= 202.5 && winddirection < 247.5) { 1330 | *WDirArrow = "⇙"; 1331 | return TXT_SW; 1332 | } 1333 | if (winddirection >= 247.5 && winddirection < 292.5) { 1334 | *WDirArrow = "⇐"; 1335 | return TXT_W; 1336 | } 1337 | if (winddirection >= 292.5 && winddirection < 337.5) { 1338 | *WDirArrow = "⇖"; 1339 | return TXT_NW; 1340 | } 1341 | return "?"; 1342 | } 1343 | //######################################################################################### 1344 | String WindToClass(float windspeed, bool convert) { // converts windspeed to colours representing the Beaufort scale 1345 | if (convert) { 1346 | if (WindspeedUnits == MPS) windspeed *= 3.6; // Convert mps to kph for a kph based Beaufort scale 1347 | if (WindspeedUnits == MPH) windspeed *= 1.609344; // Convert mph to kph for a kph based Beaufort scale 1348 | if (WindspeedUnits == KTS) windspeed *= 1.852; // Convert kts to kph for a kph based Beaufort scale 1349 | } 1350 | if (windspeed < 2) return Calm; // 0 1351 | if (windspeed >= 2 && windspeed < 6) return LightAir; // 1 1352 | if (windspeed >= 6 && windspeed < 12) return LightBreeze; // 2 1353 | if (windspeed >= 12 && windspeed < 20) return GentleBreeze; // 3 1354 | if (windspeed >= 20 && windspeed < 29) return ModerateBreeze; // 4 1355 | if (windspeed >= 29 && windspeed < 39) return FreshBreeze; // 5 1356 | if (windspeed >= 39 && windspeed < 50) return StrongBreeze; // 6 1357 | if (windspeed >= 50 && windspeed < 62) return HighWind; // 7 1358 | if (windspeed >= 62 && windspeed < 75) return Gale; // 8 1359 | if (windspeed >= 75 && windspeed < 89) return SevereGale; // 9 1360 | if (windspeed >= 89 && windspeed < 103) return Storm; // 10 1361 | if (windspeed >= 103 && windspeed < 118) return ViolentStorm; // 11 1362 | if (windspeed >= 118) return Hurricane; // 12 1363 | return "White"; 1364 | } 1365 | //######################################################################################### 1366 | String WindToName(float windspeed) { // converts windspeed to Beaufort scale names 1367 | if (WindspeedUnits == MPS) windspeed *= 3.6; // Convert mps to kph for a kph based Beaufort scale 1368 | if (WindspeedUnits == MPH) windspeed *= 1.609344; // Convert mph to kph for a kph based Beaufort scale 1369 | if (WindspeedUnits == KTS) windspeed *= 1.852; // Convert kts to kph for a kph based Beaufort scale 1370 | if (windspeed < 2) return TXT_BFS0; // Force 0 1371 | if (windspeed >= 2 && windspeed < 6) return TXT_BFS1; // Force 1 1372 | if (windspeed >= 6 && windspeed < 12) return TXT_BFS2; // Force 2 1373 | if (windspeed >= 12 && windspeed < 20) return TXT_BFS3; // Force 3 1374 | if (windspeed >= 20 && windspeed < 29) return TXT_BFS4; // Force 4 1375 | if (windspeed >= 29 && windspeed < 39) return TXT_BFS5; // Force 5 1376 | if (windspeed >= 39 && windspeed < 50) return TXT_BFS6; // Force 6 1377 | if (windspeed >= 50 && windspeed < 62) return TXT_BFS7; // Force 7 1378 | if (windspeed >= 62 && windspeed < 75) return TXT_BFS8; // Force 8 1379 | if (windspeed >= 75 && windspeed < 89) return TXT_BFS9; // Force 9 1380 | if (windspeed >= 89 && windspeed < 103) return TXT_BFS10; // Force 10 1381 | if (windspeed >= 103 && windspeed < 118) return TXT_BFS11; // Force 11 1382 | if (windspeed >= 118) return TXT_BFS12; // Force 12 1383 | return "White"; 1384 | } 1385 | //######################################################################################### 1386 | float Convert_to_WS_Units(float windspeed) { 1387 | if (Units == "M" && WindspeedUnits == MPH) windspeed *= 2.23693629; // mps to mph 1388 | if (Units == "M" && WindspeedUnits == KPH) windspeed *= 3.6; // mps to kph 1389 | if (Units == "M" && WindspeedUnits == KTS) windspeed *= 1.94384449; // mps to kts 1390 | if (Units == "I" && WindspeedUnits == MPS) windspeed *= 0.44704; // mph to mps 1391 | if (Units == "I" && WindspeedUnits == KPH) windspeed *= 1.609344; // mph to kph 1392 | if (Units == "I" && WindspeedUnits == KTS) windspeed *= 0.86897624; // mph to kts 1393 | return windspeed; 1394 | } 1395 | //######################################################################################### 1396 | String GetMoonImage() { 1397 | String MoonPhase = GetMoonPhase(); 1398 | if (Hemisphere == "north") { 1399 | if (MoonPhase == TXT_MOON_PHASE1) return MoonNew(); // New; 0% illuminated 1400 | if (MoonPhase == TXT_MOON_PHASE2) return MoonWaxingCrescent(); // Waxing crescent; 25% illuminated 1401 | if (MoonPhase == TXT_MOON_PHASE3) return MoonFirstQuarter(); // First quarter; 50% illuminated 1402 | if (MoonPhase == TXT_MOON_PHASE4) return MoonWaxingGibbous(); // Waxing gibbous; 75% illuminated 1403 | if (MoonPhase == TXT_MOON_PHASE5) return MoonFull(); // Full; 100% illuminated 1404 | if (MoonPhase == TXT_MOON_PHASE6) return MoonWaningGibbous(); // Waning gibbous; 75% illuminated 1405 | if (MoonPhase == TXT_MOON_PHASE7) return MoonThirdQuarter(); // Third quarter; 50% illuminated 1406 | if (MoonPhase == TXT_MOON_PHASE8) return MoonWaningCrescent(); // Waning crescent; 25% illuminated 1407 | } 1408 | else 1409 | { 1410 | if (MoonPhase == TXT_MOON_PHASE1) return MoonNew(); // New; 0% illuminated 1411 | if (MoonPhase == TXT_MOON_PHASE2) return MoonWaningCrescent(); // Waning crescent; 25% illuminated 1412 | if (MoonPhase == TXT_MOON_PHASE3) return MoonThirdQuarter(); // Third quarter; 50% illuminated 1413 | if (MoonPhase == TXT_MOON_PHASE4) return MoonWaningGibbous(); // Waning gibbous; 75% illuminated 1414 | if (MoonPhase == TXT_MOON_PHASE5) return MoonFull(); // Full; 100% illuminated 1415 | if (MoonPhase == TXT_MOON_PHASE6) return MoonWaxingGibbous(); // Waxing gibbous; 75% illuminated 1416 | if (MoonPhase == TXT_MOON_PHASE7) return MoonFirstQuarter(); // First quarter; 50% illuminated 1417 | if (MoonPhase == TXT_MOON_PHASE8) return MoonWaxingCrescent(); // Waxing crescent; 25% illuminated 1418 | } 1419 | return MoonPhase; 1420 | } 1421 | //######################################################################################### 1422 | String GetMoonPhase() { 1423 | time_t now = time(NULL); 1424 | struct tm * now_utc = gmtime(&now); 1425 | const int day_utc = now_utc->tm_mday; 1426 | const int month_utc = now_utc->tm_mon + 1; 1427 | const int year_utc = now_utc->tm_year + 1900; 1428 | return MoonPhase(day_utc, month_utc, year_utc, Hemisphere); 1429 | } 1430 | //######################################################################################### 1431 | String MoonPhase(int d, int m, int y, String hemisphere) { 1432 | int c, e; 1433 | double jd; 1434 | int b; 1435 | if (m < 3) { 1436 | y--; 1437 | m += 12; 1438 | } 1439 | ++m; 1440 | c = 365.25 * y; 1441 | e = 30.6 * m; 1442 | jd = c + e + d - 694039.09; /* jd is total days elapsed */ 1443 | jd /= 29.53059; /* divide by the moon cycle (29.53 days) */ 1444 | b = jd; /* int(jd) -> b, take integer part of jd */ 1445 | jd -= b; /* subtract integer part to leave fractional part of original jd */ 1446 | b = jd * 8 + 0.5; /* scale fraction from 0-8 and round by adding 0.5 */ 1447 | b = b & 7; /* 0 and 8 are the same phase so modulo 8 for 0 */ 1448 | //if (hemisphere == "south") b = 7 - b; 1449 | if (b == 0) return TXT_MOON_PHASE1; // New; 0% illuminated 1450 | if (b == 1) return TXT_MOON_PHASE2; // Waxing crescent; 25% illuminated 1451 | if (b == 2) return TXT_MOON_PHASE3; // First quarter; 50% illuminated 1452 | if (b == 3) return TXT_MOON_PHASE4; // Waxing gibbous; 75% illuminated 1453 | if (b == 4) return TXT_MOON_PHASE5; // Full; 100% illuminated 1454 | if (b == 5) return TXT_MOON_PHASE6; // Waning gibbous; 75% illuminated 1455 | if (b == 6) return TXT_MOON_PHASE7; // Third quarter; 50% illuminated 1456 | if (b == 7) return TXT_MOON_PHASE8; // Waning crescent; 25% illuminated 1457 | return ""; 1458 | } 1459 | String UVIndexLevel(float UVI) { 1460 | if (UVI <= 2) return "L"; 1461 | if (UVI <= 5) return "M"; 1462 | if (UVI <= 7) return "H"; 1463 | if (UVI <= 10) return "VH"; 1464 | if (UVI > 10) return "EX"; 1465 | return ""; 1466 | } 1467 | //######################################################################################### 1468 | float DewPoint(float Temperature, float RH) { 1469 | float DP = 0; 1470 | if (Units == "I") Temperature = (Temperature - 32) * 5.0 / 9.0; // Convert F to C 1471 | DP = (237.3 * (log(RH / 100) + ( (17.27 * Temperature) / (237.3 + Temperature)))) / (17.27 - (log(RH / 100) + ( (17.27 * Temperature) / (237.3 + Temperature) ))); 1472 | //Where: Tdew = dew point temperature in degrees Celsius (°C), T = air temperature in degrees Celsius (°C), RH = relative humidity (%), ln = natural logarithm. 1473 | if (Units == "I") DP = DP * 9.0 / 5.0 + 32; // Convert C to F 1474 | return DP; 1475 | } 1476 | //######################################################################################### 1477 | float calc_windchill(int Temperature, int WindSpeed) { 1478 | float result; 1479 | // Derived from wind_chill = 13.12 + 0.6215 * Tair - 11.37 * POWER(wind_speed,0.16)+0.3965 * Tair * POWER(wind_speed,0.16) 1480 | WindSpeed *= 1.852; // Convert to Kph 1481 | result = 13.12 + 0.6215 * Temperature - 11.37 * pow(WindSpeed, 0.16) + 0.3965 * Temperature * pow(WindSpeed, 0.16); 1482 | if (result < 0 ) { 1483 | return result - 0.5; 1484 | } else { 1485 | return result + 0.5; 1486 | } 1487 | } 1488 | //######################################################################################### 1489 | float calc_heatindex(int T, int RH) { 1490 | float HI = 0; 1491 | // The computation of the heat index is a refinement of a result obtained by multiple regression analysis carried out by Lans P. Rothfusz and 1492 | // described in a 1990 National Weather Service (NWS) Technical Attachment (SR 90-23). 1493 | // The regression equation of Rothfusz is: 1494 | // HI = -42.379 + 2.04901523*T + 10.14333127*RH - .22475541*T*RH - .00683783*T*T - .05481717*RH*RH + .00122874*T*T*RH + .00085282*T*RH*RH - .00000199*T*T*RH*RH 1495 | // where T is temperature in degrees F and RH is relative humidity in percent. 1496 | // HI is the heat index expressed as an apparent temperature in degrees F. 1497 | // 1498 | // If the RH is less than 13% and the temperature is between 80 and 112 degrees F, then the following adjustment is subtracted from HI: 1499 | // ADJUSTMENT = [(13-RH)/4]*SQRT{[17-ABS(T-95.)]/17} 1500 | // where ABS and SQRT are the absolute value and square root functions, respectively. 1501 | // On the other hand, if the RH is greater than 85% and the temperature is between 80 and 87 degrees F, then the following adjustment is added to HI: 1502 | // ADJUSTMENT = [(RH-85)/10] * [(87-T)/5] 1503 | // The Rothfusz regression is not appropriate when conditions of temperature and humidity warrant a heat index value below about 80 degrees F. In those cases, a simpler formula is applied to calculate values consistent with Steadman's results: 1504 | if (Units == "M") T = T * 9.0 / 5.0 + 32; 1505 | if (T >= 80) { 1506 | HI = -42.379 + 2.04901523 * T + 10.14333127 * RH - 0.22475541 * T * RH - 0.00683783 * sq(T) - 0.05481717 * sq(RH) + 0.00122874 * sq(T) * RH + 0.00085282 * T * sq(RH) - 0.00000199 * sq(T) * sq(RH); 1507 | if (RH <= 13 && T >= 80 && T <= 112) { 1508 | HI -= ((13 - RH) / 4) * sqrt((17 - abs(T - 95.)) / 17); 1509 | } 1510 | if (RH >= 85 && T >= 80 && T <= 87) { 1511 | HI += ((RH - 85) / 10) * ((87 - T) / 5); 1512 | } 1513 | } 1514 | else 1515 | HI = 0.5 * (T + 61.0 + ((T - 68.0) * 1.2) + (RH * 0.094)); 1516 | if (Units == "M") HI = (HI - 32) / 9.0 * 5.0; 1517 | return HI; 1518 | } 1519 | //######################################################################################### 1520 | float WindChill(int Temperature, int WindSpeed) { 1521 | float result; 1522 | // Derived from wind_chill = 13.12 + 0.6215 * Tair - 11.37 * POWER(wind_speed,0.16)+0.3965 * Tair * POWER(wind_speed,0.16) 1523 | if (Units == "I") WindSpeed = WindSpeed * 1.852; // Convert to Kph 1524 | result = 13.12 + 0.6215 * Temperature - 11.37 * pow(WindSpeed, 0.16) + 0.3965 * Temperature * pow(WindSpeed, 0.16); 1525 | if (result < 0 ) { 1526 | return result - 0.5; 1527 | } else { 1528 | return result + 0.5; 1529 | } 1530 | } 1531 | //######################################################################################### 1532 | int HeatIndex(float T, int RH) { 1533 | if ( Units == "M" ) T = (T * 9 / 5) + 32; 1534 | int tHI = (-42.379 + (2.04901523 * T) + (10.14333127 * RH) - (0.22475541 * T * RH) - (0.00683783 * sq(T)) - (0.05481717 * sq(RH)) + (0.00122874 * sq(T) * RH) + (0.00085282 * T * sq(RH)) - (0.00000199 * sq(T) * sq(RH))); 1535 | if ( Units == "M" ) tHI = (tHI - 32) / 9.0 * 5.0; 1536 | return tHI; 1537 | //where HI = -42.379 + 2.04901523*T + 10.14333127*RH - 0.22475541*T*RH - 0.00683783*T*T - 0.05481717*RH*RH + 0.00122874*T*T*RH + 0.00085282*T*RH*RH - 0.00000199*T*T*RH*RH 1538 | //tHI = heat index (°F) T = air temperature (°F) (t > 57oF) RH = relative humidity (%) 1539 | } 1540 | //######################################################################################### 1541 | void Convert_Readings_to_Imperial() { 1542 | WxConditions[0].Pressure = hPa_to_inHg(WxConditions[0].Pressure); 1543 | WxForecast[1].Rainfall = mm_to_inches(WxForecast[1].Rainfall); 1544 | WxForecast[1].Snowfall = mm_to_inches(WxForecast[1].Snowfall); 1545 | } 1546 | //######################################################################################### 1547 | String TempToClass(float T) { 1548 | if (Units == "I") T = (T - 32) / 9.0 * 5.0; // Back to Celcius for this comparison 1549 | if (T > 27) return "tempH"; // Red 1550 | if (T >= 16 && T <= 27) return "tempM"; // Green 1551 | if (T < 16) return "tempL"; // Blue 1552 | return "tempM"; 1553 | } 1554 | //######################################################################################### 1555 | String HumiToClass(float H) { 1556 | if (H > 80) return "humiH"; // Red > 80% 1557 | if (H >= 40 && H <= 80) return "humiM"; // Green 40-80% 1558 | if (H < 40) return "humiL"; // Blue < 40% 1559 | return "humiM"; 1560 | } 1561 | //######################################################################################### 1562 | // Problems with stucturing JSON decodes, see here: https://arduinojson.org/assistant/ 1563 | bool DecodeWeather(WiFiClient& json, String Type) { 1564 | Serial.print("\nCreating object...and "); 1565 | // allocate the JsonDocument 1566 | DynamicJsonDocument doc(25 * 1024); 1567 | // Deserialize the JSON document 1568 | DeserializationError error = deserializeJson(doc, json); 1569 | // Test if parsing succeeds. 1570 | if (error) { 1571 | Serial.print(F("deserializeJson() failed: ")); 1572 | Serial.println(error.c_str()); 1573 | return false; 1574 | } 1575 | // convert it to a JsonObject 1576 | JsonObject root = doc.as(); 1577 | Serial.println(" Decoding " + Type + " data..................."); 1578 | if (Type == "weather") { 1579 | Serial.println("Getting : " + Type + " data"); 1580 | // All Serial.println statements are for diagnostic purposes and not required, remove if not needed 1581 | WxConditions[0].Description = root["weather"][0]["description"].as(); Serial.println("Des1: " + String(WxConditions[0].Description)); 1582 | WxConditions[0].Description2 = root["weather"][1]["description"].as(); Serial.println("Des2: " + String(WxConditions[0].Description2)); 1583 | WxConditions[0].Icon = root["weather"][0]["icon"].as(); Serial.println("Icon: " + String(WxConditions[0].Icon)); 1584 | WxConditions[0].Temperature = root["main"]["temp"]; Serial.println("Temp: " + String(WxConditions[0].Temperature)); 1585 | WxConditions[0].Feelslike = root["main"]["feels_like"]; Serial.println("FLik: " + String(WxConditions[0].Feelslike)); 1586 | WxConditions[0].Pressure = root["main"]["pressure"]; Serial.println("Pres: " + String(WxConditions[0].Pressure)); 1587 | WxConditions[0].Humidity = root["main"]["humidity"]; Serial.println("Humi: " + String(WxConditions[0].Humidity)); 1588 | WxConditions[0].Low = root["main"]["temp_min"]; Serial.println("TLow: " + String(WxConditions[0].Low)); 1589 | WxConditions[0].High = root["main"]["temp_max"]; Serial.println("THig: " + String(WxConditions[0].High)); 1590 | WxConditions[0].Windspeed = root["wind"]["speed"]; Serial.println("WSpd: " + String(WxConditions[0].Windspeed)); 1591 | WxConditions[0].Gust = root["wind"]["gust"]; Serial.println("WGus: " + String(WxConditions[0].Gust)); 1592 | WxConditions[0].Winddir = root["wind"]["deg"]; Serial.println("WDir: " + String(WxConditions[0].Winddir)); 1593 | WxConditions[0].Cloudcover = root["clouds"]["all"]; Serial.println("CCov: " + String(WxConditions[0].Cloudcover)); // in per-unit of % of cloud cover 1594 | WxConditions[0].Visibility = root["visibility"]; Serial.println("Visi: " + String(WxConditions[0].Visibility)); // in metres 1595 | WxConditions[0].Sunrise = root["sys"]["sunrise"]; Serial.println("SRis: " + String(WxConditions[0].Sunrise)); 1596 | WxConditions[0].Sunset = root["sys"]["sunset"]; Serial.println("SSet: " + String(WxConditions[0].Sunset)); 1597 | WxConditions[0].Rainrate = doc["rain"]["1h"]; Serial.println("RRat: " + String(WxConditions[0].Rainrate)); 1598 | WxConditions[0].Snowrate = doc["snow"]["1h"]; Serial.println("SRat: " + String(WxConditions[0].Snowrate)); 1599 | } 1600 | if (Type == "forecast") { 1601 | Serial.println("Getting : " + Type + " data"); 1602 | //Serial.println(json); 1603 | Serial.print(F("\nReceiving Forecast period - ")); //------------------------------------------------ 1604 | JsonArray list = root["list"]; 1605 | for (byte r = 0; r < max_readings; r++) { 1606 | Serial.println("\nPeriod-" + String(r) + "--------------"); 1607 | WxForecast[r].Dt = list[r]["dt"]; Serial.println("DT : " + String(WxForecast[r].Dt)); 1608 | WxForecast[r].Temperature = list[r]["main"]["temp"]; Serial.println("Temp: " + String(WxForecast[r].Temperature)); 1609 | WxForecast[r].Low = list[r]["main"]["temp_min"]; Serial.println("TLow: " + String(WxForecast[r].Low)); 1610 | WxForecast[r].High = list[r]["main"]["temp_max"]; Serial.println("THig: " + String(WxForecast[r].High)); 1611 | WxForecast[r].Pressure = list[r]["main"]["pressure"]; Serial.println("Pres: " + String(WxForecast[r].Pressure)); 1612 | WxForecast[r].Humidity = list[r]["main"]["humidity"]; Serial.println("Humi: " + String(WxForecast[r].Humidity)); 1613 | WxForecast[r].Description = list[r]["weather"][0]["description"].as(); Serial.println("Desc: " + String(WxForecast[r].Description)); 1614 | WxForecast[r].Icon = list[r]["weather"][0]["icon"].as(); Serial.println("Icon: " + String(WxForecast[r].Icon)); 1615 | WxForecast[r].Cloudcover = list[r]["clouds"]["all"]; Serial.println("CCov: " + String(WxForecast[r].Cloudcover)); // in % of cloud cover 1616 | WxForecast[r].Windspeed = list[r]["wind"]["speed"]; Serial.println("WSpd: " + String(WxForecast[r].Windspeed)); 1617 | WxForecast[r].Winddir = list[r]["wind"]["deg"]; Serial.println("WDir: " + String(WxForecast[r].Winddir)); 1618 | WxForecast[r].Rainfall = list[r]["rain"]["3h"]; Serial.println("Rain: " + String(WxForecast[r].Rainfall)); 1619 | WxForecast[r].Snowfall = list[r]["snow"]["3h"]; Serial.println("Snow: " + String(WxForecast[r].Snowfall)); 1620 | WxForecast[r].Pop = list[r]["pop"]; Serial.println("Pop : " + String(WxForecast[r].Pop * 100)); // in per-unit of % probability 1621 | } 1622 | //------------------------------------------ 1623 | float pressure_trend = WxForecast[1].Pressure - WxForecast[0].Pressure; // Measure pressure slope between ~now and 3-hours later 1624 | pressure_trend = ((int)(pressure_trend * 10)) / 10.0; // Remove any small variations less than 0.1 1625 | WxConditions[0].Trend = "0"; 1626 | if (pressure_trend > 0) WxConditions[0].Trend = "⇗"; 1627 | if (pressure_trend < 0) WxConditions[0].Trend = "⇘"; 1628 | if (pressure_trend == 0) WxConditions[0].Trend = "⇒"; 1629 | if (Units == "I") Convert_Readings_to_Imperial(); 1630 | } 1631 | if (Type == "onecall") { 1632 | Serial.println("Getting : " + Type + " data"); 1633 | WxConditions[0].uvIndex = root["value"]; Serial.println("UVIn: " + String(WxConditions[0].uvIndex)); 1634 | } 1635 | return true; 1636 | } 1637 | //######################################################################################### 1638 | String ConvertUnixTime(int unix_time) { 1639 | // Returns either '21:12 ' or ' 09:12pm' depending on Units mode 1640 | time_t tm = unix_time; 1641 | struct tm *now_tm = localtime(&tm); 1642 | char output[40]; 1643 | if (Units == "M") { 1644 | strftime(output, sizeof(output), "%H:%M %d/%m/%y", now_tm); 1645 | } 1646 | else { 1647 | strftime(output, sizeof(output), "%I:%M%P %m/%d/%y", now_tm); 1648 | } 1649 | return output; 1650 | } 1651 | //######################################################################################### 1652 | String DayOfWeek(int unix_time) { 1653 | // Returns 'Mon' or 'Tue' or the contents of the array weekday_D[n] ... 1654 | // See http://www.cplusplus.com/reference/ctime/strftime/ 1655 | time_t tm = unix_time; 1656 | struct tm *now_tm = localtime(&tm); 1657 | char output[40]; 1658 | strftime(output, sizeof(output), "%w", now_tm); 1659 | return String(weekday_D[atoi(output)]); 1660 | } 1661 | //######################################################################################### 1662 | bool obtain_wx_data(WiFiClient& client, const String& RequestType) { 1663 | String units = (Units == "M" ? "metric" : "imperial"); 1664 | client.stop(); // close connection before sending a new request 1665 | // api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={API key} 1666 | // Request type is either 'weather', 'forecast' or 'uvi' 1667 | HTTPClient http; 1668 | String uri = "/data/2.5/" + RequestType + "?lat=" + Latitude + "&lon=" + Longitude + "&APPID=" + apikey + "&mode=json&units=" + units + "&lang=" + Language; 1669 | if (RequestType != "weather") { 1670 | uri += "&cnt=" + String(max_readings); 1671 | } 1672 | if (RequestType == "onccall") { 1673 | uri += "&exclude=minutely,hourly,daily,alerts"; 1674 | } 1675 | Serial.println(uri); 1676 | //http.begin(uri,test_root_ca); //HTTPS example connection 1677 | http.begin(client, server, 80, uri); 1678 | int httpCode = http.GET(); 1679 | if (httpCode == HTTP_CODE_OK) { 1680 | if (!DecodeWeather(http.getStream(), RequestType)) return false; 1681 | client.stop(); 1682 | http.end(); 1683 | return true; 1684 | } 1685 | else 1686 | { 1687 | Serial.printf("connection failed, error: %s", http.errorToString(httpCode).c_str()); 1688 | client.stop(); 1689 | http.end(); 1690 | return false; 1691 | } 1692 | http.end(); 1693 | return true; 1694 | } 1695 | //######################################################################################### 1696 | String TitleCase(String text) { 1697 | if (text.length() > 0) { 1698 | String temp_text = text.substring(0, 1); 1699 | temp_text.toUpperCase(); 1700 | return temp_text + text.substring(1); // Title-case the string 1701 | } 1702 | else return text; 1703 | } 1704 | //######################################################################################### 1705 | float mm_to_inches(float value_mm) { 1706 | return 0.0393701 * value_mm; 1707 | } 1708 | //######################################################################################### 1709 | float hPa_to_inHg(float value_hPa) { 1710 | return 0.02953 * value_hPa; 1711 | } 1712 | //######################################################################################### 1713 | ////////////// SPIFFS Support //////////////////////////////// 1714 | // For ESP8266 See: http://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html 1715 | void StartSPIFFS() { 1716 | Serial.println("Starting SPIFFS"); 1717 | boolean SPIFFS_Status; 1718 | SPIFFS_Status = SPIFFS.begin(); 1719 | if (SPIFFS_Status == false) 1720 | { // Most likely SPIFFS has not yet been formated, so do so 1721 | #ifdef ESP8266 1722 | Serial.println("Formatting SPIFFS Please wait .... "); 1723 | if (SPIFFS.format() == true) Serial.println("SPIFFS formatted successfully"); 1724 | if (SPIFFS.begin() == false) Serial.println("SPIFFS failed to start..."); 1725 | #else 1726 | Serial.println("Formatting SPIFFS..."); 1727 | SPIFFS.begin(true); // Now format SPIFFS 1728 | File datafile = SPIFFS.open("/" + DataFile, FILE_READ); 1729 | if (!datafile || !datafile.isDirectory()) { 1730 | Serial.println("SPIFFS failed to start..."); // If ESP32 nothing more can be done, so delete and then create another file 1731 | SPIFFS.remove("/" + DataFile); // The file is corrupted!! 1732 | datafile.close(); 1733 | } 1734 | #endif 1735 | } 1736 | else Serial.println("SPIFFS Started successfully..."); 1737 | } 1738 | //######################################################################################### 1739 | void RecoverSettings() { 1740 | Serial.println("Reading Settings"); 1741 | File dataFile = SPIFFS.open("/" + DataFile, FILE_READ); 1742 | if (dataFile) { // if the file is available, read it 1743 | while (dataFile.available()) { 1744 | City = dataFile.readStringUntil('\n'); City.trim(); // Must trim recovered text 1745 | Language = dataFile.readStringUntil('\n'); Language.trim(); 1746 | Hemisphere = dataFile.readStringUntil('\n'); Hemisphere.trim(); 1747 | WindUnits = dataFile.readStringUntil('\n'); WindUnits.trim(); 1748 | Units = dataFile.readStringUntil('\n'); Units.trim(); 1749 | TXT_FORECAST_GRAPHS = dataFile.readStringUntil('\n'); TXT_FORECAST_GRAPHS.trim(); 1750 | TXT_TEMPERATURE = dataFile.readStringUntil('\n'); TXT_TEMPERATURE.trim(); 1751 | TXT_FEELSLIKE = dataFile.readStringUntil('\n'); TXT_FEELSLIKE.trim(); 1752 | TXT_TEMPDEWPOINT = dataFile.readStringUntil('\n'); TXT_TEMPDEWPOINT.trim(); 1753 | TXT_HUMIDITY = dataFile.readStringUntil('\n'); TXT_HUMIDITY.trim(); 1754 | TXT_DEWPOINT = dataFile.readStringUntil('\n'); TXT_DEWPOINT.trim(); 1755 | TXT_HEATINDEX = dataFile.readStringUntil('\n'); TXT_HEATINDEX.trim(); 1756 | TXT_WINDSPEED = dataFile.readStringUntil('\n'); TXT_WINDSPEED.trim(); 1757 | TXT_WINDCHILL = dataFile.readStringUntil('\n'); TXT_WINDCHILL.trim(); 1758 | TXT_WCHILL_HINDEX = dataFile.readStringUntil('\n'); TXT_WCHILL_HINDEX.trim(); 1759 | TXT_PRESSURE = dataFile.readStringUntil('\n'); TXT_PRESSURE.trim(); 1760 | TXT_CLOUDCOVER = dataFile.readStringUntil('\n'); TXT_CLOUDCOVER.trim(); 1761 | TXT_VISIBILITY = dataFile.readStringUntil('\n'); TXT_VISIBILITY.trim(); 1762 | TXT_PRECIPITATION = dataFile.readStringUntil('\n'); TXT_PRECIPITATION.trim(); 1763 | TXT_RAINFALL = dataFile.readStringUntil('\n'); TXT_RAINFALL.trim(); 1764 | TXT_SNOWFALL = dataFile.readStringUntil('\n'); TXT_SNOWFALL.trim(); 1765 | TXT_MOON_PHASE1 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE1.trim(); 1766 | TXT_MOON_PHASE2 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE2.trim(); 1767 | TXT_MOON_PHASE3 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE3.trim(); 1768 | TXT_MOON_PHASE4 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE4.trim(); 1769 | TXT_MOON_PHASE5 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE5.trim(); 1770 | TXT_MOON_PHASE6 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE6.trim(); 1771 | TXT_MOON_PHASE7 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE7.trim(); 1772 | TXT_MOON_PHASE8 = dataFile.readStringUntil('\n'); TXT_MOON_PHASE8.trim(); 1773 | TXT_HI = dataFile.readStringUntil('\n'); TXT_HI.trim(); 1774 | TXT_LO = dataFile.readStringUntil('\n'); TXT_LO.trim(); 1775 | TXT_RH = dataFile.readStringUntil('\n'); TXT_RH.trim(); 1776 | TXT_UVINDEX = dataFile.readStringUntil('\n'); TXT_UVINDEX.trim(); 1777 | Latitude = dataFile.readStringUntil('\n'); Latitude.trim(); 1778 | Longitude = dataFile.readStringUntil('\n'); Longitude.trim(); 1779 | TXT_SUN = dataFile.readStringUntil('\n'); TXT_SUN.trim(); 1780 | TXT_MON = dataFile.readStringUntil('\n'); TXT_MON.trim(); 1781 | TXT_TUE = dataFile.readStringUntil('\n'); TXT_TUE.trim(); 1782 | TXT_WED = dataFile.readStringUntil('\n'); TXT_WED.trim(); 1783 | TXT_THU = dataFile.readStringUntil('\n'); TXT_THU.trim(); 1784 | TXT_FRI = dataFile.readStringUntil('\n'); TXT_FRI.trim(); 1785 | TXT_SAT = dataFile.readStringUntil('\n'); TXT_SAT.trim(); 1786 | TXT_JAN = dataFile.readStringUntil('\n'); TXT_JAN.trim(); 1787 | TXT_FEB = dataFile.readStringUntil('\n'); TXT_FEB.trim(); 1788 | TXT_MAR = dataFile.readStringUntil('\n'); TXT_MAR.trim(); 1789 | TXT_APR = dataFile.readStringUntil('\n'); TXT_APR.trim(); 1790 | TXT_MAY = dataFile.readStringUntil('\n'); TXT_MAY.trim(); 1791 | TXT_JUN = dataFile.readStringUntil('\n'); TXT_JUN.trim(); 1792 | TXT_JUL = dataFile.readStringUntil('\n'); TXT_JUL.trim(); 1793 | TXT_AUG = dataFile.readStringUntil('\n'); TXT_AUG.trim(); 1794 | TXT_SEP = dataFile.readStringUntil('\n'); TXT_SEP.trim(); 1795 | TXT_OCT = dataFile.readStringUntil('\n'); TXT_OCT.trim(); 1796 | TXT_NOV = dataFile.readStringUntil('\n'); TXT_NOV.trim(); 1797 | TXT_DEC = dataFile.readStringUntil('\n'); TXT_DEC.trim(); 1798 | TXT_N = dataFile.readStringUntil('\n'); TXT_N.trim(); 1799 | TXT_NE = dataFile.readStringUntil('\n'); TXT_NE.trim(); 1800 | TXT_E = dataFile.readStringUntil('\n'); TXT_E.trim(); 1801 | TXT_SE = dataFile.readStringUntil('\n'); TXT_SE.trim(); 1802 | TXT_S = dataFile.readStringUntil('\n'); TXT_S.trim(); 1803 | TXT_SW = dataFile.readStringUntil('\n'); TXT_SW.trim(); 1804 | TXT_W = dataFile.readStringUntil('\n'); TXT_W.trim(); 1805 | TXT_NW = dataFile.readStringUntil('\n'); TXT_NW.trim(); 1806 | TXT_BFS0 = dataFile.readStringUntil('\n'); TXT_BFS0.trim(); 1807 | TXT_BFS1 = dataFile.readStringUntil('\n'); TXT_BFS1.trim(); 1808 | TXT_BFS2 = dataFile.readStringUntil('\n'); TXT_BFS2.trim(); 1809 | TXT_BFS3 = dataFile.readStringUntil('\n'); TXT_BFS3.trim(); 1810 | TXT_BFS4 = dataFile.readStringUntil('\n'); TXT_BFS4.trim(); 1811 | TXT_BFS5 = dataFile.readStringUntil('\n'); TXT_BFS5.trim(); 1812 | TXT_BFS6 = dataFile.readStringUntil('\n'); TXT_BFS6.trim(); 1813 | TXT_BFS7 = dataFile.readStringUntil('\n'); TXT_BFS7.trim(); 1814 | TXT_BFS8 = dataFile.readStringUntil('\n'); TXT_BFS8.trim(); 1815 | TXT_BFS9 = dataFile.readStringUntil('\n'); TXT_BFS9.trim(); 1816 | TXT_BFS10 = dataFile.readStringUntil('\n'); TXT_BFS10.trim(); 1817 | TXT_BFS11 = dataFile.readStringUntil('\n'); TXT_BFS11.trim(); 1818 | TXT_BFS12 = dataFile.readStringUntil('\n'); TXT_BFS12.trim(); 1819 | TXT_HOME = dataFile.readStringUntil('\n'); TXT_HOME.trim(); 1820 | TXT_WEATHERMAP = dataFile.readStringUntil('\n'); TXT_WEATHERMAP.trim(); 1821 | TXT_SYSTEMSETUP = dataFile.readStringUntil('\n'); TXT_SYSTEMSETUP.trim(); 1822 | TXT_PREFILLSETTINGS = dataFile.readStringUntil('\n'); TXT_PREFILLSETTINGS.trim(); 1823 | TXT_HELP = dataFile.readStringUntil('\n'); TXT_HELP.trim(); 1824 | TXT_LANGUAGE_TABLE = dataFile.readStringUntil('\n'); TXT_LANGUAGE_TABLE.trim(); 1825 | } 1826 | dataFile.close(); 1827 | Serial.println(" Recovered City Name of : " + City); 1828 | Serial.println("Recovered NW Definition of : " + TXT_NW); 1829 | } 1830 | } 1831 | //######################################################################################### 1832 | // “r” read, read only 1833 | // “r +” read and write. The pointer is positioned at the start of the file 1834 | // “w” write, write. The existing content is deleted. The file is created if it does not exist 1835 | // “w +” opens file for reading and writing. The file is created if it does not exist, otherwise it is truncated. The pointer is positioned at the start of the file 1836 | // “a” append, opens file to add data, file is created if not exists. Pointer is positioned at end of file if it already exists 1837 | // “a +” append, opens file to add data, file is created if not exists. Pointer is positioned at start of file for reading and at end for writing (appending) 1838 | void SaveSettings() { 1839 | Serial.println("Getting ready to save settings..."); 1840 | Serial.println("Have valid entry for City name of : " + City); 1841 | Serial.println(" Have valid definition for NW of : " + TXT_NW); 1842 | if (TXT_SNOWFALL != "") { // Any entry to test 1843 | File dataFile = SPIFFS.open("/" + DataFile, FILE_WRITE); 1844 | if (dataFile) { // Save settings 1845 | Serial.println("Saving settings..."); 1846 | dataFile.println(City); 1847 | dataFile.println(Language); 1848 | dataFile.println(Hemisphere); 1849 | dataFile.println(WindUnits); 1850 | dataFile.println(Units); 1851 | dataFile.println(TXT_FORECAST_GRAPHS); 1852 | dataFile.println(TXT_TEMPERATURE); 1853 | dataFile.println(TXT_FEELSLIKE); 1854 | dataFile.println(TXT_TEMPDEWPOINT); 1855 | dataFile.println(TXT_HUMIDITY); 1856 | dataFile.println(TXT_DEWPOINT); 1857 | dataFile.println(TXT_HEATINDEX); 1858 | dataFile.println(TXT_WINDSPEED); 1859 | dataFile.println(TXT_WINDCHILL); 1860 | dataFile.println(TXT_WCHILL_HINDEX); 1861 | dataFile.println(TXT_PRESSURE); 1862 | dataFile.println(TXT_CLOUDCOVER); 1863 | dataFile.println(TXT_VISIBILITY); 1864 | dataFile.println(TXT_PRECIPITATION); 1865 | dataFile.println(TXT_RAINFALL); 1866 | dataFile.println(TXT_SNOWFALL); 1867 | dataFile.println(TXT_MOON_PHASE1); 1868 | dataFile.println(TXT_MOON_PHASE2); 1869 | dataFile.println(TXT_MOON_PHASE3); 1870 | dataFile.println(TXT_MOON_PHASE4); 1871 | dataFile.println(TXT_MOON_PHASE5); 1872 | dataFile.println(TXT_MOON_PHASE6); 1873 | dataFile.println(TXT_MOON_PHASE7); 1874 | dataFile.println(TXT_MOON_PHASE8); 1875 | dataFile.println(TXT_HI); 1876 | dataFile.println(TXT_LO); 1877 | dataFile.println(TXT_RH); 1878 | dataFile.println(TXT_UVINDEX); 1879 | dataFile.println(Latitude); 1880 | dataFile.println(Longitude); 1881 | dataFile.println(TXT_SUN); 1882 | dataFile.println(TXT_MON); 1883 | dataFile.println(TXT_TUE); 1884 | dataFile.println(TXT_WED); 1885 | dataFile.println(TXT_THU); 1886 | dataFile.println(TXT_FRI); 1887 | dataFile.println(TXT_SAT); 1888 | dataFile.println(TXT_JAN); 1889 | dataFile.println(TXT_FEB); 1890 | dataFile.println(TXT_MAR); 1891 | dataFile.println(TXT_APR); 1892 | dataFile.println(TXT_MAY); 1893 | dataFile.println(TXT_JUN); 1894 | dataFile.println(TXT_JUL); 1895 | dataFile.println(TXT_AUG); 1896 | dataFile.println(TXT_SEP); 1897 | dataFile.println(TXT_OCT); 1898 | dataFile.println(TXT_NOV); 1899 | dataFile.println(TXT_DEC); 1900 | dataFile.println(TXT_N); 1901 | dataFile.println(TXT_NE); 1902 | dataFile.println(TXT_E); 1903 | dataFile.println(TXT_SE); 1904 | dataFile.println(TXT_S); 1905 | dataFile.println(TXT_SW); 1906 | dataFile.println(TXT_W); 1907 | dataFile.println(TXT_NW); 1908 | dataFile.println(TXT_BFS0); 1909 | dataFile.println(TXT_BFS1); 1910 | dataFile.println(TXT_BFS2); 1911 | dataFile.println(TXT_BFS3); 1912 | dataFile.println(TXT_BFS4); 1913 | dataFile.println(TXT_BFS5); 1914 | dataFile.println(TXT_BFS6); 1915 | dataFile.println(TXT_BFS7); 1916 | dataFile.println(TXT_BFS8); 1917 | dataFile.println(TXT_BFS9); 1918 | dataFile.println(TXT_BFS10); 1919 | dataFile.println(TXT_BFS11); 1920 | dataFile.println(TXT_BFS12); 1921 | dataFile.println(TXT_HOME); 1922 | dataFile.println(TXT_WEATHERMAP); 1923 | dataFile.println(TXT_SYSTEMSETUP); 1924 | dataFile.println(TXT_PREFILLSETTINGS); 1925 | dataFile.println(TXT_HELP); 1926 | dataFile.println(TXT_LANGUAGE_TABLE); 1927 | dataFile.close(); 1928 | } 1929 | Serial.println("Settings saved"); 1930 | } 1931 | } 1932 | //######################################################################################### 1933 | void SunrisesetIcon() { 1934 | webpage += ""; 1946 | } 1947 | //######################################################################################### 1948 | String RainDrop() { 1949 | String webpage = ""; 1950 | webpage += ""; 1959 | return webpage; 1960 | } 1961 | //######################################################################################### 1962 | void CloudCover() { 1963 | webpage += ""; 1973 | } 1974 | //######################################################################################### 1975 | String MoonNew() { 1976 | String webpage = ""; 1988 | return webpage; 1989 | } 1990 | 1991 | //######################################################################################### 1992 | String MoonWaningCrescent() { 1993 | String webpage = ""; 2001 | return webpage; 2002 | } 2003 | //######################################################################################### 2004 | String MoonThirdQuarter() { 2005 | String webpage = ""; 2015 | return webpage; 2016 | } 2017 | //######################################################################################### 2018 | String MoonWaningGibbous() { 2019 | String webpage = ""; 2030 | return webpage; 2031 | } 2032 | //######################################################################################### 2033 | String MoonFull() { 2034 | String webpage = ""; 2046 | return webpage; 2047 | } 2048 | //######################################################################################### 2049 | String MoonWaxingGibbous() { 2050 | String webpage = ""; 2060 | return webpage; 2061 | } 2062 | //######################################################################################### 2063 | String MoonFirstQuarter() { 2064 | String webpage = ""; 2073 | return webpage; 2074 | } 2075 | //######################################################################################### 2076 | String MoonWaxingCrescent() { 2077 | String webpage = ""; 2086 | return webpage; 2087 | } 2088 | //######################################################################################### 2089 | /* 2090 | Version 3. (24-Feb-2021) 2091 | 1. (2068 lines of code) 2092 | */ 2093 | -------------------------------------------------------------------------------- /ESPweather_v3_es.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/G6EJD/ESP32_Advanced_Weather_Server/b3496a42ef476e39bb5b15751854d8fe2548a929/ESPweather_v3_es.JPG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_Advanced_Weather_Server 2 | Using an ESP32 to display Open-Weather-Map data using a Webserver 3 | 4 | An ESP32 is used to obtain OWM Data and then displays this on a web-page using a webserver 5 | 6 | Monitor the serial port for the assigned IP address. E.g. http://192.168.0.19/ or use the logical name http://wxserver.local/ 7 | 8 | Then connect to server with http://192.168.0.19/ or http://wxserver.local/ 9 | 10 | System Set-up: 11 | 12 | Get a free OWM API Key from here: https://openweathermap.org/ 13 | Copy the following files to your sketch folder: 14 | 1. ESP32_OWM_WX_Server_v3.ino 15 | 2. owm_credentials.h 16 | 3. credentials_EN.h for English 17 | 18 | Update the 'owm_credentials.h; tab with your credentials for WiFi and OWM 19 | 20 | 1. Enter your City/Location name, choose any name. 21 | 2. Enter your location Latitude and Longitude, e.g. 51.38, -2.33 22 | - Use negative numbers for South of the Equator and East of the Meridian. 23 | 3. Enter your Language e.g. 'en', see below for OWM supported languages. 24 | 4. Enter your locations Hemisphere, North or South. 25 | 5. Enter your desired wind units either MPS (Metres-per-Second), MPH (Miles-per-hour), KPH (Kilometres-per-hour) or KTS (Knots) 26 | 6. Enter your desired Units either Metric or Imperial. 27 | 7. Enter the text for each item in the Language Translation Table. e.g. for 'de' 'Pressure' enter 'Druck' 28 | - For months you could use '01' for JAN, etc. 29 | 30 | If the SPIFFS wont start, or recover settings, then: 31 | 1, Uncomment this line: //SPIFFS.remove("/"+DataFile); // In-case SPIFFS file gets corrupted, it can happen! 32 | 2. Compile and upload, restart the EPS 33 | 3. Comment line out, then re-compile and upload. 34 | 35 | OWM Supports the following languages: 36 | af Afrikaans, al Albanian, ar Arabic, az Azerbaijani 37 | bg Bulgarian 38 | ca Catalan, cz Czech 39 | da Danish, de German 40 | el Greek, en English, eu Basque 41 | fa Persian (Farsi), fi Finnish, fr French 42 | gl Galician 43 | he Hebrew, hi Hindi, hr Croatian, hu Hungarian 44 | id Indonesian, it Italian 45 | ja Japanese 46 | kr Korean 47 | la Latvian, lt Lithuanian 48 | mk Macedonian 49 | no Norwegian, nl Dutch 50 | pl Polish, pt Portuguese, pt_br Português Brasil 51 | ro Romanian, ru Russian 52 | sv, se Swedish, sk Slovak, sl Slovenian, sp, es Spanish, sr Serbian 53 | th Thai, tr Turkish 54 | ua, uk Ukrainian 55 | vi Vietnamese 56 | zh_cn Chinese Simplified, zh_tw Chinese Traditional, zu Zulu 57 | - Example: German set lauguage = 'de' 58 | 59 | ![alt_text, width="200"](/WX_V2-1.jpg) 60 | 61 | -------------------------------------------------------------------------------- /WX_V2-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/G6EJD/ESP32_Advanced_Weather_Server/b3496a42ef476e39bb5b15751854d8fe2548a929/WX_V2-1.jpg -------------------------------------------------------------------------------- /credentials_EN.h: -------------------------------------------------------------------------------- 1 | // Change to your WiFi credentials 2 | const char* ssid = "your_WiFi_SSID"; 3 | const char* password = "your_WiFi_PASSWORD"; 4 | 5 | // Use your own API key by signing up for a free developer account at https://openweathermap.org/ 6 | String apikey = "your_OWM_API_key"; // See: https://openweathermap.org/ 7 | const char server[] = "api.openweathermap.org"; 8 | 9 | // EXAMPLE API CALLS: 10 | //http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=24 11 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1 12 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1&lang=en 13 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric 14 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OQM_API_key&mode=json&units=metric 15 | //http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=2 16 | //https://openweathermap.org/weathermap?basemap=map&cities=false&layer=radar&lat=51&lon=-2&zoom=10 17 | 18 | String City = "Bath"; 19 | String Latitude = "51.3704"; // Required location Latitude 20 | String Longitude = "-2.1376"; // Required location Longitude 21 | String Language = "en"; // NOTE: Only the weather description is translated by OWM 22 | // Languages supported: ar, bg, ca, de, el, en, es, fa, fi, fr, gl, hr, hu, it, ja, kr, la, lt, mk, nl, pl, pt, ro, ru, 23 | // se, sk, sl, tr, ua, vi, zh_cn, zh_tw 24 | String Hemisphere = "north"; // or "south" 25 | String Units = "M"; // Use 'M' for Metric or I for Imperial 26 | const char* Timezone = "GMT0BST,M3.5.0/01,M10.5.0/02"; // Choose your time zone from: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv 27 | //const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // e.g. Germany 28 | // See below for examples 29 | const char* ntpServer = "pool.ntp.org"; // Or, choose a time server close to you, but in most cases it's best to use pool.ntp.org to find an NTP server 30 | // then the NTP system decides e.g. 0.country-code.pool.ntp.org, 1.country-code.pool.ntp.org etc... 31 | // UK "0.uk.pool.ntp.org" 32 | // EU "0.europe.pool.ntp.org" 33 | // DE "0.de.pool.ntp.org" 34 | // US "0.north-america.pool.ntp.org" 35 | // See: https://www.ntppool.org/en/ AND see below for examples 36 | 37 | const String DEF_TXT_LANGUAGE_TABLE = "Language Translation Table"; 38 | const String DEF_TXT_FORECAST_GRAPHS = "5 day / 3 hour forecast"; 39 | const String DEF_TXT_TEMPDEWPOINT = "Temperature / Dewpoint"; 40 | const String DEF_TXT_TEMPERATURE = "Temperature"; 41 | const String DEF_TXT_FEELSLIKE = "Feelslike"; 42 | const String DEF_TXT_HUMIDITY = "Humidity"; 43 | const String DEF_TXT_DEWPOINT = "Dewpoint"; 44 | const String DEF_TXT_WINDCHILL = "Windchill"; 45 | const String DEF_TXT_WCHILL_HINDEX = "Windchill / Heat Index"; 46 | const String DEF_TXT_HEATINDEX = "Heat Index"; 47 | const String DEF_TXT_WINDSPEED = "Windspeed"; 48 | const String DEF_TXT_PRESSURE = "Pressure"; 49 | const String DEF_TXT_CLOUDCOVER = "Cloud Cover"; 50 | const String DEF_TXT_VISIBILITY = "Visibility"; 51 | const String DEF_TXT_PRECIPITATION = "Precipitation"; 52 | const String DEF_TXT_RAINFALL = "Rainfall"; 53 | const String DEF_TXT_RAINRATE = "Rainrate"; 54 | const String DEF_TXT_SNOWFALL = "Snowfall"; 55 | const String DEF_TXT_SNOWRATE = "Snowrate"; 56 | const String DEF_TXT_UVINDEX = "UV Index"; 57 | //Moon phases 58 | const String DEF_TXT_MOON_PHASE1 = "New"; 59 | const String DEF_TXT_MOON_PHASE2 = "Waxing Crescent"; 60 | const String DEF_TXT_MOON_PHASE3 = "First Quarter"; 61 | const String DEF_TXT_MOON_PHASE4 = "Waxing Gibbous"; 62 | const String DEF_TXT_MOON_PHASE5 = "Full"; 63 | const String DEF_TXT_MOON_PHASE6 = "Waning Gibbous"; 64 | const String DEF_TXT_MOON_PHASE7 = "Third Quarter"; 65 | const String DEF_TXT_MOON_PHASE8 = "Waning Crescent"; 66 | //Wind 67 | const String DEF_TXT_N = "N"; 68 | const String DEF_TXT_NE = "NE"; 69 | const String DEF_TXT_E = "E"; 70 | const String DEF_TXT_SE = "SE"; 71 | const String DEF_TXT_S = "S"; 72 | const String DEF_TXT_SW = "SW"; 73 | const String DEF_TXT_W = "W"; 74 | const String DEF_TXT_NW = "NW"; 75 | const String DEF_TXT_WINDUNITS = "mph"; // Ensure text here matahces units chosen above! Use a leading space 76 | //Windspeed warning colours based on Beaufort scale in kph *** NOTE these are HTML colours, don't translate! 77 | const String DEF_TXT_BFS0 = "Calm"; 78 | const String DEF_TXT_BFS1 = "Light Air"; 79 | const String DEF_TXT_BFS2 = "Light Breeze"; 80 | const String DEF_TXT_BFS3 = "Gentle Breeze"; 81 | const String DEF_TXT_BFS4 = "Moderate Breeze"; 82 | const String DEF_TXT_BFS5 = "Fresh Breeze"; 83 | const String DEF_TXT_BFS6 = "Strong Breeze"; 84 | const String DEF_TXT_BFS7 = "High Wind"; 85 | const String DEF_TXT_BFS8 = "Gale"; 86 | const String DEF_TXT_BFS9 = "Severe Gale"; 87 | const String DEF_TXT_BFS10 = "Storm"; 88 | const String DEF_TXT_BFS11 = "Violent Storm"; 89 | const String DEF_TXT_BFS12 = "Hurricane"; 90 | //Wind description 91 | const String DEF_TXT_MBFS0 = "Force-0"; 92 | const String DEF_TXT_MBFS1 = "Force-1"; 93 | const String DEF_TXT_MBFS2 = "Force-2"; 94 | const String DEF_TXT_MBFS3 = "Force-3"; 95 | const String DEF_TXT_MBFS4 = "Force-4"; 96 | const String DEF_TXT_MBFS5 = "Force-5"; 97 | const String DEF_TXT_MBFS6 = "Force-6"; 98 | const String DEF_TXT_MBFS7 = "Force-7"; 99 | const String DEF_TXT_MBFS8 = "Force-8"; 100 | const String DEF_TXT_MBFS9 = "Force-9"; 101 | const String DEF_TXT_MBFS10 = "Force-10"; 102 | const String DEF_TXT_MBFS11 = "Force-11"; 103 | const String DEF_TXT_MBFS12 = "Force-12"; 104 | //Days of the week 105 | const String DEF_TXT_SUN = "Sun"; 106 | const String DEF_TXT_MON = "Mon"; 107 | const String DEF_TXT_TUE = "Tue"; 108 | const String DEF_TXT_WED = "Wed"; 109 | const String DEF_TXT_THU = "Thu"; 110 | const String DEF_TXT_FRI = "Fri"; 111 | const String DEF_TXT_SAT = "Sat"; 112 | //Months 113 | const String DEF_TXT_JAN = "Jan"; 114 | const String DEF_TXT_FEB = "Feb"; 115 | const String DEF_TXT_MAR = "Mar"; 116 | const String DEF_TXT_APR = "Apr"; 117 | const String DEF_TXT_MAY = "May"; 118 | const String DEF_TXT_JUN = "Jun"; 119 | const String DEF_TXT_JUL = "Jul"; 120 | const String DEF_TXT_AUG = "Aug"; 121 | const String DEF_TXT_SEP = "Sep"; 122 | const String DEF_TXT_OCT = "Oct"; 123 | const String DEF_TXT_NOV = "Nov"; 124 | const String DEF_TXT_DEC = "Dec"; 125 | const String DEF_TXT_HI = "Hi"; 126 | const String DEF_TXT_LO = "Lo"; 127 | const String DEF_TXT_RH = "RH"; 128 | const String DEF_TXT_HOME = "Home"; 129 | const String DEF_TXT_WEATHERMAP = "Weather Map"; 130 | const String DEF_TXT_SYSTEMSETUP = "System Setup"; 131 | const String DEF_TXT_PREFILLSETTINGS = "Pre-Fill Settings"; 132 | const String DEF_TXT_HELP = "Help"; 133 | 134 | // TIMEZOEN Definition examples 135 | // NOTE: Because this code uses the SETENV variable the following offset values are ignored! 136 | int gmtOffset_sec = 0; // UK normal time is GMT, so GMT Offset is 0, for US (-5Hrs) is typically -18000, AU is typically (+8hrs) 28800 137 | int daylightOffset_sec = 3600; // In the UK DST is +1hr or 3600-secs, other countries may use 2hrs 7200 or 30-mins 1800 or 5.5hrs 19800 Ahead of GMT use + offset behind - offset 138 | 139 | // Example time zones 140 | //const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe e.g. Austria, Belgium, France, Germany, Holland, Norway, Poland, Spain, Sweden 141 | //const char* Timezone = "CET-1CEST,M3.5.0,M10.5.0/3"; // Central Europe 142 | //const char* Timezone = "EST-2METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe e.g. Belarus, Finland, Romania 143 | //const char* Timezone = "EST5EDT,M3.2.0,M11.1.0"; // EST USA 144 | //const char* Timezone = "CST6CDT,M3.2.0,M11.1.0"; // CST USA 145 | //const char* Timezone = "MST7MDT,M4.1.0,M10.5.0"; // MST USA 146 | //const char* Timezone = "NZST-12NZDT,M9.5.0,M4.1.0/3"; // Auckland 147 | //const char* Timezone = "EET-2EEST,M3.5.5/0,M10.5.5/0"; // Asia 148 | //const char* Timezone = "ACST-9:30ACDT,M10.1.0,M4.1.0/3": // Australia 149 | -------------------------------------------------------------------------------- /credentials_ES.h: -------------------------------------------------------------------------------- 1 | const char* ssid = "your_WiFi_SSID"; 2 | const char* password = "your_WiFi_PASSWORD"; 3 | 4 | // // Use your own API key by signing up for a free developer account at https://openweathermap.org/ 5 | String apikey = "your_OWM_API_key"; // See: https://openweathermap.org/ 6 | const char server[] = "api.openweathermap.org"; 7 | 8 | // EXAMPLE API CALLS: 9 | //http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=24 10 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1 11 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1&lang=en 12 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric 13 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OQM_API_key&mode=json&units=metric 14 | //http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=2 15 | //https://openweathermap.org/weathermap?basemap=map&cities=false&layer=radar&lat=51&lon=-2&zoom=10 16 | 17 | String City = "Algorta"; 18 | String Latitude = "43.3"; // Required location Latitude 19 | String Longitude = "-3.02"; // Required location Longitude 20 | String Language = "es"; // NOTE: Only the weather description is translated by OWM 21 | // Languages supported: ar, bg, ca, de, el, en, es, fa, fi, fr, gl, hr, hu, it, ja, kr, la, lt, mk, nl, pl, pt, ro, ru, 22 | // se, sk, sl, tr, ua, vi, zh_cn, zh_tw 23 | String Hemisphere = "north"; // or "south" 24 | String Units = "M"; // Use 'M' for Metric or I for Imperial 25 | //const char* Timezone = "GMT0BST,M3.5.0/01,M10.5.0/02"; // Choose your time zone from: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv 26 | const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // e.g. Germany 27 | // See below for examples 28 | const char* ntpServer = "pool.ntp.org"; // Or, choose a time server close to you, but in most cases it's best to use pool.ntp.org to find an NTP server 29 | // then the NTP system decides e.g. 0.country-code.pool.ntp.org, 1.country-code.pool.ntp.org etc... 30 | // UK "0.uk.pool.ntp.org" 31 | // EU "0.europe.pool.ntp.org" 32 | // DE "0.de.pool.ntp.org" 33 | // US "0.north-america.pool.ntp.org" 34 | // See: https://www.ntppool.org/en/ AND see below for examples 35 | 36 | const String DEF_TXT_LANGUAGE_TABLE = "Tabla de traducción de idioma"; 37 | const String DEF_TXT_FORECAST_GRAPHS = "Pronóstico a 5 días / 3 horas"; 38 | const String DEF_TXT_TEMPDEWPOINT = "Temperatura / Punto de rocío"; 39 | const String DEF_TXT_TEMPERATURE = "Temperatura"; 40 | const String DEF_TXT_FEELSLIKE = "Sensación"; 41 | const String DEF_TXT_HUMIDITY = "Humedad"; 42 | const String DEF_TXT_DEWPOINT = "Punto de rocío"; 43 | const String DEF_TXT_WINDCHILL = "Sensación térmica"; 44 | const String DEF_TXT_WCHILL_HINDEX = "Sensación térmica / Índice de calor"; 45 | const String DEF_TXT_HEATINDEX = "Indice de calor"; 46 | const String DEF_TXT_WINDSPEED = "Velocidad del viento"; 47 | const String DEF_TXT_PRESSURE = "Presión"; 48 | const String DEF_TXT_CLOUDCOVER = "Nubosidad"; 49 | const String DEF_TXT_VISIBILITY = "Visibilidad"; 50 | const String DEF_TXT_PRECIPITATION = "Precipitación"; 51 | const String DEF_TXT_RAINFALL = "Lluvia"; 52 | const String DEF_TXT_RAINRATE = "Promedio de lluvia"; 53 | const String DEF_TXT_SNOWFALL = "Nieve"; 54 | const String DEF_TXT_SNOWRATE = "Promedio de nieve"; 55 | const String DEF_TXT_UVINDEX = "Índice UV"; 56 | //Moon phases 57 | const String DEF_TXT_MOON_PHASE1 = "Nueva"; 58 | const String DEF_TXT_MOON_PHASE2 = "Creciente"; 59 | const String DEF_TXT_MOON_PHASE3 = "Primer cuarto"; 60 | const String DEF_TXT_MOON_PHASE4 = "Menguante"; 61 | const String DEF_TXT_MOON_PHASE5 = "Llena"; 62 | const String DEF_TXT_MOON_PHASE6 = "Menguante"; 63 | const String DEF_TXT_MOON_PHASE7 = "Tercer cuarto"; 64 | const String DEF_TXT_MOON_PHASE8 = "Creciente menguante"; 65 | //Wind 66 | const String DEF_TXT_N = "N"; 67 | const String DEF_TXT_NE = "NE"; 68 | const String DEF_TXT_E = "E"; 69 | const String DEF_TXT_SE = "SE"; 70 | const String DEF_TXT_S = "S"; 71 | const String DEF_TXT_SW = "SO"; 72 | const String DEF_TXT_W = "O"; 73 | const String DEF_TXT_NW = "NO"; 74 | const String DEF_TXT_WINDUNITS = "mph"; // Ensure text here matahces units chosen above! Use a leading space 75 | //Windspeed warning colours based on Beaufort scale in kph *** NOTE these are HTML colours, don't translate! 76 | const String DEF_TXT_BFS0 = "Calma"; 77 | const String DEF_TXT_BFS1 = "Aire ligero"; 78 | const String DEF_TXT_BFS2 = "Brisa ligera"; 79 | const String DEF_TXT_BFS3 = "Suave brisa"; 80 | const String DEF_TXT_BFS4 = "Brisa moderada"; 81 | const String DEF_TXT_BFS5 = "Brisa fresca"; 82 | const String DEF_TXT_BFS6 = "Brisa fuerte"; 83 | const String DEF_TXT_BFS7 = "Fuerte viento"; 84 | const String DEF_TXT_BFS8 = "Vendaval"; 85 | const String DEF_TXT_BFS9 = "Vendaval severo"; 86 | const String DEF_TXT_BFS10 = "Tormenta"; 87 | const String DEF_TXT_BFS11 = "Tormenta violenta"; 88 | const String DEF_TXT_BFS12 = "Huracán"; 89 | //Wind description 90 | const String DEF_TXT_MBFS0 = "Fuerza-0"; 91 | const String DEF_TXT_MBFS1 = "Fuerza-1"; 92 | const String DEF_TXT_MBFS2 = "Fuerza-2"; 93 | const String DEF_TXT_MBFS3 = "Fuerza-3"; 94 | const String DEF_TXT_MBFS4 = "Fuerza-4"; 95 | const String DEF_TXT_MBFS5 = "Fuerza-5"; 96 | const String DEF_TXT_MBFS6 = "Fuerza-6"; 97 | const String DEF_TXT_MBFS7 = "Fuerza-7"; 98 | const String DEF_TXT_MBFS8 = "Fuerza-8"; 99 | const String DEF_TXT_MBFS9 = "Fuerza-9"; 100 | const String DEF_TXT_MBFS10 = "Fuerza-10"; 101 | const String DEF_TXT_MBFS11 = "Fuerza-11"; 102 | const String DEF_TXT_MBFS12 = "Fuerza-12"; 103 | //Days of the week 104 | const String DEF_TXT_SUN = "Dom"; 105 | const String DEF_TXT_MON = "Lun"; 106 | const String DEF_TXT_TUE = "Mar"; 107 | const String DEF_TXT_WED = "Mié"; 108 | const String DEF_TXT_THU = "Jue"; 109 | const String DEF_TXT_FRI = "Vie"; 110 | const String DEF_TXT_SAT = "Sáb"; 111 | //Months 112 | const String DEF_TXT_JAN = "Ene"; 113 | const String DEF_TXT_FEB = "Feb"; 114 | const String DEF_TXT_MAR = "Mar"; 115 | const String DEF_TXT_APR = "Abr"; 116 | const String DEF_TXT_MAY = "May"; 117 | const String DEF_TXT_JUN = "Jun"; 118 | const String DEF_TXT_JUL = "Jul"; 119 | const String DEF_TXT_AUG = "Ago"; 120 | const String DEF_TXT_SEP = "Sep"; 121 | const String DEF_TXT_OCT = "Oct"; 122 | const String DEF_TXT_NOV = "Nov"; 123 | const String DEF_TXT_DEC = "Dic"; 124 | // High Low and Relative Humidity 125 | const String DEF_TXT_HI = "Máx."; 126 | const String DEF_TXT_LO = "Mín."; 127 | const String DEF_TXT_RH = "Humedad"; 128 | const String DEF_TXT_HOME = "Principal"; 129 | const String DEF_TXT_WEATHERMAP = "Mapa Meteorológico"; 130 | const String DEF_TXT_SYSTEMSETUP = "Ajustes"; 131 | const String DEF_TXT_PREFILLSETTINGS = "Pre-Carga Ajustes"; 132 | const String DEF_TXT_HELP = "Ayuda"; 133 | 134 | // TIMEZOEN Definition examples 135 | // NOTE: Because this code uses the SETENV variable the following offset values are ignored! 136 | int gmtOffset_sec = 0; // UK normal time is GMT, so GMT Offset is 0, for US (-5Hrs) is typically -18000, AU is typically (+8hrs) 28800 137 | int daylightOffset_sec = 3600; // In the UK DST is +1hr or 3600-secs, other countries may use 2hrs 7200 or 30-mins 1800 or 5.5hrs 19800 Ahead of GMT use + offset behind - offset 138 | 139 | // Example time zones 140 | //const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe e.g. Austria, Belgium, France, Germany, Holland, Norway, Poland, Spain, Sweden 141 | //const char* Timezone = "CET-1CEST,M3.5.0,M10.5.0/3"; // Central Europe 142 | //const char* Timezone = "EST-2METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe e.g. Belarus, Finland, Romania 143 | //const char* Timezone = "EST5EDT,M3.2.0,M11.1.0"; // EST USA 144 | //const char* Timezone = "CST6CDT,M3.2.0,M11.1.0"; // CST USA 145 | //const char* Timezone = "MST7MDT,M4.1.0,M10.5.0"; // MST USA 146 | //const char* Timezone = "NZST-12NZDT,M9.5.0,M4.1.0/3"; // Auckland 147 | //const char* Timezone = "EET-2EEST,M3.5.5/0,M10.5.5/0"; // Asia 148 | //const char* Timezone = "ACST-9:30ACDT,M10.1.0,M4.1.0/3": // Australia 149 | -------------------------------------------------------------------------------- /owm_credentials.h: -------------------------------------------------------------------------------- 1 | // Change to your WiFi credentials 2 | const char* ssid = "your_WiFi_SSID"; 3 | const char* password = "your_WiFi_PASSWORD"; 4 | 5 | // Use your own API key by signing up for a free developer account at https://openweathermap.org/ 6 | String apikey = "your_OWM_API_Key"; // See: https://openweathermap.org/ 7 | const char server[] = "api.openweathermap.org"; 8 | 9 | // EXAMPLE API CALLS: 10 | //http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=24 11 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1 12 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1&lang=en 13 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric 14 | //http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric 15 | //http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=2 16 | //https://openweathermap.org/weathermap?basemap=map&cities=false&layer=radar&lat=51&lon=-2&zoom=10 17 | 18 | String City = "Bath"; 19 | String Latitude = "51.3704"; // Required location Latitude 20 | String Longitude = "-2.1376"; // Required location Longitude 21 | String Language = "en"; // NOTE: Only the weather description is translated by OWM 22 | // Languages supported: ar, bg, ca, de, el, en, es, fa, fi, fr, gl, hr, hu, it, ja, kr, la, lt, mk, nl, pl, pt, ro, ru, 23 | // se, sk, sl, tr, ua, vi, zh_cn, zh_tw 24 | String Hemisphere = "north"; // or "south" 25 | String Units = "M"; // Use 'M' for Metric or I for Imperial 26 | const char* Timezone = "GMT0BST,M3.5.0/01,M10.5.0/02"; // Choose your time zone from: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv 27 | //const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // e.g. Germany 28 | // See below for examples 29 | const char* ntpServer = "pool.ntp.org"; // Or, choose a time server close to you, but in most cases it's best to use pool.ntp.org to find an NTP server 30 | // then the NTP system decides e.g. 0.country-code.pool.ntp.org, 1.country-code.pool.ntp.org etc... 31 | // UK "0.uk.pool.ntp.org" 32 | // EU "0.europe.pool.ntp.org" 33 | // DE "0.de.pool.ntp.org" 34 | // US "0.north-america.pool.ntp.org" 35 | // See: https://www.ntppool.org/en/ 36 | 37 | // NOTE: Because this code uses the SETENV variable the following offset values are ignored! 38 | int gmtOffset_sec = 0; // UK normal time is GMT, so GMT Offset is 0, for US (-5Hrs) is typically -18000, AU is typically (+8hrs) 28800 39 | int daylightOffset_sec = 3600; // In the UK DST is +1hr or 3600-secs, other countries may use 2hrs 7200 or 30-mins 1800 or 5.5hrs 19800 Ahead of GMT use + offset behind - offset 40 | 41 | // Example time zones 42 | //const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe e.g. Austria, Belgium, France, Germany, Holland, Norway, Poland, Spain, Sweden 43 | //const char* Timezone = "CET-1CEST,M3.5.0,M10.5.0/3"; // Central Europe 44 | //const char* Timezone = "EST-2METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe e.g. Belarus, Finland, Romania 45 | //const char* Timezone = "EST5EDT,M3.2.0,M11.1.0"; // EST USA 46 | //const char* Timezone = "CST6CDT,M3.2.0,M11.1.0"; // CST USA 47 | //const char* Timezone = "MST7MDT,M4.1.0,M10.5.0"; // MST USA 48 | //const char* Timezone = "NZST-12NZDT,M9.5.0,M4.1.0/3"; // Auckland 49 | //const char* Timezone = "EET-2EEST,M3.5.5/0,M10.5.5/0"; // Asia 50 | //const char* Timezone = "ACST-9:30ACDT,M10.1.0,M4.1.0/3": // Australia 51 | --------------------------------------------------------------------------------