From 53357891da3e2125b6a9e15cc7103c2fd6bb995a Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Sun, 29 Dec 2024 16:17:41 +0100 Subject: [PATCH] code cleanup --- include/YaSolR.h | 5 +- src/fn/yasolr_configure.cpp | 39 +++ src/fn/yasolr_divert.cpp | 23 -- src/fn/yasolr_event_listeners.cpp | 33 --- src/fn/yasolr_frequency.cpp | 35 --- src/fn/yasolr_start_grid.cpp | 30 ++ src/fn/yasolr_start_rest_api.cpp | 424 --------------------------- src/fn/yasolr_start_website.cpp | 461 ++++++++++++++++++++++++++++-- src/main.cpp | 4 +- 9 files changed, 513 insertions(+), 541 deletions(-) delete mode 100644 src/fn/yasolr_divert.cpp delete mode 100644 src/fn/yasolr_event_listeners.cpp delete mode 100644 src/fn/yasolr_frequency.cpp delete mode 100644 src/fn/yasolr_start_rest_api.cpp diff --git a/include/YaSolR.h b/include/YaSolR.h index 5e6b61d5..f90dddad 100644 --- a/include/YaSolR.h +++ b/include/YaSolR.h @@ -41,6 +41,7 @@ #include #include #include +#include #ifdef APP_MODEL_TRIAL #include @@ -149,7 +150,6 @@ extern float yasolr_frequency(); extern void yasolr_boot(); extern void yasolr_configure(); extern void yasolr_divert(); -extern void yasolr_event_listeners(); extern void yasolr_start_config(); extern void yasolr_start_display(); @@ -162,10 +162,9 @@ extern void yasolr_start_logging(); extern void yasolr_start_mqtt(); extern void yasolr_start_network(); extern void yasolr_start_pzem(); -extern void yasolr_start_rest_api(); extern void yasolr_start_system(); extern void yasolr_start_trial(); -extern void yasolr_start_website(); +extern void yasolr_start_web_server(); extern void yasolr_start_zcd(); extern void yasolr_configure_logging(); diff --git a/src/fn/yasolr_configure.cpp b/src/fn/yasolr_configure.cpp index 790d3aca..ff3b0ca5 100644 --- a/src/fn/yasolr_configure.cpp +++ b/src/fn/yasolr_configure.cpp @@ -3,9 +3,27 @@ * Copyright (C) 2023-2024 Mathieu Carbou */ #include +#include #include +void yasolr_divert() { + if (router.isCalibrationRunning()) + return; + + if (!output1.isAutoDimmerEnabled() && !output2.isAutoDimmerEnabled()) + return; + + std::optional voltage = grid.getVoltage(); + + if (voltage.has_value() && grid.getPower().isPresent()) { + router.divert(voltage.value(), grid.getPower().get()); + if (website.pidCharts()) { + dashboardUpdateTask.requestEarlyRun(); + } + } +} + void yasolr_configure() { logger.info(TAG, "Configuring %s", Mycila::AppInfo.nameModelVersion.c_str()); @@ -85,4 +103,25 @@ void yasolr_configure() { // Router router.addOutput(output1); router.addOutput(output2); + + bypassRelayO1.listen([](bool state) { + logger.info(TAG, "Output 1 Relay changed to %s", state ? "ON" : "OFF"); + if (mqttPublishTask) + mqttPublishTask->requestEarlyRun(); + }); + bypassRelayO2.listen([](bool state) { + logger.info(TAG, "Output 2 Relay changed to %s", state ? "ON" : "OFF"); + if (mqttPublishTask) + mqttPublishTask->requestEarlyRun(); + }); + relay1.listen([](bool state) { + logger.info(TAG, "Relay 1 changed to %s", state ? "ON" : "OFF"); + if (mqttPublishTask) + mqttPublishTask->requestEarlyRun(); + }); + relay2.listen([](bool state) { + logger.info(TAG, "Relay 2 changed to %s", state ? "ON" : "OFF"); + if (mqttPublishTask) + mqttPublishTask->requestEarlyRun(); + }); } diff --git a/src/fn/yasolr_divert.cpp b/src/fn/yasolr_divert.cpp deleted file mode 100644 index c2294215..00000000 --- a/src/fn/yasolr_divert.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * Copyright (C) 2023-2024 Mathieu Carbou - */ -#include -#include - -void yasolr_divert() { - if (router.isCalibrationRunning()) - return; - - if (!output1.isAutoDimmerEnabled() && !output2.isAutoDimmerEnabled()) - return; - - std::optional voltage = grid.getVoltage(); - - if (voltage.has_value() && grid.getPower().isPresent()) { - router.divert(voltage.value(), grid.getPower().get()); - if (website.pidCharts()) { - dashboardUpdateTask.requestEarlyRun(); - } - } -} diff --git a/src/fn/yasolr_event_listeners.cpp b/src/fn/yasolr_event_listeners.cpp deleted file mode 100644 index 54078180..00000000 --- a/src/fn/yasolr_event_listeners.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * Copyright (C) 2023-2024 Mathieu Carbou - */ -#include -#include - -#include - -void yasolr_event_listeners() { - logger.info(TAG, "Initializing Event Listeners"); - - bypassRelayO1.listen([](bool state) { - logger.info(TAG, "Output 1 Relay changed to %s", state ? "ON" : "OFF"); - if (mqttPublishTask) - mqttPublishTask->requestEarlyRun(); - }); - bypassRelayO2.listen([](bool state) { - logger.info(TAG, "Output 2 Relay changed to %s", state ? "ON" : "OFF"); - if (mqttPublishTask) - mqttPublishTask->requestEarlyRun(); - }); - relay1.listen([](bool state) { - logger.info(TAG, "Relay 1 changed to %s", state ? "ON" : "OFF"); - if (mqttPublishTask) - mqttPublishTask->requestEarlyRun(); - }); - relay2.listen([](bool state) { - logger.info(TAG, "Relay 2 changed to %s", state ? "ON" : "OFF"); - if (mqttPublishTask) - mqttPublishTask->requestEarlyRun(); - }); -} diff --git a/src/fn/yasolr_frequency.cpp b/src/fn/yasolr_frequency.cpp deleted file mode 100644 index f7a28bfc..00000000 --- a/src/fn/yasolr_frequency.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * Copyright (C) 2023-2024 Mathieu Carbou - */ -#include - -float yasolr_frequency() { - float frequency = round(grid.getFrequency().value_or(NAN)); - if (frequency > 0) - return frequency; - - if (pzemO1) { - frequency = round(pzemO1->data.frequency); - if (frequency > 0) - return frequency; - } - - if (pzemO2) { - frequency = round(pzemO2->data.frequency); - if (frequency > 0) - return frequency; - } - - frequency = config.getFloat(KEY_GRID_FREQUENCY); - if (frequency) - return frequency; - - if (pulseAnalyzer) { - frequency = pulseAnalyzer->getNominalGridFrequency(); - if (frequency) - return frequency; - } - - return 0; -} diff --git a/src/fn/yasolr_start_grid.cpp b/src/fn/yasolr_start_grid.cpp index 5711abac..811c50fd 100644 --- a/src/fn/yasolr_start_grid.cpp +++ b/src/fn/yasolr_start_grid.cpp @@ -6,6 +6,36 @@ Mycila::Grid grid; +float yasolr_frequency() { + float frequency = round(grid.getFrequency().value_or(NAN)); + if (frequency > 0) + return frequency; + + if (pzemO1) { + frequency = round(pzemO1->data.frequency); + if (frequency > 0) + return frequency; + } + + if (pzemO2) { + frequency = round(pzemO2->data.frequency); + if (frequency > 0) + return frequency; + } + + frequency = config.getFloat(KEY_GRID_FREQUENCY); + if (frequency) + return frequency; + + if (pulseAnalyzer) { + frequency = pulseAnalyzer->getNominalGridFrequency(); + if (frequency) + return frequency; + } + + return 0; +} + void yasolr_start_grid() { grid.localMetrics().setExpiration(10000); // local is fast grid.remoteMetrics().setExpiration(10000); // remote JSY is fast diff --git a/src/fn/yasolr_start_rest_api.cpp b/src/fn/yasolr_start_rest_api.cpp deleted file mode 100644 index 17a721d0..00000000 --- a/src/fn/yasolr_start_rest_api.cpp +++ /dev/null @@ -1,424 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * Copyright (C) 2023-2024 Mathieu Carbou - */ -#include - -#include - -#include -#include - -void yasolr_start_rest_api() { - logger.info(TAG, "Initializing REST API"); - - // debug - - webServer - .on("/api/debug", HTTP_GET, [](AsyncWebServerRequest* request) { - if (!config.getBool(KEY_ENABLE_DEBUG)) { - return request->send(404, "application/json", "{\"error\":\"Debug mode is disabled.\"}"); - } - - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); - float voltage = grid.getVoltage().value_or(0); - - Mycila::AppInfo.toJson(root["app"].to()); - config.toJson(root["config"].to()); - grid.toJson(root["grid"].to()); - if (jsy) - jsy->toJson(root["jsy"].to()); - espConnect.toJson(root["network"].to()); - - pidController.toJson(root["pid"].to()); - if (pulseAnalyzer) - pulseAnalyzer->toJson(root["pulse_analyzer"].to()); - - // relays - relay1.toJson(root["relay1"].to()); - relay2.toJson(root["relay2"].to()); - - // router - router.toJson(root["router"].to(), voltage); - - // output 1 - output1.toJson(root["router"]["output1"].to(), voltage); - dimmerO1.dimmerToJson(root["router"]["output1"]["dimmer"].to()); - if (ds18O1) - ds18O1->toJson(root["router"]["output1"]["ds18"].to()); - if (pzemO1) - pzemO1->toJson(root["router"]["output1"]["pzem"].to()); - bypassRelayO1.toJson(root["router"]["output1"]["relay"].to()); - - // output 2 - output2.toJson(root["router"]["output2"].to(), voltage); - dimmerO2.dimmerToJson(root["router"]["output2"]["dimmer"].to()); - if (ds18O2) - ds18O2->toJson(root["router"]["output2"]["ds18"].to()); - if (pzemO2) - pzemO2->toJson(root["router"]["output2"]["pzem"].to()); - bypassRelayO2.toJson(root["router"]["output2"]["relay"].to()); - - // system - JsonObject system = root["system"].to(); - Mycila::System::toJson(system); - if (ds18Sys) - ds18Sys->toJson(system["ds18"].to()); - lights.toJson(system["leds"].to()); - - // stack - Mycila::TaskMonitor.toJson(system["stack"].to()); - - // tasks - JsonObject tasks = system["task"].to(); - coreTaskManager.toJson(tasks[coreTaskManager.getName()].to()); - if (jsyTaskManager) - jsyTaskManager->toJson(tasks[jsyTaskManager->getName()].to()); - if (pzemTaskManager) - pzemTaskManager->toJson(tasks[pzemTaskManager->getName()].to()); - unsafeTaskManager.toJson(tasks[unsafeTaskManager.getName()].to()); - - // libs versions - JsonObject library = system["lib"].to(); - library["ArduinoJson"] = ARDUINOJSON_VERSION; - library["AsyncTCP"] = ASYNCTCP_VERSION; - library["CRC"] = CRC_LIB_VERSION; - library["ESPAsyncWebServer"] = ASYNCWEBSERVER_VERSION; - library["MycilaConfig"] = MYCILA_CONFIG_VERSION; - library["MycilaDS18"] = MYCILA_DS18_VERSION; - library["MycilaEasyDisplay"] = MYCILA_EASY_DISPLAY_VERSION; - library["MycilaESPConnect"] = ESPCONNECT_VERSION; - library["MycilaHADiscovery"] = MYCILA_HA_VERSION; - library["MycilaJSY"] = MYCILA_JSY_VERSION; - library["MycilaLogger"] = MYCILA_LOGGER_VERSION; - library["MycilaMQTT"] = MYCILA_MQTT_VERSION; - library["MycilaNTP"] = MYCILA_NTP_VERSION; - library["MycilaPulseAnalyzer"] = MYCILA_PULSE_VERSION; - library["MycilaPZEM004Tv3"] = MYCILA_PZEM_VERSION; - library["MycilaRelay"] = MYCILA_RELAY_VERSION; - library["MycilaSystem"] = MYCILA_SYSTEM_VERSION; - library["MycilaTaskManager"] = MYCILA_TASK_MANAGER_VERSION; - library["MycilaTaskMonitor"] = MYCILA_TASK_MONITOR_VERSION; - library["MycilaTrafficLight"] = MYCILA_TRAFFIC_LIGHT_VERSION; - library["MycilaUtilities"] = MYCILA_UTILITIES_VERSION; - - response->setLength(); - request->send(response); - }); - - // config - - webServer - .on("/api/config/backup", HTTP_GET, [](AsyncWebServerRequest* request) { - StreamString body; - body.reserve(4096); - config.backup(body); - AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", body); - response->addHeader("Content-Disposition", "attachment; filename=\"config.txt\""); - request->send(response); - }); - - webServer - .on( - "/api/config/restore", - HTTP_POST, - [](AsyncWebServerRequest* request) { - if (!request->_tempObject) { - return request->send(400, "text/plain", "No config file uploaded"); - } - StreamString* buffer = reinterpret_cast(request->_tempObject); - config.restore((*buffer).c_str()); - delete buffer; - request->_tempObject = nullptr; - request->send(200, "text/plain", "OK"); - }, - [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { - if (!index) { - if (request->_tempObject) { - delete reinterpret_cast(request->_tempObject); - } - StreamString* buffer = new StreamString(); - buffer->reserve(4096); - request->_tempObject = buffer; - } - if (len) { - reinterpret_cast(request->_tempObject)->write(data, len); - } - }); - - webServer - .on( - "/api/config/mqttServerCertificate", - HTTP_POST, - [](AsyncWebServerRequest* request) { - AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", "OK"); - if (!LittleFS.exists(YASOLR_MQTT_SERVER_CERT_FILE)) { - return request->send(400, "text/plain", "No PEM server certificate file uploaded"); - } - File serverCertFile = LittleFS.open(YASOLR_MQTT_SERVER_CERT_FILE, "r"); - logger.info(TAG, "Uploaded MQTT PEM server certificate:\n%s", serverCertFile.readString().c_str()); - serverCertFile.close(); - dashboardInitTask.resume(); - request->send(response); - }, - [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { - if (!index) - request->_tempFile = LittleFS.open(YASOLR_MQTT_SERVER_CERT_FILE, "w"); - if (len) - request->_tempFile.write(data, len); - if (final) - request->_tempFile.close(); - }); - - webServer - .on("/api/config", HTTP_POST, [](AsyncWebServerRequest* request) { - std::map settings; - for (size_t i = 0, max = request->params(); i < max; i++) { - const AsyncWebParameter* p = request->getParam(i); - if (p->isPost() && !p->isFile()) { - const char* keyRef = config.keyRef(p->name().c_str()); - settings[keyRef] = p->value().c_str(); - } - } - request->send(200); - config.set(settings); - }); - - webServer - .on("/api/config", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false); - config.toJson(response->getRoot()); - response->setLength(); - request->send(response); - }); - - // system - - webServer - .on("/api/system/restart", HTTP_ANY, [](AsyncWebServerRequest* request) { - restartTask.resume(); - request->send(200); - }); - - webServer - .on("/api/system/safeboot", HTTP_ANY, [](AsyncWebServerRequest* request) { - safeBootTask.resume(); - request->send(200); - }); - - webServer - .on("/api/system/reset", HTTP_ANY, [](AsyncWebServerRequest* request) { - resetTask.resume(); - request->send(200); - }); - - webServer - .on("/api/system", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); - - Mycila::System::Memory memory; - Mycila::System::getMemory(memory); - - root["app"]["manufacturer"] = Mycila::AppInfo.manufacturer; - root["app"]["model"] = Mycila::AppInfo.model; - root["app"]["name"] = Mycila::AppInfo.name; - root["app"]["version"] = Mycila::AppInfo.version; - - root["device"]["boots"] = Mycila::System::getBootCount(); - root["device"]["cores"] = ESP.getChipCores(); - root["device"]["cpu_freq"] = ESP.getCpuFreqMHz(); - root["device"]["heap"]["total"] = memory.total; - root["device"]["heap"]["usage"] = memory.usage; - root["device"]["heap"]["used"] = memory.used; - root["device"]["id"] = Mycila::AppInfo.id; - root["device"]["model"] = ESP.getChipModel(); - root["device"]["revision"] = ESP.getChipRevision(); - root["device"]["uptime"] = Mycila::System::getUptime(); - - root["firmware"]["build"]["branch"] = Mycila::AppInfo.buildBranch; - root["firmware"]["build"]["hash"] = Mycila::AppInfo.buildHash; - root["firmware"]["build"]["timestamp"] = Mycila::AppInfo.buildDate; - root["firmware"]["debug"] = Mycila::AppInfo.debug; - root["firmware"]["filename"] = Mycila::AppInfo.firmware; - - root["network"]["eth"]["ip_address"] = espConnect.getIPAddress(Mycila::ESPConnect::Mode::ETH).toString(); - root["network"]["eth"]["mac_address"] = espConnect.getMACAddress(Mycila::ESPConnect::Mode::ETH); - - root["network"]["hostname"] = espConnect.getHostname(); - root["network"]["ip_address"] = espConnect.getIPAddress().toString(); - root["network"]["mac_address"] = espConnect.getMACAddress(); - switch (espConnect.getMode()) { - case Mycila::ESPConnect::Mode::ETH: - root["network"]["mode"] = "eth"; - break; - case Mycila::ESPConnect::Mode::STA: - root["network"]["mode"] = "wifi"; - break; - case Mycila::ESPConnect::Mode::AP: - root["network"]["mode"] = "ap"; - break; - default: - root["network"]["mode"] = ""; - break; - } - - root["network"]["ntp"] = YASOLR_STATE(Mycila::NTP.isSynced()); - - root["network"]["wifi"]["bssid"] = espConnect.getWiFiBSSID(); - root["network"]["wifi"]["ip_address"] = espConnect.getIPAddress(Mycila::ESPConnect::Mode::STA).toString(); - root["network"]["wifi"]["mac_address"] = espConnect.getMACAddress(Mycila::ESPConnect::Mode::STA); - root["network"]["wifi"]["quality"] = espConnect.getWiFiSignalQuality(); - root["network"]["wifi"]["rssi"] = espConnect.getWiFiRSSI(); - root["network"]["wifi"]["ssid"] = espConnect.getWiFiSSID(); - - response->setLength(); - request->send(response); - }); - - // grid - - webServer - .on("/api/grid", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); - Mycila::Grid::Metrics metrics; - grid.getGridMeasurements(metrics); - Mycila::Grid::toJson(root, metrics); - response->setLength(); - request->send(response); - }); - - // router relays - - webServer - .on("/api/router/relay1", HTTP_POST, [](AsyncWebServerRequest* request) { - if (relay1.isEnabled() && request->hasParam("state", true)) { - std::string state = request->getParam("state", true)->value().c_str(); - if (state == YASOLR_ON) - routerRelay1.tryRelayState(true); - else if (state == YASOLR_OFF) - routerRelay1.tryRelayState(false); - } - request->send(200); - }); - - webServer - .on("/api/router/relay2", HTTP_POST, [](AsyncWebServerRequest* request) { - if (relay2.isEnabled() && request->hasParam("state", true)) { - std::string state = request->getParam("state", true)->value().c_str(); - if (state == YASOLR_ON) - routerRelay2.tryRelayState(true); - else if (state == YASOLR_OFF) - routerRelay2.tryRelayState(false); - } - request->send(200); - }); - - // router dimmers - - webServer - .on("/api/router/output1/dimmer", HTTP_POST, [](AsyncWebServerRequest* request) { - if (request->hasParam("duty_cycle", true)) - output1.setDimmerDutyCycle(request->getParam("duty_cycle", true)->value().toFloat() / 100); - request->send(200); - }); - - webServer - .on("/api/router/output2/dimmer", HTTP_POST, [](AsyncWebServerRequest* request) { - if (request->hasParam("duty_cycle", true)) - output2.setDimmerDutyCycle(request->getParam("duty_cycle", true)->value().toFloat() / 100); - request->send(200); - }); - - // router bypass - - webServer - .on("/api/router/output1/bypass", HTTP_POST, [](AsyncWebServerRequest* request) { - if (request->hasParam("state", true)) { - std::string state = request->getParam("state", true)->value().c_str(); - if (state == YASOLR_ON) - output1.setBypassOn(); - else if (state == YASOLR_OFF) - output1.setBypassOff(); - } - request->send(200); - }); - - webServer - .on("/api/router/output2/bypass", HTTP_POST, [](AsyncWebServerRequest* request) { - if (request->hasParam("state", true)) { - std::string state = request->getParam("state", true)->value().c_str(); - if (state == YASOLR_ON) - output2.setBypassOn(); - else if (state == YASOLR_OFF) - output2.setBypassOff(); - } - request->send(200); - }); - - webServer - .on("/api/router", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); - - Mycila::Router::Metrics routerMeasurements; - router.getRouterMeasurements(routerMeasurements); - - root["lights"] = lights.toString(); - root["relay1"] = YASOLR_STATE(relay1.isOn()); - root["relay2"] = YASOLR_STATE(relay2.isOn()); - if (ds18Sys) { - float t = ds18Sys->getTemperature().value_or(NAN); - if (!isnanf(t)) - root["temperature"] = t; - } - float virtual_grid_power = grid.getPower().orElse(NAN) - routerMeasurements.power; - if (!isnanf(virtual_grid_power)) - root["virtual_grid_power"] = virtual_grid_power; - - Mycila::Router::toJson(root["measurements"].to(), routerMeasurements); - - for (const auto& output : router.getOutputs()) { - JsonObject json = root[output->getName()].to(); - json["state"] = output->getStateName(); - json["bypass"] = YASOLR_STATE(output->isBypassOn()); - json["dimmer"] = YASOLR_STATE(output->isDimmerOn()); - json["duty_cycle"] = output->getDimmerDutyCycle() * 100; - float t = output->temperature().orElse(NAN); - if (!isnanf(t)) { - json["temperature"] = t; - } - - Mycila::RouterOutput::Metrics outputMeasurements; - output->getOutputMeasurements(outputMeasurements); - Mycila::RouterOutput::toJson(json["measurements"].to(), outputMeasurements); - } - - response->setLength(); - request->send(response); - }); - - webServer - .on("/api", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); - - std::string base = "http://"; - base += espConnect.getIPAddress().toString().c_str(); - base += "/api"; - - root["config"] = base + "/config"; - root["config/backup"] = base + "/config/backup"; - if (config.getBool(KEY_ENABLE_DEBUG)) { - root["debug"] = base + "/debug"; - } - root["grid"] = base + "/grid"; - root["router"] = base + "/router"; - root["system"] = base + "/system"; - - response->setLength(); - request->send(response); - }); -} diff --git a/src/fn/yasolr_start_website.cpp b/src/fn/yasolr_start_website.cpp index 71e1499c..d8f34252 100644 --- a/src/fn/yasolr_start_website.cpp +++ b/src/fn/yasolr_start_website.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include + extern const uint8_t logo_png_gz_start[] asm("_binary__pio_data_logo_png_gz_start"); extern const uint8_t logo_png_gz_end[] asm("_binary__pio_data_logo_png_gz_end"); extern const uint8_t logo_icon_png_gz_start[] asm("_binary__pio_data_logo_icon_png_gz_start"); @@ -36,24 +39,7 @@ Mycila::Task dashboardUpdateTask("Dashboard Update", [](void* params) { dashboard.sendUpdates(); }); -void yasolr_start_website() { - logger.info(TAG, "Initializing Web Server"); - - // Middleware - - loggingMiddleware.setOutput(Serial); - - authMiddleware.setAuthType(AsyncAuthType::AUTH_DIGEST); - authMiddleware.setRealm("YaSolR"); - authMiddleware.setUsername(YASOLR_ADMIN_USERNAME); - authMiddleware.setPassword(config.get(KEY_ADMIN_PASSWORD)); - authMiddleware.generateHash(); - - webServer.addMiddleware(&loggingMiddleware); - webServer.addMiddleware(&authMiddleware); - - // Rewrites - +void rewrites() { webServer.rewrite("/dash/assets/logo/mini", "/logo-icon"); webServer.rewrite("/dash/assets/logo/large", "/logo"); webServer.rewrite("/dash/assets/logo", "/logo"); @@ -62,9 +48,9 @@ void yasolr_start_website() { webServer.rewrite("/wsl/logo/dark", "/logo"); webServer.rewrite("/wsl/logo/light", "/logo"); webServer.rewrite("/", "/dashboard").setFilter([](AsyncWebServerRequest* request) { return espConnect.getState() != Mycila::ESPConnect::State::PORTAL_STARTED; }); +} - // Routes - +void routes() { webServer.on("/logo-icon", HTTP_GET, [](AsyncWebServerRequest* request) { AsyncWebServerResponse* response = request->beginResponse(200, "image/png", logo_icon_png_gz_start, logo_icon_png_gz_end - logo_icon_png_gz_start); response->addHeader("Content-Encoding", "gzip"); @@ -93,6 +79,441 @@ void yasolr_start_website() { response->setLength(); request->send(response); }); +} + +void rest_api() { + logger.info(TAG, "Initializing REST API"); + + // debug + + webServer + .on("/api/debug", HTTP_GET, [](AsyncWebServerRequest* request) { + if (!config.getBool(KEY_ENABLE_DEBUG)) { + return request->send(404, "application/json", "{\"error\":\"Debug mode is disabled.\"}"); + } + + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + float voltage = grid.getVoltage().value_or(0); + + Mycila::AppInfo.toJson(root["app"].to()); + config.toJson(root["config"].to()); + grid.toJson(root["grid"].to()); + if (jsy) + jsy->toJson(root["jsy"].to()); + espConnect.toJson(root["network"].to()); + + pidController.toJson(root["pid"].to()); + if (pulseAnalyzer) + pulseAnalyzer->toJson(root["pulse_analyzer"].to()); + + // relays + relay1.toJson(root["relay1"].to()); + relay2.toJson(root["relay2"].to()); + + // router + router.toJson(root["router"].to(), voltage); + + // output 1 + output1.toJson(root["router"]["output1"].to(), voltage); + dimmerO1.dimmerToJson(root["router"]["output1"]["dimmer"].to()); + if (ds18O1) + ds18O1->toJson(root["router"]["output1"]["ds18"].to()); + if (pzemO1) + pzemO1->toJson(root["router"]["output1"]["pzem"].to()); + bypassRelayO1.toJson(root["router"]["output1"]["relay"].to()); + + // output 2 + output2.toJson(root["router"]["output2"].to(), voltage); + dimmerO2.dimmerToJson(root["router"]["output2"]["dimmer"].to()); + if (ds18O2) + ds18O2->toJson(root["router"]["output2"]["ds18"].to()); + if (pzemO2) + pzemO2->toJson(root["router"]["output2"]["pzem"].to()); + bypassRelayO2.toJson(root["router"]["output2"]["relay"].to()); + + // system + JsonObject system = root["system"].to(); + Mycila::System::toJson(system); + if (ds18Sys) + ds18Sys->toJson(system["ds18"].to()); + lights.toJson(system["leds"].to()); + + // stack + Mycila::TaskMonitor.toJson(system["stack"].to()); + + // tasks + JsonObject tasks = system["task"].to(); + coreTaskManager.toJson(tasks[coreTaskManager.getName()].to()); + if (jsyTaskManager) + jsyTaskManager->toJson(tasks[jsyTaskManager->getName()].to()); + if (pzemTaskManager) + pzemTaskManager->toJson(tasks[pzemTaskManager->getName()].to()); + unsafeTaskManager.toJson(tasks[unsafeTaskManager.getName()].to()); + + // libs versions + JsonObject library = system["lib"].to(); + library["ArduinoJson"] = ARDUINOJSON_VERSION; + library["AsyncTCP"] = ASYNCTCP_VERSION; + library["CRC"] = CRC_LIB_VERSION; + library["ESPAsyncWebServer"] = ASYNCWEBSERVER_VERSION; + library["MycilaConfig"] = MYCILA_CONFIG_VERSION; + library["MycilaDS18"] = MYCILA_DS18_VERSION; + library["MycilaEasyDisplay"] = MYCILA_EASY_DISPLAY_VERSION; + library["MycilaESPConnect"] = ESPCONNECT_VERSION; + library["MycilaHADiscovery"] = MYCILA_HA_VERSION; + library["MycilaJSY"] = MYCILA_JSY_VERSION; + library["MycilaLogger"] = MYCILA_LOGGER_VERSION; + library["MycilaMQTT"] = MYCILA_MQTT_VERSION; + library["MycilaNTP"] = MYCILA_NTP_VERSION; + library["MycilaPulseAnalyzer"] = MYCILA_PULSE_VERSION; + library["MycilaPZEM004Tv3"] = MYCILA_PZEM_VERSION; + library["MycilaRelay"] = MYCILA_RELAY_VERSION; + library["MycilaSystem"] = MYCILA_SYSTEM_VERSION; + library["MycilaTaskManager"] = MYCILA_TASK_MANAGER_VERSION; + library["MycilaTaskMonitor"] = MYCILA_TASK_MONITOR_VERSION; + library["MycilaTrafficLight"] = MYCILA_TRAFFIC_LIGHT_VERSION; + library["MycilaUtilities"] = MYCILA_UTILITIES_VERSION; + + response->setLength(); + request->send(response); + }); + + // config + + webServer + .on("/api/config/backup", HTTP_GET, [](AsyncWebServerRequest* request) { + StreamString body; + body.reserve(4096); + config.backup(body); + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", body); + response->addHeader("Content-Disposition", "attachment; filename=\"config.txt\""); + request->send(response); + }); + + webServer + .on( + "/api/config/restore", + HTTP_POST, + [](AsyncWebServerRequest* request) { + if (!request->_tempObject) { + return request->send(400, "text/plain", "No config file uploaded"); + } + StreamString* buffer = reinterpret_cast(request->_tempObject); + config.restore((*buffer).c_str()); + delete buffer; + request->_tempObject = nullptr; + request->send(200, "text/plain", "OK"); + }, + [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { + if (!index) { + if (request->_tempObject) { + delete reinterpret_cast(request->_tempObject); + } + StreamString* buffer = new StreamString(); + buffer->reserve(4096); + request->_tempObject = buffer; + } + if (len) { + reinterpret_cast(request->_tempObject)->write(data, len); + } + }); + + webServer + .on( + "/api/config/mqttServerCertificate", + HTTP_POST, + [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", "OK"); + if (!LittleFS.exists(YASOLR_MQTT_SERVER_CERT_FILE)) { + return request->send(400, "text/plain", "No PEM server certificate file uploaded"); + } + File serverCertFile = LittleFS.open(YASOLR_MQTT_SERVER_CERT_FILE, "r"); + logger.info(TAG, "Uploaded MQTT PEM server certificate:\n%s", serverCertFile.readString().c_str()); + serverCertFile.close(); + dashboardInitTask.resume(); + request->send(response); + }, + [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { + if (!index) + request->_tempFile = LittleFS.open(YASOLR_MQTT_SERVER_CERT_FILE, "w"); + if (len) + request->_tempFile.write(data, len); + if (final) + request->_tempFile.close(); + }); + + webServer + .on("/api/config", HTTP_POST, [](AsyncWebServerRequest* request) { + std::map settings; + for (size_t i = 0, max = request->params(); i < max; i++) { + const AsyncWebParameter* p = request->getParam(i); + if (p->isPost() && !p->isFile()) { + const char* keyRef = config.keyRef(p->name().c_str()); + settings[keyRef] = p->value().c_str(); + } + } + request->send(200); + config.set(settings); + }); + + webServer + .on("/api/config", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false); + config.toJson(response->getRoot()); + response->setLength(); + request->send(response); + }); + + // system + + webServer + .on("/api/system/restart", HTTP_ANY, [](AsyncWebServerRequest* request) { + restartTask.resume(); + request->send(200); + }); + + webServer + .on("/api/system/safeboot", HTTP_ANY, [](AsyncWebServerRequest* request) { + safeBootTask.resume(); + request->send(200); + }); + + webServer + .on("/api/system/reset", HTTP_ANY, [](AsyncWebServerRequest* request) { + resetTask.resume(); + request->send(200); + }); + + webServer + .on("/api/system", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + + Mycila::System::Memory memory; + Mycila::System::getMemory(memory); + + root["app"]["manufacturer"] = Mycila::AppInfo.manufacturer; + root["app"]["model"] = Mycila::AppInfo.model; + root["app"]["name"] = Mycila::AppInfo.name; + root["app"]["version"] = Mycila::AppInfo.version; + + root["device"]["boots"] = Mycila::System::getBootCount(); + root["device"]["cores"] = ESP.getChipCores(); + root["device"]["cpu_freq"] = ESP.getCpuFreqMHz(); + root["device"]["heap"]["total"] = memory.total; + root["device"]["heap"]["usage"] = memory.usage; + root["device"]["heap"]["used"] = memory.used; + root["device"]["id"] = Mycila::AppInfo.id; + root["device"]["model"] = ESP.getChipModel(); + root["device"]["revision"] = ESP.getChipRevision(); + root["device"]["uptime"] = Mycila::System::getUptime(); + + root["firmware"]["build"]["branch"] = Mycila::AppInfo.buildBranch; + root["firmware"]["build"]["hash"] = Mycila::AppInfo.buildHash; + root["firmware"]["build"]["timestamp"] = Mycila::AppInfo.buildDate; + root["firmware"]["debug"] = Mycila::AppInfo.debug; + root["firmware"]["filename"] = Mycila::AppInfo.firmware; + + root["network"]["eth"]["ip_address"] = espConnect.getIPAddress(Mycila::ESPConnect::Mode::ETH).toString(); + root["network"]["eth"]["mac_address"] = espConnect.getMACAddress(Mycila::ESPConnect::Mode::ETH); + + root["network"]["hostname"] = espConnect.getHostname(); + root["network"]["ip_address"] = espConnect.getIPAddress().toString(); + root["network"]["mac_address"] = espConnect.getMACAddress(); + switch (espConnect.getMode()) { + case Mycila::ESPConnect::Mode::ETH: + root["network"]["mode"] = "eth"; + break; + case Mycila::ESPConnect::Mode::STA: + root["network"]["mode"] = "wifi"; + break; + case Mycila::ESPConnect::Mode::AP: + root["network"]["mode"] = "ap"; + break; + default: + root["network"]["mode"] = ""; + break; + } + + root["network"]["ntp"] = YASOLR_STATE(Mycila::NTP.isSynced()); + + root["network"]["wifi"]["bssid"] = espConnect.getWiFiBSSID(); + root["network"]["wifi"]["ip_address"] = espConnect.getIPAddress(Mycila::ESPConnect::Mode::STA).toString(); + root["network"]["wifi"]["mac_address"] = espConnect.getMACAddress(Mycila::ESPConnect::Mode::STA); + root["network"]["wifi"]["quality"] = espConnect.getWiFiSignalQuality(); + root["network"]["wifi"]["rssi"] = espConnect.getWiFiRSSI(); + root["network"]["wifi"]["ssid"] = espConnect.getWiFiSSID(); + + response->setLength(); + request->send(response); + }); + + // grid + + webServer + .on("/api/grid", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + Mycila::Grid::Metrics metrics; + grid.getGridMeasurements(metrics); + Mycila::Grid::toJson(root, metrics); + response->setLength(); + request->send(response); + }); + + // router relays + + webServer + .on("/api/router/relay1", HTTP_POST, [](AsyncWebServerRequest* request) { + if (relay1.isEnabled() && request->hasParam("state", true)) { + std::string state = request->getParam("state", true)->value().c_str(); + if (state == YASOLR_ON) + routerRelay1.tryRelayState(true); + else if (state == YASOLR_OFF) + routerRelay1.tryRelayState(false); + } + request->send(200); + }); + + webServer + .on("/api/router/relay2", HTTP_POST, [](AsyncWebServerRequest* request) { + if (relay2.isEnabled() && request->hasParam("state", true)) { + std::string state = request->getParam("state", true)->value().c_str(); + if (state == YASOLR_ON) + routerRelay2.tryRelayState(true); + else if (state == YASOLR_OFF) + routerRelay2.tryRelayState(false); + } + request->send(200); + }); + + // router dimmers + + webServer + .on("/api/router/output1/dimmer", HTTP_POST, [](AsyncWebServerRequest* request) { + if (request->hasParam("duty_cycle", true)) + output1.setDimmerDutyCycle(request->getParam("duty_cycle", true)->value().toFloat() / 100); + request->send(200); + }); + + webServer + .on("/api/router/output2/dimmer", HTTP_POST, [](AsyncWebServerRequest* request) { + if (request->hasParam("duty_cycle", true)) + output2.setDimmerDutyCycle(request->getParam("duty_cycle", true)->value().toFloat() / 100); + request->send(200); + }); + + // router bypass + + webServer + .on("/api/router/output1/bypass", HTTP_POST, [](AsyncWebServerRequest* request) { + if (request->hasParam("state", true)) { + std::string state = request->getParam("state", true)->value().c_str(); + if (state == YASOLR_ON) + output1.setBypassOn(); + else if (state == YASOLR_OFF) + output1.setBypassOff(); + } + request->send(200); + }); + + webServer + .on("/api/router/output2/bypass", HTTP_POST, [](AsyncWebServerRequest* request) { + if (request->hasParam("state", true)) { + std::string state = request->getParam("state", true)->value().c_str(); + if (state == YASOLR_ON) + output2.setBypassOn(); + else if (state == YASOLR_OFF) + output2.setBypassOff(); + } + request->send(200); + }); + + webServer + .on("/api/router", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + + Mycila::Router::Metrics routerMeasurements; + router.getRouterMeasurements(routerMeasurements); + + root["lights"] = lights.toString(); + root["relay1"] = YASOLR_STATE(relay1.isOn()); + root["relay2"] = YASOLR_STATE(relay2.isOn()); + if (ds18Sys) { + float t = ds18Sys->getTemperature().value_or(NAN); + if (!isnanf(t)) + root["temperature"] = t; + } + float virtual_grid_power = grid.getPower().orElse(NAN) - routerMeasurements.power; + if (!isnanf(virtual_grid_power)) + root["virtual_grid_power"] = virtual_grid_power; + + Mycila::Router::toJson(root["measurements"].to(), routerMeasurements); + + for (const auto& output : router.getOutputs()) { + JsonObject json = root[output->getName()].to(); + json["state"] = output->getStateName(); + json["bypass"] = YASOLR_STATE(output->isBypassOn()); + json["dimmer"] = YASOLR_STATE(output->isDimmerOn()); + json["duty_cycle"] = output->getDimmerDutyCycle() * 100; + float t = output->temperature().orElse(NAN); + if (!isnanf(t)) { + json["temperature"] = t; + } + + Mycila::RouterOutput::Metrics outputMeasurements; + output->getOutputMeasurements(outputMeasurements); + Mycila::RouterOutput::toJson(json["measurements"].to(), outputMeasurements); + } + + response->setLength(); + request->send(response); + }); + + webServer + .on("/api", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + + std::string base = "http://"; + base += espConnect.getIPAddress().toString().c_str(); + base += "/api"; + + root["config"] = base + "/config"; + root["config/backup"] = base + "/config/backup"; + if (config.getBool(KEY_ENABLE_DEBUG)) { + root["debug"] = base + "/debug"; + } + root["grid"] = base + "/grid"; + root["router"] = base + "/router"; + root["system"] = base + "/system"; + + response->setLength(); + request->send(response); + }); +} + +void yasolr_start_web_server() { + logger.info(TAG, "Initializing Web Server"); + + // Middleware + + loggingMiddleware.setOutput(Serial); + + authMiddleware.setAuthType(AsyncAuthType::AUTH_DIGEST); + authMiddleware.setRealm("YaSolR"); + authMiddleware.setUsername(YASOLR_ADMIN_USERNAME); + authMiddleware.setPassword(config.get(KEY_ADMIN_PASSWORD)); + authMiddleware.generateHash(); + + webServer.addMiddleware(&loggingMiddleware); + webServer.addMiddleware(&authMiddleware); + + rewrites(); + routes(); + rest_api(); // ESP-DASH diff --git a/src/main.cpp b/src/main.cpp index 1761686d..52c761bb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,8 +22,6 @@ void setup() { yasolr_start_config(); yasolr_configure(); - yasolr_event_listeners(); - yasolr_start_rest_api(); yasolr_start_display(); yasolr_start_ds18(); @@ -36,7 +34,7 @@ void setup() { yasolr_start_pzem(); yasolr_start_system(); yasolr_start_trial(); - yasolr_start_website(); + yasolr_start_web_server(); yasolr_start_zcd(); yasolr_configure_logging();