From 80ec1859e49f66a4e57c50c7aef6684ed8c37834 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 18 Sep 2020 18:13:09 +0200 Subject: [PATCH] refactor commands to its own class, implement rest API #506 --- CHANGELOG.md | 3 + lib_standalone/ESP8266React.h | 13 + lib_standalone/ESPAsyncWebServer.h | 67 +++++- lib_standalone/WString.h | 7 +- src/EMSESPAPIService.cpp | 103 ++++++++ src/EMSESPAPIService.h | 41 ++++ src/EMSESPDevicesService.cpp | 3 +- src/EMSESPStatusService.cpp | 2 - src/command.cpp | 186 ++++++++++++++ src/command.h | 77 ++++++ src/console.cpp | 11 + src/devices/boiler.cpp | 139 ++++++----- src/devices/boiler.h | 30 +-- src/devices/mixing.cpp | 10 +- src/devices/thermostat.cpp | 375 +++++++++++++++++------------ src/devices/thermostat.h | 67 +++--- src/emsdevice.cpp | 119 ++++----- src/emsdevice.h | 18 +- src/emsesp.cpp | 19 +- src/emsesp.h | 3 + src/locale_EN.h | 12 +- src/mqtt.cpp | 68 +----- src/mqtt.h | 26 +- src/system.cpp | 107 ++++++-- src/system.h | 15 +- src/test/test.cpp | 8 +- src/version.h | 2 +- 27 files changed, 1049 insertions(+), 482 deletions(-) create mode 100644 src/EMSESPAPIService.cpp create mode 100644 src/EMSESPAPIService.h create mode 100644 src/command.cpp create mode 100644 src/command.h diff --git a/CHANGELOG.md b/CHANGELOG.md index db5451e12..990236540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - support for uploading compressed firmware binaries - add wWType to MQTT publish - option to set the MQTT retain flag +- HTTP REST API, e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 +- `show commands` command ### Fixed - fix wwontime readback @@ -21,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - renamed wWCircPumpType to wWChargeType - Installation doc moved to wiki +- Removed the _cmd from the MQTT topic names ### Removed - diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index f6007fd40..7183c4a36 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -31,6 +31,12 @@ class DummySettings { String jwtSecret = "ems-esp"; String ssid = "ems-esp"; String password = "ems-esp"; + String localIP; + String gatewayIP; + String subnetMask; + String staticIPConfig; + String dnsIP1; + String dnsIP2; uint16_t publish_time_boiler; uint16_t publish_time_thermostat; uint16_t publish_time_solar; @@ -105,4 +111,11 @@ class EMSESPSettingsService { private: }; +class JsonUtils { + public: + static void writeIP(JsonObject & root, const String & key, const String & ip) { + root[key] = ip; + } +}; + #endif diff --git a/lib_standalone/ESPAsyncWebServer.h b/lib_standalone/ESPAsyncWebServer.h index 7b4a188e2..b5472799e 100644 --- a/lib_standalone/ESPAsyncWebServer.h +++ b/lib_standalone/ESPAsyncWebServer.h @@ -12,6 +12,39 @@ class AsyncWebServerRequest; class AsyncWebServerResponse; class AsyncJsonResponse; +class AsyncWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0) + : _name(name) + , _value(value) + , _size(size) + , _isForm(form) + , _isFile(file) { + } + const String & name() const { + return _name; + } + const String & value() const { + return _value; + } + size_t size() const { + return _size; + } + bool isPost() const { + return _isForm; + } + bool isFile() const { + return _isFile; + } +}; + typedef enum { HTTP_GET = 0b00000001, HTTP_POST = 0b00000010, @@ -54,9 +87,41 @@ class AsyncWebServerRequest { void send(AsyncWebServerResponse * response){}; void send(AsyncJsonResponse * response){}; void send(int code, const String & contentType = String(), const String & content = String()){}; + void send(int code, const String & contentType, const __FlashStringHelper *){}; + + bool hasParam(const String & name, bool post, bool file) const { + return false; + } + + bool hasParam(const char * name, bool post, bool file) const { + return false; + } + + bool hasParam(const __FlashStringHelper * data) const { + return false; + } + + bool hasParam(const __FlashStringHelper * data, bool post, bool file) const { + return false; + } + + AsyncWebParameter * getParam(const String & name, bool post, bool file) const { + return nullptr; + } + + AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const { + return nullptr; + } + + AsyncWebParameter * getParam(const __FlashStringHelper * data) const { + return nullptr; + } + + AsyncWebParameter * getParam(size_t num) const { + return nullptr; + } AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) { - // AsyncWebServerResponse *a = new AsyncWebServerResponse() return nullptr; } diff --git a/lib_standalone/WString.h b/lib_standalone/WString.h index c7b481bf2..fe4b2dfe0 100644 --- a/lib_standalone/WString.h +++ b/lib_standalone/WString.h @@ -33,14 +33,13 @@ class String { return lhs; } - /// bool isEmpty() { return _str.empty(); } - // long toInt() const { - // return std::stol(_str); - // } + long toInt() const { + return std::stol(_str); + } bool equals(const char * s) { return _str == s; diff --git a/src/EMSESPAPIService.cpp b/src/EMSESPAPIService.cpp new file mode 100644 index 000000000..c1df17702 --- /dev/null +++ b/src/EMSESPAPIService.cpp @@ -0,0 +1,103 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 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 "EMSESPAPIService.h" +#include "emsesp.h" +#include "command.h" +#include "emsdevice.h" +#include "system.h" + +namespace emsesp { + +EMSESPAPIService::EMSESPAPIService(AsyncWebServer * server) { + server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&EMSESPAPIService::emsespAPIService, this, std::placeholders::_1)); +} + +// http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 +void EMSESPAPIService::emsespAPIService(AsyncWebServerRequest * request) { + // must have device and cmd parameters + if ((!request->hasParam(F_(device))) || (!request->hasParam(F_(cmd)))) { + request->send(400, "text/plain", F("invalid syntax")); + return; + } + + // get device + String device = request->getParam(F_(device))->value(); + uint8_t device_type = EMSdevice::device_name_2_device_type(device.c_str()); + if (device_type == emsesp::EMSdevice::DeviceType::UNKNOWN) { + request->send(400, "text/plain", F("invalid device")); + return; + } + + // get cmd, we know we have one + String cmd = request->getParam(F_(cmd))->value(); + + // first test for special service commands + // e.g. http://ems-esp/api?device=system&cmd=info + if (device.equals("system")) { + if (cmd.equals("info")) { + request->send(200, "text/plain", System::export_settings()); + EMSESP::logger().info(F("Sent settings json to web UI")); + return; + } + } + + // look up command in our list + if (!Command::find(device_type, cmd.c_str())) { + request->send(400, "text/plain", F("invalid cmd")); + return; + } + + String data; + if (request->hasParam(F_(data))) { + data = request->getParam(F_(data))->value(); + } + + String id; + if (request->hasParam(F_(id))) { + id = request->getParam(F_(id))->value(); + } + + // execute the command + bool ok = false; + if (data.isEmpty()) { + ok = Command::call_command(device_type, cmd.c_str(), nullptr, -1); // command only + } else if (id.isEmpty()) { + ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), -1); // only ID + } else { + ok = Command::call_command(device_type, cmd.c_str(), data.c_str(), id.toInt()); // has cmd, data and id + } + +// debug +#if defined(EMSESP_DEBUG) + std::string output(200, '\0'); + snprintf_P(&output[0], + output.capacity() + 1, + PSTR("API: device=%s cmd=%s data=%s id=%s [%s]"), + device.c_str(), + cmd.c_str(), + data.c_str(), + id.c_str(), + ok ? F("OK") : F("Failed")); + EMSESP::logger().info(output.c_str()); +#endif + + request->send(200, "text/plain", ok ? F("OK") : F("Failed")); +} + +} // namespace emsesp \ No newline at end of file diff --git a/src/EMSESPAPIService.h b/src/EMSESPAPIService.h new file mode 100644 index 000000000..90d45c11d --- /dev/null +++ b/src/EMSESPAPIService.h @@ -0,0 +1,41 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 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 EMSESPAPIService_h +#define EMSESPAPIService_h + +#include +#include +#include + +#define EMSESP_API_SERVICE_PATH "/api" + +namespace emsesp { + +class EMSESPAPIService { + public: + EMSESPAPIService(AsyncWebServer * server); + + private: + void emsespAPIService(AsyncWebServerRequest * request); + +}; + +} // namespace emsesp + +#endif diff --git a/src/EMSESPDevicesService.cpp b/src/EMSESPDevicesService.cpp index a914d89c0..8bfd0cb66 100644 --- a/src/EMSESPDevicesService.cpp +++ b/src/EMSESPDevicesService.cpp @@ -18,7 +18,6 @@ #include "EMSESPDevicesService.h" #include "emsesp.h" -#include "mqtt.h" namespace emsesp { @@ -57,7 +56,7 @@ void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) { obj["type"] = emsdevice->device_type_name(); obj["brand"] = emsdevice->brand_to_string(); obj["name"] = emsdevice->name(); - obj["deviceid"] = emsdevice->get_device_id(); + obj["deviceid"] = emsdevice->device_id(); obj["productid"] = emsdevice->product_id(); obj["version"] = emsdevice->version(); } diff --git a/src/EMSESPStatusService.cpp b/src/EMSESPStatusService.cpp index c0f6252b9..ca54c0393 100644 --- a/src/EMSESPStatusService.cpp +++ b/src/EMSESPStatusService.cpp @@ -18,8 +18,6 @@ #include "EMSESPStatusService.h" #include "emsesp.h" -#include "mqtt.h" -#include "version.h" namespace emsesp { diff --git a/src/command.cpp b/src/command.cpp new file mode 100644 index 000000000..7694caf29 --- /dev/null +++ b/src/command.cpp @@ -0,0 +1,186 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 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 "command.h" +#include "emsdevice.h" +#include "emsesp.h" + +namespace emsesp { + +uuid::log::Logger Command::logger_{F_(command), uuid::log::Facility::DAEMON}; + +std::vector Command::cmdfunctions_; + +// calls a command, context is the device_type +// id may be used to represent a heating circuit for example +// returns false if error or not found +bool Command::call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { +#ifdef EMSESP_DEBUG + if (id == -1) { + LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value); + } else { + LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d"), cmd, value, id); + } +#endif + + if (!cmdfunctions_.empty()) { + for (const auto & cf : cmdfunctions_) { + if (cf.device_type_ == device_type) { + const char * cf_cmd = uuid::read_flash_string(cf.cmd_).c_str(); + if (strcmp(cf_cmd, cmd) == 0) { + return ((cf.cmdfunction_)(value, id)); // call function, data needs to be a string and can be null + } + } + } + } + return false; +} + +// add a command to the list +void Command::add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) { + cmdfunctions_.emplace_back(device_type, device_id, cmd, cb); + + // see if we need to subscribe + Mqtt::register_command(device_type, device_id, cmd, cb); +} + +// see if a command exists for that device type +bool Command::find(const uint8_t device_type, const char * cmd) { + for (const auto & cf : cmdfunctions_) { + if (strcmp(cmd, uuid::read_flash_string(cf.cmd_).c_str()) == 0) { + return true; + } + } + return false; // not found +} + +// output list of all commands to console for a specific DeviceType +void Command::show_commands(uuid::console::Shell & shell, uint8_t device_type) { + for (const auto & cf : Command::commands()) { + if (cf.device_type_ == device_type) { + shell.printf("%s ", uuid::read_flash_string(cf.cmd_).c_str()); + } + } + shell.println(); +} + +// determines the device_type from the shell context we're in +uint8_t Command::context_2_device_type(unsigned int context) { + if (context == ShellContext::MAIN) { + return EMSdevice::DeviceType::SERVICEKEY; + } + if (context == ShellContext::BOILER) { + return EMSdevice::DeviceType::BOILER; + } + if (context == ShellContext::MIXING) { + return EMSdevice::DeviceType::MIXING; + } + if (context == ShellContext::SOLAR) { + return EMSdevice::DeviceType::SOLAR; + } + if (context == ShellContext::SYSTEM) { + return EMSdevice::DeviceType::SERVICEKEY; + } + if (context == ShellContext::THERMOSTAT) { + return EMSdevice::DeviceType::THERMOSTAT; + } + + return EMSdevice::DeviceType::UNKNOWN; // unknown type +} + +// show command per current context +void Command::show_commands(uuid::console::Shell & shell) { + show_commands(shell, context_2_device_type(shell.context())); +} + + +// output list of all commands to console +void Command::show_all_commands(uuid::console::Shell & shell) { + // show system first + shell.printf("%s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SERVICEKEY).c_str()); + show_commands(shell, EMSdevice::DeviceType::SERVICEKEY); + + // do this in the order of factory classes to keep a consistent order when displaying + for (const auto & device_class : EMSFactory::device_handlers()) { + for (const auto & emsdevice : EMSESP::emsdevices) { + if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { + shell.printf("%s: ", EMSdevice::device_type_2_device_name(device_class.first).c_str()); + show_commands(shell, device_class.first); + } + } + } +} + +// given a context, automatically add the commands to the console +void Command::add_context_commands(unsigned int context) { + // if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit + flash_string_vector params; + if ((context == ShellContext::THERMOSTAT) || (context == ShellContext::MIXING)) { + params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(hc_optional)}; + } else if ((context == ShellContext::MAIN) || (context == ShellContext::SYSTEM)) { + params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(n_optional)}; + } else { + params = flash_string_vector{F_(cmd_optional), F_(data_optional)}; + } + + EMSESPShell::commands->add_command( + context, + CommandFlags::ADMIN, + flash_string_vector{F_(call)}, + params, + [&](Shell & shell, const std::vector & arguments) { + if (arguments.empty()) { + // list options + shell.print("Available commands: "); + show_commands(shell); + shell.println(); + return; + } + + // determine the device_type from the shell context + uint8_t device_type = context_2_device_type(shell.context()); + + const char * cmd = arguments[0].c_str(); + if (arguments.size() == 1) { + // no value specified + (void)Command::call_command(device_type, cmd, nullptr, -1); + } else if (arguments.size() == 2) { + // has a value but no id + (void)Command::call_command(device_type, cmd, arguments.back().c_str(), -1); + } else { + // use value, which could be an id or hc + (void)Command::call_command(device_type, cmd, arguments[1].c_str(), atoi(arguments[2].c_str())); + } + }, + [&](Shell & shell __attribute__((unused)), const std::vector & arguments) -> std::vector { + if (arguments.size() > 0) { + return {}; + } + std::vector commands; + uint8_t device_type = context_2_device_type(shell.context()); + for (const auto & cf : Command::commands()) { + if (cf.device_type_ == device_type) { + commands.emplace_back(uuid::read_flash_string(cf.cmd_)); + } + } + return commands; + }); +} + + +} // namespace emsesp \ No newline at end of file diff --git a/src/command.h b/src/command.h new file mode 100644 index 000000000..f020ff2d5 --- /dev/null +++ b/src/command.h @@ -0,0 +1,77 @@ +/* + * EMS-ESP - https://github.com/proddy/EMS-ESP + * Copyright 2019 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 EMSESP_COMMAND_H_ +#define EMSESP_COMMAND_H_ + +#include +#include + +#include +#include +#include + +#include "console.h" + +#include + +using uuid::console::Shell; + +namespace emsesp { + +using cmdfunction_p = std::function; + +class Command { + public: + struct CmdFunction { + uint8_t device_type_; // DeviceType:: + uint8_t device_id_; + const __FlashStringHelper * cmd_; + cmdfunction_p cmdfunction_; + + CmdFunction(uint8_t device_type, uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cmdfunction) + : device_type_(device_type) + , device_id_(device_id) + , cmd_(cmd) + , cmdfunction_(cmdfunction) { + } + }; + + static std::vector commands() { + return cmdfunctions_; + } + + static bool call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id); + static void add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb); + static void show_all_commands(uuid::console::Shell & shell); + static void show_commands(uuid::console::Shell & shell); + static void add_context_commands(unsigned int context); + static bool find(const uint8_t device_type, const char * cmd); + + static std::vector cmdfunctions_; // list of commands + + private: + static uuid::log::Logger logger_; + + static void show_commands(uuid::console::Shell & shell, uint8_t device_type); + static uint8_t context_2_device_type(unsigned int context); +}; + +} // namespace emsesp + +#endif \ No newline at end of file diff --git a/src/console.cpp b/src/console.cpp index 5809ff19f..288fa599c 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -149,6 +149,12 @@ void EMSESPShell::add_console_commands() { flash_string_vector{F_(show), F_(mqtt)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); }); + + commands->add_command(ShellContext::MAIN, + CommandFlags::USER, + flash_string_vector{F_(show), F_(commands)}, + [](Shell & shell, const std::vector & arguments __attribute__((unused))) { Command::show_all_commands(shell); }); + commands->add_command( ShellContext::MAIN, CommandFlags::ADMIN, @@ -457,6 +463,11 @@ void Console::load_standard_commands(unsigned int context) { shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id); } }); + + + // load the commands (console & mqtt topics) for this specific context + Command::add_context_commands(context); + } // prompt, change per context diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index f10e875dd..1daa9e6b5 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -52,22 +52,22 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0xE9, F("UBADHWStatus"), false, [&](std::shared_ptr t) { process_UBADHWStatus(t); }); register_telegram_type(0xEA, F("UBAParameterWWPlus"), true, [&](std::shared_ptr t) { process_UBAParameterWWPlus(t); }); - // MQTT commands for boiler_cmd topic - register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { set_warmwater_mode(value, id); }); - register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { set_warmwater_activated(value, id); }); - register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { set_tapwarmwater_activated(value, id); }); - register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { set_warmwater_onetime(value, id); }); - register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { set_warmwater_circulation(value, id); }); - register_mqtt_cmd(F("flowtemp"), [&](const char * value, const int8_t id) { set_flow_temp(value, id); }); - register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { set_warmwater_temp(value, id); }); - register_mqtt_cmd(F("heatingactivated"), [&](const char * value, const int8_t id) { set_heating_activated(value, id); }); - register_mqtt_cmd(F("heatingtemp"), [&](const char * value, const int8_t id) { set_heating_temp(value, id); }); - register_mqtt_cmd(F("burnmaxpower"), [&](const char * value, const int8_t id) { set_max_power(value, id); }); - register_mqtt_cmd(F("burnminpower"), [&](const char * value, const int8_t id) { set_min_power(value, id); }); - register_mqtt_cmd(F("boilhyston"), [&](const char * value, const int8_t id) { set_hyst_on(value, id); }); - register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { set_hyst_off(value, id); }); - register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { set_burn_period(value, id); }); - register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { set_pump_delay(value, id); }); + // MQTT commands for boiler topic + register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { return set_warmwater_mode(value, id); }); + register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { return set_warmwater_activated(value, id); }); + register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { return set_tapwarmwater_activated(value, id); }); + register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_warmwater_onetime(value, id); }); + register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { return set_warmwater_circulation(value, id); }); + register_mqtt_cmd(F("flowtemp"), [&](const char * value, const int8_t id) { return set_flow_temp(value, id); }); + register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { return set_warmwater_temp(value, id); }); + register_mqtt_cmd(F("heatingactivated"), [&](const char * value, const int8_t id) { return set_heating_activated(value, id); }); + register_mqtt_cmd(F("heatingtemp"), [&](const char * value, const int8_t id) { return set_heating_temp(value, id); }); + register_mqtt_cmd(F("burnmaxpower"), [&](const char * value, const int8_t id) { return set_max_power(value, id); }); + register_mqtt_cmd(F("burnminpower"), [&](const char * value, const int8_t id) { return set_min_power(value, id); }); + register_mqtt_cmd(F("boilhyston"), [&](const char * value, const int8_t id) { return set_hyst_on(value, id); }); + register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { return set_hyst_off(value, id); }); + register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); }); + register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); }); EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { mqtt_format_ = settings.mqtt_format; // single, nested or ha @@ -85,7 +85,6 @@ void Boiler::add_context_menu() { flash_string_vector{F_(boiler)}, [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { Boiler::console_commands(shell, ShellContext::BOILER); - add_context_commands(ShellContext::BOILER); }); } @@ -765,16 +764,11 @@ void Boiler::process_UBAMaintenanceData(std::shared_ptr telegram } } -/* - * Commands - */ - - // Set the warm water temperature 0x33 -void Boiler::set_warmwater_temp(const char * value, const int8_t id) { +bool Boiler::set_warmwater_temp(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler warm water temperature to %d C"), v); @@ -784,24 +778,28 @@ void Boiler::set_warmwater_temp(const char * value, const int8_t id) { write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW); write_command(EMS_TYPE_UBAFlags, 3, v, EMS_TYPE_UBAParameterWW); // for i9000, see #397 } + + return true; } // flow temp -void Boiler::set_flow_temp(const char * value, const int8_t id) { +bool Boiler::set_flow_temp(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler flow temperature to %d C"), v); write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints); + + return true; } // set min boiler output -void Boiler::set_heating_activated(const char * value, const int8_t id) { +bool Boiler::set_heating_activated(const char * value, const int8_t id) { bool v = false; if (!Helpers::value2bool(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler heating "), v ? "on" : "off"); if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { @@ -809,41 +807,49 @@ void Boiler::set_heating_activated(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAParameters, 0, v ? 0xFF : 0, EMS_TYPE_UBAParameters); } + + return true; } // set heating maximum temperature -void Boiler::set_heating_temp(const char * value, const int8_t id) { +bool Boiler::set_heating_temp(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } + LOG_INFO(F("Setting boiler heating temperature to "), v); if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { write_command(EMS_TYPE_UBAParametersPlus, 1, v, EMS_TYPE_UBAParametersPlus); } else { write_command(EMS_TYPE_UBAParameters, 1, v, EMS_TYPE_UBAParameters); } + + return true; } // set min boiler output -void Boiler::set_min_power(const char * value, const int8_t id) { +bool Boiler::set_min_power(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } + LOG_INFO(F("Setting boiler min power to "), v); if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { write_command(EMS_TYPE_UBAParametersPlus, 7, v, EMS_TYPE_UBAParametersPlus); } else { write_command(EMS_TYPE_UBAParameters, 3, v, EMS_TYPE_UBAParameters); } + + return true; } // set max temp -void Boiler::set_max_power(const char * value, const int8_t id) { +bool Boiler::set_max_power(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler max power to %d C"), v); @@ -852,13 +858,15 @@ void Boiler::set_max_power(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAParameters, 2, v, EMS_TYPE_UBAParameters); } + + return true; } // set boiler on hysteresis -void Boiler::set_hyst_on(const char * value, const int8_t id) { +bool Boiler::set_hyst_on(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler hysteresis on to %d C"), v); @@ -867,13 +875,15 @@ void Boiler::set_hyst_on(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAParameters, 5, v, EMS_TYPE_UBAParameters); } + + return true; } // set boiler off hysteresis -void Boiler::set_hyst_off(const char * value, const int8_t id) { +bool Boiler::set_hyst_off(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler hysteresis off to %d C"), v); @@ -882,13 +892,15 @@ void Boiler::set_hyst_off(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAParameters, 4, v, EMS_TYPE_UBAParameters); } + + return true; } // set min burner period -void Boiler::set_burn_period(const char * value, const int8_t id) { +bool Boiler::set_burn_period(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } LOG_INFO(F("Setting burner min. period to %d min"), v); @@ -897,30 +909,36 @@ void Boiler::set_burn_period(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAParameters, 6, v, EMS_TYPE_UBAParameters); } + + return true; } // set pump delay -void Boiler::set_pump_delay(const char * value, const int8_t id) { +bool Boiler::set_pump_delay(const char * value, const int8_t id) { int v = 0; if (!Helpers::value2number(value, v)) { - return; + return false; } if (get_toggle_fetch(EMS_TYPE_UBAParameters)) { LOG_INFO(F("Setting boiler pump delay to %d min"), v); write_command(EMS_TYPE_UBAParameters, 8, v, EMS_TYPE_UBAParameters); } + + return true; } // note some boilers do not have this setting, than it's done by thermostat // on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent -void Boiler::set_warmwater_mode(const char * value, const int8_t id) { +bool Boiler::set_warmwater_mode(const char * value, const int8_t id) { if (value == nullptr) { - return; + return false; } + if (!get_toggle_fetch(EMS_TYPE_UBAParameterWW)) { - return; + return false; } + uint8_t set; if (strcmp(value, "hot") == 0) { LOG_INFO(F("Setting boiler warm water to Hot")); @@ -932,16 +950,18 @@ void Boiler::set_warmwater_mode(const char * value, const int8_t id) { LOG_INFO(F("Setting boiler warm water to Intelligent")); set = 0xEC; } else { - return; // do nothing + return false; // do nothing } + write_command(EMS_TYPE_UBAParameterWW, 9, set, EMS_TYPE_UBAParameterWW); + return true; } // turn on/off warm water -void Boiler::set_warmwater_activated(const char * value, const int8_t id) { +bool Boiler::set_warmwater_activated(const char * value, const int8_t id) { bool v = false; if (!Helpers::value2bool(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler warm water %s"), v ? "on" : "off"); @@ -953,19 +973,22 @@ void Boiler::set_warmwater_activated(const char * value, const int8_t id) { } else { n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off } + if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { write_command(EMS_TYPE_UBAParameterWWPlus, 1, v ? 1 : 0, EMS_TYPE_UBAParameterWWPlus); } else { write_command(EMS_TYPE_UBAParameterWW, 1, n, EMS_TYPE_UBAParameterWW); } + + return true; } // Activate / De-activate the Warm Tap Water // Note: Using the type 0x1D to put the boiler into Test mode. This may be shown on the boiler with a flashing 'T' -void Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) { +bool Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) { bool v = false; if (!Helpers::value2bool(value, v)) { - return; + return false; } LOG_INFO(F("Setting tap warm tap water %s"), v ? "on" : "off"); @@ -990,15 +1013,17 @@ void Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) { } write_command(EMS_TYPE_UBAFunctionTest, 0, message_data, sizeof(message_data), 0); + + return true; } // Activate / De-activate One Time warm water 0x35 // true = on, false = off // See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers -void Boiler::set_warmwater_onetime(const char * value, const int8_t id) { +bool Boiler::set_warmwater_onetime(const char * value, const int8_t id) { bool v = false; if (!Helpers::value2bool(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off"); @@ -1007,14 +1032,16 @@ void Boiler::set_warmwater_onetime(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x34); } + + return true; } // Activate / De-activate circulation of warm water 0x35 // true = on, false = off -void Boiler::set_warmwater_circulation(const char * value, const int8_t id) { +bool Boiler::set_warmwater_circulation(const char * value, const int8_t id) { bool v = false; if (!Helpers::value2bool(value, v)) { - return; + return false; } LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off"); @@ -1023,6 +1050,8 @@ void Boiler::set_warmwater_circulation(const char * value, const int8_t id) { } else { write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x34); } + + return true; } // add console commands @@ -1034,7 +1063,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) { [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { uint16_t type_id = Helpers::hextoint(arguments.front().c_str()); EMSESP::set_read_id(type_id); - EMSESP::send_read_request(type_id, get_device_id()); + EMSESP::send_read_request(type_id, device_id()); }); EMSESPShell::commands->add_command(ShellContext::BOILER, diff --git a/src/devices/boiler.h b/src/devices/boiler.h index 49ab43024..d1c63d244 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -167,21 +167,21 @@ class Boiler : public EMSdevice { void process_UBADHWStatus(std::shared_ptr telegram); // commands - none of these use the additional id parameter - void set_warmwater_mode(const char * value, const int8_t id); - void set_warmwater_activated(const char * value, const int8_t id); - void set_tapwarmwater_activated(const char * value, const int8_t id); - void set_warmwater_onetime(const char * value, const int8_t id); - void set_warmwater_circulation(const char * value, const int8_t id); - void set_warmwater_temp(const char * value, const int8_t id); - void set_flow_temp(const char * value, const int8_t id); - void set_heating_activated(const char * value, const int8_t id); - void set_heating_temp(const char * value, const int8_t id); - void set_min_power(const char * value, const int8_t id); - void set_max_power(const char * value, const int8_t id); - void set_hyst_on(const char * value, const int8_t id); - void set_hyst_off(const char * value, const int8_t id); - void set_burn_period(const char * value, const int8_t id); - void set_pump_delay(const char * value, const int8_t id); + bool set_warmwater_mode(const char * value, const int8_t id); + bool set_warmwater_activated(const char * value, const int8_t id); + bool set_tapwarmwater_activated(const char * value, const int8_t id); + bool set_warmwater_onetime(const char * value, const int8_t id); + bool set_warmwater_circulation(const char * value, const int8_t id); + bool set_warmwater_temp(const char * value, const int8_t id); + bool set_flow_temp(const char * value, const int8_t id); + bool set_heating_activated(const char * value, const int8_t id); + bool set_heating_temp(const char * value, const int8_t id); + bool set_min_power(const char * value, const int8_t id); + bool set_max_power(const char * value, const int8_t id); + bool set_hyst_on(const char * value, const int8_t id); + bool set_hyst_off(const char * value, const int8_t id); + bool set_burn_period(const char * value, const int8_t id); + bool set_pump_delay(const char * value, const int8_t id); }; } // namespace emsesp diff --git a/src/devices/mixing.cpp b/src/devices/mixing.cpp index ee8077761..2026c158f 100644 --- a/src/devices/mixing.cpp +++ b/src/devices/mixing.cpp @@ -158,7 +158,7 @@ void Mixing::publish_values() { char topic[30]; strlcpy(topic, "mixing_data", 30); - strlcat(topic, Helpers::itoa(s, get_device_id() - 0x20 + 1), 30); // append hc to topic + strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic Mqtt::publish(topic, doc); } @@ -190,7 +190,7 @@ void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr tel // A1 00 FF 00 00 0C 02 04 00 01 1D 00 82 void Mixing::process_IPMStatusMessage(std::shared_ptr telegram) { type_ = Type::HC; - hc_ = get_device_id() - 0x20 + 1; + hc_ = device_id() - 0x20 + 1; uint8_t ismixed = 0; changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed if (ismixed == 0) { @@ -213,7 +213,7 @@ void Mixing::process_MMStatusMessage(std::shared_ptr telegram) { // the heating circuit is determine by which device_id it is, 0x20 - 0x23 // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 - hc_ = get_device_id() - 0x20 + 1; + hc_ = device_id() - 0x20 + 1; changed_ |= telegram->read_value(flowTemp_, 1); // is * 10 changed_ |= telegram->read_value(pump_, 3); changed_ |= telegram->read_value(flowSetTemp_, 0); @@ -226,7 +226,7 @@ void Mixing::process_MMStatusMessage(std::shared_ptr telegram) { // Mixing on a MM10 - 0xAA // e.g. Thermostat -> Mixing Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx void Mixing::process_MMConfigMessage(std::shared_ptr telegram) { - hc_ = get_device_id() - 0x20 + 1; + hc_ = device_id() - 0x20 + 1; // pos 0: active FF = on // pos 1: valve runtime 0C = 120 sec in units of 10 sec } @@ -234,7 +234,7 @@ void Mixing::process_MMConfigMessage(std::shared_ptr telegram) { // Mixing on a MM10 - 0xAC // e.g. Thermostat -> Mixing Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB void Mixing::process_MMSetMessage(std::shared_ptr telegram) { - hc_ = get_device_id() - 0x20 + 1; + hc_ = device_id() - 0x20 + 1; // pos 0: flowtemp setpoint 1E = 30°C // pos 1: position in % } diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 834c824d7..fbb205de9 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -222,7 +222,7 @@ void Thermostat::device_info_web(JsonArray & root) { // context menu "thermostat" void Thermostat::add_context_menu() { // only add it once, to prevent conflicts when there are multiple thermostats - if (this->get_device_id() != EMSESP::actual_master_thermostat()) { + if (this->device_id() != EMSESP::actual_master_thermostat()) { return; } @@ -231,7 +231,6 @@ void Thermostat::add_context_menu() { flash_string_vector{F_(thermostat)}, [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { Thermostat::console_commands(shell, ShellContext::THERMOSTAT); - add_context_commands(ShellContext::THERMOSTAT); }); } @@ -239,7 +238,7 @@ void Thermostat::add_context_menu() { // we check if any of the thermostat values have changed and then republish if necessary bool Thermostat::updated_values() { // only publish on the master thermostat - if (EMSESP::actual_master_thermostat() != this->get_device_id()) { + if (EMSESP::actual_master_thermostat() != this->device_id()) { return false; } if (changed_) { @@ -252,7 +251,7 @@ bool Thermostat::updated_values() { // publish values via MQTT void Thermostat::publish_values() { // only publish on the master thermostat - if (EMSESP::actual_master_thermostat() != this->get_device_id()) { + if (EMSESP::actual_master_thermostat() != this->device_id()) { return; } @@ -1191,7 +1190,7 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) { [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { uint16_t type_id = Helpers::hextoint(arguments.front().c_str()); EMSESP::set_read_id(type_id); - EMSESP::send_read_request(type_id, this->get_device_id()); + EMSESP::send_read_request(type_id, this->device_id()); }); EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, @@ -1217,50 +1216,61 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) { } // 0xA5 - Set minimum external temperature -void Thermostat::set_minexttemp(const char * value, const int8_t id) { +bool Thermostat::set_minexttemp(const char * value, const int8_t id) { int mt = 0; if (!Helpers::value2number(value, mt)) { - return; + return false; } + LOG_INFO(F("Setting min external temperature to %d"), mt); write_command(EMS_TYPE_IBASettings, 5, mt, EMS_TYPE_IBASettings); + + return true; } // 0xA5 - Clock offset -void Thermostat::set_clockoffset(const char * value, const int8_t id) { +bool Thermostat::set_clockoffset(const char * value, const int8_t id) { int co = 0; if (!Helpers::value2number(value, co)) { - return; + return false; } + LOG_INFO(F("Setting clock offset to %d"), co); write_command(EMS_TYPE_IBASettings, 12, co, EMS_TYPE_IBASettings); + + return true; } // 0xA5 - Calibrate internal temperature -void Thermostat::set_calinttemp(const char * value, const int8_t id) { +bool Thermostat::set_calinttemp(const char * value, const int8_t id) { int ct = 0; if (!Helpers::value2number(value, ct)) { - return; + return false; } - // does this value need to be multiple by 10? + LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10); write_command(EMS_TYPE_IBASettings, 2, ct, EMS_TYPE_IBASettings); + + return true; } // 0xA5 - Set the display settings -void Thermostat::set_display(const char * value, const int8_t id) { +bool Thermostat::set_display(const char * value, const int8_t id) { int ds = 0; if (!Helpers::value2number(value, ds)) { - return; + return false; } + LOG_INFO(F("Setting display to %d"), ds); write_command(EMS_TYPE_IBASettings, 0, ds, EMS_TYPE_IBASettings); + + return true; } -void Thermostat::set_remotetemp(const char * value, const int8_t id) { +bool Thermostat::set_remotetemp(const char * value, const int8_t id) { float f = 0; if (!Helpers::value2float(value, f)) { - return; + return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; @@ -1270,13 +1280,15 @@ void Thermostat::set_remotetemp(const char * value, const int8_t id) { } else { Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); } + + return true; } // 0xA5 - Set the building settings -void Thermostat::set_building(const char * value, const int8_t id) { +bool Thermostat::set_building(const char * value, const int8_t id) { std::string bd(20, '\0'); if (!Helpers::value2string(value, bd)) { - return; + return false; } uint8_t bg = 0; @@ -1287,28 +1299,33 @@ void Thermostat::set_building(const char * value, const int8_t id) { } else if (bd == "heavy") { bg = 2; } else { - return; // invalid + return false; // invalid } LOG_INFO(F("Setting building to %d"), bg); write_command(EMS_TYPE_IBASettings, 6, bg, EMS_TYPE_IBASettings); + + return true; } // 0xA5 Set the language settings -void Thermostat::set_language(const char * value, const int8_t id) { +bool Thermostat::set_language(const char * value, const int8_t id) { int lg = 0; if (!Helpers::value2number(value, lg)) { - return; + return false; } + LOG_INFO(F("Setting language to %d"), lg); write_command(EMS_TYPE_IBASettings, 1, lg, EMS_TYPE_IBASettings); + + return true; } // Set the control-mode for hc 0-off, 1-RC20, 2-RC3x -void Thermostat::set_control(const char * value, const int8_t id) { +bool Thermostat::set_control(const char * value, const int8_t id) { int ctrl = 0; if (!Helpers::value2number(value, ctrl)) { - return; + return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; @@ -1316,23 +1333,25 @@ void Thermostat::set_control(const char * value, const int8_t id) { std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { LOG_WARNING(F("Set control: Heating Circuit %d not found or activated"), hc_num); - return; + return false; } if (ctrl > 2) { LOG_WARNING(F("Set control: Invalid control mode: %d"), ctrl); - return; + return false; } LOG_INFO(F("Setting circuit-control for hc%d to %d"), hc_num, ctrl); write_command(set_typeids[hc->hc_num() - 1], 26, ctrl); + + return true; } // sets the thermostat ww working mode, where mode is a string -void Thermostat::set_wwmode(const char * value, const int8_t id) { +bool Thermostat::set_wwmode(const char * value, const int8_t id) { std::string v(10, '\0'); if (!Helpers::value2string(value, v)) { - return; + return false; } uint8_t set = 0xFF; // some dummy value @@ -1350,21 +1369,24 @@ void Thermostat::set_wwmode(const char * value, const int8_t id) { } else { LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str()); } + + return true; } // set the holiday as string dd.mm.yyyy-dd.mm.yyyy -void Thermostat::set_holiday(const char * value, const int8_t id) { +bool Thermostat::set_holiday(const char * value, const int8_t id) { std::string hd(30, '\0'); if (!Helpers::value2string(value, hd)) { - return; + return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { - LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->get_device_id()); - return; + LOG_WARNING(F("Set holiday: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id()); + return false; } + uint8_t data[6]; data[0] = (hd[0] - '0') * 10 + (hd[1] - '0'); data[1] = (hd[3] - '0') * 10 + (hd[4] - '0'); @@ -1375,49 +1397,56 @@ void Thermostat::set_holiday(const char * value, const int8_t id) { LOG_INFO(F("Setting holiday for hc %d"), hc->hc_num()); write_command(timer_typeids[hc->hc_num() - 1], 87, data, 6, 0); + + return true; } // set pause in hours -void Thermostat::set_pause(const char * value, const int8_t id) { +bool Thermostat::set_pause(const char * value, const int8_t id) { int hrs = 0; if (!Helpers::value2number(value, hrs)) { - return; + return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { - LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->get_device_id()); - return; + LOG_WARNING(F("Set pause: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id()); + return false; } + LOG_INFO(F("Setting pause: %d hours, hc: %d"), hrs, hc->hc_num()); write_command(timer_typeids[hc->hc_num() - 1], 85, hrs); + + return true; } // set partymode in hours -void Thermostat::set_party(const char * value, const int8_t id) { +bool Thermostat::set_party(const char * value, const int8_t id) { int hrs = 0; if (!Helpers::value2number(value, hrs)) { - return; + return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { - LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->get_device_id()); - return; + LOG_WARNING(F("Set party: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id()); + return false; } LOG_INFO(F("Setting party: %d hours, hc: %d"), hrs, hc->hc_num()); write_command(timer_typeids[hc->hc_num() - 1], 86, hrs); + + return true; } // set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time // dw - day of week (0..6), dst- summertime (0/1) // id is ignored -void Thermostat::set_datetime(const char * value, const int8_t id) { +bool Thermostat::set_datetime(const char * value, const int8_t id) { std::string dt(30, '\0'); if (!Helpers::value2string(value, dt)) { - return; + return false; } uint8_t data[9]; @@ -1426,8 +1455,9 @@ void Thermostat::set_datetime(const char * value, const int8_t id) { tm * tm_ = localtime(&now); if (tm_->tm_year < 110) { // no NTP time LOG_WARNING(F("No NTP time. Cannot set RCtime")); - return; + return false; } + data[0] = tm_->tm_year - 100; // Bosch counts from 2000 data[1] = tm_->tm_mon; data[2] = tm_->tm_hour; @@ -1449,53 +1479,65 @@ void Thermostat::set_datetime(const char * value, const int8_t id) { data[6] = (dt[20] - '0'); // day of week data[7] = (dt[22] - '0') + 2; // DST and flag } + LOG_INFO(F("Setting date and time")); write_command(EMS_TYPE_time, 0, data, 8, EMS_TYPE_time); + + return true; } // sets the thermostat working mode, where mode is a string // converts string mode to HeatingCircuit::Mode -void Thermostat::set_mode(const char * value, const int8_t id) { +bool Thermostat::set_mode(const char * value, const int8_t id) { std::string mode(10, '\0'); if (!Helpers::value2string(value, mode)) { - return; + return false; } uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; - if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) { - set_mode_n(HeatingCircuit::Mode::OFF, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { - set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { - set_mode_n(HeatingCircuit::Mode::AUTO, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { - set_mode_n(HeatingCircuit::Mode::DAY, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { - set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { - set_mode_n(HeatingCircuit::Mode::HEAT, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { - set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { - set_mode_n(HeatingCircuit::Mode::ECO, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { - set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { - set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num); - } else { - LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str()); + return set_mode_n(HeatingCircuit::Mode::OFF, hc_num); } + if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { + return set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { + return set_mode_n(HeatingCircuit::Mode::AUTO, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { + return set_mode_n(HeatingCircuit::Mode::DAY, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { + return set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { + return set_mode_n(HeatingCircuit::Mode::HEAT, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { + return set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { + return set_mode_n(HeatingCircuit::Mode::ECO, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { + return set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { + return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num); + } + + LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str()); + return false; } // Set the thermostat working mode // mode is HeatingCircuit::Mode -void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { +bool Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { // get hc based on number std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { LOG_WARNING(F("set mode: Heating Circuit %d not found or activated"), hc_num); - return; + return false; } uint8_t set_mode_value, offset; @@ -1578,47 +1620,61 @@ void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { // add the write command to the Tx queue // post validate is the corresponding monitor or set type IDs as they can differ per model write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid); + + return true; } // sets the thermostat temp, where mode is a string -void Thermostat::set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num) { +bool Thermostat::set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num) { if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::MANUAL, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::AUTO, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::DAY, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::NIGHT, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::COMFORT, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::HEAT, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::ECO, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::NOFROST, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::SUMMER) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::SUMMER, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::HOLIDAY, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::OFFSET) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::OFFSET, hc_num); - } else if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) { - set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num); - } else { - LOG_WARNING(F("Invalid mode %s."), mode.c_str()); + return set_temperature(temperature, HeatingCircuit::Mode::MANUAL, hc_num); } + if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::AUTO, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::DAY, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::NIGHT, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::COMFORT, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::HEAT, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::ECO, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::NOFROST, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::SUMMER) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::SUMMER, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::HOLIDAY, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::OFFSET) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::OFFSET, hc_num); + } + if (mode_tostring(HeatingCircuit::Mode::DESIGN) == mode) { + return set_temperature(temperature, HeatingCircuit::Mode::DESIGN, hc_num); + } + + LOG_WARNING(F("Invalid mode %s."), mode.c_str()); + return false; } // Set the temperature of the thermostat // the id passed into this function is the heating circuit number -void Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) { +bool Thermostat::set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num) { // get hc based on number std::shared_ptr hc = heating_circuit(hc_num); if (hc == nullptr) { - LOG_WARNING(F("Set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->get_device_id()); - return; + LOG_WARNING(F("Set temperature: Heating Circuit %d not found or activated for device ID 0x%02X"), hc_num, this->device_id()); + return false; } uint8_t model = this->model(); @@ -1790,75 +1846,80 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co // add the write command to the Tx queue. value is *2 // post validate is the corresponding monitor or set type IDs as they can differ per model write_command(set_typeids[hc->hc_num() - 1], offset, (uint8_t)((float)temperature * (float)factor), validate_typeid); + return true; } + + return false; } // for HA specifically when receiving over MQTT -void Thermostat::thermostat_cmd_temp(const char * message) { +bool Thermostat::thermostat_cmd_temp(const char * message) { float f = strtof((char *)message, 0); - set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT); + return set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT); } // for HA specifically when receiving over MQTT // message payload holds the text name of the mode e.g. "auto" -void Thermostat::thermostat_cmd_mode(const char * message) { - set_mode(message, AUTO_HEATING_CIRCUIT); +bool Thermostat::thermostat_cmd_mode(const char * message) { + return set_mode(message, AUTO_HEATING_CIRCUIT); } -void Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) { +bool Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) { float f = 0; uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id; if (Helpers::value2float(value, f)) { - set_temperature(f, mode, hc_num); + return set_temperature(f, mode, hc_num); + } else { + return false; } } -void Thermostat::set_temp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::AUTO); +bool Thermostat::set_temp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::AUTO); } -void Thermostat::set_nighttemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT); +bool Thermostat::set_nighttemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::NIGHT); } -void Thermostat::set_daytemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::DAY); +bool Thermostat::set_daytemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::DAY); } -void Thermostat::set_comforttemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT); +bool Thermostat::set_comforttemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::COMFORT); } -void Thermostat::set_nofrosttemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST); +bool Thermostat::set_nofrosttemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::NOFROST); } -void Thermostat::set_ecotemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::ECO); +bool Thermostat::set_ecotemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::ECO); } -void Thermostat::set_heattemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::HEAT); +bool Thermostat::set_heattemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::HEAT); } -void Thermostat::set_summertemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER); +bool Thermostat::set_summertemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::SUMMER); } -void Thermostat::set_designtemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN); +bool Thermostat::set_designtemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::DESIGN); } -void Thermostat::set_offsettemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET); +bool Thermostat::set_offsettemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::OFFSET); } -void Thermostat::set_holidaytemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY); +bool Thermostat::set_holidaytemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY); } -void Thermostat::set_manualtemp(const char * value, const int8_t id) { - set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL); +bool Thermostat::set_manualtemp(const char * value, const int8_t id) { + return set_temperature_value(value, id, HeatingCircuit::Mode::MANUAL); } // commands for MQTT and Console @@ -1869,48 +1930,48 @@ void Thermostat::add_commands() { } // common to all thermostats - register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { set_wwmode(value, id); }); - register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { set_temp(value, id); }); - register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { set_mode(value, id); }); + register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); }); + register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { return set_temp(value, id); }); + register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { return set_mode(value, id); }); uint8_t model = this->model(); switch (model) { case EMS_DEVICE_FLAG_RC100: case EMS_DEVICE_FLAG_RC300: - register_mqtt_cmd(F("manualtemp"), [&](const char * value, const int8_t id) { set_manualtemp(value, id); }); - register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { set_ecotemp(value, id); }); - register_mqtt_cmd(F("comforttemp"), [&](const char * value, const int8_t id) { set_comforttemp(value, id); }); + register_mqtt_cmd(F("manualtemp"), [&](const char * value, const int8_t id) { return set_manualtemp(value, id); }); + register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { return set_ecotemp(value, id); }); + register_mqtt_cmd(F("comforttemp"), [&](const char * value, const int8_t id) { return set_comforttemp(value, id); }); break; case EMS_DEVICE_FLAG_RC20_2: - register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); }); - register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { set_daytemp(value, id); }); + register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); }); + register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); }); break; case EMS_DEVICE_FLAG_RC30_1: // only RC30_1 - register_mqtt_cmd(F("clockoffset"), [&](const char * value, const int8_t id) { set_clockoffset(value, id); }); - register_mqtt_cmd(F("language"), [&](const char * value, const int8_t id) { set_language(value, id); }); - register_mqtt_cmd(F("display"), [&](const char * value, const int8_t id) { set_display(value, id); }); + register_mqtt_cmd(F("clockoffset"), [&](const char * value, const int8_t id) { return set_clockoffset(value, id); }); + register_mqtt_cmd(F("language"), [&](const char * value, const int8_t id) { return set_language(value, id); }); + register_mqtt_cmd(F("display"), [&](const char * value, const int8_t id) { return set_display(value, id); }); case EMS_DEVICE_FLAG_RC35: // RC30 and RC35 - register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { set_nighttemp(value, id); }); - register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { set_daytemp(value, id); }); - register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { set_nofrosttemp(value, id); }); - register_mqtt_cmd(F("remotetemp"), [&](const char * value, const int8_t id) { set_remotetemp(value, id); }); - register_mqtt_cmd(F("datetime"), [&](const char * value, const int8_t id) { set_datetime(value, id); }); - register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { set_minexttemp(value, id); }); - register_mqtt_cmd(F("calinttemp"), [&](const char * value, const int8_t id) { set_calinttemp(value, id); }); - register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { set_building(value, id); }); - register_mqtt_cmd(F("control"), [&](const char * value, const int8_t id) { set_control(value, id); }); - register_mqtt_cmd(F("pause"), [&](const char * value, const int8_t id) { set_pause(value, id); }); - register_mqtt_cmd(F("party"), [&](const char * value, const int8_t id) { set_party(value, id); }); - register_mqtt_cmd(F("holiday"), [&](const char * value, const int8_t id) { set_holiday(value, id); }); - register_mqtt_cmd(F("summertemp"), [&](const char * value, const int8_t id) { set_summertemp(value, id); }); - register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { set_designtemp(value, id); }); - register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { set_offsettemp(value, id); }); - register_mqtt_cmd(F("holidaytemp"), [&](const char * value, const int8_t id) { set_holidaytemp(value, id); }); + register_mqtt_cmd(F("nighttemp"), [&](const char * value, const int8_t id) { return set_nighttemp(value, id); }); + register_mqtt_cmd(F("daytemp"), [&](const char * value, const int8_t id) { return set_daytemp(value, id); }); + register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); }); + register_mqtt_cmd(F("remotetemp"), [&](const char * value, const int8_t id) { return set_remotetemp(value, id); }); + register_mqtt_cmd(F("datetime"), [&](const char * value, const int8_t id) { return set_datetime(value, id); }); + register_mqtt_cmd(F("minexttemp"), [&](const char * value, const int8_t id) { return set_minexttemp(value, id); }); + register_mqtt_cmd(F("calinttemp"), [&](const char * value, const int8_t id) { return set_calinttemp(value, id); }); + register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); }); + register_mqtt_cmd(F("control"), [&](const char * value, const int8_t id) { return set_control(value, id); }); + register_mqtt_cmd(F("pause"), [&](const char * value, const int8_t id) { return set_pause(value, id); }); + register_mqtt_cmd(F("party"), [&](const char * value, const int8_t id) { return set_party(value, id); }); + register_mqtt_cmd(F("holiday"), [&](const char * value, const int8_t id) { return set_holiday(value, id); }); + register_mqtt_cmd(F("summertemp"), [&](const char * value, const int8_t id) { return set_summertemp(value, id); }); + register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { return set_designtemp(value, id); }); + register_mqtt_cmd(F("offsettemp"), [&](const char * value, const int8_t id) { return set_offsettemp(value, id); }); + register_mqtt_cmd(F("holidaytemp"), [&](const char * value, const int8_t id) { return set_holidaytemp(value, id); }); break; case EMS_DEVICE_FLAG_JUNKERS: - register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { set_nofrosttemp(value, id); }); - register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { set_ecotemp(value, id); }); - register_mqtt_cmd(F("heattemp"), [&](const char * value, const int8_t id) { set_heattemp(value, id); }); + register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); }); + register_mqtt_cmd(F("ecotemp"), [&](const char * value, const int8_t id) { return set_ecotemp(value, id); }); + register_mqtt_cmd(F("heattemp"), [&](const char * value, const int8_t id) { return set_heattemp(value, id); }); break; default: break; diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index a2f13bbfb..39fa79b60 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -246,48 +246,47 @@ class Thermostat : public EMSdevice { void process_RC300WWmode(std::shared_ptr telegram); // internal helper functions - void set_mode_n(const uint8_t mode, const uint8_t hc_num); + bool set_mode_n(const uint8_t mode, const uint8_t hc_num); - void set_temperature_value(const char * value, const int8_t id, const uint8_t mode); - void set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num); - void set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num); + bool set_temperature_value(const char * value, const int8_t id, const uint8_t mode); + bool set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num); + bool set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num); // for HA specifically. MQTT functions. - void thermostat_cmd_temp(const char * message); - void thermostat_cmd_mode(const char * message); + bool thermostat_cmd_temp(const char * message); + bool thermostat_cmd_mode(const char * message); // set functions - these use the id/hc - void set_mode(const char * value, const int8_t id); - void set_control(const char * value, const int8_t id); - void set_holiday(const char * value, const int8_t id); - void set_pause(const char * value, const int8_t id); - void set_party(const char * value, const int8_t id); + bool set_mode(const char * value, const int8_t id); + bool set_control(const char * value, const int8_t id); + bool set_holiday(const char * value, const int8_t id); + bool set_pause(const char * value, const int8_t id); + bool set_party(const char * value, const int8_t id); - void set_temp(const char * value, const int8_t id); - void set_nighttemp(const char * value, const int8_t id); - void set_daytemp(const char * value, const int8_t id); - void set_comforttemp(const char * value, const int8_t id); - void set_nofrosttemp(const char * value, const int8_t id); - void set_ecotemp(const char * value, const int8_t id); - void set_heattemp(const char * value, const int8_t id); - void set_summertemp(const char * value, const int8_t id); - void set_designtemp(const char * value, const int8_t id); - void set_offsettemp(const char * value, const int8_t id); - void set_holidaytemp(const char * value, const int8_t id); - void set_manualtemp(const char * value, const int8_t id); - - void set_remotetemp(const char * value, const int8_t id); + bool set_temp(const char * value, const int8_t id); + bool set_nighttemp(const char * value, const int8_t id); + bool set_daytemp(const char * value, const int8_t id); + bool set_comforttemp(const char * value, const int8_t id); + bool set_nofrosttemp(const char * value, const int8_t id); + bool set_ecotemp(const char * value, const int8_t id); + bool set_heattemp(const char * value, const int8_t id); + bool set_summertemp(const char * value, const int8_t id); + bool set_designtemp(const char * value, const int8_t id); + bool set_offsettemp(const char * value, const int8_t id); + bool set_holidaytemp(const char * value, const int8_t id); + bool set_manualtemp(const char * value, const int8_t id); + bool set_remotetemp(const char * value, const int8_t id); // set functions - these don't use the id/hc, the parameters are ignored - void set_wwmode(const char * value, const int8_t id); - void set_datetime(const char * value, const int8_t id); - void set_minexttemp(const char * value, const int8_t id); - void set_clockoffset(const char * value, const int8_t id); - void set_calinttemp(const char * value, const int8_t id); - void set_display(const char * value, const int8_t id); - void set_building(const char * value, const int8_t id); - void set_language(const char * value, const int8_t id); -}; // namespace emsesp + bool set_wwmode(const char * value, const int8_t id); + bool set_datetime(const char * value, const int8_t id); + bool set_minexttemp(const char * value, const int8_t id); + bool set_clockoffset(const char * value, const int8_t id); + bool set_calinttemp(const char * value, const int8_t id); + bool set_display(const char * value, const int8_t id); + bool set_building(const char * value, const int8_t id); + bool set_language(const char * value, const int8_t id); +}; } // namespace emsesp diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index fbdced66c..af6fb9180 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -18,7 +18,6 @@ #include "emsdevice.h" #include "emsesp.h" -#include "mqtt.h" // for the mqtt_function_p namespace emsesp { @@ -54,30 +53,30 @@ std::string EMSdevice::brand_to_string() const { } // returns the name of the MQTT topic to use for a specific device -std::string EMSdevice::device_type_topic_name(const uint8_t device_type) { +std::string EMSdevice::device_type_2_device_name(const uint8_t device_type) { switch (device_type) { case DeviceType::SERVICEKEY: - return read_flash_string(F("system_cmd")); + return read_flash_string(F("system")); break; case DeviceType::BOILER: - return read_flash_string(F("boiler_cmd")); + return read_flash_string(F("boiler")); break; case DeviceType::THERMOSTAT: - return read_flash_string(F("thermostat_cmd")); + return read_flash_string(F("thermostat")); break; case DeviceType::HEATPUMP: - return read_flash_string(F("heatpump_cmd")); + return read_flash_string(F("heatpump")); break; case DeviceType::SOLAR: - return read_flash_string(F("solar_cmd")); + return read_flash_string(F("solar")); break; case DeviceType::MIXING: - return read_flash_string(F("mixing_cmd")); + return read_flash_string(F("mixing")); break; default: @@ -86,6 +85,35 @@ std::string EMSdevice::device_type_topic_name(const uint8_t device_type) { } } +// returns device_type from a string +uint8_t EMSdevice::device_name_2_device_type(const char * topic) { + if (strcmp(topic, "boiler") == 0) { + return DeviceType::BOILER; + } + + if (strcmp(topic, "thermostat") == 0) { + return DeviceType::THERMOSTAT; + } + + if (strcmp(topic, "system") == 0) { + return DeviceType::SERVICEKEY; + } + + if (strcmp(topic, "heatpump") == 0) { + return DeviceType::HEATPUMP; + } + + if (strcmp(topic, "solar") == 0) { + return DeviceType::SOLAR; + } + + if (strcmp(topic, "mixing") == 0) { + return DeviceType::MIXING; + } + + return DeviceType::UNKNOWN; +} + std::string EMSdevice::device_type_name() const { switch (device_type_) { case DeviceType::BOILER: @@ -202,7 +230,7 @@ void EMSdevice::show_values(uuid::console::Shell & shell) { // for each telegram that has the fetch value set (true) do a read request void EMSdevice::fetch_values() { - LOG_DEBUG(F("Fetching values for device ID 0x%02X"), get_device_id()); + LOG_DEBUG(F("Fetching values for device ID 0x%02X"), device_id()); for (const auto & tf : telegram_functions_) { if (tf.fetch_) { @@ -213,7 +241,7 @@ void EMSdevice::fetch_values() { // toggle on/off automatic fetch for a telegram id void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) { - LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), get_device_id(), telegram_id, toggle); + LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), device_id(), telegram_id, toggle); for (auto & tf : telegram_functions_) { if (tf.telegram_type_id_ == telegram_id) { @@ -226,7 +254,7 @@ void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) { bool EMSdevice::get_toggle_fetch(uint16_t telegram_id) { for (auto & tf : telegram_functions_) { if (tf.telegram_type_id_ == telegram_id) { - return tf.fetch_ ; + return tf.fetch_; } } return false; @@ -256,8 +284,9 @@ void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_ Mqtt::subscribe(this->device_type_, topic, f); } -void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f) { - Mqtt::add_command(this->device_type_, this->device_id_, cmd, f); +// add command to library +void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f) { + Command::add_command(this->device_type_, this->device_id_, cmd, f); } // register a call back function for a specific telegram type @@ -306,22 +335,22 @@ bool EMSdevice::handle_telegram(std::shared_ptr telegram) { // send Tx write with a data block void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) { - EMSESP::send_write_request(type_id, this->get_device_id(), offset, message_data, message_length, validate_typeid); + EMSESP::send_write_request(type_id, this->device_id(), offset, message_data, message_length, validate_typeid); } // send Tx write with a single value void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) { - EMSESP::send_write_request(type_id, this->get_device_id(), offset, value, validate_typeid); + EMSESP::send_write_request(type_id, this->device_id(), offset, value, validate_typeid); } // send Tx write with a single value, with no post validation void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value) { - EMSESP::send_write_request(type_id, this->get_device_id(), offset, value, 0); + EMSESP::send_write_request(type_id, this->device_id(), offset, value, 0); } // send Tx read command to the device void EMSdevice::read_command(const uint16_t type_id) { - EMSESP::send_read_request(type_id, get_device_id()); + EMSESP::send_read_request(type_id, device_id()); } // prints a string value to the console @@ -339,60 +368,4 @@ void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const shell.printfln(PSTR("%s: %s"), uuid::read_flash_string(name).c_str(), value); } -// given a context, automatically add the commands taken them from the MQTT registry for "_cmd" topics -void EMSdevice::add_context_commands(unsigned int context) { - // if we're adding commands for a thermostat or mixing, then include an additional optional paramter called heating circuit - flash_string_vector params; - if ((context == ShellContext::THERMOSTAT) || (context == ShellContext::MIXING)) { - params = flash_string_vector{F_(cmd_optional), F_(data_optional), F_(hc_optional)}; - } else { - params = flash_string_vector{F_(cmd_optional), F_(data_optional)}; - } - - EMSESPShell::commands->add_command( - context, - CommandFlags::ADMIN, - flash_string_vector{F_(call)}, - params, - [&](Shell & shell, const std::vector & arguments) { - uint8_t device_type_ = device_type(); - if (arguments.empty()) { - // list options - shell.print("Available commands:"); - for (const auto & cf : Mqtt::commands()) { - if (cf.device_type_ == device_type_) { - shell.printf(" %s", uuid::read_flash_string(cf.cmd_).c_str()); - } - } - shell.println(); - return; - } - - const char * cmd = arguments[0].c_str(); - if (arguments.size() == 1) { - // no value specified - Mqtt::call_command(device_type_, cmd, nullptr, -1); - } else if (arguments.size() == 2) { - // has a value but no id - Mqtt::call_command(device_type_, cmd, arguments.back().c_str(), -1); - } else { - // use value, which could be an id or hc - Mqtt::call_command(device_type_, cmd, arguments[1].c_str(), atoi(arguments[2].c_str())); - } - }, - [&](Shell & shell __attribute__((unused)), const std::vector & arguments) -> std::vector { - if (arguments.size() > 0) { - return {}; - } - std::vector commands; - for (const auto & cf : Mqtt::commands()) { - if (cf.device_type_ == device_type()) { - commands.emplace_back(uuid::read_flash_string(cf.cmd_)); - } - } - return commands; - }); -} - - } // namespace emsesp diff --git a/src/emsdevice.h b/src/emsdevice.h index fdd697288..6293ed5ed 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -47,12 +47,13 @@ class EMSdevice { virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class - inline uint8_t get_device_id() const { + inline uint8_t device_id() const { return device_id_; } std::string device_type_name() const; - static std::string device_type_topic_name(const uint8_t device_type); + static std::string device_type_2_device_name(const uint8_t device_type); + static uint8_t device_name_2_device_type(const char * topic); inline uint8_t product_id() const { return product_id_; @@ -127,20 +128,17 @@ class EMSdevice { void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value); - void read_command(const uint16_t type_id); - void add_context_commands(unsigned int context); - void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f); - void register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f); + void register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f); // virtual functions overrules by derived classes virtual void show_values(uuid::console::Shell & shell) = 0; virtual void publish_values() = 0; virtual bool updated_values() = 0; virtual void add_context_menu() = 0; - virtual void device_info_web(JsonArray & root) = 0; + virtual void device_info_web(JsonArray & root) = 0; std::string telegram_type_name(std::shared_ptr telegram); @@ -230,7 +228,7 @@ class EMSdevice { }; enum DeviceType : uint8_t { - SERVICEKEY = 0, // this is us + SERVICEKEY = 0, // this is us (EMS-ESP) BOILER, THERMOSTAT, MIXING, @@ -239,8 +237,8 @@ class EMSdevice { GATEWAY, SWITCH, CONTROLLER, - CONNECT - + CONNECT, + UNKNOWN }; // device IDs diff --git a/src/emsesp.cpp b/src/emsesp.cpp index a422dbbaf..795da3842 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -36,6 +36,7 @@ EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&web EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager()); EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager()); +EMSESPAPIService EMSESP::emsespAPIService = EMSESPAPIService(&webServer); using DeviceFlags = emsesp::EMSdevice; using DeviceType = emsesp::EMSdevice::DeviceType; @@ -615,7 +616,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) { for (const auto & emsdevice : emsdevices) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); - if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->get_device_id() == actual_master_thermostat())) { + if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) { shell.printf(F(" ** master device **")); } shell.println(); @@ -727,7 +728,7 @@ void EMSESP::send_write_request(const uint16_t type_id, const uint8_t dest, cons // we check if its a complete telegram or just a single byte (which could be a poll or a return status) // the CRC check is not done here, only when it's added to the Rx queue with add() void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { -#ifdef EMSESP_DEBUG +#ifdef EMSESP_UART_DEBUG static uint32_t rx_time_ = 0; #endif // check first for echo @@ -735,9 +736,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { if (((first_value & 0x7F) == txservice_.ems_bus_id()) && (length > 1)) { // if we ask ourself at roomcontrol for version e.g. 0B 98 02 00 20 Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); -#ifdef EMSESP_DEBUG +#ifdef EMSESP_UART_DEBUG // get_uptime is only updated once per loop, does not give the right time - LOG_TRACE(F("[DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); + LOG_TRACE(F("[UART_DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); #endif return; // it's an echo } @@ -786,14 +787,14 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { if (length == 1) { EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active -#ifdef EMSESP_DEBUG +#ifdef EMSESP_UART_DEBUG char s[4]; if (first_value & 0x80) { - LOG_TRACE(F("[DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); + LOG_TRACE(F("[UART_DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); // time measurement starts here, use millis because get_uptime is only updated once per loop rx_time_ = ::millis(); } else { - LOG_TRACE(F("[DEBUG] Poll ack %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); + LOG_TRACE(F("[UART_DEBUG] Poll ack %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_); } #endif // check for poll to us, if so send top message from Tx queue immediately and quit @@ -805,8 +806,8 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { Roomctrl::send(first_value ^ 0x80 ^ rxservice_.ems_mask()); return; } else { -#ifdef EMSESP_DEBUG - LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); +#ifdef EMSESP_UART_DEBUG + LOG_TRACE(F("[UART_DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str()); #endif Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data); // check if there is a message for the roomcontroller diff --git a/src/emsesp.h b/src/emsesp.h index 479a4c35a..6bf913c5e 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -37,6 +37,7 @@ #include "EMSESPStatusService.h" #include "EMSESPDevicesService.h" #include "EMSESPSettingsService.h" +#include "EMSESPAPIService.h" #include "emsdevice.h" #include "emsfactory.h" @@ -47,6 +48,7 @@ #include "console.h" #include "shower.h" #include "roomcontrol.h" +#include "command.h" #define WATCH_ID_NONE 0 // no watch id set @@ -162,6 +164,7 @@ class EMSESP { static EMSESPSettingsService emsespSettingsService; static EMSESPStatusService emsespStatusService; static EMSESPDevicesService emsespDevicesService; + static EMSESPAPIService emsespAPIService; static uuid::log::Logger logger() { return logger_; diff --git a/src/locale_EN.h b/src/locale_EN.h index 548c21d76..169d18f6c 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -61,11 +61,20 @@ MAKE_PSTR_WORD(ssid) MAKE_PSTR_WORD(heartbeat) MAKE_PSTR_WORD(users) MAKE_PSTR_WORD(master) -MAKE_PSTR_WORD(test) MAKE_PSTR_WORD(pin) +#if defined(EMSESP_DEBUG) +MAKE_PSTR_WORD(test) +#endif + // for commands MAKE_PSTR_WORD(call) +MAKE_PSTR_WORD(cmd) +MAKE_PSTR_WORD(id) +MAKE_PSTR_WORD(device) +MAKE_PSTR_WORD(data) +MAKE_PSTR_WORD(command) +MAKE_PSTR_WORD(commands) // devices MAKE_PSTR_WORD(boiler) @@ -104,7 +113,6 @@ MAKE_PSTR(degrees, "°C") MAKE_PSTR(asterisks, "********") MAKE_PSTR(n_mandatory, "") MAKE_PSTR(n_optional, "[n]") -MAKE_PSTR(gpio_mandatory, "") MAKE_PSTR(data_optional, "[data]") MAKE_PSTR(typeid_mandatory, "") MAKE_PSTR(deviceid_mandatory, "") diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 2bfea6436..d3cc9e36c 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -37,7 +37,6 @@ uint32_t Mqtt::publish_time_other_; uint32_t Mqtt::publish_time_sensor_; std::vector Mqtt::mqtt_subfunctions_; -std::vector Mqtt::mqtt_cmdfunctions_; uint16_t Mqtt::mqtt_publish_fails_ = 0; size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES; @@ -67,10 +66,9 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_ mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(message->topic), std::move(cb)); } -// adds a command and callback function for a specific device -void Mqtt::add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb) { - // subscribe to the command topic if it doesn't exist yet - std::string cmd_topic = EMSdevice::device_type_topic_name(device_type); // cmd topic for a device like "_cmd" e.g. "boiler_cmd" +// subscribe to the command topic if it doesn't exist yet +void Mqtt::register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) { + std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); bool exists = false; if (!mqtt_subfunctions_.empty()) { @@ -84,9 +82,7 @@ void Mqtt::add_command(const uint8_t device_type, const uint8_t device_id, const Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function } - LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_topic_name(device_type).c_str()); - - mqtt_cmdfunctions_.emplace_back(device_type, device_id, cmd, cb); + LOG_DEBUG(F("Registering MQTT cmd %s with topic %s"), uuid::read_flash_string(cmd).c_str(), EMSdevice::device_type_2_device_name(device_type).c_str()); } // subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device @@ -156,25 +152,13 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat = settings.system_heartbeat; }); shell.printfln(F_(system_heartbeat_fmt), system_heartbeat ? F_(enabled) : F_(disabled)); - shell.printfln(F("MQTT publish fails: %lu"), mqtt_publish_fails_); + shell.printfln(F("MQTT publish fails count: %lu"), mqtt_publish_fails_); shell.println(); // show subscriptions - shell.printfln(F("MQTT subscriptions:")); + shell.printfln(F("MQTT topic subscriptions:")); for (const auto & mqtt_subfunction : mqtt_subfunctions_) { - // don't show commands if its homeassistant - if ((strncmp(mqtt_subfunction.full_topic_.c_str(), "homeassistant/", 13) == 0)) { - shell.printf(F(" topic: %s"), mqtt_subfunction.full_topic_.c_str()); - } else { - // show the commands associated with this subscription - shell.printf(F(" topic: %s, [cmd]:"), mqtt_subfunction.full_topic_.c_str()); - for (const auto & mqtt_cmdfunction : mqtt_cmdfunctions_) { - if (EMSdevice::device_type_topic_name(mqtt_cmdfunction.device_type_) == mqtt_subfunction.topic_) { - shell.printf(F(" %s"), uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str()); - } - } - } - shell.println(); + shell.printfln(F(" %s"), mqtt_subfunction.full_topic_.c_str()); } shell.println(); @@ -224,31 +208,6 @@ void Mqtt::incoming(const char * topic, const char * payload) { } #endif -// calls a command, context is the device_type -// id may be used to represent a heating circuit for example -bool Mqtt::call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { -#ifdef EMSESP_DEBUG - if (id == -1) { - LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is default"), cmd, value); - } else { - LOG_DEBUG(F("[DEBUG] Calling command %s, value %s, id is %d"), cmd, value, id); - } -#endif - - if (!mqtt_cmdfunctions_.empty()) { - for (const auto & cf : mqtt_cmdfunctions_) { - if (cf.device_type_ == device_type) { - const char * cf_cmd = uuid::read_flash_string(cf.cmd_).c_str(); - if (strcmp(cf_cmd, cmd) == 0) { - (cf.mqtt_cmdfunction_)(value, id); // call function, data needs to be a string and can be null - return true; - } - } - } - } - return false; -} - // received an MQTT message that we subscribed too void Mqtt::on_message(const char * topic, const char * payload, size_t len) { if (len == 0) { @@ -295,13 +254,13 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) { bool cmd_known = false; JsonVariant data = doc["data"]; if (data.is()) { - cmd_known = call_command(mf.device_type_, command, data.as(), n); + cmd_known = Command::call_command(mf.device_type_, command, data.as(), n); } else if (data.is()) { char data_str[10]; - cmd_known = call_command(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as()), n); + cmd_known = Command::call_command(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as()), n); } else if (data.is()) { char data_str[10]; - cmd_known = call_command(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as(), 2), n); + cmd_known = Command::call_command(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as(), 2), n); } if (!cmd_known) { @@ -417,7 +376,6 @@ void Mqtt::start() { }); // create space for command buffer, to avoid heap memory fragmentation - mqtt_cmdfunctions_.reserve(40); // current count with boiler+thermostat is 37 mqtt_subfunctions_.reserve(10); } @@ -493,9 +451,9 @@ void Mqtt::on_connect() { resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics - // these commands respond to the topic "system_cmd" and take a payload like {cmd:"", data:"", id:""} - add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("pin"), System::mqtt_command_pin); - add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("send"), System::mqtt_command_send); + // these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""} + Command::add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("pin"), System::command_pin); + Command::add_command(EMSdevice::DeviceType::SERVICEKEY, bus_id_, F("send"), System::command_send); LOG_INFO(F("MQTT connected")); } diff --git a/src/mqtt.h b/src/mqtt.h index 9a6b4f17c..1abafd70e 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -32,6 +32,7 @@ #include "helpers.h" #include "system.h" #include "console.h" +#include "command.h" #include @@ -44,7 +45,7 @@ using uuid::console::Shell; namespace emsesp { using mqtt_subfunction_p = std::function; -using mqtt_cmdfunction_p = std::function; +using cmdfunction_p = std::function; struct MqttMessage { ~MqttMessage() = default; @@ -85,7 +86,7 @@ class Mqtt { static void subscribe(const std::string & topic, mqtt_subfunction_p cb); static void resubscribe(); - static void add_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb); + static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb); static void publish(const std::string & topic, const std::string & payload); static void publish(const std::string & topic, const JsonDocument & payload); @@ -104,8 +105,6 @@ class Mqtt { static void on_connect(); - static bool call_command(const uint8_t device_type, const char * cmd, const char * value, const int8_t id); - void disconnect() { mqttClient_->disconnect(); } @@ -126,24 +125,6 @@ class Mqtt { mqtt_publish_fails_ = 0; } - struct MQTTCmdFunction { - uint8_t device_type_; - uint8_t device_id_; - const __FlashStringHelper * cmd_; - mqtt_cmdfunction_p mqtt_cmdfunction_; - - MQTTCmdFunction(uint8_t device_type, uint8_t device_id, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction) - : device_type_(device_type) - , device_id_(device_id) - , cmd_(cmd) - , mqtt_cmdfunction_(mqtt_cmdfunction) { - } - }; - - static std::vector commands() { - return mqtt_cmdfunctions_; - } - private: static uuid::log::Logger logger_; @@ -199,7 +180,6 @@ class Mqtt { }; static std::vector mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices - static std::vector mqtt_cmdfunctions_; // list of commands uint32_t last_mqtt_poll_ = 0; uint32_t last_publish_boiler_ = 0; diff --git a/src/system.cpp b/src/system.cpp index 4fe892103..eb8e70ede 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -39,18 +39,22 @@ uint16_t System::analog_ = 0; // send on/off to a gpio pin // value: true = HIGH, false = LOW -void System::mqtt_command_pin(const char * value, const int8_t id) { +bool System::command_pin(const char * value, const int8_t id) { bool v = false; if (Helpers::value2bool(value, v)) { pinMode(id, OUTPUT); digitalWrite(id, v); LOG_INFO(F("GPIO %d set to %s"), id, v ? "HIGH" : "LOW"); + return true; } + + return false; } // send raw -void System::mqtt_command_send(const char * value, const int8_t id) { +bool System::command_send(const char * value, const int8_t id) { EMSESP::send_raw_telegram(value); // ignore id + return true; } // restart EMS-ESP @@ -590,32 +594,12 @@ void System::console_commands(Shell & shell, unsigned int context) { shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks)); }); }); - /* - EMSESPShell::commands->add_command(ShellContext::SYSTEM, - CommandFlags::USER, - flash_string_vector{F_(show), F_(mqtt)}, - [](Shell & shell, const std::vector & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); }); -*/ + EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(show), F_(users)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { System::show_users(shell); }); - EMSESPShell::commands->add_command(ShellContext::SYSTEM, - CommandFlags::ADMIN, - flash_string_vector{F_(pin)}, - flash_string_vector{F_(gpio_mandatory), F_(data_optional)}, - [](Shell & shell, const std::vector & arguments) { - if (arguments.size() == 1) { - shell.printfln(F("use on/off, 1/0 or true/false")); - return; - } - int pin = 0; - if (Helpers::value2number(arguments[0].c_str(), pin)) { - System::mqtt_command_pin(arguments[1].c_str(), pin); - } - }); - // enter the context Console::enter_custom_context(shell, context); } @@ -820,4 +804,81 @@ bool System::check_upgrade() { #endif } +// export all settings to JSON text +// http://ems-esp/api?device=system&cmd=info +String System::export_settings() { + StaticJsonDocument doc; + +#ifndef EMSESP_STANDALONE + JsonObject root = doc.to(); + + EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { + JsonObject node = root.createNestedObject("WIFI"); + node["ssid"] = settings.ssid; + // node["password"] = settings.password; + node["hostname"] = settings.hostname; + node["static_ip_config"] = settings.staticIPConfig; + JsonUtils::writeIP(node, "local_ip", settings.localIP); + JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP); + JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask); + JsonUtils::writeIP(node, "dns_ip_1", settings.dnsIP1); + JsonUtils::writeIP(node, "dns_ip_2", settings.dnsIP2); + }); + + EMSESP::esp8266React.getAPSettingsService()->read([&](APSettings & settings) { + JsonObject node = root.createNestedObject("AP"); + node["provision_mode"] = settings.provisionMode; + node["ssid"] = settings.ssid; + // node["password"] = settings.password; + node["local_ip"] = settings.localIP.toString(); + node["gateway_ip"] = settings.gatewayIP.toString(); + node["subnet_mask"] = settings.subnetMask.toString(); + }); + + EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { + JsonObject node = root.createNestedObject("MQTT"); + node["enabled"] = settings.enabled; + node["host"] = settings.host; + node["port"] = settings.port; + node["username"] = settings.username; + // node["password"] = settings.password; + node["client_id"] = settings.clientId; + node["keep_alive"] = settings.keepAlive; + node["clean_session"] = settings.cleanSession; + node["max_topic_length"] = settings.maxTopicLength; + node["system_heartbeat"] = settings.system_heartbeat; + node["publish_time_boiler"] = settings.publish_time_boiler; + node["publish_time_thermostat"] = settings.publish_time_thermostat; + node["publish_time_solar"] = settings.publish_time_solar; + node["publish_time_mixing"] = settings.publish_time_mixing; + node["publish_time_other"] = settings.publish_time_other; + node["publish_time_sensor"] = settings.publish_time_sensor; + node["mqtt_format"] = settings.mqtt_format; + node["mqtt_qos"] = settings.mqtt_qos; + node["mqtt_retain"] = settings.mqtt_retain; + }); + + EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) { + JsonObject node = root.createNestedObject("NTP"); + node["enabled"] = settings.enabled; + node["server"] = settings.server; + node["tz_label"] = settings.tzLabel; + node["tz_format"] = settings.tzFormat; + }); + + EMSESP::esp8266React.getOTASettingsService()->read([&](OTASettings & settings) { + JsonObject node = root.createNestedObject("OTA"); + node["enabled"] = settings.enabled; + node["port"] = settings.port; + // node["password"] = settings.password; + }); + +#endif + + String buffer; + serializeJsonPretty(doc, buffer); + + return buffer; +} + } // namespace emsesp diff --git a/src/system.h b/src/system.h index c63dd9f07..22c13a141 100644 --- a/src/system.h +++ b/src/system.h @@ -47,19 +47,20 @@ class System { static void format(uuid::console::Shell & shell); static void console_commands(Shell & shell, unsigned int context); - - static void mqtt_command_pin(const char * value, const int8_t id); - static void mqtt_command_send(const char * value, const int8_t id); + static bool command_pin(const char * value, const int8_t id); + static bool command_send(const char * value, const int8_t id); static uint8_t free_mem(); static void upload_status(bool in_progress); static bool upload_status(); - void syslog_init(); - void set_heartbeat(bool system_heartbeat); - void send_heartbeat(); static void show_mem(const char * note); static void set_led(); - bool check_upgrade(); + static String export_settings(); + + bool check_upgrade(); + void syslog_init(); + void set_heartbeat(bool system_heartbeat); + void send_heartbeat(); private: static uuid::log::Logger logger_; diff --git a/src/test/test.cpp b/src/test/test.cpp index 0a6bee463..04507f840 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -601,12 +601,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { // test publish and adding to queue EMSESP::txservice_.flush_tx_queue(); - EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me"); + EMSESP::EMSESP::mqtt_.publish("boiler", "test me"); Mqtt::show_mqtt(shell); // show queue - strcpy(boiler_topic, "ems-esp/boiler_cmd"); - strcpy(thermostat_topic, "ems-esp/thermostat_cmd"); - strcpy(system_topic, "ems-esp/saystem_cmd"); + strcpy(boiler_topic, "ems-esp/boiler"); + strcpy(thermostat_topic, "ems-esp/thermostat"); + strcpy(system_topic, "ems-esp/saystem"); EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic diff --git a/src/version.h b/src/version.h index 1122d499d..53a6dd302 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.2b2" +#define EMSESP_APP_VERSION "2.0.2b3"