Skip to content

Commit 8d57fab

Browse files
committed
solar_forecast: Add API/config
1 parent 628e334 commit 8d57fab

File tree

2 files changed

+109
-26
lines changed

2 files changed

+109
-26
lines changed

software/src/modules/solar_forecast/solar_forecast.cpp

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ extern "C" esp_err_t esp_crt_bundle_attach(void *conf);
3232

3333
extern SolarForecast dap;
3434

35+
enum Resolution {
36+
RESOLUTION_15MIN,
37+
RESOLUTION_60MIN
38+
};
39+
3540
void SolarForecast::pre_setup()
3641
{
3742
config = ConfigRoot{Config::Object({
@@ -42,25 +47,51 @@ void SolarForecast::pre_setup()
4247
String api_url = update.get("api_url")->asString();
4348

4449
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";
4651
}
4752

4853
return "";
4954
}};
5055

5156
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)},
5559
});
5660

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+
}
5987
}
6088

6189
void SolarForecast::setup()
6290
{
6391
api.restorePersistentConfig("solar_forecast/config", &config);
92+
for (SolarForecastPlane &plane : planes) {
93+
api.restorePersistentConfig(get_path(plane, SolarForecast::PathType::Config), &plane.config);
94+
}
6495

6596
json_buffer = nullptr;
6697
json_buffer_position = 0;
@@ -71,8 +102,12 @@ void SolarForecast::setup()
71102
void SolarForecast::register_urls()
72103
{
73104
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+
}
76111

77112
task_scheduler.scheduleWithFixedDelay([this]() {
78113
this->update();
@@ -140,11 +175,21 @@ void SolarForecast::update()
140175
return;
141176
}
142177

143-
if (state.get("next_check")->asUint() > timestamp_minutes()) {
178+
if (config.get("enable")->asBool() == false) {
144179
return;
145180
}
146181

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
148193
return;
149194
}
150195

@@ -159,7 +204,7 @@ void SolarForecast::update()
159204

160205
esp_http_client_config_t http_config = {};
161206

162-
http_config.url = get_api_url_with_path();
207+
http_config.url = get_api_url_with_path(*plane_current);
163208
http_config.event_handler = update_event_handler;
164209
http_config.user_data = this;
165210
http_config.is_async = true;
@@ -258,7 +303,7 @@ void SolarForecast::update()
258303
}
259304

260305
// 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)
262307
{
263308
static String api_url_with_path;
264309

@@ -268,7 +313,24 @@ const char* SolarForecast::get_api_url_with_path()
268313
api_url += "/";
269314
}
270315

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);
272322

273323
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;
274336
}

software/src/modules/solar_forecast/solar_forecast.h

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "config.h"
2828

2929
#define SOLAR_FORECAST_MAX_JSON_LENGTH 4096*4 // TODO: How big does this need to be?
30+
#define SOLAR_FORECAST_PLANES 6
3031

3132
enum SFDownloadState {
3233
SF_DOWNLOAD_STATE_OK,
@@ -36,10 +37,40 @@ enum SFDownloadState {
3637

3738
class SolarForecast final : public IModule
3839
{
40+
public:
41+
enum class PathType {
42+
Base = 0,
43+
Config = 1,
44+
State = 2,
45+
Forecast = 3,
46+
_max = 3
47+
};
48+
49+
SolarForecast(){}
50+
void pre_setup() override;
51+
void setup() override;
52+
void register_urls() override;
53+
esp_err_t update_event_handler_impl(esp_http_client_event_t *event);
54+
55+
ConfigRoot config;
56+
ConfigRoot state;
57+
ConfigRoot forecast;
58+
3959
private:
60+
class SolarForecastPlane {
61+
public:
62+
uint8_t index;
63+
ConfigRoot config;
64+
ConfigRoot forecast;
65+
ConfigRoot state;
66+
};
67+
4068
void update();
4169
void update_price();
42-
const char* get_api_url_with_path();
70+
String get_path(const SolarForecastPlane &plane, const PathType path_type);
71+
const char* get_api_url_with_path(const SolarForecastPlane &plane);
72+
73+
SolarForecastPlane *plane_current;
4374

4475
std::unique_ptr<unsigned char[]> cert = nullptr;
4576
esp_http_client_handle_t http_client = nullptr;
@@ -48,16 +79,6 @@ class SolarForecast final : public IModule
4879
char *json_buffer;
4980
uint32_t json_buffer_position;
5081

51-
SFDownloadState download_state = SF_DOWNLOAD_STATE_OK;
52-
53-
public:
54-
SolarForecast(){}
55-
void pre_setup() override;
56-
void setup() override;
57-
void register_urls() override;
58-
esp_err_t update_event_handler_impl(esp_http_client_event_t *event);
59-
60-
ConfigRoot config;
61-
ConfigRoot state;
62-
ConfigRoot forecast;
82+
SFDownloadState download_state = SF_DOWNLOAD_STATE_OK;
83+
SolarForecastPlane planes[SOLAR_FORECAST_PLANES];
6384
};

0 commit comments

Comments
 (0)