diff --git a/README.md b/README.md index b35746b05..8b77b170d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # EMS-ESP version 2.0 (alpha) -*Warning: this is a snapshot the EMS-ESP2 development in still in early stages of development and not for production!* +*Warning: this is a snapshot from my EMS-ESP2 development repo and still in early stages of it's development. Not all features have been fully tested. Use at your own risk!* + +Note: Version 2.0 is not backward compatible with v1.0. The File system structure is different. When coming from version 1.9.x its best if you first erase the flash on the ESP and upload using USB. `esptool.py erase_flash` will clean the flash and `esptool.py -p COM6 -b 921600 write_flash 0x00000 firmware.bin` is an example of how to upload the firmware over USB. --- @@ -8,9 +10,9 @@ ### **Design & Coding principles** -- The code can be built and run without an ESP microcontroller, which helps with testing and simulating handling of telegrams. +- The code can be built and run without an ESP microcontroller, which is useful with testing and simulating handling of telegrams. 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 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). +- 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. @@ -26,29 +28,111 @@ * `su` to switch to Admin which enables more commands such as most of the `set` commands. The default password is "neo". When in Admin mode the command prompt switches from `$` to `#`. * `log` sets the logging. `log off` disables logging. Use `log trace` to see the telegram traffic and `log debug` for very verbose logging. To watch a specific telegram ID or device ID use `log trace [id]`. -- There is no "serial mode" anymore. When the Wifi cannot connect to the SSID it will automatically enter a "safe" mode where the Serial console is activated. Note Serial is always available on the ESP32 because it has 2 UARTs. +- There is no "serial mode" anymore like with version 1.9. When the Wifi cannot connect to the SSID it will automatically enter a "safe" mode where the Serial console is activated, baud 115200. Note Serial is always available on the ESP32 because it has 2 UARTs. - LED behaves like in 1.9. A solid LED means good connection and EMS data is coming in. A slow pulse means either the WiFi or the EMS bus is not connected. A very fast pulse is when the system is booting up and configuring itself. - on a new install you will want to enter `su` and then go to the `system` context. Use `set wifi ...` to set the network up. Then go to the `mqtt` context to set the mqtt up. +# Full Console + +``` +common commands available in all contexts: + exit + help + log [level] [trace ID] + su + +top level/root + refresh + show + show version + ems (is a menu) + mqtt (is a menu) + system (is a menu) + boiler (is a menu) + thermostat (is a menu) + +ems + scan devices [deep] + send telegram <"XX XX ..."> + set + set bus_id + set read_only + set tx_mode + show + show devices + show emsbus + +mqtt + publish + reconnect + set + set base + set enabled + set heartbeat + set ip + set nested_json + set password + set publish_time + set qos + set username [name] + show + +system + format + passwd + restart + set + set hostname + set syslog host [IP address] + set syslog level [level] + set syslog mark [seconds] + set wifi password + set wifi ssid + show + test + wifi disconnect + wifi reconnect + wifi scan + +boiler + change comfort + change flowtemp + change wwactive + change wwcirculation + change wwonetime + change wwtemp + read + set + set shower alert + set shower timer + show + +thermostat + change mode [heating circuit] + change temp [heating circuit] + read + set + set master [device ID] + show +``` ---------- -### **Fixes and Improvements to work on now** +### **Known issues, bugs and improvements currently working on** ``` -TODO figure out why sometimes telnet on ESP32 (and sometimes ESP8266) has slow response times. +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 console auto-complete with 'set' command in the system context is not showing all commands, only the hostname. - +TODO sometimes get an error after a Tx send when first booting up. timeout perhaps? ``` ### **Features to add next** ``` -TODO finish porting over code for Solar, Mixing and Heat pump. -TODO implement 0xE9 telegram type for EMS+ boilers. This was a request #382 (https://github.com/proddy/EMS-ESP/issues/382) +TODO validate 0xE9 with data from Koen. (https://github.com/proddy/EMS-ESP/issues/382) ``` ### **To tidy up in code later** @@ -62,7 +146,7 @@ TODO add real unit tests using platformio's test bed (https://docs.platformio.or TODO See if it's easier to use timers instead of millis() timers, using https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino ``` -### **These features to add** +### **These features to add next** ``` TODO merge in the web code diff --git a/lib/uuid-console/src/commands.cpp b/lib/uuid-console/src/commands.cpp index 171fbb8f3..eb33408da 100644 --- a/lib/uuid-console/src/commands.cpp +++ b/lib/uuid-console/src/commands.cpp @@ -79,10 +79,17 @@ void Commands::add_command(unsigned int context, } // added by proddy -void Commands::remove_context_commands(unsigned int context) { - commands_.erase(context); - /* +// note we should really iterate and free up the lambda code and any flashstrings +void Commands::remove_all_commands() { + commands_.clear(); +} +// added by proddy +// note we should really iterate and free up the lambda code and any flashstrings +void Commands::remove_context_commands(unsigned int context) { + commands_.erase(context); + + /* auto commands = commands_.equal_range(context); for (auto command_it = commands.first; command_it != commands.second; command_it++) { shell.printf("Got: "); diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index 59a5f122b..3ee59a8dd 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -57,9 +57,8 @@ Shell::~Shell() { } void Shell::start() { - // Added by proddy - default log level #ifdef EMSESP_DEBUG - uuid::log::Logger::register_handler(this, uuid::log::Level::NOTICE); // was debug + uuid::log::Logger::register_handler(this, uuid::log::Level::DEBUG); // added by proddy #else uuid::log::Logger::register_handler(this, uuid::log::Level::NOTICE); #endif diff --git a/lib/uuid-console/src/shell_print.cpp b/lib/uuid-console/src/shell_print.cpp index 66f78fa01..b1d2c84c4 100644 --- a/lib/uuid-console/src/shell_print.cpp +++ b/lib/uuid-console/src/shell_print.cpp @@ -128,7 +128,6 @@ void Shell::print_all_available_commands() { command_line.escape_initial_parameters(name.size()); name.clear(); arguments.clear(); - print(" "); // added by proddy println(command_line.to_string(maximum_command_line_length())); }); */ diff --git a/lib/uuid-console/src/uuid/console.h b/lib/uuid-console/src/uuid/console.h index ea4dc24a8..423e85ae8 100644 --- a/lib/uuid-console/src/uuid/console.h +++ b/lib/uuid-console/src/uuid/console.h @@ -1313,6 +1313,7 @@ class Commands { void for_each_available_command(Shell & shell, apply_function f) const; void remove_context_commands(unsigned int context); // added by proddy + void remove_all_commands(); // added by proddy private: /** diff --git a/lib/uuid-log/src/uuid/log.h b/lib/uuid-log/src/uuid/log.h index 8f2297ff6..48ee3f727 100644 --- a/lib/uuid-log/src/uuid/log.h +++ b/lib/uuid-log/src/uuid/log.h @@ -66,35 +66,12 @@ namespace uuid { #define COLOR_BRIGHT_CYAN "\x1B[0;96m" #define COLOR_BRIGHT_WHITE "\x1B[0;97m" #define COLOR_UNDERLINE "\x1B[4m" - -/* -Background Black: \u001b[40m -Background Red: \u001b[41m -Background Green: \u001b[42m -Background Yellow: \u001b[43m -Background Blue: \u001b[44m -Background Magenta: \u001b[45m -Background Cyan: \u001b[46m -Background White: \u001b[47m -With the bright versions being: - -Background Bright Black: \u001b[40;1m -Background Bright Red: \u001b[41;1m -Background Bright Green: \u001b[42;1m -Background Bright Yellow: \u001b[43;1m -Background Bright Blue: \u001b[44;1m -Background Bright Magenta: \u001b[45;1m -Background Bright Cyan: \u001b[46;1m -Background Bright White: \u001b[47;1m -*/ - #define COLOR_BRIGHT_RED_BACKGROUND "\x1B[41;1m" - namespace log { /** - * Severity level of log messages. Proddy added a VERBOSE + * Severity level of log messages. Proddy switches trace & debug * * @since 1.0.0 */ @@ -340,7 +317,7 @@ class Logger { * * @since 1.0.0 */ - static constexpr size_t MAX_LOG_LENGTH = 255; // proddy note, kept at 255 + static constexpr size_t MAX_LOG_LENGTH = 255; /** * Create a new logger with the given name and logging facility. diff --git a/platformio.ini b/platformio.ini index 981cabcf1..bff78b4c3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ extra_configs = pio_local.ini ;debug_flags = -DDEBUG_ESP_PORT=Serial -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM debug_flags = - -D EMSESP_DEBUG + ; -D EMSESP_DEBUG ; -D EMSESP_SAFE_MODE ; -D ENABLE_CORS -D CORS_ORIGIN=\"http://localhost:3000\" @@ -57,13 +57,14 @@ upload_protocol = esptool ; upload_flags = ; --port=8266 ; --auth=neo -; upload_port = ems-esp2.local +; upload_port = ems-esp.local [env:esp8266] build_type = release platform = espressif8266 ; https://github.com/platformio/platform-espressif8266/releases ;platform = espressif8266@2.4.0 ; Arduino core 2.6.3 -board = esp12e +; board = esp12e +board = d1_mini ; https://github.com/platformio/platform-espressif8266/blob/master/boards/d1_mini.json 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 diff --git a/src/boiler.cpp b/src/boiler.cpp index e173e3761..9a1b6e4e0 100644 --- a/src/boiler.cpp +++ b/src/boiler.cpp @@ -49,21 +49,24 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const DEBUG_LOG(F("Registering new Boiler with device ID 0x%02X"), device_id); // the telegram handlers... - register_telegram_type(0x18, F("UBAMonitorFast"), true, std::bind(&Boiler::process_UBAMonitorFast, this, _1)); // 0x18 - register_telegram_type(0x19, F("UBAMonitorSlow"), true, std::bind(&Boiler::process_UBAMonitorSlow, this, _1)); // 0x19 - register_telegram_type(0x34, F("UBAMonitorWW"), false, std::bind(&Boiler::process_UBAMonitorWW, this, _1)); // 0x34 - register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, std::bind(&Boiler::process_UBAMaintenanceStatus, this, _1)); // 0x1C - register_telegram_type(0x2A, F("MC10Status"), false, std::bind(&Boiler::process_MC10Status, this, _1)); // 0x2A - register_telegram_type(0x33, F("UBAParameterWW"), true, std::bind(&Boiler::process_UBAParameterWW, this, _1)); // 0x33 - register_telegram_type(0x14, F("UBATotalUptime"), true, std::bind(&Boiler::process_UBATotalUptime, this, _1)); // 0x14 - register_telegram_type(0x35, F("UBAFlags"), false, std::bind(&Boiler::process_UBAFlags, this, _1)); // 0x35 - register_telegram_type(0x15, F("UBAMaintenanceSettings"), false, std::bind(&Boiler::process_UBAMaintenanceSettings, this, _1)); // 0x15 - register_telegram_type(0x16, F("UBAParameters"), true, std::bind(&Boiler::process_UBAParameters, this, _1)); // 0x16 - register_telegram_type(0x1A, F("UBASetPoints"), false, std::bind(&Boiler::process_UBASetPoints, this, _1)); // 0x1A - register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, std::bind(&Boiler::process_UBAOutdoorTemp, this, _1)); // 0xD1 - register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, std::bind(&Boiler::process_UBAMonitorFastPlus, this, _1)); // 0xE4 - register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus, this, _1)); // 0xE5 - register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1)); // 0xE9 + register_telegram_type(0x18, F("UBAMonitorFast"), true, std::bind(&Boiler::process_UBAMonitorFast, this, _1)); + register_telegram_type(0x19, F("UBAMonitorSlow"), true, std::bind(&Boiler::process_UBAMonitorSlow, this, _1)); + register_telegram_type(0x34, F("UBAMonitorWW"), false, std::bind(&Boiler::process_UBAMonitorWW, this, _1)); + 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(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(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)); + register_telegram_type(0xE5, F("HeatPumpMonitor2"), true, std::bind(&Boiler::process_HPMonitor2, this, _1)); // MQTT callbacks register_mqtt_topic("boiler_cmd", std::bind(&Boiler::boiler_cmd, this, _1)); @@ -73,6 +76,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_mqtt_topic("boiler_cmd_wwtemp", std::bind(&Boiler::boiler_cmd_wwtemp, this, _1)); } +// add submenu context void Boiler::add_context_menu() { EMSESPShell::commands->add_command(ShellContext::MAIN, CommandFlags::USER, @@ -83,8 +87,8 @@ void Boiler::add_context_menu() { }); } +// boiler_cmd topic void Boiler::boiler_cmd(const char * message) { - // convert JSON and get the command StaticJsonDocument doc; DeserializationError error = deserializeJson(doc, message); if (error) { @@ -157,148 +161,155 @@ void Boiler::boiler_cmd_wwtemp(const char * message) { void Boiler::publish_values() { const size_t capacity = JSON_OBJECT_SIZE(47); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/ DynamicJsonDocument doc(capacity); - JsonObject rootBoiler = doc.to(); char s[10]; // for formatting strings if (wWComfort_ == 0x00) { - rootBoiler["wWComfort"] = "Hot"; + doc["wWComfort"] = "Hot"; } else if (wWComfort_ == 0xD8) { - rootBoiler["wWComfort"] = "Eco"; + doc["wWComfort"] = "Eco"; } else if (wWComfort_ == 0xEC) { - rootBoiler["wWComfort"] = "Intelligent"; + doc["wWComfort"] = "Intelligent"; } if (wWSelTemp_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["wWSelTemp"] = wWSelTemp_; + doc["wWSelTemp"] = wWSelTemp_; } if (wWDisinfectTemp_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["wWDisinfectionTemp"] = wWDisinfectTemp_; + doc["wWDisinfectionTemp"] = wWDisinfectTemp_; } if (selFlowTemp_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["selFlowTemp"] = selFlowTemp_; + doc["selFlowTemp"] = selFlowTemp_; } if (selBurnPow_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["selBurnPow"] = selBurnPow_; + doc["selBurnPow"] = selBurnPow_; } if (curBurnPow_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["curBurnPow"] = curBurnPow_; + doc["curBurnPow"] = curBurnPow_; } if (pumpMod_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["pumpMod"] = pumpMod_; + doc["pumpMod"] = pumpMod_; } if (wWCircPump_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWCircPump"] = wWCircPump_; + doc["wWCircPump"] = wWCircPump_; } if (wWCircPumpType_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWCiPuType"] = wWCircPumpType_; + doc["wWCiPuType"] = wWCircPumpType_; } if (wWCircPumpMode_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["wWCiPuMode"] = wWCircPumpMode_; + doc["wWCiPuMode"] = wWCircPumpMode_; } if (extTemp_ != EMS_VALUE_SHORT_NOTSET) { - rootBoiler["outdoorTemp"] = (float)extTemp_ / 10; + doc["outdoorTemp"] = (float)extTemp_ / 10; } if (wWCurTmp_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["wWCurTmp"] = (float)wWCurTmp_ / 10; + doc["wWCurTmp"] = (float)wWCurTmp_ / 10; } if (wWCurFlow_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["wWCurFlow"] = (float)wWCurFlow_ / 10; + doc["wWCurFlow"] = (float)wWCurFlow_ / 10; } if (curFlowTemp_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["curFlowTemp"] = (float)curFlowTemp_ / 10; + doc["curFlowTemp"] = (float)curFlowTemp_ / 10; } if (retTemp_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["retTemp"] = (float)retTemp_ / 10; + doc["retTemp"] = (float)retTemp_ / 10; } if (switchTemp_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["switchTemp"] = (float)switchTemp_ / 10; + doc["switchTemp"] = (float)switchTemp_ / 10; } if (sysPress_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["sysPress"] = (float)sysPress_ / 10; + doc["sysPress"] = (float)sysPress_ / 10; } if (boilTemp_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["boilTemp"] = (float)boilTemp_ / 10; + doc["boilTemp"] = (float)boilTemp_ / 10; } if (wwStorageTemp1_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10; + doc["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10; } if (wwStorageTemp2_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10; + doc["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10; } if (exhaustTemp_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["exhaustTemp"] = (float)exhaustTemp_ / 10; + doc["exhaustTemp"] = (float)exhaustTemp_ / 10; } if (wWActivated_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL); + doc["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL); } if (wWOneTime_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL); + doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL); } if (wWDesinfecting_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL); + doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL); } if (wWReadiness_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL); + doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL); } if (wWRecharging_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL); + doc["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL); } if (wWTemperatureOK_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL); + doc["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL); } if (wWCirc_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL); + doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL); } if (burnGas_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL); + doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL); } if (flameCurr_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["flameCurr"] = (float)(int16_t)flameCurr_ / 10; + doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10; } if (heatPmp_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["heatPmp"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL); + doc["heatPmp"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL); } if (fanWork_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL); + doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL); } if (ignWork_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL); + doc["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL); } if (wWHeat_ != EMS_VALUE_BOOL_NOTSET) { - rootBoiler["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL); + doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL); } if (heating_temp_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["heating_temp"] = heating_temp_; + doc["heating_temp"] = heating_temp_; } if (pump_mod_max_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["pump_mod_max"] = pump_mod_max_; + doc["pump_mod_max"] = pump_mod_max_; } if (pump_mod_min_ != EMS_VALUE_UINT_NOTSET) { - rootBoiler["pump_mod_min"] = pump_mod_min_; + doc["pump_mod_min"] = pump_mod_min_; } if (wWStarts_ != EMS_VALUE_ULONG_NOTSET) { - rootBoiler["wWStarts"] = wWStarts_; + doc["wWStarts"] = wWStarts_; } if (wWWorkM_ != EMS_VALUE_ULONG_NOTSET) { - rootBoiler["wWWorkM"] = wWWorkM_; + doc["wWWorkM"] = wWWorkM_; } if (UBAuptime_ != EMS_VALUE_ULONG_NOTSET) { - rootBoiler["UBAuptime"] = UBAuptime_; + doc["UBAuptime"] = UBAuptime_; } if (burnStarts_ != EMS_VALUE_ULONG_NOTSET) { - rootBoiler["burnStarts"] = burnStarts_; + doc["burnStarts"] = burnStarts_; } if (burnWorkMin_ != EMS_VALUE_ULONG_NOTSET) { - rootBoiler["burnWorkMin"] = burnWorkMin_; + doc["burnWorkMin"] = burnWorkMin_; } if (heatWorkMin_ != EMS_VALUE_ULONG_NOTSET) { - rootBoiler["heatWorkMin"] = heatWorkMin_; + doc["heatWorkMin"] = heatWorkMin_; } if (serviceCode_ != EMS_VALUE_USHORT_NOTSET) { - rootBoiler["ServiceCode"] = serviceCodeChar_; - rootBoiler["ServiceCodeNumber"] = serviceCode_; + doc["serviceCode"] = serviceCodeChar_; + doc["serviceCodeNumber"] = serviceCode_; + } + + // heatpump specific + if (hpModulation_ != EMS_VALUE_UINT_NOTSET) { + doc["pumpmodulation"] = hpModulation_; + } + if (hpSpeed_ != EMS_VALUE_UINT_NOTSET) { + doc["pumpspeed"] = hpSpeed_; } #ifdef EMSESP_DEBUG @@ -410,6 +421,14 @@ void Boiler::show_values(uuid::console::Shell & shell) { shell.printfln(F(" Total UBA working time: %d days %d hours %d minutes"), UBAuptime_ / 1440, (UBAuptime_ % 1440) / 60, UBAuptime_ % 60); } + if (hpModulation_ != EMS_VALUE_UINT_NOTSET) { + print_value(shell, 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)); + } + shell.println(); } @@ -583,6 +602,39 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr telegram telegram->read_value(pumpMod_, 25); // or is it switchTemp ? } +/* + * Type 0xE3 - HeatPump Monitor 1 + */ +void Boiler::process_HPMonitor1(std::shared_ptr telegram) { + telegram->read_value(hpModulation_, 13); +} + +/* + * Type 0xE5 - HeatPump Monitor 2 + */ +void Boiler::process_HPMonitor2(std::shared_ptr telegram) { + telegram->read_value(hpSpeed_, 25); +} + +// 0xE9 - DHW Status +// e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27 +void Boiler::process_UBADHWStatus(std::shared_ptr telegram) { + telegram->read_value(wWSetTmp_, 0); + telegram->read_value(wWCurTmp_, 1); + telegram->read_value(wWCurTmp2_, 3); + telegram->read_value(wWWorkM_, 17); + telegram->read_value(wWStarts_, 14); + telegram->read_value(wWOneTime_, 12, 2); + telegram->read_value(wWDesinfecting_, 12, 3); + telegram->read_value(wWReadiness_, 12, 4); + telegram->read_value(wWRecharging_, 13, 4); + telegram->read_value(wWTemperatureOK_, 13, 5); + telegram->read_value(wWActivated_, 20); + telegram->read_value(wWCircPump_, 13, 2); + telegram->read_value(wWSelTemp_, 10); + telegram->read_value(wWDisinfectTemp_, 9); +} + /** * UBAOutdoorTemp - type 0xD1 - external temperature EMS+ */ @@ -593,11 +645,6 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr telegram) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" -// 0xE9 - DHW Status -void Boiler::process_UBADHWStatus(std::shared_ptr telegram) { -} - - // UBASetPoint 0x1A // not yet implemented void Boiler::process_UBASetPoints(std::shared_ptr telegram) { diff --git a/src/boiler.h b/src/boiler.h index 21bc21096..67ebd977e 100644 --- a/src/boiler.h +++ b/src/boiler.h @@ -32,9 +32,6 @@ #include "helpers.h" #include "mqtt.h" -MAKE_PSTR(logger_name2, "boiler") - - namespace emsesp { class Boiler : public EMSdevice { @@ -53,10 +50,10 @@ class Boiler : public EMSdevice { uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off - static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; - static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; - static constexpr uint8_t EMS_TYPE_UBAFlags = 0x35; - static constexpr uint8_t EMS_TYPE_UBASetPoints = 0x1A; + static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; + static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; + static constexpr uint8_t EMS_TYPE_UBAFlags = 0x35; + static constexpr uint8_t EMS_TYPE_UBASetPoints = 0x1A; static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344 @@ -122,6 +119,10 @@ class Boiler : public EMSdevice { uint8_t tap_water_active_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off + // heatpump boilers + uint8_t hpModulation_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation in % + uint8_t hpSpeed_ = EMS_VALUE_UINT_NOTSET; // speed 0-100 % + void process_UBAParameterWW(std::shared_ptr telegram); void process_UBAMonitorFast(std::shared_ptr telegram); void process_UBATotalUptime(std::shared_ptr telegram); @@ -136,7 +137,10 @@ class Boiler : public EMSdevice { void process_MC10Status(std::shared_ptr telegram); void process_UBAMaintenanceStatus(std::shared_ptr telegram); void process_UBAMaintenanceSettings(std::shared_ptr telegram); + void process_UBADHWStatus(std::shared_ptr telegram); + void process_HPMonitor1(std::shared_ptr telegram); + void process_HPMonitor2(std::shared_ptr telegram); void check_active(); diff --git a/src/console.cpp b/src/console.cpp index b63e6593d..d816a63d0 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -50,6 +50,10 @@ void EMSESPShell::stopped() { logger().log(LogLevel::INFO, LogFacility::AUTH, F("Admin session closed on console %s"), console_name().c_str()); } logger().log(LogLevel::INFO, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str()); + + // remove all custom contexts + commands->remove_all_commands(); + _console_commands_loaded = false; // make sure they got loaded next time a console is opened } // show welcome banner @@ -87,7 +91,9 @@ void EMSESPShell::add_console_commands() { return; } - commands->remove_context_commands(ShellContext::MAIN); // just in case, remove everything + // just in case, remove everything + // commands->remove_context_commands(ShellContext::MAIN); + commands->remove_all_commands(); commands->add_command(ShellContext::MAIN, CommandFlags::USER, @@ -96,7 +102,7 @@ void EMSESPShell::add_console_commands() { shell.printfln(F("Refreshing console and fetching device data")); _console_commands_loaded = false; add_console_commands(); - EMSESP::fetch_all_values(); + EMSESP::fetch_device_values(); }); commands->add_command(ShellContext::MAIN, @@ -119,7 +125,7 @@ void EMSESPShell::add_console_commands() { }); /* - * add the submenus... + * add the submenu contexts... */ // MQTT @@ -150,7 +156,7 @@ void EMSESPShell::add_console_commands() { }); // add all the context menus for the connected devices - // this assumes they have been loaded + // this assumes they devices have been detected and registered EMSESP::add_context_menu(); enter_custom_context(ShellContext::MAIN); // add su, exit and help @@ -223,7 +229,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/telegram type ID of 0x%02X"), watch_id); + shell.printfln(("Tracing only telegrams that match a device ID/telegram type ID of 0x%02X"), watch_id); } emsesp::EMSESP::trace_watch_id(watch_id); } @@ -405,10 +411,10 @@ void Console::start() { } // always start the telnet service +// default idle is 10 minutes, default write timeout is 0 (automatic) // note, this must be started after the network/wifi for ESP32 otherwise it'll crash #ifndef EMSESP_STANDALONE telnet_.start(); - // default idle is 10 minutes, default write timeout is 0 (automatic) // telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second #endif } @@ -422,6 +428,8 @@ 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 9287a9c52..c49d81e38 100644 --- a/src/console.h +++ b/src/console.h @@ -52,7 +52,9 @@ using uuid::log::Level; // clang-format on // common words +#ifdef EMSESP_DEBUG MAKE_PSTR_WORD(test) +#endif MAKE_PSTR_WORD(exit) MAKE_PSTR_WORD(help) MAKE_PSTR_WORD(settings) diff --git a/src/controller.cpp b/src/controller.cpp index 46e114b2d..d1f8a38ec 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -18,7 +18,7 @@ #include "controller.h" -MAKE_PSTR_WORD(controller) +// MAKE_PSTR_WORD(controller) namespace emsesp { diff --git a/src/device_library.h b/src/device_library.h index d7638f94d..96ded014f 100644 --- a/src/device_library.h +++ b/src/device_library.h @@ -38,27 +38,27 @@ {170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {172, DeviceType::BOILER, F("Enviline/Compress 6000AW"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -// Solar Modules - type 0x30 +// Solar Modules - 0x30 { 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, {163, DeviceType::SOLAR, F("SM100"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {164, DeviceType::SOLAR, F("SM200"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, -// Mixing Modules - type 0x20 or 0x21 +// Mixing Modules - 0x20 / 0x21 {160, DeviceType::MIXING, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, {161, DeviceType::MIXING, F("MM200"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, { 69, DeviceType::MIXING, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10}, {159, DeviceType::MIXING, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS}, -// Heat Pumps - type 0x38 +// Heat Pumps - 0x38 {252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, {200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, -// Switches - type 0x11 +// Switches - 0x11 { 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x11 -// Controllers - type 0x09 and 0x10 +// Controllers - 0x09 / 0x10 { 68, DeviceType::CONTROLLER, F("BC10/RFM20"), 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 @@ -71,21 +71,20 @@ {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 - type 0x02 +// 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 -// Gateways - type 0x48 and sometimes 0x18? +// 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 -// Thermostat - not currently supporting write operations +// 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 {203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write -{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18 -// Thermostat - Buderus/Nefit specific +// Thermostat - Common for Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 { 79, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC10},// 0x17 { 80, DeviceType::THERMOSTAT, F("Moduline 200"), DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17 { 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17 @@ -93,14 +92,15 @@ { 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10 { 86, DeviceType::THERMOSTAT, F("RC35"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10 { 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19 +{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18 {158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10 {165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38 -// Thermostat - Sieger +// Thermostat - Sieger - 0x10 / 0x17 { 76, DeviceType::THERMOSTAT, F("ES73"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10 {113, DeviceType::THERMOSTAT, F("ES72"), DeviceFlags::EMS_DEVICE_FLAG_RC20_2}, // 0x17 -// Thermostat - Junkers - all 0x10 +// Thermostat - Junkers - 0x10 {105, DeviceType::THERMOSTAT, F("FW100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {106, DeviceType::THERMOSTAT, F("FW200"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 3ed53f431..3eb9f539e 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -23,7 +23,6 @@ MAKE_PSTR_WORD(tx_mode) MAKE_PSTR_WORD(read_only) MAKE_PSTR_WORD(emsbus) MAKE_PSTR_WORD(devices) -MAKE_PSTR_WORD(deep) MAKE_PSTR_WORD(send) MAKE_PSTR_WORD(telegram) @@ -56,9 +55,9 @@ Network EMSESP::network_; // WiFi Shower EMSESP::shower_; // Shower logic // static/common variables -uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_NOTSET; // which thermostat leads when multiple found -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() +uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found +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_; #ifdef EMSESP_DEBUG @@ -66,19 +65,16 @@ bool EMSESP::ems_read_only_; #endif // for each associated EMS device go and request data values -void EMSESP::fetch_all_values() { - for (const auto & emsdevice : emsdevices) { - if (emsdevice) { - emsdevice->fetch_values(); - } - } +void EMSESP::fetch_device_values() { + fetch_device_values(0); // fetch all } // for a specific EMS device go and request data values +// or if device_id is 0 it will fetch from all known devices void EMSESP::fetch_device_values(const uint8_t device_id) { for (const auto & emsdevice : emsdevices) { if (emsdevice) { - if (emsdevice->is_device_id(device_id)) { + if ((device_id == 0) || emsdevice->is_device_id(device_id)) { emsdevice->fetch_values(); return; } @@ -478,7 +474,7 @@ void EMSESP::show_devices(uuid::console::Shell & shell) { shell.println(); for (const auto & emsdevice : emsdevices) { if (emsdevice) { - shell.printf(F("%s%s%s"), COLOR_BOLD_ON, emsdevice->to_string().c_str(), COLOR_BOLD_OFF); + shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) { shell.printf(F(" ** master device **")); } @@ -584,7 +580,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { txservice_.send_poll(); // close the bus } else { #ifdef EMSESP_DEBUG - logger_.err(F("Waiting for Tx ACK (1 or 4) but got 0x%02X"), first_value); + logger_.err(F("Expecting Tx ACK (1/4) but got 0x%02X. Tx:%s"), first_value, txservice_.last_tx_to_string().c_str()); #endif } @@ -611,7 +607,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { } // check for poll, if so, send Tx from the queue immediately - // if ht3 poll must be ems_bus_id else if buderus poll must be (ems_bus_id | 0x80) + // if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80) if ((length == 1) && ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id())) { EMSbus::last_bus_activity(millis()); // set the flag indication the EMS bus is active txservice_.send(); @@ -729,7 +725,9 @@ void EMSESP::console_commands() { CommandFlags::USER, flash_string_vector{F_(send), F_(telegram)}, flash_string_vector{F_(data_mandatory)}, - [](Shell & shell __attribute__((unused)), const std::vector & arguments) { EMSESP::send_raw_telegram(arguments.front().c_str()); }); + [](Shell & shell __attribute__((unused)), const std::vector & arguments) { + EMSESP::send_raw_telegram(arguments.front().c_str()); + }); EMSESPShell::commands->add_command(ShellContext::EMS, CommandFlags::USER, @@ -784,7 +782,6 @@ void EMSESP::start() { system_.start(); network_.start(); console_.start(); - mqtt_.start(); sensors_.start(); rxservice_.start(); txservice_.start(); diff --git a/src/emsesp.h b/src/emsesp.h index 1c126a7f9..2c16dcb94 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -54,7 +54,6 @@ class EMSESP { static void start(); static void loop(); - static void fetch_all_values(); static void publish_all_values(); #ifdef EMSESP_DEBUG @@ -114,13 +113,14 @@ class EMSESP { static void console_commands(); + static void fetch_device_values(const uint8_t device_id); + static void fetch_device_values(); private: EMSESP() = delete; static uuid::log::Logger logger_; - static void fetch_device_values(const uint8_t device_id); static bool add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand); static std::string device_tostring(const uint8_t device_id); diff --git a/src/heatpump.cpp b/src/heatpump.cpp index c5bf94e91..cb628bf01 100644 --- a/src/heatpump.cpp +++ b/src/heatpump.cpp @@ -18,7 +18,14 @@ #include "heatpump.h" -MAKE_PSTR_WORD(heatpump) +// MAKE_PSTR_WORD(heatpump) + +/* + example telegrams 0x32B, 0x37B + "38 10 FF 00 03 7B 08 24 00 4B", + "38 10 FF 00 03 2B 00 C7 07 C3 01", + "38 10 FF 00 03 2B 00 D1 08 2A 01", +*/ namespace emsesp { @@ -28,20 +35,25 @@ uuid::log::Logger Heatpump::logger_{F_(logger_name), uuid::log::Facility::CONSOL Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { + DEBUG_LOG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id); + // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Heatpump::process_XX, this, _1)); + register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1)); + register_telegram_type(0x042B, F("HP2"), true, std::bind(&Heatpump::process_HPMonitor2, this, _1)); // MQTT callbacks // register_mqtt_topic("cmd", std::bind(&Heatpump::cmd, this, _1)); - } +// context submenu void Heatpump::add_context_menu() { } // display all values into the shell console void Heatpump::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header + + shell.println(); } // publish values via MQTT @@ -57,4 +69,25 @@ bool Heatpump::updated_values() { void Heatpump::console_commands() { } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +/* + * Type 0x42B- HeatPump Monitor 1 + * e.g. "38 10 FF 00 03 2B 00 D1 08 2A 01" + */ +void Heatpump::process_HPMonitor1(std::shared_ptr telegram) { + // still to implement +} + +/* + * Type 0x47B - HeatPump Monitor 2 + * e.g. "38 10 FF 00 03 7B 08 24 00 4B" + */ +void Heatpump::process_HPMonitor2(std::shared_ptr telegram) { + // still to implement +} + +#pragma GCC diagnostic pop + } // namespace emsesp \ No newline at end of file diff --git a/src/heatpump.h b/src/heatpump.h index 58710e0a7..f45593ddc 100644 --- a/src/heatpump.h +++ b/src/heatpump.h @@ -45,6 +45,9 @@ class Heatpump : public EMSdevice { static uuid::log::Logger logger_; void console_commands(); + + void process_HPMonitor1(std::shared_ptr telegram); + void process_HPMonitor2(std::shared_ptr telegram); }; } // namespace emsesp diff --git a/src/mixing.cpp b/src/mixing.cpp index c4df4d3cd..b8b5280e5 100644 --- a/src/mixing.cpp +++ b/src/mixing.cpp @@ -18,7 +18,7 @@ #include "mixing.h" -MAKE_PSTR_WORD(mixing) +// MAKE_PSTR_WORD(mixing) namespace emsesp { @@ -28,24 +28,74 @@ uuid::log::Logger Mixing::logger_{F_(logger_name), uuid::log::Facility::CONSOLE} Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Mixing::process_XX, this, _1)); + DEBUG_LOG(F("Registering new Mixing module with device ID 0x%02X"), device_id); + + // telegram handlers 0x20 - 0x27 for HC + register_telegram_type(0x02D7, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1)); + register_telegram_type(0x02D8, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1)); + register_telegram_type(0x02D9, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1)); + register_telegram_type(0x02DA, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1)); + + // telegram handlers for warm water/DHW 0x28, 0x29 + register_telegram_type(0x0331, F("MMPLUSStatusMessage_WWC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_WWC, this, _1)); + register_telegram_type(0x0332, F("MMPLUSStatusMessage_WWC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_WWC, this, _1)); + + // EMS 1.0 + register_telegram_type(0x00AB, F("MMStatusMessage"), true, std::bind(&Mixing::process_MMStatusMessage, this, _1)); // MQTT callbacks // register_mqtt_topic("cmd", std::bind(&Mixing::cmd, this, _1)); - } +// add context submenu void Mixing::add_context_menu() { } // display all values into the shell console void Mixing::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header + + char buffer[10]; // used for formatting + + shell.printfln(F(" 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, valveStatus_, 1)); + + shell.println(); } // publish values via MQTT +// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually void Mixing::publish_values() { + DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL); + + if (flowTemp_ != EMS_VALUE_USHORT_NOTSET) { + doc["flowTemp"] = (float)flowTemp_ / 10; + } + + if (pumpMod_ != EMS_VALUE_UINT_NOTSET) { + doc["pumpMod"] = pumpMod_; + } + + if (valveStatus_ != EMS_VALUE_UINT_NOTSET) { + doc["valveStatus_"] = valveStatus_; + } + + if (flowSetTemp_ != EMS_VALUE_UINT_NOTSET) { + doc["flowSetTemp_"] = flowSetTemp_; + } + +#ifdef EMSESP_DEBUG + DEBUG_LOG(F("[DEBUG] Performing a mixing module publish")); +#endif + + char topic[30]; + char s[3]; // for formatting strings + strlcpy(topic, "mixing_data", 30); + strlcat(topic, Helpers::itoa(s, hc_), 30); // append hc to topic + Mqtt::publish(topic, doc); } // check to see if values have been updated @@ -57,4 +107,27 @@ bool Mixing::updated_values() { void Mixing::console_commands() { } +// 0x02D7, 0x02D8 etc... +void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr telegram) { + hc_ = 0x02D8 - telegram->type_id; // determine which circuit this is + telegram->read_value(flowTemp_, 3); // isd * 10 + telegram->read_value(pumpMod_, 5); + telegram->read_value(valveStatus_, 2); +} + +// Mixing module warm water loading - 0x0331, 0x0332 +void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr telegram) { + hc_ = 0x0332 - telegram->type_id; // determine which circuit this is. There are max 2. + telegram->read_value(flowTemp_, 0); // isd * 10 + telegram->read_value(pumpMod_, 2); + telegram->read_value(valveStatus_, 11); +} + +void Mixing::process_MMStatusMessage(std::shared_ptr telegram) { + hc_ = 1; // fixed + telegram->read_value(flowTemp_, 1); // isd * 10 + telegram->read_value(pumpMod_, 3); + telegram->read_value(valveStatus_, 0); +} + } // namespace emsesp \ No newline at end of file diff --git a/src/mixing.h b/src/mixing.h index 770d1e8b8..ef1b8b8c4 100644 --- a/src/mixing.h +++ b/src/mixing.h @@ -45,6 +45,17 @@ class Mixing : public EMSdevice { static uuid::log::Logger logger_; void console_commands(); + + void process_MMPLUSStatusMessage_HC(std::shared_ptr telegram); + void process_MMPLUSStatusMessage_WWC(std::shared_ptr telegram); + void process_MMStatusMessage(std::shared_ptr telegram); + + private: + uint16_t hc_ = EMS_VALUE_USHORT_NOTSET; + uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET; + uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET; + uint8_t valveStatus_ = EMS_VALUE_UINT_NOTSET; + uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET; }; } // namespace emsesp diff --git a/src/mqtt.cpp b/src/mqtt.cpp index ed8b6419f..b46de93b3 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -47,11 +47,9 @@ MAKE_PSTR(logger_name, "mqtt") namespace emsesp { // exposing static stuff to compiler/linker - #ifndef EMSESP_STANDALONE AsyncMqttClient Mqtt::mqttClient_; #endif - std::vector Mqtt::mqtt_functions_; bool Mqtt::mqtt_retain_; uint8_t Mqtt::mqtt_qos_; @@ -59,6 +57,7 @@ std::string Mqtt::mqtt_hostname_; // copy of hostname std::string Mqtt::mqtt_base_; uint16_t Mqtt::mqtt_publish_fails_ = 0; size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES; +bool Mqtt::force_publish_ = false; uint16_t Mqtt::mqtt_message_id_ = 0; std::deque Mqtt::mqtt_messages_; char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer @@ -87,20 +86,22 @@ void Mqtt::flush_message_queue() { } // restart MQTT services -void Mqtt ::reconnect() { +void Mqtt::reconnect() { #ifndef EMSESP_STANDALONE mqttClient_.disconnect(); #endif - DEBUG_LOG(F("Reconnecting...")); - - // flush_message_queue(); // clean the queues - // start(); } // MQTT setup void Mqtt::start() { - // get some settings and store them locally. This is also because the asyncmqtt library uses references + // exit if already initialized + if (mqtt_start_) { + return; + } + mqtt_start_ = true; + + // get some settings and store them locally. This is also because the asyncmqtt library uses references for char * Settings settings; mqtt_enabled_ = settings.mqtt_enabled(); mqtt_hostname_ = settings.hostname(); @@ -119,9 +120,7 @@ void Mqtt::start() { mqtt_enabled_ = false; } - if (!mqtt_init_) { - init(); // set up call backs. only done once. - } + init(); // set up call backs. only done once. #ifdef EMSESP_STANDALONE mqtt_enabled_ = true; // force it on for debugging standalone @@ -141,17 +140,24 @@ void Mqtt::start() { mqtt_connecting_ = false; mqtt_last_connection_ = millis(); mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN; + + DEBUG_LOG(F("Configuring MQTT service...")); } // MQTT init callbacks // This should only be executed once void Mqtt::init() { + if (mqtt_init_) { + return; + } + mqtt_init_ = true; + #ifndef EMSESP_STANDALONE mqttClient_.onConnect([this](bool sessionPresent) { on_connect(); }); mqttClient_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { - logger_.err(F("Cannot connect to server")); + logger_.err(F("Disconnected from server")); } if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { logger_.err(F("Server identifier Rejected")); @@ -169,6 +175,7 @@ void Mqtt::init() { // Reset reconnection delay mqtt_last_connection_ = millis(); mqtt_connecting_ = false; + mqtt_start_ = false; // will force a new start() }); // mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { DEBUG_LOG(F("Subscribe ACK for PID %d"), packetId); }); @@ -179,8 +186,6 @@ void Mqtt::init() { on_message(topic, payload, len); }); #endif - - mqtt_init_ = true; } Mqtt::MQTTFunction::MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function) @@ -192,8 +197,7 @@ Mqtt::MQTTFunction::MQTTFunction(uint8_t device_id, const std::string && topic, // subscribe to an MQTT topic, and store the associated callback function void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb) { mqtt_functions_.emplace_back(device_id, std::move(topic), cb); // register a call back function for a specific telegram type - - queue_subscribe_message(topic); // add subscription to queue + queue_subscribe_message(topic); // add subscription to queue } // subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a device @@ -201,7 +205,9 @@ void Mqtt::subscribe(const std::string & topic, mqtt_function_p cb) { subscribe(0, topic, cb); // no device_id needed, if generic to EMS-ESP } -// Main loops. Checks for connection, sends out top item on publish queue +// Main MQTT loop +// Checks for connection, establishes a connection if not +// sends out top item on publish queue void Mqtt::loop() { // exit if MQTT is not enabled, there is no WIFI or we're still in the MQTT connection process #ifndef EMSESP_STANDALONE @@ -214,6 +220,13 @@ void Mqtt::loop() { // if we're already connected.... if (connected()) { + if (force_publish_) { + force_publish_ = false; + send_heartbeat(); // create a heartbeat payload + EMSESP::publish_all_values(); // add sensors and mqtt to queue + publish_all_queue(); // publish everything on queue + } + // send out heartbeat uint32_t currentMillis = millis(); if ((currentMillis - last_heartbeat_ > MQTT_HEARTBEAT_INTERVAL)) { @@ -227,7 +240,7 @@ void Mqtt::loop() { EMSESP::publish_all_values(); } - // publish top item from MQTT queue - this happens every 200ms to stop flooding + // publish top item from MQTT queue to stop flooding if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) { last_mqtt_poll_ = currentMillis; publish_queue(); @@ -236,24 +249,23 @@ void Mqtt::loop() { return; } - // We need to reconnect. Check when was the last time we tried. - if (millis() - mqtt_last_connection_ < mqtt_reconnect_delay_) { + // We need to reconnect. Check when was the last time we tried this + if (mqtt_last_connection_ && (millis() - mqtt_last_connection_ < mqtt_reconnect_delay_)) { return; } - mqtt_connecting_ = true; // we're doing a connection + mqtt_connecting_ = true; // we're doing a connection now - // Increase the reconnect delay + // Increase the reconnect delay for next time mqtt_reconnect_delay_ += MQTT_RECONNECT_DELAY_STEP; if (mqtt_reconnect_delay_ > MQTT_RECONNECT_DELAY_MAX) { mqtt_reconnect_delay_ = MQTT_RECONNECT_DELAY_MAX; } - // Connect to the MQTT broker - logger_.info(F("Connecting to MQTT server...")); - #ifndef EMSESP_STANDALONE - mqttClient_.connect(); + start(); + logger_.info(F("Connecting to MQTT server...")); + mqttClient_.connect(); // Connect to the MQTT broker #endif } @@ -270,7 +282,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { return; } - shell.printfln(F("MQTT queue (%d items):"), mqtt_messages_.size()); + shell.printfln(F("MQTT queue (%d messages):"), mqtt_messages_.size()); for (const auto & message : mqtt_messages_) { auto content = message.content_; @@ -400,14 +412,11 @@ char * Mqtt::make_topic(char * result, const std::string & topic) { // send online appended with the version information as JSON void Mqtt::send_start_topic() { - Settings settings; - - StaticJsonDocument<200> doc; // length is actually about 60 - JsonObject payload = doc.to(); - payload["event"] = "start"; - payload["version"] = settings.app_version(); + StaticJsonDocument<90> doc; + doc["event"] = "start"; + doc["version"] = Settings().app_version(); #ifndef EMSESP_STANDALONE - payload["IP"] = WiFi.localIP().toString(); + doc["IP"] = WiFi.localIP().toString(); #endif publish("info", doc, false); // send with retain off @@ -434,13 +443,12 @@ void Mqtt::send_heartbeat() { return; } - StaticJsonDocument<100> doc; // length is 76 - JsonObject rootHeartbeat = doc.to(); + StaticJsonDocument<90> doc; - rootHeartbeat["rssid"] = Network::wifi_quality(); - rootHeartbeat["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); - rootHeartbeat["freemem"] = System::free_mem(); - rootHeartbeat["mqttpublishfails"] = mqtt_publish_fails_; + doc["rssid"] = Network::wifi_quality(); + doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); + doc["freemem"] = System::free_mem(); + doc["mqttpublishfails"] = mqtt_publish_fails_; publish("heartbeat", doc, false); // send to MQTT with retain off } @@ -529,6 +537,13 @@ void Mqtt::publish(const char * topic, const bool value) { queue_publish_message(topic, value ? "1" : "0", mqtt_retain_); } +// publish all queued messages to MQTT +void Mqtt::publish_all_queue() { + while (!mqtt_messages_.empty()) { + publish_queue(); + } +} + // take top from queue and try and publish it // assumes there is an MQTT connection void Mqtt::publish_queue() { @@ -576,12 +591,14 @@ void Mqtt::publish_queue() { if (packet_id == 0) { // it failed. if we retried n times, give up. remove from queue if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) { - DEBUG_LOG(F("Failed to publish to %s after %d attemps, payload %s"), full_topic, mqtt_message.retry_count_ + 1, message->payload.c_str()); + logger_.err(F("Failed to publish to %s after %d attempts"), full_topic, mqtt_message.retry_count_ + 1); mqtt_publish_fails_++; // increment failure counter mqtt_messages_.pop_front(); // delete return; } else { mqtt_messages_.front().retry_count_++; + // logger_.err(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1); + DEBUG_LOG(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1); return; // leave on queue for next time so it gets republished } } @@ -728,11 +745,11 @@ void Mqtt::console_commands() { }); EMSESPShell::commands->add_command(ShellContext::MQTT, - CommandFlags::ADMIN, + CommandFlags::USER, flash_string_vector{F_(publish)}, - [](Shell & shell, const std::vector & arguments __attribute__((unused))) { + [=](Shell & shell, const std::vector & arguments __attribute__((unused))) { shell.printfln(F("Publishing all values to MQTT")); - EMSESP::publish_all_values(); + force_publish_ = true; }); EMSESPShell::commands->add_command(ShellContext::MQTT, diff --git a/src/mqtt.h b/src/mqtt.h index 1658a9f4a..40407a6e1 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -39,9 +39,9 @@ #include -#define EMSESP_MAX_JSON_SIZE 800 // for json docs from ems devices -#define EMSESP_MAX_JSON_SIZE_SMALL 200 // for smaller json docs from ems devices -#define EMSESP_MAX_JSON_SIZE_MEDIUM 200 // for smaller json docs from ems devices +#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 namespace emsesp { @@ -61,7 +61,6 @@ struct MqttMessage { class Mqtt { public: - void start(); void loop(); void send_heartbeat(); @@ -108,6 +107,7 @@ class Mqtt { }; static std::deque mqtt_messages_; + void start(); #ifndef EMSESP_STANDALONE static AsyncMqttClient mqttClient_; @@ -120,8 +120,8 @@ class Mqtt { static uint16_t mqtt_message_id_; static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50; - static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, although it should be asynchronous! - static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 4; // max retries for giving up on publishing + static constexpr uint32_t MQTT_PUBLISH_WAIT = 750; // delay between sending publishes, although it should be asynchronous! + static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing static constexpr uint8_t MQTT_KEEP_ALIVE = 60; static constexpr uint32_t MQTT_RECONNECT_DELAY_MIN = 2000; // Try to reconnect in 2 seconds upon disconnection static constexpr uint32_t MQTT_RECONNECT_DELAY_STEP = 3000; // Increase the reconnect delay in 3 seconds after each failed attempt @@ -140,6 +140,7 @@ class Mqtt { void on_connect(); static char * make_topic(char * result, const std::string & topic); void publish_queue(); + void publish_all_queue(); void send_start_topic(); static void reconnect(); void init(); @@ -157,29 +158,31 @@ class Mqtt { }; static std::vector mqtt_functions_; // list of mqtt callbacks for all devices - static std::string mqtt_hostname_; - static std::string mqtt_base_; - static uint8_t mqtt_qos_; - static uint16_t mqtt_publish_fails_; - uint32_t mqtt_last_connection_; + uint32_t mqtt_last_connection_ = 0; uint32_t mqtt_reconnect_delay_ = MQTT_RECONNECT_DELAY_MIN; bool mqtt_init_ = false; - bool mqtt_connecting_; + bool mqtt_start_ = false; + bool mqtt_connecting_ = false; uint16_t mqtt_publish_time_; uint32_t last_heartbeat_ = 0; uint32_t last_mqtt_poll_ = 0; uint32_t last_publish_ = 0; + static bool force_publish_; + // settings - std::string mqtt_ip_; - std::string mqtt_user_; - std::string mqtt_password_; - bool mqtt_enabled_; - bool mqtt_heartbeat_; - uint16_t mqtt_port_; + static std::string mqtt_hostname_; + static std::string mqtt_base_; + static uint8_t mqtt_qos_; + std::string mqtt_ip_; + std::string mqtt_user_; + std::string mqtt_password_; + bool mqtt_enabled_ = true; // start off assuming we want to connect + bool mqtt_heartbeat_; + uint16_t mqtt_port_; }; } // namespace emsesp diff --git a/src/sensors.cpp b/src/sensors.cpp index c6ee33d52..62cd66a97 100644 --- a/src/sensors.cpp +++ b/src/sensors.cpp @@ -219,25 +219,31 @@ void Sensors::publish_values() { // if we're not using nested JSON, send each sensor out seperately // sensor1, sensor2 etc... - // e.g. {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"21.81"}} + // e.g. sensor_1 = {"temp":20.2} if (!mqtt_nestedjson_) { - StaticJsonDocument<100> doc; // length of payload is 60 bytes - JsonObject sensors = doc.to(); + StaticJsonDocument<20> doc; for (const auto & device : devices_) { char s[5]; - sensors["temp"] = Helpers::render_value(s, device.temperature_c_, 2); + doc["temp"] = Helpers::render_value(s, device.temperature_c_, 2); char topic[60]; // sensors{1-n} strlcpy(topic, "sensor_", 50); // create topic strlcat(topic, device.to_string().c_str(), 50); Mqtt::publish(topic, doc); + doc.clear(); // clear json doc so we can reuse the buffer again } return; } // group all sensors together - https://github.com/proddy/EMS-ESP/issues/327 - const size_t capacity = JSON_OBJECT_SIZE(num_devices * 60); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/ - DynamicJsonDocument doc(capacity); - JsonObject sensors = doc.to(); + // https://arduinojson.org/v6/assistant/ + // sensors = { + // "sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, + // "sensor2":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, + // "sensor3":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}, + // "sensor4":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"} + // } + // const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices); + DynamicJsonDocument doc(100 * num_devices); uint8_t i = 1; for (const auto & device : devices_) { @@ -245,7 +251,7 @@ void Sensors::publish_values() { strlcpy(sensorID, "sensor", 10); char s[5]; strlcat(sensorID, Helpers::itoa(s, i++), 10); - JsonObject dataSensor = sensors.createNestedObject(sensorID); + JsonObject dataSensor = doc.createNestedObject(sensorID); dataSensor["id"] = device.to_string(); dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2); } diff --git a/src/settings.cpp b/src/settings.cpp index b0321014d..31a349be9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -34,13 +34,13 @@ namespace emsesp { EMSESP_SETTINGS_SIMPLE(std::string, "", wifi_password, "", (), "") \ EMSESP_SETTINGS_CUSTOM(std::string, "", syslog_host, "", (), "") \ EMSESP_SETTINGS_ENUM(uuid::log::Level, "", syslog_level, "", (), uuid::log::Level::OFF) \ - EMSESP_SETTINGS_SIMPLE(unsigned long, "", syslog_mark_interval, "", (), 0) \ + EMSESP_SETTINGS_SIMPLE(unsigned long, "", syslog_mark_interval, "", (), EMSESP_DEFAULT_SYSLOG_INTERVAL) \ EMSESP_SETTINGS_SIMPLE(uint8_t, "", ems_bus_id, "", (), EMSESP_DEFAULT_BUS_ID) \ EMSESP_SETTINGS_SIMPLE(uint8_t, "", ems_tx_mode, "", (), EMSESP_DEFAULT_TX_MODE) \ - EMSESP_SETTINGS_SIMPLE(bool, "", ems_read_only, "", (), false) \ - EMSESP_SETTINGS_SIMPLE(bool, "", shower_timer, "", (), false) \ - EMSESP_SETTINGS_SIMPLE(bool, "", shower_alert, "", (), false) \ - EMSESP_SETTINGS_SIMPLE(uint8_t, "", master_thermostat, "", (), EMSESP_DEFAULT_NOTSET) \ + EMSESP_SETTINGS_SIMPLE(bool, "", ems_read_only, "", (), EMSESP_DEFAULT_EMS_READ_ONLY) \ + EMSESP_SETTINGS_SIMPLE(bool, "", shower_timer, "", (), EMSESP_DEFAULT_SHOWER_TIMER) \ + EMSESP_SETTINGS_SIMPLE(bool, "", shower_alert, "", (), EMSESP_DEFAULT_SHOWER_ALERT) \ + EMSESP_SETTINGS_SIMPLE(uint8_t, "", master_thermostat, "", (), EMSESP_DEFAULT_MASTER_THERMOSTAT) \ EMSESP_SETTINGS_SIMPLE(uint16_t, "", mqtt_publish_time, "", (), EMSESP_DEFAULT_MQTT_PUBLISH_TIME) \ EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_ip, "", (), "") \ EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_user, "", (), "") \ diff --git a/src/settings.h b/src/settings.h index 38edd4574..b8a025f74 100644 --- a/src/settings.h +++ b/src/settings.h @@ -41,13 +41,11 @@ #include "version.h" #include "console.h" -// defaults -#define EMSESP_DEFAULT_HOSTNAME "ems-esp2" // TODO rename to ems-esp +// default settings - these can be customized from within the application +#define EMSESP_DEFAULT_HOSTNAME "ems-esp" #define EMSESP_DEFAULT_ADMIN_PASSWORD "neo" #define EMSESP_DEFAULT_BUS_ID 0x0B #define EMSESP_DEFAULT_TX_MODE 1 -#define EMSESP_DEFAULT_NOTSET 0 - #define EMSESP_DEFAULT_MQTT_ENABLED true #define EMSESP_DEFAULT_MQTT_BASE "home" #define EMSESP_DEFAULT_MQTT_PORT 1883 @@ -55,6 +53,11 @@ #define EMSESP_DEFAULT_MQTT_RETAIN false #define EMSESP_DEFAULT_MQTT_NESTEDJSON true #define EMSESP_DEFAULT_MQTT_HEARTBEAT true +#define EMSESP_DEFAULT_EMS_READ_ONLY false +#define EMSESP_DEFAULT_SHOWER_TIMER false +#define EMSESP_DEFAULT_SHOWER_ALERT false +#define EMSESP_DEFAULT_SYSLOG_INTERVAL 0 +#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set #ifndef EMSESP_STANDALONE #define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 10 diff --git a/src/shower.cpp b/src/shower.cpp index b5035f50b..6e8dc0a82 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -115,10 +115,9 @@ void Shower::shower_alert_start() { // Publish shower data // returns true if added to MQTT queue went ok void Shower::publish_values() { - StaticJsonDocument doc; - JsonObject rootShower = doc.to(); - rootShower["shower_timer"] = shower_timer_ ? "1" : "0"; - rootShower["shower_alert"] = shower_alert_ ? "1" : "0"; + StaticJsonDocument<90> doc; + doc["shower_timer"] = shower_timer_ ? "1" : "0"; + doc["shower_alert"] = shower_alert_ ? "1" : "0"; // only publish shower duration if there is a value char s[50]; @@ -126,9 +125,9 @@ void Shower::publish_values() { char buffer[16] = {0}; strlcpy(s, Helpers::itoa(buffer, (uint8_t)((duration_ / (1000 * 60)) % 60), 10), 50); strlcat(s, " minutes and ", 50); - strlcat(s, Helpers::itoa(buffer,(uint8_t)((duration_ / 1000) % 60), 10), 50); + strlcat(s, Helpers::itoa(buffer, (uint8_t)((duration_ / 1000) % 60), 10), 50); strlcat(s, " seconds", 50); - rootShower["duration"] = s; + doc["duration"] = s; } Mqtt::publish("shower_data", doc); diff --git a/src/solar.cpp b/src/solar.cpp index a4cc20391..768569563 100644 --- a/src/solar.cpp +++ b/src/solar.cpp @@ -18,7 +18,9 @@ #include "solar.h" -MAKE_PSTR_WORD(solar) +// MAKE_PSTR_WORD(solar) +MAKE_PSTR(kwh, "kWh") +MAKE_PSTR(wh, "Wh") namespace emsesp { @@ -28,24 +30,91 @@ uuid::log::Logger Solar::logger_{F_(logger_name), uuid::log::Facility::CONSOLE}; Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { + DEBUG_LOG(F("Registering new Solar module with device ID 0x%02X"), device_id); + // telegram handlers - // register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Solar::process_XX, this, _1)); + register_telegram_type(0x0097, F("SM10Monitor"), true, std::bind(&Solar::process_SM10Monitor, this, _1)); + register_telegram_type(0x0362, F("SM100Monitor"), true, std::bind(&Solar::process_SM100Monitor, this, _1)); + register_telegram_type(0x0364, F("SM100Status"), false, std::bind(&Solar::process_SM100Status, this, _1)); + register_telegram_type(0x036A, F("SM100Status2"), false, std::bind(&Solar::process_SM100Status2, this, _1)); + register_telegram_type(0x038E, F("SM100Energy"), false, std::bind(&Solar::process_SM100Energy, this, _1)); + register_telegram_type(0x0003, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1)); + register_telegram_type(0x0001, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1)); // MQTT callbacks // register_mqtt_topic("cmd", std::bind(&Solar::cmd, this, _1)); - } +// context submenu void Solar::add_context_menu() { } // display all values into the shell console void Solar::show_values(uuid::console::Shell & shell) { EMSdevice::show_values(shell); // always call this to show header + + 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)); + + 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)); + + shell.println(); } // publish values via MQTT void Solar::publish_values() { + DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM); + + char s[10]; // for formatting strings + + if (collectorTemp_ != EMS_VALUE_SHORT_NOTSET) { + doc["collectortemp"] = (float)collectorTemp_ / 10; + } + if (bottomTemp_ != EMS_VALUE_SHORT_NOTSET) { + doc["bottomtemp"] = (float)bottomTemp_ / 10; + } + if (bottomTemp2_ != EMS_VALUE_SHORT_NOTSET) { + doc["bottomtemp2"] = (float)bottomTemp2_ / 10; + } + if (pumpModulation_ != EMS_VALUE_INT_NOTSET) { + doc["pumpmodulation"] = pumpModulation_; + } + if (pump_ != EMS_VALUE_BOOL_NOTSET) { + doc["pump"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL); + } + if (valveStatus_ != EMS_VALUE_BOOL_NOTSET) { + doc["valvestatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL); + } + if (pumpWorkMin_ != EMS_VALUE_ULONG_NOTSET) { + doc["pumpWorkMin"] = (float)pumpWorkMin_; + } + if (energyLastHour_ != EMS_VALUE_ULONG_NOTSET) { + doc["energylasthour"] = (float)energyLastHour_ / 10; + } + if (energyToday_ != EMS_VALUE_ULONG_NOTSET) { + doc["energytoday"] = energyToday_; + } + if (energyTotal_ != EMS_VALUE_ULONG_NOTSET) { + doc["energytotal"] = (float)energyTotal_ / 10; + } + +#ifdef EMSESP_DEBUG + DEBUG_LOG(F("[DEBUG] Performing a solar module publish")); +#endif + + Mqtt::publish("sm_data", doc); } // check to see if values have been updated @@ -57,4 +126,78 @@ bool Solar::updated_values() { void Solar::console_commands() { } +// SM10Monitor - type 0x97 +void Solar::process_SM10Monitor(std::shared_ptr telegram) { + telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10 + telegram->read_value(bottomTemp_, 5); // bottom temp from SM10, is *10 + telegram->read_value(pumpModulation_, 4); // modulation solar pump + telegram->read_value(pump_, 7, 1); +} + +/* + * SM100Monitor - type 0x0162 EMS+ - for SM100 and SM200 + * e.g. B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80 + * e.g, 30 00 FF 00 02 62 01 AC + * 30 00 FF 18 02 62 80 00 + * 30 00 FF 00 02 62 01 A1 - for bottom temps + * bytes 0+1 = TS1 Temperature sensor for collector + * bytes 2+3 = TS2 Temperature sensor bottom cylinder 1 + * bytes 16+17 = TS5 Temperature sensor bottom cylinder 2 + */ +void Solar::process_SM100Monitor(std::shared_ptr telegram) { + telegram->read_value(collectorTemp_, 0); // is *10 + telegram->read_value(bottomTemp_, 2); // is *10 + telegram->read_value(bottomTemp2_, 16); // is *10 +} + +/* + * SM100Status - type 0x0264 EMS+ for pump modulation - for SM100 and SM200 + * e.g. 30 00 FF 09 02 64 64 = 100% + * 30 00 FF 09 02 64 1E = 30% + */ +void Solar::process_SM100Status(std::shared_ptr telegram) { + telegram->read_value(pumpModulation_, 9); +} + +/* + * SM100Status2 - type 0x026A EMS+ for pump on/off at offset 0x0A - for SM100 and SM200 + * e.g. B0 00 FF 00 02 6A 03 03 03 03 01 03 03 03 03 03 01 03 + * byte 4 = VS2 3-way valve for cylinder 2 : test=01, on=04 and off=03 + * byte 10 = PS1 Solar circuit pump for collector array 1: test=01, on=04 and off=03 + */ +void Solar::process_SM100Status2(std::shared_ptr telegram) { + telegram->read_value(valveStatus_, 4, 2); // on if bit 2 set + telegram->read_value(pump_, 10, 2); // on if bit 2 set +} + +/* + * SM100Energy - type 0x028E EMS+ for energy readings + * e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35 + */ +void Solar::process_SM100Energy(std::shared_ptr telegram) { + telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh + telegram->read_value(energyToday_, 4); // todays in Wh + telegram->read_value(energyTotal_, 8); // total / 10 in kWh +} + +/* + * Junkers ISM1 Solar Module - type 0x0003 EMS+ for energy readings + * e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0 + */ +void Solar::process_ISM1StatusMessage(std::shared_ptr telegram) { + telegram->read_value(collectorTemp_, 4); // Collector Temperature + telegram->read_value(bottomTemp_, 6); // Temperature Bottom of Solar Boiler + telegram->read_value(energyLastHour_, 2); // Solar Energy produced in last hour - is * 10 and handled in ems-esp.cpp + telegram->read_value(pump_, 8, 0); // Solar pump on (1) or off (0) + telegram->read_value(pumpWorkMin_, 10); +} + +/* + * Junkers ISM1 Solar Module - type 0x0001 EMS+ for setting values + * e.g. 90 30 FF 06 00 01 50 + */ +void Solar::process_ISM1Set(std::shared_ptr telegram) { + telegram->read_value(setpoint_maxBottomTemp_, 6); +} + } // namespace emsesp \ No newline at end of file diff --git a/src/solar.h b/src/solar.h index e70c22aef..d8f8fcf52 100644 --- a/src/solar.h +++ b/src/solar.h @@ -45,6 +45,26 @@ class Solar : public EMSdevice { static uuid::log::Logger logger_; void console_commands(); + + int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // collector temp (TS1) + int16_t bottomTemp_ = EMS_VALUE_SHORT_NOTSET; // bottom temp (TS2) + int16_t bottomTemp2_ = EMS_VALUE_SHORT_NOTSET; // bottom temp cylinder 2 (TS5) + uint8_t pumpModulation_ = EMS_VALUE_UINT_NOTSET; // modulation solar pump + uint8_t pump_ = EMS_VALUE_BOOL_NOTSET; // pump active + uint8_t valveStatus_ = EMS_VALUE_BOOL_NOTSET; // valve status (VS2) + int16_t setpoint_maxBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // setpoint for maximum collector temp + uint32_t energyLastHour_ = EMS_VALUE_ULONG_NOTSET; + uint32_t energyToday_ = EMS_VALUE_ULONG_NOTSET; + uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET; + uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time + + void process_SM10Monitor(std::shared_ptr telegram); + void process_SM100Monitor(std::shared_ptr telegram); + void process_SM100Status(std::shared_ptr telegram); + void process_SM100Status2(std::shared_ptr telegram); + void process_SM100Energy(std::shared_ptr telegram); + void process_ISM1StatusMessage(std::shared_ptr telegram); + void process_ISM1Set(std::shared_ptr telegram); }; } // namespace emsesp diff --git a/src/system.cpp b/src/system.cpp index 61d887c2c..c9bf2a11f 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -58,7 +58,6 @@ int System::reset_counter_; // handle generic system related MQTT commands void System::mqtt_commands(const char * message) { - // convert JSON and get the command StaticJsonDocument doc; DeserializationError error = deserializeJson(doc, message); if (error) { diff --git a/src/telegram.cpp b/src/telegram.cpp index 072720356..e740aaeff 100644 --- a/src/telegram.cpp +++ b/src/telegram.cpp @@ -602,4 +602,9 @@ void TxService::post_send_query() { } } +// returns details of the last Tx message that was sent (for debugging) +std::string TxService::last_tx_to_string() const { + return Helpers::data_to_hex(telegram_last_, telegram_last_length_); +} + } // namespace emsesp diff --git a/src/telegram.h b/src/telegram.h index eefea0b63..f67d8154c 100644 --- a/src/telegram.h +++ b/src/telegram.h @@ -82,7 +82,6 @@ class Telegram { void read_value8(int16_t & param, const uint8_t index) const; void read_value(int8_t & param, const uint8_t index) const; - private: int8_t _getDataPosition(const uint8_t index) const; }; @@ -288,6 +287,9 @@ class TxService : public EMSbus { return tx_telegrams_; } + std::string last_tx_to_string() const; + + private: static constexpr uint8_t MAXIMUM_TX_RETRIES = 3; diff --git a/src/test/test_data.h b/src/test/test_data.h index 4e1d6b940..93c46fe38 100644 --- a/src/test/test_data.h +++ b/src/test/test_data.h @@ -37,6 +37,28 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) return; } + if (command == "solar") { + shell.printfln(F("Testing Solar")); + + rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); + + std::string version("1.2.3"); + add_device(0x30, 163, version, EMSdevice::Brand::BUDERUS); // SM100 + + rxservice_.loop(); + + // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 + // B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80 + uint8_t s1[] = {0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00, + 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x89}; + rxservice_.add(s1, sizeof(s1)); + rxservice_.loop(); + + shell.loop_all(); + + return; + } + if (command == "cr100") { shell.printfln(F("Testing CR100")); @@ -259,7 +281,7 @@ void EMSESP::run_test(uuid::console::Shell & shell, const std::string & command) strcpy(payload, "{\"cmd\":\"temp\",\"hc\":2,\"data\":22}"); mqtt_.incoming(topic, payload); - strcpy(topic, "home/ems-esp2/cmd"); + strcpy(topic, "home/ems-esp/cmd"); strcpy(payload, "restart"); mqtt_.incoming(topic, payload); diff --git a/src/thermostat.cpp b/src/thermostat.cpp index 0590bb80c..8b5813246 100644 --- a/src/thermostat.cpp +++ b/src/thermostat.cpp @@ -118,7 +118,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i // if we're on auto mode, register this first one we find as we may find multiple // or if its the master thermostat we defined - if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_NOTSET)) || (master_thermostat == device_id)) { + if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) { EMSESP::actual_master_thermostat(device_id); DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X (as the master)"), device_id); @@ -150,7 +150,6 @@ void Thermostat::add_context_menu() { // general MQTT command for controlling thermostat // e.g. { "cmd":"daytemp2", "data": 20 } void Thermostat::thermostat_cmd(const char * message) { - // convert JSON and get the command StaticJsonDocument doc; DeserializationError error = deserializeJson(doc, message); if (error) { @@ -296,18 +295,22 @@ void Thermostat::publish_values() { // optional, add external temp if (flags == EMS_DEVICE_FLAG_RC35) { if (dampedoutdoortemp != EMS_VALUE_INT_NOTSET) { - rootThermostat["dampedtemp"] = dampedoutdoortemp; + doc["dampedtemp"] = dampedoutdoortemp; } if (tempsensor1 != EMS_VALUE_USHORT_NOTSET) { - rootThermostat["tempsensor1"] = (float)tempsensor1 / 10; + doc["tempsensor1"] = (float)tempsensor1 / 10; } if (tempsensor2 != EMS_VALUE_USHORT_NOTSET) { - rootThermostat["tempsensor1"] = (float)tempsensor2 / 10; + doc["tempsensor1"] = (float)tempsensor2 / 10; } } + // go through all the heating circuits for (const auto & hc : heating_circuits_) { - // only send if we have an actual setpoint temp temperature values + if ((hc->setpoint_roomTemp == EMS_VALUE_SHORT_NOTSET) || (hc->curr_roomTemp == EMS_VALUE_SHORT_NOTSET)) { + break; // skip this HC as we don't have the temperature values yet + } + has_data = true; if (mqtt_nested_json_) { // create nested json for each HC @@ -367,8 +370,12 @@ void Thermostat::publish_values() { dataThermostat["designtemp"] = hc->designtemp; } - dataThermostat["mode"] = mode_tostring(hc->get_mode(flags)); - dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags)); + if (hc->mode != EMS_VALUE_UINT_NOTSET) { + dataThermostat["mode"] = mode_tostring(hc->get_mode(flags)); + } + if (hc->mode_type != EMS_VALUE_UINT_NOTSET) { + dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags)); + } // if its not nested, send immediately if (!mqtt_nested_json_) { @@ -376,18 +383,14 @@ void Thermostat::publish_values() { char s[3]; // for formatting strings strlcpy(topic, "thermostat_data", 30); strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic - char data[EMSESP_MAX_JSON_SIZE]; - serializeJson(doc, data); - Mqtt::publish(topic, data); + Mqtt::publish(topic, doc); return; } } // if we're using nested json, send all in one go if (mqtt_nested_json_ && has_data) { - char data[EMSESP_MAX_JSON_SIZE]; - serializeJson(doc, data); - Mqtt::publish("thermostat_data", data); + Mqtt::publish("thermostat_data", doc); } } @@ -1164,7 +1167,7 @@ void Thermostat::console_commands() { [](Shell & shell, const std::vector & arguments) { uint8_t value; if (arguments.empty()) { - value = EMSESP_DEFAULT_NOTSET; + value = EMSESP_DEFAULT_MASTER_THERMOSTAT; } else { value = Helpers::hextoint(arguments.front().c_str()); } diff --git a/src/uart/emsuart_esp8266.cpp b/src/uart/emsuart_esp8266.cpp index df880d28a..597b82fad 100644 --- a/src/uart/emsuart_esp8266.cpp +++ b/src/uart/emsuart_esp8266.cpp @@ -37,17 +37,15 @@ uint8_t tx_mode_ = EMS_TXMODE_DEFAULT; // Important: must not use ICACHE_FLASH_ATTR // void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) { - static uint8_t length; + static uint8_t length = 0; + static bool rx_idle_ = true; static uint8_t uart_buffer[EMS_MAXBUFFERSIZE + 2]; - // TODO check if need UART Rx idle/busy - /* // is a new buffer? if so init the thing for a new telegram - if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) { - EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy - length = 0; + if (rx_idle_) { + rx_idle_ = false; // status set to busy + length = 0; } - */ // fill IRQ buffer, by emptying Rx FIFO if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) { @@ -68,8 +66,7 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) { pEMSRxBuf->length = (length > EMS_MAXBUFFERSIZE) ? EMS_MAXBUFFERSIZE : length; os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end - length = 0; - // EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // TODO check set the status flag stating BRK has been received and we can start a new package + rx_idle_ = true; // check set the status flag stating BRK has been received and we can start a new package ETS_UART_INTR_ENABLE(); // re-enable UART interrupts system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity diff --git a/src/version.h b/src/version.h index 5bb4e46f5..41d67fa13 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "2.0.0a1" +#define EMSESP_APP_VERSION "2.0.0a2"