Merge pull request #619 from MichaelDvP/dev

Some changes
This commit is contained in:
Proddy
2020-11-18 19:09:42 +01:00
committed by GitHub
18 changed files with 393 additions and 99 deletions

View File

@@ -8,6 +8,8 @@
- expose test framework via api (#611)
- SysLog has enable/disable flag in WebUI
- Add solar configuration telegrams (#616) [thanks @hpanther]
- `log trace` shows decoded telegrams, `watch unknown` for only unknown telegrams
- WM10 switch
### Fixed
- mixer IPM pumpstatus

View File

@@ -231,6 +231,7 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
<MenuItem value={5}>NOTICE</MenuItem>
<MenuItem value={6}>INFO</MenuItem>
<MenuItem value={7}>DEBUG</MenuItem>
<MenuItem value={8}>ALL</MenuItem>
</SelectValidator>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:65535']}

View File

@@ -262,9 +262,9 @@ void Shell::loop_normal() {
} else if (esc_ == 20) { // F9
set_command_str(F("call system info"));
} else if (esc_ == 21) { // F10
set_command_str(F("call system report"));
set_command_str(F("call system settings"));
} else if (esc_ == 23) { // F11
line_buffer_ = read_flash_string(F("send telegram \"0B \""));
line_buffer_ = read_flash_string(F("call send \"0B \""));
cursor_ = 1;
} else if (esc_ == 24) { // F12
set_command_str(F("log debug; watch raw"));
@@ -395,9 +395,9 @@ void Shell::loop_delay() {
function_copy(*this);
if (running()) {
display_prompt();
}
// if (running()) {
// display_prompt();
// }
idle_time_ = uuid::get_uptime_ms();
}
@@ -425,9 +425,9 @@ void Shell::loop_blocking() {
stop();
}
if (running()) {
display_prompt();
}
// if (running()) {
// display_prompt();
// }
idle_time_ = uuid::get_uptime_ms();
}
@@ -568,9 +568,9 @@ void Shell::process_password(bool completed) {
function_copy(*this, completed, line_buffer_);
line_buffer_.clear();
if (running()) {
display_prompt();
}
// if (running()) {
// display_prompt();
// }
}
void Shell::invoke_command(const std::string & line) {

View File

@@ -307,11 +307,16 @@ void EMSESPShell::add_console_commands() {
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
} else if (arguments[0] == read_flash_string(F_(off))) {
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
} else if (emsesp::EMSESP::watch() == EMSESP::WATCH_OFF) {
shell.printfln(F_(invalid_watch));
return;
} else if (arguments[0] == read_flash_string(F_(unknown))) {
emsesp::EMSESP::watch(EMSESP::WATCH_UNKNOWN); // unknown
watch_id = WATCH_ID_NONE;
} else {
watch_id = Helpers::hextoint(arguments[0].c_str());
if ((emsesp::EMSESP::watch() == EMSESP::WATCH_OFF) && watch_id) {
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
} else if ((emsesp::EMSESP::watch() == EMSESP::WATCH_UNKNOWN) || !watch_id) {
return;
}
}
if (arguments.size() == 2) {
@@ -335,8 +340,10 @@ void EMSESPShell::add_console_commands() {
if (watch == EMSESP::WATCH_ON) {
shell.printfln(F("Watching incoming telegrams, displayed in decoded format"));
} else {
} else if (watch == EMSESP::WATCH_RAW) {
shell.printfln(F("Watching incoming telegrams, displayed as raw bytes")); // WATCH_RAW
} else {
shell.printfln(F("Watching unknown telegrams")); // WATCH_UNKNOWN
}
watch_id = emsesp::EMSESP::watch_id();
@@ -380,7 +387,7 @@ void EMSESPShell::add_console_commands() {
return;
}
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_DYN);
JsonObject json = doc.to<JsonObject>();
bool ok = false;

View File

@@ -70,6 +70,10 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_mqtt_cmd(F("boilhystoff"), [&](const char * value, const int8_t id) { return set_hyst_off(value, id); });
register_mqtt_cmd(F("burnperiod"), [&](const char * value, const int8_t id) { return set_burn_period(value, id); });
register_mqtt_cmd(F("pumpdelay"), [&](const char * value, const int8_t id) { return set_pump_delay(value, id); });
register_mqtt_cmd(F("reset"), [&](const char * value, const int8_t id) { return set_reset(value, id); });
EMSESP::send_read_request(0x10, device_id); // read last errorcode on start (only published on errors)
EMSESP::send_read_request(0x11, device_id); // read last errorcode on start (only published on errors)
}
// create the config topics for Home Assistant MQTT Discovery
@@ -80,7 +84,7 @@ void Boiler::register_mqtt_ha_config() {
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = F("Service Code");
doc["uniq_id"] = F("boiler");
doc["ic"] = F("mdi:home-thermometer-outline");
@@ -187,12 +191,12 @@ void Boiler::device_info_web(JsonArray & root) {
if (!export_values_main(json)) {
return; // empty
}
export_values_ww(json); // append ww values
print_value_json(root, F("heatingActive"), nullptr, F_(heatingActive), nullptr, json);
print_value_json(root, F("tapwaterActive"), nullptr, F_(tapwaterActive), nullptr, json);
print_value_json(root, F("serviceCode"), nullptr, F_(serviceCode), nullptr, json);
print_value_json(root, F("serviceCodeNumber"), nullptr, F_(serviceCodeNumber), nullptr, json);
print_value_json(root, F("lastCode"), nullptr, F_(lastCode), nullptr, json);
print_value_json(root, F("selFlowTemp"), nullptr, F_(selFlowTemp), F_(degrees), json);
print_value_json(root, F("selBurnPow"), nullptr, F_(selBurnPow), F_(percent), json);
print_value_json(root, F("curBurnPow"), nullptr, F_(curBurnPow), F_(percent), json);
@@ -226,6 +230,10 @@ void Boiler::device_info_web(JsonArray & root) {
print_value_json(root, F("heatWorkMin"), nullptr, F_(heatWorkMin), F_(min), json);
print_value_json(root, F("UBAuptime"), nullptr, F_(UBAuptime), F_(min), json);
doc.clear();
if (!export_values_ww(json)) { // append ww values
return;
}
// ww
print_value_json(root, F("wWSelTemp"), nullptr, F_(wWSelTemp), F_(degrees), json);
print_value_json(root, F("wWSetTemp"), nullptr, F_(wWSetTemp), F_(degrees), json);
@@ -617,6 +625,10 @@ bool Boiler::export_values_main(JsonObject & json) {
json["serviceCodeNumber"] = serviceCodeNumber_;
}
if (lastCode_[0] != '\0') {
json["lastCode"] = lastCode_;
}
return (json.size());
}
@@ -673,51 +685,30 @@ void Boiler::show_values(uuid::console::Shell & shell) {
if (!export_values_main(json)) {
return; // empty
}
export_values_ww(json); // append ww values
// doc.shrinkToFit();
print_value_json(shell, F("heatingActive"), nullptr, F_(heatingActive), nullptr, json);
print_value_json(shell, F("tapwaterActive"), nullptr, F_(tapwaterActive), nullptr, json);
print_value_json(shell, F("serviceCode"), nullptr, F_(serviceCode), nullptr, json);
print_value_json(shell, F("serviceCodeNumber"), nullptr, F_(serviceCodeNumber), nullptr, json);
print_value_json(shell, F("wWSelTemp"), nullptr, F_(wWSelTemp), F_(degrees), json);
print_value_json(shell, F("wWSetTemp"), nullptr, F_(wWSetTemp), F_(degrees), json);
print_value_json(shell, F("wWDisinfectionTemp"), nullptr, F_(wWDisinfectionTemp), F_(degrees), json);
print_value_json(shell, F("lastCode"), nullptr, F_(lastCode), nullptr, json);
print_value_json(shell, F("selFlowTemp"), nullptr, F_(selFlowTemp), F_(degrees), json);
print_value_json(shell, F("selBurnPow"), nullptr, F_(selBurnPow), F_(percent), json);
print_value_json(shell, F("curBurnPow"), nullptr, F_(curBurnPow), F_(percent), json);
print_value_json(shell, F("pumpMod"), nullptr, F_(pumpMod), F_(percent), json);
print_value_json(shell, F("pumpMod2"), nullptr, F_(pumpMod2), F_(percent), json);
print_value_json(shell, F("wWType"), nullptr, F_(wWType), nullptr, json);
print_value_json(shell, F("wWChargeType"), nullptr, F_(wWChargeType), nullptr, json);
print_value_json(shell, F("wWCircPump"), nullptr, F_(wWCircPump), nullptr, json);
print_value_json(shell, F("wWCircPumpMode"), nullptr, F_(wWCircPumpMode), nullptr, json);
print_value_json(shell, F("wWCirc"), nullptr, F_(wWCirc), nullptr, json);
print_value_json(shell, F("outdoorTemp"), nullptr, F_(outdoorTemp), F_(degrees), json);
print_value_json(shell, F("wWCurTemp"), nullptr, F_(wWCurTemp), F_(degrees), json);
print_value_json(shell, F("wWCurTemp2"), nullptr, F_(wWCurTemp2), F_(degrees), json);
print_value_json(shell, F("wWCurFlow"), nullptr, F_(wWCurFlow), F("l/min"), json);
print_value_json(shell, F("curFlowTemp"), nullptr, F_(curFlowTemp), F_(degrees), json);
print_value_json(shell, F("retTemp"), nullptr, F_(retTemp), F_(degrees), json);
print_value_json(shell, F("switchTemp"), nullptr, F_(switchTemp), F_(degrees), json);
print_value_json(shell, F("sysPress"), nullptr, F_(sysPress), nullptr, json);
print_value_json(shell, F("boilTemp"), nullptr, F_(boilTemp), F_(degrees), json);
print_value_json(shell, F("wwStorageTemp1"), nullptr, F_(wwStorageTemp1), F_(degrees), json);
print_value_json(shell, F("wwStorageTemp2"), nullptr, F_(wwStorageTemp2), F_(degrees), json);
print_value_json(shell, F("exhaustTemp"), nullptr, F_(exhaustTemp), F_(degrees), json);
print_value_json(shell, F("wWActivated"), nullptr, F_(wWActivated), nullptr, json);
print_value_json(shell, F("wWOneTime"), nullptr, F_(wWOneTime), nullptr, json);
print_value_json(shell, F("wWDisinfecting"), nullptr, F_(wWDisinfecting), nullptr, json);
print_value_json(shell, F("wWCharging"), nullptr, F_(wWCharging), nullptr, json);
print_value_json(shell, F("wWRecharging"), nullptr, F_(wWRecharging), nullptr, json);
print_value_json(shell, F("wWTempOK"), nullptr, F_(wWTempOK), nullptr, json);
print_value_json(shell, F("wWActive"), nullptr, F_(wWActive), nullptr, json);
print_value_json(shell, F("burnGas"), nullptr, F_(burnGas), nullptr, json);
print_value_json(shell, F("flameCurr"), nullptr, F_(flameCurr), F_(uA), json);
print_value_json(shell, F("heatPump"), nullptr, F_(heatPump), nullptr, json);
print_value_json(shell, F("fanWork"), nullptr, F_(fanWork), nullptr, json);
print_value_json(shell, F("ignWork"), nullptr, F_(ignWork), nullptr, json);
print_value_json(shell, F("wWHeat"), nullptr, F_(wWHeat), nullptr, json);
print_value_json(shell, F("heatingActivated"), nullptr, F_(heatingActivated), nullptr, json);
print_value_json(shell, F("heatingTemp"), nullptr, F_(heatingTemp), F_(degrees), json);
print_value_json(shell, F("pumpModMax"), nullptr, F_(pumpModMax), F_(percent), json);
@@ -729,18 +720,9 @@ void Boiler::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("boilHystOn"), nullptr, F_(boilHystOn), F_(degrees), json);
print_value_json(shell, F("boilHystOff"), nullptr, F_(boilHystOff), F_(degrees), json);
print_value_json(shell, F("setFlowTemp"), nullptr, F_(setFlowTemp), F_(degrees), json);
print_value_json(shell, F("wWSetPumpPower"), nullptr, F_(wWSetPumpPower), F_(percent), json);
print_value_json(shell, F("wwMixTemperature"), nullptr, F_(wwMixTemperature), F_(degrees), json);
print_value_json(shell, F("wwBufferTemperature"), nullptr, F_(wwBufferTemperature), F_(degrees), json);
print_value_json(shell, F("wWStarts"), nullptr, F_(wWStarts), nullptr, json);
print_value_json(shell, F("wWWorkM"), nullptr, F_(wWWorkM), nullptr, json);
print_value_json(shell, F("setBurnPow"), nullptr, F_(setBurnPow), F_(percent), json);
print_value_json(shell, F("burnStarts"), nullptr, F_(burnStarts), nullptr, json);
if (Helpers::hasValue(wWWorkM_)) {
shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60);
}
if (Helpers::hasValue(burnWorkMin_)) {
shell.printfln(F(" Total burner operating time: %d days %d hours %d minutes"), burnWorkMin_ / 1440, (burnWorkMin_ % 1440) / 60, burnWorkMin_ % 60);
}
@@ -751,6 +733,41 @@ 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);
}
doc.clear();
if (!export_values_ww(json)) { // append ww values
shell.println();
return;
}
print_value_json(shell, F("wWSelTemp"), nullptr, F_(wWSelTemp), F_(degrees), json);
print_value_json(shell, F("wWSetTemp"), nullptr, F_(wWSetTemp), F_(degrees), json);
print_value_json(shell, F("wWDisinfectionTemp"), nullptr, F_(wWDisinfectionTemp), F_(degrees), json);
print_value_json(shell, F("wWType"), nullptr, F_(wWType), nullptr, json);
print_value_json(shell, F("wWChargeType"), nullptr, F_(wWChargeType), nullptr, json);
print_value_json(shell, F("wWCircPump"), nullptr, F_(wWCircPump), nullptr, json);
print_value_json(shell, F("wWCircPumpMode"), nullptr, F_(wWCircPumpMode), nullptr, json);
print_value_json(shell, F("wWCirc"), nullptr, F_(wWCirc), nullptr, json);
print_value_json(shell, F("wWCurTemp"), nullptr, F_(wWCurTemp), F_(degrees), json);
print_value_json(shell, F("wWCurTemp2"), nullptr, F_(wWCurTemp2), F_(degrees), json);
print_value_json(shell, F("wWCurFlow"), nullptr, F_(wWCurFlow), F("l/min"), json);
print_value_json(shell, F("wwStorageTemp1"), nullptr, F_(wwStorageTemp1), F_(degrees), json);
print_value_json(shell, F("wwStorageTemp2"), nullptr, F_(wwStorageTemp2), F_(degrees), json);
print_value_json(shell, F("wWActivated"), nullptr, F_(wWActivated), nullptr, json);
print_value_json(shell, F("wWOneTime"), nullptr, F_(wWOneTime), nullptr, json);
print_value_json(shell, F("wWDisinfecting"), nullptr, F_(wWDisinfecting), nullptr, json);
print_value_json(shell, F("wWCharging"), nullptr, F_(wWCharging), nullptr, json);
print_value_json(shell, F("wWRecharging"), nullptr, F_(wWRecharging), nullptr, json);
print_value_json(shell, F("wWTempOK"), nullptr, F_(wWTempOK), nullptr, json);
print_value_json(shell, F("wWActive"), nullptr, F_(wWActive), nullptr, json);
print_value_json(shell, F("wWHeat"), nullptr, F_(wWHeat), nullptr, json);
print_value_json(shell, F("wWSetPumpPower"), nullptr, F_(wWSetPumpPower), F_(percent), json);
print_value_json(shell, F("wwMixTemperature"), nullptr, F_(wwMixTemperature), F_(degrees), json);
print_value_json(shell, F("wwBufferTemperature"), nullptr, F_(wwBufferTemperature), F_(degrees), json);
print_value_json(shell, F("wWStarts"), nullptr, F_(wWStarts), nullptr, json);
if (Helpers::hasValue(wWWorkM_)) {
shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60);
}
shell.println();
}
@@ -1022,10 +1039,28 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegr
// first byte: Maintenance due (0 = no, 3 = yes, due to operating hours, 8 = yes, due to date)
}
// 0x10, 0x11, 0x12
// not yet implemented
// 0x10, 0x11
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
// data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr
if (telegram->message_data[4] & 0x80) { // valid date
char code[3];
uint16_t codeNo;
code[0] = telegram->message_data[0];
code[1] = telegram->message_data[1];
code[2] = 0;
telegram->read_value(codeNo, 2);
uint16_t year = (telegram->message_data[4] & 0x7F) + 2000;
uint8_t month = telegram->message_data[5];
uint8_t day = telegram->message_data[7];
uint8_t hour = telegram->message_data[6];
uint8_t min = telegram->message_data[8];
uint32_t date = (year - 2000) * 535680UL + month * 44640UL + day * 1440UL + hour * 60 + min;
// store only the newest code from telegrams 10 and 11
if (date > lastCodeDate_) {
snprintf_P(lastCode_, sizeof(lastCode_), PSTR("%s(%d) %02d.%02d.%d %02d:%02d"), code, codeNo, day, month, year, hour, min);
lastCodeDate_ = date;
}
}
}
#pragma GCC diagnostic pop
@@ -1385,5 +1420,19 @@ bool Boiler::set_warmwater_circulation_mode(const char * value, const int8_t id)
return true;
}
// Reset command
bool Boiler::set_reset(const char * value, const int8_t id) {
bool v = false;
if (!Helpers::value2bool(value, v)) {
return false;
}
if (v == false) {
return false;
}
LOG_INFO(F("restarting boiler"));
write_command(0x05, 0x08, 0xFF);
return true;
}
} // namespace emsesp

