From cfe8235cee925780ddbbc0e6454a65b3ad244279 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 30 May 2024 18:16:31 +0200 Subject: [PATCH] add web for Modules --- interface/package.json | 2 +- interface/src/project/Modules.tsx | 11 ++++- interface/yarn.lock | 10 ++-- lib/framework/FSPersistence.h | 11 ++++- mock-api/rest_server.ts | 19 +++---- src/emsesp.cpp | 9 ++-- src/emsesp.h | 4 +- src/web/WebModulesService.cpp | 82 +++++++++++++++++++++++++++++++ src/web/WebModulesService.h | 50 +++++++++++++++++++ 9 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 src/web/WebModulesService.cpp create mode 100644 src/web/WebModulesService.h diff --git a/interface/package.json b/interface/package.json index f312babdb..3c5bf6560 100644 --- a/interface/package.json +++ b/interface/package.json @@ -30,7 +30,7 @@ "@mui/material": "^5.15.19", "@table-library/react-table-library": "4.1.7", "@types/lodash-es": "^4.17.12", - "@types/node": "^20.12.12", + "@types/node": "^20.12.13", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-router-dom": "^5.3.3", diff --git a/interface/src/project/Modules.tsx b/interface/src/project/Modules.tsx index 55affc530..9ca592cc6 100644 --- a/interface/src/project/Modules.tsx +++ b/interface/src/project/Modules.tsx @@ -141,6 +141,14 @@ const Modules: FC = () => { useLayoutTitle('Modules'); + if (modules.length === 0) { + return ( + + No modules detected + + ); + } + return ( { {blocker ? : null} - Activate or de-activate EMS-ESP library modules (** experimental **) + Activate or de-activate EMS-ESP library modules by selecting (** + experimental **) {renderModules()} diff --git a/interface/yarn.lock b/interface/yarn.lock index 91ff98290..e9c3fa42e 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1738,12 +1738,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.12.12": - version: 20.12.12 - resolution: "@types/node@npm:20.12.12" +"@types/node@npm:^20.12.13": + version: 20.12.13 + resolution: "@types/node@npm:20.12.13" dependencies: undici-types: "npm:~5.26.4" - checksum: 10c0/f374b763c744e8f16e4f38cf6e2c0eef31781ec9228c9e43a6f267880fea420fab0a238b59f10a7cb3444e49547c5e3785787e371fc242307310995b21988812 + checksum: 10c0/2ac92bb631dbddfb560eb3ba4eedbb1c688044a0130bc1ef032f5c0f20148ac7c9aa3c5aaa5a9787b6c4c6299847d754b96ee8c9def951481ba6628c46b683f5 languageName: node linkType: hard @@ -1974,7 +1974,7 @@ __metadata: "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@types/babel__core": "npm:^7" "@types/lodash-es": "npm:^4.17.12" - "@types/node": "npm:^20.12.12" + "@types/node": "npm:^20.12.13" "@types/react": "npm:^18.3.3" "@types/react-dom": "npm:^18.3.0" "@types/react-router-dom": "npm:^5.3.3" diff --git a/lib/framework/FSPersistence.h b/lib/framework/FSPersistence.h index 3ccf53a53..a7ad681a8 100644 --- a/lib/framework/FSPersistence.h +++ b/lib/framework/FSPersistence.h @@ -26,6 +26,11 @@ class FSPersistence { if (error == DeserializationError::Ok && jsonDocument.is()) { JsonObject jsonObject = jsonDocument.as(); _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); +#ifdef EMSESP_DEBUG + Serial.printf("Reading settings from %s ", _filePath); + serializeJson(jsonDocument, Serial); + Serial.println(); +#endif settingsFile.close(); return; } @@ -65,9 +70,11 @@ class FSPersistence { return false; } -// serialize the data to the file + // serialize the data to the file #ifdef EMSESP_DEBUG - Serial.println("Writing settings to " + String(_filePath)); + Serial.printf("Writing settings to %s ", _filePath); + serializeJson(jsonDocument, Serial); + Serial.println(); #endif serializeJson(jsonDocument, settingsFile); settingsFile.close(); diff --git a/mock-api/rest_server.ts b/mock-api/rest_server.ts index e8e0a4abe..8ee5a404a 100644 --- a/mock-api/rest_server.ts +++ b/mock-api/rest_server.ts @@ -2130,6 +2130,7 @@ let emsesp_schedule = { // SCHEDULE let emsesp_modules = { + // 'modules': [] "modules": [ { id: 1, @@ -2511,15 +2512,15 @@ router // Modules .post(EMSESP_MODULES_ENDPOINT, async (request: any) => { const content = await request.json(); - // TODO find the one and toggle it - console.log('modules saved', emsesp_modules); - /* - const dd_objIndex = dd.data.findIndex((obj: any) => obj.id.slice(2) === fullname); - if (dd_objIndex !== -1) { - let changed = new Boolean(false); - emsesp_modules = content; - - */ + let modules = content.modules; + for (let i = 0; i < modules.length; i++) { + const name = modules[i].name; + const objIndex = emsesp_modules.modules.findIndex((obj: any) => obj.name === name); + if (objIndex !== -1) { + emsesp_modules.modules[objIndex].enabled = modules[i].enabled; + } + } + console.log('modules updated', emsesp_modules); return status(200); }) .get(EMSESP_MODULES_ENDPOINT, () => emsesp_modules) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 0a1b2706d..01e55dfa4 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -37,12 +37,14 @@ WebSettingsService EMSESP::webSettingsService = WebSettingsService(&we WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); +WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &dummyFS, EMSESP::esp8266React.getSecurityManager()); #else ESP8266React EMSESP::esp8266React(&webServer, &LittleFS); WebSettingsService EMSESP::webSettingsService = WebSettingsService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); WebCustomizationService EMSESP::webCustomizationService = WebCustomizationService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); WebSchedulerService EMSESP::webSchedulerService = WebSchedulerService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); WebCustomEntityService EMSESP::webCustomEntityService = WebCustomEntityService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); +WebModulesService EMSESP::webModulesService = WebModulesService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager()); #endif WebActivityService EMSESP::webActivityService = WebActivityService(&webServer, EMSESP::esp8266React.getSecurityManager()); @@ -75,7 +77,6 @@ TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors Shower EMSESP::shower_; // Shower logic Preferences EMSESP::nvs_; // NV Storage -ModuleLibrary EMSESP::module_; // Module Library // static/common variables uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set @@ -1635,6 +1636,8 @@ void EMSESP::start() { analogsensor_.start(); // Analog external sensors webLogService.start(); // apply settings to weblog service + webModulesService.begin(); // setup the external library modules + // Load our library of known devices into stack mem. Names are stored in Flash memory device_library_ = { #include "device_library.h" @@ -1646,8 +1649,6 @@ void EMSESP::start() { #endif webServer.begin(); // start the web server - - module_.start(this); // setup the external library modules } // main loop calling all services @@ -1666,7 +1667,7 @@ void EMSESP::loop() { publish_all_loop(); // with HA messages in parts to avoid flooding the mqtt queue mqtt_.loop(); // sends out anything in the MQTT queue webSchedulerService.loop(); // handle any scheduled jobs - module_.loop(); // loop 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) scheduled_fetch_values(); diff --git a/src/emsesp.h b/src/emsesp.h index 71a1c87e6..bfc83c816 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -49,6 +49,7 @@ #include "web/WebAPIService.h" #include "web/WebLogService.h" #include "web/WebCustomEntityService.h" +#include "web/WebModulesService.h" #include "emsdevicevalue.h" #include "emsdevice.h" @@ -222,7 +223,6 @@ class EMSESP { static RxService rxservice_; static TxService txservice_; static Preferences nvs_; - static ModuleLibrary module_; // web controllers static ESP8266React esp8266React; @@ -235,6 +235,7 @@ class EMSESP { static WebCustomizationService webCustomizationService; static WebSchedulerService webSchedulerService; static WebCustomEntityService webCustomEntityService; + static WebModulesService webModulesService; private: static std::string device_tostring(const uint8_t device_id); @@ -280,7 +281,6 @@ class EMSESP { #endif protected: - // EMSESP(); static uuid::log::Logger logger_; }; diff --git a/src/web/WebModulesService.cpp b/src/web/WebModulesService.cpp new file mode 100644 index 000000000..b9cc9f350 --- /dev/null +++ b/src/web/WebModulesService.cpp @@ -0,0 +1,82 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2024 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "emsesp.h" +#include "WebModulesService.h" + +namespace emsesp { + +ModuleLibrary moduleLibrary; // Module Library +EMSESP * emsesp_; // forward declaration + +WebModulesService::WebModulesService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) + : _httpEndpoint(WebModules::read, WebModules::update, this, server, EMSESP_MODULES_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED) + , _fsPersistence(WebModules::read, WebModules::update, this, fs, EMSESP_MODULES_FILE) { +} + +// load the settings when the service starts +void WebModulesService::begin() { + EMSESP::logger().info("Starting Modules service"); + moduleLibrary.start(emsesp_); + + _fsPersistence.readFromFS(); // read the file from FS +} + +void WebModulesService::loop() { + moduleLibrary.loop(); // loop the external library modules +} + +// this creates the modules file, saving it to the FS +// and also calls when the Modules web page is refreshed +void WebModules::read(WebModules & webModules, JsonObject root) { + // EMSESP::logger().debug("module read called"); // TODO remove + + JsonDocument doc_modules; + JsonObject root_modules = doc_modules.to(); + moduleLibrary.list(root_modules); // get list the external library modules, put in root json object + + JsonArray modules = root["modules"].to(); + uint8_t counter = 0; + for (const JsonObject module : root_modules["modules"].as()) { + JsonObject mi = modules.add(); + mi["id"] = counter++; // id is only used to render the table and must be unique + mi["name"] = module["name"].as(); + mi["author"] = module["author"].as(); + mi["version"] = module["version"].as(); + mi["status"] = module["status"].as(); + mi["enabled"] = module["enabled"].as(); + } +} + +// read any Module settings from the FS settings +// and then apply the enable/disable +// it's also called on a save +StateUpdateResult WebModules::update(JsonObject root, WebModules & webModules) { + // EMSESP::logger().debug("module update called"); // TODO remove + + if (root["modules"].is()) { + for (const JsonObject module : root["modules"].as()) { + // set enabled/disabled + moduleLibrary.enable(module["name"], module["enabled"].as()); + } + } + + return StateUpdateResult::CHANGED; +} + +} // namespace emsesp diff --git a/src/web/WebModulesService.h b/src/web/WebModulesService.h new file mode 100644 index 000000000..715424a42 --- /dev/null +++ b/src/web/WebModulesService.h @@ -0,0 +1,50 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2024 Paul Derbyshire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WebModulesService_h +#define WebModulesService_h + +#define EMSESP_MODULES_FILE "/config/emsespModules.json" +#define EMSESP_MODULES_SERVICE_PATH "/rest/modules" // GET and POST + +namespace emsesp { + +class WebModules { + public: + static void read(WebModules & webModules, JsonObject root); + static StateUpdateResult update(JsonObject root, WebModules & webModules); +}; + +class WebModulesService : public StatefulService { + public: + WebModulesService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); + + void begin(); + void loop(); + +#ifndef EMSESP_STANDALONE + private: +#endif + + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; +}; + +} // namespace emsesp + +#endif \ No newline at end of file