From f1c5a911f946519e8eefb3c3c4aab6af94632ac5 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 21 Sep 2025 19:18:19 +0200 Subject: [PATCH] special checks for message command --- src/core/command.cpp | 13 ++- src/test/test.cpp | 171 +++++++++++++++++++++++++---- src/test/test.h | 3 +- src/web/WebCustomEntityService.cpp | 15 ++- 4 files changed, 167 insertions(+), 35 deletions(-) diff --git a/src/core/command.cpp b/src/core/command.cpp index 28c296ab4..b5f6a4611 100644 --- a/src/core/command.cpp +++ b/src/core/command.cpp @@ -143,7 +143,8 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec } // check if data is entity like device/hc/name/value - if (data.is()) { + // unless the command is system/message + if ((strcmp(command_p, "message") != 0) && data.is()) { const char * d = data.as(); if (strlen(d)) { char * device_end = (char *)strchr(d, '/'); @@ -183,9 +184,9 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec } } - // call the command based on the type uint8_t return_code = CommandRet::OK; + // call the command based on the type if (data.is()) { return_code = Command::call(device_type, command_p, data.as(), is_admin, id_n, output); } else if (data.is()) { @@ -289,7 +290,7 @@ const char * Command::parse_command_string(const char * command, int8_t & id) { return command; } -// check if command contains an attribute +// check if command string contains an attribute and returns it const char * Command::get_attribute(const char * cmd) { char * breakp = (char *)strchr(cmd, '/'); if (breakp) { @@ -299,9 +300,9 @@ const char * Command::get_attribute(const char * cmd) { return nullptr; } -// returns the attribute in the given JSON object as a key/value pair called api_data +// get the attribute in the given JSON object as a key/value pair called api_data // or errors if the attribute is not found -bool Command::set_attribute(JsonObject output, const char * cmd, const char * attribute) { +bool Command::get_attribute(JsonObject output, const char * cmd, const char * attribute) { if (attribute == nullptr) { return true; } @@ -325,7 +326,7 @@ bool Command::set_attribute(JsonObject output, const char * cmd, const char * at char error[100]; snprintf(error, sizeof(error), "no attribute '%s' in %s", attribute, cmd); output["message"] = error; - return false; + return false; // fail } // calls a command directly diff --git a/src/test/test.cpp b/src/test/test.cpp index 8ba43900a..77609c8bd 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -327,10 +327,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // add devices test("general"); - EMSESP::webCustomEntityService.test(); // custom entities - EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS - EMSESP::temperaturesensor_.test(); // add temperature sensors - EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions + EMSESP::webCustomEntityService.load_test_data(); // custom entities + EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS + EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors + EMSESP::webSchedulerService.load_test_data(); // add scheduler data shell.invoke_command("show values"); ok = true; @@ -348,7 +348,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.printfln("Adding custom entities..."); // add some dummy entities - EMSESP::webCustomEntityService.test(); + EMSESP::webCustomEntityService.load_test_data(); #ifdef EMSESP_STANDALONE AsyncWebServerRequest request; @@ -372,15 +372,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const char data[] = "{\"value\":\"99\"}"; deserializeJson(doc, data); json = doc.as(); - // validate request.url("/api/custom/test_ram"); EMSESP::webAPIService.webAPIService(&request, json); + // validate by showing values request.method(HTTP_GET); request.url("/api/custom"); EMSESP::webAPIService.webAPIService(&request); - #endif ok = true; } @@ -389,7 +388,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.printfln("Adding Scheduler items..."); // add some dummy entities - EMSESP::webSchedulerService.test(); + EMSESP::webSchedulerService.load_test_data(); #ifdef EMSESP_STANDALONE AsyncWebServerRequest request; @@ -832,7 +831,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // load some EMS data // test("general"); - emsesp::EMSESP::temperaturesensor_.test(); + emsesp::EMSESP::temperaturesensor_.load_test_data(); shell.invoke_command("call temperaturesensor"); shell.invoke_command("show values"); @@ -857,7 +856,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const Mqtt::nested_format(1); // Mqtt::nested_format(0); - emsesp::EMSESP::temperaturesensor_.test(); + emsesp::EMSESP::temperaturesensor_.load_test_data(); shell.invoke_command("show values"); shell.invoke_command("call system publish"); @@ -879,7 +878,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // load some EMS data test("general"); - EMSESP::webCustomizationService.test(); // load the analog sensors + EMSESP::webCustomizationService.load_test_data(); // load the analog sensors shell.invoke_command("call analogsensor"); shell.invoke_command("show values"); @@ -1073,6 +1072,129 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const shell.invoke_command("call boiler circpump/value"); } + if (command == "shuntingyard") { + shell.printfln("Testing shunting yard..."); + + // load devices + test("general"); + + EMSESP::webCustomEntityService.load_test_data(); // custom entities + EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS + EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors + EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions + + JsonDocument doc; + AsyncWebServerRequest request; + + // request.method(HTTP_GET); + // request.url("/api/custom/test_seltemp/val"); + // EMSESP::webAPIService.webAPIService(&request); + + // // set a custom value + // request.method(HTTP_POST); + // char data0[] = "{\"value\":\"99\"}"; + // deserializeJson(doc, data0); + // JsonVariant json = doc.as(); + // request.url("/api/custom/test_seltemp"); + // EMSESP::webAPIService.webAPIService(&request, json); + + request.method(HTTP_POST); + request.url("/api/system/message"); + + // output: hello world + EMSESP::webAPIService.webAPIService(&request, "'hello world'"); + + // output: locale is en + EMSESP::webAPIService.webAPIService(&request, "'locale is 'system/settings/locale"); + + // output: locale is en + EMSESP::webAPIService.webAPIService(&request, "'locale is '(system/settings/locale)"); + + // output: rssi is -23 + EMSESP::webAPIService.webAPIService(&request, "'rssi is '0+system/network/rssi"); + + // output: rssi is -23 dBm + EMSESP::webAPIService.webAPIService(&request, "'rssi is '(system/network/rssi)' dBm'"); + + // output: 14 + EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp"); + + // output: 14 + EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp/value"); + + // output: seltemp=14 + EMSESP::webAPIService.webAPIService(&request, "'seltemp='custom/test_seltemp/value"); + + // output: 14 + EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp"); + + // output: 40 + EMSESP::webAPIService.webAPIService(&request, "boiler/flowtempoffset"); + + // output: 40 + EMSESP::webAPIService.webAPIService(&request, "boiler/flowtempoffset/value"); + + // output: 53.8 + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp1/value"); + + // output: -67.8 + EMSESP::webAPIService.webAPIService(&request, "(custom/test_seltemp - boiler/flowtempoffset) * 2.8 + 5"); + + // output: 1 + EMSESP::webAPIService.webAPIService(&request, "thermostat/hc1/modetype == 'comfort'"); + + // output: 1 + EMSESP::webAPIService.webAPIService(&request, "thermostat/hc1/modetype == 'Comfort'"); + + // output: 0 + EMSESP::webAPIService.webAPIService(&request, "thermostat/hc1/modetype == 'unknown'"); + + // output: 53.8 + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp1/value"); + + // check when entity has no value, should pass (storagetemp2 has no value set) + // output: 1 (true) + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2 == \"\""); + + // check when entity has no value, should pass (storagetemp2 has no value set) + // output: 1 (true) + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2 == ''"); + + // + // these next tests should fail or give warnings or strange results, but not crash + // + + // Output is "comfort" == Comfort (because missing closing quote) + EMSESP::webAPIService.webAPIService(&request, "'thermostat/hc1/modetype == 'Comfort'"); + + // can't find entity, should fail with no entity 'storagetemp' in boiler + // output: "" + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp/value1"); + + // can't find entity, should fail with no attribute 'value1' in storagetemp1 + // output: "" + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp1/value1"); + + // check when entity has no value, should pass (storagetemp2 has no value set) + // output: "" + EMSESP::webAPIService.webAPIService(&request, "boiler/storagetemp2/value"); + + // can't set empty value! + EMSESP::webAPIService.webAPIService(&request, "/api/custom/test_seltemp/val"); + + // this should work + // output: 14 + EMSESP::webAPIService.webAPIService(&request, "custom/test_seltemp"); + + // test HTTP POST to call HA script + // test_cmd = "{\"method\":\"POST\",\"url\":\"http://192.168.1.42:8123/api/services/script/test_notify2\", \"header\":{\"authorization\":\"Bearer " + // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhMmNlYWI5NDgzMmI0ODE2YWQ2NzU4MjkzZDE2YWMxZSIsImlhdCI6MTcyMTM5MTI0NCwiZXhwIjoyMDM2NzUxMjQ0fQ." + // "S5sago1tEI6lNhrDCO0dM_WsVQHkD_laAjcks8tWAqo\"}}"; + // command("test99", test_cmd.c_str(), ""); + + ok = true; + } + if (command == "api3") { shell.printfln("Testing API getting values from system"); EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR @@ -1081,8 +1203,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const bool single; - single = true; - // single = false; + // single = true; + single = false; AsyncWebServerRequest request; JsonDocument doc; @@ -1096,10 +1218,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const if (single) { // run dedicated tests only - // EMSESP::webCustomEntityService.test(); // custom entities - // EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS - // EMSESP::temperaturesensor_.test(); // add temperature sensors - // EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions + // EMSESP::webCustomEntityService.load_test_data(); // custom entities + // EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS + // EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors + // EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions // request.url("/rest/deviceEntities"); // EMSESP::webCustomizationService.device_entities(&request); @@ -1134,6 +1256,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const deserializeJson(doc, data0); request.url("/api/thermostat/seltemp"); EMSESP::webAPIService.webAPIService(&request, doc.as()); + // request.method(HTTP_GET); // request.url("/api/thermostat/seltemp/value"); // EMSESP::webAPIService.webAPIService(&request); @@ -1201,10 +1324,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // shell.invoke_command("call system read \"8 2 27 1\""); } else { - EMSESP::webCustomEntityService.test(); // custom entities - EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS - EMSESP::temperaturesensor_.test(); // add temperature sensors - EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions + EMSESP::webCustomEntityService.load_test_data(); // custom entities + EMSESP::webCustomizationService.load_test_data(); // set customizations - this will overwrite any settings in the FS + EMSESP::temperaturesensor_.load_test_data(); // add temperature sensors + EMSESP::webSchedulerService.load_test_data(); // run scheduler tests, and conditions request.method(HTTP_GET); @@ -1249,7 +1372,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); request.url("/api/custom/info"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/custom/seltemp"); + request.url("/api/custom/test_seltemp"); EMSESP::webAPIService.webAPIService(&request); // system @@ -1358,9 +1481,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); // custom - request.url("/api/custom/seltemp2"); + request.url("/api/custom/test_seltemp2"); EMSESP::webAPIService.webAPIService(&request); - request.url("/api/custom/seltemp/val"); + request.url("/api/custom/test_seltemp/val"); EMSESP::webAPIService.webAPIService(&request); // temperaturesensor diff --git a/src/test/test.h b/src/test/test.h index 67fd370dc..952730d6a 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -61,7 +61,8 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "heat_exchange" // #define EMSESP_DEBUG_DEFAULT "ls" // #define EMSESP_DEBUG_DEFAULT "upload" -#define EMSESP_DEBUG_DEFAULT "hpmode" +// #define EMSESP_DEBUG_DEFAULT "hpmode" +#define EMSESP_DEBUG_DEFAULT "shuntingyard" #ifndef EMSESP_DEBUG_DEFAULT #define EMSESP_DEBUG_DEFAULT "general" diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index 4e5aaec4b..d10b18c5a 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -153,6 +153,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web // set value by api command bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) { + // don't write if there is no value, to prevent setting an empty value by mistake when parsing attributes + if (!strlen(value)) { + EMSESP::logger().debug("can't set empty value!"); + return false; + } + for (CustomEntityItem & entityItem : *customEntityItems_) { if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) { if (entityItem.ram == 1) { @@ -318,7 +324,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) for (auto & entity : *customEntityItems_) { if (Helpers::toLower(entity.name) == cmd) { get_value_json(output, entity); - return Command::set_attribute(output, cmd, attribute_s); + return Command::get_attribute(output, cmd, attribute_s); } } return false; // not found @@ -681,7 +687,7 @@ bool WebCustomEntityService::get_value(std::shared_ptr telegram) // hard coded tests // add the entity and also add the command for writeable entities #ifdef EMSESP_TEST -void WebCustomEntityService::test() { +void WebCustomEntityService::load_test_data() { update([&](WebCustomEntity & webCustomEntity) { webCustomEntity.customEntityItems.clear(); auto entityItem = CustomEntityItem(); @@ -698,6 +704,7 @@ void WebCustomEntityService::test() { entityItem.value_type = 1; entityItem.writeable = true; entityItem.data = "70"; + entityItem.value = 70; webCustomEntity.customEntityItems.push_back(entityItem); Command::add( EMSdevice::DeviceType::CUSTOM, @@ -751,12 +758,12 @@ void WebCustomEntityService::test() { entityItem.type_id = 0; entityItem.offset = 0; entityItem.factor = 1; - entityItem.name = "seltemp"; + entityItem.name = "test_seltemp"; entityItem.uom = 0; entityItem.value_type = 8; entityItem.writeable = true; entityItem.data = "14"; - entityItem.value = 12; + entityItem.value = 14; webCustomEntity.customEntityItems.push_back(entityItem); Command::add( EMSdevice::DeviceType::CUSTOM,