From 6154ff38f24a1cc1bbed90e5b808d762d07f514c Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 3 Aug 2020 23:14:43 +0200 Subject: [PATCH] initial commit with refactored mqtt commands --- README.md | 82 +- lib/OneWire/OneWire.cpp | 4 +- lib/OneWire/OneWire.h | 10 +- lib/framework/NTPSettingsService.cpp | 2 +- .../MsgPackSerializer/serializeObject.cpp | 22 - .../ArduinoJson/extras/tests/catch/catch.hpp | 1 - .../MsgPack/MsgPackDeserializer.hpp | 2 - makefile | 1 + platformio.ini | 8 +- scripts/build.sh | 0 src/console.cpp | 16 +- src/console.h | 65 +- src/devices/boiler.cpp | 519 ++++-------- src/devices/boiler.h | 37 +- src/devices/connect.cpp | 10 +- src/devices/controller.cpp | 12 +- src/devices/gateway.cpp | 10 +- src/devices/heatpump.cpp | 19 +- src/devices/mixing.cpp | 11 +- src/devices/solar.cpp | 21 +- src/devices/switch.cpp | 12 +- src/devices/thermostat.cpp | 787 +++++++----------- src/devices/thermostat.h | 58 +- src/emsdevice.cpp | 95 ++- src/emsdevice.h | 18 +- src/emsesp.cpp | 16 +- src/helpers.cpp | 64 +- src/helpers.h | 11 +- src/locale_EN.h | 117 +++ src/mqtt.cpp | 181 +++- src/mqtt.h | 43 +- src/sensors.cpp | 10 +- src/sensors.h | 13 +- src/shower.cpp | 4 +- src/system.cpp | 122 +-- src/system.h | 4 +- src/telegram.cpp | 6 +- src/telegram.h | 6 +- src/test/test.cpp | 136 ++- src/test/test.h | 2 - src/version.h | 2 +- 41 files changed, 1208 insertions(+), 1351 deletions(-) mode change 100755 => 100644 scripts/build.sh create mode 100644 src/locale_EN.h diff --git a/README.md b/README.md index 01dc95c21..866e5718d 100644 --- a/README.md +++ b/README.md @@ -66,59 +66,49 @@ common commands available in all contexts: su (from the root) - set - fetch - system (enters a context) - boiler (enters a context) - thermostat (enters a context) - scan devices [deep] - send telegram <"XX XX ..."> - set bus_id - set tx_mode - show - show devices - show ems - show values + set + fetch + system (enters a context) + boiler (enters a context) + thermostat (enters a context) + scan devices [deep] + send telegram <"XX XX ..."> + set bus_id + set tx_mode + show + show devices + show ems + show values system - set - show - show mqtt + set + show + show mqtt show users - passwd - restart - set wifi hostname - set wifi password - set wifi ssid + passwd + restart + set wifi hostname + set wifi password + set wifi ssid wifi reconnect boiler - comfort - flowtemp - wwactive - wwcirculation - wwonetime - wwtemp - read - maxpower <%> - minpower <%> + read + call [cmd] [n] (cmd's: comfort wwactivated wwtapactivated wwonetime wwcirculation flowtemp wwtemp burnmaxpower burnminpower boilhyston boilhystoff burnperiod pumpdelay) thermostat - set - set master [device ID] - mode [heating circuit] - temp [heating circuit] - read + set + set master [device ID] + read + call [cmd] [n] (cmd's: wwmode control mode holiday pause party datetime minexttemp clockoffset calinttemp display building language remotetemp temp nighttemp daytemp nofrosttemp ecotemp heattemp summertemp designtemp offsettemp holidaytemp) + ``` ---------- ### **mqtt commands** -commands can be written as `{"cmd": ,"data": }` or direct. -To set thermostat commands depending on heatingcircuits add `"hc": ` or nest the command. Without `"hc":` the first ative heatingcircuit is set. -These commands are equivalent: -`{"hc":2,"cmd":"daytemp","data":21}` or `{"hc":2,"daytemp":21}`or `{"hc2"{"daytemp":21}}` -In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21,"nighttemp":17},"hc2":{"daytemp":20}}` +commands must be written as `{"cmd": ,"data":, "id": }`. The `id` can be replaced with `hc` for some devices. + ``` *boiler_cmd* comfort @@ -161,10 +151,7 @@ In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21 *cmd* send <"0B XX XX .."> - D0 <0 | 1> - D1 <0 | 1> - D2 <0 | 1> - D3 <0 | 1> + gpio <0 | 1> <0-3> (for D0-D3) ``` @@ -180,13 +167,6 @@ In direct commands it's possible to combine commands, i.e. `{"hc1":{"daytemp":21 - See if it's easier to use timers instead of millis() based timers, using [polledTimeout](https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino). - Port over to ESP-IDF. The Arduino SDK is showing its limitations -### **Features to add** - -- Multi-language. German, Dutch, French -- Click on a device in the Web UI shows it's details -- Publish time can be customized per device (solar, mixing etc) -- add homeassistant mqtt discovery for Boiler as well - ### **Customizing the Web UI** The Web is based off Rick's awesome [esp8266-react](https://github.com/rjwats/esp8266-react/) framework. These are the files that are modified: diff --git a/lib/OneWire/OneWire.cpp b/lib/OneWire/OneWire.cpp index cd9dc8448..e5d19ff05 100644 --- a/lib/OneWire/OneWire.cpp +++ b/lib/OneWire/OneWire.cpp @@ -202,7 +202,7 @@ uint8_t OneWire::reset(void) { #ifdef ARDUINO_ARCH_ESP32 void IRAM_ATTR OneWire::write_bit(uint8_t v) { #else -void OneWire::write_bit(uint8_t v) { +void OneWire::write_bit(uint8_t v) { #endif IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; volatile IO_REG_TYPE * reg IO_REG_BASE_ATTR = baseReg; @@ -578,4 +578,4 @@ uint16_t OneWire::crc16(const uint8_t * input, uint16_t len, uint16_t crc) { #endif -#pragma GCC diagnostic pop +#pragma GCC diagnostic pop \ No newline at end of file diff --git a/lib/OneWire/OneWire.h b/lib/OneWire/OneWire.h index 436eecd1f..52db16c53 100644 --- a/lib/OneWire/OneWire.h +++ b/lib/OneWire/OneWire.h @@ -105,10 +105,10 @@ class OneWire { // Write a bit. The bus is always left powered at the end, see // note in write() about that. - #ifdef ARDUINO_ARCH_ESP32 - void IRAM_ATTR write_bit(uint8_t v); - #else - void write_bit(uint8_t v); +#ifdef ARDUINO_ARCH_ESP32 + void IRAM_ATTR write_bit(uint8_t v); +#else + void write_bit(uint8_t v); #endif // Read a bit. @@ -192,4 +192,4 @@ class OneWire { #endif #endif // __cplusplus -#endif // OneWire_h +#endif // OneWire_h \ No newline at end of file diff --git a/lib/framework/NTPSettingsService.cpp b/lib/framework/NTPSettingsService.cpp index cf53a0769..ec3a1efa0 100644 --- a/lib/framework/NTPSettingsService.cpp +++ b/lib/framework/NTPSettingsService.cpp @@ -54,7 +54,7 @@ void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDis void NTPSettingsService::configureNTP() { if (WiFi.isConnected() && _state.enabled) { - Serial.println(F("Starting NTP...")); + // Serial.println(F("Starting NTP...")); #ifdef ESP32 configTzTime(_state.tzFormat.c_str(), _state.server.c_str()); #elif defined(ESP8266) diff --git a/lib_standalone/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp b/lib_standalone/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp index dbe746432..617c5a6ac 100644 --- a/lib_standalone/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp +++ b/lib_standalone/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp @@ -22,11 +22,6 @@ static void check(const JsonObject object, const char (&expected_data)[N]) { check(object, expected_data, expected_len); } -// TODO: used by the commented test -// static void check(const JsonObject object, const std::string& expected) { -// check(object, expected.data(), expected.length()); -//} - TEST_CASE("serialize MsgPack object") { DynamicJsonDocument doc(4096); JsonObject object = doc.to(); @@ -54,23 +49,6 @@ TEST_CASE("serialize MsgPack object") { "iC\x0C\xA2iD\x0D\xA2iE\x0E\xA2iF\x0F"); } - // TODO: improve performance and uncomment - // SECTION("map 32") { - // std::string expected("\xDF\x00\x01\x00\x00", 5); - // - // for (int i = 0; i < 65536; ++i) { - // char kv[16]; - // sprintf(kv, "%04x", i); - // object[kv] = kv; - // expected += '\xA4'; - // expected += kv; - // expected += '\xA4'; - // expected += kv; - // } - // - // check(object, expected); - // } - SECTION("serialized(const char*)") { object["hello"] = serialized("\xDB\x00\x01\x00\x00", 5); check(object, "\x81\xA5hello\xDB\x00\x01\x00\x00"); diff --git a/lib_standalone/ArduinoJson/extras/tests/catch/catch.hpp b/lib_standalone/ArduinoJson/extras/tests/catch/catch.hpp index 7c351e931..f97a3fedb 100644 --- a/lib_standalone/ArduinoJson/extras/tests/catch/catch.hpp +++ b/lib_standalone/ArduinoJson/extras/tests/catch/catch.hpp @@ -10323,7 +10323,6 @@ namespace Catch { virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) diff --git a/lib_standalone/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/lib_standalone/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp index ed328913a..0c46e9b2b 100644 --- a/lib_standalone/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp +++ b/lib_standalone/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp @@ -23,7 +23,6 @@ class MsgPackDeserializer { TStringStorage stringStorage) : _pool(&pool), _reader(reader), _stringStorage(stringStorage) {} - // TODO: add support for filter DeserializationError parse(VariantData &variant, AllowAllFilter, NestingLimit nestingLimit) { return parse(variant, nestingLimit); @@ -40,7 +39,6 @@ class MsgPackDeserializer { } if ((code & 0xe0) == 0xe0) { - // TODO: add setNegativeInteger() variant.setSignedInteger(static_cast(code)); return DeserializationError::Ok; } diff --git a/makefile b/makefile index 06928a441..575648548 100644 --- a/makefile +++ b/makefile @@ -27,6 +27,7 @@ CXX_STANDARD := -std=c++11 # Defined Symbols #---------------------------------------------------------------------- DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_NO_LED +DEFINES += -DRUN_TEST #---------------------------------------------------------------------- # Sources & Files diff --git a/platformio.ini b/platformio.ini index a7c1bed39..cf5138467 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,8 +1,8 @@ ; PlatformIO Project Configuration File for EMS-ESP [platformio] -; default_envs = esp8266 -default_envs = esp32 +default_envs = esp8266 +; default_envs = esp32 # override any settings with your own local ones in pio_local.ini extra_configs = @@ -76,7 +76,7 @@ board_build.f_cpu = 160000000L ; 160MHz ; eagle.flash.4m1m.ld = 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer ; eagle.flash.4m2m.ld = 1019 KB sketch, 2024 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 1028 KB OTA & buffer ; board_build.ldscript = eagle.flash.4m2m.ld -build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=0 +build_flags = ${common.build_flags} ${common.debug_flags} -D FT_NTP=0 lib_ignore = AsyncTCP @@ -87,4 +87,4 @@ build_type = release platform = https://github.com/platformio/platform-espressif32.git board_build.partitions = min_spiffs.csv ; https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/ lib_deps = ${common.libs_core} -build_flags = ${common.build_flags} ${common.debug_flags} -DFT_NTP=1 +build_flags = ${common.build_flags} ${common.debug_flags} -D FT_NTP=1 diff --git a/scripts/build.sh b/scripts/build.sh old mode 100755 new mode 100644 diff --git a/src/console.cpp b/src/console.cpp index c594e5ba2..9f245b28d 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -91,6 +91,12 @@ void EMSESPShell::display_banner() { // turn off watch emsesp::EMSESP::watch_id(WATCH_ID_NONE); emsesp::EMSESP::watch(EMSESP::WATCH_OFF); + +#if defined(EMSESP_STANDALONE) +#ifdef RUN_TEST + invoke_command("test"); // "test default" +#endif +#endif } // pre-loads all the console commands into the MAIN context @@ -285,11 +291,15 @@ void Console::enter_custom_context(Shell & shell, unsigned int context) { void Console::load_standard_commands(unsigned int context) { #ifdef EMSESP_STANDALONE EMSESPShell::commands->add_command(context, - CommandFlags::ADMIN, + CommandFlags::USER, flash_string_vector{F_(test)}, - flash_string_vector{F_(name_mandatory)}, + flash_string_vector{F_(name_optional)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { - Test::run_test(shell, arguments.front()); + if (arguments.size() == 0) { + Test::run_test(shell, "default"); + } else { + Test::run_test(shell, arguments.front()); + } }); #endif diff --git a/src/console.h b/src/console.h index e7458abf0..1b4fd7304 100644 --- a/src/console.h +++ b/src/console.h @@ -50,69 +50,8 @@ using uuid::log::Level; #define F_(string_name) FPSTR(__pstr__##string_name) // clang-format on -// common words -MAKE_PSTR_WORD(exit) -MAKE_PSTR_WORD(help) -MAKE_PSTR_WORD(settings) -MAKE_PSTR_WORD(log) -MAKE_PSTR_WORD(logout) -MAKE_PSTR_WORD(enabled) -MAKE_PSTR_WORD(disabled) -MAKE_PSTR_WORD(yes) -MAKE_PSTR_WORD(no) -MAKE_PSTR_WORD(set) -MAKE_PSTR_WORD(show) -MAKE_PSTR_WORD(on) -MAKE_PSTR_WORD(off) -MAKE_PSTR_WORD(su) -MAKE_PSTR_WORD(name) -MAKE_PSTR_WORD(auto) -MAKE_PSTR_WORD(scan) -MAKE_PSTR_WORD(password) -MAKE_PSTR_WORD(read) -MAKE_PSTR_WORD(version) -MAKE_PSTR_WORD(values) -MAKE_PSTR_WORD(system) -MAKE_PSTR_WORD(fetch) -MAKE_PSTR_WORD(restart) -MAKE_PSTR_WORD(format) -MAKE_PSTR_WORD(raw) -MAKE_PSTR_WORD(watch) -MAKE_PSTR_WORD(mqtt) -MAKE_PSTR_WORD(send) -MAKE_PSTR_WORD(telegram) -MAKE_PSTR_WORD(bus_id) -MAKE_PSTR_WORD(tx_mode) -MAKE_PSTR_WORD(ems) -MAKE_PSTR_WORD(devices) - -MAKE_PSTR(deep_optional, "[deep]") -MAKE_PSTR(tx_mode_fmt, "Tx mode = %d") -MAKE_PSTR(bus_id_fmt, "Bus ID = %02X") -MAKE_PSTR(watchid_optional, "[ID]") -MAKE_PSTR(watch_format_mandatory, "") -MAKE_PSTR(invalid_watch, "Invalid watch type") -MAKE_PSTR(data_mandatory, "<\"XX XX ...\">") -MAKE_PSTR(percent, "%") -MAKE_PSTR(degrees, "°C") -MAKE_PSTR(degrees_mandatory, "") -MAKE_PSTR(asterisks, "********") -MAKE_PSTR(n_mandatory, "") -MAKE_PSTR(n_optional, "[n]") -MAKE_PSTR(bool_mandatory, "") -MAKE_PSTR(typeid_mandatory, "") -MAKE_PSTR(deviceid_mandatory, "") -MAKE_PSTR(deviceid_optional, "[device ID]") -MAKE_PSTR(invalid_log_level, "Invalid log level") -MAKE_PSTR(port_mandatory, "") -MAKE_PSTR(log_level_fmt, "Log level = %s") -MAKE_PSTR(log_level_optional, "[level]") -MAKE_PSTR(name_mandatory, "") -MAKE_PSTR(name_optional, "[name]") -MAKE_PSTR(new_password_prompt1, "Enter new password: ") -MAKE_PSTR(new_password_prompt2, "Retype new password: ") -MAKE_PSTR(password_prompt, "Password: ") -MAKE_PSTR(unset, "") +// localizations +#include "locale_EN.h" #ifdef LOCAL #undef LOCAL diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 64adca5d2..490d77c44 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -18,37 +18,15 @@ #include "boiler.h" -MAKE_PSTR_WORD(boiler) -MAKE_PSTR_WORD(wwtemp) -MAKE_PSTR_WORD(flowtemp) -MAKE_PSTR_WORD(wwactive) -MAKE_PSTR_WORD(wwonetime) -MAKE_PSTR_WORD(wwcirculation) -MAKE_PSTR_WORD(comfort) -MAKE_PSTR_WORD(eco) -MAKE_PSTR_WORD(intelligent) -MAKE_PSTR_WORD(hot) -MAKE_PSTR_WORD(maxpower) -MAKE_PSTR_WORD(minpower) - -MAKE_PSTR(comfort_mandatory, "") - -// shower -MAKE_PSTR_WORD(shower) -MAKE_PSTR_WORD(timer) -MAKE_PSTR_WORD(alert) -MAKE_PSTR(shower_timer_fmt, "Shower Timer is %s") -MAKE_PSTR(shower_alert_fmt, "Shower Alert is %s") - namespace emsesp { REGISTER_FACTORY(Boiler, EMSdevice::DeviceType::BOILER) -MAKE_PSTR(logger_name, "boiler") -uuid::log::Logger Boiler::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; + +uuid::log::Logger Boiler::logger_{F_(boiler), uuid::log::Facility::CONSOLE}; Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id); + LOG_DEBUG(F("Adding new Boiler with device ID 0x%02X"), device_id); // the telegram handlers... register_telegram_type(0x10, F("UBAErrorMessage1"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1)); @@ -68,15 +46,22 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus2, this, _1)); register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, std::bind(&Boiler::process_UBAMonitorFastPlus, this, _1)); register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus, this, _1)); - register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1)); - // MQTT callbacks - register_mqtt_topic("boiler_cmd", std::bind(&Boiler::boiler_cmd, this, _1)); - register_mqtt_topic("boiler_cmd_wwactivated", std::bind(&Boiler::boiler_cmd_wwactivated, this, _1)); - register_mqtt_topic("boiler_cmd_wwonetime", std::bind(&Boiler::boiler_cmd_wwonetime, this, _1)); - register_mqtt_topic("boiler_cmd_wwcirculation", std::bind(&Boiler::boiler_cmd_wwcirculation, this, _1)); - register_mqtt_topic("boiler_cmd_wwtemp", std::bind(&Boiler::boiler_cmd_wwtemp, this, _1)); + // MQTT commands for boiler_cmd topic + register_mqtt_cmd(F("comfort"), std::bind(&Boiler::set_warmwater_mode, this, _1, _2)); + register_mqtt_cmd(F("wwactivated"), std::bind(&Boiler::set_warmwater_activated, this, _1, _2)); + register_mqtt_cmd(F("wwtapactivated"), std::bind(&Boiler::set_tapwarmwater_activated, this, _1, _2)); + register_mqtt_cmd(F("wwonetime"), std::bind(&Boiler::set_warmwater_onetime, this, _1, _2)); + register_mqtt_cmd(F("wwcirculation"), std::bind(&Boiler::set_warmwater_circulation, this, _1, _2)); + register_mqtt_cmd(F("flowtemp"), std::bind(&Boiler::set_flow_temp, this, _1, _2)); + register_mqtt_cmd(F("wwtemp"), std::bind(&Boiler::set_warmwater_temp, this, _1, _2)); + register_mqtt_cmd(F("burnmaxpower"), std::bind(&Boiler::set_max_power, this, _1, _2)); + register_mqtt_cmd(F("burnminpower"), std::bind(&Boiler::set_min_power, this, _1, _2)); + register_mqtt_cmd(F("boilhyston"), std::bind(&Boiler::set_hyst_on, this, _1, _2)); + register_mqtt_cmd(F("boilhystoff"), std::bind(&Boiler::set_hyst_off, this, _1, _2)); + register_mqtt_cmd(F("burnperiod"), std::bind(&Boiler::set_burn_period, this, _1, _2)); + register_mqtt_cmd(F("pumpdelay"), std::bind(&Boiler::set_pump_delay, this, _1, _2)); } // add submenu context @@ -86,156 +71,10 @@ 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); }); } -// boiler_cmd topic -void Boiler::boiler_cmd(const char * message) { - StaticJsonDocument doc; - DeserializationError error = deserializeJson(doc, message); - if (error) { - LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str()); - return; - } - if (nullptr != doc["flowtemp"]) { - uint8_t t = doc["flowtemp"]; - set_flow_temp(t); - } - if (nullptr != doc["wwtemp"]) { - uint8_t t = doc["wwtemp"]; - set_warmwater_temp(t); - } - if (nullptr != doc["boilhyston"]) { - int8_t t = doc["boilhyston"]; - set_hyst_on(t); - } - if (nullptr != doc["boilhystoff"]) { - uint8_t t = doc["boilhystoff"]; - set_hyst_off(t); - } - if (nullptr != doc["burnperiod"]) { - uint8_t t = doc["burnperiod"]; - set_burn_period(t); - } - if (nullptr != doc["burnminpower"]) { - uint8_t p = doc["burnminpower"]; - set_min_power(p); - } - if (nullptr != doc["burnmaxpower"]) { - uint8_t p = doc["burnmaxpower"]; - set_max_power(p); - } - if (nullptr != doc["pumpdelay"]) { - uint8_t t = doc["pumpdelay"]; - set_pump_delay(t); - } - - if (nullptr != doc["comfort"]) { - const char * data = doc["comfort"]; - if (strcmp((char *)data, "hot") == 0) { - set_warmwater_mode(1); - } else if (strcmp((char *)data, "eco") == 0) { - set_warmwater_mode(2); - } else if (strcmp((char *)data, "intelligent") == 0) { - set_warmwater_mode(3); - } - } - - const char * command = doc["cmd"]; - if (command == nullptr || doc["data"] == nullptr) { - return; - } - - // boiler ww comfort setting - if (strcmp(command, "comfort") == 0) { - const char * data = doc["data"]; - if (strcmp((char *)data, "hot") == 0) { - set_warmwater_mode(1); - } else if (strcmp((char *)data, "eco") == 0) { - set_warmwater_mode(2); - } else if (strcmp((char *)data, "intelligent") == 0) { - set_warmwater_mode(3); - } - return; - } - - // boiler flowtemp setting - if (strcmp(command, "flowtemp") == 0) { - uint8_t t = doc["data"]; - set_flow_temp(t); - return; - } - if (strcmp(command, "wwtemp") == 0) { - uint8_t t = doc["data"]; - set_warmwater_temp(t); - return; - } - // boiler max power setting - if (strcmp(command, "burnmaxpower") == 0) { - uint8_t p = doc["data"]; - set_max_power(p); - return; - } - - // boiler min power setting - if (strcmp(command, "burnminpower") == 0) { - uint8_t p = doc["data"]; - set_min_power(p); - return; - } - if (strcmp(command, "boilhyston") == 0) { - int8_t t = doc["data"]; - set_hyst_on(t); - return; - } - if (strcmp(command, "boilhystoff") == 0) { - uint8_t t = doc["data"]; - set_hyst_off(t); - return; - } - if (strcmp(command, "burnperiod") == 0) { - uint8_t t = doc["data"]; - set_burn_period(t); - return; - } - if (strcmp(command, "pumpdelay") == 0) { - uint8_t t = doc["data"]; - set_pump_delay(t); - return; - } -} - -void Boiler::boiler_cmd_wwactivated(const char * message) { - if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) { - set_warmwater_activated(true); - } else if (message[0] == '0' || strcmp(message, "off") == 0) { - set_warmwater_activated(false); - } -} - -void Boiler::boiler_cmd_wwonetime(const char * message) { - if (message[0] == '1' || strcmp(message, "on") == 0) { - set_warmwater_onetime(true); - } else if (message[0] == '0' || strcmp(message, "off") == 0) { - set_warmwater_onetime(false); - } -} - -void Boiler::boiler_cmd_wwcirculation(const char * message) { - if (message[0] == '1' || strcmp(message, "on") == 0) { - set_warmwater_circulation(true); - } else if (message[0] == '0' || strcmp(message, "off") == 0) { - set_warmwater_circulation(false); - } -} - -void Boiler::boiler_cmd_wwtemp(const char * message) { - uint8_t t = atoi((char *)message); - if (t) { - set_warmwater_temp(t); - } -} - void Boiler::device_info(JsonArray & root) { JsonObject dataElement; @@ -295,16 +134,16 @@ void Boiler::publish_values() { if (Helpers::hasValue(pumpMod2_)) { doc["pumpMod2"] = pumpMod2_; } - if (Helpers::hasValue(wWCircPump_, VALUE_BOOL)) { + if (Helpers::hasValue(wWCircPump_, EMS_VALUE_BOOL)) { doc["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWCircPumpType_, VALUE_BOOL)) { + if (Helpers::hasValue(wWCircPumpType_, EMS_VALUE_BOOL)) { doc["wWCiPuType"] = wWCircPumpType_ ? "valve" : "pump"; } if (Helpers::hasValue(wWCircPumpMode_)) { doc["wWCiPuMode"] = wWCircPumpMode_; } - if (Helpers::hasValue(wWCirc_, VALUE_BOOL)) { + if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) { doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL); } if (Helpers::hasValue(extTemp_)) { @@ -340,43 +179,43 @@ void Boiler::publish_values() { if (Helpers::hasValue(exhaustTemp_)) { doc["exhaustTemp"] = (float)exhaustTemp_ / 10; } - if (Helpers::hasValue(wWActivated_, VALUE_BOOL)) { + if (Helpers::hasValue(wWActivated_, EMS_VALUE_BOOL)) { doc["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWOneTime_, VALUE_BOOL)) { + if (Helpers::hasValue(wWOneTime_, EMS_VALUE_BOOL)) { doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWDesinfecting_, VALUE_BOOL)) { + if (Helpers::hasValue(wWDesinfecting_, EMS_VALUE_BOOL)) { doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWReadiness_, VALUE_BOOL)) { + if (Helpers::hasValue(wWReadiness_, EMS_VALUE_BOOL)) { doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWRecharging_, VALUE_BOOL)) { + if (Helpers::hasValue(wWRecharging_, EMS_VALUE_BOOL)) { doc["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWTemperatureOK_, VALUE_BOOL)) { + if (Helpers::hasValue(wWTemperatureOK_, EMS_VALUE_BOOL)) { doc["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWCirc_, VALUE_BOOL)) { + if (Helpers::hasValue(wWCirc_, EMS_VALUE_BOOL)) { doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(burnGas_, VALUE_BOOL)) { + if (Helpers::hasValue(burnGas_, EMS_VALUE_BOOL)) { doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL); } if (Helpers::hasValue(flameCurr_)) { doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10; } - if (Helpers::hasValue(heatPmp_, VALUE_BOOL)) { + if (Helpers::hasValue(heatPmp_, EMS_VALUE_BOOL)) { doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(fanWork_, VALUE_BOOL)) { + if (Helpers::hasValue(fanWork_, EMS_VALUE_BOOL)) { doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(ignWork_, VALUE_BOOL)) { + if (Helpers::hasValue(ignWork_, EMS_VALUE_BOOL)) { doc["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(wWHeat_, VALUE_BOOL)) { + if (Helpers::hasValue(wWHeat_, EMS_VALUE_BOOL)) { doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL); } if (Helpers::hasValue(heating_temp_)) { @@ -450,16 +289,16 @@ bool Boiler::updated_values() { void Boiler::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // for showing the header - if (Helpers::hasValue(tap_water_active_, VALUE_BOOL)) { + if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL)) { print_value(shell, 2, F("Hot tap water"), tap_water_active_ ? F("running") : F("off")); } - if (Helpers::hasValue(heating_active_, VALUE_BOOL)) { + if (Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) { print_value(shell, 2, F("Central heating"), heating_active_ ? F("active") : F("off")); } print_value(shell, 2, F("Warm Water activated"), wWActivated_, nullptr, EMS_VALUE_BOOL); - if (Helpers::hasValue(wWCircPumpType_, VALUE_BOOL)) { + if (Helpers::hasValue(wWCircPumpType_, EMS_VALUE_BOOL)) { print_value(shell, 2, F("Warm Water charging type"), wWCircPumpType_ ? F("3-way valve") : F("charge pump")); } print_value(shell, 2, F("Warm Water circulation pump available"), wWCircPump_, nullptr, EMS_VALUE_BOOL); @@ -557,6 +396,8 @@ void Boiler::show_values(uuid::console::Shell & shell) { if (Helpers::hasValue(UBAuptime_)) { shell.printfln(F(" Total UBA working time: %d days %d hours %d minutes"), UBAuptime_ / 1440, (UBAuptime_ % 1440) / 60, UBAuptime_ % 60); } + + shell.println(); } /* @@ -579,7 +420,7 @@ void Boiler::check_active() { // see if the heating or hot tap water has changed, if so send // last_boilerActive stores heating in bit 1 and tap water in bit 2 - if (Helpers::hasValue(tap_water_active_, VALUE_BOOL) && Helpers::hasValue(heating_active_, VALUE_BOOL)) { + if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL) && Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) { uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_; if (latest_boilerState != last_boilerState) { last_boilerState = latest_boilerState; @@ -650,12 +491,12 @@ void Boiler::process_UBATotalUptime(std::shared_ptr telegram) { */ void Boiler::process_UBAParameters(std::shared_ptr telegram) { telegram->read_value(heating_temp_, 1); - telegram->read_value(burnPowermax_,2); - telegram->read_value(burnPowermin_,3); - telegram->read_value(boilTemp_off_,4); - telegram->read_value(boilTemp_on_,5); - telegram->read_value(burnPeriod_,6); - telegram->read_value(pumpDelay_,8); + telegram->read_value(burnPowermax_, 2); + telegram->read_value(burnPowermin_, 3); + telegram->read_value(boilTemp_off_, 4); + telegram->read_value(boilTemp_on_, 5); + telegram->read_value(burnPeriod_, 6); + telegram->read_value(pumpDelay_, 8); telegram->read_value(pump_mod_max_, 9); telegram->read_value(pump_mod_min_, 10); } @@ -804,7 +645,7 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr telegr // 0x10, 0x11, 0x12 // not yet implemented void Boiler::process_UBAErrorMessage(std::shared_ptr telegram) { - // data: displaycode(2), errornumner(2), year, month, hour, day, minute, duration(2), src-addr + // data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr } #pragma GCC diagnostic pop @@ -818,67 +659,113 @@ void Boiler::process_UBAMaintenanceData(std::shared_ptr telegram } } +/* + * Commands + */ + + // Set the warm water temperature 0x33 -void Boiler::set_warmwater_temp(const uint8_t temperature) { - LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature); - write_command(EMS_TYPE_UBAParameterWW, 2, temperature); - write_command(EMS_TYPE_UBAFlags, 3, temperature); // for i9000, see #397 +void Boiler::set_warmwater_temp(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler warm water temperature to %d C"), v); + write_command(EMS_TYPE_UBAParameterWW, 2, v); + write_command(EMS_TYPE_UBAFlags, 3, v); // for i9000, see #397 } // flow temp -void Boiler::set_flow_temp(const uint8_t temperature) { - LOG_INFO(F("Setting boiler flow temperature to %d C"), temperature); - write_command(EMS_TYPE_UBASetPoints, 0, temperature); +void Boiler::set_flow_temp(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler flow temperature to %d C"), v); + write_command(EMS_TYPE_UBASetPoints, 0, v); } // set min boiler output -void Boiler::set_min_power(const uint8_t power) { - LOG_INFO(F("Setting boiler min power to "), power); - write_command(EMS_TYPE_UBAParameters, 3, power); +void Boiler::set_min_power(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + LOG_INFO(F("Setting boiler min power to "), v); + write_command(EMS_TYPE_UBAParameters, 3, v); } // set max temp -void Boiler::set_max_power(const uint8_t power) { - LOG_INFO(F("Setting boiler max power to %d C"), power); - write_command(EMS_TYPE_UBAParameters, 2, power); +void Boiler::set_max_power(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler max power to %d C"), v); + write_command(EMS_TYPE_UBAParameters, 2, v); } // set oiler on hysteresis -void Boiler::set_hyst_on(const uint8_t temp) { - LOG_INFO(F("Setting boiler hysteresis on to %d C"), temp); - write_command(EMS_TYPE_UBAParameters, 5, temp); +void Boiler::set_hyst_on(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler hysteresis on to %d C"), v); + write_command(EMS_TYPE_UBAParameters, 5, v); } // set boiler off hysteresis -void Boiler::set_hyst_off(const uint8_t temp) { - LOG_INFO(F("Setting boiler hysteresis off to %d C"), temp); - write_command(EMS_TYPE_UBAParameters, 4, temp); +void Boiler::set_hyst_off(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler hysteresis off to %d C"), v); + write_command(EMS_TYPE_UBAParameters, 4, v); } // set min burner period -void Boiler::set_burn_period(const uint8_t t) { - LOG_INFO(F("Setting burner min. period to %d min"), t); - write_command(EMS_TYPE_UBAParameters, 6, t); +void Boiler::set_burn_period(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting burner min. period to %d min"), v); + write_command(EMS_TYPE_UBAParameters, 6, v); } // set pump delay -void Boiler::set_pump_delay(const uint8_t t) { - LOG_INFO(F("Setting boiler pump delay to %d min"), t); - write_command(EMS_TYPE_UBAParameters, 8, t); +void Boiler::set_pump_delay(const char * value, const int8_t id) { + uint8_t v = 0; + if (!Helpers::value2number(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler pump delay to %d min"), v); + write_command(EMS_TYPE_UBAParameters, 8, v); } -// 1=hot, 2=eco, 3=intelligent // 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) -void Boiler::set_warmwater_mode(const uint8_t comfort) { +// 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) { + if (value == nullptr) { + return; + } uint8_t set; - if (comfort == 1) { + if (strcmp(value, "hot") == 0) { LOG_INFO(F("Setting boiler warm water to Hot")); set = 0x00; - } else if (comfort == 2) { + } else if (strcmp(value, "eco") == 0) { LOG_INFO(F("Setting boiler warm water to Eco")); set = 0xD8; - } else if (comfort == 3) { + } else if (strcmp(value, "intelligent") == 0) { LOG_INFO(F("Setting boiler warm water to Intelligent")); set = 0xEC; } else { @@ -888,24 +775,33 @@ void Boiler::set_warmwater_mode(const uint8_t comfort) { } // turn on/off warm water -void Boiler::set_warmwater_activated(const bool activated) { - LOG_INFO(F("Setting boiler warm water %s"), activated ? "on" : "off"); - uint8_t value; +void Boiler::set_warmwater_activated(const char * value, const int8_t id) { + bool v = false; + if (!Helpers::value2bool(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler warm water %s"), v ? "on" : "off"); // https://github.com/proddy/EMS-ESP/issues/268 + uint8_t n; if (EMSbus::is_ht3()) { - value = (activated ? 0x08 : 0x00); // 0x08 is on, 0x00 is off + n = (v ? 0x08 : 0x00); // 0x08 is on, 0x00 is off } else { - value = (activated ? 0xFF : 0x00); // 0xFF is on, 0x00 is off + n = (v ? 0xFF : 0x00); // 0xFF is on, 0x00 is off } - write_command(EMS_TYPE_UBAParameterWW, 1, value); + write_command(EMS_TYPE_UBAParameterWW, 1, n); } // Activate / De-activate the Warm Tap Water -// true = on, false = off // 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 bool activated) { - LOG_INFO(F("Setting boiler warm tap water %s"), activated ? "on" : "off"); +void Boiler::set_tapwarmwater_activated(const char * value, const int8_t id) { + bool v = false; + if (!Helpers::value2bool(value, v)) { + return; + } + + LOG_INFO(F("Setting tap warm tap water %s"), v ? "on" : "off"); uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH]; for (uint8_t i = 0; i < sizeof(message_data); i++) { message_data[i] = 0x00; @@ -914,7 +810,7 @@ void Boiler::set_tapwarmwater_activated(const bool activated) { // we use the special test mode 0x1D for this. Setting the first data to 5A puts the system into test mode and // a setting of 0x00 puts it back into normal operating mode // when in test mode we're able to mess around with the 3-way valve settings - if (!activated) { + if (!v) { // on message_data[0] = 0x5A; // test mode on message_data[1] = 0x00; // burner output 0% @@ -932,16 +828,26 @@ void Boiler::set_tapwarmwater_activated(const bool activated) { // 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 bool activated) { - LOG_INFO(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off"); - write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02)); +void Boiler::set_warmwater_onetime(const char * value, const int8_t id) { + bool v = false; + if (!Helpers::value2bool(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler warm water OneTime loading %s"), v ? "on" : "off"); + write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02)); } // Activate / De-activate circulation of warm water 0x35 // true = on, false = off -void Boiler::set_warmwater_circulation(const bool activated) { - LOG_INFO(F("Setting boiler warm water circulation %s"), activated ? "on" : "off"); - write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02)); +void Boiler::set_warmwater_circulation(const char * value, const int8_t id) { + bool v = false; + if (!Helpers::value2bool(value, v)) { + return; + } + + LOG_INFO(F("Setting boiler warm water circulation %s"), v ? "on" : "off"); + write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02)); } // add console commands @@ -955,115 +861,6 @@ void Boiler::console_commands(Shell & shell, unsigned int context) { EMSESP::send_read_request(type_id, device_id()); }); - EMSESPShell::commands->add_command(ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(wwtemp)}, - flash_string_vector{F_(degrees_mandatory)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - set_warmwater_temp(Helpers::atoint(arguments.front().c_str())); - }); - - EMSESPShell::commands->add_command(ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(flowtemp)}, - flash_string_vector{F_(degrees_mandatory)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - set_flow_temp(Helpers::atoint(arguments.front().c_str())); - }); - - EMSESPShell::commands->add_command(ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(maxpower)}, - flash_string_vector{F_(n_mandatory)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - set_max_power(Helpers::atoint(arguments.front().c_str())); - }); - - EMSESPShell::commands->add_command(ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(minpower)}, - flash_string_vector{F_(n_mandatory)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - set_min_power(Helpers::atoint(arguments.front().c_str())); - }); - - EMSESPShell::commands->add_command( - ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(wwactive)}, - flash_string_vector{F_(bool_mandatory)}, - [=](Shell & shell, const std::vector & arguments) { - if (arguments[0] == read_flash_string(F_(on))) { - set_warmwater_activated(true); - } else if (arguments[0] == read_flash_string(F_(off))) { - set_warmwater_activated(false); - } else { - shell.println(F("Must be on or off")); - return; - } - }, - [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F_(on)), read_flash_string(F_(off))}; - }); - - EMSESPShell::commands->add_command( - ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(wwonetime)}, - flash_string_vector{F_(bool_mandatory)}, - [=](Shell & shell, const std::vector & arguments) { - if (arguments[0] == read_flash_string(F_(on))) { - set_warmwater_onetime(true); - } else if (arguments[0] == read_flash_string(F_(off))) { - set_warmwater_onetime(false); - } else { - shell.println(F("Must be on or off")); - return; - } - }, - [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F_(on)), read_flash_string(F_(off))}; - }); - - EMSESPShell::commands->add_command( - ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(wwcirculation)}, - flash_string_vector{F_(bool_mandatory)}, - [=](Shell & shell, const std::vector & arguments) { - if (arguments[0] == read_flash_string(F_(on))) { - set_warmwater_circulation(true); - } else if (arguments[0] == read_flash_string(F_(off))) { - set_warmwater_circulation(false); - } else { - shell.println(F("Must be on or off")); - return; - } - }, - [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F_(on)), read_flash_string(F_(off))}; - }); - - EMSESPShell::commands->add_command( - ShellContext::BOILER, - CommandFlags::ADMIN, - flash_string_vector{F_(comfort)}, - flash_string_vector{F_(comfort_mandatory)}, - [=](Shell & shell, const std::vector & arguments) { - if (arguments[0] == read_flash_string(F_(hot))) { - set_warmwater_mode(1); - } else if (arguments[0] == read_flash_string(F_(eco))) { - set_warmwater_mode(2); - } else if (arguments[0] == read_flash_string(F_(intelligent))) { - set_warmwater_mode(3); - } else { - shell.println(F("Invalid value. Must be hot, eco or intelligent")); - } - }, - [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F_(hot)), read_flash_string(F_(eco)), read_flash_string(F_(intelligent))}; - }); - EMSESPShell::commands->add_command(ShellContext::BOILER, CommandFlags::USER, flash_string_vector{F_(show)}, diff --git a/src/devices/boiler.h b/src/devices/boiler.h index fc77d7e28..d40e8b60d 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -124,7 +124,7 @@ class Boiler : public EMSdevice { uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET; uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET; int8_t boilTemp_off_ = EMS_VALUE_INT_NOTSET; - int8_t boilTemp_on_ = EMS_VALUE_UINT_NOTSET; + int8_t boilTemp_on_ = EMS_VALUE_INT_NOTSET; uint8_t burnPeriod_ = EMS_VALUE_UINT_NOTSET; uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET; @@ -160,27 +160,20 @@ class Boiler : public EMSdevice { void check_active(); - void set_warmwater_temp(const uint8_t temperature); - void set_flow_temp(const uint8_t temperature); - void set_warmwater_mode(const uint8_t comfort); - void set_warmwater_activated(const bool activated); - void set_tapwarmwater_activated(const bool activated); - void set_warmwater_onetime(const bool activated); - void set_warmwater_circulation(const bool activated); - void set_min_power(const uint8_t power); - void set_max_power(const uint8_t power); - void set_hyst_on(const uint8_t temp); - void set_hyst_off(const uint8_t temp); - void set_burn_period(const uint8_t t); - void set_pump_delay(const uint8_t t); - - - // mqtt callbacks - void boiler_cmd(const char * message); - void boiler_cmd_wwactivated(const char * message); - void boiler_cmd_wwonetime(const char * message); - void boiler_cmd_wwcirculation(const char * message); - void boiler_cmd_wwtemp(const char * message); + // commands + 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_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); }; } // namespace emsesp diff --git a/src/devices/connect.cpp b/src/devices/connect.cpp index d8da0d612..60b8de5c3 100644 --- a/src/devices/connect.cpp +++ b/src/devices/connect.cpp @@ -22,16 +22,10 @@ namespace emsesp { REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT); -MAKE_PSTR(logger_name, "connect") -uuid::log::Logger Connect::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Connect::logger_{F_(connect), uuid::log::Facility::CONSOLE}; Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1)); - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Connect::cmd, this, _1)); } void Connect::device_info(JsonArray & root) { @@ -42,7 +36,7 @@ void Connect::add_context_menu() { // display all values into the shell console void Connect::show_values(uuid::console::Shell & shell) { - EMSdevice::show_values(shell); // always call this to show header + // EMSdevice::show_values(shell); // always call this to show header } // publish values via MQTT diff --git a/src/devices/controller.cpp b/src/devices/controller.cpp index a6796ce8b..07bd9879c 100644 --- a/src/devices/controller.cpp +++ b/src/devices/controller.cpp @@ -18,22 +18,14 @@ #include "controller.h" -// MAKE_PSTR_WORD(controller) - namespace emsesp { REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER); -MAKE_PSTR(logger_name, "controller") -uuid::log::Logger Controller::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Controller::logger_{F_(controller), uuid::log::Facility::CONSOLE}; Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1)); - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Controller::cmd, this, _1)); } void Controller::add_context_menu() { @@ -44,7 +36,7 @@ void Controller::device_info(JsonArray & root) { // display all values into the shell console void Controller::show_values(uuid::console::Shell & shell) { - EMSdevice::show_values(shell); // always call this to show header + // EMSdevice::show_values(shell); // always call this to show header } // publish values via MQTT diff --git a/src/devices/gateway.cpp b/src/devices/gateway.cpp index aa8bec0bf..e36e40c38 100644 --- a/src/devices/gateway.cpp +++ b/src/devices/gateway.cpp @@ -18,22 +18,14 @@ #include "gateway.h" -// MAKE_PSTR_WORD(gateway) - namespace emsesp { REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY); -MAKE_PSTR(logger_name, "gateway") -uuid::log::Logger Gateway::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Gateway::logger_{F_(gateway), uuid::log::Facility::CONSOLE}; Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1)); - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Gateway::cmd, this, _1)); } void Gateway::add_context_menu() { diff --git a/src/devices/heatpump.cpp b/src/devices/heatpump.cpp index 14b8e74ea..6895dd733 100644 --- a/src/devices/heatpump.cpp +++ b/src/devices/heatpump.cpp @@ -18,32 +18,19 @@ #include "heatpump.h" -// MAKE_PSTR_WORD(heatpump) - -/* - example telegrams 0x32B, 0x37B - "38 10 FF 00 03 7B 08 24 00 4B", - "38 10 FF 00 03 2B 00 C7 07 C3 01", - "38 10 FF 00 03 2B 00 D1 08 2A 01", -*/ - namespace emsesp { REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP); -MAKE_PSTR(logger_name, "heatpump") -uuid::log::Logger Heatpump::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Heatpump::logger_{F_(heatpump), uuid::log::Facility::CONSOLE}; Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - LOG_DEBUG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id); + LOG_DEBUG(F("Adding new Heat Pump module with device ID 0x%02X"), device_id); // telegram handlers register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1)); register_telegram_type(0x042B, F("HP2"), true, std::bind(&Heatpump::process_HPMonitor2, this, _1)); - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Heatpump::cmd, this, _1)); } // context submenu @@ -55,7 +42,7 @@ void Heatpump::device_info(JsonArray & root) { // display all values into the shell console void Heatpump::show_values(uuid::console::Shell & shell) { - EMSdevice::show_values(shell); // always call this to show header + // EMSdevice::show_values(shell); // always call this to show header } // publish values via MQTT diff --git a/src/devices/mixing.cpp b/src/devices/mixing.cpp index 7fc8e4424..fe7c09b8b 100644 --- a/src/devices/mixing.cpp +++ b/src/devices/mixing.cpp @@ -22,12 +22,11 @@ namespace emsesp { REGISTER_FACTORY(Mixing, EMSdevice::DeviceType::MIXING); -MAKE_PSTR(logger_name, "mixing") -uuid::log::Logger Mixing::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Mixing::logger_{F_(mixing), uuid::log::Facility::CONSOLE}; Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - LOG_DEBUG(F("Registering new Mixing module with device ID 0x%02X"), device_id); + LOG_DEBUG(F("Adding new Mixing module with device ID 0x%02X"), device_id); if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) { if (device_id <= 0x27) { @@ -48,9 +47,6 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) { register_telegram_type(0x010C, F("IPMSetMessage"), false, std::bind(&Mixing::process_IPMStatusMessage, this, _1)); } - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Mixing::cmd, this, _1)); } // add context submenu @@ -72,7 +68,6 @@ void Mixing::device_info(JsonArray & root) { render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees)); render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent)); render_value_json(root, "", F("Current valve status"), status_, nullptr); - } // check to see if values have been updated @@ -101,6 +96,8 @@ void Mixing::show_values(uuid::console::Shell & shell) { print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees)); print_value(shell, 4, F("Current pump modulation"), pumpMod_, F_(percent)); print_value(shell, 4, F("Current valve status"), status_, nullptr); + + shell.println(); } // publish values via MQTT diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index 30f8974a7..2e6918a88 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -18,19 +18,15 @@ #include "solar.h" -MAKE_PSTR(kwh, "kWh") -MAKE_PSTR(wh, "Wh") - namespace emsesp { REGISTER_FACTORY(Solar, EMSdevice::DeviceType::SOLAR); -MAKE_PSTR(logger_name, "solar") -uuid::log::Logger Solar::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Solar::logger_{F_(solar), uuid::log::Facility::CONSOLE}; Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - LOG_DEBUG(F("Registering new Solar module with device ID 0x%02X"), device_id); + LOG_DEBUG(F("Adding new Solar module with device ID 0x%02X"), device_id); // telegram handlers if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) { @@ -49,9 +45,6 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s register_telegram_type(0x0103, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1)); register_telegram_type(0x0101, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1)); } - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Solar::cmd, this, _1)); } // context submenu @@ -105,6 +98,8 @@ void Solar::show_values(uuid::console::Shell & shell) { print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10); print_value(shell, 2, F("Energy today"), energyToday_, F_(wh)); print_value(shell, 2, F("Energy total"), energyTotal_, F_(kwh), 10); + + shell.println(); } // publish values via MQTT @@ -129,11 +124,11 @@ void Solar::publish_values() { doc["pumpmodulation"] = pumpModulation_; } - if (Helpers::hasValue(pump_, VALUE_BOOL)) { + if (Helpers::hasValue(pump_, EMS_VALUE_BOOL)) { doc["pump"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(valveStatus_, VALUE_BOOL)) { + if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) { doc["valvestatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL); } @@ -141,11 +136,11 @@ void Solar::publish_values() { doc["pumpWorkMin"] = (float)pumpWorkMin_; } - if (Helpers::hasValue(tankHeated_, VALUE_BOOL)) { + if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) { doc["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL); } - if (Helpers::hasValue(collectorOnOff_, VALUE_BOOL)) { + if (Helpers::hasValue(collectorOnOff_, EMS_VALUE_BOOL)) { doc["collectorOnOff"] = Helpers::render_value(s, collectorOnOff_, EMS_VALUE_BOOL); } diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index c7a160260..95d1f4454 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -18,22 +18,14 @@ #include "switch.h" -// MAKE_PSTR_WORD(switch) - namespace emsesp { REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH); -MAKE_PSTR(logger_name, "switch") -uuid::log::Logger Switch::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Switch::logger_{F_(switch), uuid::log::Facility::CONSOLE}; Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1)); - - // MQTT callbacks - // register_mqtt_topic("topic", std::bind(&Switch::cmd, this, _1)); } void Switch::add_context_menu() { @@ -44,7 +36,7 @@ void Switch::device_info(JsonArray & root) { // display all values into the shell console void Switch::show_values(uuid::console::Shell & shell) { - EMSdevice::show_values(shell); // always call this to show header + // EMSdevice::show_values(shell); // always call this to show header } // publish values via MQTT diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 3468abe06..eedcf4568 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -18,22 +18,11 @@ #include "thermostat.h" -MAKE_PSTR_WORD(thermostat) -MAKE_PSTR_WORD(master) -MAKE_PSTR_WORD(temp) -MAKE_PSTR_WORD(mode) -MAKE_PSTR_WORD(wwmode) - -MAKE_PSTR(hc_optional, "[heating circuit]") -MAKE_PSTR(mode_mandatory, "") -MAKE_PSTR(mode_optional, "[mode]") -MAKE_PSTR(master_thermostat_fmt, "Master Thermostat device ID = %s") - namespace emsesp { REGISTER_FACTORY(Thermostat, EMSdevice::DeviceType::THERMOSTAT); -MAKE_PSTR(logger_name, "thermostat") -uuid::log::Logger Thermostat::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; + +uuid::log::Logger Thermostat::logger_{F_(thermostat), uuid::log::Facility::CONSOLE}; Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { @@ -138,18 +127,17 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat - // if we're on auto mode, register this thermostat if it has a device id of 0x10 or 0x17 + // if we're on auto mode, register this thermostat if it has a device id of 0x10, 0x17 or 0x18 // or if its the master thermostat we defined // see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161 if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) { EMSESP::actual_master_thermostat(device_id); - LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as master)"), device_id); + LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id); init_mqtt(); } else { - LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X"), device_id); + LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id); } - // for the thermostat, go a query all the heating circuits. This is only done once. The automatic fetch will from now on // only update the active heating circuits for (uint8_t i = 0; i < monitor_typeids.size(); i++) { @@ -160,14 +148,6 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i } } -// for the master thermostat initialize the MQTT subscribes -// these will be prefixed with hostname -void Thermostat::init_mqtt() { - register_mqtt_topic("thermostat_cmd", std::bind(&Thermostat::thermostat_cmd, this, _1)); // generic commands - register_mqtt_topic("thermostat_cmd_temp", std::bind(&Thermostat::thermostat_cmd_temp, this, _1)); - register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1)); -} - // prepare data for Web UI void Thermostat::device_info(JsonArray & root) { uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits @@ -205,14 +185,18 @@ void Thermostat::device_info(JsonArray & root) { dataElement = root.createNestedObject(); std::string mode_str(15, '\0'); snprintf_P(&mode_str[0], mode_str.capacity() + 1, PSTR("%sMode"), hc_str.c_str()); - dataElement["name"] = mode_str; + dataElement["name"] = mode_str; std::string modetype_str(20, '\0'); if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) { snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - summer"), mode_tostring(hc->get_mode(flags)).c_str()); } else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) { snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - holiday"), mode_tostring(hc->get_mode(flags)).c_str()); } else if (Helpers::hasValue(hc->mode_type)) { - snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - %s"), mode_tostring(hc->get_mode(flags)).c_str(), mode_tostring(hc->get_mode_type(flags)).c_str()); + snprintf_P(&modetype_str[0], + modetype_str.capacity() + 1, + PSTR("%s - %s"), + mode_tostring(hc->get_mode(flags)).c_str(), + mode_tostring(hc->get_mode_type(flags)).c_str()); } else { snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, mode_tostring(hc->get_mode(flags)).c_str()); } @@ -221,8 +205,9 @@ void Thermostat::device_info(JsonArray & root) { } } -// only add the menu for the master thermostat +// context menu "thermostat" void Thermostat::add_context_menu() { + // only add it once, to prevent conflicts when there are multiple thermostats if (device_id() != EMSESP::actual_master_thermostat()) { return; } @@ -232,318 +217,10 @@ 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); }); } -// general MQTT command for controlling thermostat -// e.g. { "hc": 1, "cmd":"daytemp", "data": 20 } -// or { "hc": 1, "daytemp": 20 } or { "hc2": { "daytemp":20 }} -void Thermostat::thermostat_cmd(const char * message) { - StaticJsonDocument doc; - DeserializationError error = deserializeJson(doc, message); - if (error) { - LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str()); - return; - } - - // check for nested commands like {"hc2":{"temp":21}} - for (const auto & hc : heating_circuits_) { - char hc_name[6], s[3]; // hc{1-4} - strlcpy(hc_name, "hc", 6); - uint8_t hc_num = hc->hc_num(); - strlcat(hc_name, Helpers::itoa(s, hc_num), 6); - if (nullptr != doc[hc_name]["mode"]) { - std::string mode = doc[hc_name]["mode"]; - set_mode(mode, hc_num); - } - if (float f = doc[hc_name]["temp"]) { - set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num); - } - if (float f = doc[hc_name]["nighttemp"]) { - set_temperature(f, HeatingCircuit::Mode::NIGHT, hc_num); - } - if (float f = doc[hc_name]["daytemp"]) { - set_temperature(f, HeatingCircuit::Mode::DAY, hc_num); - } - if (float f = doc[hc_name]["nofrosttemp"]) { - set_temperature(f, HeatingCircuit::Mode::NOFROST, hc_num); - } - if (float f = doc[hc_name]["ecotemp"]) { - set_temperature(f, HeatingCircuit::Mode::ECO, hc_num); - } - if (float f = doc[hc_name]["heattemp"]) { - set_temperature(f, HeatingCircuit::Mode::HEAT, hc_num); - } - if (float f = doc[hc_name]["summertemp"]) { - set_temperature(f, HeatingCircuit::Mode::SUMMER, hc_num); - } - if (float f = doc[hc_name]["designtemp"]) { - set_temperature(f, HeatingCircuit::Mode::DESIGN, hc_num); - } - if (float f = doc[hc_name]["offsettemp"]) { - set_temperature(f, HeatingCircuit::Mode::OFFSET, hc_num); - } - if (float f = doc[hc_name]["holidaytemp"]) { // - set_temperature(f, HeatingCircuit::Mode::HOLIDAY, hc_num); - } - if (float f = doc[hc_name]["remotetemp"]) { - if (f > 100 || f < 0) { - Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET); - } else { - Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); - } - } - if (nullptr != doc[hc_name]["control"]) { - uint8_t ctrl = doc[hc_name]["control"]; - set_control(ctrl, hc_num); - } - if (nullptr != doc[hc_name]["pause"]) { - uint8_t p = doc[hc_name]["pause"]; - set_pause(p, hc_num); - } - if (nullptr != doc[hc_name]["party"]) { - uint8_t p = doc[hc_name]["party"]; - set_party(p, hc_num); - } - if (nullptr != doc[hc_name]["holiday"]) { - std::string holiday = doc[hc_name]["holiday"]; - set_holiday(holiday.c_str(), hc_num); - } - } - // commands without heatingcircuit - if (nullptr != doc["wwmode"]) { - std::string mode = doc["wwmode"]; - set_ww_mode(mode); - } - if (float ct = doc["calinttemp"]) { - set_settings_calinttemp((int8_t)(ct * 10)); - } - if (nullptr != doc["minexttemp"]) { - int8_t mt = doc["minexttemp"]; - set_settings_minexttemp(mt); - } - if (nullptr != doc["building"]) { - std::string bds = doc["building"]; - uint8_t bd = doc["building"]; - if (strcmp(bds.c_str(), "light") == 0) { - bd = 0; - } else if (strcmp(bds.c_str(), "medium") == 0) { - bd = 1; - } else if (strcmp(bds.c_str(), "heavy") == 0) { - bd = 2; - } - set_settings_building(bd); - } - if (nullptr != doc["language"]) { - uint8_t lg = doc["language"]; - set_settings_language(lg); - } - if (nullptr != doc["display"]) { - uint8_t dp = doc["display"]; - set_settings_display(dp); - } - if (nullptr != doc["clockoffset"]) { - int8_t co = doc["clockoffset"]; - set_settings_clockoffset(co); - } - - // get heating circuit if it exists - uint8_t hc_num = doc["hc"] | AUTO_HEATING_CIRCUIT; - - // check for unnested commands like {"temp":21} or {"hc":2,"temp":21,"mode":"auto"} - if (nullptr != doc["mode"]) { - std::string mode = doc["mode"]; - set_mode(mode, hc_num); - } - if (float f = doc["temp"]) { - set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num); - } - if (float f = doc["nighttemp"]) { - set_temperature(f, HeatingCircuit::Mode::NIGHT, hc_num); - } - if (float f = doc["daytemp"]) { - set_temperature(f, HeatingCircuit::Mode::DAY, hc_num); - } - if (float f = doc["nofrosttemp"]) { - set_temperature(f, HeatingCircuit::Mode::NOFROST, hc_num); - } - if (float f = doc["ecotemp"]) { - set_temperature(f, HeatingCircuit::Mode::ECO, hc_num); - } - if (float f = doc["heattemp"]) { - set_temperature(f, HeatingCircuit::Mode::HEAT, hc_num); - } - if (float f = doc["summertemp"]) { - set_temperature(f, HeatingCircuit::Mode::SUMMER, hc_num); - } - if (float f = doc["designtemp"]) { - set_temperature(f, HeatingCircuit::Mode::DESIGN, hc_num); - } - if (float f = doc["offsettemp"]) { - set_temperature(f, HeatingCircuit::Mode::OFFSET, hc_num); - } - if (float f = doc["holidaytemp"]) { // - set_temperature(f, HeatingCircuit::Mode::HOLIDAY, hc_num); - } - if (float f = doc["remotetemp"]) { - if (f > 100 || f < 0) { - Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET); - } else { - Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); - } - } - if (nullptr != doc["control"]) { - uint8_t ctrl = doc["control"]; - set_control(ctrl, hc_num); - } - if (nullptr != doc["pause"]) { - uint8_t p = doc["pause"]; - set_pause(p, hc_num); - } - if (nullptr != doc["party"]) { - uint8_t p = doc["party"]; - set_party(p, hc_num); - } - if (nullptr != doc["holiday"]) { - std::string holiday = doc["holiday"]; - set_holiday(holiday.c_str(), hc_num); - } - if (nullptr != doc["date"]) { - std::string date = doc["date"]; - set_datetime(date.c_str()); - } - - // check for commands like {"hc":2,"cmd":"temp","data":21} - const char * command = doc["cmd"]; - if (command == nullptr || doc["data"] == nullptr) { - return; - } - // ok, we have command and data - if (strcmp(command, "temp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::AUTO, hc_num); - } - return; - } - if (strcmp(command, "mode") == 0) { - std::string mode = doc["data"]; - if (mode.empty()) { - return; - } - set_mode(mode, hc_num); - return; - } - if (strcmp(command, "nighttemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::NIGHT, hc_num); - } - return; - } - if (strcmp(command, "daytemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::DAY, hc_num); - } - return; - } - if (strcmp(command, "holidaytemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::HOLIDAY, hc_num); - } - return; - } - if (strcmp(command, "ecotemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::ECO, hc_num); - } - return; - } - if (strcmp(command, "heattemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::HEAT, hc_num); - } - return; - } - if (strcmp(command, "nofrosttemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::NOFROST, hc_num); - } - return; - } - if (strcmp(command, "summertemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::SUMMER, hc_num); - } - return; - } - if (strcmp(command, "designtemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::DESIGN, hc_num); - } - return; - } - if (strcmp(command, "offsettemp") == 0) { - float f = doc["data"]; - if (f) { - set_temperature(f, HeatingCircuit::Mode::OFFSET, hc_num); - } - return; - } - if (strcmp(command, "remotetemp") == 0) { - float f = doc["data"]; - if (f > 100 || f < 0) { - Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET); - } else { - Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); - } - return; - } - if (strcmp(command, "control") == 0) { - uint8_t ctrl = doc["data"]; - set_control(ctrl, hc_num); - return; - } - if (strcmp(command, "pause") == 0) { - uint8_t p = doc["data"]; - set_pause(p, hc_num); - return; - } - if (strcmp(command, "party") == 0) { - uint8_t p = doc["data"]; - set_party(p, hc_num); - return; - } - if (strcmp(command, "holiday") == 0) { - std::string holiday = doc["data"]; - set_holiday(holiday.c_str(), hc_num); - return; - } - if (strcmp(command, "date") == 0) { - std::string date = doc["data"]; - set_datetime(date.c_str()); - return; - } -} - -void Thermostat::thermostat_cmd_temp(const char * message) { - float f = strtof((char *)message, 0); - set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT); -} - -// message payload holds the text name of the mode e.g. "auto" -void Thermostat::thermostat_cmd_mode(const char * message) { - std::string s(message); - set_mode(s, AUTO_HEATING_CIRCUIT); -} - // this function is called post the telegram handler function has been executed // we check if any of the thermostat values have changed and then republish if necessary bool Thermostat::updated_values() { @@ -761,12 +438,14 @@ void Thermostat::publish_values() { // returns the heating circuit object based on the hc number // of nullptr if it doesn't exist yet std::shared_ptr Thermostat::heating_circuit(const uint8_t hc_num) { + // if hc_num is 0 then return the first existing hc in the list if (hc_num == 0) { - // return first existing hc for (const auto & heating_circuit : heating_circuits_) { return heating_circuit; } } + + // otherwise find a match for (const auto & heating_circuit : heating_circuits_) { if (heating_circuit->hc_num() == hc_num) { return heating_circuit; @@ -1161,6 +840,8 @@ void Thermostat::show_values(uuid::console::Shell & shell) { print_value(shell, 4, F("Target flow temperature"), hc->targetflowtemp, F_(degrees)); } } + + shell.println(); } // 0xA8 - for reading the mode from the RC20 thermostat (0x17) @@ -1360,8 +1041,7 @@ void Thermostat::process_RC35Monitor(std::shared_ptr telegram) { // type 0x3D (HC1), 0x47 (HC2), 0x51 (HC3), 0x5B (HC4) - Working Mode Heating - for reading the mode from the RC35 thermostat (0x10) void Thermostat::process_RC35Set(std::shared_ptr telegram) { - // check to see we have a valid type - // heating: 1 radiator, 2 convectors, 3 floors, 4 room supply + // check to see we have a valid type. heating: 1 radiator, 2 convectors, 3 floors, 4 room supply if (telegram->message_data[0] == 0x00) { return; } @@ -1389,7 +1069,7 @@ void Thermostat::process_RCTime(std::shared_ptr telegram) { return; } if (telegram->message_data[7] & 0x0C) { // date and time not valid - set_datetime("NTP"); // set from NTP + set_datetime("ntp", -1); // set from NTP return; } if (datetime_.empty()) { @@ -1415,8 +1095,69 @@ void Thermostat::process_RCTime(std::shared_ptr telegram) { ); } +// add console commands +void Thermostat::console_commands(Shell & shell, unsigned int context) { + EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, + CommandFlags::ADMIN, + flash_string_vector{F_(set), F_(master)}, + flash_string_vector{F_(deviceid_optional)}, + [](Shell & shell, const std::vector & arguments) { + uint8_t value; + if (arguments.empty()) { + value = EMSESP_DEFAULT_MASTER_THERMOSTAT; + } else { + value = Helpers::hextoint(arguments.front().c_str()); + } + + EMSESP::emsespSettingsService.update( + [&](EMSESPSettings & settings) { + settings.master_thermostat = value; + EMSESP::actual_master_thermostat(value); // set the internal value too + char buffer[5]; + shell.printfln(F_(master_thermostat_fmt), + !value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value)); + return StateUpdateResult::CHANGED; + }, + "local"); + }); + + EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, + CommandFlags::ADMIN, + flash_string_vector{F_(read)}, + flash_string_vector{F_(typeid_mandatory)}, + [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { + uint16_t type_id = Helpers::hextoint(arguments.front().c_str()); + EMSESP::send_read_request(type_id, device_id()); + }); + + EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, + CommandFlags::USER, + flash_string_vector{F_(show)}, + [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { show_values(shell); }); + + EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, + CommandFlags::USER, + flash_string_vector{F_(set)}, + [](Shell & shell, const std::vector & arguments __attribute__((unused))) { + EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { + char buffer[4]; + shell.printfln(F_(master_thermostat_fmt), + settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str() + : Helpers::hextoa(buffer, settings.master_thermostat)); + shell.println(); + }); + }); + + // enter the context + Console::enter_custom_context(shell, context); +} + // 0xA5 - Set minimum external temperature -void Thermostat::set_settings_minexttemp(const int8_t mt) { +void Thermostat::set_settings_minexttemp(const char * value, const int8_t id) { + int8_t mt = 0; + if (!Helpers::value2number(value, mt)) { + return; + } if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) { LOG_INFO(F("Setting min external temperature to %d"), mt); write_command(EMS_TYPE_IBASettings, 5, mt); @@ -1424,14 +1165,25 @@ void Thermostat::set_settings_minexttemp(const int8_t mt) { } // 0xA5 - Clock offset -void Thermostat::set_settings_clockoffset(const int8_t co) { +void Thermostat::set_settings_clockoffset(const char * value, const int8_t id) { + int8_t co = 0; + if (!Helpers::value2number(value, co)) { + return; + } if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { LOG_INFO(F("Setting clock offset to %d"), co); write_command(EMS_TYPE_IBASettings, 12, co); } } + // 0xA5 - Calibrate internal temperature -void Thermostat::set_settings_calinttemp(const int8_t ct) { +void Thermostat::set_settings_calinttemp(const char * value, const int8_t id) { + int8_t ct = 0; + if (!Helpers::value2number(value, ct)) { + return; + } + + // TODO: Michael - does this value need to be multiple by 10? if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) { LOG_INFO(F("Calibrating internal temperature to %d.%d"), ct / 10, ct < 0 ? -ct % 10 : ct % 10); write_command(EMS_TYPE_IBASettings, 2, ct); @@ -1439,15 +1191,50 @@ void Thermostat::set_settings_calinttemp(const int8_t ct) { } // 0xA5 - Set the display settings -void Thermostat::set_settings_display(const uint8_t ds) { +void Thermostat::set_settings_display(const char * value, const int8_t id) { + uint8_t ds = 0; + if (!Helpers::value2number(value, ds)) { + return; + } if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { LOG_INFO(F("Setting display to %d"), ds); write_command(EMS_TYPE_IBASettings, 0, ds); } } +void Thermostat::set_remotetemp(const char * value, const int8_t id) { + float f = 0; + if (!Helpers::value2float(value, f)) { + return; + } + + uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id; + + if (f > 100 || f < 0) { + Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET); + } else { + Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10)); + } +} + // 0xA5 - Set the building settings -void Thermostat::set_settings_building(const uint8_t bg) { +void Thermostat::set_settings_building(const char * value, const int8_t id) { + std::string bd; + if (!Helpers::value2string(value, bd)) { + return; + } + + uint8_t bg = 0; + if (bd == "light") { + bg = 0; + } else if (bd == "medium") { + bg = 1; + } else if (bd == "heavy") { + bg = 2; + } else { + return; // invalid + } + if (((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) || ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35)) { LOG_INFO(F("Setting building to %d"), bg); write_command(EMS_TYPE_IBASettings, 6, bg); @@ -1455,7 +1242,11 @@ void Thermostat::set_settings_building(const uint8_t bg) { } // 0xA5 Set the language settings -void Thermostat::set_settings_language(const uint8_t lg) { +void Thermostat::set_settings_language(const char * value, const int8_t id) { + uint8_t lg = 0; + if (!Helpers::value2number(value, lg)) { + return; + } if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { LOG_INFO(F("Setting language to %d"), lg); write_command(EMS_TYPE_IBASettings, 1, lg); @@ -1463,16 +1254,25 @@ void Thermostat::set_settings_language(const uint8_t lg) { } // Set the control-mode for hc 0-off, 1-RC20, 2-RC3x -void Thermostat::set_control(const uint8_t ctrl, const uint8_t hc_num) { +void Thermostat::set_control(const char * value, const int8_t id) { + uint8_t ctrl = 0; + if (!Helpers::value2number(value, ctrl)) { + return; + } + + uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : 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; } + if (ctrl > 2) { LOG_WARNING(F("Set control: Invalid control mode: %d"), ctrl); return; } + if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) { LOG_INFO(F("Setting circuit-control for hc%d to %d"), hc_num, ctrl); write_command(set_typeids[hc->hc_num() - 1], 26, ctrl); @@ -1482,23 +1282,37 @@ void Thermostat::set_control(const uint8_t ctrl, const uint8_t hc_num) { } // sets the thermostat ww working mode, where mode is a string -void Thermostat::set_ww_mode(const std::string & mode) { - if (strcasecmp("off", mode.c_str()) == 0) { - LOG_INFO(F("Setting thermostat warm water mode to %s"), mode.c_str()); - write_command(EMS_TYPE_wwSettings, 2, 0); - } else if (strcasecmp("on", mode.c_str()) == 0) { - LOG_INFO(F("Setting thermostat warm water mode to %s"), mode.c_str()); - write_command(EMS_TYPE_wwSettings, 2, 1); - } else if (strcasecmp("auto", mode.c_str()) == 0) { - LOG_INFO(F("Setting thermostat warm water mode to %s"), mode.c_str()); - write_command(EMS_TYPE_wwSettings, 2, 2); +void Thermostat::set_wwmode(const char * value, const int8_t id) { + std::string v; + if (!Helpers::value2string(value, v)) { + return; + } + + uint8_t set = 0xFF; // some dummy value + if (v == "off") { + set = 0; + } else if (v == "on") { + set = 1; + } else if (v == "auto") { + set = 2; + } + + if (set != 0xFF) { + LOG_INFO(F("Setting thermostat warm water mode to %s"), v.c_str()); + write_command(EMS_TYPE_wwSettings, 2, set); } else { - LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), mode.c_str()); + LOG_WARNING(F("Set thermostat warm water mode: Invalid mode: %s"), v.c_str()); } } // set the holiday as string dd.mm.yyyy-dd.mm.yyyy -void Thermostat::set_holiday(const char * hd, const uint8_t hc_num) { +void Thermostat::set_holiday(const char * value, const int8_t id) { + std::string hd; + if (!Helpers::value2string(value, hd)) { + return; + } + uint8_t hc_num = (id == -1) ? DEFAULT_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, device_id()); @@ -1520,7 +1334,13 @@ void Thermostat::set_holiday(const char * hd, const uint8_t hc_num) { } // set pause in hours -void Thermostat::set_pause(const uint8_t hrs, const uint8_t hc_num) { +void Thermostat::set_pause(const char * value, const int8_t id) { + uint8_t hrs = 0; + if (!Helpers::value2number(value, hrs)) { + return; + } + uint8_t hc_num = (id == -1) ? DEFAULT_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, device_id()); @@ -1535,7 +1355,13 @@ void Thermostat::set_pause(const uint8_t hrs, const uint8_t hc_num) { } // set partymode in hours -void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) { +void Thermostat::set_party(const char * value, const int8_t id) { + uint8_t hrs = 0; + if (!Helpers::value2number(value, hrs)) { + return; + } + uint8_t hc_num = (id == -1) ? DEFAULT_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, device_id()); @@ -1551,11 +1377,17 @@ void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) { // 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) -void Thermostat::set_datetime(const char * dt) { +// id is ignored +void Thermostat::set_datetime(const char * value, const int8_t id) { + std::string dt; + if (!Helpers::value2string(value, dt)) { + return; + } + uint8_t data[9]; - if (strcmp(dt,"NTP") == 0) { - time_t now = time(nullptr); - tm * tm_ = localtime(&now); + if (dt == "ntp") { + time_t now = time(nullptr); + tm * tm_ = localtime(&now); if (tm_->tm_year < 110) { // no NTP time LOG_WARNING(F("No NTP time. Cannot set RCtime")); return; @@ -1590,34 +1422,43 @@ void Thermostat::set_datetime(const char * dt) { } // sets the thermostat working mode, where mode is a string -void Thermostat::set_mode(const std::string & mode, const uint8_t hc_num) { +// converts string mode to HeatingCircuit::Mode +void Thermostat::set_mode(const char * value, const int8_t id) { + std::string mode; + if (!Helpers::value2string(value, mode)) { + return; + } + + uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id; + if (mode_tostring(HeatingCircuit::Mode::OFF) == mode) { - set_mode(HeatingCircuit::Mode::OFF, hc_num); + set_mode_n(HeatingCircuit::Mode::OFF, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::MANUAL) == mode) { - set_mode(HeatingCircuit::Mode::MANUAL, hc_num); + set_mode_n(HeatingCircuit::Mode::MANUAL, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::AUTO) == mode) { - set_mode(HeatingCircuit::Mode::AUTO, hc_num); + set_mode_n(HeatingCircuit::Mode::AUTO, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::DAY) == mode) { - set_mode(HeatingCircuit::Mode::DAY, hc_num); + set_mode_n(HeatingCircuit::Mode::DAY, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::NIGHT) == mode) { - set_mode(HeatingCircuit::Mode::NIGHT, hc_num); + set_mode_n(HeatingCircuit::Mode::NIGHT, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::HEAT) == mode) { - set_mode(HeatingCircuit::Mode::HEAT, hc_num); + set_mode_n(HeatingCircuit::Mode::HEAT, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::NOFROST) == mode) { - set_mode(HeatingCircuit::Mode::NOFROST, hc_num); + set_mode_n(HeatingCircuit::Mode::NOFROST, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) { - set_mode(HeatingCircuit::Mode::ECO, hc_num); + set_mode_n(HeatingCircuit::Mode::ECO, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) { - set_mode(HeatingCircuit::Mode::HOLIDAY, hc_num); + set_mode_n(HeatingCircuit::Mode::HOLIDAY, hc_num); } else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) { - set_mode(HeatingCircuit::Mode::COMFORT, hc_num); + set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num); } else { LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str()); } } // Set the thermostat working mode -void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) { +// mode is HeatingCircuit::Mode +void Thermostat::set_mode_n(const uint8_t mode, const uint8_t hc_num) { if (can_write()) { LOG_WARNING(F("Write not supported for this model Thermostat")); return; @@ -1893,109 +1734,93 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co } } -// add console commands -void Thermostat::console_commands(Shell & shell, unsigned int context) { - EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, - CommandFlags::ADMIN, - flash_string_vector{F_(set), F_(master)}, - flash_string_vector{F_(deviceid_optional)}, - [](Shell & shell, const std::vector & arguments) { - uint8_t value; - if (arguments.empty()) { - value = EMSESP_DEFAULT_MASTER_THERMOSTAT; - } else { - value = Helpers::hextoint(arguments.front().c_str()); - } - - EMSESP::emsespSettingsService.update( - [&](EMSESPSettings & settings) { - settings.master_thermostat = value; - EMSESP::actual_master_thermostat(value); // set the internal value too - char buffer[5]; - shell.printfln(F_(master_thermostat_fmt), - !value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value)); - return StateUpdateResult::CHANGED; - }, - "local"); - }); - - EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, - CommandFlags::ADMIN, - flash_string_vector{F_(read)}, - flash_string_vector{F_(typeid_mandatory)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - uint16_t type_id = Helpers::hextoint(arguments.front().c_str()); - EMSESP::send_read_request(type_id, device_id()); - }); - - EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, - CommandFlags::ADMIN, - flash_string_vector{F_(temp)}, - flash_string_vector{F_(degrees_mandatory), F_(hc_optional), F_(mode_optional)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - uint8_t hc = (arguments.size() >= 2) ? arguments[1].at(0) - '0' : AUTO_HEATING_CIRCUIT; - if ((arguments.size() == 3)) { - set_temperature(atof(arguments.front().c_str()), arguments.back().c_str(), hc); - } else if (arguments[1].at(0) >= 'A') { - set_temperature(atof(arguments.front().c_str()), arguments.back().c_str(), AUTO_HEATING_CIRCUIT); - } else { - set_temperature(atof(arguments.front().c_str()), HeatingCircuit::Mode::AUTO, hc); - } - }); - - EMSESPShell::commands->add_command( - ShellContext::THERMOSTAT, - CommandFlags::ADMIN, - flash_string_vector{F_(mode)}, - flash_string_vector{F_(mode_mandatory), F_(hc_optional)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { - uint8_t hc = (arguments.size() == 2) ? arguments[1].at(0) - '0' : AUTO_HEATING_CIRCUIT; - set_mode(arguments.front(), hc); - }, - [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F("off")), - read_flash_string(F("manual")), - read_flash_string(F("day")), - read_flash_string(F("night")), - read_flash_string(F("eco")), - read_flash_string(F("comfort")), - read_flash_string(F("heat")), - read_flash_string(F("nofrost")), - read_flash_string(F("auto")) - - }; - }); - - EMSESPShell::commands->add_command( - ShellContext::THERMOSTAT, - CommandFlags::ADMIN, - flash_string_vector{F_(wwmode)}, - flash_string_vector{F_(mode_mandatory)}, - [=](Shell & shell __attribute__((unused)), const std::vector & arguments) { set_ww_mode(arguments.front()); }, - [](Shell & shell __attribute__((unused)), const std::vector & arguments __attribute__((unused))) -> const std::vector { - return std::vector{read_flash_string(F("off")), read_flash_string(F("on")), read_flash_string(F("auto"))}; - }); - - EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, - CommandFlags::USER, - flash_string_vector{F_(show)}, - [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { show_values(shell); }); - - EMSESPShell::commands->add_command(ShellContext::THERMOSTAT, - CommandFlags::USER, - flash_string_vector{F_(set)}, - [](Shell & shell, const std::vector & arguments __attribute__((unused))) { - EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { - char buffer[4]; - shell.printfln(F_(master_thermostat_fmt), - settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str() - : Helpers::hextoa(buffer, settings.master_thermostat)); - shell.println(); - }); - }); - - // enter the context - Console::enter_custom_context(shell, context); +// for HA specifically when receiving over MQTT +void Thermostat::thermostat_cmd_temp(const char * message) { + float f = strtof((char *)message, 0); + 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); +} + +void Thermostat::set_temperature_value(const char * value, const uint8_t id, const uint8_t mode) { + float f = 0; + uint8_t hc_num = (id == -1) ? DEFAULT_HEATING_CIRCUIT : id; + if (Helpers::value2float(value, f)) { + set_temperature(f, mode, hc_num); + } +} + +void Thermostat::set_temp(const char * value, const int8_t id) { + 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); +} + +void Thermostat::set_daytemp(const char * value, const int8_t id) { + set_temperature_value(value, id, HeatingCircuit::Mode::DAY); +} + +void Thermostat::set_nofrosttemp(const char * value, const int8_t id) { + 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); +} + +void Thermostat::set_heattemp(const char * value, const int8_t id) { + 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); +} + +void Thermostat::set_designtemp(const char * value, const int8_t id) { + 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); +} + +void Thermostat::set_holidaytemp(const char * value, const int8_t id) { + set_temperature_value(value, id, HeatingCircuit::Mode::HOLIDAY); +} + +// commands for MQTT and Console +void Thermostat::init_mqtt() { + register_mqtt_cmd(F("wwmode"), std::bind(&Thermostat::set_wwmode, this, _1, _2)); + register_mqtt_cmd(F("control"), std::bind(&Thermostat::set_control, this, _1, _2)); + register_mqtt_cmd(F("mode"), std::bind(&Thermostat::set_mode, this, _1, _2)); + register_mqtt_cmd(F("holiday"), std::bind(&Thermostat::set_holiday, this, _1, _2)); + register_mqtt_cmd(F("pause"), std::bind(&Thermostat::set_pause, this, _1, _2)); + register_mqtt_cmd(F("party"), std::bind(&Thermostat::set_party, this, _1, _2)); + register_mqtt_cmd(F("datetime"), std::bind(&Thermostat::set_datetime, this, _1, _2)); + register_mqtt_cmd(F("minexttemp"), std::bind(&Thermostat::set_settings_minexttemp, this, _1, _2)); + register_mqtt_cmd(F("clockoffset"), std::bind(&Thermostat::set_settings_clockoffset, this, _1, _2)); + register_mqtt_cmd(F("calinttemp"), std::bind(&Thermostat::set_settings_calinttemp, this, _1, _2)); + register_mqtt_cmd(F("display"), std::bind(&Thermostat::set_settings_display, this, _1, _2)); + register_mqtt_cmd(F("building"), std::bind(&Thermostat::set_settings_building, this, _1, _2)); + register_mqtt_cmd(F("language"), std::bind(&Thermostat::set_settings_language, this, _1, _2)); + register_mqtt_cmd(F("remotetemp"), std::bind(&Thermostat::set_remotetemp, this, _1, _2)); + register_mqtt_cmd(F("temp"), std::bind(&Thermostat::set_temp, this, _1, _2)); + register_mqtt_cmd(F("nighttemp"), std::bind(&Thermostat::set_nighttemp, this, _1, _2)); + register_mqtt_cmd(F("daytemp"), std::bind(&Thermostat::set_daytemp, this, _1, _2)); + register_mqtt_cmd(F("nofrosttemp"), std::bind(&Thermostat::set_nofrosttemp, this, _1, _2)); + register_mqtt_cmd(F("ecotemp"), std::bind(&Thermostat::set_ecotemp, this, _1, _2)); + register_mqtt_cmd(F("heattemp"), std::bind(&Thermostat::set_heattemp, this, _1, _2)); + register_mqtt_cmd(F("summertemp"), std::bind(&Thermostat::set_summertemp, this, _1, _2)); + register_mqtt_cmd(F("designtemp"), std::bind(&Thermostat::set_designtemp, this, _1, _2)); + register_mqtt_cmd(F("offsettemp"), std::bind(&Thermostat::set_offsettemp, this, _1, _2)); + register_mqtt_cmd(F("holidaytemp"), std::bind(&Thermostat::set_holidaytemp, this, _1, _2)); +} + + } // namespace emsesp \ No newline at end of file diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index e0db67f99..02749a4f0 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -220,55 +220,61 @@ class Thermostat : public EMSdevice { void process_IBASettings(std::shared_ptr telegram); void process_RCTime(std::shared_ptr telegram); void process_RC35wwSettings(std::shared_ptr telegram); - void process_RC35Monitor(std::shared_ptr telegram); void process_RC35Set(std::shared_ptr telegram); - void process_RC30Monitor(std::shared_ptr telegram); void process_RC30Set(std::shared_ptr telegram); - void process_RC20Monitor(std::shared_ptr telegram); void process_RC20Set(std::shared_ptr telegram); void process_RC20Remote(std::shared_ptr telegram); - void process_RC20Monitor_2(std::shared_ptr telegram); void process_RC20Set_2(std::shared_ptr telegram); - void process_RC10Monitor(std::shared_ptr telegram); void process_RC10Set(std::shared_ptr telegram); - void process_RC300Monitor(std::shared_ptr telegram); void process_RC300Set(std::shared_ptr telegram); - void process_JunkersMonitor(std::shared_ptr telegram); void process_JunkersSet(std::shared_ptr telegram); - void process_EasyMonitor(std::shared_ptr telegram); - void process_RC300WWmode(std::shared_ptr telegram); - // set functions - void set_settings_minexttemp(const int8_t mt); - void set_settings_calinttemp(const int8_t ct); - void set_settings_clockoffset(const int8_t co); - void set_settings_display(const uint8_t ds); - void set_settings_building(const uint8_t bg); - void set_settings_language(const uint8_t lg); - void set_control(const uint8_t ctrl, const uint8_t hc_num); - void set_ww_mode(const std::string & mode); - void set_holiday(const char * hd, const uint8_t hc_num); - void set_datetime(const char * dt); - void set_pause(const uint8_t hrs, const uint8_t hc_num); - void set_party(const uint8_t hrs, const uint8_t hc_num); - void set_mode(const uint8_t mode, const uint8_t hc_num); - void set_mode(const std::string & mode, const uint8_t hc_num); + // internal helper functions + void set_mode_n(const uint8_t mode, const uint8_t hc_num); + void set_temperature_value(const char * value, const uint8_t hc, 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); - // MQTT functions - void thermostat_cmd(const char * message); + // for HA specifically. MQTT functions. void thermostat_cmd_temp(const char * message); void 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); + 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_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_remotetemp(const char * value, const int8_t id); + + // set functions - these don't use the id/hc + void set_wwmode(const char * value, const int8_t id); + void set_datetime(const char * value, const int8_t id); + void set_settings_minexttemp(const char * value, const int8_t id); + void set_settings_clockoffset(const char * value, const int8_t id); + void set_settings_calinttemp(const char * value, const int8_t id); + void set_settings_display(const char * value, const int8_t id); + void set_settings_building(const char * value, const int8_t id); + void set_settings_language(const char * value, const int8_t id); }; } // namespace emsesp diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 2185b17df..cb8932bd6 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -20,11 +20,9 @@ #include "emsesp.h" #include "mqtt.h" // for the mqtt_function_p -MAKE_PSTR(logger_name, "emsdevice") - namespace emsesp { -uuid::log::Logger EMSdevice::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger EMSdevice::logger_{F_(emsesp), uuid::log::Facility::CONSOLE}; std::string EMSdevice::brand_to_string() const { switch (brand_) { @@ -55,6 +53,35 @@ std::string EMSdevice::brand_to_string() const { return std::string{}; } +// 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) { + switch (device_type) { + case DeviceType::BOILER: + return read_flash_string(F("boiler")); + break; + + case DeviceType::THERMOSTAT: + return read_flash_string(F("thermostat")); + break; + + case DeviceType::HEATPUMP: + return read_flash_string(F("heatpump")); + break; + + case DeviceType::SOLAR: + return read_flash_string(F("solar")); + break; + + case DeviceType::MIXING: + return read_flash_string(F("mixing")); + break; + + default: + return std::string{}; + break; + } +} + std::string EMSdevice::device_type_name() const { switch (device_type_) { case DeviceType::BOILER: @@ -206,12 +233,17 @@ void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) { // list all the mqtt handlers for this device void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) { - Mqtt::show_topic_handlers(shell, this->device_id_); + Mqtt::show_topic_handlers(shell, this->device_type_); } void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) { - LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_); - Mqtt::subscribe(this->device_id_, topic, f); + LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X and type %s"), topic.c_str(), this->device_id_, this->device_type_name().c_str()); + Mqtt::subscribe(this->device_type_, topic, f); +} + +void EMSdevice::register_mqtt_cmd(const __FlashStringHelper * cmd, mqtt_cmdfunction_p f) { + LOG_DEBUG(F("Registering MQTT cmd %s for device type %s"), uuid::read_flash_string(cmd).c_str(), this->device_type_name().c_str()); + Mqtt::add_command(this->device_type_, cmd, f); } EMSdevice::TelegramFunction::TelegramFunction(uint16_t telegram_type_id, @@ -239,7 +271,7 @@ std::string EMSdevice::telegram_type_name(std::shared_ptr telegr } for (const auto & tf : telegram_functions_) { - if ((tf.telegram_type_id_ == telegram->type_id) && ((telegram->type_id & 0x0F0) != 0xF0)) { + if ((tf.telegram_type_id_ == telegram->type_id) && ((telegram->type_id & 0xF0) != 0xF0)) { return uuid::read_flash_string(tf.telegram_type_name_); } } @@ -295,6 +327,7 @@ void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const print_value(shell, padding, name, uuid::read_flash_string(value).c_str()); } +// print string value, value is not in flash void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value) { uint8_t i = padding; while (i-- > 0) { @@ -304,4 +337,52 @@ 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) { + EMSESPShell::commands->add_command( + context, + CommandFlags::ADMIN, + flash_string_vector{F_(call)}, + flash_string_vector{F_(cmd_optional), F_(data_optional), F_(id_optional)}, + [&](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 2abc3bc42..905b7bab8 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -47,11 +47,22 @@ class EMSdevice { virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class + /* + // https://github.com/proddy/EMS-ESP/issues/434#issuecomment-667840531 + inline uint8_t device_id(uint8_t hc = 0) const { + if (((device_id_ & 0x7F) >= 0x18) && ((device_id_ & 0x7F) <= 0x1B)) { + return ((device_id_ & 0x80) + 0x18 + hc); + } + return device_id_; + } + */ + inline uint8_t device_id() const { return device_id_; } - std::string device_type_name() const; + std::string device_type_name() const; + static std::string device_type_topic_name(const uint8_t device_type); inline uint8_t product_id() const { return product_id_; @@ -129,7 +140,10 @@ class EMSdevice { 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); // virtual functions overrules by derived classes virtual void show_values(uuid::console::Shell & shell) = 0; @@ -222,7 +236,7 @@ class EMSdevice { enum DeviceType : uint8_t { UNKNOWN = 0, - SERVICEKEY, + SERVICEKEY, // us BOILER, THERMOSTAT, MIXING, diff --git a/src/emsesp.cpp b/src/emsesp.cpp index e26fb9e26..f1bcd34d9 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -18,8 +18,6 @@ #include "emsesp.h" -MAKE_PSTR(logger_name, "emsesp") - namespace emsesp { using DeviceFlags = emsesp::EMSdevice; @@ -45,7 +43,7 @@ EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webSer std::vector> EMSESP::emsdevices; // array of all the detected EMS devices std::vector EMSESP::device_library_; // libary of all our known EMS devices so far -uuid::log::Logger EMSESP::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::KERN}; +uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN}; // The services RxService EMSESP::rxservice_; // incoming Telegram Rx handler @@ -242,7 +240,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) { for (const auto & emsdevice : emsdevices) { if ((emsdevice) && (emsdevice->device_type() == device_class.first)) { emsdevice->show_values(shell); - shell.println(); } } } @@ -758,17 +755,14 @@ void EMSESP::start() { esp8266React.begin(); // loads system settings (wifi, mqtt, etc) emsespSettingsService.begin(); // load EMS-ESP specific settings - // system_.check_upgrade(); // see if we need to migrate from previous versions - + mqtt_.start(); // mqtt init console_.start(); // telnet and serial console system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings. - mqtt_.start(); // mqtt init shower_.start(); // initialize shower timer and shower alert txservice_.start(); // sets bus ID, sends out request for EMS devices sensors_.start(); // dallas external sensors - - webServer.begin(); // start web server + webServer.begin(); // start web server } // main loop calling all services @@ -777,9 +771,11 @@ void EMSESP::loop() { // if we're doing an OTA upload, skip MQTT and EMS if (system_.upload_status()) { + /* #if defined(ESP32) delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt) #endif +*/ return; } @@ -796,7 +792,9 @@ void EMSESP::loop() { fetch_device_values(); } +#if defined(ESP8266) delay(1); +#endif } } // namespace emsesp diff --git a/src/helpers.cpp b/src/helpers.cpp index 5cdde4c60..4c145dab0 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -17,7 +17,6 @@ */ #include "helpers.h" -#include "telegram.h" // for EMS_VALUE_* settings namespace emsesp { @@ -329,9 +328,9 @@ bool Helpers::check_abs(const int32_t i) { return ((i < 0 ? -i : i) != 0xFFFFFF); } -// for booleans, use isBool true (VALUE_BOOL) -bool Helpers::hasValue(const uint8_t v, bool isBool) { - if (isBool) { +// for booleans, use isBool true (EMS_VALUE_BOOL) +bool Helpers::hasValue(const uint8_t v, const uint8_t isBool) { + if (isBool == EMS_VALUE_BOOL) { return (v != EMS_VALUE_BOOL_NOTSET); } return (v != EMS_VALUE_UINT_NOTSET); @@ -354,4 +353,61 @@ bool Helpers::hasValue(const uint32_t v) { return (v != EMS_VALUE_ULONG_NOTSET); } +// checks if we can convert a char string to an int value +bool Helpers::value2number(const char * v, int value) { + if ((v == nullptr) || (strlen(v) == 0)) { + value = 0; + return false; + } + value = atoi((char *)v); + return true; +} + +// checks if we can convert a char string to a float value +bool Helpers::value2float(const char * v, float value) { + if ((v == nullptr) || (strlen(v) == 0)) { + value = 0; + return false; + } + value = atof((char *)v); + return true; +} + +// https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case +std::string Helpers::toLower(std::string const & s) { + std::string lc = s; + std::transform(lc.begin(), lc.end(), lc.begin(), [](unsigned char c) { return std::tolower(c); }); + return lc; +} + +// checks if we can convert a chat string to an int value +bool Helpers::value2string(const char * v, std::string & value) { + if ((v == nullptr) || (strlen(v) == 0)) { + value = {}; + return false; + } + value = toLower(v); + return true; +} + +// checks to see if a string (usually a command or payload cmd) looks like a boolean +// on, off, true, false, 1, 0 +bool Helpers::value2bool(const char * v, uint8_t value) { + if ((v == nullptr) || (strlen(v) == 0)) { + return false; + } + + if ((strncmp(v, "on", 2) == 0) || (strncmp(v, "1", 1) == 0) || (strncmp(v, "true", 4) == 0)) { + value = true; + return true; + } + + if ((strncmp(v, "off", 3) == 0) || (strncmp(v, "0", 1) == 0) || (strncmp(v, "false", 5) == 0)) { + value = false; + return true; + } + + return false; +} + } // namespace emsesp diff --git a/src/helpers.h b/src/helpers.h index 33fcc13a5..7ef33be3f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -22,6 +22,8 @@ #include #include +#include "telegram.h" // for EMS_VALUE_* settings + namespace emsesp { class Helpers { @@ -48,11 +50,18 @@ class Helpers { static char * ultostr(char * ptr, uint32_t value, const uint8_t base); #endif - static bool hasValue(const uint8_t v, bool isBool = false); // use isBool=true for bool's + static bool hasValue(const uint8_t v, const uint8_t isBool = 0); static bool hasValue(const int8_t v); static bool hasValue(const int16_t v); static bool hasValue(const uint16_t v); static bool hasValue(const uint32_t v); + + static std::string toLower(std::string const & s); + + static bool value2number(const char * v, int value); + static bool value2float(const char * v, float value); + static bool value2bool(const char * v, uint8_t value); + static bool value2string(const char * v, std::string & value); }; } // namespace emsesp diff --git a/src/locale_EN.h b/src/locale_EN.h new file mode 100644 index 000000000..507a110ed --- /dev/null +++ b/src/locale_EN.h @@ -0,0 +1,117 @@ +/* + * 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 . + */ + +// common words +MAKE_PSTR_WORD(exit) +MAKE_PSTR_WORD(help) +MAKE_PSTR_WORD(settings) +MAKE_PSTR_WORD(log) +MAKE_PSTR_WORD(logout) +MAKE_PSTR_WORD(enabled) +MAKE_PSTR_WORD(disabled) +MAKE_PSTR_WORD(set) +MAKE_PSTR_WORD(show) +MAKE_PSTR_WORD(on) +MAKE_PSTR_WORD(off) +MAKE_PSTR_WORD(su) +MAKE_PSTR_WORD(name) +MAKE_PSTR_WORD(auto) +MAKE_PSTR_WORD(scan) +MAKE_PSTR_WORD(password) +MAKE_PSTR_WORD(read) +MAKE_PSTR_WORD(version) +MAKE_PSTR_WORD(values) +MAKE_PSTR_WORD(system) +MAKE_PSTR_WORD(fetch) +MAKE_PSTR_WORD(restart) +MAKE_PSTR_WORD(format) +MAKE_PSTR_WORD(raw) +MAKE_PSTR_WORD(watch) +MAKE_PSTR_WORD(send) +MAKE_PSTR_WORD(telegram) +MAKE_PSTR_WORD(bus_id) +MAKE_PSTR_WORD(tx_mode) +MAKE_PSTR_WORD(ems) +MAKE_PSTR_WORD(devices) +MAKE_PSTR_WORD(shower) +MAKE_PSTR_WORD(mqtt) +MAKE_PSTR_WORD(emsesp) +MAKE_PSTR_WORD(connected) +MAKE_PSTR_WORD(disconnected) +MAKE_PSTR_WORD(passwd) +MAKE_PSTR_WORD(hostname) +MAKE_PSTR_WORD(wifi) +MAKE_PSTR_WORD(reconnect) +MAKE_PSTR_WORD(ssid) +MAKE_PSTR_WORD(heartbeat) +MAKE_PSTR_WORD(users) +MAKE_PSTR_WORD(master) +MAKE_PSTR_WORD(test) +MAKE_PSTR_WORD(call) + +// devices +MAKE_PSTR_WORD(boiler) +MAKE_PSTR_WORD(thermostat) +MAKE_PSTR_WORD(switch) +MAKE_PSTR_WORD(solar) +MAKE_PSTR_WORD(mixing) +MAKE_PSTR_WORD(gateway) +MAKE_PSTR_WORD(controller) +MAKE_PSTR_WORD(connect) +MAKE_PSTR_WORD(heatpump) + +// dallas sensors +MAKE_PSTR_WORD(sensors) + +MAKE_PSTR(kwh, "kWh") +MAKE_PSTR(wh, "Wh") +MAKE_PSTR(hc_optional, "[heating circuit]") +MAKE_PSTR(master_thermostat_fmt, "Master Thermostat device ID = %s") +MAKE_PSTR(host_fmt, "Host = %s") +MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s") +MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus"); +MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s"); +MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S") +MAKE_PSTR(system_heartbeat_fmt, "Heartbeat is %s") +MAKE_PSTR(cmd_optional, "[cmd]") +MAKE_PSTR(deep_optional, "[deep]") +MAKE_PSTR(tx_mode_fmt, "Tx mode = %d") +MAKE_PSTR(bus_id_fmt, "Bus ID = %02X") +MAKE_PSTR(watchid_optional, "[ID]") +MAKE_PSTR(watch_format_mandatory, "") +MAKE_PSTR(invalid_watch, "Invalid watch type") +MAKE_PSTR(data_mandatory, "<\"XX XX ...\">") +MAKE_PSTR(percent, "%") +MAKE_PSTR(degrees, "°C") +MAKE_PSTR(asterisks, "********") +MAKE_PSTR(n_mandatory, "") +MAKE_PSTR(n_optional, "[n]") +MAKE_PSTR(data_optional, "[data]") +MAKE_PSTR(id_optional, "[id]") +MAKE_PSTR(typeid_mandatory, "") +MAKE_PSTR(deviceid_mandatory, "") +MAKE_PSTR(deviceid_optional, "[device ID]") +MAKE_PSTR(invalid_log_level, "Invalid log level") +MAKE_PSTR(log_level_fmt, "Log level = %s") +MAKE_PSTR(log_level_optional, "[level]") +MAKE_PSTR(name_mandatory, "") +MAKE_PSTR(name_optional, "[name]") +MAKE_PSTR(new_password_prompt1, "Enter new password: ") +MAKE_PSTR(new_password_prompt2, "Retype new password: ") +MAKE_PSTR(password_prompt, "Password: ") +MAKE_PSTR(unset, "") diff --git a/src/mqtt.cpp b/src/mqtt.cpp index a38bc4ec5..471dade1f 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -20,12 +20,6 @@ #include "emsesp.h" #include "version.h" -MAKE_PSTR_WORD(connected) -MAKE_PSTR_WORD(disconnected) -MAKE_PSTR(system_heartbeat_fmt, "Heartbeat is %s") - -MAKE_PSTR(logger_name, "mqtt") - namespace emsesp { AsyncMqttClient * Mqtt::mqttClient_; @@ -35,14 +29,16 @@ std::string Mqtt::hostname_; uint8_t Mqtt::mqtt_qos_; uint16_t Mqtt::publish_time_; -std::vector Mqtt::mqtt_subfunctions_; +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; uint16_t Mqtt::mqtt_message_id_ = 0; std::deque Mqtt::mqtt_messages_; char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer -uuid::log::Logger Mqtt::logger_{F_(logger_name), uuid::log::Facility::DAEMON}; +uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON}; Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr && content) : id_(id) @@ -58,34 +54,65 @@ MqttMessage::MqttMessage(const uint8_t operation, const std::string & topic, con , retain(retain) { } -Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction) - : device_id_(device_id) +Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_type, const std::string && topic, const std::string && full_topic, mqtt_subfunction_p mqtt_subfunction) + : device_type_(device_type) , topic_(topic) + , full_topic_(full_topic) , mqtt_subfunction_(mqtt_subfunction) { } +Mqtt::MQTTCmdFunction::MQTTCmdFunction(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction) + : device_type_(device_type) + , cmd_(cmd) + , mqtt_cmdfunction_(mqtt_cmdfunction) { +} + // subscribe to an MQTT topic, and store the associated callback function -void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb) { - auto message = queue_subscribe_message(topic); // add subscription to queue. The hostname will automatically be appended - - if (message == nullptr) { - return; - } - - // the message will contain the full topic, with the hostname prefixed +// only if it already hasn't been added +void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) { // check if we already have the topic subscribed, if so don't add it again - bool exists = false; if (!mqtt_subfunctions_.empty()) { for (const auto & mqtt_subfunction : mqtt_subfunctions_) { - if ((mqtt_subfunction.device_id_ == device_id) && (strcmp(mqtt_subfunction.topic_.c_str(), message->topic.c_str()) == 0)) { - exists = true; + if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) { + return; // it exists, exit } } } - if (!exists) { - mqtt_subfunctions_.emplace_back(device_id, std::move(message->topic), cb); // register a call back function for a specific telegram type + // add to MQTT queue as a subscribe operation + auto message = queue_subscribe_message(topic); + + // register in our libary with the callback function. + // We store both the original topic and the fully-qualified + mqtt_subfunctions_.emplace_back(device_type, std::move(topic), std::move(message->topic), cb); +} + +// adds a command and callback function for a specific device +void Mqtt::add_command(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb) { + // subscribe to the command topic if it doesn't exist yet + // create the cmd topic for a device like "_cmd" e.g. "boiler_cmd" + // unless its a system MQTT command + std::string cmd_topic(40, '\0'); + if (device_type == EMSdevice::DeviceType::SERVICEKEY) { + cmd_topic = "system"; // hard-coded system + } else { + snprintf_P(&cmd_topic[0], 40, PSTR("%s_cmd"), EMSdevice::device_type_topic_name(device_type).c_str()); } + + bool exists = false; + if (!mqtt_subfunctions_.empty()) { + for (const auto & mqtt_subfunction : mqtt_subfunctions_) { + if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), cmd_topic.c_str()) == 0)) { + exists = true; + } + } + } + if (!exists) { + Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function + } + + // add the function to our list + mqtt_cmdfunctions_.emplace_back(device_type, cmd, cb); } // subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device @@ -142,7 +169,20 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { // show subscriptions shell.printfln(F("MQTT subscriptions:")); for (const auto & mqtt_subfunction : mqtt_subfunctions_) { - shell.printfln(F(" %s"), mqtt_subfunction.topic_.c_str()); + shell.printfln(F(" %s"), mqtt_subfunction.full_topic_.c_str()); + } + shell.println(); + + // show command handlers + shell.printfln(F("MQTT commands:")); + for (const auto & mqtt_cmdfunction : mqtt_cmdfunctions_) { + if (mqtt_cmdfunction.device_type_ == EMSdevice::DeviceType::SERVICEKEY) { + shell.printfln(F(" on topic: system, cmd: %s"), uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str()); // hardcoded topic is system + } else { + shell.printfln(F(" on topic: %s_cmd, cmd: %s"), + EMSdevice::device_type_topic_name(mqtt_cmdfunction.device_type_).c_str(), + uuid::read_flash_string(mqtt_cmdfunction.cmd_).c_str()); + } } shell.println(); @@ -190,6 +230,31 @@ void Mqtt::incoming(char * topic, char * payload) { on_message(topic, payload, strlen(payload)); } +// 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("calling command %s, value %s, id is default"), cmd, value); + } else { + LOG_DEBUG(F("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(char * topic, char * payload, size_t len) { if (len == 0) { @@ -199,33 +264,65 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) { // convert payload to a null-terminated char string char message[len + 2]; strlcpy(message, payload, len + 1); + +#ifdef EMSESP_DEBUG LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len); +#endif // see if we have this topic in our subscription list, then call its callback handler - // note: this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail. Not sure if this is going to be an issue? for (const auto & mf : mqtt_subfunctions_) { - if (strcmp(topic, mf.topic_.c_str()) == 0) { - (mf.mqtt_subfunction_)(message); - return; + if (strcmp(topic, mf.full_topic_.c_str()) == 0) { + if (mf.mqtt_subfunction_) { + (mf.mqtt_subfunction_)(message); // matching function, call it + return; + } else { + // empty function. It's a command then. Find the command from the json and call it directly. + StaticJsonDocument doc; + DeserializationError error = deserializeJson(doc, message); + if (error) { + LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str()); + return; + } + + const char * command = doc["cmd"]; + if (command == nullptr) { + LOG_ERROR(F("MQTT error: invalid payload cmd format. message=%s"), message); + return; + } + + // check for hc and id + int8_t n = -1; // no value + if (doc.containsKey("hc")) { + n = doc["hc"]; + } else if (doc.containsKey("id")) { + n = doc["id"]; + } + + if (!call_command(mf.device_type_, command, doc["data"], n)) { + // if we got here we didn't find a matching command + LOG_ERROR(F("MQTT error: no matching cmd: %s"), command); + } + + return; + } } } - // if we got here we didn't find a topic match - LOG_DEBUG(F("No responding handler found for topic %s"), topic); + LOG_ERROR(F("No MQTT handler found for topic %s and payload %s"), topic, message); } -// print all the topics related to a specific device_id -void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id) { +// print all the topics related to a specific device type +void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type) { if (std::count_if(mqtt_subfunctions_.cbegin(), mqtt_subfunctions_.cend(), - [=](MQTTSubFunction const & mqtt_subfunction) { return device_id == mqtt_subfunction.device_id_; }) + [=](MQTTSubFunction const & mqtt_subfunction) { return device_type == mqtt_subfunction.device_type_; }) == 0) { return; } shell.print(F(" Subscribed MQTT topics: ")); for (const auto & mqtt_subfunction : mqtt_subfunctions_) { - if (mqtt_subfunction.device_id_ == device_id) { + if (mqtt_subfunction.device_type_ == device_type) { shell.printf(F("%s "), mqtt_subfunction.topic_.c_str()); } } @@ -297,8 +394,13 @@ void Mqtt::start() { mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true mqttClient_->onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + // receiving mqtt on_message(topic, payload, len); - mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); }); + }); + + mqttClient_->onPublish([this](uint16_t packetId) { + // publish + on_publish(packetId); }); } @@ -328,15 +430,18 @@ void Mqtt::on_connect() { resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics // add the system MQTT subscriptions, only if its a fresh start with no previous subscriptions + // these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""} if (mqtt_subfunctions_.empty()) { - Mqtt::subscribe("cmd", System::mqtt_commands); + add_command(EMSdevice::DeviceType::SERVICEKEY, F("gpio"), System::mqtt_command_gpio); + add_command(EMSdevice::DeviceType::SERVICEKEY, F("send"), System::mqtt_command_send); } LOG_INFO(F("MQTT connected")); } -// add sub or pub task to the queue. When the message is created, the topic will have -// automatically the hostname prefixed. +// add sub or pub task to the queue. +// a fully-qualified topic is created by prefixing the hostname, unless it's HA +// returns a pointer to the message created std::shared_ptr Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix) { if (topic.empty()) { diff --git a/src/mqtt.h b/src/mqtt.h index b0ec6b3e8..cae72b82e 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -44,6 +44,8 @@ using uuid::console::Shell; namespace emsesp { using mqtt_subfunction_p = std::function; +using mqtt_cmdfunction_p = std::function; + using namespace std::placeholders; // for `_1` struct MqttMessage { @@ -68,20 +70,24 @@ class Mqtt { static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100; - static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb); + static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb); static void subscribe(const std::string & topic, mqtt_subfunction_p cb); static void resubscribe(); + static void add_command(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p cb); + static void publish(const std::string & topic, const std::string & payload, bool retain = false); static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false); static void publish(const std::string & topic, const bool value); static void publish(const std::string & topic); - static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id); + static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_mqtt(uuid::console::Shell & shell); 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(); } @@ -102,6 +108,20 @@ class Mqtt { static std::string hostname_; + class MQTTCmdFunction { + public: + MQTTCmdFunction(const uint8_t device_type, const __FlashStringHelper * cmd, mqtt_cmdfunction_p mqtt_cmdfunction); + ~MQTTCmdFunction() = default; + + const uint8_t device_type_; + const __FlashStringHelper * cmd_; + mqtt_cmdfunction_p mqtt_cmdfunction_; + }; + + static std::vector commands() { + return mqtt_cmdfunctions_; + } + private: static uuid::log::Logger logger_; @@ -127,29 +147,32 @@ class Mqtt { static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing - static std::shared_ptr queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix = false); + static std::shared_ptr + queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix = false); static std::shared_ptr queue_publish_message(const std::string & topic, const std::string & payload, const bool retain); static std::shared_ptr queue_subscribe_message(const std::string & topic); - void on_publish(uint16_t packetId); - void on_message(char * topic, char * payload, size_t len); - void process_queue(); - void process_all_queue(); + void on_publish(uint16_t packetId); + void on_message(char * topic, char * payload, size_t len); + void process_queue(); + void process_all_queue(); static uint16_t mqtt_publish_fails_; // function handlers for MQTT subscriptions class MQTTSubFunction { public: - MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction); + MQTTSubFunction(const uint8_t device_type, const std::string && topic, const std::string && full_topic, mqtt_subfunction_p mqtt_subfunction); ~MQTTSubFunction() = default; - const uint8_t device_id_; // which device ID owns this + const uint8_t device_type_; // which device type, from DeviceType:: const std::string topic_; - mqtt_subfunction_p mqtt_subfunction_; + const std::string full_topic_; // the fully qualified topic name, usually with the hostname prefixed + mqtt_subfunction_p mqtt_subfunction_; // can be empty }; 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_ = 0; diff --git a/src/sensors.cpp b/src/sensors.cpp index 26a0a1223..ffe33d18b 100644 --- a/src/sensors.cpp +++ b/src/sensors.cpp @@ -16,13 +16,11 @@ * along with this program. If not, see . */ -// code written by nomis - https://github.com/nomis +// code originally written by nomis - https://github.com/nomis #include "sensors.h" #include "emsesp.h" -MAKE_PSTR(logger_name, "sensors") - #ifdef ESP32 #define YIELD #else @@ -31,7 +29,7 @@ MAKE_PSTR(logger_name, "sensors") namespace emsesp { -uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON}; +uuid::log::Logger Sensors::logger_{F_(sensors), uuid::log::Facility::DAEMON}; void Sensors::start() { // copy over values from MQTT so we don't keep on quering the filesystem @@ -206,7 +204,7 @@ float Sensors::get_temperature_c(const uint8_t addr[]) { break; } - uint32_t raw = (raw_value *625) / 100; // round to 0.01 + uint32_t raw = (raw_value * 625) / 100; // round to 0.01 return (float)raw / 100; #else return NAN; @@ -303,4 +301,4 @@ void Sensors::publish_values() { } } -} // namespace emsesp +} // namespace emsesp \ No newline at end of file diff --git a/src/sensors.h b/src/sensors.h index 7ec37d6d3..f98797cd5 100644 --- a/src/sensors.h +++ b/src/sensors.h @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -// code written by nomis - https://github.com/nomis +// code originally written by nomis - https://github.com/nomis #ifndef EMSESP_SENSORS_H #define EMSESP_SENSORS_H @@ -87,10 +87,10 @@ class Sensors { static constexpr uint8_t TYPE_DS1822 = 0x22; static constexpr uint8_t TYPE_DS1825 = 0x3B; - static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds - static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds - static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds - static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds + static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds + static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds + static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds + static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds static constexpr uint8_t CMD_CONVERT_TEMP = 0x44; static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE; @@ -112,9 +112,8 @@ class Sensors { uint8_t mqtt_format_; uint8_t retrycnt_ = 0; - }; } // namespace emsesp -#endif +#endif \ No newline at end of file diff --git a/src/shower.cpp b/src/shower.cpp index 6ca21de5d..2d8e33ef2 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -18,11 +18,9 @@ #include "shower.h" -MAKE_PSTR(logger_name, "shower") - namespace emsesp { -uuid::log::Logger Shower::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger Shower::logger_{F_(shower), uuid::log::Facility::CONSOLE}; void Shower::start() { EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { diff --git a/src/system.cpp b/src/system.cpp index 9bac30722..a99188a07 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -21,25 +21,9 @@ #include "version.h" // firmware version of EMS-ESP -MAKE_PSTR_WORD(passwd) -MAKE_PSTR_WORD(hostname) -MAKE_PSTR_WORD(wifi) -MAKE_PSTR_WORD(reconnect) -MAKE_PSTR_WORD(ssid) -MAKE_PSTR_WORD(heartbeat) -MAKE_PSTR_WORD(users) - -MAKE_PSTR(host_fmt, "Host = %s") -MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s") -MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus"); -MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s"); -MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S") - -MAKE_PSTR(logger_name, "system") - namespace emsesp { -uuid::log::Logger System::logger_{F_(logger_name), uuid::log::Facility::KERN}; +uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN}; #ifndef EMSESP_STANDALONE uuid::syslog::SyslogService System::syslog_; @@ -50,94 +34,28 @@ uint32_t System::heap_start_ = 0; int System::reset_counter_ = 0; bool System::upload_status_ = false; -// handle generic system related MQTT commands -void System::mqtt_commands(const char * message) { - StaticJsonDocument doc; - DeserializationError error = deserializeJson(doc, message); - if (error) { - LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str()); - return; - } - - if (doc["send"] != nullptr) { - const char * data = doc["send"]; - EMSESP::send_raw_telegram(data); - LOG_INFO(F("Sending raw: %s"), data); - } - +// send on/off to a gpio pin +// value: true = HIGH, false = LOW +void System::mqtt_command_gpio(const char * value, const int8_t id) { #if defined(ESP8266) - const uint8_t d0_ = 16; - const uint8_t d1_ = 5; - const uint8_t d2_ = 4; - const uint8_t d3_ = 0; + const uint8_t pins[] = {16, 5, 4, 0}; #elif defined(ESP32) - const uint8_t d0_ = 26; - const uint8_t d1_ = 22; - const uint8_t d2_ = 21; - const uint8_t d3_ = 17; + const uint8_t pins[] = {26, 22, 21, 17}; +#else + const uint8_t pins[] = {0, 1, 2, 3}; #endif - -#ifndef EMSESP_STANDALONE - if (doc["D0"] != nullptr) { - const int8_t set = doc["D0"]; - pinMode(d0_, OUTPUT); - if (set == 1) { - digitalWrite(d0_, HIGH); - } else if (set == 0) { - digitalWrite(d0_, LOW); - } - LOG_INFO(F("Port D0 set to %d"), set); + bool v = false; + if (Helpers::value2bool(value, v)) { + uint8_t gpio = pins[id]; // D0 - D3 + pinMode(gpio, OUTPUT); + digitalWrite(gpio, v); + LOG_INFO(F("Port D%d set to %s"), id, v ? "HIGH" : "LOW"); } +} - if (doc["D1"] != nullptr) { - const int8_t set = doc["D1"]; - pinMode(d1_, OUTPUT); - if (set == 1) { - digitalWrite(d1_, HIGH); - } else if (set == 0) { - digitalWrite(d1_, LOW); - } - LOG_INFO(F("Port D1 set to %d"), set); - } - - if (doc["D2"] != nullptr) { - const int8_t set = doc["D2"]; - pinMode(d2_, OUTPUT); - if (set == 1) { - digitalWrite(d2_, HIGH); - } else if (set == 0) { - digitalWrite(d2_, LOW); - } - LOG_INFO(F("Port D2 set to %d"), set); - } - - if (doc["D3"] != nullptr) { - const int8_t set = doc["D3"]; - pinMode(d3_, OUTPUT); - if (set == 1) { - digitalWrite(d3_, HIGH); - } else if (set == 0) { - digitalWrite(d3_, LOW); - } - LOG_INFO(F("Port D3 set to %d"), set); - } -#endif - - const char * command = doc["cmd"]; - if (command == nullptr) { - return; - } - - // send raw command - if (strcmp(command, "send") == 0) { - const char * data = doc["data"]; - if (data == nullptr) { - return; - } - EMSESP::send_raw_telegram(data); - LOG_INFO(F("Sending raw: %s"), data); - return; - } +// send raw +void System::mqtt_command_send(const char * value, const int8_t id) { + EMSESP::send_raw_telegram(value); // ignore id } // restart EMS-ESP @@ -646,9 +564,10 @@ void System::console_commands(Shell & shell, unsigned int context) { // upgrade from previous versions of EMS-ESP void System::check_upgrade() { + /* // check for v1.9. It uses SPIFFS and only on the ESP8266 #if defined(ESP8266) - Serial.begin(115200); // TODO remove, just for debugging + Serial.begin(115200); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -771,6 +690,7 @@ void System::check_upgrade() { }, "local"); #endif +*/ } } // namespace emsesp diff --git a/src/system.h b/src/system.h index e46965017..b45402fed 100644 --- a/src/system.h +++ b/src/system.h @@ -47,7 +47,9 @@ class System { static void format(uuid::console::Shell & shell); static void console_commands(Shell & shell, unsigned int context); - static void mqtt_commands(const char * message); + + static void mqtt_command_gpio(const char * value, const int8_t id); + static void mqtt_command_send(const char * value, const int8_t id); static uint8_t free_mem(); static void upload_status(bool in_progress); diff --git a/src/telegram.cpp b/src/telegram.cpp index 97e2a41ce..9f9d7fd89 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -19,8 +19,6 @@ #include "telegram.h" #include "emsesp.h" -MAKE_PSTR(logger_name, "telegram") - namespace emsesp { // CRC lookup table with poly 12 for faster checking @@ -45,7 +43,7 @@ uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID; uint8_t EMSbus::tx_mode_ = EMSESP_DEFAULT_TX_MODE; uint8_t EMSbus::tx_state_ = Telegram::Operation::NONE; -uuid::log::Logger EMSbus::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; +uuid::log::Logger EMSbus::logger_{F_(telegram), uuid::log::Facility::CONSOLE}; // Calculates CRC checksum using lookup table for speed // length excludes the last byte (which mainly is the CRC) @@ -572,11 +570,13 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui return; } +#ifdef EMSESP_DENUG LOG_DEBUG(F("[DEBUG] Last Tx %s operation failed. Retry #%d. sent message: %s, received: %s"), (operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"), retry_count_, telegram_last_->to_string().c_str(), Helpers::data_to_hex(data, length).c_str()); +#endif // add to the top of the queue if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) { diff --git a/src/telegram.h b/src/telegram.h index 14f762a12..aaf1580db 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -38,9 +38,9 @@ #include "helpers.h" // default values for null values -static constexpr uint8_t VALUE_BOOL = true; // is a boolean -static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // is a boolean -static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false. True can be 0x01 or 0xFF sometimes. +static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that something is a boolean +static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false +static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes diff --git a/src/test/test.cpp b/src/test/test.cpp index 440245cad..f12793f58 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -17,6 +17,7 @@ * along with this program. If not, see . */ + #if defined(EMSESP_STANDALONE) #include "test.h" @@ -26,6 +27,14 @@ namespace emsesp { // create some fake test data // used with the 'test' command, under su/admin void Test::run_test(uuid::console::Shell & shell, const std::string & command) { + if (command == "default") { + run_test(shell, "mqtt"); // add the default test case here + } + + if (command.empty()) { + run_test(shell, "default"); + } + if (command == "render") { uint8_t test1 = 12; int8_t test2 = -12; @@ -129,7 +138,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); } - if (command == "boiler2") { + if (command == "boiler") { // question: do we need to set the mask? std::string version("1.2.3"); EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline @@ -180,7 +189,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { } if (command == "thermostat") { - shell.printfln(F("Testing adding devices on the EMS bus...")); + shell.printfln(F("Testing adding a thermostat to the EMS bus...")); // create some fake devices std::string version("1.2.3"); @@ -245,7 +254,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { // see https://github.com/proddy/EMS-ESP/issues/390 /* - uart_telegram_withCRC("90 48 FF 04 01 A6 5C"); uart_telegram_withCRC("90 48 FF 00 01 A6 4C"); uart_telegram_withCRC("90 48 FF 08 01 A7 6D"); @@ -283,7 +291,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { uart_telegram_withCRC("C8 90 FF 00 02 01 A6 D0"); // uart_telegram_withCRC("10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00"); - */ uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA"); @@ -473,49 +480,115 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { EMSESP::txservice_.flush_tx_queue(); } - if (command == "mqtt1") { + if (command == "mqtt") { shell.printfln(F("Testing MQTT...")); - // MQTT test - EMSESP::txservice_.flush_tx_queue(); - EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me"); - // EMSESP::mqtt_.show_queue(); - // simulate an incoming mqtt msg + // simulate an on connect + Mqtt::on_connect(); + + // add a boiler + // question: do we need to set the mask? + std::string version("1.2.3"); + EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline + + // add a thermostat + EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 + EMSESP::rxservice_.loop(); + + // RCPLUSStatusMessage_HC1(0x01A5) + uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24, + 0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); + uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC + uart_telegram_withCRC("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 13"); // with CRC + + EMSESP::rxservice_.loop(); + shell.loop_all(); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char payload[100]; - strcpy(topic, "boiler_cmd"); + // test publish and adding to queue + EMSESP::txservice_.flush_tx_queue(); + EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me"); + Mqtt::show_mqtt(shell); + + strcpy(topic, "ems-esp/boiler_cmd"); strcpy(payload, "12345"); - EMSESP::mqtt_.incoming(topic, payload); - EMSESP::mqtt_.incoming(payload, payload); // should report error + EMSESP::mqtt_.incoming(topic, payload); // invalid format + EMSESP::mqtt_.incoming(payload, payload); // no matching topic - strcpy(topic, "thermostat_cmd_mode"); - strcpy(payload, "auto"); + strcpy(topic, "ems-esp/boiler_cmd"); + strcpy(payload, "{\"cmd\":\"garbage\",\"data\":22.52}"); + EMSESP::mqtt_.incoming(topic, payload); // should report error + + strcpy(topic, "ems-esp/boiler_cmd"); + strcpy(payload, "{\"cmd\":\"comfort\",\"data\":\"eco\"}"); EMSESP::mqtt_.incoming(topic, payload); - strcpy(topic, "thermostat_cmd_temp"); - strcpy(payload, "20"); + strcpy(topic, "ems-esp/boiler_cmd"); + strcpy(payload, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}"); EMSESP::mqtt_.incoming(topic, payload); - strcpy(topic, "thermostat_cmd"); + strcpy(topic, "ems-esp/boiler_cmd"); + strcpy(payload, "{\"cmd\":\"wwactivated\",\"data\":1}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/boiler_cmd"); + strcpy(payload, "{\"cmd\":\"flowtemp\",\"data\":55}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/system"); + strcpy(payload, "{\"cmd\":\"send\",\"data\":\"01 02 03 04 05\"}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/system"); + strcpy(payload, "{\"cmd\":\"gpio\",\"id\":1,\"data\":\"1\"}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"wwmode\",\"data\":\"auto\"}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"control\",\"data\":\"1\"}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"control\",\"data\":1}"); + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}"); // with id + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // with hc + EMSESP::mqtt_.incoming(topic, payload); + + strcpy(topic, "ems-esp/thermostat_cmd"); strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}"); EMSESP::mqtt_.incoming(topic, payload); - strcpy(topic, "boiler_cmd_wwtemp"); - strcpy(payload, "66"); + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}"); EMSESP::mqtt_.incoming(topic, payload); - strcpy(topic, "thermostat_cmd"); - strcpy(payload, "{\"cmd\":\"temp\",\"hc\":2,\"data\":22}"); - EMSESP::mqtt_.incoming(topic, payload); - - strcpy(topic, "home/ems-esp/cmd"); - strcpy(payload, "restart"); + strcpy(topic, "ems-esp/thermostat_cmd"); + strcpy(payload, "{\"cmd\":\"temp\",\"id\":2,\"data\":22}"); EMSESP::mqtt_.incoming(topic, payload); // EMSESP::txservice_.show_tx_queue(); + // EMSESP::publish_all_values(); - EMSESP::publish_all_values(); + EMSESP::add_context_menus(); // need to add this as it happens later in the code + shell.invoke_command("su"); + shell.invoke_command("thermostat"); + shell.invoke_command("help"); + shell.invoke_command("call"); + shell.invoke_command("call wwmode"); + shell.invoke_command("call mode auto 2"); + + shell.loop_all(); } if (command == "poll2") { @@ -542,12 +615,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) { uart_telegram({0x21, 0x0B, 0xFF, 0x00}); } - if (command == "mqtt2") { - for (uint8_t i = 0; i < 30; i++) { - Mqtt::subscribe("topic", dummy_mqtt_commands); - } - } - // testing the UART tx command, without a queue if (command == "tx2") { uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC @@ -686,9 +753,6 @@ void Test::uart_telegram(const char * rx_data) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" -void Test::dummy_mqtt_commands(const char * message) { - // -} #pragma GCC diagnostic pop diff --git a/src/test/test.h b/src/test/test.h index c45fe42d4..5e3fee5aa 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -36,8 +36,6 @@ #include "mqtt.h" #include "emsesp.h" -MAKE_PSTR_WORD(test) - namespace emsesp { class Test { diff --git a/src/version.h b/src/version.h index 0d74e7d44..2d648f16b 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0b11" +#define EMSESP_APP_VERSION "2.0.0b12"