This commit is contained in:
Paul
2020-05-23 14:00:14 +02:00
parent 1ceef8afd5
commit b2bb8e2b5a
24 changed files with 391 additions and 304 deletions

View File

@@ -15,7 +15,7 @@ Note: Version 2.0 is not backward compatible with v1.0. The File system structur
- 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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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]

View File

@@ -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<std::string> & arguments __attribute__((unused))) {
dynamic_cast<EMSESPShell &>(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
// 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<const Telegram> telegram) {
/*
* UBAMonitorFastPlus - type 0xE4 - central heating monitor EMS+
* Still to figure out are: serviceCode, retTemp, sysPress
*/
void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram) {
telegram->read_value(selFlowTemp_, 6);
@@ -562,8 +565,6 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> 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

View File

@@ -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
@@ -82,7 +82,7 @@ class Boiler : public EMSdevice {
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
char serviceCodeChar_[3] = {'\0'}; // 2 character status/service code
uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code
// UBAMonitorSlow - 0x19 on EMS1

View File

@@ -21,7 +21,11 @@
namespace emsesp {
static std::shared_ptr<Commands> commands = std::make_shared<Commands>();
std::shared_ptr<Commands> EMSESPShell::commands = [] {
std::shared_ptr<Commands> commands = std::make_shared<Commands>();
return commands;
}();
static std::shared_ptr<EMSESPShell> shell;
std::vector<bool> EMSESPStreamConsole::ptys_;
@@ -32,11 +36,6 @@ uuid::telnet::TelnetService telnet_([](Stream & stream, const IPAddress & addr,
});
#endif
std::shared_ptr<Commands> EMSESPShell::commands = [] {
std::shared_ptr<Commands> commands = std::make_shared<Commands>();
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<std::string> & arguments __attribute__((unused))) {
dynamic_cast<EMSESPShell &>(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<std::string> & arguments __attribute__((unused))) {
dynamic_cast<EMSESPShell &>(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<std::string> & arguments __attribute__((unused))) {
dynamic_cast<EMSESPShell &>(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,
EMSESPShell::commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(test)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::run_test(shell, arguments.front()); });
[](Shell & shell, const std::vector<std::string> & 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,25 +247,28 @@ void EMSESPShell::enter_custom_context(unsigned int context) {
return uuid::log::levels_lowercase();
});
commands->add_command(context,
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(help)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { shell.print_all_available_commands(); });
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.print_all_available_commands();
});
commands->add_command(context,
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(exit)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { shell.exit_context(); });
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
// delete MAIN console stuff first to save memory
EMSESPShell::commands->remove_context_commands(context);
shell.exit_context();
});
commands->add_command(context,
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(su)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
auto become_admin = [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE,
LogFacility::AUTH,
F("Admin session opened on console %s"),
dynamic_cast<EMSESPShell &>(shell).console_name().c_str());
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Admin session opened on console"));
shell.add_flags(CommandFlags::ADMIN);
};
@@ -272,10 +283,7 @@ void EMSESPShell::enter_custom_context(unsigned int context) {
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<EMSESPShell &>(shell).console_name().c_str());
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid admin password on console"));
shell.println(F("su: incorrect password"));
});
}
@@ -285,10 +293,8 @@ void EMSESPShell::enter_custom_context(unsigned int context) {
});
#ifdef EMSESP_DEBUG
commands->add_command(context,
CommandFlags::ADMIN,
flash_string_vector{F_(debug)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESPShell::commands->add_command(
context, CommandFlags::ADMIN, flash_string_vector{F_(debug)}, [&](Shell & shell, const std::vector<std::string> & 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();
@@ -296,11 +302,6 @@ void EMSESPShell::enter_custom_context(unsigned int context) {
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

View File

@@ -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<uuid::console::Commands> commands;
static std::shared_ptr<EMSESPShell> 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;

View File

@@ -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},
@@ -60,11 +60,12 @@
// Controllers - 0x09 / 0x10
{ 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
{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

View File

@@ -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

View File

@@ -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,8 +195,7 @@ 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?
bool fetch_; // if this type_id be queried automatically
process_function_p process_function_;
};
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types

View File

@@ -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<const Telegram> 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<const Telegram> 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();
}
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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::MQTTFunction> 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

View File

@@ -39,6 +39,8 @@
#include <uuid/log.h>
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);

View File

@@ -346,6 +346,7 @@ void Network::show_network(uuid::console::Shell & shell) {
shell.printfln(F("WiFi: unknown"));
break;
}
shell.println();
#endif
}

View File

@@ -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

View File

@@ -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<std::string> & 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);
}

View File

@@ -43,6 +43,8 @@
#include <uuid/log.h>
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_;

View File

@@ -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..."));

View File

@@ -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<std::string> & arguments __attribute__((unused))) {
dynamic_cast<EMSESPShell &>(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<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
@@ -364,6 +371,7 @@ void Thermostat::publish_values() {
}
has_data = true;
// if the MQTT format is 'nested' then create the parent object hc<n>
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::HeatingCircuit> 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,10 +692,14 @@ 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) {
@@ -689,19 +708,19 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
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
@@ -740,11 +759,11 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
}
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) {
@@ -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

View File

@@ -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

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0a4"
#define EMSESP_APP_VERSION "2.0.0a6"