From 8cd0a9a761c9d7f031da8341d1f0a76d764ce18d Mon Sep 17 00:00:00 2001 From: Proddy Date: Thu, 28 Dec 2023 14:25:39 +0100 Subject: [PATCH] API updates --- interface/public/fonts/re.woff2 | Bin 15744 -> 17056 bytes .../framework/network/NetworkSettingsForm.tsx | 2 +- interface/src/project/DashboardDevices.tsx | 56 +++++++------- interface/src/project/Help.tsx | 2 +- lib/framework/FSPersistence.h | 9 +-- platformio.ini | 1 - scripts/api_test.http | 69 +++++------------ src/analogsensor.cpp | 8 +- src/devices/thermostat.cpp | 2 +- src/emsdevice.cpp | 2 +- src/emsesp.cpp | 6 +- src/emsesp.h | 10 +-- src/main.cpp | 4 +- src/mqtt.cpp | 8 +- src/shower.cpp | 14 ++-- src/system.cpp | 44 ++++++----- src/temperaturesensor.cpp | 4 +- src/test/test.cpp | 22 +++--- src/web/WebAPIService.cpp | 71 +++++++++++------- src/web/WebAPIService.h | 2 +- src/web/WebCustomEntityService.cpp | 4 +- src/web/WebCustomizationService.cpp | 2 +- src/web/WebDataService.cpp | 2 +- src/web/WebSchedulerService.cpp | 6 +- src/web/WebSettingsService.cpp | 2 +- 25 files changed, 174 insertions(+), 178 deletions(-) diff --git a/interface/public/fonts/re.woff2 b/interface/public/fonts/re.woff2 index 020729ef8d353ff843438008300bedee1f519380..716979a3470e44a963b7594a6bd24b8cf6f63758 100644 GIT binary patch literal 17056 zcmV)6K*+y$Pew8T0RR91079Su5&!@I0GaRr075tb0RR9100000000000000000000 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 P_3Hy(1Zpw-1Zn{Qc8ubJ literal 15744 zcmV-`J%7S?Pew8T0RR9106l;J5&!@I0FHD306iK20RR9100000000000000000000 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 y{^U~631l%Viyw0%mK9y;eEd { )} {restartNeeded && ( - + diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index 693ca740e..abbb07b18 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -420,33 +420,35 @@ const DashboardDevices: FC = () => { )} - - {(tableList: any) => ( - <> -
- - - {LL.DESCRIPTION()} - {LL.TYPE(0)} - -
- - {tableList.map((device: Device) => ( - - - - - - {device.n} -   ({device.e}) - - {device.tn} - - ))} - - - )} -
+ {coreData.connected && ( + + {(tableList: any) => ( + <> +
+ + + {LL.DESCRIPTION()} + {LL.TYPE(0)} + +
+ + {tableList.map((device: Device) => ( + + + + + + {device.n} +   ({device.e}) + + {device.tn} + + ))} + + + )} +
+ )} ); diff --git a/interface/src/project/Help.tsx b/interface/src/project/Help.tsx index 611695e74..810ce107c 100644 --- a/interface/src/project/Help.tsx +++ b/interface/src/project/Help.tsx @@ -111,7 +111,7 @@ const Help: FC = () => { {'github.com/emsesp/EMS-ESP32'} - + @proddy @MichaelDvP diff --git a/lib/framework/FSPersistence.h b/lib/framework/FSPersistence.h index cdbc7f1e3..434ab78ee 100644 --- a/lib/framework/FSPersistence.h +++ b/lib/framework/FSPersistence.h @@ -63,16 +63,15 @@ class FSPersistence { // serialize it to filesystem File settingsFile = _fs->open(_filePath, "w"); -#ifdef EMSESP_DEBUG - Serial.println("Writing settings to " + String(_filePath)); -#endif - // failed to open file, return false if (!settingsFile || !jsonObject.size()) { return false; } - // serialize the data to the file +// serialize the data to the file +#ifdef EMSESP_DEBUG + Serial.println("Writing settings to " + String(_filePath)); +#endif serializeJson(jsonDocument, settingsFile); settingsFile.close(); return true; diff --git a/platformio.ini b/platformio.ini index a41ae6381..a6fac42a1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -184,7 +184,6 @@ build_flags = ; platform = https://github.com/platformio/platform-espressif32.git ; platform = https://github.com/Jason2866/platform-espressif32.git#Arduino/IDF5 ; platform_packages = https://github.com/espressif/arduino-esp32.git#3.0.0-alpha2 - platform = espressif32@6.4.0 framework = arduino board = esp32dev diff --git a/scripts/api_test.http b/scripts/api_test.http index 2d62cee7d..b79a172be 100755 --- a/scripts/api_test.http +++ b/scripts/api_test.http @@ -2,6 +2,12 @@ # to be used with "REST Client" extension in Visual Studio Code (https://marketplace.visualstudio.com/items?itemName=humao.rest-client) # Open this file in VSC, modify the token, go to the API call and click on 'Send Request' (or Ctrl+Alt+R) # The response will be shown in the right panel +# +# You can also test with the command line +# test using CLI with: +# curl -X POST http://10.10.10.135/rest/signIn \ +# -H 'Content-Type: application/json' \ +# -d '{ "value" : 22 }' @host = http://ems-esp.local @host_dev = http://10.10.10.135 @@ -9,13 +15,9 @@ @token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWV9.2bHpWya2C7Q12WjNUBD6_7N3RCD7CMl-EGhyQVzFdDg GET {{host}}/api/system/info - ### - GET {{host}}/api/thermostat/seltemp - ### - POST {{host}}/api/thermostat/seltemp Content-Type: application/json Authorization: Bearer {{token}} @@ -23,9 +25,7 @@ Authorization: Bearer {{token}} { "value" : 21.0 } - ### - POST {{host}}/api/thermostat Content-Type: application/json Authorization: Bearer {{token}} @@ -34,9 +34,7 @@ Authorization: Bearer {{token}} "entity" : "seltemp", "value" : 21.0 } - ### - POST {{host}}/api Content-Type: application/json Authorization: Bearer {{token}} @@ -46,95 +44,68 @@ Authorization: Bearer {{token}} "entity" : "wwtapactivated", "value" : "on" } - ### - GET {{host}}/api/system/restart Authorization: Bearer {{token}} - ### - GET {{host}}/api/boiler/coldshot Authorization: Bearer {{token}} - ### - GET {{host}}/api/temperaturesensor/info - ### # # Test on dev # -GET {{host_dev}}/rest/features - -### GET {{host_dev}}/api/system/info - -# Run a test. EMS-ESP must be compiled with -DEMSESP_TEST -# Use this to load up a dummy thermostat and boiler with data - ### - GET {{host_dev}}/api?device=system&cmd=test&data=general - ### - GET {{host_dev}}/api/boiler/info - ### GET {{host_dev}}/api/boiler/values - ### - GET {{host_dev}}/api/analogsensor/info - ### - GET {{host_dev}}/api/boiler/coldshot Authorization: Bearer {{token}} +### +GET {{host_dev}}/api/system/commands +### +POST {{host_dev}}/api +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "device" : "system", + "entity" : "info", + "id" : 0 +} ### - -GET {{host_dev}}/api/system/commands - +GET {{host_dev}}/rest/features ### GET {{host_dev}}/rest/getSettings Authorization: Bearer {{token}} - ### - POST {{host_dev}}/rest/signIn Content-Type: application/json - ### - GET {{host_dev}}/rest/coreData Authorization: Bearer {{token}} - ### - GET {{host_dev}}/rest/logSettings - ### - GET {{host_dev}}/rest/systemStatus Authorization: Bearer {{token}} - ### - GET {{host_dev}}/rest/deviceData?id=1 Authorization: Bearer {{token}} - ### - GET {{host_dev}}/rest/networkSettings Authorization: Bearer {{token}} -# test using CLI with: - curl -X POST http://10.10.10.135/rest/signIn \ - -H 'Content-Type: application/json' \ - -d '{ "value" : 22 }' + diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index b94cb2c6d..25b32b57f 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020 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 @@ -116,7 +116,7 @@ void AnalogSensor::reload() { } if (!found) { sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type); - sensors_.back().ha_registered = false; // this will trigger recrate of the HA config + sensors_.back().ha_registered = false; // this will trigger recreate of the HA config if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { sensors_.back().set_value(sensor.offset); } else { @@ -616,9 +616,9 @@ void AnalogSensor::publish_values(const bool force) { } JsonObject dev = config.createNestedObject("dev"); - dev["name"] = name; + dev["name"] = Mqtt::basename() + " Analog"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-analog"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 30602656a..d6ff921a6 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -4226,7 +4226,7 @@ void Thermostat::register_device_values() { #if defined(EMSESP_STANDALONE_DUMP) // if we're just dumping out values, create a single dummy hc - register_device_values_hc(std::make_shared(1, this->model())); // hc=1 + register_device_values_hc(std::make_shared(1, this->model())); // hc=1 #endif } diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index c9c6bc6a5..f62fa278b 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -1755,7 +1755,7 @@ void EMSdevice::mqtt_ha_entity_config_create() { } #ifndef EMSESP_STANDALONE // always create minimum one config - if (ESP.getMaxAllocHeap() < (6 * 1024) || (!emsesp::EMSESP::system_.PSram() && ESP.getFreeHeap() < (65 * 1024))) { + if (ESP.getMaxAllocHeap() < (6 * 1024) || (!EMSESP::system_.PSram() && ESP.getFreeHeap() < (65 * 1024))) { break; } #endif diff --git a/src/emsesp.cpp b/src/emsesp.cpp index ad67f7268..3608c190b 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1159,7 +1159,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const emsdevices.back()->unique_id(++unique_id_count_); // sort devices based on type - std::sort(emsdevices.begin(), emsdevices.end(), [](const std::unique_ptr & a, const std::unique_ptr & b) { + std::sort(emsdevices.begin(), emsdevices.end(), [](const std::unique_ptr & a, const std::unique_ptr & b) { return a->device_type() < b->device_type(); }); @@ -1398,7 +1398,7 @@ void EMSESP::scheduled_fetch_values() { EMSESP::EMSESP() #ifndef EMSESP_STANDALONE : telnet_([this](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr { - return std::make_shared(*this, stream, addr, port); + return std::make_shared(*this, stream, addr, port); }) #endif { @@ -1488,7 +1488,7 @@ void EMSESP::start() { // start the file system #ifndef EMSESP_STANDALONE if (!LittleFS.begin(true)) { - Serial.println("LittleFS Mount Failed. EMS-ESP stopped."); + Serial.println("LittleFS Mount Failed. Using default settings."); return; } #endif diff --git a/src/emsesp.h b/src/emsesp.h index 755a34d86..ea02110b6 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -83,11 +83,11 @@ namespace emsesp { -using DeviceValueUOM = emsesp::DeviceValue::DeviceValueUOM; -using DeviceValueType = emsesp::DeviceValue::DeviceValueType; -using DeviceValueState = emsesp::DeviceValue::DeviceValueState; -using DeviceValueTAG = emsesp::DeviceValue::DeviceValueTAG; -using DeviceValueNumOp = emsesp::DeviceValue::DeviceValueNumOp; +using DeviceValueUOM = DeviceValue::DeviceValueUOM; +using DeviceValueType = DeviceValue::DeviceValueType; +using DeviceValueState = DeviceValue::DeviceValueState; +using DeviceValueTAG = DeviceValue::DeviceValueTAG; +using DeviceValueNumOp = DeviceValue::DeviceValueNumOp; // forward declarations for compiler class EMSESPShell; diff --git a/src/main.cpp b/src/main.cpp index 0906f965a..722f5fb39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,9 @@ #include "emsesp.h" -static emsesp::EMSESP application; +using namespace emsesp; + +static EMSESP application; // the main application void setup() { application.start(); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index ff2f42351..3b0745405 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -1194,12 +1194,16 @@ bool Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, snprintf(mode_str_tpl, sizeof(mode_str_tpl), - "{%%if %s%%}off{%%elif %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}", + "{%%if %s%%}off{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}heat{%%elif %s=='%s'%%}off{%%elif %s=='%s'%%}off{%%else%%}auto{%%endif%%}", hc_mode_cond, hc_mode_s, + Helpers::translated_word(FL_(manual)), hc_mode_s, + Helpers::translated_word(FL_(day)), hc_mode_s, - hc_mode_s); + Helpers::translated_word(FL_(night)), + hc_mode_s, + Helpers::translated_word(FL_(off))); snprintf(name_s, sizeof(name_s), "Hc%d", hc_num); diff --git a/src/shower.cpp b/src/shower.cpp index f83813cb1..8c59abbf6 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -211,9 +211,9 @@ void Shower::set_shower_state(bool state, bool force) { } JsonObject dev = doc.createNestedObject("dev"); - dev["name"] = "EMS-ESP"; + dev["name"] = "EMS-ESP Shower"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-shower"); Mqtt::add_avty_to_doc(stat_t, doc.as()); // add "availability" section @@ -221,7 +221,7 @@ void Shower::set_shower_state(bool state, bool force) { ha_configdone_ = Mqtt::queue_ha(topic, doc.as()); // publish the config payload with retain flag // - // shower duaration + // shower duration // doc.clear(); @@ -241,9 +241,9 @@ void Shower::set_shower_state(bool state, bool force) { // doc["ent_cat"] = "diagnostic"; JsonObject dev2 = doc.createNestedObject("dev"); - dev2["name"] = "EMS-ESP"; + dev2["name"] = "EMS-ESP Shower"; JsonArray ids2 = dev2.createNestedArray("ids"); - ids2.add(Mqtt::basename()); + ids2.add(Mqtt::basename() + "-shower"); Mqtt::add_avty_to_doc(stat_t, doc.as(), "value_json.duration is defined"); // add "availability" section @@ -268,9 +268,9 @@ void Shower::set_shower_state(bool state, bool force) { // doc["ent_cat"] = "diagnostic"; JsonObject dev3 = doc.createNestedObject("dev"); - dev3["name"] = "EMS-ESP"; + dev3["name"] = "EMS-ESP Shower"; JsonArray ids3 = dev3.createNestedArray("ids"); - ids3.add(Mqtt::basename()); + ids3.add(Mqtt::basename() + "-shower"); Mqtt::add_avty_to_doc(stat_t, doc.as(), "value_json.timestamp is defined"); // add "availability" section diff --git a/src/system.cpp b/src/system.cpp index aa32ae67d..d327c0fa9 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -1112,7 +1112,7 @@ bool System::check_upgrade(bool factory_settings) { #if defined(EMSESP_DEBUG) if (!missing_version) { - LOG_INFO("Current version from settings is %d.%d.%d-%s", + LOG_INFO("Checking version (settings has %d.%d.%d-%s)...", settings_version.major(), settings_version.minor(), settings_version.patch(), @@ -1120,26 +1120,20 @@ bool System::check_upgrade(bool factory_settings) { } #endif - // always save the new version to the settings - EMSESP::webSettingsService.update( - [&](WebSettings & settings) { - settings.version = EMSESP_APP_VERSION; - return StateUpdateResult::CHANGED; - }, - "local"); - if (factory_settings) { return false; // fresh install, do nothing } version::Semver200_version this_version(EMSESP_APP_VERSION); + bool save_version = true; + // compare versions - bool reboot_required = false; if (this_version > settings_version) { + // need upgrade LOG_NOTICE("Upgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); - // if we're coming from 3.4.4 or 3.5.0b14 then we need to apply new settings + // if we're coming from 3.4.4 or 3.5.0b14 which had no version stored then we need to apply new settings if (missing_version) { LOG_DEBUG("Setting MQTT Entity ID format to v3.4 format"); EMSESP::esp8266React.getMqttSettingsService()->update( @@ -1149,15 +1143,26 @@ bool System::check_upgrade(bool factory_settings) { }, "local"); } - } else if (this_version < settings_version) { + // need downgrade LOG_NOTICE("Downgrading to version %d.%d.%d-%s", this_version.major(), this_version.minor(), this_version.patch(), this_version.prerelease().c_str()); } else { // same version, do nothing - return false; + save_version = false; } - return reboot_required; + // if we did a change, set the new version and reboot + if (save_version) { + EMSESP::webSettingsService.update( + [&](WebSettings & settings) { + settings.version = EMSESP_APP_VERSION; + return StateUpdateResult::CHANGED; + }, + "local"); + return true; // need reboot + } + + return false; } // list commands @@ -1220,15 +1225,14 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp node["free app"] = EMSESP::system_.appFree(); // kilobytes node["partition"] = esp_ota_get_running_partition()->label; - // TODO test if works - const esp_app_desc_t * desc = esp_ota_get_app_description(); + // hash: Helpers::data_to_hex(desc->app_elf_sha256, sizeof(desc->app_elf_sha256)); + const esp_app_desc_t * desc = + esp_ota_get_app_description(); // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/misc_system_api.html#_CPPv414esp_app_desc_t if (desc != nullptr) { - node["app_build"] = std::string(desc->date) + " " + desc->time + " hash: " + Helpers::data_to_hex(desc->app_elf_sha256, sizeof(desc->app_elf_sha256)); + node["app_build"] = std::string(desc->date) + " " + desc->time; } -#endif - node["reset reason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1); + node["build_date"] = std::string(__DATE__) + " " + __TIME__; -#ifndef EMSESP_STANDALONE // Network Status node = output.createNestedObject("Network Info"); if (EMSESP::system_.ethernet_connected()) { diff --git a/src/temperaturesensor.cpp b/src/temperaturesensor.cpp index 3f91c463c..31c50599b 100644 --- a/src/temperaturesensor.cpp +++ b/src/temperaturesensor.cpp @@ -544,9 +544,9 @@ void TemperatureSensor::publish_values(const bool force) { config["name"] = name; JsonObject dev = config.createNestedObject("dev"); - dev["name"] = Mqtt::basename(); + dev["name"] = Mqtt::basename() + " Temperature"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-temperature"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/test/test.cpp b/src/test/test.cpp index ac72bacfd..c5e3cf040 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -282,8 +282,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const run_test("general"); // add sensors - emsesp::EMSESP::analogsensor_.test(); - emsesp::EMSESP::temperaturesensor_.test(); + EMSESP::analogsensor_.test(); + EMSESP::temperaturesensor_.test(); // shell.invoke_command("show devices"); shell.invoke_command("show values"); @@ -704,7 +704,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const if (command == "temperature") { shell.printfln("Testing adding Temperature sensor"); - emsesp::EMSESP::temperaturesensor_.test(); + EMSESP::temperaturesensor_.test(); ok = true; } @@ -714,7 +714,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::temperaturesensor_.test(); shell.invoke_command("show values"); shell.invoke_command("call system publish"); @@ -732,7 +732,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const Mqtt::nested_format(1); // Mqtt::nested_format(0); - emsesp::EMSESP::analogsensor_.test(); + EMSESP::analogsensor_.test(); shell.invoke_command("show values"); // shell.invoke_command("call system publish"); // shell.invoke_command("show mqtt"); @@ -956,19 +956,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const /* requestX.url("/api/system"); // check if defaults to info EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/system/info"); EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/thermostat"); // check if defaults to values EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/thermostat/info"); EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("*"); + EMSESP::logger().notice("*"); requestX.url("/api/thermostat/seltemp"); EMSESP::webAPIService.webAPIService_get(&requestX); @@ -984,7 +984,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const /* requestX.url("/api/temperaturesensor/xxxx"); EMSESP::webAPIService.webAPIService_get(&requestX); - emsesp::EMSESP::logger().notice("****"); + EMSESP::logger().notice("****"); requestX.url("/api/temperaturesensor/info"); EMSESP::webAPIService.webAPIService_get(&requestX); return; @@ -1160,7 +1160,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const request.url("/api"); EMSESP::webAPIService.webAPIService_post(&request, json); - emsesp::EMSESP::logger().warning("* these next ones should fail *"); + EMSESP::logger().warning("* these next ones should fail *"); // write value from web - testing hc9/seltemp - should fail! char data8[] = "{\"id\":2,\"devicevalue\":{\"v\":\"55\",\"u\":1,\"n\":\"hc2 selected room temperature\",\"c\":\"hc9/seltemp\"}"; diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index 2ec12d390..2e6fcb3b2 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -31,10 +31,10 @@ WebAPIService::WebAPIService(PsychicHttpServer * server, SecurityManager * secur } void WebAPIService::registerURI() { - // POST /{device}[/{hc|id}][/{name}] + // POST /api/{device}[/{hc|wwc|id}][/{name}] // note: must explicity use 'Content-Type: application/json' in header _server->on(EMSESP_API_SERVICE_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) { - // if no body then treat it as a secure GET + // if no json body then treat it as a secure GET if (!json.is()) { StaticJsonDocument input_doc; JsonObject input = input_doc.to(); @@ -49,7 +49,15 @@ void WebAPIService::registerURI() { // GET /{device}/{entity} _server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, [this](PsychicRequest * request) { StaticJsonDocument input_doc; - JsonObject input = input_doc.to(); + JsonObject input = input_doc.to(); // empty input json + return parse(request, input); + }); + + + // GET - for when using GET query parameters, the old style from v2 + _server->on("/api", HTTP_GET, [this](PsychicRequest * request) { + StaticJsonDocument input_doc; + JsonObject input = input_doc.to(); // empty input json return parse(request, input); }); @@ -75,33 +83,40 @@ esp_err_t WebAPIService::parse(PsychicRequest * request, JsonObject & input) { // check for query parameters first, the old style from v2 // api?device={device}&cmd={name}&data={value}&id={hc} // TODO check if this works, because we're using wildcard now for api/* - if (request->url() == "/api") { - // get the device - if (request->hasParam(F_(device))) { - input["device"] = request->getParam(F_(device))->value().c_str(); - } - if (request->hasParam(F_(cmd))) { - input["cmd"] = request->getParam(F_(cmd))->value().c_str(); - } - if (request->hasParam(F_(data))) { - input["data"] = request->getParam(F_(data))->value().c_str(); - } - if (request->hasParam(F_(value))) { - input["value"] = request->getParam(F_(value))->value().c_str(); - } - if (request->hasParam(F_(id))) { - input["id"] = Helpers::atoint(request->getParam(F_(id))->value().c_str()); - } - if (request->hasParam(F_(hc))) { - input["hc"] = Helpers::atoint(request->getParam(F_(hc))->value().c_str()); - } - if (request->hasParam(F_(wwc))) { - input["wwc"] = Helpers::atoint(request->getParam(F_(wwc))->value().c_str()); - } + EMSESP::logger().info("API URL: %s", request->url().c_str()); // TODO remove + if (request->hasParam(F_(device))) { + input["device"] = request->getParam(F_(device))->value().c_str(); + EMSESP::logger().info("API: have device"); // TODO remove } + // if (request->url() == "/api") { + // get the device + if (request->hasParam(F_(device))) { + input["device"] = request->getParam(F_(device))->value().c_str(); + } + if (request->hasParam(F_(cmd))) { + input["cmd"] = request->getParam(F_(cmd))->value().c_str(); + } + if (request->hasParam(F_(data))) { + input["data"] = request->getParam(F_(data))->value().c_str(); + } + if (request->hasParam(F_(value))) { + input["value"] = request->getParam(F_(value))->value().c_str(); + } + if (request->hasParam(F_(id))) { + input["id"] = Helpers::atoint(request->getParam(F_(id))->value().c_str()); + } + if (request->hasParam(F_(hc))) { + input["hc"] = Helpers::atoint(request->getParam(F_(hc))->value().c_str()); + } + if (request->hasParam(F_(wwc))) { + input["wwc"] = Helpers::atoint(request->getParam(F_(wwc))->value().c_str()); + } + serializeJson(input, Serial); // TODO remove + // } + // capture current heap memory before allocating the large return buffer - emsesp::EMSESP::system_.refreshHeapMem(); + EMSESP::system_.refreshHeapMem(); // output json buffer size_t buffer = EMSESP_JSON_SIZE_XXXLARGE; @@ -129,7 +144,7 @@ esp_err_t WebAPIService::parse(PsychicRequest * request, JsonObject & input) { } else { snprintf(error, sizeof(error), "API failed with error code (%s)", Command::return_code_string(return_code).c_str()); } - emsesp::EMSESP::logger().err(error); + EMSESP::logger().err(error); api_fails_++; // FAIL, OK, NOT_FOUND, ERROR, NOT_ALLOWED = 400 (bad request), 200 (OK), 400 (not found), 400 (bad request), 401 (unauthorized) diff --git a/src/web/WebAPIService.h b/src/web/WebAPIService.h index fb4f3e7ee..22fbb56b6 100644 --- a/src/web/WebAPIService.h +++ b/src/web/WebAPIService.h @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 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 diff --git a/src/web/WebCustomEntityService.cpp b/src/web/WebCustomEntityService.cpp index d1a645174..68c6da4db 100644 --- a/src/web/WebCustomEntityService.cpp +++ b/src/web/WebCustomEntityService.cpp @@ -431,9 +431,9 @@ void WebCustomEntityService::publish(const bool force) { } } JsonObject dev = config.createNestedObject("dev"); - dev["name"] = Mqtt::basename(); + dev["name"] = Mqtt::basename() + " Custom"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-custom"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 2998e937c..7f21f5c40 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -256,7 +256,7 @@ esp_err_t WebCustomizationService::customization_entities(PsychicRequest * reque } else { emsdevice->setCustomizationEntity(id_s); } - // emsesp::EMSESP::logger().info(id.as()); + // EMSESP::logger().info(id.as()); } // add deleted entities from file read([&](WebCustomization & settings) { diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index e7393a0cb..e51cdf1db 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -189,7 +189,7 @@ esp_err_t WebDataService::device_data(PsychicRequest * request) { for (const auto & emsdevice : EMSESP::emsdevices) { if (emsdevice->unique_id() == id) { // wait max 2.5 sec for updated data (post_send_delay is 2 sec) - for (uint16_t i = 0; i < (emsesp::TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) { + for (uint16_t i = 0; i < (TxService::POST_SEND_DELAY + 500) && EMSESP::wait_validate(); i++) { delay(1); } EMSESP::wait_validate(0); // reset in case of timeout diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index cf1f02cae..c6989396d 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -292,9 +292,9 @@ void WebSchedulerService::publish(const bool force) { } JsonObject dev = config.createNestedObject("dev"); - dev["name"] = Mqtt::basename(); + dev["name"] = Mqtt::basename() + " Scheduler"; JsonArray ids = dev.createNestedArray("ids"); - ids.add(Mqtt::basename()); + ids.add(Mqtt::basename() + "-scheduler"); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); @@ -356,7 +356,7 @@ bool WebSchedulerService::command(const char * cmd, const char * data) { } else { snprintf(error, sizeof(error), "Scheduled command %s failed with error code (%s)", cmd, Command::return_code_string(return_code).c_str()); } - emsesp::EMSESP::logger().err(error); + EMSESP::logger().err(error); return false; } diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 65ceee4d7..cb68c66c8 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -350,7 +350,7 @@ void WebSettingsService::onUpdate() { } if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) { - emsesp::Mqtt::reset_mqtt(); // reload MQTT, init HA etc + Mqtt::reset_mqtt(); // reload MQTT, init HA etc } WebSettings::reset_flags();