From 798e20a2666d67a58b6dedc23626e89f358c1d83 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 20 Oct 2024 15:02:11 +0200 Subject: [PATCH] Call read commands from Web #2116 --- CHANGELOG_LATEST.md | 1 + interface/src/app/status/SystemLog.tsx | 94 +++++++++++++++---- interface/src/i18n/cz/index.ts | 1 + interface/src/i18n/de/index.ts | 3 +- interface/src/i18n/en/index.ts | 3 +- interface/src/i18n/fr/index.ts | 5 +- interface/src/i18n/it/index.ts | 5 +- interface/src/i18n/nl/index.ts | 5 +- interface/src/i18n/no/index.ts | 5 +- interface/src/i18n/pl/index.ts | 5 +- interface/src/i18n/sk/index.ts | 5 +- interface/src/i18n/sv/index.ts | 5 +- interface/src/i18n/tr/index.ts | 5 +- mock-api/rest_server.ts | 120 +++++++++++++------------ src/console.cpp | 25 +++--- src/helpers.cpp | 10 +++ src/helpers.h | 5 +- src/locale_translations.h | 1 + src/system.cpp | 46 +++++++++- src/system.h | 12 +++ src/test/test.cpp | 22 +++-- src/version.h | 2 +- src/web/WebAPIService.cpp | 5 +- src/web/WebSettingsService.cpp | 6 +- src/web/WebSettingsService.h | 13 ++- 25 files changed, 282 insertions(+), 127 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index d131ad6c6..37bccce83 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -48,6 +48,7 @@ For more details go to [www.emsesp.org](https://www.emsesp.org/). - internal ESP32 temperature sensor on the S3 [#2077](https://github.com/emsesp/EMS-ESP32/issues/2077) - MQTT status topic (used in connect and last will) set to Retain [#2086](https://github.com/emsesp/EMS-ESP32/discussions/2086) - Czech language [2096](https://github.com/emsesp/EMS-ESP32/issues/2096) +- Developer Mode and send EMS Read Commands from WebUI [#2116](https://github.com/emsesp/EMS-ESP32/issues/2116) ## Fixed diff --git a/interface/src/app/status/SystemLog.tsx b/interface/src/app/status/SystemLog.tsx index 8028cb98e..34f4d2bd3 100644 --- a/interface/src/app/status/SystemLog.tsx +++ b/interface/src/app/status/SystemLog.tsx @@ -2,13 +2,24 @@ import { useEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; import DownloadIcon from '@mui/icons-material/GetApp'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import WarningIcon from '@mui/icons-material/Warning'; -import { Box, Button, Checkbox, MenuItem, TextField, styled } from '@mui/material'; +import { + Box, + Button, + Checkbox, + IconButton, + MenuItem, + TextField, + styled +} from '@mui/material'; import Grid from '@mui/material/Grid2'; +import { API } from 'api/app'; import { fetchLogES, readLogSettings, updateLogSettings } from 'api/system'; -import { useSSE } from 'alova/client'; +import { useRequest, useSSE } from 'alova/client'; +import type { APIcall } from 'app/main/types'; import { BlockFormControlLabel, BlockNavigation, @@ -80,9 +91,20 @@ const SystemLog = () => { update: updateLogSettings }); + const { send } = useRequest( + (data: string) => API({ device: 'system', cmd: 'read', id: 0, value: data }), + { + immediate: false + } + ); + + const [readValue, setReadValue] = useState(''); + const [readOpen, setReadOpen] = useState(false); const [logEntries, setLogEntries] = useState([]); const [autoscroll, setAutoscroll] = useState(true); + const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/; + const updateFormValue = updateValueDirty( origData, dirtyFlags, @@ -150,6 +172,17 @@ const SystemLog = () => { } }, [logEntries.length]); + const sendReadCommand = () => { + if (readValue === '') { + setReadOpen(!readOpen); + return; + } + void send(readValue); + console.log('send read command', readValue); // TODO remove + setReadOpen(false); + setReadValue(''); + }; + const content = () => { if (!data) { return ; @@ -163,7 +196,7 @@ const SystemLog = () => { name="level" label={LL.LOG_LEVEL()} value={data.level} - sx={{ width: '15ch' }} + sx={{ width: '10ch' }} variant="outlined" onChange={updateFormValue} margin="normal" @@ -218,24 +251,49 @@ const SystemLog = () => { label={LL.AUTO_SCROLL()} /> - - {dirtyFlags && dirtyFlags.length !== 0 && ( + - )} + {dirtyFlags && dirtyFlags.length !== 0 && ( + + )} + + + + {readOpen && ( + { + const value = event.target.value; + if (value !== '' && !ALPHA_NUMERIC_DASH_REGEX.test(value)) { + return; + } + setReadValue(value); + }} + focused={true} + label="Send Read command" + variant="outlined" + helperText=" [offset] [length]" + size="small" + /> + )} + + + + const & commands) { } }); + // read [offset] [length] commands->add_command(ShellContext::MAIN, CommandFlags::USER, {F_(read)}, string_vector{F_(deviceid_mandatory), F_(typeid_mandatory), F_(offset_optional), F_(length_optional)}, [=](Shell & shell, const std::vector & arguments) { - uint8_t device_id = Helpers::hextoint(arguments.front().c_str()); + // loop through arguments and add to data as text, separated by a space + std::string data; + for (const auto & arg : arguments) { + if (!data.empty()) { + data += " "; + } + data += arg; + } - if (!EMSESP::valid_device(device_id)) { + if (!System::readCommand(data.c_str())) { shell.printfln("Invalid deviceID"); return; } - - uint16_t type_id = Helpers::hextoint(arguments[1].c_str()); - if (arguments.size() == 4) { - uint16_t offset = Helpers::hextoint(arguments[2].c_str()); - uint8_t length = Helpers::hextoint(arguments.back().c_str()); - EMSESP::send_read_request(type_id, device_id, offset, length, true); - } else if (arguments.size() == 3) { - uint16_t offset = Helpers::hextoint(arguments.back().c_str()); - EMSESP::send_read_request(type_id, device_id, offset, 0, true); - } else { - EMSESP::send_read_request(type_id, device_id, 0, 0, true); - } - EMSESP::set_read_id(type_id); }); commands->add_command(ShellContext::MAIN, diff --git a/src/helpers.cpp b/src/helpers.cpp index 2d8f9a76c..2ae936012 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -834,4 +834,14 @@ float Helpers::numericoperator2scalefactor(int8_t numeric_operator) { return -numeric_operator; } +// convert the data into a vector of strings +void Helpers::splitArguments(const char * data, std::vector & arguments) { + std::stringstream ss(data); + std::string item; + + while (std::getline(ss, item, ' ')) { + arguments.push_back(item); + } +} + } // namespace emsesp diff --git a/src/helpers.h b/src/helpers.h index e33142995..d7a9a6039 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -19,8 +19,9 @@ #ifndef EMSESP_HELPERS_H #define EMSESP_HELPERS_H -#include "telegram.h" // for EMS_VALUE_* settings +#include +#include "telegram.h" // for EMS_VALUE_* settings #include "common.h" namespace emsesp { @@ -82,6 +83,8 @@ class Helpers { static const char * translated_word(const char * const * strings, const bool force_en = false); + static void splitArguments(const char * data, std::vector & arguments); + #ifdef EMSESP_STANDALONE static char * ultostr(char * ptr, uint32_t value, const uint8_t base); #endif diff --git a/src/locale_translations.h b/src/locale_translations.h index 5312a1813..4f37f85f7 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -63,6 +63,7 @@ MAKE_WORD_TRANSLATION(info_cmd, "list all values (verbose)", "Liste aller Werte" MAKE_WORD_TRANSLATION(commands_cmd, "list all commands", "Liste aller Kommandos", "lijst van alle commando's", "", "wyświetl wszystkie komendy", "Viser alle kommandoer", "", "Tüm komutları listele", "elencaa tutti i comandi", "zobraziť všetky príkazy") // TODO translate MAKE_WORD_TRANSLATION(entities_cmd, "list all entities", "Liste aller Entitäten", "lijst van alle entiteiten", "", "wyświetl wszsytkie encje", "Viser alle enheter", "", "Tüm varlıkları listele", "elenca tutte le entità", "zobraziť všetky entity") // TODO translate MAKE_WORD_TRANSLATION(send_cmd, "send a telegram", "Sende EMS-Telegramm", "stuur een telegram", "", "wyślij telegram", "send et telegram", "", "Bir telegram gönder", "invia un telegramma", "poslať telegram") // TODO translate +MAKE_WORD_TRANSLATION(read_cmd, "send read request", "", "", "", "", "", "", "", "", "") // TODO translate MAKE_WORD_TRANSLATION(setiovalue_cmd, "set I/O value", "Setze Werte E/A", "instellen standaardwaarde", "", "ustaw wartość", "sett en io verdi", "", "Giriş/Çıkış değerlerini ayarla", "imposta valore io", "nastaviť hodnotu io") // TODO translate MAKE_WORD_TRANSLATION(changeloglevel_cmd, "change log level", "Ändere Protokollebene", "aanpassen log niveau", "", "zmień poziom log-u", "endre loggnivå", "", "Kayıt seviyesini değiştir", "cambia livello registrazione", "zmeniť úroveň protokolu") // TODO translate MAKE_WORD_TRANSLATION(fetch_cmd, "refresh all EMS values", "Aktualisiere alle EMS-Werte", "Verversen alle EMS waardes", "", "odśwież wszystkie wartości EMS", "oppfrisk alle EMS verdier", "", "Bütün EMS değerlerini yenile", "aggiornare tutti i valori EMS", "obnoviť všetky hodnoty EMS") // TODO translate diff --git a/src/system.cpp b/src/system.cpp index 7b3c0adc1..01d84c887 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -409,7 +409,8 @@ void System::reload_settings() { eth_phy_addr_ = settings.eth_phy_addr; eth_clock_mode_ = settings.eth_clock_mode; - locale_ = settings.locale; + locale_ = settings.locale; + developer_mode_ = settings.developer_mode; }); } @@ -851,6 +852,7 @@ void System::system_check() { // commands - takes static function pointers // can be called via Console using 'call system ' void System::commands_init() { + Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), System::command_read, FL_(read_cmd), CommandFlag::ADMIN_ONLY); 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); Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY); @@ -1689,6 +1691,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output #endif node["modbusEnabled"] = settings.modbus_enabled; node["forceHeatingOff"] = settings.boiler_heatingoff; + node["developerMode"] = settings.developer_mode; }); // Devices - show EMS devices if we have any @@ -2012,4 +2015,45 @@ bool System::uploadFirmwareURL(const char * url) { return true; // OK } +// read command, e.g. read [offset] [length] from console +// or a call system read [offset] [length] from the API +bool System::readCommand(const char * data) { + // convert the data into a vector of strings + std::vector arguments = {}; + Helpers::splitArguments(data, arguments); + + auto num_args = arguments.size(); + + if (num_args > 4 || num_args == 0) { + return false; + } + + uint8_t device_id = Helpers::hextoint(arguments[0].c_str()); + if (!EMSESP::valid_device(device_id)) { + LOG_ERROR("Invalid device ID for read command"); + return false; // invalid device + } + + uint16_t type_id = Helpers::hextoint(arguments[1].c_str()); + uint8_t length = 0; + uint16_t offset = 0; + + if (num_args == 4) { + offset = Helpers::hextoint(arguments[2].c_str()); + length = Helpers::hextoint(arguments[3].c_str()); + } else if (num_args == 3) { + offset = Helpers::hextoint(arguments.back().c_str()); + } + + EMSESP::send_read_request(type_id, device_id, offset, length, true); + EMSESP::set_read_id(type_id); + + return true; +} + +// system read command +bool System::command_read(const char * value, const int8_t id) { + return readCommand(value); +} + } // namespace emsesp diff --git a/src/system.h b/src/system.h index 3bf59eef7..ffc620a3f 100644 --- a/src/system.h +++ b/src/system.h @@ -63,6 +63,7 @@ class System { void loop(); // commands + static bool command_read(const char * value, const int8_t id); static bool command_send(const char * value, const int8_t id); static bool command_publish(const char * value, const int8_t id); static bool command_fetch(const char * value, const int8_t id); @@ -124,6 +125,8 @@ class System { static bool is_valid_gpio(uint8_t pin, bool has_psram = false); static bool load_board_profile(std::vector & data, const std::string & board_profile); + static bool readCommand(const char * data); + static void restart_requested(bool restart_requested) { restart_requested_ = restart_requested; } @@ -170,6 +173,14 @@ class System { readonly_mode_ = readonly_mode; } + bool developer_mode() { + return developer_mode_; + } + + void developer_mode(bool developer_mode) { + developer_mode_ = developer_mode; + } + // Boolean Format API/MQTT uint8_t bool_format() { return bool_format_; @@ -398,6 +409,7 @@ class System { uint16_t modbus_port_; uint8_t modbus_max_clients_; uint32_t modbus_timeout_; + bool developer_mode_; // ethernet uint8_t phy_type_; diff --git a/src/test/test.cpp b/src/test/test.cpp index 50e33aec5..b17a696f1 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -973,16 +973,16 @@ 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.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 // request.url("/rest/deviceEntities"); // EMSESP::webCustomizationService.device_entities(&request); - request.url("/rest/dashboardData"); - EMSESP::webDataService.dashboard_data(&request); + // request.url("/rest/dashboardData"); + // EMSESP::webDataService.dashboard_data(&request); // COMMANDS // shell.invoke_command("call system fetch"); @@ -999,7 +999,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // EMSESP::webAPIService.webAPIService(&request); // POST COMMANDS - // request.method(HTTP_POST); + request.method(HTTP_POST); // char data1[] = "{\"device\":\"system\", \"cmd\":\"restart\",\"id\":-1}"; // deserializeJson(doc, data1); @@ -1021,6 +1021,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // request.url("/rest/action"); // EMSESP::webStatusService.action(&request, doc.as()); + char data6[] = "{\"device\":\"system\", \"cmd\":\"read\",\"value\":\"8 2 27 1\"}"; + deserializeJson(doc, data6); + json = doc.as(); + request.url("/api"); + EMSESP::webAPIService.webAPIService(&request, json); + + shell.invoke_command("call system read \"8 2 27 1\""); + } else { EMSESP::webCustomEntityService.test(); // custom entities diff --git a/src/version.h b/src/version.h index 82448f289..3b42266e5 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.7.0-dev.46" \ No newline at end of file +#define EMSESP_APP_VERSION "3.7.0-dev.47" \ No newline at end of file diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index 063ca114f..4dd87e2b3 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -28,8 +28,9 @@ WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * security server->on(EMSESP_API_SERVICE_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { webAPIService(request, json); }); } -// POST|GET /{device} -// POST|GET /{device}/{entity} +// POST|GET api/ +// POST|GET api/{device} +// POST|GET api/{device}/{entity} void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant json) { JsonObject input; // if no body then treat it as a secure GET diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 6be813006..769f3bf71 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -80,6 +80,7 @@ void WebSettings::read(WebSettings & settings, JsonObject root) { root["modbus_port"] = settings.modbus_port; root["modbus_max_clients"] = settings.modbus_max_clients; root["modbus_timeout"] = settings.modbus_timeout; + root["developer_mode"] = settings.developer_mode; } // call on initialization and also when settings are updated via web or console @@ -358,9 +359,12 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) { settings.fahrenheit = root["fahrenheit"]; EMSESP::system_.fahrenheit(settings.fahrenheit); - settings.readonly_mode = root["readonly_mode"]; + settings.readonly_mode = root["readonly_mode"] | false; EMSESP::system_.readonly_mode(settings.readonly_mode); + settings.developer_mode = root["developer_mode"] | false; + EMSESP::system_.developer_mode(settings.developer_mode); + settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT; EMSESP::system_.bool_dashboard(settings.bool_dashboard); diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index ed20485b3..57a2a00c8 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -65,10 +65,13 @@ class WebSettings { uint8_t bool_format; uint8_t bool_dashboard; uint8_t enum_format; - int8_t weblog_level; - uint8_t weblog_buffer; - bool weblog_compact; - bool fahrenheit; + + int8_t weblog_level; + uint8_t weblog_buffer; + bool weblog_compact; + + bool fahrenheit; + bool modbus_enabled; uint16_t modbus_port; uint8_t modbus_max_clients; @@ -79,6 +82,8 @@ class WebSettings { uint8_t eth_phy_addr; uint8_t eth_clock_mode; + bool developer_mode; // developer mode + static void read(WebSettings & settings, JsonObject root); static StateUpdateResult update(JsonObject root, WebSettings & settings);