@@ -32,6 +32,11 @@ extern "C" esp_err_t esp_crt_bundle_attach(void *conf);
32
32
33
33
extern SolarForecast dap;
34
34
35
+ enum Resolution {
36
+ RESOLUTION_15MIN,
37
+ RESOLUTION_60MIN
38
+ };
39
+
35
40
void SolarForecast::pre_setup ()
36
41
{
37
42
config = ConfigRoot{Config::Object ({
@@ -42,25 +47,51 @@ void SolarForecast::pre_setup()
42
47
String api_url = update.get (" api_url" )->asString ();
43
48
44
49
if ((api_url.length () > 0 ) && !api_url.startsWith (" https://" )) {
45
- return " HTTPS required for Day Ahead Price API URL" ;
50
+ return " HTTPS required for Solar Forecast API URL" ;
46
51
}
47
52
48
53
return " " ;
49
54
}};
50
55
51
56
state = Config::Object ({
52
- {" last_sync" , Config::Uint32 (0 )}, // unix timestamp in minutes
53
- {" last_check" , Config::Uint32 (0 )}, // unix timestamp in minutes
54
- {" next_check" , Config::Uint32 (0 )} // unix timestamp in minutes
57
+ {" rate_limit" , Config::Uint8 (0 )},
58
+ {" rate_remaining" , Config::Uint8 (0 )},
55
59
});
56
60
57
- forecast = Config::Object ({
58
- });
61
+ uint8_t index = 0 ;
62
+ for (SolarForecastPlane &plane : planes) {
63
+ plane.config = ConfigRoot{Config::Object ({
64
+ {" active" , Config::Bool (false )},
65
+ {" latitude" , Config::Float (0 )},
66
+ {" longitude" , Config::Float (0 )},
67
+ {" declination" , Config::Int16 (0 )},
68
+ {" azimuth" , Config::Int16 (0 )},
69
+ {" kwp" , Config::Float (0 )}
70
+ })};
71
+
72
+ plane.state = Config::Object ({
73
+ {" last_sync" , Config::Uint32 (0 )}, // unix timestamp in minutes
74
+ {" last_check" , Config::Uint32 (0 )}, // unix timestamp in minutes
75
+ {" next_check" , Config::Uint32 (0 )}, // unix timestamp in minutes
76
+ {" place" , Config::Str (" Unknown" , 0 , 128 )}
77
+ });
78
+
79
+ plane.forecast = Config::Object ({
80
+ {" first_date" , Config::Uint32 (0 )}, // unix timestamp in minutes
81
+ {" resolution" , Config::Uint (RESOLUTION_60MIN, RESOLUTION_15MIN, RESOLUTION_60MIN)},
82
+ {" forecast" , Config::Array ({}, new Config{Config::Uint32 (0 )}, 0 , 200 , Config::type_id<Config::ConfInt>())}
83
+ });
84
+
85
+ plane.index = index++;
86
+ }
59
87
}
60
88
61
89
void SolarForecast::setup ()
62
90
{
63
91
api.restorePersistentConfig (" solar_forecast/config" , &config);
92
+ for (SolarForecastPlane &plane : planes) {
93
+ api.restorePersistentConfig (get_path (plane, SolarForecast::PathType::Config), &plane.config );
94
+ }
64
95
65
96
json_buffer = nullptr ;
66
97
json_buffer_position = 0 ;
@@ -71,8 +102,12 @@ void SolarForecast::setup()
71
102
void SolarForecast::register_urls ()
72
103
{
73
104
api.addPersistentConfig (" solar_forecast/config" , &config);
74
- api.addState (" solar_forecast/state" , &state);
75
- api.addState (" solar_forecast/forecast" , &forecast);
105
+ api.addState (" solar_forecast/state" , &state);
106
+ for (SolarForecastPlane &plane : planes) {
107
+ api.addPersistentConfig (get_path (plane, SolarForecast::PathType::Config), &plane.config );
108
+ api.addState (get_path (plane, SolarForecast::PathType::State), &plane.state );
109
+ api.addState (get_path (plane, SolarForecast::PathType::Forecast), &plane.forecast );
110
+ }
76
111
77
112
task_scheduler.scheduleWithFixedDelay ([this ]() {
78
113
this ->update ();
@@ -140,11 +175,21 @@ void SolarForecast::update()
140
175
return ;
141
176
}
142
177
143
- if (state .get (" next_check " )->asUint () > timestamp_minutes () ) {
178
+ if (config .get (" enable " )->asBool () == false ) {
144
179
return ;
145
180
}
146
181
147
- if (config.get (" enable" )->asBool () == false ) {
182
+ // Find plane that is due for update
183
+ plane_current = nullptr ;
184
+ for (SolarForecastPlane &plane : planes) {
185
+ if (plane.state .get (" active" )->asBool () && (plane.state .get (" next_check" )->asUint () < timestamp_minutes ())) {
186
+ plane_current = &plane;
187
+ break ;
188
+ }
189
+ }
190
+
191
+ if (plane_current == nullptr ) {
192
+ // No plane due for update found
148
193
return ;
149
194
}
150
195
@@ -159,7 +204,7 @@ void SolarForecast::update()
159
204
160
205
esp_http_client_config_t http_config = {};
161
206
162
- http_config.url = get_api_url_with_path ();
207
+ http_config.url = get_api_url_with_path (*plane_current );
163
208
http_config.event_handler = update_event_handler;
164
209
http_config.user_data = this ;
165
210
http_config.is_async = true ;
@@ -258,7 +303,7 @@ void SolarForecast::update()
258
303
}
259
304
260
305
// Create API path including user configuration
261
- const char * SolarForecast::get_api_url_with_path ()
306
+ const char * SolarForecast::get_api_url_with_path (const SolarForecastPlane &plane )
262
307
{
263
308
static String api_url_with_path;
264
309
@@ -268,7 +313,24 @@ const char* SolarForecast::get_api_url_with_path()
268
313
api_url += " /" ;
269
314
}
270
315
271
- // TODO
316
+ api_url_with_path = api_url + " estimate/"
317
+ + String (plane.config .get (" latitude" )->asFloat (), 4 ) + " /"
318
+ + String (plane.config .get (" longitude" )->asFloat (), 4 ) + " /"
319
+ + String (plane.config .get (" declination" )->asFloat (), 4 ) + " /"
320
+ + String (plane.config .get (" azimuth" )->asFloat (), 4 ) + " /"
321
+ + String (plane.config .get (" kwp" )->asFloat (), 4 );
272
322
273
323
return api_url_with_path.c_str ();
324
+ }
325
+
326
+ static const char *solar_forecast_path_postfixes[] = {" " , " config" , " state" , " forecast" };
327
+ static_assert (ARRAY_SIZE(solar_forecast_path_postfixes) == static_cast<uint32_t>(SolarForecast::PathType::_max) + 1, "Path postfix length mismatch");
328
+ String SolarForecast::get_path (const SolarForecastPlane &plane, const SolarForecast::PathType path_type)
329
+ {
330
+ String path = " solar_forecast/planes" ;
331
+ path.concat (plane.index );
332
+ path.concat (' /' );
333
+ path.concat (solar_forecast_path_postfixes[static_cast <uint32_t >(path_type)]);
334
+
335
+ return path;
274
336
}
0 commit comments