diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 11b8cd41d..a7e39dc21 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -22,6 +22,7 @@ - timeout for remote thermostat emulation [#1680](https://github.com/emsesp/EMS-ESP32/discussions/1680), [#1774](https://github.com/emsesp/EMS-ESP32/issues/1774) - CR120 thermostat as own model() [#1779](https://github.com/emsesp/EMS-ESP32/discussions/1779) - Modules - external linkable module library [#1778](https://github.com/emsesp/EMS-ESP32/issues/1778) +- Scheduler onChange and Conditions [#1806](https://github.com/emsesp/EMS-ESP32/issues/1806) ## Fixed diff --git a/platformio.ini b/platformio.ini index 6c7dca081..3262c6ed7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -195,7 +195,7 @@ build_flags = [env:espressi32_v3] platform = espressif32 platform_packages= - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.0-rc2 + platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1 platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1 framework = arduino board = esp32dev diff --git a/src/command.cpp b/src/command.cpp index 143f6644c..fbd132a8f 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -322,7 +322,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * if (single_command) { // exception 1: anything that is from System // exception 2: boiler coldshot command - bool get_attributes = (!cf || !cf->cmdfunction_json_) && (device_type > EMSdevice::DeviceType::SYSTEM) && (strcmp(cmd, F_(coldshot)) != 0); + bool get_attributes = (!cf || !cf->cmdfunction_json_) && (strcmp(cmd, F_(coldshot)) != 0); if (get_attributes) { LOG_DEBUG("Calling %s command '%s' to retrieve attributes", dname, cmd); diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 88a74195f..aabaa64bc 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -771,6 +771,10 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8 return webCustomEntityService.get_value_info(root, cmd); } + if (devicetype == DeviceType::SYSTEM) { + return system_.get_value_info(root, cmd); + } + char error[100]; snprintf(error, sizeof(error), "cannot find values for entity '%s'", cmd); root["message"] = error; diff --git a/src/system.cpp b/src/system.cpp index 9f9fea13c..d01ea90e0 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -1272,6 +1272,44 @@ bool System::saveSettings(const char * filename, const char * section, JsonObjec return false; // not found } +bool System::get_value_info(JsonObject root, const char * command) { + if (command == nullptr || strlen(command) == 0) { + LOG_ERROR("empty system command"); + return false; + } + char cmd[COMMAND_MAX_LENGTH]; + strlcpy(cmd, command, sizeof(cmd)); + char * val = strstr(cmd, "/value"); + if (val) { + val[0] = '\0'; + } + char * dash = strchr(cmd, '/'); + if (dash) { + *dash = '\0'; + dash++; + } + if (command_info("", 0, root)) { + std::string s; + if (dash && root[cmd].containsKey(dash)) { + s = root[cmd][dash].as(); + } else if (root.containsKey(cmd)) { + s = root[cmd].as(); + } + if (!s.empty()) { + root.clear(); + if (val) { + root["api_data"] = s; + } else { + root["value"] = s; + } + return true; + } + } + root.clear(); + LOG_ERROR("system command not found: %s from %s", cmd, command); + return false; +} + // export status information including the device information // http://ems-esp/api/system/info bool System::command_info(const char * value, const int8_t id, JsonObject output) { @@ -1452,13 +1490,13 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output // Settings node = output["Settings"].to(); EMSESP::webSettingsService.read([&](WebSettings & settings) { - node["board profile"] = settings.board_profile; - node["locale"] = settings.locale; - node["tx mode"] = settings.tx_mode; - node["ems bus id"] = settings.ems_bus_id; - node["shower timer"] = settings.shower_timer; - node["shower alert"] = settings.shower_alert; - node["shpwe_min_duration"] = settings.shower_min_duration; // seconds + node["board profile"] = settings.board_profile; + node["locale"] = settings.locale; + node["tx mode"] = settings.tx_mode; + node["ems bus id"] = settings.ems_bus_id; + node["shower timer"] = settings.shower_timer; + node["shower min duration"] = settings.shower_min_duration; // seconds + node["shower alert"] = settings.shower_alert; if (settings.shower_alert) { node["shower alert coldshot"] = settings.shower_alert_coldshot; // seconds node["shower alert trigger"] = settings.shower_alert_trigger; // minutes @@ -1503,7 +1541,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output obj["product id"] = emsdevice->product_id(); obj["version"] = emsdevice->version(); obj["entities"] = emsdevice->count_entities(); - char result[300]; + char result[500]; (void)emsdevice->show_telegram_handlers(result, sizeof(result), EMSdevice::Handlers::RECEIVED); if (result[0] != '\0') { obj["handlers received"] = result; // don't show handlers if there aren't any diff --git a/src/system.h b/src/system.h index 8ccbd4ee9..fff6e4afb 100644 --- a/src/system.h +++ b/src/system.h @@ -61,6 +61,7 @@ class System { static bool command_commands(const char * value, const int8_t id, JsonObject output); static bool command_response(const char * value, const int8_t id, JsonObject output); static bool command_allvalues(const char * value, const int8_t id, JsonObject output); + static bool get_value_info(JsonObject root, const char *cmd); #if defined(EMSESP_TEST) static bool command_test(const char * value, const int8_t id); diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 223c5cc17..dd331fcb7 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -369,7 +369,9 @@ bool WebSchedulerService::command(const char * cmd, const char * data) { bool WebSchedulerService::onChange(const char * cmd) { for (const ScheduleItem & scheduleItem : *scheduleItems_) { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE && Helpers::toLower(scheduleItem.time) == Helpers::toLower(cmd)) { +#ifdef EMESESP_DEBUG // emsesp::EMSESP::logger().debug(scheduleItem.cmd.c_str()); +#endif return command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str()); } } @@ -381,7 +383,7 @@ void WebSchedulerService::condition() { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) { auto match = compute(scheduleItem.time.c_str()); #ifdef EMESESP_DEBUG - emsesp::EMSESP::logger().debug("condition match: %s", match.c_str()); + // emsesp::EMSESP::logger().debug("condition match: %s", match.c_str()); #endif if (!match.empty() && match[0] == '1') { if (scheduleItem.retry_cnt == 0xFF) { // default unswitched diff --git a/src/web/shuntingYard.hpp b/src/web/shuntingYard.hpp index ef06f8bf2..10e2fa08e 100644 --- a/src/web/shuntingYard.hpp +++ b/src/web/shuntingYard.hpp @@ -303,16 +303,27 @@ std::deque shuntingYard(const std::deque & tokens) { return queue; } +bool isnum(const std::string & s) { + if (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos)) { + return true; + } + return false; +} + + // replace commands like "//" with its value" std::string commands(std::string & expr) { for (uint8_t device = 0; device < emsesp::EMSdevice::DeviceType::UNKNOWN; device++) { const char * d = emsesp::EMSdevice::device_type_2_device_name(device); auto f = expr.find(d); while (f != std::string::npos) { - auto e = expr.find_first_of(" )=<>|&+-*", f); + auto e = expr.find_first_of(")=<>|&+-*!", f); if (e == std::string::npos) { e = expr.length(); } + while (e > 0 && expr[e - 1] == ' ') { // remove blanks from end + e--; + } char cmd[COMMAND_MAX_LENGTH]; size_t l = e - f; if (l >= sizeof(cmd) - 1) { @@ -330,8 +341,7 @@ std::string commands(std::string & expr) { emsesp::Command::process(cmd_s.c_str(), true, input, output); if (output.containsKey("api_data")) { std::string data = output["api_data"].as(); - // set strings in quotations for something like "3-way-valve" - if (isdigit(data[0] == 0 && data[0] != '-')) { + if (!isnum(data)) { data.insert(data.begin(), '"'); data.insert(data.end(), '"'); } @@ -346,7 +356,7 @@ std::string commands(std::string & expr) { return expr; } -int islogic(const std::string & s) { +int to_logic(const std::string & s) { if (s[0] == '1' || s == "on" || s == "ON" || s == "true") { return 1; } @@ -356,15 +366,16 @@ int islogic(const std::string & s) { return 0; } -bool isnum(const std::string & s) { - if (isdigit(s[0]) || (s[0] == '-' && isdigit(s[1]))) { - return true; +std::string to_string(double d) { + if (d == static_cast(d)) { + return std::to_string(static_cast(d)); } - return false; + return std::to_string(d); } std::string compute(const std::string & expr) { - auto expr_new = emsesp::Helpers::toLower(expr); + auto expr_new = expr; //emsesp::Helpers::toLower(expr); + // emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str()); commands(expr_new); // emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str()); const auto tokens = exprToTokens(expr_new); @@ -399,10 +410,10 @@ std::string compute(const std::string & expr) { if (!isnum(rhs)) { return ""; } - stack.push_back(std::to_string(-1 * std::stod(rhs))); + stack.push_back(to_string(-1 * std::stod(rhs))); break; case '!': - stack.push_back(islogic(rhs) == 0 ? "1" : "0"); + stack.push_back(to_logic(rhs) == 0 ? "1" : "0"); break; } @@ -468,9 +479,9 @@ std::string compute(const std::string & expr) { if (stack.size() < 2) { return ""; } - const auto rhs = islogic(stack.back()); + const auto rhs = to_logic(stack.back()); stack.pop_back(); - const auto lhs = islogic(stack.back()); + const auto lhs = to_logic(stack.back()); stack.pop_back(); switch (token.str[0]) { default: @@ -502,22 +513,22 @@ std::string compute(const std::string & expr) { return ""; break; case '^': - stack.push_back(std::to_string(pow(lhs, rhs))); + stack.push_back(to_string(pow(lhs, rhs))); break; case '*': - stack.push_back(std::to_string(lhs * rhs)); + stack.push_back(to_string(lhs * rhs)); break; case '/': - stack.push_back(std::to_string(lhs / rhs)); + stack.push_back(to_string(lhs / rhs)); break; case '%': stack.push_back(std::to_string(static_cast(lhs) % static_cast(rhs))); break; case '+': - stack.push_back(std::to_string(lhs + rhs)); + stack.push_back(to_string(lhs + rhs)); break; case '-': - stack.push_back(std::to_string(lhs - rhs)); + stack.push_back(to_string(lhs - rhs)); break; } } break;