View File

@@ -99,6 +99,8 @@ class Boiler : public EMSdevice {
char serviceCode_[3] = {'\0'}; // 2 character status/service code
uint16_t serviceCodeNumber_ = EMS_VALUE_USHORT_NOTSET; // error/service code
uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag
char lastCode_[30] = {'\0'};
uint32_t lastCodeDate_ = 0;
// UBAMonitorSlow - 0x19 on EMS1
int16_t outdoorTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature
@@ -188,6 +190,7 @@ class Boiler : public EMSdevice {
bool set_hyst_off(const char * value, const int8_t id);
bool set_burn_period(const char * value, const int8_t id);
bool set_pump_delay(const char * value, const int8_t id);
bool set_reset(const char * value, const int8_t id);
};
} // namespace emsesp

View File

@@ -97,7 +97,7 @@ void Heatpump::register_mqtt_ha_config() {
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(iconheatpump);

View File

@@ -166,7 +166,7 @@ void Mixer::register_mqtt_ha_config() {
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
char name[20];
snprintf_P(name, sizeof(name), PSTR("Mixer %02X"), device_id() - 0x20 + 1);

View File

@@ -159,7 +159,7 @@ void Solar::register_mqtt_ha_config() {
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(solar);
doc["ic"] = F_(iconthermostat);

View File

@@ -28,28 +28,134 @@ uuid::log::Logger Switch::logger_ {
Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
LOG_DEBUG(F("Adding new Switch with device ID 0x%02X"), device_id);
register_telegram_type(0x9C, F("WM10MonitorMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10MonitorMessage(t); });
register_telegram_type(0x9B, F("WM10SetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10SetMessage(t); });
}
void Switch::device_info_web(JsonArray & root) {
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (export_values(json)) {
print_value_json(root, F("activated"), nullptr, F_(activated), nullptr, json);
print_value_json(root, F("flowTemp"), nullptr, F_(flowTemp), F_(degrees), json);
print_value_json(root, F("status"), nullptr, F_(status), nullptr, json);
}
}
// display all values into the shell console
void Switch::show_values(uuid::console::Shell & shell) {
// EMSdevice::show_values(shell); // always call this to show header
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (export_values(json)) {
print_value_json(shell, F("activated"), nullptr, F_(activated), nullptr, json);
print_value_json(shell, F("flowTemp"), F_(2spaces), F_(flowTemp), F_(degrees), json);
print_value_json(shell, F("status"), nullptr, F_(status), nullptr, json);
}
}
// publish values via MQTT
void Switch::publish_values(JsonObject & json, bool force) {
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values(json_data)) {
Mqtt::publish(F("switch_data"), doc.as<JsonObject>());
}
}
// export values to JSON
bool Switch::export_values(JsonObject & json) {
if (Helpers::hasValue(flowTemp_)) {
char s[7];
json["activated"] = Helpers::render_value(s, activated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(flowTemp_)) {
json["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowTemp_)) {
json["status"] = status_;
}
return true;
}
// check to see if values have been updated
bool Switch::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// publish config topic for HA MQTT Discovery
void Switch::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(flowTemp_)) {
return;
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
char name[10];
snprintf_P(name, sizeof(name), PSTR("Switch"));
doc["name"] = name;
char uniq_id[10];
snprintf_P(uniq_id, sizeof(uniq_id), PSTR("switch"));
doc["uniq_id"] = uniq_id;
doc["ic"] = F("mdi:home-thermometer-outline");
char stat_t[50];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), System::hostname().c_str());
doc["stat_t"] = stat_t;
doc["val_tpl"] = F("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = F("EMS-ESP Switch");
dev["sw"] = EMSESP_APP_VERSION;
dev["mf"] = this->brand_to_string();
dev["mdl"] = this->name();
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-switch");
Mqtt::publish_retain(F("homeassistant/sensor/ems-esp/switch/config"), doc.as<JsonObject>(), true); // publish the config payload with retain flag
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(activated), this->device_type(), "activated", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(flowTemp), this->device_type(), "flowTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(status), this->device_type(), "status", nullptr, nullptr);
mqtt_ha_config_ = true; // done
}
// message 0x9B switch on/off
void Switch::process_WM10SetMessage(std::shared_ptr<const Telegram> telegram){
changed_ |= telegram->read_value(activated_, 0);
}
// message 0x9C holds flowtemp and unknown statusvalue
void Switch::process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram){
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
changed_ |= telegram->read_value(status_, 2);
}
} // namespace emsesp

View File

@@ -25,6 +25,7 @@
#include <uuid/log.h>
#include "emsdevice.h"
#include "emsesp.h"
#include "telegram.h"
#include "helpers.h"
#include "mqtt.h"
@@ -43,6 +44,17 @@ class Switch : public EMSdevice {
private:
static uuid::log::Logger logger_;
void process_WM10SetMessage(std::shared_ptr<const Telegram> telegram);
void process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram);
void register_mqtt_ha_config();
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
uint8_t status_ = EMS_VALUE_UINT_NOTSET;
uint8_t activated_ = EMS_VALUE_BOOL_NOTSET;
bool changed_ = false;
bool mqtt_ha_config_ = false; // for HA MQTT Discovery
};
} // namespace emsesp

View File

@@ -48,6 +48,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
register_telegram_type(EMS_TYPE_RCOutdoorTemp, F("RCOutdoorTemp"), false, [&](std::shared_ptr<const Telegram> t) { process_RCOutdoorTemp(t); });
register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, [&](std::shared_ptr<const Telegram> t) { process_RCTime(t); });
register_telegram_type(0xA2, F("RCError"), false, [&](std::shared_ptr<const Telegram> t) { process_RCError(t); });
register_telegram_type(0x12, F("RCErrorMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_RCErrorMessage(t); });
}
// RC10
if (model == EMSdevice::EMS_DEVICE_FLAG_RC10) {
@@ -176,15 +177,17 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
for (uint8_t i = 0; i < curve_typeids.size(); i++) {
EMSESP::send_read_request(curve_typeids[i], device_id);
}
EMSESP::send_read_request(0x12, device_id); // read last error (only published on errors)
}
// prepare data for Web UI
void Thermostat::device_info_web(JsonArray & root) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc_main;
JsonObject json_main = doc_main.to<JsonObject>();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json_main = doc.to<JsonObject>();
if (export_values_main(json_main)) {
print_value_json(root, F("time"), nullptr, F_(time), nullptr, json_main);
print_value_json(root, F("errorcode"), nullptr, F_(error), nullptr, json_main);
print_value_json(root, F("lastcode"), nullptr, F_(lastCode), nullptr, json_main);
print_value_json(root, F("display"), nullptr, F_(display), nullptr, json_main);
print_value_json(root, F("language"), nullptr, F_(language), nullptr, json_main);
print_value_json(root, F("offsetclock"), nullptr, F_(offsetclock), nullptr, json_main);
@@ -202,9 +205,8 @@ void Thermostat::device_info_web(JsonArray & root) {
print_value_json(root, F("wwextra1"), nullptr, F_(wwextra1), nullptr, json_main);
print_value_json(root, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main);
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc_hc;
JsonObject json_hc = doc_hc.to<JsonObject>();
doc.clear();
JsonObject json_hc = doc.to<JsonObject>();
if (export_values_hc(Mqtt::Format::NESTED, json_hc)) {
// display for each active heating circuit
@@ -267,11 +269,12 @@ bool Thermostat::export_values(JsonObject & json) {
void Thermostat::show_values(uuid::console::Shell & shell) {
EMSdevice::show_values(shell); // always call this to show header
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc_main;
JsonObject json_main = doc_main.to<JsonObject>();
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json_main = doc.to<JsonObject>();
if (export_values_main(json_main)) {
print_value_json(shell, F("time"), nullptr, F_(time), nullptr, json_main);
print_value_json(shell, F("errorcode"), nullptr, F_(error), nullptr, json_main);
print_value_json(shell, F("lastcode"), nullptr, F_(lastCode), nullptr, json_main);
print_value_json(shell, F("display"), nullptr, F_(display), nullptr, json_main);
print_value_json(shell, F("language"), nullptr, F_(language), nullptr, json_main);
print_value_json(shell, F("offsetclock"), nullptr, F_(offsetclock), nullptr, json_main);
@@ -290,8 +293,8 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
print_value_json(shell, F("wwcircmode"), nullptr, F_(wwcircmode), nullptr, json_main);
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc_hc;
JsonObject json_hc = doc_hc.to<JsonObject>();
doc.clear(); // reuse the doc
JsonObject json_hc = doc.to<JsonObject>();
// e.g. {"hc1":{"seltemp":849.4,"currtemp":819.2,"mode":"unknown","modetype":"day"},"hc2":{"seltemp":875.1,"currtemp":409.6,"mode":"unknown","modetype":"day"},"hc3":{"seltemp":0,"currtemp":0,"mode":"unknown","modetype":"day"}}
if (export_values_hc(Mqtt::Format::NESTED, json_hc)) {
@@ -336,6 +339,20 @@ void Thermostat::publish_values(JsonObject & json, bool force) {
if (EMSESP::actual_master_thermostat() != this->device_id()) {
return;
}
// if MQTT is in single mode send out the main data to the thermostat_data topic
if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values_main(json_data)) {
Mqtt::publish(F("thermostat_data"), json_data);
json_data.clear();
}
// this function will also have published each of the heating circuits
export_values_hc(Mqtt::mqtt_format(), json_data);
return;
}
// see if we have already registered this with HA MQTT Discovery, if not send the config
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!ha_config(force)) {
@@ -347,19 +364,12 @@ void Thermostat::publish_values(JsonObject & json, bool force) {
JsonObject json_data = doc.to<JsonObject>();
bool has_data = false;
// if MQTT is in single mode send out the main data to the thermostat_data topic
has_data |= export_values_main(json_data);
if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE && has_data) {
Mqtt::publish(F("thermostat_data"), json_data);
json_data.clear();
}
// get the thermostat data.
// if we're in Single mode this function will also have published each of the heating circuits
has_data |= export_values_main(json_data);
has_data |= export_values_hc(Mqtt::mqtt_format(), json_data);
// if we're in HA or CUSTOM, send out the complete topic with all the data
if (Mqtt::mqtt_format() != Mqtt::Format::SINGLE && has_data) {
// we're in HA or CUSTOM, send out the complete topic with all the data
if (has_data) {
Mqtt::publish(F("thermostat_data"), json_data);
}
}
@@ -376,6 +386,10 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
rootThermostat["errorcode"] = errorCode_;
}
if (lastCode_[0] != '\0') {
rootThermostat["lastcode"] = lastCode_;
}
if (model == EMSdevice::EMS_DEVICE_FLAG_RC30_1) {
// Display
if (Helpers::hasValue(ibaMainDisplay_)) {
@@ -498,9 +512,13 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
// Warm Water circulation mode
if (Helpers::hasValue(wwCircMode_)) {
char s[7];
char s[10];
if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) {
rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto"), F("own_prog")}, wwCircMode_);
} else {
rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwCircMode_);
}
}
return (rootThermostat.size());
}
@@ -836,7 +854,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
// publish config topic for HA MQTT Discovery for main thermostat values
// homeassistant/sensor/ems-esp/thermostat/config
void Thermostat::register_mqtt_ha_config() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
doc["uniq_id"] = F("thermostat");
doc["ic"] = F("mdi:home-thermometer-outline");
@@ -873,6 +891,7 @@ void Thermostat::register_mqtt_ha_config() {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(floordrytemp), this->device_type(), "floordrytemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), this->device_type(), "wwmode", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwtemp), this->device_type(), "wwtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwcircmode), this->device_type(), "wwcircmode", nullptr, nullptr);
}
if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) {
@@ -881,8 +900,6 @@ void Thermostat::register_mqtt_ha_config() {
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(building), this->device_type(), "building", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(minexttemp), this->device_type(), "minexttemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwmode), this->device_type(), "wwmode", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwtemp), this->device_type(), "wwtemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwtemplow), this->device_type(), "wwtemplow", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(wwcircmode), this->device_type(), "wwcircmode", nullptr, nullptr);
}
}
@@ -1185,14 +1202,18 @@ std::string Thermostat::mode_tostring(uint8_t mode) {
// 0xA8 - for reading the mode from the RC20 thermostat (0x17)
void Thermostat::process_RC20Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->mode, 23);
}
// type 0xAE - data from the RC20 thermostat (0x17)
void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_bitvalue(hc->mode_type, 0, 7); // day/night MSB 7th bit is day
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10
@@ -1202,20 +1223,27 @@ void Thermostat::process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram)
// see https://github.com/proddy/EMS-ESP/issues/334#issuecomment-611698259
void Thermostat::process_RC20Set_2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->mode, 3);
}
// 0xAF - for reading the roomtemperature from the RC20/ES72 thermostat (0x18, 0x19, ..)
void Thermostat::process_RC20Remote(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->curr_roomTemp, 0);
}
// type 0xB1 - data from the RC10 thermostat (0x17)
void Thermostat::process_RC10Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
}
@@ -1232,6 +1260,9 @@ void Thermostat::process_RC10Set(std::shared_ptr<const Telegram> telegram) {
// type 0x0165, ff
void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->daytemp, 17); // is * 2
changed_ |= telegram->read_value(hc->nighttemp, 16); // is * 2
changed_ |= telegram->read_value(hc->nofrosttemp, 15); // is * 2
@@ -1239,6 +1270,9 @@ void Thermostat::process_JunkersSet(std::shared_ptr<const Telegram> telegram) {
// type 0x0179, ff
void Thermostat::process_JunkersSet2(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->daytemp, 7); // is * 2
changed_ |= telegram->read_value(hc->nighttemp, 6); // is * 2
changed_ |= telegram->read_value(hc->nofrosttemp, 5); // is * 2
@@ -1257,7 +1291,9 @@ void Thermostat::process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram)
// 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long
void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 2); // is * 10
}
@@ -1265,7 +1301,9 @@ void Thermostat::process_RC20Monitor(std::shared_ptr<const Telegram> telegram) {
// type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long
void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->curr_roomTemp, 8); // is * 100
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 10); // is * 100
}
@@ -1297,7 +1335,9 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
}
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->curr_roomTemp, 4); // value is * 10
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2); // value is * 10
@@ -1308,7 +1348,9 @@ void Thermostat::process_JunkersMonitor(std::shared_ptr<const Telegram> telegram
// type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10
void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->curr_roomTemp, 0); // is * 10
changed_ |= telegram->read_bitvalue(hc->mode_type, 10, 1);
@@ -1329,7 +1371,9 @@ void Thermostat::process_RC300Monitor(std::shared_ptr<const Telegram> telegram)
// type 0x02B9 EMS+ for reading from RC300/RC310 thermostat
void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
// NOTE when setting the room temp we pick from two values, hopefully one is correct!
// manual is position 10
// comfort is position 2, there are 3 levels in pos 3, 2, 1
@@ -1349,6 +1393,9 @@ void Thermostat::process_RC300Set(std::shared_ptr<const Telegram> telegram) {
// types 0x2AF ff
void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->roominfluence, 0);
changed_ |= telegram->read_value(hc->offsettemp, 2);
changed_ |= telegram->read_value(hc->summertemp, 6);
@@ -1364,6 +1411,9 @@ void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
// types 0x29B ff
void Thermostat::process_RC300Curve(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->heatingtype, 1); // 1=radiator, 2=convector, 3=floor
changed_ |= telegram->read_value(hc->nofrosttemp, 6);
if (hc->heatingtype < 3) {
@@ -1382,8 +1432,9 @@ void Thermostat::process_RC300WWtemp(std::shared_ptr<const Telegram> telegram) {
// type 02F5
void Thermostat::process_RC300WWmode(std::shared_ptr<const Telegram> telegram) {
// circulation pump see: https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20der%20Bedieneinheit%20RC310.md
// changed_ |= telegram->read_value(wwCircMode_, 1); // 0=off, FF=on
changed_ |= telegram->read_value(wwCircPump_, 1); // FF=off, 0=on ?
changed_ |= telegram->read_value(wwMode_, 2); // 0=off, 1=low, 2=high, 3=auto, 4=own prog
changed_ |= telegram->read_value(wwCircMode_, 3); // 0=off, 1=on, 2=auto, 4=own?
}
// types 0x31D and 0x31E
@@ -1419,7 +1470,9 @@ void Thermostat::process_RC300Floordry(std::shared_ptr<const Telegram> telegram)
// type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long
void Thermostat::process_RC30Monitor(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 1, 1); // is * 2, force as single byte
changed_ |= telegram->read_value(hc->curr_roomTemp, 2);
}
@@ -1427,7 +1480,9 @@ void Thermostat::process_RC30Monitor(std::shared_ptr<const Telegram> telegram) {
// type 0xA7 - for reading the mode from the RC30 thermostat (0x10)
void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->mode, 23);
}
@@ -1441,7 +1496,9 @@ void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
}
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->setpoint_roomTemp, 2, 1); // is * 2, force to single byte, is 0 in summermode
changed_ |= telegram->read_value(hc->curr_roomTemp, 3); // is * 10 - or 0x7D00 if thermostat is mounted on boiler
@@ -1460,7 +1517,9 @@ void Thermostat::process_RC35Set(std::shared_ptr<const Telegram> telegram) {
}
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(telegram);
if (hc == nullptr) {
return;
}
changed_ |= telegram->read_value(hc->heatingtype, 0); // 0- off, 1-radiator, 2-convector, 3-floor
changed_ |= telegram->read_value(hc->nighttemp, 1); // is * 2
changed_ |= telegram->read_value(hc->daytemp, 2); // is * 2
@@ -1537,6 +1596,24 @@ void Thermostat::process_RCError(std::shared_ptr<const Telegram> telegram) {
snprintf_P(&errorCode_[0], errorCode_.capacity() + 1, PSTR("%s(%d)"), buf, errorNumber_);
}
// 0x12
void Thermostat::process_RCErrorMessage(std::shared_ptr<const Telegram> telegram) {
// data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr
if (telegram->message_data[4] & 0x80) { // valid date
char code[3];
uint16_t codeNo;
code[0] = telegram->message_data[0];
code[1] = telegram->message_data[1];
code[2] = 0;
telegram->read_value(codeNo, 2);
uint16_t year = (telegram->message_data[4] & 0x7F) + 2000;
uint8_t month = telegram->message_data[5];
uint8_t day = telegram->message_data[7];
uint8_t hour = telegram->message_data[6];
uint8_t min = telegram->message_data[8];
snprintf_P(lastCode_, sizeof(lastCode_), PSTR("%s(%d) %02d.%02d.%d %02d:%02d"), code, codeNo, day, month, year, hour, min);
}
}
// 0xA5 - Set minimum external temperature
bool Thermostat::set_minexttemp(const char * value, const int8_t id) {
@@ -1731,6 +1808,15 @@ bool Thermostat::set_wwonetime(const char * value, const int8_t id) {
// sets the thermostat ww circulation working mode, where mode is a string
bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
uint8_t set = 0xFF;
if ((this->model() == EMS_DEVICE_FLAG_RC300) || (this->model() == EMS_DEVICE_FLAG_RC100)) {
if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto"), F("own")})) {
LOG_WARNING(F("Set warm water circulation mode: Invalid mode"));
return false;
}
LOG_INFO(F("Setting warm water circulation mode to %s"), value);
write_command(0x02F5, 3, set, 0x02F5);
return true;
}
if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) {
LOG_WARNING(F("Set warm water circulation mode: Invalid mode"));
return false;
@@ -1860,7 +1946,7 @@ bool Thermostat::set_datetime(const char * value, const int8_t id) {
// converts string mode to HeatingCircuit::Mode
bool Thermostat::set_mode(const char * value, const int8_t id) {
// quit if its numerical, as it could be mistaken as a temperature value
if (value[0] <= 'A') {
if (value[0] < 'A') {
return false;
}
@@ -2431,6 +2517,7 @@ void Thermostat::add_commands() {
register_mqtt_cmd(F("wwtemp"), [&](const char * value, const int8_t id) { return set_wwtemp(value, id); });
register_mqtt_cmd(F("wwtemplow"), [&](const char * value, const int8_t id) { return set_wwtemplow(value, id); });
register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_wwonetime(value, id); });
register_mqtt_cmd(F("wwcircmode"), [&](const char * value, const int8_t id) { return set_wwcircmode(value, id); });
register_mqtt_cmd(F("building"), [&](const char * value, const int8_t id) { return set_building(value, id); });
register_mqtt_cmd(F("nofrosttemp"), [&](const char * value, const int8_t id) { return set_nofrosttemp(value, id); });
register_mqtt_cmd(F("designtemp"), [&](const char * value, const int8_t id) { return set_designtemp(value, id); });

View File

@@ -170,6 +170,7 @@ class Thermostat : public EMSdevice {
uint8_t ibaClockOffset_ = EMS_VALUE_UINT_NOTSET; // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
uint16_t errorNumber_ = EMS_VALUE_USHORT_NOTSET;
char lastCode_[30] = {'\0'};
int8_t dampedoutdoortemp_ = EMS_VALUE_INT_NOTSET;
uint16_t tempsensor1_ = EMS_VALUE_USHORT_NOTSET;
uint16_t tempsensor2_ = EMS_VALUE_USHORT_NOTSET;
@@ -180,6 +181,7 @@ class Thermostat : public EMSdevice {
uint8_t wwExtra1_ = EMS_VALUE_UINT_NOTSET; // wwExtra active for wwSystem 1
uint8_t wwExtra2_ = EMS_VALUE_UINT_NOTSET;
uint8_t wwMode_ = EMS_VALUE_UINT_NOTSET;
uint8_t wwCircPump_ = EMS_VALUE_UINT_NOTSET;
uint8_t wwCircMode_ = EMS_VALUE_UINT_NOTSET;
uint8_t wwTemp_ = EMS_VALUE_UINT_NOTSET;
uint8_t wwTempLow_ = EMS_VALUE_UINT_NOTSET;
@@ -274,6 +276,7 @@ class Thermostat : public EMSdevice {
void process_IBASettings(std::shared_ptr<const Telegram> telegram);
void process_RCTime(std::shared_ptr<const Telegram> telegram);
void process_RCError(std::shared_ptr<const Telegram> telegram);
void process_RCErrorMessage(std::shared_ptr<const Telegram> telegram);
void process_RC35wwSettings(std::shared_ptr<const Telegram> telegram);
void process_RC35Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC35Set(std::shared_ptr<const Telegram> telegram);

View File

@@ -602,7 +602,11 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if ((watch_id_ == WATCH_ID_NONE) || (telegram->type_id == watch_id_)
|| ((watch_id_ < 0x80) && ((telegram->src == watch_id_) || (telegram->dest == watch_id_)))) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
} else {
LOG_TRACE(pretty_telegram(telegram).c_str());
}
} else {
LOG_TRACE(pretty_telegram(telegram).c_str());
}
// only process broadcast telegrams or ones sent to us on request
@@ -644,6 +648,9 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
if (!found) {
LOG_DEBUG(F("No telegram type handler found for ID 0x%02X (src 0x%02X)"), telegram->type_id, telegram->src);
if (watch() == WATCH_UNKNOWN) {
LOG_NOTICE(pretty_telegram(telegram).c_str());
}
}
return found;

View File

@@ -116,7 +116,7 @@ class EMSESP {
return (!(dallassensor_.sensors().empty()));
}
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW };
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN };
static void watch_id(uint16_t id);
static uint16_t watch_id() {
return watch_id_;

View File

@@ -111,7 +111,7 @@ MAKE_PSTR(deep_optional, "[deep]")
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_optional, "[off | on | raw]")
MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]")
MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(data_mandatory, "\"XX XX ...\"")
MAKE_PSTR(percent, "%")
@@ -139,6 +139,7 @@ MAKE_PSTR(heatingActive, "Heating active")
MAKE_PSTR(tapwaterActive, "Warm water/DHW active")
MAKE_PSTR(serviceCode, "Service code")
MAKE_PSTR(serviceCodeNumber, "Service code number")
MAKE_PSTR(lastCode, "Last error")
MAKE_PSTR(wWSelTemp, "Warm water selected temperature")
MAKE_PSTR(wWSetTemp, "Warm water set temperature")
MAKE_PSTR(wWDisinfectionTemp, "Warm water disinfection temperature")
@@ -275,6 +276,11 @@ MAKE_PSTR(modetype, "Mode type")
MAKE_PSTR(airHumidity, "Relative air humidity")
MAKE_PSTR(dewTemperature, "Dew point temperature")
// other
MAKE_PSTR(activated, "Switch activated")
MAKE_PSTR(status, "Switch status")
// Home Assistant icons
MAKE_PSTR(icontemperature, "mdi:coolant-temperature")
MAKE_PSTR(iconpercent, "mdi:sine-wave")

View File

@@ -789,8 +789,11 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
}
new_name[0] = toupper(new_name[0]); // capitalize first letter
#if defined(ESP32)
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
#else
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_HA_CONFIG);
#endif
doc["name"] = new_name;
doc["uniq_id"] = uniq;
if (uom != nullptr) {
@@ -805,9 +808,14 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
JsonArray ids = dev.createNestedArray("ids");
ids.add(ha_device);
#if defined(ESP32)
// queue MQTT publish
publish_retain(topic, doc.as<JsonObject>(), true);
#else
// convert json to string and publish immediately with retain forced to true
std::string payload_text;
serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text.c_str());
if (!packet_id) {
LOG_ERROR(F("Failed to publish topic %s"), topic);
@@ -820,6 +828,7 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
}
delay(50); // enough time to send the short message out
#endif
}
} // namespace emsesp

View File

@@ -39,7 +39,7 @@
using uuid::console::Shell;
#define EMSESP_MAX_JSON_SIZE_HA_CONFIG 384 // for small HA config payloads
#define EMSESP_MAX_JSON_SIZE_SMALL 384 // for smaller json docs when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_SMALL 256 // for smaller json docs when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, when using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data. Using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_DYN 2048 // for large json docs from web. Using DynamicJsonDocument
@@ -176,6 +176,8 @@ class Mqtt {
#if defined(EMSESP_STANDALONE)
static constexpr size_t MAX_MQTT_MESSAGES = 70; // size of queue
#elif defined(ESP32)
static constexpr size_t MAX_MQTT_MESSAGES = 100; // size of queue
#else
static constexpr size_t MAX_MQTT_MESSAGES = 20; // size of queue
#endif