@@ -20,12 +20,12 @@ static constexpr unsigned DEFAULT_INTERVAL_SEC = 3600; // 1 hour
20
20
// - these are user visible in the webapp settings UI
21
21
// - they are scoped to this module, don't need to be globally unique
22
22
//
23
- const char CFG_API_BASE[] = " ApiBase" ;
24
- const char CFG_API_KEY[] = " ApiKey" ;
25
- const char CFG_LATITUDE[] = " Latitude" ;
26
- const char CFG_LONGITUDE[] = " Longitude" ;
27
- const char CFG_INTERVAL_SEC[] = " IntervalSec" ;
28
- const char CFG_LOCATION[] = " Location" ;
23
+ const char CFG_API_BASE[] PROGMEM = " ApiBase" ;
24
+ const char CFG_API_KEY[] PROGMEM = " ApiKey" ;
25
+ const char CFG_LATITUDE[] PROGMEM = " Latitude" ;
26
+ const char CFG_LONGITUDE[] PROGMEM = " Longitude" ;
27
+ const char CFG_INTERVAL_SEC[] PROGMEM = " IntervalSec" ;
28
+ const char CFG_LOCATION[] PROGMEM = " Location" ;
29
29
30
30
// keep commas; encode spaces etc.
31
31
static void urlEncode (const char * src, char * dst, size_t dstSize) {
@@ -46,6 +46,27 @@ static void urlEncode(const char* src, char* dst, size_t dstSize) {
46
46
dst[di] = ' \0 ' ;
47
47
}
48
48
49
+ // Redact the API key in a URL by replacing the value after "appid=" with '*'.
50
+ static void redactApiKeyInUrl (const char * in, char * out, size_t outLen) {
51
+ if (!in || !out || outLen == 0 ) return ;
52
+ const char * p = strstr (in, " appid=" );
53
+ if (!p) {
54
+ strncpy (out, in, outLen);
55
+ out[outLen - 1 ] = ' \0 ' ;
56
+ return ;
57
+ }
58
+ size_t prefixLen = (size_t )(p - in) + 6 ; // include "appid="
59
+ if (prefixLen >= outLen) {
60
+ // Not enough space; best effort copy and terminate
61
+ strncpy (out, in, outLen);
62
+ out[outLen - 1 ] = ' \0 ' ;
63
+ return ;
64
+ }
65
+ memcpy (out, in, prefixLen);
66
+ out[prefixLen] = ' *' ;
67
+ out[prefixLen + 1 ] = ' \0 ' ;
68
+ }
69
+
49
70
// Normalize "Oakland, CA, USA" → "Oakland,CA,US" in-place
50
71
static void normalizeLocation (char * q) {
51
72
// trim spaces and commas
@@ -57,9 +78,8 @@ static void normalizeLocation(char* q) {
57
78
*out = ' \0 ' ;
58
79
len = strlen (q);
59
80
if (len >= 4 && strcasecmp (q + len - 4 , " ,USA" ) == 0 ) {
60
- q[len - 2 ] = ' U' ;
61
- q[len - 1 ] = ' S' ;
62
- q[len] = ' \0 ' ;
81
+ // Truncate the trailing 'A' so ",USA" → ",US" without corrupting chars
82
+ q[len - 1 ] = ' \0 ' ;
63
83
}
64
84
}
65
85
@@ -200,11 +220,14 @@ std::unique_ptr<SkyModel> OpenWeatherMapSource::fetch(std::time_t now) {
200
220
return nullptr ;
201
221
202
222
lastFetch_ = now;
223
+ lastHistFetch_ = now; // history fetches should wait
203
224
204
225
// Fetch JSON
205
226
char url[256 ];
206
227
composeApiUrl (url, sizeof (url));
207
- DEBUG_PRINTF (" SkyStrip: %s::fetch URL: %s\n " , name ().c_str (), url);
228
+ char redacted[256 ];
229
+ redactApiKeyInUrl (url, redacted, sizeof (redacted));
230
+ DEBUG_PRINTF (" SkyStrip: %s::fetch URL: %s\n " , name ().c_str (), redacted);
208
231
209
232
auto doc = getJson (url);
210
233
if (!doc) {
@@ -253,12 +276,12 @@ std::unique_ptr<SkyModel> OpenWeatherMapSource::fetch(std::time_t now) {
253
276
model->sunset_ = sunset;
254
277
for (JsonObject hour : hourly) {
255
278
time_t dt = hour[" dt" ].as <time_t >();
256
- model->temperature_forecast .push_back ({ dt, hour[" temp" ].as <double >() });
257
- model->dew_point_forecast .push_back ({ dt, hour[" dew_point" ].as <double >() });
258
- model->wind_speed_forecast .push_back ({ dt, hour[" wind_speed" ].as <double >() });
259
- model->wind_dir_forecast .push_back ({ dt, hour[" wind_deg" ].as <double >() });
260
- model->wind_gust_forecast .push_back ({ dt, hour[" wind_gust" ].as <double >() });
261
- model->cloud_cover_forecast .push_back ({ dt, hour[" clouds" ].as <double >() });
279
+ model->temperature_forecast .push_back ({ dt, ( float ) hour[" temp" ].as <double >() });
280
+ model->dew_point_forecast .push_back ({ dt, ( float ) hour[" dew_point" ].as <double >() });
281
+ model->wind_speed_forecast .push_back ({ dt, ( float ) hour[" wind_speed" ].as <double >() });
282
+ model->wind_dir_forecast .push_back ({ dt, ( float ) hour[" wind_deg" ].as <double >() });
283
+ model->wind_gust_forecast .push_back ({ dt, ( float ) hour[" wind_gust" ].as <double >() });
284
+ model->cloud_cover_forecast .push_back ({ dt, ( float ) hour[" clouds" ].as <double >() });
262
285
JsonArray wArr = hour[" weather" ].as <JsonArray>();
263
286
bool hasRain = false , hasSnow = false ;
264
287
if (hour.containsKey (" rain" )) {
@@ -278,10 +301,13 @@ std::unique_ptr<SkyModel> OpenWeatherMapSource::fetch(std::time_t now) {
278
301
hasSnow = true ;
279
302
}
280
303
int ptype = hasRain && hasSnow ? 3 : (hasSnow ? 2 : (hasRain ? 1 : 0 ));
281
- model->precip_type_forecast .push_back ({ dt, double (ptype) });
282
- model->precip_prob_forecast .push_back ({ dt, hour[" pop" ].as <double >() });
304
+ model->precip_type_forecast .push_back ({ dt, ( float )ptype });
305
+ model->precip_prob_forecast .push_back ({ dt, ( float ) hour[" pop" ].as <double >() });
283
306
}
284
307
308
+ // Stagger history fetch to avoid back-to-back GETs in same loop iteration
309
+ // and reduce risk of watchdog resets. Enforce at least 15s before history.
310
+ lastHistFetch_ = skystrip::util::time_now_utc ();
285
311
return model;
286
312
}
287
313
@@ -298,7 +324,9 @@ std::unique_ptr<SkyModel> OpenWeatherMapSource::checkhistory(time_t now, std::ti
298
324
snprintf (url, sizeof (url),
299
325
" %s/data/3.0/onecall/timemachine?lat=%.6f&lon=%.6f&dt=%ld&units=imperial&appid=%s" ,
300
326
apiBase_.c_str (), latitude_, longitude_, (long )fetchDt, apiKey_.c_str ());
301
- DEBUG_PRINTF (" SkyStrip: %s::checkhistory URL: %s\n " , name ().c_str (), url);
327
+ char redacted[256 ];
328
+ redactApiKeyInUrl (url, redacted, sizeof (redacted));
329
+ DEBUG_PRINTF (" SkyStrip: %s::checkhistory URL: %s\n " , name ().c_str (), redacted);
302
330
303
331
auto doc = getJson (url);
304
332
if (!doc) {
@@ -321,12 +349,12 @@ std::unique_ptr<SkyModel> OpenWeatherMapSource::checkhistory(time_t now, std::ti
321
349
for (JsonObject hour : hourly) {
322
350
time_t dt = hour[" dt" ].as <time_t >();
323
351
if (dt >= oldestTstamp) continue ;
324
- model->temperature_forecast .push_back ({ dt, hour[" temp" ].as <double >() });
325
- model->dew_point_forecast .push_back ({ dt, hour[" dew_point" ].as <double >() });
326
- model->wind_speed_forecast .push_back ({ dt, hour[" wind_speed" ].as <double >() });
327
- model->wind_dir_forecast .push_back ({ dt, hour[" wind_deg" ].as <double >() });
328
- model->wind_gust_forecast .push_back ({ dt, hour[" wind_gust" ].as <double >() });
329
- model->cloud_cover_forecast .push_back ({ dt, hour[" clouds" ].as <double >() });
352
+ model->temperature_forecast .push_back ({ dt, ( float ) hour[" temp" ].as <double >() });
353
+ model->dew_point_forecast .push_back ({ dt, ( float ) hour[" dew_point" ].as <double >() });
354
+ model->wind_speed_forecast .push_back ({ dt, ( float ) hour[" wind_speed" ].as <double >() });
355
+ model->wind_dir_forecast .push_back ({ dt, ( float ) hour[" wind_deg" ].as <double >() });
356
+ model->wind_gust_forecast .push_back ({ dt, ( float ) hour[" wind_gust" ].as <double >() });
357
+ model->cloud_cover_forecast .push_back ({ dt, ( float ) hour[" clouds" ].as <double >() });
330
358
JsonArray wArr = hour[" weather" ].as <JsonArray>();
331
359
bool hasRain = false , hasSnow = false ;
332
360
if (hour.containsKey (" rain" )) {
@@ -346,8 +374,8 @@ std::unique_ptr<SkyModel> OpenWeatherMapSource::checkhistory(time_t now, std::ti
346
374
hasSnow = true ;
347
375
}
348
376
int ptype = hasRain && hasSnow ? 3 : (hasSnow ? 2 : (hasRain ? 1 : 0 ));
349
- model->precip_type_forecast .push_back ({ dt, double (ptype) });
350
- model->precip_prob_forecast .push_back ({ dt, hour[" pop" ].as <double >() });
377
+ model->precip_type_forecast .push_back ({ dt, ( float )ptype });
378
+ model->precip_prob_forecast .push_back ({ dt, ( float ) hour[" pop" ].as <double >() });
351
379
}
352
380
353
381
if (model->temperature_forecast .empty ()) return nullptr ;
@@ -384,7 +412,9 @@ bool OpenWeatherMapSource::geocodeOWM(std::string const & rawQuery,
384
412
snprintf (url, sizeof (url),
385
413
" %s/geo/1.0/direct?q=%s&limit=5&appid=%s" ,
386
414
apiBase_.c_str (), enc, apiKey_.c_str ());
387
- DEBUG_PRINTF (" SkyStrip: %s::geocodeOWM URL: %s\n " , name ().c_str (), url);
415
+ char redacted[512 ];
416
+ redactApiKeyInUrl (url, redacted, sizeof (redacted));
417
+ DEBUG_PRINTF (" SkyStrip: %s::geocodeOWM URL: %s\n " , name ().c_str (), redacted);
388
418
389
419
auto doc = getJson (url);
390
420
resetRateLimit (); // we want to do a fetch immediately after ...
0 commit comments