diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 83cf35f9a..67638c456 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -31,10 +31,12 @@ FS dummyFS; ESP8266React EMSESP::esp8266React(&webServer, &dummyFS); WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); +WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); #else ESP8266React EMSESP::esp8266React(&webServer, &LittleFS); WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); +WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); #endif WebStatusService EMSESP::webStatusService = WebStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); @@ -1445,6 +1447,7 @@ void EMSESP::start() { system_.reload_settings(); // ... and store some of the settings locally webCustomizationService.begin(); // load the customizations + webSchedulerService.begin(); // load the scheduler events // start telnet service if it's enabled // default idle is 10 minutes, default write timeout is 0 (automatic) @@ -1490,13 +1493,14 @@ void EMSESP::loop() { // if we're doing an OTA upload, skip everything except from console refresh if (!system_.upload_status()) { // service loops - webLogService.loop(); // log in Web UI - rxservice_.loop(); // process any incoming Rx telegrams - shower_.loop(); // check for shower on/off - dallassensor_.loop(); // read dallas sensor temperatures - analogsensor_.loop(); // read analog sensor values - publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue - mqtt_.loop(); // sends out anything in the MQTT queue + webLogService.loop(); // log in Web UI + rxservice_.loop(); // process any incoming Rx telegrams + shower_.loop(); // check for shower on/off + dallassensor_.loop(); // read dallas sensor temperatures + analogsensor_.loop(); // read analog sensor values + publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue + mqtt_.loop(); // sends out anything in the MQTT queue + webSchedulerService.loop(); // handle any scheduled jobs // force a query on the EMS devices to fetch latest data at a set interval (1 min) scheduled_fetch_values(); diff --git a/src/emsesp.h b/src/emsesp.h index 7ee19ef6f..c01d8849a 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -43,6 +43,7 @@ #include "web/WebDataService.h" #include "web/WebSettingsService.h" #include "web/WebCustomizationService.h" +#include "web/WebSchedulerService.h" #include "web/WebAPIService.h" #include "web/WebLogService.h" @@ -228,6 +229,7 @@ class EMSESP { static WebAPIService webAPIService; static WebLogService webLogService; static WebCustomizationService webCustomizationService; + static WebSchedulerService webSchedulerService; private: static std::string device_tostring(const uint8_t device_id); diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp new file mode 100644 index 000000000..f96929153 --- /dev/null +++ b/src/web/WebSchedulerService.cpp @@ -0,0 +1,134 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2023 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "emsesp.h" + +namespace emsesp { + +using namespace std::placeholders; // for `_1` etc + +WebSchedulerService::WebSchedulerService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) + : _httpEndpoint(WebScheduler::read, WebScheduler::update, this, server, EMSESP_SCHEDULER_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED) + , _fsPersistence(WebScheduler::read, WebScheduler::update, this, fs, EMSESP_SCHEDULER_FILE) { +} + +// load the settings when the service starts +void WebSchedulerService::begin() { + _fsPersistence.readFromFS(); +} + +// this creates the scheduler file, saving it to the FS +void WebScheduler::read(WebScheduler & webScheduler, JsonObject & root) { + JsonArray schedule = root.createNestedArray("schedule"); + for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) { + JsonObject si = schedule.createNestedObject(); + si["active"] = scheduleItem.active; + si["flags"] = scheduleItem.flags; + si["time"] = scheduleItem.time; + si["e_min"] = scheduleItem.elapsed_min; + si["cmd"] = scheduleItem.cmd; + si["value"] = scheduleItem.value; + si["description"] = scheduleItem.description; + } +} + +// call on initialization and also when the page is saved via web UI +// this loads the data into the internal class +StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webScheduler) { +#ifdef EMSESP_STANDALONE + // invoke some fake data for testing + const char * json = + "{[{\"active\":true,\"flags\":31,\"time\": \"07:30\",\"cmd\": \"hc1/mode\",\"value\": \"day\",\"description\": \"turn on central heating\"}]}"; + StaticJsonDocument<500> doc; + deserializeJson(doc, json); + root = doc.as(); + Serial.println(COLOR_BRIGHT_MAGENTA); + Serial.print("Using custom file: "); + serializeJson(root, Serial); + Serial.println(COLOR_RESET); +#endif + + webScheduler.scheduleItems.clear(); + + if (root["schedule"].is()) { + for (const JsonObject schedule : root["schedule"].as()) { + // create each schedule item, overwriting any previous settings + // ignore the id (as this is only used in the web for table rendering) + auto si = ScheduleItem(); + si.active = schedule["active"]; + si.flags = schedule["flags"]; + si.time = schedule["time"].as(); + si.cmd = schedule["cmd"].as(); + si.value = schedule["value"].as(); + si.description = schedule["description"].as(); + + // calculated elapsed minutes + si.elapsed_min = Helpers::string2minutes(si.time); + + webScheduler.scheduleItems.push_back(si); // add to list + } + } + return StateUpdateResult::CHANGED; +} + +// process any scheduled jobs +void WebSchedulerService::loop() { + /* + + // quit if nothing in the webScheduler.scheduleItems list + if (!webScheduler.scheduleItems.count()) { + return; + } + + // trigger first on elapsed time and process any Timer events + // elapsed time is scheduleItems.elapsed_time and already calculated + + // next trigger on the minute and process all the Weekly events + + time_t now = time(nullptr); + tm * tm = localtime(&now); + static int last_min = 0; + + if (tm->tm_min == last_min || tm->tm_year < 122) { + return; + } + + last_min = tm->tm_min; + uint8_t dow = 1 << tm->tm_wday; // I used 0 for Monday but can change + uint16_t min = tm->tm_hour * 60 + tm->tm_min; + + for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) { + if ((dow & scheduleItem.flags) && min == scheduleItem.elapsed_min) { + StaticJsonDocument docin; + StaticJsonDocument docout; // only for commands without output + JsonObject in = docin.to(); + JsonObject out = docout.to(); + in["data"] = scheduleItem.value; + if (CommandRet::OK != Command::process(scheduleItem.cmd.c_str(), true, in, out)) { + EMSESP::logger().warning("Scheduled command %s with data %s failed", scheduleItem.cmd.c_str(), scheduleItem.value.c_str()); + } else { + EMSESP::logger().debug("Scheduled command %s with data %s executed", scheduleItem.cmd.c_str(), scheduleItem.value.c_str()); + } + } + } + + */ +} + + +} // namespace emsesp \ No newline at end of file diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h new file mode 100644 index 000000000..104e8bbf2 --- /dev/null +++ b/src/web/WebSchedulerService.h @@ -0,0 +1,66 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2023 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WebSchedulerService_h +#define WebSchedulerService_h + +#define EMSESP_SCHEDULER_FILE "/config/emsespScheduler.json" +#define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST + +namespace emsesp { + +class ScheduleItem { + public: + boolean active; + uint8_t flags; + uint16_t elapsed_min; // total mins from 00:00 + std::string time; // HH:MM + std::string cmd; + std::string value; + std::string description; +}; + +class WebScheduler { + public: + std::list scheduleItems; + + static void read(WebScheduler & webScheduler, JsonObject & root); + static StateUpdateResult update(JsonObject & root, WebScheduler & webScheduler); +}; + +class WebSchedulerService : public StatefulService { + public: + WebSchedulerService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + + void begin(); + void loop(); + +// make all functions public so we can test in the debug and standalone mode +#ifndef EMSESP_STANDALONE + private: +#endif + + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; + + // void endpointScheduler(AsyncWebServerRequest * request); // GET +}; + +} // namespace emsesp + +#endif \ No newline at end of file