From: Roman Bazalevskiy Date: Tue, 20 Aug 2024 15:18:37 +0000 (+0300) Subject: Merge remote-tracking branch 'refs/remotes/origin/master' X-Git-Url: https://git.rvb.name/weathermon.git/commitdiff_plain/HEAD?hp=6691228bdeefd3ae7857c0aa4b8c76fa5f770a94 Merge remote-tracking branch 'refs/remotes/origin/master' --- diff --git a/weather_screen/weather_screen.ino b/weather_screen/weather_screen.ino index 75448cd..aa42be8 100644 --- a/weather_screen/weather_screen.ino +++ b/weather_screen/weather_screen.ino @@ -44,12 +44,10 @@ U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; #include "fonts.h" //define your default values here, if there are different values in config.json, they are overwritten. -char api_key[40] = ""; // See: https://openweathermap.org/ // It's free to get an API key, but don't take more than 60 readings/minute! +//char api_key[40] = ""; // See: https://openweathermap.org/ // It's free to get an API key, but don't take more than 60 readings/minute! char latitude[8] = "55.5"; char longitude[8] = "37.5"; -char language[4] = "RU"; // NOTE: Only the weather description is translated by OWM - // Examples: Arabic (AR) Czech (CZ) English (EN) Greek (EL) Persian(Farsi) (FA) Galician (GL) Hungarian (HU) Japanese (JA) - // Korean (KR) Latvian (LA) Lithuanian (LT) Macedonian (MK) Slovak (SK) Slovenian (SL) Vietnamese (VI) +char timezone[40] = "Europe/Moscow"; // Forecast beginning of day char current_weather_server[255] = ""; char current_weather_uri[255] = ""; @@ -59,7 +57,7 @@ char current_weather_humidity[40] = "HUMIDITY"; char current_weather_windspeed[40] = ""; char current_weather_lux[40] = "LUX"; -const char server[] = "api.openweathermap.org"; +const char server[] = "api.open-meteo.com"; int SleepDuration = 10; @@ -91,10 +89,9 @@ void WiFiSetup() { // The extra parameters to be configured (can be either global or just in the setup) // After connecting, parameter.getValue() will get you the configured value // id/name placeholder/prompt default length - WiFiManagerParameter custom_api_key("api_key", "OWN API key", api_key, 40); WiFiManagerParameter custom_latitude("latitide", "Latitude", latitude, 8); WiFiManagerParameter custom_longitude("longitude", "Longitude", longitude, 8); - WiFiManagerParameter custom_language("language", "Language", language, 4); + WiFiManagerParameter custom_timezone("timezone", "Time zone", timezone, 40); WiFiManagerParameter custom_current_weather_server("current_weather_server", "Weather server", current_weather_server, 255); WiFiManagerParameter custom_current_weather_uri("current_weather_uri", "Weather URI", current_weather_uri, 255); WiFiManagerParameter custom_current_weather_pressure("current_weather_pressure", "Pressure key", current_weather_pressure, 40); @@ -111,10 +108,9 @@ void WiFiSetup() { wifiManager.setSaveConfigCallback(saveConfigCallback); //add all your parameters here - wifiManager.addParameter(&custom_api_key); wifiManager.addParameter(&custom_latitude); wifiManager.addParameter(&custom_longitude); - wifiManager.addParameter(&custom_language); + wifiManager.addParameter(&custom_timezone); wifiManager.addParameter(&custom_current_weather_server); wifiManager.addParameter(&custom_current_weather_uri); wifiManager.addParameter(&custom_current_weather_pressure); @@ -125,6 +121,12 @@ void WiFiSetup() { wifiManager.setTimeout(300); + if (WiFi. status() != WL_CONNECTED) { + WiFi.disconnect(); + WiFi.mode(WIFI_AP); + WiFi.begin(); + } + if (!wifiManager.startConfigPortal()) { Serial.println("failed to connect and hit timeout"); delay(3000); @@ -136,10 +138,9 @@ void WiFiSetup() { Serial.println("connected..."); //read updated parameters - strcpy(api_key, custom_api_key.getValue()); strcpy(latitude, custom_latitude.getValue()); strcpy(longitude, custom_longitude.getValue()); - strcpy(language, custom_language.getValue()); + strcpy(timezone, custom_timezone.getValue()); strcpy(current_weather_server, custom_current_weather_server.getValue()); strcpy(current_weather_uri, custom_current_weather_uri.getValue()); strcpy(current_weather_pressure, custom_current_weather_pressure.getValue()); @@ -152,10 +153,10 @@ void WiFiSetup() { if (shouldSaveConfig) { Serial.println("saving config"); DynamicJsonDocument json(1024); - json["api_key"] = api_key; + // json["api_key"] = api_key; json["latitude"] = latitude; json["longitude"] = longitude; - json["language"] = language; + json["timezone"] = timezone; json["current_weather_server"] = current_weather_server; json["current_weather_uri"] = current_weather_uri; json["current_weather_pressure"] = current_weather_pressure; @@ -205,10 +206,9 @@ void setup() { serializeJson(json, Serial); if ( ! deserializeError ) { Serial.println("\nparsed json"); - if (json.containsKey("api_key")) { strcpy(api_key, json["api_key"]); } if (json.containsKey("latitude")) { strcpy(latitude, json["latitude"]); } if (json.containsKey("longitude")) { strcpy(longitude, json["longitude"]); } - if (json.containsKey("language")) { strcpy(language, json["language"]); } + if (json.containsKey("timezone")) { strcpy(timezone, json["timezone"]); } if (json.containsKey("current_weather_server")) { strcpy(current_weather_server, json["current_weather_server"]); } if (json.containsKey("current_weather_uri")) { strcpy(current_weather_uri, json["current_weather_uri"]); } if (json.containsKey("current_weather_pressure")) { strcpy(current_weather_pressure, json["current_weather_pressure"]); } @@ -238,6 +238,7 @@ void setup() { wifiManager.setTimeout(10); if(!wifiManager.autoConnect()) { Serial.println("failed to connect and hit timeout"); + WiFiSetup(); delay(3000); ESP.restart(); } @@ -251,32 +252,20 @@ void setup() { } int TimeZoneOffset; -int SunRise, SunSet; +// int SunRise, SunSet; int HourlyDT[MaxHourlyFC]; float HourlyTemp[MaxHourlyFC]; float HourlyFeelsLike[MaxHourlyFC]; float HourlyPressure[MaxHourlyFC]; float HourlyHumidity[MaxHourlyFC]; -float HourlyClouds[MaxHourlyFC]; +// float HourlyClouds[MaxHourlyFC]; float HourlyWindSpeed[MaxHourlyFC]; -float HourlyRain[MaxHourlyFC]; -float HourlySnow[MaxHourlyFC]; +// float HourlyRain[MaxHourlyFC]; +// float HourlySnow[MaxHourlyFC]; +// float HourlyShowers[MaxHourlyFC]; float HourlyPrecip[MaxHourlyFC]; -int DailyDT[MaxDailyFC]; -float DailyTempMin[MaxDailyFC]; -float DailyTempMax[MaxDailyFC]; -float DailyFeelsLikeMin[MaxDailyFC]; -float DailyFeelsLikeMax[MaxDailyFC]; -float DailyPressure[MaxDailyFC]; -float DailyHumidity[MaxDailyFC]; -float DailyClouds[MaxDailyFC]; -float DailyWindSpeed[MaxDailyFC]; -float DailyRain[MaxDailyFC]; -float DailySnow[MaxDailyFC]; -float DailyPrecip[MaxDailyFC]; - String UnixTime(int unix_time) { time_t tm = unix_time; struct tm *now_tm = localtime(&tm); @@ -314,73 +303,48 @@ bool DecodeWeather(WiFiClient& json) { // convert it to a JsonObject JsonObject root = doc.as(); - TimeZoneOffset = root["timezone_offset"].as(); + TimeZoneOffset = root["utc_offset_seconds"].as(); - SunRise = root["current"]["sunrise"].as(); - SunSet = root["current"]["sunset"].as(); - - CurrentWeather.dt = root["current"]["dt"].as(); - CurrentWeather.temp = root["current"]["temp"].as(); - CurrentWeather.feels_like = root["current"]["feels_like"].as(); - CurrentWeather.pressure = 0.75 * root["current"]["pressure"].as(); - CurrentWeather.humidity = root["current"]["humidity"].as(); - CurrentWeather.clouds = root["current"]["clouds"].as(); - CurrentWeather.wind_speed = root["current"]["wind_speed"].as(); - CurrentWeather.wind_deg = root["current"]["wind_deg"].as(); - CurrentWeather.rain = root["current"]["rain"]["1h"].as(); - CurrentWeather.snow = root["current"]["snow"]["1h"].as(); - CurrentWeather.description = root["current"]["weather"][0]["description"].as(); - CurrentWeather.icon = root["current"]["weather"][0]["icon"].as(); + + CurrentWeather.dt = root["current"]["time"].as(); + CurrentWeather.temp = root["current"]["temperature_2m"].as(); + CurrentWeather.feels_like = root["current"]["apparent_temperature"].as(); + CurrentWeather.pressure = 0.75 * root["current"]["surface_pressure"].as(); + CurrentWeather.humidity = root["current"]["relative_humidity_2m"].as(); + CurrentWeather.wind_speed = root["current"]["wind_speed_10m"].as(); + CurrentWeather.wind_deg = root["current"]["wind_direction_10m"].as(); + CurrentWeather.precip = root["current"]["precipitation"].as(); + CurrentWeather.wmo = root["current"]["weather_code"].as(); struct timeval now = { .tv_sec = CurrentWeather.dt }; settimeofday(&now, NULL); - JsonArray hourly = root["hourly"]; + int firstForecastDT = root["hourly"]["time"][0].as(); + + int offset = (CurrentWeather.dt - firstForecastDT) / 3600; + for (byte i=0; i(); - HourlyTemp[i] = HourlyForecasts[i].temp = hourly[i]["temp"].as(); - HourlyFeelsLike[i] = HourlyForecasts[i].feels_like = hourly[i]["feels_like"].as(); - HourlyPressure[i] = HourlyForecasts[i].pressure = 0.75 * hourly[i]["pressure"].as(); - HourlyHumidity[i] = HourlyForecasts[i].humidity = hourly[i]["humidity"].as(); - HourlyClouds[i] = HourlyForecasts[i].clouds = hourly[i]["clouds"].as(); - HourlyWindSpeed[i] = HourlyForecasts[i].wind_speed = hourly[i]["wind_speed"].as(); - HourlyForecasts[i].wind_deg = hourly[i]["wind_deg"].as(); - HourlyRain[i] = HourlyForecasts[i].rain = hourly[i]["rain"]["1h"].as(); - HourlySnow[i] = HourlyForecasts[i].snow = hourly[i]["snow"]["1h"].as(); - HourlyPrecip[i] = HourlyRain[i] + HourlySnow[i]; - HourlyForecasts[i].description = hourly[i]["weather"][0]["description"].as(); - HourlyForecasts[i].icon = hourly[i]["weather"][0]["icon"].as(); - } - - JsonArray daily = root["daily"]; - for (byte i=0; i(); - DailyTempMin[i] = DailyForecasts[i].temp_min = daily[i]["temp"]["min"].as(); - DailyFeelsLikeMin[i] = DailyForecasts[i].feels_like_min = daily[i]["feels_like"]["min"].as(); - DailyTempMax[i] = DailyForecasts[i].temp_max = daily[i]["temp"]["max"].as(); - DailyFeelsLikeMax[i] = DailyForecasts[i].feels_like_max = daily[i]["feels_like"]["max"].as(); - DailyPressure[i] = DailyForecasts[i].pressure = 0.75 * daily[i]["pressure"].as(); - DailyHumidity[i] = DailyForecasts[i].humidity = daily[i]["humidity"].as(); - DailyClouds[i] = DailyForecasts[i].clouds = daily[i]["clouds"].as(); - DailyWindSpeed[i] = DailyForecasts[i].wind_speed = daily[i]["wind_speed"].as(); - DailyForecasts[i].wind_deg = daily[i]["wind_deg"].as(); - DailyRain[i] = DailyForecasts[i].rain = daily[i]["rain"].as(); - DailySnow[i] = DailyForecasts[i].snow = daily[i]["snow"].as(); - DailyPrecip[i] = DailyRain[i] + DailySnow[i]; - DailyForecasts[i].description = daily[i]["weather"][0]["description"].as(); - DailyForecasts[i].icon = daily[i]["weather"][0]["icon"].as(); + HourlyDT[i] = HourlyForecasts[i].dt = root["hourly"]["time"][i+offset].as(); + HourlyTemp[i] = HourlyForecasts[i].temp = root["hourly"]["temperature_2m"][i+offset].as(); + HourlyFeelsLike[i] = HourlyForecasts[i].feels_like = root["hourly"]["apparent_temperature"][i+offset].as(); + HourlyPressure[i] = HourlyForecasts[i].pressure = 0.75 * root["hourly"]["surface_pressure"][i+offset].as(); + HourlyHumidity[i] = HourlyForecasts[i].humidity = root["hourly"]["relative_humidity_2m"][i+offset].as(); + HourlyWindSpeed[i] = HourlyForecasts[i].wind_speed = root["hourly"]["wind_speed_10m"][i+offset].as(); + HourlyForecasts[i].wind_deg = root["hourly"]["wind_direction_10m"][i+offset].as(); + HourlyPrecip[i] = HourlyForecasts[i].precip = root["hourly"]["precipitation"][i+offset].as(); + HourlyForecasts[i].wmo = root["hourly"]["weather_code"][i+offset].as(); } - + return true; } bool GetWeather(WiFiClient& client) { client.stop(); // close connection before sending a new request HTTPClient http; - char uri[128]; - sprintf(uri,"/data/2.5/onecall?lat=%s&lon=%s&appid=%s&lang=%s&units=metric",latitude,longitude,api_key,language); + char uri[512]; + sprintf(uri,"/v1/forecast?latitude=%s&longitude=%s¤t=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,surface_pressure,wind_speed_10m,wind_direction_10m&hourly=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,surface_pressure,wind_speed_10m,wind_direction_10m&wind_speed_unit=ms&timeformat=unixtime&forecast_days=3&timezone=%s",latitude,longitude,timezone); http.useHTTP10(true); - Serial.println("Connecting..."); + Serial.printf("Connecting http://%s%s ...\n", server, uri); http.begin(client, server, 80, uri); int httpCode = http.GET(); if(httpCode == HTTP_CODE_OK) { @@ -389,7 +353,7 @@ bool GetWeather(WiFiClient& client) { http.end(); return true; } else { - Serial.printf("connection failed, error: %s\n", http.errorToString(httpCode).c_str()); + Serial.printf("Forecast server connection failed, error: %s\n", http.errorToString(httpCode).c_str()); client.stop(); http.end(); return false; @@ -413,15 +377,19 @@ bool DecodeCurrentWeather(WiFiClient& json) { if (current_weather_pressure[0] && root.containsKey(current_weather_pressure)) { CurrentWeather.pressure = root[current_weather_pressure].as(); + Serial.printf("Current pressure: %f\n",CurrentWeather.pressure); } if (current_weather_temperature[0] && root.containsKey(current_weather_temperature)) { CurrentWeather.temp = root[current_weather_temperature].as(); + Serial.printf("Current temperature: %f\n",CurrentWeather.temp); } if (current_weather_humidity[0] && root.containsKey(current_weather_humidity)) { CurrentWeather.humidity = root[current_weather_humidity].as(); + Serial.printf("Current humidity: %f\n",CurrentWeather.humidity); } if (current_weather_windspeed[0] && root.containsKey(current_weather_windspeed)) { CurrentWeather.wind_speed = root[current_weather_windspeed].as(); + Serial.printf("Current windspeed: %f\n",CurrentWeather.wind_speed); } float Ro = (CurrentWeather.humidity/100) * 6.105 * pow(2.712828, 17.27 * CurrentWeather.temp/(237.7+CurrentWeather.temp)); @@ -430,8 +398,7 @@ bool DecodeCurrentWeather(WiFiClient& json) { float wm2 = root[current_weather_lux].as()/685; CurrentWeather.feels_like = CurrentWeather.feels_like + 0.70*wm2/(CurrentWeather.wind_speed + 10); } - Serial.print("Calculated feels like: "); - Serial.println(CurrentWeather.feels_like); + Serial.printf("Calculated feels like: %f\n",CurrentWeather.feels_like); return true; } @@ -442,7 +409,7 @@ bool GetCurrentWeather(WiFiClient& client) { client.stop(); // close connection before sending a new request HTTPClient http; http.useHTTP10(true); - Serial.println("Connecting..."); + Serial.printf("Connecting http://%s%s ...\n",current_weather_server,current_weather_uri); http.begin(client, current_weather_server, 80, current_weather_uri); int httpCode = http.GET(); if(httpCode == HTTP_CODE_OK) { @@ -451,7 +418,7 @@ bool GetCurrentWeather(WiFiClient& client) { http.end(); return true; } else { - Serial.printf("connection failed, error: %s\n", http.errorToString(httpCode).c_str()); + Serial.printf("Current weather server connection failed, error: %s\n", http.errorToString(httpCode).c_str()); client.stop(); http.end(); return false; @@ -774,30 +741,95 @@ void DisplayIcon(int x1, int x2, int y1, int y2, WeatherRecord weather) { int scale = w/14; int fscale = w/12; - String icon = weather.icon; - String description = weather.description; + int wmo = weather.wmo; + String description; - if (icon == "01d" || icon == "01n") + if (wmo == 0) { Sunny(x, y, scale); - else if (icon == "02d" || icon == "02n") + description = "ясно"; + } else if (wmo == 1) { MostlySunny(x, y, scale); - else if (icon == "03d" || icon == "03n") + description = "премущественно ясно"; + } else if (wmo == 2) { MostlyCloudy(x, y, scale); - else if (icon == "04d" || icon == "04n") + description = "переменная облачность"; + } else if (wmo == 3) { Cloudy(x, y, scale); - else if (icon == "09d" || icon == "09n") + description = "пасмурно"; + } else if (wmo == 61) { + Rain(x, y, scale); + description = "небольшой дождь"; + } else if (wmo == 63) { + Rain(x, y, scale); + description = "дождь"; + } else if (wmo == 65) { + Rain(x, y, scale); + description = "сильный дождь"; + } else if (wmo == 51) { + Rain(x, y, scale); + description = "небольшая морось"; + } else if (wmo == 53) { + Rain(x, y, scale); + description = "морось"; + } else if (wmo == 55) { + Rain(x, y, scale); + description = "сильная морось"; + } else if (wmo == 66) { + Rain(x, y, scale); + description = "ледяной дождь"; + } else if (wmo == 67) { Rain(x, y, scale); - else if (icon == "10d" || icon == "10n") + description = "сильный ледяной дождь"; + } else if (wmo == 56) { + Rain(x, y, scale); + description = "ледяная морось"; + } else if (wmo == 57) { + Rain(x, y, scale); + description = "сильная ледяная морось"; + } else if (wmo == 80) { + ExpectRain(x, y, scale); + description = "возможны ливни"; + } else if (wmo == 81) { + ExpectRain(x, y, scale); + description = "ливни"; + } else if (wmo == 82) { ExpectRain(x, y, scale); - else if (icon == "11d" || icon == "11n") + description = "сильные ливни"; + } else if (wmo == 95) { Tstorms(x, y, scale); - else if (icon == "13d" || icon == "13n") + description = "гроза"; + } else if (wmo == 96) { + Tstorms(x, y, scale); + description = "гроза с градом"; + } else if (wmo == 97) { + Tstorms(x, y, scale); + description = "сильная гроза"; + } else if (wmo == 71) { + Snow(x, y, scale); + description = "слабый снег"; + } else if (wmo == 73) { Snow(x, y, scale); - else if (icon == "50d") + description = "снег"; + } else if (wmo == 75) { + Snow(x, y, scale); + description = "сильный снег"; + } else if (wmo == 77) { + Snow(x, y, scale); + description = "снежная крупа"; + } else if (wmo == 85) { + Snow(x, y, scale); + description = "возможна метель"; + } else if (wmo == 86) { + Snow(x, y, scale); + description = "метель"; + } else if (wmo == 48) { Haze(x, y, scale); - else if (icon == "50n") + description = "ледяной туман"; + } else if (wmo == 45) { Fog(x, y, scale); - + description = "туман"; + } + SetCyrFont(fscale); drawString(x, y2-fscale*2/3, description, CENTER); @@ -830,29 +862,67 @@ void DisplayForecast(int x1, int x2, int y1, int y2, WeatherRecord weather) { int fscale = w/6; if (fscale<6) fscale = 6; - String icon = weather.icon; String temperature = String(weather.temp,1); - if (icon == "01d" || icon == "01n") + int wmo = weather.wmo; + + if (wmo == 0) { Sunny(x, y, scale); - else if (icon == "02d" || icon == "02n") + } else if (wmo == 1) { MostlySunny(x, y, scale); - else if (icon == "03d" || icon == "03n") + } else if (wmo == 2) { MostlyCloudy(x, y, scale); - else if (icon == "04d" || icon == "04n") + } else if (wmo == 3) { Cloudy(x, y, scale); - else if (icon == "09d" || icon == "09n") + } else if (wmo == 61) { + Rain(x, y, scale); + } else if (wmo == 63) { + Rain(x, y, scale); + } else if (wmo == 65) { + Rain(x, y, scale); + } else if (wmo == 51) { + Rain(x, y, scale); + } else if (wmo == 53) { + Rain(x, y, scale); + } else if (wmo == 55) { + Rain(x, y, scale); + } else if (wmo == 66) { + Rain(x, y, scale); + } else if (wmo == 67) { Rain(x, y, scale); - else if (icon == "10d" || icon == "10n") + } else if (wmo == 56) { + Rain(x, y, scale); + } else if (wmo == 57) { + Rain(x, y, scale); + } else if (wmo == 80) { + ExpectRain(x, y, scale); + } else if (wmo == 81) { + ExpectRain(x, y, scale); + } else if (wmo == 82) { ExpectRain(x, y, scale); - else if (icon == "11d" || icon == "11n") + } else if (wmo == 95) { Tstorms(x, y, scale); - else if (icon == "13d" || icon == "13n") + } else if (wmo == 96) { + Tstorms(x, y, scale); + } else if (wmo == 97) { + Tstorms(x, y, scale); + } else if (wmo == 71) { + Snow(x, y, scale); + } else if (wmo == 73) { Snow(x, y, scale); - else if (icon == "50d") + } else if (wmo == 75) { + Snow(x, y, scale); + } else if (wmo == 77) { + Snow(x, y, scale); + } else if (wmo == 85) { + Snow(x, y, scale); + } else if (wmo == 86) { + Snow(x, y, scale); + } else if (wmo == 48) { Haze(x, y, scale); - else if (icon == "50n") + } else if (wmo == 45) { Fog(x, y, scale); + } SetCyrFont(fscale); drawString(x, y1+fscale*2/3, UnixTimeOnly(weather.dt+ TimeZoneOffset), CENTER); @@ -940,17 +1010,29 @@ void DisplayWeather() { void loop() { delay(2000); + WiFiManager wifiManager; - wifiManager.setTimeout(10); - if(!wifiManager.autoConnect()) { + WiFi.mode(WIFI_STA); + wifiManager.setConnectTimeout(10); - Serial.println("failed to connect and hit timeout"); + if (WiFi.SSID().isEmpty()) { - } else { + wifiManager.setTimeout(600); + WiFiSetup(); + + } else if (!wifiManager.autoConnect()) { - if (Serial.available() && Serial.read() == 'c') { - WiFiSetup(); - } + Serial.println("failed to connect to stored SSID and hit timeout"); + wifiManager.setTimeout(600); + WiFi.mode(WIFI_AP); + WiFiSetup(); + + } else if (Serial.available() && Serial.read() == 'c') { + + wifiManager.setTimeout(600); + WiFiSetup(); + + } else { // put your main code here, to run repeatedly: byte Attempts = 1; diff --git a/weather_screen/weather_types.h b/weather_screen/weather_types.h index 6a49dff..bdf869b 100644 --- a/weather_screen/weather_types.h +++ b/weather_screen/weather_types.h @@ -1,26 +1,17 @@ typedef struct { // For current Day and Day 1, 2, 3, etc int dt; float temp; - float temp_min; - float temp_max; float feels_like; - float feels_like_min; - float feels_like_max; float pressure; float humidity; - float clouds; float wind_speed; float wind_deg; - float rain; - float snow; - String description; - String icon; + float precip; + int wmo; } WeatherRecord; WeatherRecord CurrentWeather; #define MaxHourlyFC 48 -#define MaxDailyFC 7 WeatherRecord HourlyForecasts[MaxHourlyFC]; -WeatherRecord DailyForecasts[MaxDailyFC];