diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 11de8eaed..821a44efc 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -7,6 +7,7 @@ - new device WATER shows dhw entities from MM100 and SM100 in dhw setting - renamed WWC to DHW, always create DHW nests/topics, remove ww prefix from mqtt names [#1634](https://github.com/emsesp/EMS-ESP32/issues/1634) - change temperaturesensor id to underscore +- system/info API command has the word 'Info' removed from the object name for each section (E.g. 'Network Info' is now just 'Network') ## Added @@ -28,6 +29,7 @@ - added extra pump characteristics (mode and pressure for EMS+) by @SLTKA [#1802](https://github.com/emsesp/EMS-ESP32/pull/1802) - allow device name to be customized [#1174](https://github.com/emsesp/EMS-ESP32/issues/1174) - Modbus support by @mheyse [#1744](https://github.com/emsesp/EMS-ESP32/issues/1744) +- System Message command [#1854](https://github.com/emsesp/EMS-ESP32/issues/1854) ## Fixed diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 88f51e63d..440b0f43b 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -715,6 +715,7 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int return true; // found a match, exit } } + return false; // not found } diff --git a/src/command.cpp b/src/command.cpp index 773e0fa17..1c9cbb2d3 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -296,11 +296,25 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * if (cmd == nullptr) { return CommandRet::NOT_FOUND; } - uint8_t return_code = CommandRet::OK; - auto dname = EMSdevice::device_type_2_device_name(device_type); + auto dname = EMSdevice::device_type_2_device_name(device_type); // device name, not translated + + // check first if there is a command given as it may be calling a device's attribute (e.g. /api/boiler/nrgheat) + bool single_command = (!value || !strlen(value)); + if (single_command) { + if (EMSESP::get_device_value_info(output, cmd, id, device_type)) { // entity = cmd + LOG_DEBUG("Fetched device entity attributes for %s/%s", dname, cmd); + return CommandRet::OK; + } else { + // char error[100]; + // snprintf(error, sizeof(error), "no data for device %s", dname); + // output["message"] = error; + } + } + uint8_t device_id = EMSESP::device_id_from_cmd(device_type, cmd, id); + // determine flags based on id (which is the tag) uint8_t flag = CommandFlag::CMD_FLAG_DEFAULT; int8_t tag = id; if (tag >= DeviceValueTAG::TAG_HC1 && tag <= DeviceValueTAG::TAG_HC8) { @@ -312,31 +326,15 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * } else if (tag >= DeviceValueTAG::TAG_AHS1 && tag <= DeviceValueTAG::TAG_AHS1) { flag = CommandFlag::CMD_FLAG_AHS; } - // see if there is a command registered + + // first see if there is a command registered and it's valid auto cf = find_command(device_type, device_id, cmd, flag); - - // check if its a call to an end-point of a device - // this is used to fetch the attributes of the device entity, or call a command directly - // for example info, values, commands, etc - bool single_command = (!value || !strlen(value)); - if (single_command) { - // exception: boiler coldshot command - 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); - return EMSESP::get_device_value_info(output, cmd, id, device_type) ? CommandRet::OK : CommandRet::ERROR; // entity = cmd - } - } - - // check if we have a matching command if (!cf) { - // we didn't find the command, report error LOG_WARNING("Command failed: invalid command '%s'", cmd ? cmd : ""); - return message(CommandRet::NOT_FOUND, "invalid command", output); + return CommandRet::ERROR; } - // check permissions and abort if not authorized + // before calling the command, check permissions and abort if not authorized if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !is_admin) { LOG_WARNING("Command failed: authentication failed"); output["message"] = "authentication failed"; @@ -353,7 +351,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * } else { snprintf(info_s, sizeof(info_s), "'%s/%s'", dname, cmd); } - if ((value == nullptr) || (strlen(value) == 0)) { + if (single_command) { LOG_DEBUG(("%sCalling command %s"), ro.c_str(), info_s); } else { if (id > 0) { @@ -364,6 +362,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * } // call the function based on type, either with a json package or no parameters + uint8_t return_code = CommandRet::OK; if (cf->cmdfunction_json_) { // JSON return_code = ((cf->cmdfunction_json_)(value, id, output)) ? CommandRet::OK : CommandRet::ERROR; @@ -378,7 +377,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * // report back. If not OK show output from error, other return the HTTP code if (return_code != CommandRet::OK) { - if ((value == nullptr) || (strlen(value) == 0)) { + if (single_command) { LOG_ERROR("Command '%s' failed with error '%s'", cmd, FL_(cmdRet)[return_code]); } else { LOG_ERROR("Command '%s: %s' failed with error '%s'", cmd, value, FL_(cmdRet)[return_code]); diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 3153f94b1..e2c209fa1 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -100,7 +100,7 @@ const char * EMSdevice::brand_to_char() { } } -// returns the short name of the device, used in MQTT and console commands, all lowercase +// returns the short name of the device, used in MQTT and console commands, all lowercase, no translated const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) { switch (device_type) { case DeviceType::SYSTEM: diff --git a/src/emsesp.cpp b/src/emsesp.cpp index d99c7d7d1..60aa232dc 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -740,7 +740,7 @@ void EMSESP::publish_response(std::shared_ptr telegram) { buffer = nullptr; } -// builds json with the detail of each value, for an EMS device +// builds json with the detail of each value, for a given EMS device // for other types like sensors, scheduler, custom entities it will process single commands like 'info', 'values', 'commands'... bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8_t id, const uint8_t devicetype) { // check first for EMS devices @@ -752,7 +752,7 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8 } } - // temperaturesensor + // temperature sensor if (devicetype == DeviceType::TEMPERATURESENSOR) { return temperaturesensor_.get_value_info(root, cmd, id); } @@ -772,15 +772,12 @@ bool EMSESP::get_device_value_info(JsonObject root, const char * cmd, const int8 return webCustomEntityService.get_value_info(root, cmd); } + // system 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; - - return false; + return false; // not found } // search for recognized device_ids : Me, All, otherwise print hex value diff --git a/src/locale_common.h b/src/locale_common.h index ec8cf4153..f2d55f275 100644 --- a/src/locale_common.h +++ b/src/locale_common.h @@ -67,6 +67,7 @@ MAKE_WORD(publish) MAKE_WORD(board_profile) MAKE_WORD(setvalue) MAKE_WORD(service) +MAKE_WORD(message) // for commands MAKE_WORD(call) diff --git a/src/locale_translations.h b/src/locale_translations.h index 6ee851ee6..8e1790cfa 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -75,6 +75,7 @@ MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entit MAKE_WORD_TRANSLATION(commands_response, "get response", "Hole Antwort", "Verzoek om antwoord", "", "uzyskaj odpowiedź", "", "", "gelen cevap", "", "získať odpoveď") // TODO translate MAKE_WORD_TRANSLATION(coldshot_cmd, "send a cold shot of water", "", "", "", "uruchom tryśnięcie zimnej wody", "", "", "soğuk su gönder", "", "pošlite studenú dávku vody") // TODO translate MAKE_WORD_TRANSLATION(allvalues_cmd, "output all values", "", "", "", "wyświetl wszystkie wartości", "", "", "", "", "vypísať všetky hodnoty") // TODO translate +MAKE_WORD_TRANSLATION(message_cmd, "send message", "", "", "", "", "", "", "", "", "") // TODO translate // tags MAKE_WORD_TRANSLATION(tag_hc1, "hc1", "HK1", "hc1", "VK1", "OG1", "hc1", "hc1", "ID1", "hc1", "hc1") diff --git a/src/system.cpp b/src/system.cpp index 412cc4d3e..1319770e6 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -226,6 +226,18 @@ bool System::command_syslog_level(const char * value, const int8_t id) { } */ +// send message - to log and MQTT +bool System::command_message(const char * value, const int8_t id) { + if (value == nullptr || value[0] == '\0') { + return false; // must have a string value + } + + LOG_INFO("Message: %s", value); + Mqtt::queue_publish(F_(message), value); + + return true; +} + // watch bool System::command_watch(const char * value, const int8_t id) { uint8_t w = 0xff; @@ -831,10 +843,10 @@ void System::commands_init() { Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY); Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY); - // restart and watch (and test) are also exposed as Console commands + // restart, watch, message (and test) are also exposed as Console commands Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY); Command::add(EMSdevice::DeviceType::SYSTEM, F_(watch), System::command_watch, FL_(watch_cmd)); - + Command::add(EMSdevice::DeviceType::SYSTEM, F_(message), System::command_message, FL_(message_cmd)); #if defined(EMSESP_TEST) Command::add(EMSdevice::DeviceType::SYSTEM, ("test"), System::command_test, FL_(test_cmd)); #endif @@ -1107,7 +1119,7 @@ bool System::check_restore() { #ifndef EMSESP_STANDALONE // see if we have a temp file, if so try and read it - // TODO find a nicer way to see if a file exists without reporting an error + // TODO find a nicer way to see if a file exists without reporting an error, like using lfs_stat. exists() uses open so same problem. File new_file = LittleFS.open(TEMP_FILENAME_PATH); if (new_file) { JsonDocument jsonDocument; @@ -1301,7 +1313,7 @@ bool System::get_value_info(JsonObject root, const char * command) { } if (command_info("", 0, root)) { std::string s; - // Loop through all the key-value pairs in root to find the key case independent + // Loop through all the key-value pairs in root to find the key, case independent if (dash) { // search the nest first for (JsonPair p : root) { if (p.value().is() && Helpers::toLower(p.key().c_str()) == cmd) { @@ -1331,9 +1343,10 @@ bool System::get_value_info(JsonObject root, const char * command) { return true; } } + root.clear(); - LOG_ERROR("system command '%s' not found", command); - return false; + + return false; // not found } // export status information including the device information @@ -1342,7 +1355,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output JsonObject node; // System - node = output["System Info"].to(); + node = output["System"].to(); node["version"] = EMSESP_APP_VERSION; node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); node["uptime (seconds)"] = uuid::get_uptime_sec(); @@ -1361,7 +1374,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output #ifndef EMSESP_STANDALONE // Network Status - node = output["Network Info"].to(); + node = output["Network"].to(); if (EMSESP::system_.ethernet_connected()) { node["network"] = "Ethernet"; node["hostname"] = ETH.getHostname(); @@ -1409,7 +1422,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output #endif // NTP status - node = output["NTP Info"].to(); + node = output["NTP"].to(); #ifndef EMSESP_STANDALONE node["NTP status"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected"; EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) { @@ -1421,7 +1434,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output #endif // MQTT Status - node = output["MQTT Info"].to(); + node = output["MQTT"].to(); node["MQTT status"] = Mqtt::connected() ? F_(connected) : F_(disconnected); if (Mqtt::enabled()) { node["MQTT publishes"] = Mqtt::publish_count(); @@ -1456,7 +1469,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output }); // Syslog Status - node = output["Syslog Info"].to(); + node = output["Syslog"].to(); node["enabled"] = EMSESP::system_.syslog_enabled_; #ifndef EMSESP_STANDALONE if (EMSESP::system_.syslog_enabled_) { @@ -1468,7 +1481,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output #endif // Sensor Status - node = output["Sensor Info"].to(); + node = output["Sensor"].to(); if (EMSESP::sensor_enabled()) { node["temperature sensors"] = EMSESP::temperaturesensor_.no_sensors(); node["temperature sensor reads"] = EMSESP::temperaturesensor_.reads(); @@ -1481,12 +1494,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output } // API Status - node = output["API Info"].to(); + node = output["API"].to(); node["API calls"] = WebAPIService::api_count(); node["API fails"] = WebAPIService::api_fails(); // EMS Bus Status - node = output["Bus Info"].to(); + node = output["Bus"].to(); switch (EMSESP::bus_status()) { case EMSESP::BUS_STATUS_OFFLINE: node["bus status"] = "disconnected"; diff --git a/src/system.h b/src/system.h index 7d250cdc4..d11206990 100644 --- a/src/system.h +++ b/src/system.h @@ -57,6 +57,7 @@ class System { static bool command_restart(const char * value, const int8_t id); static bool command_syslog_level(const char * value, const int8_t id); static bool command_watch(const char * value, const int8_t id); + static bool command_message(const char * value, const int8_t id); static bool command_info(const char * value, const int8_t id, JsonObject output); 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); diff --git a/src/test/test.cpp b/src/test/test.cpp index 3fec354fd..597741aa2 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -324,11 +324,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webSchedulerService.test(); // add scheduler items EMSESP::webCustomEntityService.test(); // add custom entities - shell.invoke_command("show devices"); + // shell.invoke_command("show devices"); // shell.invoke_command("show values"); // shell.invoke_command("call system allvalues"); // shell.invoke_command("call system publish"); // shell.invoke_command("show mqtt"); + shell.invoke_command("call boiler nrgheat"); ok = true; } @@ -974,6 +975,71 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService(&request); request.url("/api/boiler/flamecurr/bad"); EMSESP::webAPIService.webAPIService(&request); + + ok = true; + } + + if (command == "api2") { + shell.printfln("Testing API getting values"); + Mqtt::ha_enabled(false); + Mqtt::nested_format(1); + // Mqtt::send_response(false); + // EMSESP::bool_format(BOOL_FORMAT_10); // BOOL_FORMAT_10_STR + EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR + + test("boiler"); + test("thermostat"); + + AsyncWebServerRequest request; + JsonDocument doc; + JsonVariant json; + request.method(HTTP_GET); + + request.url("/api/boiler"); + EMSESP::webAPIService.webAPIService(&request); + + request.url("/api/boiler/comfort"); + EMSESP::webAPIService.webAPIService(&request); + + shell.invoke_command("call boiler comfort"); + shell.invoke_command("call boiler coldshot"); + + request.url("/api/boiler/values"); + EMSESP::webAPIService.webAPIService(&request); + + request.url("/api/boiler/flamecurr/bad"); + EMSESP::webAPIService.webAPIService(&request); + + shell.invoke_command("call system message test"); + + shell.invoke_command("call system message"); + + ok = true; + } + + if (command == "api3") { + shell.printfln("Testing API getting values"); + EMSESP::system_.bool_format(BOOL_FORMAT_TRUEFALSE); // BOOL_FORMAT_TRUEFALSE_STR + + // test("boiler"); + // test("thermostat"); + + AsyncWebServerRequest request; + JsonDocument doc; + JsonVariant json; + request.method(HTTP_GET); + + request.url("/api/system"); + EMSESP::webAPIService.webAPIService(&request); + + request.url("/api/system/locale"); + EMSESP::webAPIService.webAPIService(&request); + + request.url("/api/system/locale/value"); + EMSESP::webAPIService.webAPIService(&request); + + shell.invoke_command("call system locale"); + ok = true; } diff --git a/src/test/test.h b/src/test/test.h index eee3e4f68..17d4b0005 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -41,6 +41,8 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "310" // #define EMSESP_DEBUG_DEFAULT "render" // #define EMSESP_DEBUG_DEFAULT "api" +// #define EMSESP_DEBUG_DEFAULT "api2" +// #define EMSESP_DEBUG_DEFAULT "api3" // #define EMSESP_DEBUG_DEFAULT "crash" // #define EMSESP_DEBUG_DEFAULT "dv" // #define EMSESP_DEBUG_DEFAULT "lastcode" diff --git a/src/version.h b/src/version.h index bf4a09346..dbe4622bc 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.0-dev.23" +#define EMSESP_APP_VERSION "3.7.0-dev.24" diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index b787ddad8..233fe84a2 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -338,8 +338,7 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd) } } - output["message"] = "unknown command"; - return false; + return false; // not found } // publish single value diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index fb03487cb..3c9113cd3 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -205,8 +205,7 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) { return true; } - output["message"] = "unknown command"; - return false; + return false; // not found } // publish single value @@ -472,7 +471,7 @@ void WebSchedulerService::loop() { #if defined(EMSESP_TEST) void WebSchedulerService::test() { update([&](WebScheduler & webScheduler) { - webScheduler.scheduleItems.clear(); + // webScheduler.scheduleItems.clear(); // test 1 auto si = ScheduleItem(); si.active = true;