diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 52df936e2..a12b05782 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -44,7 +44,6 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject & root) { 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; @@ -84,6 +83,7 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche // calculated elapsed minutes si.elapsed_min = Helpers::string2minutes(si.time); + si.retry_cnt = 0xFF; // no starup retries webScheduler.scheduleItems.push_back(si); // add to list } @@ -91,68 +91,89 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche return StateUpdateResult::CHANGED; } +// execute scheduled command +bool WebSchedulerService::command(std::string cmd, std::string data) { + StaticJsonDocument doc_input; + JsonObject input = doc_input.to(); + if (data.length()) { + input["data"] = data; + } + + StaticJsonDocument doc_output; // only for commands without output + JsonObject output = doc_output.to(); + + // prefix "api/" to command string + auto command_str = "/api/" + cmd; + uint8_t return_code = Command::process(command_str.c_str(), true, input, output); // admin set + + if (return_code == CommandRet::OK) { + EMSESP::logger().debug("Scheduled command %s with data %s successfully", cmd.c_str(), data.c_str()); + return true; + } + + char error[100]; + if (output.size()) { + snprintf(error, sizeof(error), "Scheduled command %s failed with error: %s (%s)", cmd.c_str(), (const char *)output["message"], Command::return_code_string(return_code).c_str()); + } else { + snprintf(error, sizeof(error), "Scheduled command %s failed with error code (%s)", cmd.c_str(), Command::return_code_string(return_code).c_str()); + } + emsesp::EMSESP::logger().err(error); + return false; +} + // process any scheduled jobs // checks on the minute void WebSchedulerService::loop() { + // initialize static value on startup + static int8_t last_tm_min = -1; // invalid value also used for startup commands + static uint32_t last_uptime_min = 0; + // get list of scheduler events and exit if it's empty EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; }); if (scheduleItems->size() == 0) { return; } - time_t now = time(nullptr); - tm * tm = localtime(&now); - static int last_min = 100; // some high marker - auto uptime_seconds = uuid::get_uptime_sec(); // sync to EMS-ESP clock - auto uptime_min = uptime_seconds / 60; // sync to EMS-ESP clock - - // check if we're on the minute - taking the EMS-ESP uptime - // with the exception of allowing the first pass (on boot) through - if (uptime_min == last_min || tm->tm_year < 122) { - return; - } - last_min = uptime_min; - - // find the real dow and minute from NTP or RTC - uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday - uint16_t real_min = tm->tm_hour * 60 + tm->tm_min; - - bool has_NTP = tm->tm_year > 120; - - for (const ScheduleItem & scheduleItem : *scheduleItems) { - if (scheduleItem.active - && ((has_NTP && (real_dow & scheduleItem.flags) && real_min == scheduleItem.elapsed_min) // day of week scheduling - Weekly - || (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0 && uptime_min == 0) // only on boot - || (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0 && (uptime_seconds % scheduleItem.elapsed_min == 0) - && uptime_min))) { // periodic - Timer, avoid first minute - StaticJsonDocument doc_input; - JsonObject input = doc_input.to(); - input["data"] = scheduleItem.value; - - StaticJsonDocument doc_output; // only for commands without output - JsonObject output = doc_output.to(); - - // prefix "api/" to command string - auto command_str = "/api/" + scheduleItem.cmd; - uint8_t return_code = Command::process(command_str.c_str(), true, input, output); // admin set - - if (return_code != CommandRet::OK) { - char error[100]; - if (output.size()) { - snprintf(error, - sizeof(error), - "Call failed with error: %s (%s)", - (const char *)output["message"], - Command::return_code_string(return_code).c_str()); - } else { - snprintf(error, sizeof(error), "Call failed with error code (%s)", Command::return_code_string(return_code).c_str()); - } - emsesp::EMSESP::logger().err(error); - } else { - EMSESP::logger().debug("Scheduled command %s with data %s successfully", scheduleItem.cmd.c_str(), scheduleItem.value.c_str()); + // check startup commands + if (last_tm_min == -1) { + for (ScheduleItem & scheduleItem : *scheduleItems) { + if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) { + scheduleItem.retry_cnt = command(scheduleItem.cmd, scheduleItem.value) ? 0xFF : 0; } } + last_tm_min = 0; // startup done, now use for RTC + } + // check timer every minute, sync to EMS-ESP clock + uint32_t uptime_min = uuid::get_uptime_sec() / 60; + if (last_uptime_min != uptime_min || last_uptime_min == -1) { + for (ScheduleItem & scheduleItem : *scheduleItems) { + // retry startupcommands not yet executed + if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0 + && scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) { + scheduleItem.retry_cnt = command(scheduleItem.cmd, scheduleItem.value) ? 0xFF : scheduleItem.retry_cnt + 1; + } + // scheduled timer commands + if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0 + && (uptime_min % scheduleItem.elapsed_min == 0)) { + command(scheduleItem.cmd, scheduleItem.value); + } + } + last_uptime_min = uptime_min; + } + // check calender, sync to RTC, only execute if year is valid + time_t now = time(nullptr); + tm * tm = localtime(&now); + if (tm->tm_min != last_tm_min && tm->tm_year > 120) { + // find the real dow and minute from NTP or RTC + uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday + uint16_t real_min = tm->tm_hour * 60 + tm->tm_min; + + for (const ScheduleItem & scheduleItem : *scheduleItems) { + if (scheduleItem.active && (real_dow & scheduleItem.flags) && real_min == scheduleItem.elapsed_min) { + command(scheduleItem.cmd, scheduleItem.value); + } + } + last_tm_min = tm->tm_min; } } - } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebSchedulerService.h b/src/web/WebSchedulerService.h index 989092ab7..5784fafe4 100644 --- a/src/web/WebSchedulerService.h +++ b/src/web/WebSchedulerService.h @@ -23,6 +23,7 @@ #define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST #define SCHEDULEFLAG_SCHEDULE_TIMER 0x80 // 7th bit for Timer +#define MAX_STARTUP_RETRIES 3 // retry the statup commands x times namespace emsesp { @@ -36,6 +37,7 @@ class ScheduleItem { std::string cmd; std::string value; std::string description; + uint8_t retry_cnt; }; class WebScheduler { @@ -52,6 +54,7 @@ class WebSchedulerService : public StatefulService { void begin(); void loop(); + bool command(std::string cmd, std::string data); // make all functions public so we can test in the debug and standalone mode #ifndef EMSESP_STANDALONE