From b2bb8e2b5a1b3f83b1531619daa59afc2baa4e5c Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 23 May 2020 14:00:14 +0200 Subject: [PATCH] a6 --- README.md | 5 +- lib/uuid-console/src/shell.cpp | 3 +- lib/uuid-console/src/shell_log.cpp | 1 - platformio.ini | 15 +-- src/boiler.cpp | 119 +++++++++++---------- src/boiler.h | 36 +++---- src/console.cpp | 165 ++++++++++++++--------------- src/console.h | 13 ++- src/device_library.h | 27 ++--- src/emsdevice.cpp | 21 ++-- src/emsdevice.h | 9 +- src/emsesp.cpp | 44 +++++--- src/emsesp.h | 7 +- src/mixing.cpp | 12 +-- src/mqtt.cpp | 37 ++++--- src/mqtt.h | 20 ++-- src/network.cpp | 1 + src/solar.cpp | 18 ++-- src/system.cpp | 8 +- src/system.h | 4 +- src/test/test_data.h | 16 ++- src/thermostat.cpp | 108 +++++++++++-------- src/thermostat.h | 4 +- src/version.h | 2 +- 24 files changed, 391 insertions(+), 304 deletions(-) diff --git a/README.md b/README.md index 28302370c..093e50d34 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ Note: Version 2.0 is not backward compatible with v1.0. The File system structur ### **Design & coding principles** -- The code can be built and run without an ESP microcontroller, which is useful when testing and simulating handling of the different telegrams and devices. Make sure you have GNU make and g++ installed and use 'make' to build the image and execute the file `emsesp` (on linux). +- The code can be built and run without an ESP microcontroller, which is useful when testing and simulating handling of the different telegrams and devices. Make sure you have GNU make and g++ installed and use 'make' to build the image and execute the file `emsesp` (on linux). - I used C++11 containers where I could (std::string, std::deque, std::list, std::multimap etc). - The core is based off the great libraries from @nomis' and I adopted his general design pattens such as making everything as asynchronous as possible so that no one operation should starve another operation of it's time to execute (https://isocpp.org/wiki/faq/ctors#static-init-order). - All EMS devices (e.g. boiler, thermostat, solar modules etc) are derived from a factory base class and each class handles its own registering of telegram and mqtt handlers. This makes the EMS device code easier to manage and extend with new telegrams types and features. - Built to work with both EMS8266 and ESP32. -- Extended MQTT to use MQTT discovery on Home Assistant, just for the thermostat +- Extended MQTT to use MQTT discovery on Home Assistant, just for the thermostat for now. ### **Features** @@ -124,6 +124,7 @@ thermostat ### **Known issues, bugs and improvements currently working on** ``` +TODO when doing show, should we sort the ems devices? TODO figure out why sometimes telnet on ESP32 (and sometimes ESP8266) has slow response times. After a manual reset it seems to fix itself. Perhaps the telnet service needs to start after the wifi is up & running. TODO Get the ESP32 UART code working. TODO sometimes with tx_mode 0 there are a few CRC errors due to collision when waiting for a BRK signal. diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index 3ee59a8dd..9caaa9ed7 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -58,7 +58,8 @@ Shell::~Shell() { void Shell::start() { #ifdef EMSESP_DEBUG - uuid::log::Logger::register_handler(this, uuid::log::Level::DEBUG); // added by proddy + // uuid::log::Logger::register_handler(this, uuid::log::Level::DEBUG); // added by proddy + uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // added by proddy #else uuid::log::Logger::register_handler(this, uuid::log::Level::NOTICE); #endif diff --git a/lib/uuid-console/src/shell_log.cpp b/lib/uuid-console/src/shell_log.cpp index 7f0267b1d..a33c7463a 100644 --- a/lib/uuid-console/src/shell_log.cpp +++ b/lib/uuid-console/src/shell_log.cpp @@ -76,7 +76,6 @@ void Shell::output_logs() { auto message = std::move(log_messages_.front()); log_messages_.pop_front(); - print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3)); printf(F(" %c %lu: [%S] "), uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name); diff --git a/platformio.ini b/platformio.ini index 41c00a9b5..78616f747 100644 --- a/platformio.ini +++ b/platformio.ini @@ -3,7 +3,7 @@ [platformio] default_envs = esp8266 -;default_envs = esp32 +; default_envs = esp32 # override any settings with your own local ones in pio_local.ini extra_configs = pio_local.ini @@ -33,6 +33,7 @@ build_flags = -std=c++11 -Os -fno-exceptions -D ARDUINOJSON_USE_LONG_LONG=0 -D BEARSSL_SSL_BASIC -D PROGMEM_WWW + -D UUID_TELNET_HAVE_WIFICLIENT_NODELAY=0 libs_core = ArduinoJson @@ -44,9 +45,8 @@ libs_esp8266 = libs_esp32 = [env] -build_unflags = -fno-rtti ; for dynamic_cast<> -lib_ldf_mode = chain+ -lib_compat_mode = strict +;lib_ldf_mode = chain+ +;lib_compat_mode = strict extra_scripts = scripts/main_script.py framework = arduino monitor_speed = 115200 @@ -57,13 +57,14 @@ check_flags = cppcheck: --std=c++11 clangtidy: --checks=-*,clang-analyzer-*,performance-* +; USB upload +upload_protocol = esptool ; example ports for OSX ;upload_port = /dev/cu.wchusbserial14403 ;upload_port = /dev/cu.usbserial-1440 ;upload_port = /dev/cu.SLAB_USBtoUART -; OTA -upload_protocol = esptool +; OTA upload ; upload_protocol = espota ; upload_flags = ; --port=8266 @@ -79,7 +80,7 @@ board = esp12e lib_deps = ${common.libs_core} ${common.libs_esp8266} board_build.f_cpu = 160000000L ; 160MHz ;board_build.ldscript = eagle.flash.4m1m.ld ; 1019 KB sketch, 1000 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 2052 KB OTA & buffer -board_build.ldscript = eagle.flash.4m2m.ld ; same as above but with 2024 KB SPIFFS +board_build.ldscript = eagle.flash.4m2m.ld ; 1019 KB sketch, 2024 KB SPIFFS. 4KB EEPROM, 4KB RFCAL, 12KB WIFI stack, 1028 KB OTA & buffer build_flags = ${common.build_flags} ${common.debug_flags} -flto -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY [env:esp32] diff --git a/src/boiler.cpp b/src/boiler.cpp index 16a630238..d708dd5ed 100644 --- a/src/boiler.cpp +++ b/src/boiler.cpp @@ -55,14 +55,14 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, std::bind(&Boiler::process_UBAMaintenanceStatus, this, _1)); register_telegram_type(0x2A, F("MC10Status"), false, std::bind(&Boiler::process_MC10Status, this, _1)); register_telegram_type(0x33, F("UBAParameterWW"), true, std::bind(&Boiler::process_UBAParameterWW, this, _1)); - register_telegram_type(0x14, F("UBATotalUptime"), true, std::bind(&Boiler::process_UBATotalUptime, this, _1)); + register_telegram_type(0x14, F("UBATotalUptime"), false, std::bind(&Boiler::process_UBATotalUptime, this, _1)); register_telegram_type(0x35, F("UBAFlags"), false, std::bind(&Boiler::process_UBAFlags, this, _1)); register_telegram_type(0x15, F("UBAMaintenanceSettings"), false, std::bind(&Boiler::process_UBAMaintenanceSettings, this, _1)); register_telegram_type(0x16, F("UBAParameters"), true, std::bind(&Boiler::process_UBAParameters, this, _1)); register_telegram_type(0x1A, F("UBASetPoints"), false, std::bind(&Boiler::process_UBASetPoints, this, _1)); register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, std::bind(&Boiler::process_UBAOutdoorTemp, 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(0xE4, F("UBAMonitorFastPlus"), true, std::bind(&Boiler::process_UBAMonitorFastPlus, this, _1)); + register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), true, std::bind(&Boiler::process_UBAMonitorSlowPlus, this, _1)); register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1)); register_telegram_type(0xE3, F("HeatPumpMonitor1"), true, std::bind(&Boiler::process_HPMonitor1, this, _1)); @@ -82,8 +82,7 @@ void Boiler::add_context_menu() { CommandFlags::USER, flash_string_vector{F_(boiler)}, [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { - dynamic_cast(shell).enter_custom_context(ShellContext::BOILER); - console_commands(); + Boiler::console_commands(shell, ShellContext::BOILER); }); } @@ -316,7 +315,10 @@ void Boiler::publish_values() { DEBUG_LOG(F("[DEBUG] Performing a boiler publish")); #endif - Mqtt::publish("boiler_data", doc); + // if we have data, publish it + if (!doc.isNull()) { + Mqtt::publish("boiler_data", doc); + } } // called after a process command is called, to check values and see if we need to force an MQTT publish @@ -330,22 +332,22 @@ void Boiler::show_values(uuid::console::Shell & shell) { char buffer[10]; // used for formatting - print_value(shell, F("Selected flow temperature"), F_(degrees), Helpers::render_value(buffer, selFlowTemp_, 1)); - print_value(shell, F("Warm Water selected temperature"), F_(degrees), Helpers::render_value(buffer, wWSelTemp_, 1)); + print_value(shell, 2, F("Selected flow temperature"), F_(degrees), Helpers::render_value(buffer, selFlowTemp_, 1)); + print_value(shell, 2, F("Warm Water selected temperature"), F_(degrees), Helpers::render_value(buffer, wWSelTemp_, 1)); if (tap_water_active_ != EMS_VALUE_BOOL_NOTSET) { - print_value(shell, F("Hot tap water"), tap_water_active_ ? "running" : "off"); + print_value(shell, 2, F("Hot tap water"), tap_water_active_ ? "running" : "off"); } if (heating_active_ != EMS_VALUE_BOOL_NOTSET) { - print_value(shell, F("Central heating"), heating_active_ ? "active" : "off"); + print_value(shell, 2, F("Central heating"), heating_active_ ? "active" : "off"); } - print_value(shell, F("Warm Water activated"), Helpers::render_value(buffer, wWActivated_, EMS_VALUE_BOOL)); - print_value(shell, F("Warm Water circulation pump available"), Helpers::render_value(buffer, wWCircPump_, EMS_VALUE_BOOL)); - print_value(shell, F("Warm Water circulation pump type"), wWCircPumpType_ ? "3-way pump" : "charge pump"); + print_value(shell, 2, F("Warm Water activated"), Helpers::render_value(buffer, wWActivated_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Warm Water circulation pump available"), Helpers::render_value(buffer, wWCircPump_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Warm Water circulation pump type"), wWCircPumpType_ ? "3-way pump" : "charge pump"); if (wWCircPumpMode_ == 7) { - print_value(shell, F("Warm Water circulation pump freq"), "continuous"); + print_value(shell, 2, F("Warm Water circulation pump freq"), "continuous"); } else { char s[7]; char buffer[2]; @@ -353,64 +355,64 @@ void Boiler::show_values(uuid::console::Shell & shell) { buffer[1] = '\0'; strlcpy(s, buffer, 7); strlcat(s, "x3min", 7); - print_value(shell, F("Warm Water circulation pump freq"), s); + print_value(shell, 2, F("Warm Water circulation pump freq"), s); } if (wWComfort_ == 0x00) { - print_value(shell, F("Warm Water comfort setting"), "Hot"); + print_value(shell, 2, F("Warm Water comfort setting"), "Hot"); } else if (wWComfort_ == 0xD8) { - print_value(shell, F("Warm Water comfort setting"), "Eco"); + print_value(shell, 2, F("Warm Water comfort setting"), "Eco"); } else if (wWComfort_ == 0xEC) { - print_value(shell, F("Warm Water comfort setting"), "Intelligent"); + print_value(shell, 2, F("Warm Water comfort setting"), "Intelligent"); } - print_value(shell, F("Warm Water selected temperature"), F_(degrees), Helpers::render_value(buffer, wWSelTemp_, 1)); - print_value(shell, F("Warm Water disinfection temperature"), F_(degrees), Helpers::render_value(buffer, wWDisinfectTemp_, 1)); - print_value(shell, F("Warm Water circulation active"), F_(degrees), Helpers::render_value(buffer, wWCirc_, EMS_VALUE_BOOL)); - print_value(shell, F("Warm Water set temperature"), F_(degrees), Helpers::render_value(buffer, wWSetTmp_, 1)); - print_value(shell, F("Warm Water current temperature"), F_(degrees), Helpers::render_value(buffer, wWCurTmp_, 10)); - print_value(shell, F("Warm water temperature (intern)"), F_(degrees), Helpers::render_value(buffer, wwStorageTemp1_, 10)); - print_value(shell, F("Warm water temperature (extern)"), F_(degrees), Helpers::render_value(buffer, wwStorageTemp2_, 10)); - print_value(shell, F("Warm Water current temperature (extern)"), F_(degrees), Helpers::render_value(buffer, wWCurTmp2_, 10)); - print_value(shell, F("Warm Water current tap water flow"), F("l/min"), Helpers::render_value(buffer, wWCurFlow_, 10)); - print_value(shell, F("Warm Water # starts"), Helpers::render_value(buffer, wWStarts_, 1)); + print_value(shell, 2, F("Warm Water selected temperature"), F_(degrees), Helpers::render_value(buffer, wWSelTemp_, 1)); + print_value(shell, 2, F("Warm Water disinfection temperature"), F_(degrees), Helpers::render_value(buffer, wWDisinfectTemp_, 1)); + print_value(shell, 2, F("Warm Water circulation active"), F_(degrees), Helpers::render_value(buffer, wWCirc_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Warm Water set temperature"), F_(degrees), Helpers::render_value(buffer, wWSetTmp_, 1)); + print_value(shell, 2, F("Warm Water current temperature"), F_(degrees), Helpers::render_value(buffer, wWCurTmp_, 10)); + print_value(shell, 2, F("Warm water temperature (intern)"), F_(degrees), Helpers::render_value(buffer, wwStorageTemp1_, 10)); + print_value(shell, 2, F("Warm water temperature (extern)"), F_(degrees), Helpers::render_value(buffer, wwStorageTemp2_, 10)); + print_value(shell, 2, F("Warm Water current temperature (extern)"), F_(degrees), Helpers::render_value(buffer, wWCurTmp2_, 10)); + print_value(shell, 2, F("Warm Water current tap water flow"), F("l/min"), Helpers::render_value(buffer, wWCurFlow_, 10)); + print_value(shell, 2, F("Warm Water # starts"), Helpers::render_value(buffer, wWStarts_, 1)); if (wWWorkM_ != EMS_VALUE_ULONG_NOTSET) { shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60); } - print_value(shell, F("Warm Water 3-way valve"), Helpers::render_value(buffer, wWHeat_, EMS_VALUE_BOOL)); - print_value(shell, F("Selected flow temperature"), F_(degrees), Helpers::render_value(buffer, selFlowTemp_, 1)); - print_value(shell, F("Current flow temperature"), F_(degrees), Helpers::render_value(buffer, curFlowTemp_, 10)); - print_value(shell, F("Max boiler temperature"), F_(degrees), Helpers::render_value(buffer, boilTemp_, 10)); - print_value(shell, F("Return temperature"), F_(degrees), Helpers::render_value(buffer, retTemp_, 10)); - print_value(shell, F("Gas"), Helpers::render_value(buffer, burnGas_, EMS_VALUE_BOOL)); - print_value(shell, F("Boiler pump"), Helpers::render_value(buffer, heatPmp_, EMS_VALUE_BOOL)); - print_value(shell, F("Fan"), Helpers::render_value(buffer, fanWork_, EMS_VALUE_BOOL)); - print_value(shell, F("Ignition"), Helpers::render_value(buffer, ignWork_, EMS_VALUE_BOOL)); - print_value(shell, F("Circulation pump"), Helpers::render_value(buffer, wWCirc_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Warm Water 3-way valve"), Helpers::render_value(buffer, wWHeat_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Selected flow temperature"), F_(degrees), Helpers::render_value(buffer, selFlowTemp_, 1)); + print_value(shell, 2, F("Current flow temperature"), F_(degrees), Helpers::render_value(buffer, curFlowTemp_, 10)); + print_value(shell, 2, F("Max boiler temperature"), F_(degrees), Helpers::render_value(buffer, boilTemp_, 10)); + print_value(shell, 2, F("Return temperature"), F_(degrees), Helpers::render_value(buffer, retTemp_, 10)); + print_value(shell, 2, F("Gas"), Helpers::render_value(buffer, burnGas_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Boiler pump"), Helpers::render_value(buffer, heatPmp_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Fan"), Helpers::render_value(buffer, fanWork_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Ignition"), Helpers::render_value(buffer, ignWork_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Circulation pump"), Helpers::render_value(buffer, wWCirc_, EMS_VALUE_BOOL)); - print_value(shell, F("Burner selected max power"), F_(percent), Helpers::render_value(buffer, selBurnPow_, 1)); - print_value(shell, F("Burner current power"), F_(percent), Helpers::render_value(buffer, curBurnPow_, 1)); - print_value(shell, F("Flame current"), F("uA"), Helpers::render_value(buffer, flameCurr_, 10)); - print_value(shell, F("System pressure"), F("bar"), Helpers::render_value(buffer, sysPress_, 10)); - if (serviceCode_ == EMS_VALUE_USHORT_NOTSET) { - shell.printfln(F(" System service code: %s"), serviceCodeChar_); - } else { + print_value(shell, 2, F("Burner selected max power"), F_(percent), Helpers::render_value(buffer, selBurnPow_, 1)); + print_value(shell, 2, F("Burner current power"), F_(percent), Helpers::render_value(buffer, curBurnPow_, 1)); + print_value(shell, 2, F("Flame current"), F("uA"), Helpers::render_value(buffer, flameCurr_, 10)); + print_value(shell, 2, F("System pressure"), F("bar"), Helpers::render_value(buffer, sysPress_, 10)); + if (serviceCode_ != EMS_VALUE_USHORT_NOTSET) { shell.printfln(F(" System service code: %s (%d)"), serviceCodeChar_, serviceCode_); + } else if (serviceCodeChar_[0] != '\0') { + shell.printfln(F(" System service code: %s"), serviceCodeChar_); } // UBAParameters - print_value(shell, F("Heating temperature setting on the boiler"), F_(degrees), Helpers::render_value(buffer, heating_temp_, 1)); - print_value(shell, F("Boiler circuit pump modulation max power"), F_(percent), Helpers::render_value(buffer, pump_mod_max_, 1)); - print_value(shell, F("Boiler circuit pump modulation min power"), F_(percent), Helpers::render_value(buffer, pump_mod_min_, 1)); + print_value(shell, 2, F("Heating temperature setting on the boiler"), F_(degrees), Helpers::render_value(buffer, heating_temp_, 1)); + print_value(shell, 2, F("Boiler circuit pump modulation max power"), F_(percent), Helpers::render_value(buffer, pump_mod_max_, 1)); + print_value(shell, 2, F("Boiler circuit pump modulation min power"), F_(percent), Helpers::render_value(buffer, pump_mod_min_, 1)); // UBAMonitorSlow if (extTemp_ != EMS_VALUE_SHORT_NOTSET) { - print_value(shell, F("Outside temperature"), F_(degrees), Helpers::render_value(buffer, extTemp_, 10)); + print_value(shell, 2, F("Outside temperature"), F_(degrees), Helpers::render_value(buffer, extTemp_, 10)); } - print_value(shell, F("Exhaust temperature"), F_(degrees), Helpers::render_value(buffer, exhaustTemp_, 10)); - print_value(shell, F("Pump modulation"), F_(percent), Helpers::render_value(buffer, pumpMod_, 1)); - print_value(shell, F("Burner # starts"), Helpers::render_value(buffer, burnStarts_, 1)); + print_value(shell, 2, F("Exhaust temperature"), F_(degrees), Helpers::render_value(buffer, exhaustTemp_, 10)); + print_value(shell, 2, F("Pump modulation"), F_(percent), Helpers::render_value(buffer, pumpMod_, 1)); + print_value(shell, 2, F("Burner # starts"), Helpers::render_value(buffer, burnStarts_, 1)); if (burnWorkMin_ != EMS_VALUE_ULONG_NOTSET) { shell.printfln(F(" Total burner operating time: %d days %d hours %d minutes"), burnWorkMin_ / 1440, (burnWorkMin_ % 1440) / 60, burnWorkMin_ % 60); } @@ -422,11 +424,11 @@ void Boiler::show_values(uuid::console::Shell & shell) { } if (hpModulation_ != EMS_VALUE_UINT_NOTSET) { - print_value(shell, F("Heat Pump modulation"), F_(percent), Helpers::render_value(buffer, hpModulation_, 1)); + print_value(shell, 2, F("Heat Pump modulation"), F_(percent), Helpers::render_value(buffer, hpModulation_, 1)); } if (hpSpeed_ != EMS_VALUE_UINT_NOTSET) { - print_value(shell, F("Heat Pump speed"), F_(percent), Helpers::render_value(buffer, hpSpeed_, 1)); + print_value(shell, 2, F("Heat Pump speed"), F_(percent), Helpers::render_value(buffer, hpSpeed_, 1)); } } @@ -545,6 +547,7 @@ void Boiler::process_UBAMonitorWW(std::shared_ptr telegram) { /* * UBAMonitorFastPlus - type 0xE4 - central heating monitor EMS+ + * Still to figure out are: serviceCode, retTemp, sysPress */ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram) { telegram->read_value(selFlowTemp_, 6); @@ -562,8 +565,6 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram serviceCodeChar_[2] = '\0'; } - // still to figure out: serviceCode, retTemp, sysPress - // at this point do a quick check to see if the hot water or heating is active check_active(); } @@ -754,7 +755,7 @@ void Boiler::set_warmwater_circulation(const bool activated) { } // add console commands -void Boiler::console_commands() { +void Boiler::console_commands(Shell & shell, unsigned int context) { EMSESPShell::commands->add_command(ShellContext::BOILER, CommandFlags::ADMIN, flash_string_vector{F_(read)}, @@ -920,6 +921,10 @@ void Boiler::console_commands() { shell.printfln(F_(shower_alert_fmt), settings.shower_alert() ? F_(enabled) : F_(disabled)); shell.println(); }); + + + // enter the context + Console::enter_custom_context(shell, context); } } // namespace emsesp diff --git a/src/boiler.h b/src/boiler.h index 67ebd977e..b933d6bf7 100644 --- a/src/boiler.h +++ b/src/boiler.h @@ -46,7 +46,7 @@ class Boiler : public EMSdevice { private: static uuid::log::Logger logger_; - void console_commands(); + void console_commands(Shell & shell, unsigned int context); uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off @@ -67,23 +67,23 @@ class Boiler : public EMSdevice { uint8_t wWComfort_ = EMS_VALUE_UINT_NOTSET; // WW comfort mode // UBAMonitorFast - 0x18 on EMS1 - uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature - uint16_t curFlowTemp_ = EMS_VALUE_USHORT_NOTSET; // Current flow temperature - uint16_t wwStorageTemp1_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 1 - uint16_t wwStorageTemp2_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 2 - uint16_t retTemp_ = EMS_VALUE_USHORT_NOTSET; // Return temperature - uint8_t burnGas_ = EMS_VALUE_BOOL_NOTSET; // Gas on/off - uint8_t fanWork_ = EMS_VALUE_BOOL_NOTSET; // Fan on/off - uint8_t ignWork_ = EMS_VALUE_BOOL_NOTSET; // Ignition on/off - uint8_t heatPmp_ = EMS_VALUE_BOOL_NOTSET; // Boiler pump on/off - uint8_t wWHeat_ = EMS_VALUE_BOOL_NOTSET; // 3-way valve on WW - uint8_t wWCirc_ = EMS_VALUE_BOOL_NOTSET; // Circulation on/off - uint8_t selBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner max power % - uint8_t curBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner current power % - uint16_t flameCurr_ = EMS_VALUE_USHORT_NOTSET; // Flame current in micro amps - uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure - char serviceCodeChar_[3]; // 2 character status/service code - uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code + uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature + uint16_t curFlowTemp_ = EMS_VALUE_USHORT_NOTSET; // Current flow temperature + uint16_t wwStorageTemp1_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 1 + uint16_t wwStorageTemp2_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 2 + uint16_t retTemp_ = EMS_VALUE_USHORT_NOTSET; // Return temperature + uint8_t burnGas_ = EMS_VALUE_BOOL_NOTSET; // Gas on/off + uint8_t fanWork_ = EMS_VALUE_BOOL_NOTSET; // Fan on/off + uint8_t ignWork_ = EMS_VALUE_BOOL_NOTSET; // Ignition on/off + uint8_t heatPmp_ = EMS_VALUE_BOOL_NOTSET; // Boiler pump on/off + uint8_t wWHeat_ = EMS_VALUE_BOOL_NOTSET; // 3-way valve on WW + uint8_t wWCirc_ = EMS_VALUE_BOOL_NOTSET; // Circulation on/off + uint8_t selBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner max power % + uint8_t curBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner current power % + uint16_t flameCurr_ = EMS_VALUE_USHORT_NOTSET; // Flame current in micro amps + uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure + char serviceCodeChar_[3] = {'\0'}; // 2 character status/service code + uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code // UBAMonitorSlow - 0x19 on EMS1 int16_t extTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature diff --git a/src/console.cpp b/src/console.cpp index d816a63d0..a8f15d588 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -21,7 +21,11 @@ namespace emsesp { -static std::shared_ptr commands = std::make_shared(); +std::shared_ptr EMSESPShell::commands = [] { + std::shared_ptr commands = std::make_shared(); + return commands; +}(); + static std::shared_ptr shell; std::vector EMSESPStreamConsole::ptys_; @@ -32,11 +36,6 @@ uuid::telnet::TelnetService telnet_([](Stream & stream, const IPAddress & addr, }); #endif -std::shared_ptr EMSESPShell::commands = [] { - std::shared_ptr commands = std::make_shared(); - return commands; -}(); - EMSESPShell::EMSESPShell() : Shell() { } @@ -125,7 +124,7 @@ void EMSESPShell::add_console_commands() { }); /* - * add the submenu contexts... + * add all the submenu contexts... */ // MQTT @@ -133,8 +132,7 @@ void EMSESPShell::add_console_commands() { CommandFlags::USER, flash_string_vector{F_(mqtt)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { - dynamic_cast(shell).enter_custom_context(ShellContext::MQTT); - Mqtt::console_commands(); + Mqtt::console_commands(shell, ShellContext::MQTT); }); // EMS @@ -142,8 +140,7 @@ void EMSESPShell::add_console_commands() { CommandFlags::USER, flash_string_vector{F_(ems)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { - dynamic_cast(shell).enter_custom_context(ShellContext::EMS); - EMSESP::console_commands(); + EMSESP::console_commands(shell, ShellContext::EMS); }); // System @@ -151,15 +148,14 @@ void EMSESPShell::add_console_commands() { CommandFlags::USER, flash_string_vector{F_(system)}, [](Shell & shell, const std::vector & arguments __attribute__((unused))) { - dynamic_cast(shell).enter_custom_context(ShellContext::SYSTEM); - System::console_commands(); + System::console_commands(shell, ShellContext::SYSTEM); }); // add all the context menus for the connected devices - // this assumes they devices have been detected and registered - EMSESP::add_context_menu(); + // this assumes they devices have been detected and pre-registered + EMSESP::add_context_menus(); - enter_custom_context(ShellContext::MAIN); // add su, exit and help + Console::load_standard_commands(ShellContext::MAIN); _console_commands_loaded = true; } @@ -199,17 +195,29 @@ bool EMSESPShell::exit_context() { return Shell::exit_context(); } +// enter a custom context (sub-menu) +void Console::enter_custom_context(Shell & shell, unsigned int context) { + load_standard_commands(context); + + // don't enter context if we're already at the root + if (context != ShellContext::MAIN) { + shell.enter_context(context); + } +} + // each custom context has the common commands like log, help, exit, su etc -void EMSESPShell::enter_custom_context(unsigned int context) { +void Console::load_standard_commands(unsigned int context) { #ifdef EMSESP_DEBUG - commands->add_command(context, - CommandFlags::ADMIN, - flash_string_vector{F_(test)}, - flash_string_vector{F_(name_mandatory)}, - [](Shell & shell, const std::vector & arguments __attribute__((unused))) { EMSESP::run_test(shell, arguments.front()); }); + EMSESPShell::commands->add_command(context, + CommandFlags::ADMIN, + flash_string_vector{F_(test)}, + flash_string_vector{F_(name_mandatory)}, + [](Shell & shell, const std::vector & arguments __attribute__((unused))) { + EMSESP::run_test(shell, arguments.front()); + }); #endif - commands->add_command( + EMSESPShell::commands->add_command( context, CommandFlags::USER, flash_string_vector{F_(log)}, @@ -229,7 +237,7 @@ void EMSESPShell::enter_custom_context(unsigned int context) { uint16_t watch_id = 0; // no watch ID set if ((arguments.size() == 2) && (level == uuid::log::Level::TRACE)) { watch_id = Helpers::hextoint(arguments[1].c_str()); - shell.printfln(("Tracing only telegrams that match a device ID/telegram type ID of 0x%02X"), watch_id); + shell.printfln(("Tracing only telegrams that match a device ID or telegram type of 0x%02X"), watch_id); } emsesp::EMSESP::trace_watch_id(watch_id); } @@ -239,68 +247,61 @@ void EMSESPShell::enter_custom_context(unsigned int context) { return uuid::log::levels_lowercase(); }); - commands->add_command(context, - CommandFlags::USER, - flash_string_vector{F_(help)}, - [](Shell & shell, const std::vector & arguments __attribute__((unused))) { shell.print_all_available_commands(); }); + EMSESPShell::commands->add_command(context, + CommandFlags::USER, + flash_string_vector{F_(help)}, + [](Shell & shell, const std::vector & arguments __attribute__((unused))) { + shell.print_all_available_commands(); + }); - commands->add_command(context, - CommandFlags::USER, - flash_string_vector{F_(exit)}, - [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { shell.exit_context(); }); + EMSESPShell::commands->add_command(context, + CommandFlags::USER, + flash_string_vector{F_(exit)}, + [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { + // delete MAIN console stuff first to save memory + EMSESPShell::commands->remove_context_commands(context); + shell.exit_context(); + }); - commands->add_command(context, - CommandFlags::USER, - flash_string_vector{F_(su)}, - [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { - auto become_admin = [](Shell & shell) { - shell.logger().log(LogLevel::NOTICE, - LogFacility::AUTH, - F("Admin session opened on console %s"), - dynamic_cast(shell).console_name().c_str()); - shell.add_flags(CommandFlags::ADMIN); - }; + EMSESPShell::commands->add_command(context, + CommandFlags::USER, + flash_string_vector{F_(su)}, + [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { + auto become_admin = [](Shell & shell) { + shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Admin session opened on console")); + shell.add_flags(CommandFlags::ADMIN); + }; - if (shell.has_flags(CommandFlags::LOCAL)) { - become_admin(shell); - } else { - shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) { - if (completed) { - uint64_t now = uuid::get_uptime_ms(); + if (shell.has_flags(CommandFlags::LOCAL)) { + become_admin(shell); + } else { + shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) { + if (completed) { + uint64_t now = uuid::get_uptime_ms(); - if (!password.empty() && password == Settings().admin_password()) { - become_admin(shell); - } else { - shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) { - shell.logger().log(LogLevel::NOTICE, - LogFacility::AUTH, - F("Invalid admin password on console %s"), - dynamic_cast(shell).console_name().c_str()); - shell.println(F("su: incorrect password")); - }); - } - } - }); - } - }); + if (!password.empty() && password == Settings().admin_password()) { + become_admin(shell); + } else { + shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) { + shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid admin password on console")); + shell.println(F("su: incorrect password")); + }); + } + } + }); + } + }); #ifdef EMSESP_DEBUG - commands->add_command(context, - CommandFlags::ADMIN, - flash_string_vector{F_(debug)}, - [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { - shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, Settings().app_version().c_str(), COLOR_RESET); - Settings settings; - settings.commit(); - settings.show_settings(shell); - shell.println(); - }); + EMSESPShell::commands->add_command( + context, CommandFlags::ADMIN, flash_string_vector{F_(debug)}, [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { + shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, Settings().app_version().c_str(), COLOR_RESET); + Settings settings; + settings.commit(); + settings.show_settings(shell); + shell.println(); + }); #endif - - // don't enter context if we're at the root - if (context != ShellContext::MAIN) { - Shell::enter_context(context); - } } // prompt, change per context @@ -339,8 +340,6 @@ std::string EMSESPShell::prompt_suffix() { } void EMSESPShell::end_of_transmission() { - // delete MAIN console stuff - commands->remove_context_commands(ShellContext::MAIN); invoke_command(uuid::read_flash_string(F_(exit))); } @@ -415,7 +414,7 @@ void Console::start() { // note, this must be started after the network/wifi for ESP32 otherwise it'll crash #ifndef EMSESP_STANDALONE telnet_.start(); - // telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second + telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second #endif } @@ -428,8 +427,6 @@ void Console::loop() { #endif Shell::loop_all(); - - // delay(0); // in EMS-ESP 1.9.5 this helped with stability } } // namespace emsesp diff --git a/src/console.h b/src/console.h index cb1868807..4b0488784 100644 --- a/src/console.h +++ b/src/console.h @@ -125,7 +125,13 @@ namespace emsesp { using LogLevel = ::uuid::log::Level; using LogFacility = ::uuid::log::Facility; -enum CommandFlags : uint8_t { USER = 0, ADMIN = (1 << 0), LOCAL = (1 << 1) }; +enum CommandFlags : uint8_t { + + USER = 0, + ADMIN = (1 << 0), + LOCAL = (1 << 1) + +}; enum ShellContext : uint8_t { @@ -147,8 +153,6 @@ class EMSESPShell : virtual public uuid::console::Shell { static std::shared_ptr commands; static std::shared_ptr shell; - void enter_custom_context(unsigned int context); - protected: EMSESPShell(); @@ -191,6 +195,9 @@ class Console { uuid::log::Level log_level(); + static void enter_custom_context(Shell & shell, unsigned int context); + static void load_standard_commands(unsigned int context); + private: static constexpr unsigned long SERIAL_CONSOLE_BAUD_RATE = 115200; static constexpr auto & serial_console_ = Serial; diff --git a/src/device_library.h b/src/device_library.h index 62126d0f3..fda96c69c 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -27,7 +27,6 @@ {115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -{115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {203, DeviceType::BOILER, F("Logamax U122/Cerapur"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {208, DeviceType::BOILER, F("Logamax plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, { 64, DeviceType::BOILER, F("BK13,BK15/Smartline/GB1x2"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, @@ -37,6 +36,7 @@ {122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {172, DeviceType::BOILER, F("Enviline/Compress 6000AW"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, +{ 72, DeviceType::BOILER, F("MC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // Solar Modules - 0x30 { 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, @@ -56,29 +56,30 @@ {200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // Switches - 0x11 -{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x11 +{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x11 // Controllers - 0x09 / 0x10 -{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{ 89, DeviceType::CONTROLLER, F("BC"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50 -{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},// 0x09 -{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},// 0x09 -{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE},// 0x09 -{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE},// 0x09 -{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 -{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 +{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09 {207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10 // Connect devices - 0x02 {205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x02 {206, DeviceType::CONNECT, F("Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x02 -{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x02 +{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x02 // Gateways - 0x48 / 0x18 {189, DeviceType::GATEWAY, F("KM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x48 -{ 94, DeviceType::GATEWAY, F("RC"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18 +{ 94, DeviceType::GATEWAY, F("RC"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18 // Thermostat - not currently supporting write operations, like the Easy/100 types - 0x18 {202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 686ac800e..5726f30bb 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -138,7 +138,7 @@ std::string EMSdevice::to_string() const { } if (brand_ == Brand::NO_BRAND) { - snprintf_P(&str[0], str.capacity() + 1, PSTR("%s (DeviceID:0x%02X ProductID:%d, Version:%s)"), name_.c_str(), device_id_, product_id_, version_.c_str()); + snprintf_P(&str[0], str.capacity() + 1, PSTR("%s (DeviceID:0x%02X, ProductID:%d, Version:%s)"), name_.c_str(), device_id_, product_id_, version_.c_str()); } else { snprintf_P(&str[0], str.capacity() + 1, @@ -161,6 +161,7 @@ void EMSdevice::show_values(uuid::console::Shell & shell) { // for each telegram that has the fetch value set (true) do a read request void EMSdevice::fetch_values() { DEBUG_LOG(F("Fetching values for device ID 0x%02X"), device_id()); + for (const auto & tf : telegram_functions_) { if (tf.fetch_) { read_command(tf.telegram_type_id_); @@ -260,13 +261,21 @@ void EMSdevice::read_command(const uint16_t type_id) { } // prints a value to the console -void EMSdevice::print_value(uuid::console::Shell & shell, const __FlashStringHelper * name, const __FlashStringHelper * prefix, const char * value) { - shell.printfln(PSTR(" %s: %s%s"), uuid::read_flash_string(name).c_str(), value, uuid::read_flash_string(prefix).c_str()); +void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const __FlashStringHelper * suffix, const char * value) { + uint8_t i = padding; + while (i-- > 0) { + shell.print(F(" ")); + } + shell.printf(PSTR("%s: %s"), uuid::read_flash_string(name).c_str(), value); + if (suffix != nullptr) { + shell.print(uuid::read_flash_string(suffix).c_str()); + } + shell.println(); } -// prints a value to the console - no prefix -void EMSdevice::print_value(uuid::console::Shell & shell, const __FlashStringHelper * name, const char * value) { - shell.printfln(PSTR(" %s: %s"), uuid::read_flash_string(name).c_str(), value); +// prints a value to the console - with no prefix +void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value) { + print_value(shell, padding, name, nullptr, value); } } // namespace emsesp diff --git a/src/emsdevice.h b/src/emsdevice.h index 8cd96e4a0..94d58496e 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -119,8 +119,8 @@ class EMSdevice { void fetch_values(); - void print_value(uuid::console::Shell & shell, const __FlashStringHelper * name, const __FlashStringHelper * prefix, const char * value); - void print_value(uuid::console::Shell & shell, const __FlashStringHelper * name, const char * value); + void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const __FlashStringHelper * prefix, const char * value); + void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value); enum Brand : uint8_t { NO_BRAND, // 0 @@ -195,9 +195,8 @@ class EMSdevice { uint16_t telegram_type_id_; // it's type_id const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message - // std::string telegram_type_name_; // e.g. RC20Message - bool fetch_; // should this type_id be queried automatically? - process_function_p process_function_; + bool fetch_; // if this type_id be queried automatically + process_function_p process_function_; }; std::vector telegram_functions_; // each EMS device has its own set of registered telegram types }; diff --git a/src/emsesp.cpp b/src/emsesp.cpp index a1af2f3f8..70289c5d4 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -59,14 +59,15 @@ uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; / uint16_t EMSESP::trace_watch_id_ = 0; // for when log is TRACE bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower() bool EMSESP::ems_read_only_; +uint32_t EMSESP::last_fetch_ = 0; #ifdef EMSESP_DEBUG #include "test/test_data.h" // used with the 'test' command, under su/admin #endif -// for each associated EMS device go and request data values +// for each associated EMS device go and request its data values void EMSESP::fetch_device_values() { - fetch_device_values(0); // fetch all + fetch_device_values(0); // 0 = fetch all } // for a specific EMS device go and request data values @@ -76,7 +77,9 @@ void EMSESP::fetch_device_values(const uint8_t device_id) { if (emsdevice) { if ((device_id == 0) || emsdevice->is_device_id(device_id)) { emsdevice->fetch_values(); - return; + if (device_id != 0) { + return; // quit, we only want to return the selected device + } } } } @@ -177,6 +180,14 @@ void EMSESP::show_values(uuid::console::Shell & shell) { return; } + // show EMS device values + for (const auto & emsdevice : emsdevices) { + if (emsdevice) { + emsdevice->show_values(shell); + shell.println(); + } + } + // Dallas sensors (if available) char valuestr[8] = {0}; // for formatting temp if (!sensor_devices().empty()) { @@ -187,14 +198,6 @@ void EMSESP::show_values(uuid::console::Shell & shell) { shell.println(); } - // show EMS device values - for (const auto & emsdevice : emsdevices) { - if (emsdevice) { - emsdevice->show_values(shell); - shell.println(); - } - } - shell.println(); } @@ -340,6 +343,7 @@ void EMSESP::process_UBADevices(std::shared_ptr telegram) { } // process the Version telegram (type 0x02), which is a common type +// e.g. 09 0B 02 00 PP V1 V2 void EMSESP::process_version(std::shared_ptr telegram) { // check for valid telegram, just in case if (telegram->message_length < 3) { @@ -447,7 +451,7 @@ bool EMSESP::device_exists(const uint8_t device_id) { } // for each device add its context menu for the console -void EMSESP::add_context_menu() { +void EMSESP::add_context_menus() { for (const auto & emsdevice : emsdevices) { if (emsdevice) { emsdevice->add_context_menu(); @@ -531,10 +535,10 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: // if we don't recognize the product ID report it, but don't add it. if (!found) { - DEBUG_LOG(F("Cannot add device. Unknown device ID 0x%02X, product ID %d. Ignoring it."), device_id, product_id); + logger_.notice(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id); return false; // not found } else { - DEBUG_LOG(F("Adding new device with device ID 0x%02X, product ID %d"), device_id, product_id); + DEBUG_LOG(F("Adding new device with device ID 0x%02X with product ID %d"), device_id, product_id); // go and fetch its data, including asking for the version send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id); fetch_device_values(device_id); @@ -634,7 +638,7 @@ void EMSESP::set_ems_read_only() { } // console commands to add -void EMSESP::console_commands() { +void EMSESP::console_commands(Shell & shell, unsigned int context) { EMSESPShell::commands->add_command(ShellContext::EMS, CommandFlags::USER, flash_string_vector{F_(show), F_(devices)}, @@ -775,6 +779,9 @@ void EMSESP::console_commands() { shell.printfln(F_(bus_id_fmt), settings.ems_bus_id()); shell.printfln(F_(read_only_fmt), settings.ems_read_only() ? F_(enabled) : F_(disabled)); }); + + // enter the context + Console::enter_custom_context(shell, context); } // kick off the party, start all the services @@ -805,6 +812,13 @@ void EMSESP::loop() { rxservice_.loop(); // process what ever is in the rx queue shower_.loop(); // check for shower on/off sensors_.loop(); // this will also send out via MQTT + + // force a query on the EMS devices to fetch latest data + uint32_t currentMillis = millis(); + if ((currentMillis - last_fetch_ > EMS_FETCH_FREQUENCY)) { + last_fetch_ = currentMillis; + fetch_device_values(); + } } } diff --git a/src/emsesp.h b/src/emsesp.h index 2c16dcb94..949747c4a 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -85,7 +85,7 @@ class EMSESP { static void show_devices(uuid::console::Shell & shell); static void show_emsbus(uuid::console::Shell & shell); - static void add_context_menu(); + static void add_context_menus(); static void incoming_telegram(uint8_t * data, const uint8_t length); @@ -111,7 +111,7 @@ class EMSESP { return ems_read_only_; } - static void console_commands(); + static void console_commands(Shell & shell, unsigned int context); static void fetch_device_values(const uint8_t device_id); static void fetch_device_values(); @@ -136,6 +136,9 @@ class EMSESP { static RxService rxservice_; static TxService txservice_; + static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute + static uint32_t last_fetch_; + struct Device_record { uint8_t product_id; EMSdevice::DeviceType device_type; diff --git a/src/mixing.cpp b/src/mixing.cpp index c868a8b56..488406976 100644 --- a/src/mixing.cpp +++ b/src/mixing.cpp @@ -65,10 +65,10 @@ void Mixing::show_values(uuid::console::Shell & shell) { } else { shell.printfln(F(" Heating Circuit #: %d"), hc_); } - print_value(shell, F("Current flow temperature"), F_(degrees), Helpers::render_value(buffer, flowTemp_, 10)); - print_value(shell, F("Setpoint flow temperature"), F_(degrees), Helpers::render_value(buffer, flowSetTemp_, 1)); - print_value(shell, F("Current pump modulation"), Helpers::render_value(buffer, pumpMod_, 1)); - print_value(shell, F("Current valve status"), Helpers::render_value(buffer, status_, 1)); + print_value(shell, 2, F("Current flow temperature"), F_(degrees), Helpers::render_value(buffer, flowTemp_, 10)); + print_value(shell, 2, F("Setpoint flow temperature"), F_(degrees), Helpers::render_value(buffer, flowSetTemp_, 1)); + print_value(shell, 2, F("Current pump modulation"), Helpers::render_value(buffer, pumpMod_, 1)); + print_value(shell, 2, F("Current valve status"), Helpers::render_value(buffer, status_, 1)); } // publish values via MQTT @@ -151,9 +151,9 @@ void Mixing::process_MMStatusMessage(std::shared_ptr telegram) { type_ = Type::HC; // the heating circuit is determine by which device_id it is, 0x20 - 0x23 - // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module + // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 - hc_ = 0x22 - device_id(); + hc_ = 0x22 - device_id(); telegram->read_value(flowTemp_, 1); // is * 10 telegram->read_value(pumpMod_, 3); telegram->read_value(flowSetTemp_, 0); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index e141cbe2a..10fdc9ab9 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -40,7 +40,7 @@ MAKE_PSTR(mqtt_enabled_fmt, "MQTT is %s") MAKE_PSTR(mqtt_base_fmt, "Base = %s") MAKE_PSTR(mqtt_qos_fmt, "QOS = %ld") MAKE_PSTR(mqtt_retain_fmt, "Retain Flag = %s") -MAKE_PSTR(mqtt_format_fmt, "JSON format = %s") +MAKE_PSTR(mqtt_format_fmt, "Format for JSON = %s") MAKE_PSTR(mqtt_heartbeat_fmt, "Heartbeat = %s") MAKE_PSTR(mqtt_publish_time_fmt, "Publish time = %d seconds") @@ -52,6 +52,7 @@ namespace emsesp { #ifndef EMSESP_STANDALONE AsyncMqttClient Mqtt::mqttClient_; #endif + std::vector Mqtt::mqtt_functions_; bool Mqtt::mqtt_retain_; uint8_t Mqtt::mqtt_qos_; @@ -463,17 +464,20 @@ void Mqtt::send_heartbeat() { // add MQTT message to queue, payload is a JSON doc. // NOTE this only prints first 255 chars -void Mqtt::queue_publish_message(const char * topic, const JsonDocument & payload, const bool retain) { - if (strlen(topic) == 0) { +void Mqtt::queue_publish_message(const std::string & topic, const JsonDocument & payload, const bool retain) { + // can't have bogus topics, but empty payloads are ok + if (topic.empty()) { return; } + /* // check for empty JSON doc - we don't like those size_t capacity = measureJson(payload); if (capacity <= 3) { - // DEBUG_LOG(("Empty JSON for topic %s. Skipping"), topic); + // DEBUG_LOG(("Empty JSON payload for topic %s. Skipping"), topic); return; } + */ std::string payload_text; serializeJson(payload, payload_text); @@ -491,9 +495,9 @@ void Mqtt::queue_publish_message(const char * topic, const JsonDocument & payloa } // add MQTT message to queue, payload is a string -void Mqtt::queue_publish_message(const char * topic, const std::string & payload, const bool retain) { +void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) { // can't have bogus topics, but empty payloads are ok - if (strlen(topic) == 0) { + if (topic.empty()) { return; } @@ -525,29 +529,29 @@ void Mqtt::queue_subscribe_message(const std::string & topic) { } // MQTT Publish, using a specific retain flag -void Mqtt::publish(const char * topic, const char * payload, bool retain) { +void Mqtt::publish(const std::string & topic, const std::string & payload, bool retain) { queue_publish_message(topic, payload, retain); } // Publish using the user's custom retain flag -void Mqtt::publish(const char * topic, const char * payload) { +void Mqtt::publish(const std::string & topic, const std::string & payload) { publish(topic, payload, mqtt_retain_); } -void Mqtt::publish(const char * topic, const JsonDocument & payload) { +void Mqtt::publish(const std::string & topic, const JsonDocument & payload) { publish(topic, payload, mqtt_retain_); } -void Mqtt::publish(const char * topic, const JsonDocument & payload, bool retain) { +void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) { queue_publish_message(topic, payload, retain); } -void Mqtt::publish(const char * topic, const bool value) { +void Mqtt::publish(const std::string & topic, const bool value) { queue_publish_message(topic, value ? "1" : "0", mqtt_retain_); } // no payload -void Mqtt::publish(const char * topic) { +void Mqtt::publish(const std::string & topic) { queue_publish_message(topic, "", mqtt_retain_); } @@ -634,10 +638,10 @@ void Mqtt::process_queue() { } mqtt_messages_.pop_front(); // remove the message from the queue -} // namespace emsesp +} // add console commands -void Mqtt::console_commands() { +void Mqtt::console_commands(Shell & shell, unsigned int context) { EMSESPShell::commands->add_command( ShellContext::MQTT, CommandFlags::ADMIN, @@ -853,6 +857,9 @@ void Mqtt::console_commands() { shell.printfln(F_(mqtt_publish_time_fmt), settings.mqtt_publish_time()); shell.println(); }); -} // namespace emsesp + + // enter the context + Console::enter_custom_context(shell, context); +} } // namespace emsesp diff --git a/src/mqtt.h b/src/mqtt.h index c61a5fe69..6cc9e5ed0 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -39,6 +39,8 @@ #include +using uuid::console::Shell; + #define EMSESP_MAX_JSON_SIZE_SMALL 200 // for smaller json docs #define EMSESP_MAX_JSON_SIZE_MEDIUM 800 // for smaller json docs from ems devices #define EMSESP_MAX_JSON_SIZE_LARGE 1500 // for large json docs from ems devices, like boiler or thermostat data @@ -69,16 +71,16 @@ class Mqtt { static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb); static void subscribe(const std::string & topic, mqtt_function_p cb); - static void publish(const char * topic, const char * payload); - static void publish(const char * topic, const char * payload, bool retain); - static void publish(const char * topic, const JsonDocument & payload); - static void publish(const char * topic, const JsonDocument & payload, bool retain); - static void publish(const char * topic, const bool value); - static void publish(const char * topic); + static void publish(const std::string & topic, const std::string & payload); + static void publish(const std::string & topic, const std::string & payload, bool retain); + static void publish(const std::string & topic, const JsonDocument & payload); + static void publish(const std::string & topic, const JsonDocument & payload, bool retain); + 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 console_commands(); + static void console_commands(Shell & shell, unsigned int context); void incoming(char * topic, char * payload); // for testing @@ -130,8 +132,8 @@ class Mqtt { static bool mqtt_retain_; - static void queue_publish_message(const char * topic, const JsonDocument & payload, const bool retain); - static void queue_publish_message(const char * topic, const std::string & payload, const bool retain); + static void queue_publish_message(const std::string & topic, const JsonDocument & payload, const bool retain); + static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain); static void queue_subscribe_message(const std::string & topic); diff --git a/src/network.cpp b/src/network.cpp index 5e84ab5af..6a9b1cb2b 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -346,6 +346,7 @@ void Network::show_network(uuid::console::Shell & shell) { shell.printfln(F("WiFi: unknown")); break; } + shell.println(); #endif } diff --git a/src/solar.cpp b/src/solar.cpp index 8111daa05..25289e8c0 100644 --- a/src/solar.cpp +++ b/src/solar.cpp @@ -54,20 +54,20 @@ void Solar::show_values(uuid::console::Shell & shell) { char buffer[10]; // used for formatting - print_value(shell, F("Collector temperature (TS1)"), F_(degrees), Helpers::render_value(buffer, collectorTemp_, 10)); - print_value(shell, F("Bottom temperature (TS2)"), F_(degrees), Helpers::render_value(buffer, bottomTemp_, 10)); - print_value(shell, F("Bottom temperature (TS5)"), F_(degrees), Helpers::render_value(buffer, bottomTemp2_, 10)); - print_value(shell, F("Pump modulation"), F_(percent), Helpers::render_value(buffer, pumpModulation_, 1)); - print_value(shell, F("Valve (VS2) status"), Helpers::render_value(buffer, valveStatus_, EMS_VALUE_BOOL)); - print_value(shell, F("Pump (PS1) active"), Helpers::render_value(buffer, pump_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Collector temperature (TS1)"), F_(degrees), Helpers::render_value(buffer, collectorTemp_, 10)); + print_value(shell, 2, F("Bottom temperature (TS2)"), F_(degrees), Helpers::render_value(buffer, bottomTemp_, 10)); + print_value(shell, 2, F("Bottom temperature (TS5)"), F_(degrees), Helpers::render_value(buffer, bottomTemp2_, 10)); + print_value(shell, 2, F("Pump modulation"), F_(percent), Helpers::render_value(buffer, pumpModulation_, 1)); + print_value(shell, 2, F("Valve (VS2) status"), Helpers::render_value(buffer, valveStatus_, EMS_VALUE_BOOL)); + print_value(shell, 2, F("Pump (PS1) active"), Helpers::render_value(buffer, pump_, EMS_VALUE_BOOL)); if (pumpWorkMin_ != EMS_VALUE_ULONG_NOTSET) { shell.printfln(F(" Pump working time: %d days %d hours %d minutes"), pumpWorkMin_ / 1440, (pumpWorkMin_ % 1440) / 60, pumpWorkMin_ % 60); } - print_value(shell, F("Energy last hour"), F_(wh), Helpers::render_value(buffer, energyLastHour_, 10)); - print_value(shell, F("Energy today"), F_(wh), Helpers::render_value(buffer, energyToday_, 0)); // no division - print_value(shell, F("Energy total"), F_(kwh), Helpers::render_value(buffer, energyTotal_, 10)); + print_value(shell, 2, F("Energy last hour"), F_(wh), Helpers::render_value(buffer, energyLastHour_, 10)); + print_value(shell, 2, F("Energy today"), F_(wh), Helpers::render_value(buffer, energyToday_, 0)); // no division + print_value(shell, 2, F("Energy total"), F_(kwh), Helpers::render_value(buffer, energyTotal_, 10)); } // publish values via MQTT diff --git a/src/system.cpp b/src/system.cpp index fc38dcfe7..3b8c6411a 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -334,7 +334,7 @@ void System::show_system(uuid::console::Shell & shell) { } // console commands to add -void System::console_commands() { +void System::console_commands(Shell & shell, unsigned int context) { EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(set), F_(hostname)}, @@ -509,7 +509,6 @@ void System::console_commands() { flash_string_vector{F_(show)}, [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { Network::show_network(shell); - shell.println(); show_system(shell); shell.println(); }); @@ -522,7 +521,7 @@ void System::console_commands() { shell.printfln(F_(hostname_fmt), settings.hostname().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.hostname().c_str()); - if (shell.has_flags(CommandFlags::ADMIN | CommandFlags::LOCAL)) { + if (shell.has_flags(CommandFlags::ADMIN)) { shell.printfln("Wifi:"); shell.print(" "); shell.printfln(F_(wifi_ssid_fmt), @@ -540,6 +539,9 @@ void System::console_commands() { shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval()); } }); + + // enter the context + Console::enter_custom_context(shell, context); } diff --git a/src/system.h b/src/system.h index 77dc2dee4..b940ede13 100644 --- a/src/system.h +++ b/src/system.h @@ -43,6 +43,8 @@ #include +using uuid::console::Shell; + namespace emsesp { class System { @@ -63,7 +65,7 @@ class System { static void show_mem(const char * text); - static void console_commands(); + static void console_commands(Shell & shell, unsigned int context); private: static uuid::log::Logger logger_; diff --git a/src/test/test_data.h b/src/test/test_data.h index 6daee3a20..793cc916e 100644 --- a/src/test/test_data.h +++ b/src/test/test_data.h @@ -1,12 +1,12 @@ // create some fake test data // used with the 'test' command, under su/admin void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) { - // A fake response - UBADevices(0x07) - only for testing offline if (command == "devices") { rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up! emsdevices.push_back(EMSFactory::add(EMSdevice::DeviceType::BOILER, EMSdevice::EMS_DEVICE_ID_BOILER, 0, "", "My Boiler", 0, 0)); + // A fake response - UBADevices(0x07) uint8_t t0[] = {0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47}; rxservice_.add(t0, sizeof(t0)); @@ -21,6 +21,20 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) return; } + if (command == "unknown") { + // question: do we need to set the mask? + std::string version("1.2.3"); + add_device(0x09, 89, version, EMSdevice::Brand::BUDERUS); + rxservice_.loop(); + + // simulate getting version information back from an unknown device + uint8_t u1[] = {0x09, 0x0B, 0x02, 0x00, 0x59, 0x01, 0x02, 0x56}; + rxservice_.add(u1, sizeof(u1)); + rxservice_.loop(); + + return; + } + if (command == "thermostats") { shell.printfln(F("Testing adding devices on the EMS bus...")); diff --git a/src/thermostat.cpp b/src/thermostat.cpp index 1514c7f4d..ebcad9efa 100644 --- a/src/thermostat.cpp +++ b/src/thermostat.cpp @@ -39,6 +39,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i register_telegram_type(EMS_TYPE_RCOutdoorTemp, F("RCOutdoorTemp"), false, std::bind(&Thermostat::process_RCOutdoorTemp, this, _1)); register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), true, std::bind(&Thermostat::process_RCTime, this, _1)); // 0x06 + // RC10 if (flags == EMSdevice::EMS_DEVICE_FLAG_RC10) { monitor_typeids = {0xB1}; set_typeids = {0xB0}; @@ -47,6 +48,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i register_telegram_type(set_typeids[i], F("RC10Set"), true, std::bind(&Thermostat::process_RC10Set, this, _1)); } + // RC35 } else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC35) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC30_1)) { monitor_typeids = {0x3E, 0x48, 0x52, 0x5C}; set_typeids = {0x3D, 0x47, 0x51, 0x5B}; @@ -56,6 +58,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i } register_telegram_type(EMS_TYPE_IBASettings, F("IBASettings"), true, std::bind(&Thermostat::process_IBASettings, this, _1)); + // RC20 } else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20) { monitor_typeids = {0x91}; set_typeids = {0xA8}; @@ -64,6 +67,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i register_telegram_type(set_typeids[i], F("RC20Set"), true, std::bind(&Thermostat::process_RC20Set, this, _1)); } + // RC20 newer } else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) { monitor_typeids = {0xAE}; set_typeids = {0xAD}; @@ -72,6 +76,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i register_telegram_type(set_typeids[i], F("RC20Set"), true, std::bind(&Thermostat::process_RC20Set_2, this, _1)); } + // RC30 } else if (flags == EMSdevice::EMS_DEVICE_FLAG_RC30) { monitor_typeids = {0x41}; set_typeids = {0xA7}; @@ -80,11 +85,13 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i register_telegram_type(set_typeids[i], F("RC30Set"), true, std::bind(&Thermostat::process_RC30Set, this, _1)); } + // EASY } else if (flags == EMSdevice::EMS_DEVICE_FLAG_EASY) { monitor_typeids = {0x0A}; set_typeids = {}; register_telegram_type(monitor_typeids[0], F("EasyMonitor"), true, std::bind(&Thermostat::process_EasyMonitor, this, _1)); + // RC300/RC100 } else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) { monitor_typeids = {0x02A5, 0x02A6, 0x02A7, 0x02A8}; set_typeids = {0x02B9, 0x02BA, 0x02BB, 0x02BC}; @@ -93,6 +100,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i register_telegram_type(set_typeids[i], F("RC300Set"), true, std::bind(&Thermostat::process_RC300Set, this, _1)); } + // JUNKERS/HT3 } else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) { monitor_typeids = {0x6F, 0x70, 0x71, 0x72}; set_typeids = {0x65, 0x66, 0x67, 0x68}; @@ -194,8 +202,7 @@ void Thermostat::add_context_menu() { CommandFlags::USER, flash_string_vector{F_(thermostat)}, [&](Shell & shell, const std::vector & arguments __attribute__((unused))) { - dynamic_cast(shell).enter_custom_context(ShellContext::THERMOSTAT); - console_commands(); + Thermostat::console_commands(shell, ShellContext::THERMOSTAT); }); } @@ -326,7 +333,7 @@ bool Thermostat::updated_values() { } return false; -} // namespace emsesp +} // publish values via MQTT void Thermostat::publish_values() { @@ -337,7 +344,7 @@ void Thermostat::publish_values() { DEBUG_LOG(F("Performing a thermostat publish (device ID 0x%02X)"), device_id()); - uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits + uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, stripping the option bits bool has_data = false; StaticJsonDocument doc; @@ -364,6 +371,7 @@ void Thermostat::publish_values() { } has_data = true; + // if the MQTT format is 'nested' then create the parent object hc if (mqtt_format_ == Settings::MQTT_format::NESTED) { // create nested json for each HC char hc_name[10]; // hc{1-4} @@ -533,6 +541,10 @@ std::shared_ptr Thermostat::heating_circuit(std::sha // decodes the thermostat mode for the heating circuit based on the thermostat type // modes are off, manual, auto, day and night uint8_t Thermostat::HeatingCircuit::get_mode(uint8_t flags) const { + if (mode == EMS_VALUE_UINT_NOTSET) { + return HeatingCircuit::Mode::UNKNOWN; + } + flags &= 0x0F; // strip top 4 bits if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20) { @@ -565,7 +577,7 @@ uint8_t Thermostat::HeatingCircuit::get_mode(uint8_t flags) const { } } - return HeatingCircuit::Mode::OFF; + return HeatingCircuit::Mode::UNKNOWN; } // figures out the thermostat day/night mode depending on the thermostat type @@ -629,10 +641,13 @@ std::string Thermostat::mode_tostring(uint8_t mode) const { case HeatingCircuit::Mode::NOFROST: return read_flash_string(F("nofrost")); break; - default: case HeatingCircuit::Mode::AUTO: return read_flash_string(F("auto")); break; + default: + case HeatingCircuit::Mode::UNKNOWN: + return read_flash_string(F("unknown")); + break; } } @@ -643,22 +658,22 @@ void Thermostat::show_values(uuid::console::Shell & shell) { char buffer[10]; // for formatting only if (datetime_.size()) { - shell.printfln(F("Clock: %s"), datetime_.c_str()); + shell.printfln(F(" Clock: %s"), datetime_.c_str()); if (ibaClockOffset != EMS_VALUE_UINT_NOTSET) { - print_value(shell, F("Offset clock"), Helpers::render_value(buffer, ibaClockOffset, 1)); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s + print_value(shell, 1, F("Offset clock"), Helpers::render_value(buffer, ibaClockOffset, 1)); // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s } } uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits if (flags == EMS_DEVICE_FLAG_RC35) { - print_value(shell, F("Damped Outdoor temperature"), F_(degrees), Helpers::render_value(buffer, dampedoutdoortemp, 1)); - print_value(shell, F("Tempsensor 1"), F_(degrees), Helpers::render_value(buffer, tempsensor1, 10)); - print_value(shell, F("Tempsensor 2"), F_(degrees), Helpers::render_value(buffer, tempsensor2, 10)); + print_value(shell, 1, F("Damped Outdoor temperature"), F_(degrees), Helpers::render_value(buffer, dampedoutdoortemp, 1)); + print_value(shell, 1, F("Tempsensor 1"), F_(degrees), Helpers::render_value(buffer, tempsensor1, 10)); + print_value(shell, 1, F("Tempsensor 2"), F_(degrees), Helpers::render_value(buffer, tempsensor2, 10)); } for (const auto & hc : heating_circuits_) { - shell.printfln(F("Heating Circuit %d:"), hc->hc_num()); + shell.printfln(F(" Heating Circuit %d:"), hc->hc_num()); // different thermostat types store their temperature values differently uint8_t format_setpoint, format_curr; @@ -677,83 +692,87 @@ void Thermostat::show_values(uuid::console::Shell & shell) { break; } - print_value(shell, F("Current room temperature"), F_(degrees), Helpers::render_value(buffer, hc->curr_roomTemp, format_curr)); - print_value(shell, F("Setpoint room temperature"), F_(degrees), Helpers::render_value(buffer, hc->setpoint_roomTemp, format_setpoint)); - print_value(shell, F("Mode"), mode_tostring(hc->get_mode(flags)).c_str()); - print_value(shell, F("Mode Type"), mode_tostring(hc->get_mode_type(flags)).c_str()); + print_value(shell, 2, F("Current room temperature"), F_(degrees), Helpers::render_value(buffer, hc->curr_roomTemp, format_curr)); + print_value(shell, 2, F("Setpoint room temperature"), F_(degrees), Helpers::render_value(buffer, hc->setpoint_roomTemp, format_setpoint)); + if (hc->mode != EMS_VALUE_UINT_NOTSET) { + print_value(shell, 2, F("Mode"), mode_tostring(hc->get_mode(flags)).c_str()); + } + if (hc->mode_type != EMS_VALUE_UINT_NOTSET) { + print_value(shell, 2, F("Mode Type"), mode_tostring(hc->get_mode_type(flags)).c_str()); + } if ((flags == EMS_DEVICE_FLAG_RC35) || (flags == EMS_DEVICE_FLAG_RC30_1)) { if (hc->summer_mode) { - shell.printfln(F(" Program is set to Summer mode")); + shell.printfln(F(" Program is set to Summer mode")); } else if (hc->holiday_mode) { - shell.printfln(F(" Program is set to Holiday mode")); + shell.printfln(F(" Program is set to Holiday mode")); } - print_value(shell, F("Day temperature"), F_(degrees), Helpers::render_value(buffer, hc->daytemp, 2)); - print_value(shell, F("Night temperature"), F_(degrees), Helpers::render_value(buffer, hc->nighttemp, 2)); - print_value(shell, F("Vacation temperature"), F_(degrees), Helpers::render_value(buffer, hc->holidaytemp, 2)); + print_value(shell, 2, F("Day temperature"), F_(degrees), Helpers::render_value(buffer, hc->daytemp, 2)); + print_value(shell, 2, F("Night temperature"), F_(degrees), Helpers::render_value(buffer, hc->nighttemp, 2)); + print_value(shell, 2, F("Vacation temperature"), F_(degrees), Helpers::render_value(buffer, hc->holidaytemp, 2)); if (hc->offsettemp < 100) { - print_value(shell, F("Offset temperature"), F_(degrees), Helpers::render_value(buffer, hc->offsettemp, 2)); + print_value(shell, 2, F("Offset temperature"), F_(degrees), Helpers::render_value(buffer, hc->offsettemp, 2)); } - print_value(shell, F("Design temperature"), F_(degrees), Helpers::render_value(buffer, hc->designtemp, 2)); + print_value(shell, 2, F("Design temperature"), F_(degrees), Helpers::render_value(buffer, hc->designtemp, 2)); } // show flow temp if we have it if (hc->circuitcalctemp != EMS_VALUE_UINT_NOTSET) { - print_value(shell, F("Calculated flow temperature"), F_(degrees), Helpers::render_value(buffer, hc->circuitcalctemp, 1)); + print_value(shell, 2, F("Calculated flow temperature"), F_(degrees), Helpers::render_value(buffer, hc->circuitcalctemp, 1)); } // settings parameters if (ibaMainDisplay != EMS_VALUE_UINT_NOTSET) { if (ibaMainDisplay == 0) { - shell.printfln(F(" Display: internal temperature")); + shell.printfln(F(" Display: internal temperature")); } else if (ibaMainDisplay == 1) { - shell.printfln(F(" Display: internal setpoint")); + shell.printfln(F(" Display: internal setpoint")); } else if (ibaMainDisplay == 2) { - shell.printfln(F(" Display: external temperature")); + shell.printfln(F(" Display: external temperature")); } else if (ibaMainDisplay == 3) { - shell.printfln(F(" Display: burner temperature")); + shell.printfln(F(" Display: burner temperature")); } else if (ibaMainDisplay == 4) { - shell.printfln(F(" Display: WW temperature")); + shell.printfln(F(" Display: WW temperature")); } else if (ibaMainDisplay == 5) { - shell.printfln(F(" Display: functioning mode")); + shell.printfln(F(" Display: functioning mode")); } else if (ibaMainDisplay == 6) { - shell.printfln(F(" Display: time")); + shell.printfln(F(" Display: time")); } else if (ibaMainDisplay == 7) { - shell.printfln(F(" Display: date")); + shell.printfln(F(" Display: date")); } else if (ibaMainDisplay == 9) { - shell.printfln(F(" Display: smoke temperature")); + shell.printfln(F(" Display: smoke temperature")); } } if (ibaLanguage != EMS_VALUE_UINT_NOTSET) { if (ibaLanguage == 0) { - shell.printfln(F(" Language: German")); + shell.printfln(F(" Language: German")); } else if (ibaLanguage == 1) { - shell.printfln(F(" Language: Dutch")); + shell.printfln(F(" Language: Dutch")); } else if (ibaLanguage == 2) { - shell.printfln(F(" Language: French")); + shell.printfln(F(" Language: French")); } else if (ibaLanguage == 3) { - shell.printfln(F(" Language: Italian")); + shell.printfln(F(" Language: Italian")); } } if (ibaCalIntTemperature != EMS_VALUE_INT_NOTSET) { - print_value(shell, F("Offset int. temperature"), F_(degrees), Helpers::render_value(buffer, ibaCalIntTemperature, 2)); + print_value(shell, 2, F("Offset int. temperature"), F_(degrees), Helpers::render_value(buffer, ibaCalIntTemperature, 2)); } if (ibaMinExtTemperature != EMS_VALUE_INT_NOTSET) { - print_value(shell, F("Min ext. temperature"), F_(degrees), Helpers::render_value(buffer, ibaMinExtTemperature, 10)); // min ext temp for heating curve, in deg. + print_value(shell, 2, F("Min ext. temperature"), F_(degrees), Helpers::render_value(buffer, ibaMinExtTemperature, 10)); // min ext temp for heating curve, in deg. } if (ibaBuildingType != EMS_VALUE_UINT_NOTSET) { if (ibaBuildingType == 0) { - shell.printfln(F(" Building: light")); + shell.printfln(F(" Building: light")); } else if (ibaBuildingType == 1) { - shell.printfln(F(" Building: medium")); + shell.printfln(F(" Building: medium")); } else if (ibaBuildingType == 2) { - shell.printfln(F(" Building: heavy")); + shell.printfln(F(" Building: heavy")); } } } @@ -1238,7 +1257,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co } // add console commands -void Thermostat::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)}, @@ -1318,6 +1337,9 @@ void Thermostat::console_commands() { : Helpers::hextoa(buffer, settings.master_thermostat())); shell.println(); }); + + // enter the context + Console::enter_custom_context(shell, context); } } // namespace emsesp \ No newline at end of file diff --git a/src/thermostat.h b/src/thermostat.h index fe117fa5f..9ad8c17ab 100644 --- a/src/thermostat.h +++ b/src/thermostat.h @@ -76,7 +76,7 @@ class Thermostat : public EMSdevice { return set_typeid_; } - enum Mode : uint8_t { OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN }; + enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN }; private: uint8_t hc_num_; @@ -103,7 +103,7 @@ class Thermostat : public EMSdevice { private: static uuid::log::Logger logger_; - void console_commands(); + void console_commands(Shell & shell, unsigned int context); void init_mqtt(); std::string datetime_; // date and time stamp diff --git a/src/version.h b/src/version.h index 0f1f2959d..db2055ff8 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0a4" +#define EMSESP_APP_VERSION "2.0.0a6"