scheduler async, command use string, lowercase

This commit is contained in:
MichaelDvP
2024-07-20 18:21:26 +02:00
parent d35dd1a9c4
commit c21c0b5dd1
9 changed files with 95 additions and 98 deletions

View File

@@ -204,7 +204,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
return return_code; return return_code;
} }
std::string Command::return_code_string(const uint8_t return_code) { const char * Command::return_code_string(const uint8_t return_code) {
switch (return_code) { switch (return_code) {
case CommandRet::ERROR: case CommandRet::ERROR:
return "Error"; return "Error";
@@ -380,9 +380,9 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
if (return_code != CommandRet::OK) { if (return_code != CommandRet::OK) {
char error[100]; char error[100];
if (single_command) { if (single_command) {
snprintf(error, sizeof(error), "Command '%s' failed (%s)", cmd, FL_(cmdRet)[return_code]); snprintf(error, sizeof(error), "Command '%s' failed (%s)", cmd, return_code_string(return_code));
} else { } else {
snprintf(error, sizeof(error), "Command '%s: %s' failed (%s)", cmd, value, FL_(cmdRet)[return_code]); snprintf(error, sizeof(error), "Command '%s: %s' failed (%s)", cmd, value, return_code_string(return_code));
} }
output.clear(); output.clear();
output["message"] = error; output["message"] = error;

View File

@@ -51,8 +51,6 @@ enum CommandRet : uint8_t {
INVALID // 5 - invalid (tag) INVALID // 5 - invalid (tag)
}; };
MAKE_ENUM_FIXED(cmdRet, "fail", "ok", "not found", "error", "not allowed", "invalid")
using cmd_function_p = std::function<bool(const char * data, const int8_t id)>; using cmd_function_p = std::function<bool(const char * data, const int8_t id)>;
using cmd_json_function_p = std::function<bool(const char * data, const int8_t id, JsonObject output)>; using cmd_json_function_p = std::function<bool(const char * data, const int8_t id, JsonObject output)>;
@@ -138,7 +136,7 @@ class Command {
static const char * parse_command_string(const char * command, int8_t & id); static const char * parse_command_string(const char * command, int8_t & id);
static std::string return_code_string(const uint8_t return_code); static const char * return_code_string(const uint8_t return_code);
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -1719,7 +1719,6 @@ void EMSESP::loop() {
analogsensor_.loop(); // read analog sensor values analogsensor_.loop(); // read analog sensor values
publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue
mqtt_.loop(); // sends out anything in the MQTT queue mqtt_.loop(); // sends out anything in the MQTT queue
webSchedulerService.loop(); // handle any scheduled jobs
webModulesService.loop(); // loop through the external library modules webModulesService.loop(); // loop through the external library modules
// force a query on the EMS devices to fetch latest data at a set interval (1 min) // force a query on the EMS devices to fetch latest data at a set interval (1 min)

View File

@@ -440,9 +440,9 @@ ModbusMessage Modbus::handleWrite(const ModbusMessage & request) {
sizeof(error), sizeof(error),
"Modbus write command failed with error: %s (%s)", "Modbus write command failed with error: %s (%s)",
(const char *)output["message"], (const char *)output["message"],
Command::return_code_string(return_code).c_str()); Command::return_code_string(return_code));
} else { } else {
snprintf(error, sizeof(error), "Modbus write command failed with error code (%s)", Command::return_code_string(return_code).c_str()); snprintf(error, sizeof(error), "Modbus write command failed with error code (%s)", Command::return_code_string(return_code));
} }
LOG_ERROR(error); LOG_ERROR(error);
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_VALUE); response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_VALUE);

View File

@@ -285,13 +285,9 @@ void Mqtt::on_message(const char * topic, const uint8_t * payload, size_t len) {
if (return_code != CommandRet::OK) { if (return_code != CommandRet::OK) {
char error[100]; char error[100];
if (output.size()) { if (output.size()) {
snprintf(error, snprintf(error, sizeof(error), "MQTT command failed with error %s (%s)", (const char *)output["message"], Command::return_code_string(return_code));
sizeof(error),
"MQTT command failed with error %s (%s)",
(const char *)output["message"],
Command::return_code_string(return_code).c_str());
} else { } else {
snprintf(error, sizeof(error), "MQTT command failed with error %s", Command::return_code_string(return_code).c_str()); snprintf(error, sizeof(error), "MQTT command failed with error %s", Command::return_code_string(return_code));
} }
LOG_ERROR(error); LOG_ERROR(error);
Mqtt::queue_publish("response", error); Mqtt::queue_publish("response", error);

View File

@@ -259,7 +259,7 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
// write debug // write debug
if (return_code != CommandRet::OK) { if (return_code != CommandRet::OK) {
EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str()); EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code));
} else { } else {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Write command successful"); EMSESP::logger().debug("Write command successful");
@@ -292,7 +292,7 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar
return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<float>(), 1), true, id, output); return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as<float>(), 1), true, id, output);
} }
if (return_code != CommandRet::OK) { if (return_code != CommandRet::OK) {
EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code).c_str()); EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code));
} else { } else {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Write command successful"); EMSESP::logger().debug("Write command successful");

View File

@@ -38,6 +38,7 @@ void WebSchedulerService::begin() {
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s/#", F_(scheduler)); snprintf(topic, sizeof(topic), "%s/#", F_(scheduler));
Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, topic, nullptr); // use empty function callback Mqtt::subscribe(EMSdevice::DeviceType::SCHEDULER, topic, nullptr); // use empty function callback
xTaskCreate((TaskFunction_t)scheduler_task, "scheduler_task", 4096, NULL, 3, NULL);
} }
// this creates the scheduler file, saving it to the FS // this creates the scheduler file, saving it to the FS
@@ -330,17 +331,17 @@ bool WebSchedulerService::has_commands() {
} }
// execute scheduled command // execute scheduled command
bool WebSchedulerService::command(const char * name, const char * cmd, const char * data) { bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
std::string cmd = Helpers::toLower(command);
// check http commands. e.g. // check http commands. e.g.
// tasmota(get): http://<tasmotsIP>/cm?cmnd=power%20ON // tasmota(get): http://<tasmotsIP>/cm?cmnd=power%20ON
// shelly(get): http://<shellyIP>/relais/0?turn=on // shelly(get): http://<shellyIP>/relais/0?turn=on
const char * c = strchr(cmd, '{');
if (c) {
// parse json // parse json
JsonDocument doc; JsonDocument doc;
int httpResult = 0; if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
if (DeserializationError::Ok == deserializeJson(doc, c)) {
HTTPClient http; HTTPClient http;
int httpResult = 0;
String url = doc["url"]; String url = doc["url"];
if (http.begin(url)) { if (http.begin(url)) {
// It's an HTTP call // It's an HTTP call
@@ -349,23 +350,18 @@ bool WebSchedulerService::command(const char * name, const char * cmd, const cha
for (JsonPair p : doc["header"].as<JsonObject>()) { for (JsonPair p : doc["header"].as<JsonObject>()) {
http.addHeader(p.key().c_str(), p.value().as<String>().c_str()); http.addHeader(p.key().c_str(), p.value().as<String>().c_str());
} }
String value = doc["value"] | data; // extract value if its in the command, or take the data String value = doc["value"] | data.c_str(); // extract value if its in the command, or take the data
String method = doc["method"] | "GET"; // default GET String method = doc["method"] | "GET"; // default GET
// if there is data, force a POST // if there is data, force a POST
if (value.length()) { if (value.length() || method == "post") { // we have all lowercase
if (value.startsWith("{")) { if (value.startsWith("{")) {
http.addHeader("Content-Type", "application/json"); // auto-set to JSON http.addHeader("Content-Type", "application/json"); // auto-set to JSON
} }
httpResult = http.POST(value); httpResult = http.POST(value);
} else {
// no value, but check if it still a POST request
if (method == "POST") {
httpResult = http.POST(value);
} else { } else {
httpResult = http.GET(); // normal GET httpResult = http.GET(); // normal GET
} }
}
http.end(); http.end();
@@ -383,12 +379,12 @@ bool WebSchedulerService::command(const char * name, const char * cmd, const cha
#endif #endif
return true; return true;
} }
} // we can add other json tests here
} }
JsonDocument doc_input; doc.clear();
JsonObject input = doc_input.to<JsonObject>(); JsonObject input = doc.to<JsonObject>();
if (strlen(data)) { // empty data queries a value if (!data.empty()) { // empty data queries a value
input["data"] = data; input["data"] = data;
} }
@@ -397,15 +393,15 @@ bool WebSchedulerService::command(const char * name, const char * cmd, const cha
// prefix "api/" to command string // prefix "api/" to command string
char command_str[COMMAND_MAX_LENGTH]; char command_str[COMMAND_MAX_LENGTH];
snprintf(command_str, sizeof(command_str), "/api/%s", cmd); snprintf(command_str, sizeof(command_str), "/api/%s", cmd.c_str());
uint8_t return_code = Command::process(command_str, true, input, output); // admin set uint8_t return_code = Command::process(command_str, true, input, output); // admin set
if (return_code == CommandRet::OK) { if (return_code == CommandRet::OK) {
#if defined(EMSESP_DEBUG) #if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Schedule command '%s' with data '%s' was successful", cmd, data); EMSESP::logger().debug("Schedule command '%s' with data '%s' was successful", cmd.c_str(), data.c_str());
#endif #endif
if (strlen(data) == 0 && output.size()) { if (data.empty() && output.size()) {
Mqtt::queue_publish("response", output); Mqtt::queue_publish("response", output);
} }
return true; return true;
@@ -416,7 +412,7 @@ bool WebSchedulerService::command(const char * name, const char * cmd, const cha
// check for empty name // check for empty name
snprintf(error, sizeof(error), "Schedule %s: %s", name ? name : "", (const char *)output["message"]); // use error message if we have it snprintf(error, sizeof(error), "Schedule %s: %s", name ? name : "", (const char *)output["message"]); // use error message if we have it
} else { } else {
snprintf(error, sizeof(error), "Schedule %s: command %s failed with error %s", name, cmd, Command::return_code_string(return_code).c_str()); snprintf(error, sizeof(error), "Schedule %s: command %s failed with error %s", name, cmd.c_str(), Command::return_code_string(return_code));
} }
emsesp::EMSESP::logger().warning(error); emsesp::EMSESP::logger().warning(error);
@@ -426,17 +422,8 @@ bool WebSchedulerService::command(const char * name, const char * cmd, const cha
#include "shuntingYard.hpp" #include "shuntingYard.hpp"
bool WebSchedulerService::onChange(const char * cmd) { bool WebSchedulerService::onChange(const char * cmd) {
bool cmd_ok = false; cmd_changed_.push_back(Helpers::toLower(cmd));
for (const ScheduleItem & scheduleItem : *scheduleItems_) { return true;
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE
&& Helpers::toLower(scheduleItem.time).find(Helpers::toLower(cmd)) != std::string::npos) {
#ifdef EMESESP_DEBUG
// emsesp::EMSESP::logger().debug(scheduleItem.cmd.c_str());
#endif
cmd_ok |= command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str());
}
}
return cmd_ok;
} }
void WebSchedulerService::condition() { void WebSchedulerService::condition() {
@@ -447,7 +434,7 @@ void WebSchedulerService::condition() {
// emsesp::EMSESP::logger().debug("condition match: %s", match.c_str()); // emsesp::EMSESP::logger().debug("condition match: %s", match.c_str());
#endif #endif
if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) { if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) {
scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()) ? 1 : 0xFF; scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value)) ? 1 : 0xFF;
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) { } else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
scheduleItem.retry_cnt = 0xFF; scheduleItem.retry_cnt = 0xFF;
} else if (match.length() != 1) { // the match is not boolean } else if (match.length() != 1) { // the match is not boolean
@@ -458,7 +445,6 @@ void WebSchedulerService::condition() {
} }
// process any scheduled jobs // process any scheduled jobs
// checks on the minute and at startup
void WebSchedulerService::loop() { void WebSchedulerService::loop() {
// initialize static value on startup // initialize static value on startup
static int8_t last_tm_min = -2; // invalid value also used for startup commands static int8_t last_tm_min = -2; // invalid value also used for startup commands
@@ -470,6 +456,17 @@ void WebSchedulerService::loop() {
return; return;
} }
// check if we have onChange events
while (!cmd_changed_.empty()) {
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE
&& Helpers::toLower(scheduleItem.time).find(cmd_changed_.front()) != std::string::npos) {
command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
}
}
cmd_changed_.pop_front();
}
// check conditions every 10 seconds // check conditions every 10 seconds
uint32_t uptime_sec = uuid::get_uptime_sec() / 10; uint32_t uptime_sec = uuid::get_uptime_sec() / 10;
if (last_uptime_sec != uptime_sec) { if (last_uptime_sec != uptime_sec) {
@@ -481,7 +478,7 @@ void WebSchedulerService::loop() {
if (last_tm_min == -2) { if (last_tm_min == -2) {
for (ScheduleItem & scheduleItem : *scheduleItems_) { for (ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()) ? 0xFF : 0; scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value)) ? 0xFF : 0;
} }
} }
last_tm_min = -1; // startup done, now use for RTC last_tm_min = -1; // startup done, now use for RTC
@@ -494,13 +491,12 @@ void WebSchedulerService::loop() {
// retry startup commands not yet executed // retry startup commands not yet executed
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0 if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) { && scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
scheduleItem.retry_cnt = scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, scheduleItem.value) ? 0xFF : scheduleItem.retry_cnt + 1;
command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : scheduleItem.retry_cnt + 1;
} }
// scheduled timer commands // scheduled timer commands
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0 if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
&& (uptime_min % scheduleItem.elapsed_min == 0)) { && (uptime_min % scheduleItem.elapsed_min == 0)) {
command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()); command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
} }
} }
last_uptime_min = uptime_min; last_uptime_min = uptime_min;
@@ -517,13 +513,23 @@ void WebSchedulerService::loop() {
for (const ScheduleItem & scheduleItem : *scheduleItems_) { for (const ScheduleItem & scheduleItem : *scheduleItems_) {
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags; uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) { if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
command(scheduleItem.name.c_str(), scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()); command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
} }
} }
last_tm_min = tm->tm_min; last_tm_min = tm->tm_min;
} }
} }
void WebSchedulerService::scheduler_task(void * pvParameters) {
while (1) {
delay(100); // no need to hurry
if (!EMSESP::system_.upload_status()) {
EMSESP::webSchedulerService.loop();
}
}
vTaskDelete(NULL);
}
// hard coded tests // hard coded tests
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
void WebSchedulerService::test() { void WebSchedulerService::test() {

View File

@@ -73,7 +73,9 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
private: private:
#endif #endif
bool command(const char * name, const char * cmd, const char * data); static void scheduler_task(void * pvParameters);
bool command(const char * name, const std::string & cmd, const std::string & data);
void condition(); void condition();
HttpEndpoint<WebScheduler> _httpEndpoint; HttpEndpoint<WebScheduler> _httpEndpoint;
@@ -81,6 +83,7 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
std::list<ScheduleItem> * scheduleItems_; // pointer to the list of schedule events std::list<ScheduleItem> * scheduleItems_; // pointer to the list of schedule events
bool ha_registered_ = false; bool ha_registered_ = false;
std::deque<std::string> cmd_changed_;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -622,19 +622,14 @@ std::string compute(const std::string & expr) {
String method = doc["method"] | "GET"; // default GET String method = doc["method"] | "GET"; // default GET
// if there is data, force a POST // if there is data, force a POST
if (value.length()) { if (value.length() || method == "post") {
if (value.startsWith("{")) { if (value.startsWith("{")) {
http.addHeader("Content-Type", "application/json"); // auto-set to JSON http.addHeader("Content-Type", "application/json"); // auto-set to JSON
} }
httpResult = http.POST(value); httpResult = http.POST(value);
} else {
// no value, but check if it still a POST request
if (method == "POST") {
httpResult = http.POST(value);
} else { } else {
httpResult = http.GET(); // normal GET httpResult = http.GET(); // normal GET
} }
}
http.end(); http.end();
if (httpResult > 0) { if (httpResult > 0) {