77 * Change History:
88 * 20250628 rob040 Completely changed this OWM client API and OWM access API to use the Free Service
99 * 20250712 rob040 Revised data invalidation and data get retry count until data invalid error. New API dataGetRetryCount(), removed API getCached().
10- * 20250714 rob040 add more options for geo location; it is no longer centered around CityID
10+ * 20250714 rob040 add more options for geo location; it is no longer centered around CityID, Class Ctor does not need CityID, new method setGeoLocation()
1111 *
1212 */
1313
1414#include " OpenWeatherMapClient.h"
1515#include " timeStr.h"
1616
17- // OpenWeatherMapClient::OpenWeatherMapClient(const String &ApiKey, int CityID, boolean isMetric) {
18- // myGeoLocation_CityID = CityID;
19- // myGeoLocation = "";
20- // myGeoLocationType = LOC_CITYID;
21- // myApiKey = ApiKey;
22- // this->isMetric = isMetric;
23- // isValid = false;
24- // }
25- OpenWeatherMapClient::OpenWeatherMapClient (const String &ApiKey, boolean isMetric) {
17+ OpenWeatherMapClient::OpenWeatherMapClient (const String &ApiKey, bool isMetric) {
2618 myGeoLocation = " " ;
2719 myGeoLocation_CityID = 0 ;
2820 myGeoLocationType = LOC_UNSET;
2921 myApiKey = ApiKey;
3022 this ->isMetric = isMetric;
31- isValid = false ;
23+ weather. isValid = false ;
3224}
3325
3426// setGeoLocation
3527// args String location
3628// return int 0 on success, 1 on failure such as invalid location; no check with weather server has been performed.
3729//
38- // location string can be: A) CityID number (old backwards compatibility), B) Longitude,lattitude,
39- // C) Cityname[,state],Countrycode
30+ // location string can be:
31+ // A) CityID number (old backwards compatibility),
32+ // B) Longitude,lattitude,
33+ // C) Cityname[[,state],Countrycode]
4034// The location name must be spelled with ASCII-64, no special chars are allowed. This is
4135// because the HTML page text encoding is in UTF-8 and internally we use only ASCII with
4236// LED matrix font using ANSI CP437 extended ASCII, however there is no conversion from
@@ -63,6 +57,7 @@ int OpenWeatherMapClient::setGeoLocation(const String &location) {
6357 }
6458 }
6559
60+ // Find out what kind of GeoLocation has been passed
6661 myGeoLocationType = LOC_UNKNOWN;
6762 myGeoLocation = " " ;
6863 myGeoLocation_CityID = 0 ;
@@ -81,9 +76,6 @@ int OpenWeatherMapClient::setGeoLocation(const String &location) {
8176 if ((ch_cnt_digits == 0 ) && (ch_cnt_letters >= len-2 )) {
8277 myGeoLocationType = LOC_NAME; // city,state,countrycode OR city,countrycode OR city
8378 myGeoLocation = location;
84- // USE http://api.openweathermap.org/geo/1.0/direct?q={city-name},{state-code},{country-code}&limit={limit}&appid={API-key}
85- // to convert location to lat,lon
86- // OR
8779 // USE http://api.openweathermap.org/data/2.5/weather?q={city-name}&appid={API-key}
8880 // USE http://api.openweathermap.org/data/2.5/weather?q={city-name},{country-code}&appid={API-key}
8981 // USE http://api.openweathermap.org/data/2.5/weather?q={city-name},{state-code},{country-code}&appid={API-key}
@@ -100,7 +92,7 @@ void OpenWeatherMapClient::updateWeather() {
10092 if (myApiKey == " " ) {
10193 errorMsg = F (" Please provide an API key for weather." );
10294 Serial.println (errorMsg);
103- isValid = false ;
95+ weather. isValid = false ;
10496 return ;
10597 }
10698 switch (myGeoLocationType) {
@@ -109,7 +101,7 @@ void OpenWeatherMapClient::updateWeather() {
109101 case LOC_UNKNOWN:
110102 errorMsg = F (" Please set location for weather." );
111103 Serial.println (errorMsg);
112- isValid = false ;
104+ weather. isValid = false ;
113105 return ;
114106 case LOC_CITYID:
115107 apiGetData += F (" id=" );
@@ -141,7 +133,7 @@ void OpenWeatherMapClient::updateWeather() {
141133 else {
142134 errorMsg = F (" Connection for weather data failed" );
143135 Serial.println (errorMsg);
144- if (++dataGetRetryCount > dataGetRetryCountError) isValid = false ;
136+ if (++dataGetRetryCount > dataGetRetryCountError) weather. isValid = false ;
145137 return ;
146138 }
147139
@@ -159,7 +151,7 @@ void OpenWeatherMapClient::updateWeather() {
159151 if ((millis ()-start) >= timeout_ms) {
160152 errorMsg = F (" TIMEOUT on weatherClient data receive" );
161153 Serial.println (errorMsg);
162- if (++dataGetRetryCount > dataGetRetryCountError) isValid = false ;
154+ if (++dataGetRetryCount > dataGetRetryCountError) weather. isValid = false ;
163155 return ;
164156 }
165157 // else { //FIXME DEBUG
@@ -174,7 +166,7 @@ void OpenWeatherMapClient::updateWeather() {
174166 if (strcmp (status, " HTTP/1.1 200 OK" ) != 0 ) {
175167 errorMsg = F (" Unexpected response: " ) + String (status);
176168 Serial.println (errorMsg);
177- if (++dataGetRetryCount > dataGetRetryCountError) isValid = false ;
169+ if (++dataGetRetryCount > dataGetRetryCountError) weather. isValid = false ;
178170 return ;
179171 }
180172
@@ -183,7 +175,7 @@ void OpenWeatherMapClient::updateWeather() {
183175 if (!weatherClient.find (endOfHeaders)) {
184176 errorMsg = F (" Invalid response endOfHeaders" );
185177 Serial.println (errorMsg);
186- if (++dataGetRetryCount > dataGetRetryCountError) isValid = false ;
178+ if (++dataGetRetryCount > dataGetRetryCountError) weather. isValid = false ;
187179 return ;
188180 }
189181
@@ -193,7 +185,7 @@ void OpenWeatherMapClient::updateWeather() {
193185 if (error) {
194186 errorMsg = F (" Weather Data Parsing failed!" );
195187 Serial.println (errorMsg);
196- if (++dataGetRetryCount > dataGetRetryCountError) isValid = false ;
188+ if (++dataGetRetryCount > dataGetRetryCountError) weather. isValid = false ;
197189 return ;
198190 }
199191
@@ -204,164 +196,114 @@ void OpenWeatherMapClient::updateWeather() {
204196 Serial.println (F (" Error incomplete message, size " ) + String (len));
205197 errorMsg = F (" Error: " ) + jdoc[F (" message" )].as <String>();
206198 Serial.println (errorMsg);
207- if (++dataGetRetryCount > dataGetRetryCountError) isValid = false ;
199+ if (++dataGetRetryCount > dataGetRetryCountError) weather. isValid = false ;
208200 return ;
209201 }
210202
211203
212- lat = jdoc[" coord" ][" lat" ];
213- lon = jdoc[" coord" ][" lon" ];
214- reportTimestamp = jdoc[" dt" ];
215- city = jdoc[" name" ].as <String>();
216- country = jdoc[" sys" ][" country" ].as <String>();
217- temperature = jdoc[" main" ][" temp" ];
218- humidity = jdoc[" main" ][" humidity" ];
219- weatherId = jdoc[" weather" ][0 ][" id" ];
220- weatherCondition = jdoc[" weather" ][0 ][" main" ].as <String>();
221- weatherDescription = jdoc[" weather" ][0 ][" description" ].as <String>();
222- icon = jdoc[" weather" ][0 ][" icon" ].as <String>();
223- pressure = jdoc[" main" ][" grnd_level" ];
224- if (pressure == 0 ) // no local ground level pressure? then get main pressure (at sea level)
225- pressure = jdoc[" main" ][" pressure" ];
226- windSpeed = jdoc[" wind" ][" speed" ];
227- windDirection = jdoc[" wind" ][" deg" ];
228- cloudCoverage = jdoc[" clouds" ][" all" ];
229- tempHigh = jdoc[" main" ][" temp_max" ];
230- tempLow = jdoc[" main" ][" temp_min" ];
231- timeZone = jdoc[" timezone" ];
232- sunRise = jdoc[" sys" ][" sunrise" ];
233- sunSet = jdoc[" sys" ][" sunset" ];
234- isValid = true ;
204+ weather. lat = jdoc[" coord" ][" lat" ];
205+ weather. lon = jdoc[" coord" ][" lon" ];
206+ weather. reportTimestamp = jdoc[" dt" ];
207+ weather. city = jdoc[" name" ].as <String>();
208+ weather. country = jdoc[" sys" ][" country" ].as <String>();
209+ weather. temperature = jdoc[" main" ][" temp" ];
210+ weather. humidity = jdoc[" main" ][" humidity" ];
211+ weather. weatherId = jdoc[" weather" ][0 ][" id" ];
212+ weather. condition = jdoc[" weather" ][0 ][" main" ].as <String>();
213+ weather. description = jdoc[" weather" ][0 ][" description" ].as <String>();
214+ weather. icon = jdoc[" weather" ][0 ][" icon" ].as <String>();
215+ weather. pressure = jdoc[" main" ][" grnd_level" ];
216+ if (weather. pressure == 0 ) // no local ground level pressure? then get main pressure (at sea level)
217+ weather. pressure = jdoc[" main" ][" pressure" ];
218+ weather. windSpeed = jdoc[" wind" ][" speed" ];
219+ weather. windDirection = jdoc[" wind" ][" deg" ];
220+ weather. cloudCoverage = jdoc[" clouds" ][" all" ];
221+ weather. tempHigh = jdoc[" main" ][" temp_max" ];
222+ weather. tempLow = jdoc[" main" ][" temp_min" ];
223+ weather. timeZone = jdoc[" timezone" ];
224+ weather. sunRise = jdoc[" sys" ][" sunrise" ];
225+ weather. sunSet = jdoc[" sys" ][" sunset" ];
226+ weather. isValid = true ;
235227
236228 if (isMetric) {
237229 // convert m/s to kmh
238- windSpeed *= 3.6 ;
230+ weather. windSpeed *= 3.6 ;
239231 } else {
240232 // Imperial mode
241233 // windspeed is already in mph
242234 // convert millibars (hPa) to Inches mercury (inHg)
243- pressure = (int )((float )pressure * 0.0295300586 + 0.5 );
235+ weather. pressure = (int )((float )weather. pressure * 0.0295300586 + 0.5 );
244236 // convert millibars (hPa) to PSI
245237 // pressure = (int)((float)pressure * 0.0145037738 + 0.5);
246238 }
247239
248240#if 1 // DEBUG
249241 Serial.println (F (" Weather data:" ));
250- // Serial.print(F("lat: ")); Serial.println(lat);
251- // Serial.print(F("lon: ")); Serial.println(lon);
252- Serial.print (F (" reportTimestamp: " )); Serial.println (reportTimestamp);
253- // Serial.print(F("city: ")); Serial.println(city);
254- // Serial.print(F("country: ")); Serial.println(country);
255- Serial.print (F (" temperature: " )); Serial.println (temperature);
256- Serial.print (F (" humidity: " )); Serial.println (humidity);
257- Serial.print (F (" wind: " )); Serial.println (windSpeed);
258- Serial.print (F (" windDirection: " )); Serial.println (windDirection);
259- // Serial.print(F("weatherId: ")); Serial.println(weatherId);
260- Serial.print (F (" weatherCondition: " )); Serial.println (weatherCondition );
261- Serial.print (F (" weatherDescription: " )); Serial.println (weatherDescription );
262- // Serial.print(F("icon: ")); Serial.println(icon);
242+ // Serial.print(F("lat: ")); Serial.println(weather. lat);
243+ // Serial.print(F("lon: ")); Serial.println(weather. lon);
244+ // Serial.print(F("reportTimestamp: ")); Serial.println(weather. reportTimestamp);
245+ // Serial.print(F("city: ")); Serial.println(weather. city);
246+ // Serial.print(F("country: ")); Serial.println(weather. country);
247+ Serial.print (F (" temperature: " )); Serial.println (weather. temperature );
248+ Serial.print (F (" humidity: " )); Serial.println (weather. humidity );
249+ Serial.print (F (" wind: " )); Serial.println (weather. windSpeed );
250+ Serial.print (F (" windDirection: " )); Serial.println (weather. windDirection );
251+ // Serial.print(F("weatherId: ")); Serial.println(weather. weatherId);
252+ Serial.print (F (" weatherCondition: " )); Serial.println (weather. condition );
253+ Serial.print (F (" weatherDescription: " )); Serial.println (weather. description );
254+ // Serial.print(F("icon: ")); Serial.println(weather. icon);
263255 Serial.print (F (" timezone: " )); Serial.println (getTimeZone ());
264256 Serial.println ();
265257#endif
266258}
267259
268260
269261String OpenWeatherMapClient::getWindDirectionText () {
270- int val = floor ((windDirection / 22.5 ) + 0.5 );
262+ int val = floor ((weather. windDirection / 22.5 ) + 0.5 );
271263 String arr[] = {" N" , " NNE" , " NE" , " ENE" , " E" , " ESE" , " SE" , " SSE" , " S" , " SSW" , " SW" , " WSW" , " W" , " WNW" , " NW" , " NNW" };
272264 return arr[(val % 16 )];
273265}
274266
275267
276268String OpenWeatherMapClient::getWeekDay () {
277269 String rtnValue = " " ;
278- long timestamp = reportTimestamp;
270+ long timestamp = weather. reportTimestamp ;
279271 long day = 0 ;
280- /* static const char* dayarr[] = {
281- "Sunday",
282- "Monday",
283- "Tuesday",
284- "Wednesday",
285- "Thursday",
286- "Friday",
287- "Saturday"
288- };*/
289272 if (timestamp != 0 ) {
290- // day = (((timestamp + (3600 * (int)offset)) / 86400) + 4) % 7;
291273 // Add timezone from OWM
292- timestamp += timeZone;
274+ timestamp += weather. timeZone ;
293275 day = ((timestamp / 86400 ) + 4 ) % 7 ;
294276 rtnValue = getDayName (day);
295277 }
296278 return rtnValue;
297279}
298280
299281String OpenWeatherMapClient::getWeatherIcon () {
300- int id = weatherId;
301- String W = " )" ;
302- switch (id)
282+ // match weather condition codes to OLED icon in the weatherstation font
283+ // see https://openweathermap.org/weather-conditions
284+ // TODO: the weatherstation fonts (Meteocons, http://oleddisplay.squix.ch/) contain far more icons than used here
285+ // TODO: There is no night/day difference made here
286+ // TODO: check if translation from weather icon code OLED icon makes more sense.
287+ static const struct { int16_t nr; char w;} lookuptable[] =
303288 {
304- case 800 : W = " B" ; break ;
305- case 801 : W = " Y" ; break ;
306- case 802 : W = " H" ; break ;
307- case 803 : W = " H" ; break ;
308- case 804 : W = " Y" ; break ;
309-
310- case 200 : W = " 0" ; break ;
311- case 201 : W = " 0" ; break ;
312- case 202 : W = " 0" ; break ;
313- case 210 : W = " 0" ; break ;
314- case 211 : W = " 0" ; break ;
315- case 212 : W = " 0" ; break ;
316- case 221 : W = " 0" ; break ;
317- case 230 : W = " 0" ; break ;
318- case 231 : W = " 0" ; break ;
319- case 232 : W = " 0" ; break ;
320-
321- case 300 : W = " R" ; break ;
322- case 301 : W = " R" ; break ;
323- case 302 : W = " R" ; break ;
324- case 310 : W = " R" ; break ;
325- case 311 : W = " R" ; break ;
326- case 312 : W = " R" ; break ;
327- case 313 : W = " R" ; break ;
328- case 314 : W = " R" ; break ;
329- case 321 : W = " R" ; break ;
330-
331- case 500 : W = " R" ; break ;
332- case 501 : W = " R" ; break ;
333- case 502 : W = " R" ; break ;
334- case 503 : W = " R" ; break ;
335- case 504 : W = " R" ; break ;
336- case 511 : W = " R" ; break ;
337- case 520 : W = " R" ; break ;
338- case 521 : W = " R" ; break ;
339- case 522 : W = " R" ; break ;
340- case 531 : W = " R" ; break ;
341-
342- case 600 : W = " W" ; break ;
343- case 601 : W = " W" ; break ;
344- case 602 : W = " W" ; break ;
345- case 611 : W = " W" ; break ;
346- case 612 : W = " W" ; break ;
347- case 615 : W = " W" ; break ;
348- case 616 : W = " W" ; break ;
349- case 620 : W = " W" ; break ;
350- case 621 : W = " W" ; break ;
351- case 622 : W = " W" ; break ;
352-
353- case 701 : W = " M" ; break ;
354- case 711 : W = " M" ; break ;
355- case 721 : W = " M" ; break ;
356- case 731 : W = " M" ; break ;
357- case 741 : W = " M" ; break ;
358- case 751 : W = " M" ; break ;
359- case 761 : W = " M" ; break ;
360- case 762 : W = " M" ; break ;
361- case 771 : W = " M" ; break ;
362- case 781 : W = " M" ; break ;
363-
364- default :break ;
289+ { 800 , ' B' }, { 801 , ' Y' }, { 802 , ' H' }, { 803 , ' H' }, { 804 , ' Y' },
290+ { 200 , ' 0' }, { 201 , ' 0' }, { 202 , ' 0' }, { 210 , ' 0' }, { 211 , ' 0' },
291+ { 212 , ' 0' }, { 221 , ' 0' }, { 230 , ' 0' }, { 231 , ' 0' }, { 232 , ' 0' },
292+ { 300 , ' R' }, { 301 , ' R' }, { 302 , ' R' }, { 310 , ' R' }, { 311 , ' R' },
293+ { 312 , ' R' }, { 313 , ' R' }, { 314 , ' R' }, { 321 , ' R' },
294+ { 500 , ' R' }, { 501 , ' R' }, { 502 , ' R' }, { 503 , ' R' }, { 504 , ' R' },
295+ { 511 , ' R' }, { 520 , ' R' }, { 521 , ' R' }, { 522 , ' R' }, { 531 , ' R' },
296+ { 600 , ' W' }, { 601 , ' W' }, { 602 , ' W' }, { 611 , ' W' }, { 612 , ' W' },
297+ { 615 , ' W' }, { 616 , ' W' }, { 620 , ' W' }, { 621 , ' W' }, { 622 , ' W' },
298+ { 701 , ' M' }, { 711 , ' M' }, { 721 , ' M' }, { 731 , ' M' }, { 741 , ' M' },
299+ { 751 , ' M' }, { 761 , ' M' }, { 762 , ' M' }, { 771 , ' M' }, { 781 , ' M' },
300+ { 0 , ' )' }
301+ };
302+ int id = weather.weatherId ;
303+ int i;
304+ for (i=0 ; lookuptable[i].nr != 0 ; i++)
305+ {
306+ if (lookuptable[i].nr == id) break ;
365307 }
366- return W ;
308+ return String (lookuptable[i]. w ) ;
367309}
0 commit comments