mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
alpha 0.2 - add solar, mixing, heatpump
This commit is contained in:
104
README.md
104
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 <device ID>
|
||||
set read_only <on | off>
|
||||
set tx_mode <n>
|
||||
show
|
||||
show devices
|
||||
show emsbus
|
||||
|
||||
mqtt
|
||||
publish
|
||||
reconnect
|
||||
set
|
||||
set base <name>
|
||||
set enabled <on | off>
|
||||
set heartbeat <on | off>
|
||||
set ip <IP address>
|
||||
set nested_json <on | off>
|
||||
set password
|
||||
set publish_time <seconds>
|
||||
set qos <n>
|
||||
set username [name]
|
||||
show
|
||||
|
||||
system
|
||||
format
|
||||
passwd
|
||||
restart
|
||||
set
|
||||
set hostname <name>
|
||||
set syslog host [IP address]
|
||||
set syslog level [level]
|
||||
set syslog mark [seconds]
|
||||
set wifi password
|
||||
set wifi ssid <name>
|
||||
show
|
||||
test <name>
|
||||
wifi disconnect
|
||||
wifi reconnect
|
||||
wifi scan
|
||||
|
||||
boiler
|
||||
change comfort <hot|eco|intelligent>
|
||||
change flowtemp <degrees>
|
||||
change wwactive <on | off>
|
||||
change wwcirculation <on | off>
|
||||
change wwonetime <on | off>
|
||||
change wwtemp <degrees>
|
||||
read <type ID>
|
||||
set
|
||||
set shower alert <on | off>
|
||||
set shower timer <on | off>
|
||||
show
|
||||
|
||||
thermostat
|
||||
change mode <mode> [heating circuit]
|
||||
change temp <degrees> [heating circuit]
|
||||
read <type ID>
|
||||
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
|
||||
|
||||
@@ -79,10 +79,17 @@ void Commands::add_command(unsigned int context,
|
||||
}
|
||||
|
||||
// added by proddy
|
||||
// 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: ");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
185
src/boiler.cpp
185
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<EMSESP_MAX_JSON_SIZE_SMALL> 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<JsonObject>();
|
||||
|
||||
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<const Telegram> telegram
|
||||
telegram->read_value(pumpMod_, 25); // or is it switchTemp ?
|
||||
}
|
||||
|
||||
/*
|
||||
* Type 0xE3 - HeatPump Monitor 1
|
||||
*/
|
||||
void Boiler::process_HPMonitor1(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(hpModulation_, 13);
|
||||
}
|
||||
|
||||
/*
|
||||
* Type 0xE5 - HeatPump Monitor 2
|
||||
*/
|
||||
void Boiler::process_HPMonitor2(std::shared_ptr<const Telegram> 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<const Telegram> 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<const Telegram> telegram) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
// 0xE9 - DHW Status
|
||||
void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
|
||||
|
||||
// UBASetPoint 0x1A
|
||||
// not yet implemented
|
||||
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
10
src/boiler.h
10
src/boiler.h
@@ -32,9 +32,6 @@
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
MAKE_PSTR(logger_name2, "boiler")
|
||||
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Boiler : public EMSdevice {
|
||||
@@ -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<const Telegram> telegram);
|
||||
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -136,7 +137,10 @@ class Boiler : public EMSdevice {
|
||||
void process_MC10Status(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void check_active();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
MAKE_PSTR_WORD(controller)
|
||||
// MAKE_PSTR_WORD(controller)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,7 +55,7 @@ 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
|
||||
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_;
|
||||
@@ -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<std::string> & arguments) { EMSESP::send_raw_telegram(arguments.front().c_str()); });
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & 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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<const Telegram> 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<const Telegram> telegram) {
|
||||
// still to implement
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -45,6 +45,9 @@ class Heatpump : public EMSdevice {
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
|
||||
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -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<const Telegram> 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<const Telegram> 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<const Telegram> telegram) {
|
||||
hc_ = 1; // fixed
|
||||
telegram->read_value(flowTemp_, 1); // isd * 10
|
||||
telegram->read_value(pumpMod_, 3);
|
||||
telegram->read_value(valveStatus_, 0);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
11
src/mixing.h
11
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<const Telegram> telegram);
|
||||
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MMStatusMessage(std::shared_ptr<const Telegram> 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
|
||||
|
||||
101
src/mqtt.cpp
101
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::MQTTFunction> 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::QueuedMqttMessage> 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.
|
||||
}
|
||||
|
||||
#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,7 +197,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -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<JsonObject>();
|
||||
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<JsonObject>();
|
||||
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<std::string> & arguments __attribute__((unused))) {
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.printfln(F("Publishing all values to MQTT"));
|
||||
EMSESP::publish_all_values();
|
||||
force_publish_ = true;
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::MQTT,
|
||||
|
||||
29
src/mqtt.h
29
src/mqtt.h
@@ -39,9 +39,9 @@
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#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<QueuedMqttMessage> 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,27 +158,29 @@ class Mqtt {
|
||||
};
|
||||
static std::vector<MQTTFunction> 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
|
||||
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_;
|
||||
bool mqtt_enabled_ = true; // start off assuming we want to connect
|
||||
bool mqtt_heartbeat_;
|
||||
uint16_t mqtt_port_;
|
||||
};
|
||||
|
||||
@@ -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<JsonObject>();
|
||||
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<JsonObject>();
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -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, "", (), "") \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
JsonObject rootShower = doc.to<JsonObject>();
|
||||
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);
|
||||
|
||||
149
src/solar.cpp
149
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<const Telegram> 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<const Telegram> 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<const Telegram> 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<const Telegram> 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<const Telegram> 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<const Telegram> 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<const Telegram> telegram) {
|
||||
telegram->read_value(setpoint_maxBottomTemp_, 6);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
20
src/solar.h
20
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<const Telegram> telegram);
|
||||
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Status(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Status2(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Energy(std::shared_ptr<const Telegram> telegram);
|
||||
void process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram);
|
||||
void process_ISM1Set(std::shared_ptr<const Telegram> telegram);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -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<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<EMSESP_MAX_JSON_SIZE_SMALL> 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;
|
||||
}
|
||||
|
||||
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<std::string> & arguments) {
|
||||
uint8_t value;
|
||||
if (arguments.empty()) {
|
||||
value = EMSESP_DEFAULT_NOTSET;
|
||||
value = EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
} else {
|
||||
value = Helpers::hextoint(arguments.front().c_str());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0a1"
|
||||
#define EMSESP_APP_VERSION "2.0.0a2"
|
||||
|
||||
Reference in New Issue
Block a user