mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
schedule commands timing, retry
This commit is contained in:
@@ -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<EMSESP_JSON_SIZE_SMALL> doc_input;
|
||||
JsonObject input = doc_input.to<JsonObject>();
|
||||
if (data.length()) {
|
||||
input["data"] = data;
|
||||
}
|
||||
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc_output; // only for commands without output
|
||||
JsonObject output = doc_output.to<JsonObject>();
|
||||
|
||||
// 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<EMSESP_JSON_SIZE_SMALL> doc_input;
|
||||
JsonObject input = doc_input.to<JsonObject>();
|
||||
input["data"] = scheduleItem.value;
|
||||
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc_output; // only for commands without output
|
||||
JsonObject output = doc_output.to<JsonObject>();
|
||||
|
||||
// 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
|
||||
@@ -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<WebScheduler> {
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user