diff --git a/README.md b/README.md index 370e0a8..f15c738 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# OpenWeatherOneCall v3.3.3 ![IMAGE OF LIGHTNING](https://github.com/JHershey69/OpenWeatherOneCall/blob/master/images/lightning.jpg) +# OpenWeatherOneCall v4.0.0 ![IMAGE OF LIGHTNING](https://github.com/JHershey69/OpenWeatherOneCall/blob/master/images/lightning.jpg) ## This is for ESP32 only ##This is an upgrade for OpenWeatherMap API 3.0 and Air Pollution API 2.5 -**Please be advised** OpenWeather has changed ONE CALL to a subscription service. Signing up is +**Please be advised** OpenWeather has changed ONE CALL to a subscription PAY BY CALL service. Signing up is required to get your KEY, but you get 1000 calls a day for free (this is one call every 90 seconds and that is A LOT for free) and you will continue to get all of the features of this library. @@ -57,4 +57,6 @@ v3.0.0 has a **Legacy Mode** to maintain ease of use for previous versions
--Minor fix to update API change for Air Quality.
**v3.3.3**
--Minor fix to update API change for Air Quality and Example scripts. +
**v4.0.0** +
--MAJOR REWRITE with addition options and variable SEE DOCUMENTATION. Some features deprecated no longer supported. diff --git a/docs/OPEN WEATHER ONE CALL ADDENDUM.pdf b/docs/OPEN WEATHER ONE CALL ADDENDUM.pdf deleted file mode 100644 index 137e9a3..0000000 Binary files a/docs/OPEN WEATHER ONE CALL ADDENDUM.pdf and /dev/null differ diff --git a/docs/OpenWeatherOneCall Manual v3.0.4.pdf b/docs/OpenWeatherOneCall Manual v3.0.4.pdf deleted file mode 100644 index d31f7c1..0000000 Binary files a/docs/OpenWeatherOneCall Manual v3.0.4.pdf and /dev/null differ diff --git a/docs/OpenWeatherOneCall v3.0.4 Variables - Weather.pdf b/docs/OpenWeatherOneCall v3.0.4 Variables - Weather.pdf deleted file mode 100644 index ed68cd6..0000000 Binary files a/docs/OpenWeatherOneCall v3.0.4 Variables - Weather.pdf and /dev/null differ diff --git a/docs/OpenWeatherOneCall v4.0.0 Variables - Weather.pdf b/docs/OpenWeatherOneCall v4.0.0 Variables - Weather.pdf new file mode 100644 index 0000000..7c005f8 Binary files /dev/null and b/docs/OpenWeatherOneCall v4.0.0 Variables - Weather.pdf differ diff --git a/docs/OpenWeatherOneCall_Manual_4.pdf b/docs/OpenWeatherOneCall_Manual_4.pdf new file mode 100644 index 0000000..778ecc0 Binary files /dev/null and b/docs/OpenWeatherOneCall_Manual_4.pdf differ diff --git a/docs/README.md b/docs/README.md index 02d99c1..dfd797a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1 +1 @@ -**Documentation for v3.0.4 and above** +**Documentation for v4.0.0 and above** diff --git a/examples/Simple Latitude Longitude Weather/Simple_Latitude_Longitude_Weather_Example.ino b/examples/Simple Latitude Longitude Weather/Simple_Latitude_Longitude_Weather_Example.ino index e24eeb2..070ac43 100644 --- a/examples/Simple Latitude Longitude Weather/Simple_Latitude_Longitude_Weather_Example.ino +++ b/examples/Simple Latitude Longitude Weather/Simple_Latitude_Longitude_Weather_Example.ino @@ -1,7 +1,7 @@ /* Open Weather One Call Library - v3.0.4 - Copyright 2020 - Jessica Hershey + v4.0.0 + Copyright 2020=2024 - Jessica Hershey www.github.com/JHershey69 One Call API key at www.openweathermap.org @@ -17,50 +17,86 @@ */ // ===== Required libraries (Other required libraries are installed in header file) + #include -#include //<-- Might be native to ESP32 -// =============================================================================== + +// ======= END REQUIRED LIBRARIES ======================================= + // ========= Required only for TFT Display ============== #include -#include // Hardware-specific library +#include // Hardware-specific library -TFT_eSPI tft = TFT_eSPI(); // Invoke custom library +TFT_eSPI tft = TFT_eSPI(); // Invoke custom library //========= End TFT Library ============================= #define HOMESSID "YOUR SSID HERE" #define HOMEPW "YOUR SSID PW HERE" -#define ONECALLKEY "YOUR ONE CALL API KEY HERE" +char ONECALLKEY[] = "YOUR ONE CALL KEY HERE"; // Only needed if WiFiTri installed for Triangulation may have fee from Google -#define GOOGLEKEY "YOUR GOOGLE DEVELOPER API KEY"" +// #define GOOGLEKEY "YOUR GOOGLE DEVELOPER API KEY"" + + +// OpenWeatherOneCall API Options and Settings +//*************** LOCATION ************************ -// OpenWeatherOneCall variables +int myTimeZone = -4; //<--------- GMT OFFSET -//For Latitude and Longitude Location setting if used -float myLATITUDE = 39.9537; //<-----This is Toms River, NJ +//Location MODES are used to tell the program how you are delivering your coordinates: +// 1 LAT/LON <----- Can be from GPS, manual as below, or whatever method gets you a latitude and longitude except IP location +// 2 CITYID <----- From OpenWeatherMap City ID list. Not very accurate. +// 3 IP ADDRESS [NOT REALLY SUGGESTED CAN BE WILDLY OFFSET LOCATION IF HOTSPOT] + +int locationMode = 1; + +//For Latitude and Longitude Location MODE 1 setting if used +float myLATITUDE = 39.9537; //<-----This is Toms River, NJ float myLONGITUDE = -74.1979; -//For City ID Location setting if used -int myCITYID = 4504476; //<-----Toms River, NJ USA +//For City ID Location MODE setting if used +int myCITYID = 4504476; //<-----Toms River, NJ USA + +//*************** END LOCATION ********************** + +//*************** FLAGS ***************************** +int myCURRENT = 1; //<-----0 is CURRENT off, 1 is on +int myAIRQUALITY = 1; //<-----0 is Air Quality off, 1 is on +int myTIMESTAMP = 1; //<-----0 is TIMESTAMP off, 1 is TIMESTAMP on +char timestampDate[11] = "03/03/2023"; //<-----MM/DD/YYYY right here limit is 4 days in future, past is 01/01/1979 +int myOVERVIEW = 1; //<-----0 is OVERVIEW off, 1 is OVERVIEW on +char overviewDate[9] = "TOMORROW"; //<-----"TODAY" or "TOMORROW" +//*************************************************** -int myUNITS = IMPERIAL; //<-----METRIC, IMPERIAL, KELVIN (IMPERIAL is default) +//************** EXCLUDES *************************** +// Excludes are EXCL_D, EXCL_H, EXCL_M, EXCL_A +// Those are DAILY, HOURLY, MINUTELY, and ALERTS +// You set them like this: myEXCLUDES = EXCL_D+EXCL_H+EXCL_M+EXCL_A +// If you leave them set to 0 as below you get a JSON file with ALL CURRENT WEATHER measurements which is a huge file -//Can't get current and historical at the same time -int myHISTORY = NULL; //<-----Only required for historical data up to 5 days +int myEXCLUDES = 0; //<-----0 Excludes is default +//*************************************************** -//See manual for excludes, only CURRENT Data allows 1,000,000 calls a month -int myEXCLUDES = EXCL_D+EXCL_H+EXCL_M; //<-----0 Excludes is default +//*************** UNITS OF MEASURMENT and DATE TIME FORMAT *************** +int myUNITS = IMPERIAL; //<-----METRIC, IMPERIAL, KELVIN (IMPERIAL is default) + +//Date Time Format +int myDTF = 1; //1 M/D/Y 24H + // 2 D/M/Y 24H + // 3 M/D/Y 12H + // 4 D/M/Y 12H + +//************************************************************************ //for debugging loop counting int nextCall = 0; //int callAttempt = 1; -OpenWeatherOneCall OWOC; // <------ Invoke Library like this +OpenWeatherOneCall OWOC; // <------ Invoke Library like this // WiFi Connect function ********************************** void connectWifi() { @@ -73,11 +109,12 @@ void connectWifi() { TryNum++; if (TryNum > 20) { printf("\nUnable to connect to WiFi. Please check your parameters.\n"); - for (;;); + for (;;) + ; } } printf("Connected to: % s\n\n", HOMESSID); -} //================== END WIFI CONNECT ======================= +} //================== END WIFI CONNECT ======================= @@ -92,95 +129,102 @@ void setup() { // WiFi Connection required ********************* while (WiFi.status() != WL_CONNECTED) { connectWifi(); - } //<----------------End WiFi Connection Check - - - /* - Getting the Weather - If you desire WiFi Triangulation to find your - Latitude and Longitude, please install the - WiFiTriangulation Library - Otherwise Latitude and Longitude can be from any - source such as a GPS or entered manually. - If you DO NOT set the Latitude and Longitude - the program will attempt to find your location - by IP Address (which could be grossly innacurate - if you are using a HOTSPOT on your phone) - */ - - // First set your Key value - OWOC.setOpenWeatherKey(ONECALLKEY); - - // Second set your position (can be CITY ID, IP ADDRESS, GPS, or Manual LATITUDE/LONGITUDE) - // Choose one of the following options - - // Here we use the Lat and Lon for Pensacola, Florida (Using the predefined values) - OWOC.setLatLon(myLATITUDE, myLONGITUDE); - - // If we are using CITY ID - //OWOC.setLatLon(myCITYID); - - // If we want to use IP Address Geolocation use no arguments - //OWOC.setLatLon(); + } //<----------------End WiFi Connection Check - // Third set any EXCLUDES if required (Here we are not using any - OWOC.setExcl(myEXCLUDES); - - // Set History if you want historical weather other wise NULL - OWOC.setHistory(myHISTORY); - - // Set UNITS of MEASURE otherwise default is IMPERIAL - OWOC.setUnits(myUNITS); //Now call the weather. Please notice no arguments are required in this call OWOC.parseWeather(); - //Now display some information, note the pointer requirement for current and alert, this is NEW for v3.0.0 + //Now display some information, note the pointer requirement for current and alert - // Location info is available for ALL modes (History/Current) + // Location info is available for ALL modes printf("\nLocation: % s, % s % s\n", OWOC.location.CITY, OWOC.location.STATE, OWOC.location.COUNTRY); + //Verify all other values exist before using - if (myHISTORY) //Remember you can't get historical and current weather at the same time so we are checking here for NULL - { - if (OWOC.history) - { - printf("Mean Temp for % s : % .0f\n", OWOC.history[0].weekDayName, OWOC.history[0].temperature); - } - } else //If History was NULL we go to Current Weather. Air Quality is returned with Current Weather (Historical Air Quality COMING SOON!) + + if (OWOC.current) //Check if data is in the struct for all variables before using them { - if (OWOC.current) //Always check to see if data is in the struct for all variables before using them - { - printf("Current Temp : % .0f\n", OWOC.current->temperature); - printf("Current Humidity : % .0f\n", OWOC.current->humidity); + printf("\nCURRENT\n"); + printf("Temp : % .0f\n", OWOC.current->temperature); + printf("Humidity : % .0f\n", OWOC.current->humidity); + } else printf("\nCURRENT IS OFF or EMPTY\n"); + + //Forecast is part of CURRENT and has info for the 8 days future weather. See variables PDF + // The JSON holds this info in the DAILY section and EXCL_D turns this off + if (OWOC.forecast) { //Check if data is in the struct for all variables before using them + printf("\nFORECAST - Up to 8 days future forecast\n"); + for (int x = 0; x < 8; x++) { + printf("Date: %s High Temperature: % .0f\n", OWOC.forecast[x].readableDateTime, OWOC.forecast[x].temperatureHigh); } + } else printf("\nFORECAST IS OFF or EMPTY\n"); - if(OWOC.quality) //See Addendum for complete list of Air Quality Variables - { - printf("\nAir Quality : % d\n", OWOC.quality->aqi); - printf("Carbon : % .2f\n", OWOC.quality->co); - printf("Nitrogen : % .2f\n", OWOC.quality->no); - printf("Ozone : % .2f\n", OWOC.quality->o3); - } + //Hour is part of CURRENT and has info for 48 hours future weather. See variables PDF + // The JSON holds this info in the HOURLY section and EXCL_H turns this off + if (OWOC.hour) { //Check if data is in the struct for all variables before using them + printf("\nHOURLY - Up to 48 hours forecast\n"); - if (OWOC.forecast) - { - printf("\nForecast Temp Tomorrow : % .0f\n", OWOC.forecast[1].temperatureHigh); + for (int x = 0; x < 6; x++) { + printf("%s Actual Temp: % .0f\n", OWOC.hour[x].readableTime, OWOC.hour[x].temperature); + printf("%s Feels Like Temp: % .0f\n", OWOC.hour[x].readableTime, OWOC.hour[x].apparentTemperature); } + } else printf("\nHOURLY IS OFF or EMPTY\n"); - if (OWOC.alert) //Only if ALERTS aren't excluded - { - printf("\nALERT *** ALERT *** ALERT\n"); - printf("Sender : % s\n", OWOC.alert->senderName); - printf("Event : % s\n", OWOC.alert->event); - printf("ALERT : % s\n", OWOC.alert->summary); - } else {printf("\nNo Alerts For Area\n"); + //MINUTELY is part of CURRENT and has info for 60 minutes future weather. See variables PDF + // The JSON holds this info in the MINUTELY section and EXCL_M turns this off + if (OWOC.minute) { //Check if data is in the struct for all variables before using them + printf("\nMINUTELY - Up to 60 minutes precipitation forecast\n"); + + for (int x = 0; x < 30; x++) { + printf("%s Precipitation: % .2f\n", OWOC.minute[x].readableTime, OWOC.minute[x].precipitation); } + } else printf("\nHOURLY IS OFF or EMPTY\n"); + //ALERTS is part of CURRENT and has info for alerts in your area. See variables PDF + // The JSON holds this info in the ALERTS section and EXCL_A turns this off + if (OWOC.alert) //Check if data is in the struct for all variables before using them + { + printf("\nALERT *** ALERT *** ALERT\n"); + printf("Sender : % s\n", OWOC.alert->senderName); + printf("Event : % s\n", OWOC.alert->event); + printf("ALERT : % s\n", OWOC.alert->summary); + } else { + printf("\nNo Alerts For Area, Alerts are EXCLUDED, or Alerts struct is empty\n"); } + //The following sections are separate from CURRENT and are turned off and on with their own flags + + if (OWOC.timestamp) { //Check if data is in the struct for all variables before using them + printf("\nTIMESTAMP - Single day weather for any date from 01-01-1979 to 4 days in future\n"); + printf("% s - % s\nTemperature: % .0f\nFeels like: % .0f\nHumidity: % .0F%%\n", OWOC.timestamp[0].weekDayName, OWOC.timestamp[0].readableDateTime, OWOC.timestamp[0].temperature,OWOC.timestamp[0].apparentTemperature,OWOC.timestamp[0].humidity); + } else printf("\nTIMESTAMP IS OFF or EMPTY\n"); + + if (OWOC.overView) //Check if data is in the struct for all variables before using them + { + printf("\nOVERVIEW - Summary for TODAY or TOMORROW generated by AI\n"); + + // Print the struct values to verify + printf("Latitude: %.6f\n", OWOC.overView->lat); // %.6f for printing double with 6 decimal places + printf("Longitude: %.6f\n", OWOC.overView->lon); + printf("Timezone: %s\n", OWOC.overView->tz); // %s for const char* (string) + printf("Date: %s\n", OWOC.overView->date); + printf("Units: %s\n", OWOC.overView->units); + printf("Weather Overview: %s\n", OWOC.overView->weather_overview); + + } else printf("\nOVERVIEW IS OFF or EMPTY\n"); + + if (OWOC.quality) //Check if data is in the struct for all variables before using them + { + printf("\nAir Quality and Pollution Numbers for today"); + printf("\nAir Quality : % d\n", OWOC.quality->aqi); + printf("Carbon : % .2f\n", OWOC.quality->co); + printf("Nitrogen : % .2f\n", OWOC.quality->no); + printf("Ozone : % .2f\n", OWOC.quality->o3); + } else printf("\nAIR QUALITY IS OFF or EMPTY\n"); + + + } void loop() { - } diff --git a/examples/platformio.ini b/examples/platformio.ini index a6ff8dd..a835ebf 100644 --- a/examples/platformio.ini +++ b/examples/platformio.ini @@ -17,7 +17,7 @@ framework = arduino upload_speed = 1500000 monitor_speed = 115200 lib_deps = - ArduinoJson @ 6.17.2 + ArduinoJson @ 7 [env:esp32dev] platform = espressif32 diff --git a/library.json b/library.json index 259a1e4..91fba6b 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "OpenWeatherOneCall", - "version": "3.3.3", + "version": "4.0.0", "keywords": "openweather, darksky, weather, esp32, arduino", "description": "OpenWeatherMap Library to use One Call on the ESP32. This library allows the ESP32 to receive and parse the json from OpenWeatherMap", "dependencies": @@ -8,7 +8,7 @@ { "owner": "bblanchon", "name": "ArduinoJson", - "version": "^6.17.2" + "version": "^7.0.0" } ], "repository": diff --git a/library.properties b/library.properties index 02596c4..65d9908 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=OpenWeatherOneCall -version=3.3.3 +version=4.0.0 author=JHershey69 maintainer=JHershey69 -sentence=Current and Seven Day Weather Forecast Library for ESP32. -paragraph=This library depends on ArduinoJson v6+ +sentence=Weather Forecast Library for ESP32. +paragraph=This library depends on ArduinoJson v7+ category=other url=https://github.com/JHershey69/OpenWeatherOneCall architectures=esp32 diff --git a/src/DateTimeConversion.cpp b/src/DateTimeConversion.cpp index 78927ee..5380dc7 100644 --- a/src/DateTimeConversion.cpp +++ b/src/DateTimeConversion.cpp @@ -1,9 +1,9 @@ -// OpenWeatherOneCall v3.1.8 +// OpenWeatherOneCall v4.0.0 // www.github.com/jhershey69 +// DO NOT EDIT + +#include "OpenWeatherOneCall.h" -#include -#include -#include // Takes any EPOCH time converts to Human Readable void dateTimeConversion(long _epoch, char *_buffer, int _format) @@ -17,11 +17,30 @@ void dateTimeConversion(long _epoch, char *_buffer, int _format) 5/6 TIME ONLY 24H 7/8 TIME ONLY 12H 9 DAY SHORTNAME + 10 M/D/Y ONLY + 11 D/M/Y ONLY */ + // NTP Server + const char* ntpServer = "pool.ntp.org"; + const long gmtOffset_sec = myTimeZone * 3600; + const int daylightOffset_sec = 0; + +// Initialize NTP + configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); + + time_t rawtime = _epoch; struct tm *ptm = localtime(&rawtime); + + // Add 1 to the day + ptm->tm_mday += 1; + + // Normalize the time structure (this handles overflow of days, months, etc.) + mktime(ptm); + + switch (_format) { @@ -55,10 +74,22 @@ void dateTimeConversion(long _epoch, char *_buffer, int _format) // DAY SHORTNAME strftime(_buffer,20,"%a",ptm); break; + case 10: + // M/D/Y 24H + strftime(_buffer,20,"%m/%d/%Y",ptm); + break; + case 11: + // D/M/Y 24H + strftime(_buffer,20,"%d/%m/%Y",ptm); + break; default: // M/D/Y 24H - strftime(_buffer,20,"%m/%d/%Y %R",ptm); + strftime(_buffer,20,"%m/%d/%Y",ptm); } } + + + + diff --git a/src/OpenWeatherOneCall.cpp b/src/OpenWeatherOneCall.cpp index e5717d8..2ab3d6d 100644 --- a/src/OpenWeatherOneCall.cpp +++ b/src/OpenWeatherOneCall.cpp @@ -1,6 +1,7 @@ /* - OpenWeatherOneCall.cpp v3.3.3 - copyright 2020 - Jessica Hershey + OpenWeatherOneCall.cpp v4.0.0 + Updated for ArduinoJSON v7 on Aug 1, 2024 + copyright 2020/2024 - Jessica Hershey www.github.com/JHershey69 Open Weather Map - Weather Conditions @@ -9,9 +10,16 @@ REVISION HISTORY See User Manual + + ************************************************************** + ************NO USER EDITABLE LINES IN THIS FILE*************** + ************************************************************** */ #include "OpenWeatherOneCall.h" + + + void dateTimeConversion(long _epoch, char *_buffer, int _format); OpenWeatherOneCall::OpenWeatherOneCall() @@ -29,30 +37,57 @@ char DS_URL2[100]; #define AQ_URL2 "&lon=" #define AQ_URL3 "&appid=" - - - -// For Historical Weather Calls ********** +// For TIMESTAMP Weather Calls ********** #define TS_URL1 "https://api.openweathermap.org/data/3.0/onecall/timemachine" #define TS_URL2 "&dt=" +// For OVERVIEW weather calls *********** +#define OV_URL1 "https://api.openweathermap.org/data/3.0/onecall/overview" +#define OV_URL2 "&date=" + // For CITY Id calls #define CI_URL1 "api.openweathermap.org/data/3.0/weather?id=" #define CI_URL2 "&appid=" #define SIZEOF(a) sizeof(a)/sizeof(*a) + // Main Method for Weather API Call and Parsing int OpenWeatherOneCall::parseWeather(void) { + OpenWeatherOneCall::setOpenWeatherKey(ONECALLKEY); + OpenWeatherOneCall::setExcl(myEXCLUDES); + OpenWeatherOneCall::setUnits(myUNITS); + OpenWeatherOneCall::setDateTimeFormat(myDTF); + OpenWeatherOneCall::setCurrent(myCURRENT); + OpenWeatherOneCall::setAirQuality(myAIRQUALITY); + OpenWeatherOneCall::setTimestamp(myTIMESTAMP, timestampDate); + OpenWeatherOneCall::setOverview(myOVERVIEW, overviewDate); + + int error_code = 0; - // WiFi.status not declared in this scope error if (WiFi.status() != WL_CONNECTED) { return 25; } - // End WiFi error + + + // LOCATION MODE SET + + if(locationMode == 1) + { + OpenWeatherOneCall::setLatLon(myLATITUDE, myLONGITUDE); + } + else if(locationMode == 2) + { + OpenWeatherOneCall::setLatLon(myCITYID); + } + else if(locationMode == 3) + { + OpenWeatherOneCall::setLatLon(); + } + unsigned int SIZE_CAPACITY = 32768; @@ -63,29 +98,41 @@ int OpenWeatherOneCall::parseWeather(void) { OpenWeatherOneCall::getLocationInfo(); - if(USER_PARAM.OPEN_WEATHER_HISTORY) //If Historical Weather is requested, no CURRENT weather returned + if(USER_PARAM.OPEN_WEATHER_TIMESTAMP) { - OpenWeatherOneCall::freeCurrentMem(); - OpenWeatherOneCall::freeForecastMem(); - OpenWeatherOneCall::freeAlertMem(); - OpenWeatherOneCall::freeHourMem(); - OpenWeatherOneCall::freeMinuteMem(); - - int error_code = OpenWeatherOneCall::createHistory(); + int error_code = OpenWeatherOneCall::createTimestamp(); } else { - OpenWeatherOneCall::freeHistoryMem(); + OpenWeatherOneCall::freeTimestampMem(); + } - // TEST AIR QUALITY CALL ********************* - OpenWeatherOneCall::createAQ(384); + if(USER_PARAM.OPEN_WEATHER_OVERVIEW) + { + int error_code = OpenWeatherOneCall::createOverview(); + } + else + { + OpenWeatherOneCall::freeOverviewMem(); + } + if(USER_PARAM.OPEN_WEATHER_AIRQUALITY) + { + int error_code = OpenWeatherOneCall::createAQ(384); + } - //******************************************** + if(USER_PARAM.OPEN_WEATHER_CURRENT) + { int error_code = OpenWeatherOneCall::createCurrent(SIZE_CAPACITY); - } + else + { + OpenWeatherOneCall::freeCurrentMem(); + } + + + if(error_code) { return error_code; @@ -98,335 +145,161 @@ int OpenWeatherOneCall::parseWeather(void) return 0; } +/* +REMOVED LEGACY CALLING METHOD AS OF 3.3.4 +*/ -int OpenWeatherOneCall::parseWeather(char* DKEY, char* GKEY, float SEEK_LATITUDE, float SEEK_LONGITUDE, bool SET_UNITS, int CITY_ID, int API_EXCLUDES, int GET_HISTORY) -{ - //Legacy calling Method for versions prior to v3.0.0 - - OpenWeatherOneCall::setOpenWeatherKey(DKEY); - if(SET_UNITS) - { - OpenWeatherOneCall::setUnits(METRIC); - } - OpenWeatherOneCall::setExcl(API_EXCLUDES); - OpenWeatherOneCall::setHistory(GET_HISTORY); - if(SEEK_LATITUDE && SEEK_LONGITUDE) - { - OpenWeatherOneCall::setLatLon(SEEK_LATITUDE,SEEK_LONGITUDE); - } - else if (CITY_ID) - { - OpenWeatherOneCall::setLatLon(CITY_ID); - } - else - OpenWeatherOneCall::setLatLon(); - OpenWeatherOneCall::parseWeather(); -} - -int OpenWeatherOneCall::setLatLon(float _LAT, float _LON) -{ - int error_code = 0; - - if(abs(_LAT) <= 90) - { - USER_PARAM.OPEN_WEATHER_LATITUDE = _LAT; - location.LATITUDE = _LAT; //User copy - } - else - error_code += 1; - - if(abs(_LON) <= 180) - { - USER_PARAM.OPEN_WEATHER_LONGITUDE = _LON; - location.LONGITUDE = _LON; //User copy - } - else - error_code += 2; - - if(error_code) - return error_code; - - return (EXIT_SUCCESS); -} - -int OpenWeatherOneCall::setLatLon(int _CITY_ID) -{ - int error_code = 0; - - char cityURL[110]; - char* URL1 = "http://api.openweathermap.org/data/3.0/weather?id="; - char* URL2 = "&appid="; - - sprintf(cityURL,"%s%d%s%s",URL1,_CITY_ID,URL2,USER_PARAM.OPEN_WEATHER_DKEY); - error_code = OpenWeatherOneCall::parseCityCoordinates(cityURL); - if(error_code) - return error_code; - - return (EXIT_SUCCESS); - -} - -int OpenWeatherOneCall::setLatLon(void) +int OpenWeatherOneCall::createTimestamp() { - // IP address of NON-CELLULAR WiFi. Hotspots won't work properly. - int error_code = 0; - error_code = OpenWeatherOneCall::getIPLocation(); - if(error_code) - { - return error_code; - } - error_code = OpenWeatherOneCall::getIPAPILocation(_ipapiURL); - if(error_code) - { - return error_code; - } + char timestampURL[200] = {0}; - return 0; -} + long tempEPOCH = extractDate(USER_PARAM.OPEN_WEATHER_TIMESTAMP_DATE); + //Setup API URL for TIMESTAMP Weather Call [Single Timestamp only] -int OpenWeatherOneCall::parseCityCoordinates(char* CTY_URL) -{ - int error_code = 0; + sprintf(timestampURL,"%s?lat=%.6f&lon=%.6f%s%ld&units=%s%s%s",TS_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE,TS_URL2,tempEPOCH,units,DS_URL3,USER_PARAM.OPEN_WEATHER_DKEY); HTTPClient http; - http.begin(CTY_URL); + http.begin(timestampURL); int httpCode = http.GET(); - if(httpCode > 399) + + if (httpCode > 399) { - if(httpCode == 404) + if(httpCode == 401) { http.end(); - error_code += 4; - return error_code; + return 22; } else { http.end(); - error_code += 5; - return error_code; + return 21; } - } - const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(13) + 270; - DynamicJsonDocument doc(capacity); + + JsonDocument doc; deserializeJson(doc, http.getString()); - if(doc["coord"]["lon"]) - { - USER_PARAM.OPEN_WEATHER_LONGITUDE = doc["coord"]["lon"]; // -74.2 - } - else - { - error_code += 6; - } + strncpy(location.timezone,doc["timezone"],50); + location.timezoneOffset = doc["timezone_offset"]; - if(doc["coord"]["lat"]) + if(!timestamp) { - USER_PARAM.OPEN_WEATHER_LATITUDE = doc["coord"]["lat"]; // 39.95 + timestamp = (struct TIMESTAMP *)calloc(25,sizeof(struct TIMESTAMP)); } - else + + //Current in historical is the time of the request on that day + JsonObject current = doc["data"][0]; + timestamp[0].dayTime = current["dt"]; // 1607292481 + + if(current["dt"]) { - error_code += 7; + // Create human readable date and time + + long tempTime = current["dt"]; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,timestamp[0].readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT+9); } - http.end(); + timestamp[0].sunrise = current["sunrise"]; // 1607256309 - if(error_code) + if(current["sunrise"]) { - http.end(); - return error_code; + // Create human readable date and time + + long tempTime = current["sunrise"]; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,timestamp[0].readableSunrise,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); } - return (EXIT_SUCCESS); -} + timestamp[0].sunset = current["sunset"]; // 1607290280 -int OpenWeatherOneCall::getIPLocation() -{ - int error_code = 0; + if(current["sunset"]) + { + // Create human readable date and time - HTTPClient http; - http.begin("https://api64.ipify.org/ HTTP/1.1\r\nHost: api.ipify.org\r\n\r\n"); + long tempTime = current["sunset"]; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,timestamp[0].readableSunset,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); + } - int httpCode = http.GET(); + timestamp[0].temperature = current["temp"]; // 35.82 + timestamp[0].apparentTemperature = current["feels_like"]; // 21.7 + timestamp[0].pressure = current["pressure"]; // 1010 + timestamp[0].humidity = current["humidity"]; // 51 + timestamp[0].dewPoint = current["dew_point"]; // 20.88 + timestamp[0].uvIndex = current["uvi"]; // 1.54 + timestamp[0].cloudCover = current["clouds"]; // 1 + timestamp[0].visibility = current["visibility"]; // 16093 + timestamp[0].windSpeed = current["wind_speed"]; // 16.11 + timestamp[0].windBearing = current["wind_deg"]; // 300 + timestamp[0].windGust = current["wind_gust"]; // 24.16 - if(httpCode > 399) + // New rain and snow ======================= + if(current["rain"]) { - if(httpCode == 404) + if(USER_PARAM.OPEN_WEATHER_UNITS == 2) { - http.end(); - error_code += 8; - return error_code; + float temp = current["rain"]["1h"]; + timestamp[0].rainVolume = (temp/25.4); // 95 } else - { - http.end(); - error_code += 9; - return error_code; - } + timestamp[0].rainVolume = current["rain"]["1h"]; // 95 } - - int streamSize = http.getSize(); - char stringVarout[streamSize+1]; - - String stringVarin = http.getString(); - strcpy(stringVarout,stringVarin.c_str()); - - http.end(); - - strncpy(_ipapiURL,"https://ipapi.co/",38); - strcat(_ipapiURL,stringVarout); - strcat(_ipapiURL,"/json/"); - - return 0; -} + else + timestamp[0].rainVolume = 0; -int OpenWeatherOneCall::getIPAPILocation(char* URL) -{ - int error_code = 0; - HTTPClient http; - http.begin(URL); - int ipapi_httpCode = http.GET(); - if(ipapi_httpCode > 399) + if(current["snow"]) { - if(ipapi_httpCode == 404) + if(USER_PARAM.OPEN_WEATHER_UNITS == 2) { - error_code += 10; + float temp = current["snow"]["1h"]; + timestamp[0].snowVolume = (temp/25.4); // 95 } else - { - error_code += 11; - } - http.end(); - return error_code; + timestamp[0].snowVolume = current["snow"]["1h"]; // 95 + } + else + timestamp[0].snowVolume = 0; - const size_t capacity = JSON_OBJECT_SIZE(26) + 490; - DynamicJsonDocument doc(capacity); - deserializeJson(doc, http.getString()); + JsonObject current_weather_0 = current["weather"][0]; + timestamp[0].id = current_weather_0["id"]; // 800 - strncpy(location.CITY,doc["city"],60); // "Lakewood" - strncpy(location.STATE,doc["region_code"],2); // "NJ" - strncpy(location.COUNTRY,doc["country_code"],3); // "US" - USER_PARAM.OPEN_WEATHER_LATITUDE = doc["latitude"]; // 40.0881 - location.LATITUDE = doc["latitude"]; - USER_PARAM.OPEN_WEATHER_LONGITUDE = doc["longitude"]; // -74.1963 - location.LONGITUDE = doc["longitude"]; + timestamp[0].main = (char *)realloc(timestamp[0].main,sizeof(char) * strlen(current_weather_0["main"])+1); + strncpy(timestamp[0].main,current_weather_0["main"],strlen(current_weather_0["main"])+1); - http.end(); + timestamp[0].summary = (char *)realloc(timestamp[0].summary,sizeof(char) * strlen(current_weather_0["description"])+1); + strncpy(timestamp[0].summary,current_weather_0["description"],strlen(current_weather_0["description"])+1); - return 0; -} + strncpy(timestamp[0].icon,current_weather_0["icon"],strlen(current_weather_0["icon"])+1); + dateTimeConversion(timestamp[0].dayTime,timestamp[0].weekDayName,9); -void OpenWeatherOneCall::initAPI(void) -{ - memset(USER_PARAM.OPEN_WEATHER_DKEY,NULL,1); - USER_PARAM.OPEN_WEATHER_LATITUDE = 0.0; - USER_PARAM.OPEN_WEATHER_LONGITUDE = 0.0; - USER_PARAM.OPEN_WEATHER_UNITS = 2; - USER_PARAM.OPEN_WEATHER_EXCLUDES = 0; - USER_PARAM.OPEN_WEATHER_HISTORY = 0; + http.end(); + return 0; } - -int OpenWeatherOneCall::getLocationInfo() +int OpenWeatherOneCall::createCurrent(int sizeCap) { - int error_code = 0; - char locationURL[200]; + char getURL[200] = {0}; + int alertz = 0; - sprintf(locationURL,"https://api.bigdatacloud.net/data/reverse-geocode-client/?latitude=%f&longitude=%f",USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE); + sprintf(getURL,"%s?lat=%.6f&lon=%.6f&lang=%s%s&units=%s%s%s",DS_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE,USER_PARAM.OPEN_WEATHER_LANGUAGE,DS_URL2,units,DS_URL3,USER_PARAM.OPEN_WEATHER_DKEY); + // printf("\n%s\n",getURL); HTTPClient http; - http.begin(locationURL); //<------------ Connect to OpenWeatherMap - - int httpCode = http.GET(); - if (httpCode > 399) //<- Check for connect errors - { - if(httpCode == 401) - { - error_code = 17; - } - else if(httpCode == 404) - { - error_code = 18; - } - else - error_code = 19; - - http.end(); - return error_code; - } - - - //const size_t capacity = JSON_ARRAY_SIZE(4) + JSON_ARRAY_SIZE(5) + JSON_OBJECT_SIZE(2) + 2*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 3*JSON_OBJECT_SIZE(6) + 2*JSON_OBJECT_SIZE(8) + JSON_OBJECT_SIZE(14) + 1050; - DynamicJsonDocument doc(3072); - - deserializeJson(doc, http.getString()); - - if(doc["locality"]) - { - strncpy(location.CITY,doc["locality"],60); - } - else - { - http.end(); - return 20; - } - - if(doc["principalSubdivisionCode"]) - { - strncpy(location.STATE,doc["principalSubdivisionCode"],10); - } - else - { - http.end(); - return 20; - } - - if(doc["countryCode"]) - { - strncpy(location.COUNTRY,doc["countryCode"],10); - } - else - { - http.end(); - return 20; - } - - - http.end(); - - return 0; -} - - -int OpenWeatherOneCall::createHistory() -{ - int error_code = 0; - char historyURL[200] = {0}; - - if(USER_PARAM.OPEN_WEATHER_HISTORY > 5) - { - return 16; - } - - //Gets Timestamp for EPOCH calculation below - char tempURL[200]; - sprintf(tempURL,"https://api.openweathermap.org/data/3.0/onecall?lat=%.6f&lon=%.6f&exclude=minutely,hourly,daily,alerts&units=IMPERIAL&appid=%s",USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE,USER_PARAM.OPEN_WEATHER_DKEY); - - HTTPClient http; - http.begin(tempURL); + http.begin(getURL); int httpCode = http.GET(); if (httpCode > 399) @@ -436,451 +309,167 @@ int OpenWeatherOneCall::createHistory() http.end(); return 22; } - else - { - http.end(); - return 21; - } - } - - DynamicJsonDocument toc(768); - deserializeJson(toc, http.getString()); - - JsonObject toc_current = toc["current"]; - long tempEPOCH = toc_current["dt"]; // 1608323864 - - tempEPOCH = tempEPOCH - (86400 * USER_PARAM.OPEN_WEATHER_HISTORY); - - http.end(); - - //Timemachine request to OWM - sprintf(historyURL,"%s?lat=%.6f&lon=%.6f%s%ld&units=%s%s%s",TS_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE,TS_URL2,tempEPOCH,units,DS_URL3,USER_PARAM.OPEN_WEATHER_DKEY); - - http.begin(historyURL); - httpCode = http.GET(); + http.end(); + return 21; - if (httpCode > 399) - { - if(httpCode == 401) - { - http.end(); - return 22; - } - else - { - http.end(); - return 21; - } } - - DynamicJsonDocument doc(9000); - + const size_t capacity = sizeCap; + JsonDocument doc; deserializeJson(doc, http.getString()); + strncpy(location.timezone,doc["timezone"],50); location.timezoneOffset = doc["timezone_offset"]; - if(!history) - { - history = (struct HISTORICAL *)calloc(25,sizeof(struct HISTORICAL)); - } - - //Current in historical is the time of the request on that day - JsonObject current = doc["current"]; - history[0].dayTime = current["dt"]; // 1607292481 - - if(current["dt"]) - { - long tempTime = current["dt"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,history[0].readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT); - } - - history[0].sunrise = current["sunrise"]; // 1607256309 - - if(current["sunrise"]) - { - long tempTime = current["sunrise"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,history[0].readableSunrise,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); - } - - history[0].sunset = current["sunset"]; // 1607290280 - - if(current["sunset"]) + if(exclude.current) { - long tempTime = current["sunset"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,history[0].readableSunset,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); + OpenWeatherOneCall::freeCurrentMem(); } - - history[0].temperature = current["temp"]; // 35.82 - history[0].apparentTemperature = current["feels_like"]; // 21.7 - history[0].pressure = current["pressure"]; // 1010 - history[0].humidity = current["humidity"]; // 51 - history[0].dewPoint = current["dew_point"]; // 20.88 - history[0].uvIndex = current["uvi"]; // 1.54 - history[0].cloudCover = current["clouds"]; // 1 - history[0].visibility = current["visibility"]; // 16093 - history[0].windSpeed = current["wind_speed"]; // 16.11 - history[0].windBearing = current["wind_deg"]; // 300 - history[0].windGust = current["wind_gust"]; // 24.16 - - // New rain and snow ======================= - if(current["rain"]) + else { - if(USER_PARAM.OPEN_WEATHER_UNITS == 2) + if(!current) { - float temp = current["rain"]["1h"]; - history[0].rainVolume = (temp/25.4); // 95 + current = (struct nowData *)calloc(1,sizeof(struct nowData)); + if(current == NULL) + { + return 23; + } } - else - history[0].rainVolume = current["rain"]["1h"]; // 95 - - }else history[0].rainVolume = 0; - + JsonObject currently = doc["current"]; + current->dayTime = currently["dt"]; // 1586781931 - if(current["snow"]) - { - if(USER_PARAM.OPEN_WEATHER_UNITS == 2) + if(currently["dt"]) { - float temp = current["snow"]["1h"]; - history[0].snowVolume = (temp/25.4); // 95 - } - else - history[0].snowVolume = current["snow"]["1h"]; // 95 - - }else history[0].snowVolume = 0; - - - - JsonObject current_weather_0 = current["weather"][0]; - history[0].id = current_weather_0["id"]; // 800 - - history[0].main = (char *)realloc(history[0].main,sizeof(char) * strlen(current_weather_0["main"])+1); - strncpy(history[0].main,current_weather_0["main"],strlen(current_weather_0["main"])+1); - - history[0].summary = (char *)realloc(history[0].summary,sizeof(char) * strlen(current_weather_0["description"])+1); - strncpy(history[0].summary,current_weather_0["description"],strlen(current_weather_0["description"])+1); - - strncpy(history[0].icon,current_weather_0["icon"],strlen(current_weather_0["icon"])+1); - - dateTimeConversion(history[0].dayTime,history[0].weekDayName,9); + // Create human readable date and time + long tempTime = currently["dt"]; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,current->readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT); + dateTimeConversion(tempTime,current->readableWeekdayName,9); + } - //Hourly report for the day requested begins at 00:00GMT - JsonArray hourly = doc["hourly"]; - for(int x = 1; x < 24; x++) - { - JsonObject hourly_0 = hourly[x-1]; - history[x].dayTime = hourly_0["dt"]; // 1607212800 + current->sunriseTime = currently["sunrise"]; // 1612267442 - if(hourly_0["dt"]) + if(currently["sunrise"]) { - long tempTime = hourly_0["dt"]; + // Create human readable date and time + + long tempTime = currently["sunrise"]; tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,history[x].readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT); + dateTimeConversion(tempTime,current->readableSunrise,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); } - history[x].sunrise = current["sunrise"] | 1607256309; // 1607256309 + current->sunsetTime = currently["sunset"]; // 1612304218 - if(current["sunrise"]) + if(currently["sunset"]) { - long tempTime = current["sunrise"]; + // Create human readable date and time + + long tempTime = currently["sunset"]; tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,history[x].readableSunrise,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); + dateTimeConversion(tempTime,current->readableSunset,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); } - history[x].sunset = current["sunset"] | 1607290280; // 1607290280 - if(current["sunset"]) + + current->temperature = currently["temp"]; // 287.59 + current->apparentTemperature = currently["feels_like"]; // 281.42 + current->pressure = currently["pressure"]; // 1011 + current->humidity = currently["humidity"]; // 93 + current->dewPoint = currently["dew_point"]; // 286.47 + current->uvIndex = currently["uvi"]; // 6.31 + current->cloudCover = currently["clouds"]; // 90 + current->visibility = currently["visibility"]; // 8047 + current->windSpeed = currently["wind_speed"]; // 10.3 + current->windBearing = currently["wind_deg"]; // 170 + + if(currently["wind_gust"]) { - long tempTime = current["sunset"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,history[x].readableSunset,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); + current->windGust = currently["wind_gust"]; } - history[x].temperature = hourly_0["temp"]; // 40.21 - history[x].apparentTemperature = hourly_0["feels_like"]; // 29.39 - history[x].pressure = hourly_0["pressure"]; // 1007 - history[x].humidity = hourly_0["humidity"]; // 56 - history[x].dewPoint = hourly_0["dew_point"]; // 26.51 - history[x].cloudCover = hourly_0["clouds"]; // 1 - history[x].visibility = hourly_0["visibility"]; // 16093 - history[x].windSpeed = hourly_0["wind_speed"]; // 11.41 - history[x].windBearing = hourly_0["wind_deg"]; // 290 - - // New rain and snow ======================= - if(hourly_0["rain"]) + if(currently["snow"]["1h"]) { if(USER_PARAM.OPEN_WEATHER_UNITS == 2) { - float temp = hourly_0["rain"]["1h"]; - history[x].rainVolume = (temp/25.4); // 95 + float temp = currently["snow"]["1h"]; + current->snowVolume = (temp/25.4); // 95 } else - history[x].rainVolume = hourly_0["rain"]["1h"]; // 95 - - }else history[x].rainVolume = 0; + current->snowVolume = currently["snow"]["1h"]; // 95 + } + else + current->snowVolume = 0; - if(hourly_0["snow"]) + if(currently["rain"]["1h"]) { if(USER_PARAM.OPEN_WEATHER_UNITS == 2) { - float temp = hourly_0["snow"]["1h"]; - history[x].snowVolume = (temp/25.4); // 95 + float temp = currently["rain"]["1h"]; + current->rainVolume = (temp/25.4); // 95 } else - history[x].snowVolume = hourly_0["snow"]["1h"]; // 95 - - }else history[x].snowVolume = 0; - - JsonObject hourly_0_weather_0 = hourly_0["weather"][0]; - history[x].id = hourly_0_weather_0["id"]; // 800 + current->rainVolume = currently["rain"]["1h"]; // 95 - history[x].main = (char *)realloc(history[x].main,sizeof(char) * strlen(hourly_0_weather_0["main"])+1); - strncpy(history[x].main,hourly_0_weather_0["main"],strlen(hourly_0_weather_0["main"])+1); + } + else + current->rainVolume = 0; - history[x].summary = (char *)realloc(history[x].summary,sizeof(char) * strlen(hourly_0_weather_0["description"])+1); - strncpy(history[x].summary,hourly_0_weather_0["description"],strlen(hourly_0_weather_0["description"])+1); - strncpy(history[x].icon,hourly_0_weather_0["icon"],strlen(hourly_0_weather_0["icon"])+1); - dateTimeConversion(history[0].dayTime,history[x].weekDayName,9); - } - http.end(); - return 0; -} + current->id = currently["weather"][0]["id"]; -int OpenWeatherOneCall::createAQ(int sizeCap) -{ - char getURL[200] = {0}; + current->main = (char *)realloc(current->main,sizeof(char) * strlen(currently["weather"][0]["main"])+1); + if(current->main == NULL) + { + return 23; + } + strncpy(current->main,currently["weather"][0]["main"],strlen(currently["weather"][0]["main"])+1); - sprintf(getURL,"%s%.6f%s%.6f%s%s",AQ_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,AQ_URL2,USER_PARAM.OPEN_WEATHER_LONGITUDE,AQ_URL3,USER_PARAM.OPEN_WEATHER_DKEY); - // printf("%s\n",getURL); + current->summary = (char *)realloc(current->summary,sizeof(char) * strlen(currently["weather"][0]["description"])+1); + if(current->summary == NULL) + { + return 23; + } + strncpy(current->summary,currently["weather"][0]["description"],strlen(currently["weather"][0]["description"])+1); - HTTPClient http; - http.begin(getURL); - int httpCode = http.GET(); + strncpy(current->icon,currently["weather"][0]["icon"],strlen(currently["weather"][0]["icon"])+1); + } - if (httpCode > 399) + if(exclude.daily) { - if(httpCode == 401) + OpenWeatherOneCall::freeForecastMem(); + } + else + { + if(!forecast) { - http.end(); - return 22; + forecast = (struct futureData *)calloc(8,sizeof(struct futureData)); + if(forecast == NULL) + { + return 23; + } } - http.end(); - return 21; - - } + JsonArray daily = doc["daily"]; + for (int x = 0; x < 8; x++) + { + forecast[x].dayTime = daily[x]["dt"]; // 1586793600 - const size_t capacity = sizeCap; - DynamicJsonDocument doc(capacity); - deserializeJson(doc, http.getString()); - doc.shrinkToFit(); + if(daily[x]["dt"]) + { + long tempTime = daily[x]["dt"]; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,forecast[x].readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT+9); + } - quality = (struct airQuality *)calloc(1,sizeof(struct airQuality)); - - float coord_lon = doc["coord"]["lon"]; // -74.1975 - float coord_lat = doc["coord"]["lat"]; // 39.9533 - - JsonObject list_0 = doc["list"][0]; - - quality -> aqi = list_0["main"]["aqi"]; // 2 - - JsonObject list_0_components = list_0["components"]; - quality -> co = list_0_components["co"]; // 230.31 - quality -> no = list_0_components["no"]; // 0.43 - quality -> no2 = list_0_components["no2"]; // 1.69 - quality -> o3 = list_0_components["o3"]; // 100.14 - quality -> so2 = list_0_components["so2"]; // 0.88 - quality -> pm2_5 = list_0_components["pm2_5"]; // 0.76 - quality -> pm10 = list_0_components["pm10"]; // 1.04 - quality -> nh3 = list_0_components["nh3"]; // 0.43 - - quality -> dayTime = list_0["dt"]; // 1615838400 - - http.end(); - return 0; - - -} - -int OpenWeatherOneCall::createCurrent(int sizeCap) -{ - - char getURL[200] = {0}; - int alertz = 0; - - sprintf(getURL,"%s?lat=%.6f&lon=%.6f&lang=%s%s&units=%s%s%s",DS_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE,USER_PARAM.OPEN_WEATHER_LANGUAGE,DS_URL2,units,DS_URL3,USER_PARAM.OPEN_WEATHER_DKEY); - // printf("\n%s\n",getURL); - - HTTPClient http; - http.begin(getURL); - int httpCode = http.GET(); - - if (httpCode > 399) - { - if(httpCode == 401) - { - http.end(); - return 22; - } - - http.end(); - return 21; - - } - - const size_t capacity = sizeCap; - DynamicJsonDocument doc(capacity); - deserializeJson(doc, http.getString()); - doc.shrinkToFit(); - - strncpy(location.timezone,doc["timezone"],50); - location.timezoneOffset = doc["timezone_offset"]; - - if(exclude.current) - { - OpenWeatherOneCall::freeCurrentMem(); - } - else - { - if(!current) - { - current = (struct nowData *)calloc(1,sizeof(struct nowData)); - if(current == NULL) - { - return 23; - } - } - - JsonObject currently = doc["current"]; - current->dayTime = currently["dt"]; // 1586781931 - - if(currently["dt"]) - { - long tempTime = currently["dt"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,current->readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT); - dateTimeConversion(tempTime,current->readableWeekdayName,9); - } - - - current->sunriseTime = currently["sunrise"]; // 1612267442 - - if(currently["sunrise"]) - { - long tempTime = currently["sunrise"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,current->readableSunrise,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); - } - - current->sunsetTime = currently["sunset"]; // 1612304218 - - if(currently["sunset"]) - { - long tempTime = currently["sunset"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,current->readableSunset,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); - } - - - - current->temperature = currently["temp"]; // 287.59 - current->apparentTemperature = currently["feels_like"]; // 281.42 - current->pressure = currently["pressure"]; // 1011 - current->humidity = currently["humidity"]; // 93 - current->dewPoint = currently["dew_point"]; // 286.47 - current->uvIndex = currently["uvi"]; // 6.31 - current->cloudCover = currently["clouds"]; // 90 - current->visibility = currently["visibility"]; // 8047 - current->windSpeed = currently["wind_speed"]; // 10.3 - current->windBearing = currently["wind_deg"]; // 170 - - if(currently["wind_gust"]) - { - current->windGust = currently["wind_gust"]; - } - - if(currently["snow"]["1h"]) - { - if(USER_PARAM.OPEN_WEATHER_UNITS == 2) - { - float temp = currently["snow"]["1h"]; - current->snowVolume = (temp/25.4); // 95 - } else current->snowVolume = currently["snow"]["1h"]; // 95 - - } else current->snowVolume = 0; - - if(currently["rain"]["1h"]) - { - if(USER_PARAM.OPEN_WEATHER_UNITS == 2) - { - float temp = currently["rain"]["1h"]; - current->rainVolume = (temp/25.4); // 95 - } else current->rainVolume = currently["rain"]["1h"]; // 95 - - } else current->rainVolume = 0; - - - - - current->id = currently["weather"][0]["id"]; - - current->main = (char *)realloc(current->main,sizeof(char) * strlen(currently["weather"][0]["main"])+1); - if(current->main == NULL) - { - return 23; - } - strncpy(current->main,currently["weather"][0]["main"],strlen(currently["weather"][0]["main"])+1); - - current->summary = (char *)realloc(current->summary,sizeof(char) * strlen(currently["weather"][0]["description"])+1); - if(current->summary == NULL) - { - return 23; - } - strncpy(current->summary,currently["weather"][0]["description"],strlen(currently["weather"][0]["description"])+1); - - strncpy(current->icon,currently["weather"][0]["icon"],strlen(currently["weather"][0]["icon"])+1); - } - - if(exclude.daily) - { - OpenWeatherOneCall::freeForecastMem(); - } - else - { - if(!forecast) - { - forecast = (struct futureData *)calloc(8,sizeof(struct futureData)); - if(forecast == NULL) - { - return 23; - } - } - - JsonArray daily = doc["daily"]; - for (int x = 0; x < 8; x++) - { - forecast[x].dayTime = daily[x]["dt"]; // 1586793600 - - if(daily[x]["dt"]) - { - long tempTime = daily[x]["dt"]; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,forecast[x].readableDateTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT); - } - - forecast[x].sunriseTime = daily[x]["sunrise"]; // 1586773262 + forecast[x].sunriseTime = daily[x]["sunrise"]; // 1586773262 if(daily[x]["sunrise"]) { @@ -956,9 +545,13 @@ int OpenWeatherOneCall::createCurrent(int sizeCap) { float temp = daily[x]["rain"]; forecast[x].rainVolume = (temp/25.4); // 95 - } else forecast[x].rainVolume = daily[x]["rain"]; // 95 + } + else + forecast[x].rainVolume = daily[x]["rain"]; // 95 - } else forecast[x].rainVolume = 0; // 95 + } + else + forecast[x].rainVolume = 0; // 95 if(daily[x]["snow"]) @@ -967,9 +560,13 @@ int OpenWeatherOneCall::createCurrent(int sizeCap) { float temp = daily[x]["snow"]; forecast[x].snowVolume = (temp/25.4); // 95 - } else forecast[x].snowVolume = daily[x]["snow"]; // 95 + } + else + forecast[x].snowVolume = daily[x]["snow"]; // 95 - } else forecast[x].snowVolume = 0; + } + else + forecast[x].snowVolume = 0; forecast[x].uvIndex = daily[x]["uvi"]; // 6.31 @@ -995,7 +592,6 @@ int OpenWeatherOneCall::createCurrent(int sizeCap) } } - printf("Number of ALERTS: %d\n\n",MAX_NUM_ALERTS); if(!alert); { @@ -1031,163 +627,654 @@ int OpenWeatherOneCall::createCurrent(int sizeCap) } strncpy(alert[x].event,ALERTS_0["event"],strlen(ALERTS_0["event"])+1); - } + } + + if(ALERTS_0["start"]) + { + long tempTime = ALERTS_0["start"]; + alert[x].alertStart = tempTime; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,alert[x].startInfo,USER_PARAM.OPEN_WEATHER_DATEFORMAT); + } + + if(ALERTS_0["end"]) + { + long tempTime = ALERTS_0["end"]; + alert[x].alertEnd = tempTime; + tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,alert[x].endInfo,USER_PARAM.OPEN_WEATHER_DATEFORMAT); + } + + if(ALERTS_0["description"]) + { + alert[x].summary = (char *)realloc(alert[x].summary,sizeof(char) * strlen(ALERTS_0["description"])+1); + if(alert[x].summary == NULL) + { + return 23; + } + strncpy(alert[x].summary,ALERTS_0["description"],strlen(ALERTS_0["description"])+1); + } + } //end for + + } + else + { + // If alerts are not excluded but there are none, reset NUM_MAX_ALERTS and MEMORY + MAX_NUM_ALERTS = 0; + OpenWeatherOneCall::freeAlertMem(); + } + } + + + if(exclude.hourly) + { + OpenWeatherOneCall::freeHourMem(); + } + else + { + if(doc["hourly"]) + { + if(!hour) + { + hour = (struct HOURLY *)calloc(48, sizeof(struct HOURLY)); + if(hour == NULL) + { + return 23; + } + } + + JsonArray hourly = doc["hourly"]; + for(int h = 0; h < 48; h++) + { + + JsonObject hourly_0 = hourly[h]; + //hour[h].dayTime = hourly_0["dt"]; // 1604336400 + + if(hourly_0["dt"]) + { + long tempTime = hourly_0["dt"]; + hour[h].dayTime = tempTime; + //tempTime += location.timezoneOffset; + dateTimeConversion(tempTime,hour[h].readableTime,6); + } + + hour[h].temperature = hourly_0["temp"]; // 46.58 + hour[h].apparentTemperature = hourly_0["feels_like"]; // 28.54 + hour[h].pressure = hourly_0["pressure"]; // 1015 + hour[h].humidity = hourly_0["humidity"]; // 31 + hour[h].dewPoint = hourly_0["dew_point"]; // 19.2 + hour[h].cloudCover = hourly_0["clouds"]; // 20 + hour[h].visibility = hourly_0["visibility"]; // 10000 + hour[h].windSpeed = hourly_0["wind_speed"]; // 22.77 + hour[h].windBearing = hourly_0["wind_deg"]; // 300 + + + if(hourly_0["snow"]) + { + if(USER_PARAM.OPEN_WEATHER_UNITS == 2) + { + float temp = hourly_0["snow"]; + hour[h].snowVolume = (temp/25.4); // 95 + } + else + hour[h].snowVolume = hourly_0["snow"]; // 95 + + } + else + hour[h].snowVolume = 0; + + + if(hourly_0["rain"]) + { + if(USER_PARAM.OPEN_WEATHER_UNITS == 2) + { + float temp = hourly_0["rain"]; + hour[h].rainVolume = (temp/25.4); // 95 + } + else + hour[h].rainVolume = hourly_0["rain"]; // 95 + + } + else + hour[h].rainVolume = 0; + + + JsonObject hourly_0_weather_0 = hourly_0["weather"][0]; + hour[h].id = hourly_0_weather_0["id"]; // 801 + + if(hourly_0_weather_0["main"]) + { + hour[h].main = (char *)realloc(hour[h].main,sizeof(char) * strlen(hourly_0_weather_0["main"])+1); + if(hour[h].main == NULL) + { + return 23; + } + strncpy(hour[h].main,hourly_0_weather_0["main"],strlen(hourly_0_weather_0["main"])+1); + } + + if(hourly_0_weather_0["description"]) + { + hour[h].summary = (char *)realloc(hour[h].summary,sizeof(char) * strlen(hourly_0_weather_0["description"])+1); + if(hour[h].summary == NULL) + { + return 23; + } + strncpy(hour[h].summary,hourly_0_weather_0["description"],strlen(hourly_0_weather_0["description"])+1); + } + + + strncpy(hour[h].icon,hourly_0_weather_0["icon"],strlen(hourly_0_weather_0["icon"])+1); + + hour[h].pop = hourly_0["pop"]; // 0 + } + } + } + + + if(exclude.minutely) + { + OpenWeatherOneCall::freeMinuteMem(); + } + else + { + if(doc["minutely"]) + { + if(!minute) + { + minute = (struct MINUTELY *)calloc(61, sizeof(struct MINUTELY)); + if(minute == NULL) + { + return 23; + } + } + + + JsonArray minutely = doc["minutely"]; + + for(int x = 0; x<61; x++) + { + minute[x].dayTime = minutely[x]["dt"]; + long tempTime = minutely[x]["dt"]; + //tempTime += -3600; + dateTimeConversion(tempTime,minute[x].readableTime,USER_PARAM.OPEN_WEATHER_DATEFORMAT+4); + + minute[x].precipitation = minutely[x]["precipitation"]; // 0 + } + } + } + + http.end(); + return 0; +} + +int OpenWeatherOneCall::createOverview() +{ + + // NTP Server + const char* ntpServer = "pool.ntp.org"; + const long gmtOffset_sec = myTimeZone * 3600; + const int daylightOffset_sec = 3600; + char timeString[11]; + int maxRetries = 10; + + // Initialize NTP + configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); + + // Get current time + struct tm timeinfo; + + int retries = 0; + while (!getLocalTime(&timeinfo) && retries < maxRetries) + { + Serial.println("Failed to obtain time, retrying..."); + delay(2000); // Wait 2 seconds before retrying + retries++; + } + if (retries == maxRetries) + { + Serial.println("Failed to obtain time after maximum retries"); + return false; + } + Serial.println("Time obtained successfully"); + + if(strcmp(overviewDate,"TODAY")==0) + { + strftime(timeString, sizeof(timeString), "%Y-%m-%d", &timeinfo); + } + else if(strcmp(overviewDate,"TOMORROW")==0) + { + // Add 1 to the day + timeinfo.tm_mday += 1; + + // Normalize the time structure (this handles overflow of days, months, etc.) + mktime(&timeinfo); + strftime(timeString, sizeof(timeString), "%Y-%m-%d", &timeinfo); + } + + char overviewURL[200] = {0}; + + sprintf(overviewURL,"%s?lat=%.6f&lon=%.6f%s%s&units=%s%s%s",OV_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE,OV_URL2,timeString,OV_units,DS_URL3,USER_PARAM.OPEN_WEATHER_DKEY); + + //printf("\n%s\n",overviewURL); + + HTTPClient http; + http.begin(overviewURL); + int httpCode = http.GET(); + + if (httpCode > 399) + { + if(httpCode == 401) + { + http.end(); + return 22; + } + else + { + http.end(); + return 21; + } + } + + if(!overView) + { + overView = (struct OVERVIEW *)calloc(1,sizeof(struct OVERVIEW)); + if(overView == NULL) + { + return 23; + } + } + // String input; + + JsonDocument doc; + + DeserializationError error = deserializeJson(doc, http.getString()); + + if (error) + { + Serial.print("deserializeJson() failed: "); + Serial.println(error.c_str()); + return 22; + } + + + double lat = doc["lat"]; // 39.953701 + double lon = doc["lon"]; // -74.197899 + const char* tz = doc["tz"]; // "-04:00" + const char* date = doc["date"]; // "2024-08-06" + const char* units = doc["units"]; // "imperial" + const char* weather_overview = doc["weather_overview"]; // "The current weather in our area is overcast .. + + + overView->lat = doc["lat"]; // 39.953701 + overView->lon = doc["lon"]; // -74.197899 + overView->tz = strdup(doc["tz"]); // "-04:00" + overView->date = strdup(doc["date"]); // "2024-08-06" + overView->units = strdup(doc["units"]); // "imperial" + overView->weather_overview = strdup(doc["weather_overview"]); + + + http.end(); + return 0; +} + +void OpenWeatherOneCall::allocateAndCopy(char** destination, const char* source) +{ + *destination = (char*)malloc(strlen(source) + 1); // Allocate memory + if (*destination != NULL) + { + strcpy(*destination, source); // Copy the string + } +} + +int OpenWeatherOneCall::setLatLon(float _LAT, float _LON) +{ + int error_code = 0; + + if(abs(_LAT) <= 90) + { + USER_PARAM.OPEN_WEATHER_LATITUDE = _LAT; + location.LATITUDE = _LAT; //User copy + } + else + error_code += 1; + + if(abs(_LON) <= 180) + { + USER_PARAM.OPEN_WEATHER_LONGITUDE = _LON; + location.LONGITUDE = _LON; //User copy + } + else + error_code += 2; + + if(error_code) + return error_code; + + return (EXIT_SUCCESS); +} + +int OpenWeatherOneCall::setLatLon(int _CITY_ID) +{ + int error_code = 0; + + char cityURL[110]; + char* URL1 = "http://api.openweathermap.org/data/3.0/weather?id="; + char* URL2 = "&appid="; + + sprintf(cityURL,"%s%d%s%s",URL1,_CITY_ID,URL2,USER_PARAM.OPEN_WEATHER_DKEY); + error_code = OpenWeatherOneCall::parseCityCoordinates(cityURL); + if(error_code) + return error_code; + + return (EXIT_SUCCESS); + +} + +int OpenWeatherOneCall::setLatLon(void) +{ + // IP address of NON-CELLULAR WiFi. Hotspots won't work properly. + + int error_code = 0; + error_code = OpenWeatherOneCall::getIPLocation(); + if(error_code) + { + return error_code; + } + + error_code = OpenWeatherOneCall::getIPAPILocation(_ipapiURL); + if(error_code) + { + return error_code; + } + + return 0; +} + + +int OpenWeatherOneCall::parseCityCoordinates(char* CTY_URL) +{ + int error_code = 0; + + HTTPClient http; + http.begin(CTY_URL); + int httpCode = http.GET(); + if(httpCode > 399) + { + if(httpCode == 404) + { + http.end(); + error_code += 4; + return error_code; + } + else + { + http.end(); + error_code += 5; + return error_code; + } + + } + + const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(13) + 270; + JsonDocument doc; + + deserializeJson(doc, http.getString()); + + if(doc["coord"]["lon"]) + { + USER_PARAM.OPEN_WEATHER_LONGITUDE = doc["coord"]["lon"]; // -74.2 + } + else + { + error_code += 6; + } + + if(doc["coord"]["lat"]) + { + USER_PARAM.OPEN_WEATHER_LATITUDE = doc["coord"]["lat"]; // 39.95 + } + else + { + error_code += 7; + } + + http.end(); + + if(error_code) + { + http.end(); + return error_code; + } + + return (EXIT_SUCCESS); +} + +int OpenWeatherOneCall::getIPLocation() +{ + int error_code = 0; + + HTTPClient http; + http.begin("https://api64.ipify.org/ HTTP/1.1\r\nHost: api.ipify.org\r\n\r\n"); + + int httpCode = http.GET(); + + if(httpCode > 399) + { + if(httpCode == 404) + { + http.end(); + error_code += 8; + return error_code; + } + else + { + http.end(); + error_code += 9; + return error_code; + } + + } + + int streamSize = http.getSize(); + char stringVarout[streamSize+1]; + + String stringVarin = http.getString(); + strcpy(stringVarout,stringVarin.c_str()); + + http.end(); + + strncpy(_ipapiURL,"https://ipapi.co/",38); + strcat(_ipapiURL,stringVarout); + strcat(_ipapiURL,"/json/"); + + return 0; +} + + +int OpenWeatherOneCall::getIPAPILocation(char* URL) +{ + int error_code = 0; + HTTPClient http; + http.begin(URL); + int ipapi_httpCode = http.GET(); + + if(ipapi_httpCode > 399) + { + if(ipapi_httpCode == 404) + { + error_code += 10; + } + else + { + error_code += 11; + } + http.end(); + return error_code; + } + + + const size_t capacity = JSON_OBJECT_SIZE(26) + 490; + JsonDocument doc; + + deserializeJson(doc, http.getString()); + + strncpy(location.CITY,doc["city"],60); // "Lakewood" + strncpy(location.STATE,doc["region_code"],2); // "NJ" + strncpy(location.COUNTRY,doc["country_code"],3); // "US" + USER_PARAM.OPEN_WEATHER_LATITUDE = doc["latitude"]; // 40.0881 + location.LATITUDE = doc["latitude"]; + USER_PARAM.OPEN_WEATHER_LONGITUDE = doc["longitude"]; // -74.1963 + location.LONGITUDE = doc["longitude"]; + + http.end(); + + return 0; +} + + +void OpenWeatherOneCall::initAPI(void) +{ + memset(USER_PARAM.OPEN_WEATHER_DKEY,NULL,1); + USER_PARAM.OPEN_WEATHER_LATITUDE = 0.0; + USER_PARAM.OPEN_WEATHER_LONGITUDE = 0.0; + USER_PARAM.OPEN_WEATHER_UNITS = 2; + USER_PARAM.OPEN_WEATHER_EXCLUDES = 0; + USER_PARAM.OPEN_WEATHER_TIMESTAMP = 0; + strcpy(USER_PARAM.OPEN_WEATHER_TIMESTAMP_DATE,"01/01/1979"); + USER_PARAM.OPEN_WEATHER_OVERVIEW = 0; + strcpy(USER_PARAM.OPEN_WEATHER_OVERVIEW_DATE,"0000-00-00"); + + +} + + +int OpenWeatherOneCall::getLocationInfo() +{ + int error_code = 0; - if(ALERTS_0["start"]) - { - long tempTime = ALERTS_0["start"]; - alert[x].alertStart = tempTime; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,alert[x].startInfo,USER_PARAM.OPEN_WEATHER_DATEFORMAT); - } + char locationURL[200]; - if(ALERTS_0["end"]) - { - long tempTime = ALERTS_0["end"]; - alert[x].alertEnd = tempTime; - tempTime += location.timezoneOffset; - dateTimeConversion(tempTime,alert[x].endInfo,USER_PARAM.OPEN_WEATHER_DATEFORMAT); - } + sprintf(locationURL,"https://api.bigdatacloud.net/data/reverse-geocode-client/?latitude=%f&longitude=%f",USER_PARAM.OPEN_WEATHER_LATITUDE,USER_PARAM.OPEN_WEATHER_LONGITUDE); - if(ALERTS_0["description"]) - { - alert[x].summary = (char *)realloc(alert[x].summary,sizeof(char) * strlen(ALERTS_0["description"])+1); - if(alert[x].summary == NULL) - { - return 23; - } - strncpy(alert[x].summary,ALERTS_0["description"],strlen(ALERTS_0["description"])+1); - } - } //end for + HTTPClient http; + http.begin(locationURL); - }else{ - // If alerts are not excluded but there are none, reset NUM_MAX_ALERTS and MEMORY - MAX_NUM_ALERTS = 0; - OpenWeatherOneCall::freeAlertMem(); + int httpCode = http.GET(); + if (httpCode > 399) + { + if(httpCode == 401) + { + error_code = 17; + } + else if(httpCode == 404) + { + error_code = 18; } + else + error_code = 19; + + http.end(); + return error_code; } - if(exclude.hourly) + JsonDocument doc; + + deserializeJson(doc, http.getString()); + + if(doc["locality"]) { - OpenWeatherOneCall::freeHourMem(); + strncpy(location.CITY,doc["locality"],60); } else { - if(doc["hourly"]) - { - if(!hour) - { - hour = (struct HOURLY *)calloc(48, sizeof(struct HOURLY)); - if(hour == NULL) - { - return 23; - } - } + http.end(); + return 20; + } - JsonArray hourly = doc["hourly"]; - for(int h = 0; h < 48; h++) - { + if(doc["principalSubdivisionCode"]) + { + strncpy(location.STATE,doc["principalSubdivisionCode"],10); + } + else + { + http.end(); + return 20; + } - JsonObject hourly_0 = hourly[h]; - hour[h].dayTime = hourly_0["dt"]; // 1604336400 - hour[h].temperature = hourly_0["temp"]; // 46.58 - hour[h].apparentTemperature = hourly_0["feels_like"]; // 28.54 - hour[h].pressure = hourly_0["pressure"]; // 1015 - hour[h].humidity = hourly_0["humidity"]; // 31 - hour[h].dewPoint = hourly_0["dew_point"]; // 19.2 - hour[h].cloudCover = hourly_0["clouds"]; // 20 - hour[h].visibility = hourly_0["visibility"]; // 10000 - hour[h].windSpeed = hourly_0["wind_speed"]; // 22.77 - hour[h].windBearing = hourly_0["wind_deg"]; // 300 + if(doc["countryCode"]) + { + strncpy(location.COUNTRY,doc["countryCode"],10); + } + else + { + http.end(); + return 20; + } - if(hourly_0["snow"]) - { - if(USER_PARAM.OPEN_WEATHER_UNITS == 2) - { - float temp = hourly_0["snow"]; - hour[h].snowVolume = (temp/25.4); // 95 - } else hour[h].snowVolume = hourly_0["snow"]; // 95 + http.end(); - } else hour[h].snowVolume = 0; + return 0; +} - if(hourly_0["rain"]) - { - if(USER_PARAM.OPEN_WEATHER_UNITS == 2) - { - float temp = hourly_0["rain"]; - hour[h].rainVolume = (temp/25.4); // 95 - } else hour[h].rainVolume = hourly_0["rain"]; // 95 - } else hour[h].rainVolume = 0; +int OpenWeatherOneCall::createAQ(int sizeCap) +{ + char getURL[200] = {0}; + sprintf(getURL,"%s%.6f%s%.6f%s%s",AQ_URL1,USER_PARAM.OPEN_WEATHER_LATITUDE,AQ_URL2,USER_PARAM.OPEN_WEATHER_LONGITUDE,AQ_URL3,USER_PARAM.OPEN_WEATHER_DKEY); - JsonObject hourly_0_weather_0 = hourly_0["weather"][0]; - hour[h].id = hourly_0_weather_0["id"]; // 801 + HTTPClient http; + http.begin(getURL); + int httpCode = http.GET(); - if(hourly_0_weather_0["main"]) - { - hour[h].main = (char *)realloc(hour[h].main,sizeof(char) * strlen(hourly_0_weather_0["main"])+1); - if(hour[h].main == NULL) - { - return 23; - } - strncpy(hour[h].main,hourly_0_weather_0["main"],strlen(hourly_0_weather_0["main"])+1); - } + if (httpCode > 399) + { + if(httpCode == 401) + { + http.end(); + return 22; + } - if(hourly_0_weather_0["description"]) - { - hour[h].summary = (char *)realloc(hour[h].summary,sizeof(char) * strlen(hourly_0_weather_0["description"])+1); - if(hour[h].summary == NULL) - { - return 23; - } - strncpy(hour[h].summary,hourly_0_weather_0["description"],strlen(hourly_0_weather_0["description"])+1); - } + http.end(); + return 21; + } - strncpy(hour[h].icon,hourly_0_weather_0["icon"],strlen(hourly_0_weather_0["icon"])+1); + const size_t capacity = sizeCap; + JsonDocument doc; + deserializeJson(doc, http.getString()); - hour[h].pop = hourly_0["pop"]; // 0 - } - } - } + quality = (struct airQuality *)calloc(1,sizeof(struct airQuality)); - if(exclude.minutely) - { - OpenWeatherOneCall::freeMinuteMem(); - } - else - { - if(doc["minutely"]) - { - if(!minute) - { - minute = (struct MINUTELY *)calloc(61, sizeof(struct MINUTELY)); - if(minute == NULL) - { - return 23; - } - } + float coord_lon = doc["coord"]["lon"]; // -74.1975 + float coord_lat = doc["coord"]["lat"]; // 39.9533 + JsonObject list_0 = doc["list"][0]; - JsonArray minutely = doc["minutely"]; + quality -> aqi = list_0["main"]["aqi"]; // 2 - for(int x = 0; x<61; x++) - { - minute[x].dayTime = minutely[x]["dt"]; - minute[x].precipitation = minutely[x]["precipitation"]; // 0 - } - } - } + JsonObject list_0_components = list_0["components"]; + quality -> co = list_0_components["co"]; // 230.31 + quality -> no = list_0_components["no"]; // 0.43 + quality -> no2 = list_0_components["no2"]; // 1.69 + quality -> o3 = list_0_components["o3"]; // 100.14 + quality -> so2 = list_0_components["so2"]; // 0.88 + quality -> pm2_5 = list_0_components["pm2_5"]; // 0.76 + quality -> pm10 = list_0_components["pm10"]; // 1.04 + quality -> nh3 = list_0_components["nh3"]; // 0.43 + + quality -> dayTime = list_0["dt"]; // 1615838400 http.end(); return 0; + + } + int OpenWeatherOneCall::setExcludes(int EXCL) { unsigned int excludeMemSize = 32768; @@ -1286,27 +1373,54 @@ int OpenWeatherOneCall::setUnits(int _UNIT) { case 3: strcpy(units,"STANDARD"); + strcpy(OV_units,"standard"); break; case 2: strcpy(units,"IMPERIAL"); + strcpy(OV_units,"imperial"); break; case 1: strcpy(units,"METRIC"); + strcpy(OV_units,"metric"); break; default : strcpy(units,"IMPERIAL"); + strcpy(OV_units,"imperial"); } return 0; } -int OpenWeatherOneCall::setHistory(int _HIS) +int OpenWeatherOneCall::setCurrent(int _CUR) { - if(_HIS > 5) - { - return 16; - } - USER_PARAM.OPEN_WEATHER_HISTORY = _HIS; + + USER_PARAM.OPEN_WEATHER_CURRENT = _CUR; + + return 0; +} + +int OpenWeatherOneCall::setAirQuality(int _AQ) +{ + + USER_PARAM.OPEN_WEATHER_AIRQUALITY = _AQ; + + return 0; +} + +int OpenWeatherOneCall::setTimestamp(int _HIS, const char* _DATE) +{ + + USER_PARAM.OPEN_WEATHER_TIMESTAMP = _HIS; + strcpy(USER_PARAM.OPEN_WEATHER_TIMESTAMP_DATE,_DATE); + + return 0; +} + +int OpenWeatherOneCall::setOverview(int _OVR, const char* _DATE) +{ + + USER_PARAM.OPEN_WEATHER_OVERVIEW = _OVR; + strcpy(USER_PARAM.OPEN_WEATHER_OVERVIEW_DATE,_DATE); return 0; } @@ -1319,6 +1433,66 @@ int OpenWeatherOneCall::setDateTimeFormat(int _DTF) return 0; } +// Takes any Human Readable date converts to epoch +long OpenWeatherOneCall::extractDate(char str[11]) +{ + + long epochH; + + struct tm t; + time_t t_of_day; + + char m[3]; + char d[3]; + char yr[5]; + + + for(int x=0; x<10; x++) + { + if(isDigit(str[x])) + { + if(x<2) + { + m[x] = str[x]; + } + else + { + if(x<5) + { + d[x-3] = str[x]; + } + else + { + if(x<10) + { + yr[x-6] = str[x]; + } + } + } + } + } + + String month = String(m); + String day = String(d); + String year = String(yr); + + + t.tm_year = year.toInt()-1900; // Year - 1900 + t.tm_mon = month.toInt() - 1; // Month, where 0 = jan + t.tm_mday = day.toInt(); // Day of the month + t.tm_hour = 00; + t.tm_min = 00; + t.tm_sec = 00; + t.tm_isdst = -1; // Is DST on? 1 = yes, 0 = no, -1 = unknown + t_of_day = mktime(&t); + + + return (long) t_of_day; + +} + + + // free routines void OpenWeatherOneCall::freeCurrentMem(void) @@ -1352,19 +1526,19 @@ void OpenWeatherOneCall::freeForecastMem(void) } -void OpenWeatherOneCall::freeHistoryMem(void) +void OpenWeatherOneCall::freeTimestampMem(void) { - if(history) + if(timestamp) { for( int x = 24; x > 0; x--) { - free(history[x-1].summary); - free(history[x-1].main); + free(timestamp[x-1].summary); + free(timestamp[x-1].main); } - free(history); - history[0].summary = NULL; - history[0].main = NULL; - history = NULL; + free(timestamp); + timestamp[0].summary = NULL; + timestamp[0].main = NULL; + timestamp = NULL; } } @@ -1375,9 +1549,9 @@ void OpenWeatherOneCall::freeAlertMem(void) { for( int x = MAX_NUM_ALERTS; x > 0; x--) { - free(alert[x].senderName); - free(alert[x].event); - free(alert[x].summary); + free(alert[x].senderName); + free(alert[x].event); + free(alert[x].summary); } free(alert); alert[0].senderName = NULL; @@ -1421,6 +1595,15 @@ void OpenWeatherOneCall::freeQualityMem(void) } } +void OpenWeatherOneCall::freeOverviewMem(void) +{ + if (overView) + { + free(overView); + overView = NULL; + } +} + char* OpenWeatherOneCall::getErrorMsgs(int _errMsg) { if((_errMsg > SIZEOF(errorMsgs)) || (_errMsg < 1)) @@ -1438,8 +1621,9 @@ OpenWeatherOneCall::~OpenWeatherOneCall() OpenWeatherOneCall::freeAlertMem(); OpenWeatherOneCall::freeHourMem(); OpenWeatherOneCall::freeMinuteMem(); - OpenWeatherOneCall::freeHistoryMem(); + OpenWeatherOneCall::freeTimestampMem(); OpenWeatherOneCall::freeQualityMem(); + OpenWeatherOneCall::freeOverviewMem(); } diff --git a/src/OpenWeatherOneCall.h b/src/OpenWeatherOneCall.h index 3e9decf..678dc94 100644 --- a/src/OpenWeatherOneCall.h +++ b/src/OpenWeatherOneCall.h @@ -1,10 +1,10 @@ /* OpenWeatherOnecall.h - Upgrade v3.3.3 + Upgrade v4.0 copyright 2020 - Jessica Hershey www.github.com/jHershey69 - WEATHER: Current, hourly, minutely, 8 day future, 5 day history + WEATHER: Current, hourly, minutely, 8 day future, 45 year history, Air quality, Summary for today and tomorrow REQUIRES: OpenWeatherMap.com API key INQUIRE BY: Latitude/Longitude, IP Address, CITY ID @@ -17,11 +17,18 @@ #include // Required but installed in the ESP32 -#include // Version 6 Required +#include +#include "ESP32Time.h" + +#include "globals.h" + +#include // Version 7 Required #include #include #include +#include #include "errMsgs.h" +#include "globals.h" @@ -58,6 +65,11 @@ class OpenWeatherOneCall //Methods int parseWeather(void); + long extractDate(char str[11]); + void allocateAndCopy(char** destination, const char* source); + + + void initAPI(void); int setOpenWeatherKey(char* owKey); @@ -66,7 +78,8 @@ class OpenWeatherOneCall int setLatLon(void); int setExcl(int _EXCL); int setUnits(int _UNIT); - int setHistory(int _HIS); + int setTimestamp(int _HIS, const char* _DATE); + int setOverview(int _OVR, const char* _DATE); int setDateTimeFormat(int _DTF); char* getErrorMsgs(int errorMsg); char* nextLanguage(char * shrtPtr, char* lngPtr, int _langNum); @@ -75,9 +88,6 @@ class OpenWeatherOneCall - //Legacy Method - int parseWeather(char* DKEY, char* GKEY, float SEEK_LATITUDE, float SEEK_LONGITUDE, bool SET_UNITS, int CITY_ID, int API_EXCLUDES, int GET_HISTORY); - //Destructor ~OpenWeatherOneCall(); @@ -186,6 +196,7 @@ class OpenWeatherOneCall struct HOURLY { long dayTime; // 1604336400 + char readableTime[5]; float temperature; // 46.58 float apparentTemperature; // 28.54 float pressure; // 1015 @@ -210,41 +221,53 @@ class OpenWeatherOneCall struct MINUTELY { long dayTime; // 1604341320 + char readableTime[5]; float precipitation; // 0 } *minute = NULL; //[61] struct ALERTS { - char* senderName; //[30] = "No Alert"; // "NWS Philadelphia - Mount Holly (New Jersey, Delaware, Southeastern Pennsylvania)" - char* event; //[50] = "No Event"; // "Gale Watch" - long alertStart; // 1604271600 + char* senderName; //[30] = "No Alert"; // "NWS Philadelphia - Mount Holly (New Jersey, Delaware, Southeastern Pennsylvania)" + char* event; //[50] = "No Event"; // "Gale Watch" + long alertStart; // 1604271600 char startInfo[20]; long alertEnd; char endInfo[20]; char *summary; } *alert = NULL; + struct OVERVIEW + { + double lat; // 39.953701 + double lon; // -74.197899 + const char* tz; // "-04:00" + const char* date; // "2024-08-06" + const char* units; // "imperial" + const char* weather_overview; + } *overView = NULL; + - struct HISTORICAL + struct TIMESTAMP { + long hepoch = NULL; char weekDayName[4]; - long dayTime; // 1604242490 + long dayTime; // 1604242490 char readableDateTime[20]; - long sunrise; // 1604230151 + long sunrise; // 1604230151 char readableSunrise[5]; - long sunset; // 1604267932 + long sunset; // 1604267932 char readableSunset[5]; - float temperature; // 285.9 + float temperature; // 285.9 float apparentTemperature; // 283.42 - float pressure; // 1016 - float humidity; // 76 - float dewPoint; // 281.78 - float uvIndex; // 3.1 - float cloudCover; // 90 - float visibility; // 16093 - float windSpeed; // 3.1 - float windBearing; // 160 + float pressure; // 1016 + float humidity; // 76 + float dewPoint; // 281.78 + float uvIndex; // 3.1 + float cloudCover; // 90 + float visibility; // 16093 + float windSpeed; // 3.1 + float windBearing; // 160 float windGust; float rainVolume; float snowVolume; @@ -253,7 +276,7 @@ class OpenWeatherOneCall char* summary; // "overcast clouds" char icon[4]; // "04d" - } *history = NULL; //[25] + } *timestamp = NULL; //[25] const char* short_names[7] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; char buffer[40]; @@ -267,19 +290,25 @@ class OpenWeatherOneCall int parseCityCoordinates(char* CTY_URL); int getIPLocation(); int getIPAPILocation(char* URL); - int createHistory(void); + int createTimestamp(void); + int createOverview(void); int createCurrent(int); int setExcludes(int EXCL); int getLocationInfo(); int createAQ(int); + int setCurrent(int _CUR); + int setAirQuality(int _AQ); void freeCurrentMem(void); void freeForecastMem(void); void freeAlertMem(void); void freeHourMem(void); void freeMinuteMem(void); - void freeHistoryMem(void); + void freeTimestampMem(void); void freeQualityMem(void); + void freeOverviewMem(void); + + //Variables // For eventual struct calls @@ -292,11 +321,18 @@ class OpenWeatherOneCall float OPEN_WEATHER_LONGITUDE = NULL; int OPEN_WEATHER_UNITS = 2; int OPEN_WEATHER_EXCLUDES = NULL; - int OPEN_WEATHER_HISTORY = NULL; + int OPEN_WEATHER_TIMESTAMP = NULL; + char OPEN_WEATHER_TIMESTAMP_DATE[11] = "01/01/2024"; + int OPEN_WEATHER_OVERVIEW = NULL; + char OPEN_WEATHER_OVERVIEW_DATE[11]; + int OPEN_WEATHER_CURRENT = NULL; + int OPEN_WEATHER_AIRQUALITY = NULL; } USER_PARAM; char units[10] = "IMPERIAL"; + char OV_units[10] = "imperial"; + char _ipapiURL[38]; int summary_len = 0; diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 0000000..d93fce5 --- /dev/null +++ b/src/globals.h @@ -0,0 +1,25 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +//DO NOT EDIT THIS FILE ****************************** +// OpenWeatherOneCall v4.0.0 + + +extern float myLATITUDE; +extern float myLONGITUDE; +extern int myCITYID; +extern int myUNITS; +extern int myDTF; +extern int myTimeZone; +extern int myTIMESTAMP; +extern int myCURRENT; +extern int myAIRQUALITY; +extern char timestampDate[11]; +extern int myOVERVIEW; +extern char overviewDate[9]; +extern int myEXCLUDES; +extern char ONECALLKEY[]; +extern int locationMode; + + +#endif // GLOBALS_H