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["active"] = scheduleItem.active;
|
||||||
si["flags"] = scheduleItem.flags;
|
si["flags"] = scheduleItem.flags;
|
||||||
si["time"] = scheduleItem.time;
|
si["time"] = scheduleItem.time;
|
||||||
si["e_min"] = scheduleItem.elapsed_min;
|
|
||||||
si["cmd"] = scheduleItem.cmd;
|
si["cmd"] = scheduleItem.cmd;
|
||||||
si["value"] = scheduleItem.value;
|
si["value"] = scheduleItem.value;
|
||||||
si["description"] = scheduleItem.description;
|
si["description"] = scheduleItem.description;
|
||||||
@@ -84,6 +83,7 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche
|
|||||||
|
|
||||||
// calculated elapsed minutes
|
// calculated elapsed minutes
|
||||||
si.elapsed_min = Helpers::string2minutes(si.time);
|
si.elapsed_min = Helpers::string2minutes(si.time);
|
||||||
|
si.retry_cnt = 0xFF; // no starup retries
|
||||||
|
|
||||||
webScheduler.scheduleItems.push_back(si); // add to list
|
webScheduler.scheduleItems.push_back(si); // add to list
|
||||||
}
|
}
|
||||||
@@ -91,68 +91,89 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche
|
|||||||
return StateUpdateResult::CHANGED;
|
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
|
// process any scheduled jobs
|
||||||
// checks on the minute
|
// checks on the minute
|
||||||
void WebSchedulerService::loop() {
|
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
|
// get list of scheduler events and exit if it's empty
|
||||||
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
|
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
|
||||||
if (scheduleItems->size() == 0) {
|
if (scheduleItems->size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
time_t now = time(nullptr);
|
||||||
tm * tm = localtime(&now);
|
tm * tm = localtime(&now);
|
||||||
static int last_min = 100; // some high marker
|
if (tm->tm_min != last_tm_min && tm->tm_year > 120) {
|
||||||
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
|
// find the real dow and minute from NTP or RTC
|
||||||
uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday
|
uint8_t real_dow = 1 << tm->tm_wday; // 1 is Sunday
|
||||||
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
|
uint16_t real_min = tm->tm_hour * 60 + tm->tm_min;
|
||||||
|
|
||||||
bool has_NTP = tm->tm_year > 120;
|
|
||||||
|
|
||||||
for (const ScheduleItem & scheduleItem : *scheduleItems) {
|
for (const ScheduleItem & scheduleItem : *scheduleItems) {
|
||||||
if (scheduleItem.active
|
if (scheduleItem.active && (real_dow & scheduleItem.flags) && real_min == scheduleItem.elapsed_min) {
|
||||||
&& ((has_NTP && (real_dow & scheduleItem.flags) && real_min == scheduleItem.elapsed_min) // day of week scheduling - Weekly
|
command(scheduleItem.cmd, scheduleItem.value);
|
||||||
|| (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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
last_tm_min = tm->tm_min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace emsesp
|
} // namespace emsesp
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST
|
#define EMSESP_SCHEDULER_SERVICE_PATH "/rest/schedule" // GET and POST
|
||||||
|
|
||||||
#define SCHEDULEFLAG_SCHEDULE_TIMER 0x80 // 7th bit for Timer
|
#define SCHEDULEFLAG_SCHEDULE_TIMER 0x80 // 7th bit for Timer
|
||||||
|
#define MAX_STARTUP_RETRIES 3 // retry the statup commands x times
|
||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ class ScheduleItem {
|
|||||||
std::string cmd;
|
std::string cmd;
|
||||||
std::string value;
|
std::string value;
|
||||||
std::string description;
|
std::string description;
|
||||||
|
uint8_t retry_cnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebScheduler {
|
class WebScheduler {
|
||||||
@@ -52,6 +54,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
|||||||
|
|
||||||
void begin();
|
void begin();
|
||||||
void loop();
|
void loop();
|
||||||
|
bool command(std::string cmd, std::string data);
|
||||||
|
|
||||||
// make all functions public so we can test in the debug and standalone mode
|
// make all functions public so we can test in the debug and standalone mode
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
|
|||||||
Reference in New Issue
Block a user