From 4bf821fbd5b6fc26dbcaa50f4ccd25a64e4ef59b Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:01:41 +0100 Subject: [PATCH 1/9] use mqtt queue for ESP32 --- src/mqtt.cpp | 11 ++++++++++- src/mqtt.h | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 3a3acfb3f..ea6dec345 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -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 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(), 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 diff --git a/src/mqtt.h b/src/mqtt.h index 41e2899f6..7d2decdc3 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -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 From db06e2c3b928524c2db125436b3a9d48d733ca25 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:07:16 +0100 Subject: [PATCH 2/9] Log trace shows telegrams, watch FF for unknown telegrams --- CHANGELOG_LATEST.md | 1 + interface/src/project/EMSESPSettingsController.tsx | 1 + src/emsesp.cpp | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 159c758d5..795276cca 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -8,6 +8,7 @@ - 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 0xFF for unknown telegrams ### Fixed - mixer IPM pumpstatus diff --git a/interface/src/project/EMSESPSettingsController.tsx b/interface/src/project/EMSESPSettingsController.tsx index e09808ff7..1bf8b113c 100644 --- a/interface/src/project/EMSESPSettingsController.tsx +++ b/interface/src/project/EMSESPSettingsController.tsx @@ -231,6 +231,7 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps) NOTICE INFO DEBUG + ALL 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 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_ON) && (watch_id_ == 0xFF)) { + LOG_NOTICE(pretty_telegram(telegram).c_str()); + } } return found; From bfeca075baf235ebe987c1dab6a21f278b77e5c5 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:12:02 +0100 Subject: [PATCH 3/9] Boiler: add reset, add last code, json output 2 parts (main/ww) --- src/devices/boiler.cpp | 118 +++++++++++++++++++++++++++++------------ src/devices/boiler.h | 3 ++ src/locale_EN.h | 6 +++ 3 files changed, 93 insertions(+), 34 deletions(-) diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index f6b6b3910..dd2115998 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -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 doc; + StaticJsonDocument 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,29 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr telegr // first byte: Maintenance due (0 = no, 3 = yes, due to operating hours, 8 = yes, due to date) } -// 0x10, 0x11, 0x12 +// 0x10, 0x11 // not yet implemented void Boiler::process_UBAErrorMessage(std::shared_ptr 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 +1421,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("reseting boiler")); + write_command(0x05, 0x08, 0xFF); + + return true; +} } // namespace emsesp diff --git a/src/devices/boiler.h b/src/devices/boiler.h index 173882080..96ea37c43 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -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 diff --git a/src/locale_EN.h b/src/locale_EN.h index 380b4ac0f..51eec04d1 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -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") From d41cb6eda2ea7dd9dc2a1199c71e57fb46b4d184 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:17:03 +0100 Subject: [PATCH 4/9] Thermostat: json sizes/split, add circpump, add last Error, check HC --- src/devices/thermostat.cpp | 167 ++++++++++++++++++++++++++++--------- src/devices/thermostat.h | 3 + 2 files changed, 130 insertions(+), 40 deletions(-) diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 2819eb5af..57b5c4f4d 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -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 t) { process_RCOutdoorTemp(t); }); register_telegram_type(EMS_TYPE_RCTime, F("RCTime"), false, [&](std::shared_ptr t) { process_RCTime(t); }); register_telegram_type(0xA2, F("RCError"), false, [&](std::shared_ptr t) { process_RCError(t); }); + register_telegram_type(0x12, F("RCErrorMessage"), false, [&](std::shared_ptr 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 doc_main; - JsonObject json_main = doc_main.to(); + StaticJsonDocument doc; + JsonObject json_main = doc.to(); 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 doc_hc; - JsonObject json_hc = doc_hc.to(); + doc.clear(); + JsonObject json_hc = doc.to(); 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 doc_main; - JsonObject json_main = doc_main.to(); + StaticJsonDocument doc; + JsonObject json_main = doc.to(); 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 doc_hc; - JsonObject json_hc = doc_hc.to(); + doc.clear(); // reuse the doc + JsonObject json_hc = doc.to(); // 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 doc; + JsonObject json_data = doc.to(); + 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(); 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,8 +512,12 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) { // Warm Water circulation mode if (Helpers::hasValue(wwCircMode_)) { - char s[7]; - rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwCircMode_); + 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::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 doc; + StaticJsonDocument 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 telegram) { std::shared_ptr 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 telegram) { std::shared_ptr 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 telegram) // see https://github.com/proddy/EMS-ESP/issues/334#issuecomment-611698259 void Thermostat::process_RC20Set_2(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { std::shared_ptr 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 telegram) { std::shared_ptr 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 telegram) { // type 0x0165, ff void Thermostat::process_JunkersSet(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { // type 0x0179, ff void Thermostat::process_JunkersSet2(std::shared_ptr telegram) { std::shared_ptr 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 telegram) // 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long void Thermostat::process_RC20Monitor(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { // type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long void Thermostat::process_EasyMonitor(std::shared_ptr telegram) { std::shared_ptr 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 telegram } std::shared_ptr 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 telegram // type 0x02A5 - data from the Nefit RC1010/3000 thermostat (0x18) and RC300/310s on 0x10 void Thermostat::process_RC300Monitor(std::shared_ptr telegram) { std::shared_ptr 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 telegram) // type 0x02B9 EMS+ for reading from RC300/RC310 thermostat void Thermostat::process_RC300Set(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { // types 0x2AF ff void Thermostat::process_RC300Summer(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { // types 0x29B ff void Thermostat::process_RC300Curve(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { // type 02F5 void Thermostat::process_RC300WWmode(std::shared_ptr 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(wwMode_, 2); // 0=off, 1=low, 2=high, 3=auto, 4=own prog + 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 telegram) // type 0x41 - data from the RC30 thermostat(0x10) - 14 bytes long void Thermostat::process_RC30Monitor(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { // type 0xA7 - for reading the mode from the RC30 thermostat (0x10) void Thermostat::process_RC30Set(std::shared_ptr telegram) { std::shared_ptr 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 telegram) { } std::shared_ptr 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 telegram) { } std::shared_ptr 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 telegram) { snprintf_P(&errorCode_[0], errorCode_.capacity() + 1, PSTR("%s(%d)"), buf, errorNumber_); } +// 0x12 +void Thermostat::process_RCErrorMessage(std::shared_ptr 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); }); diff --git a/src/devices/thermostat.h b/src/devices/thermostat.h index e180f7d1c..a098e4f26 100644 --- a/src/devices/thermostat.h +++ b/src/devices/thermostat.h @@ -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 telegram); void process_RCTime(std::shared_ptr telegram); void process_RCError(std::shared_ptr telegram); + void process_RCErrorMessage(std::shared_ptr telegram); void process_RC35wwSettings(std::shared_ptr telegram); void process_RC35Monitor(std::shared_ptr telegram); void process_RC35Set(std::shared_ptr telegram); From cf3728759ec375c2bc991bae45260180addb0fe7 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:17:57 +0100 Subject: [PATCH 5/9] devices, json-ha-config --- src/devices/heatpump.cpp | 2 +- src/devices/mixer.cpp | 2 +- src/devices/solar.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/devices/heatpump.cpp b/src/devices/heatpump.cpp index 463a4cd7d..195d0da97 100644 --- a/src/devices/heatpump.cpp +++ b/src/devices/heatpump.cpp @@ -97,7 +97,7 @@ void Heatpump::register_mqtt_ha_config() { } // Create the Master device - StaticJsonDocument doc; + StaticJsonDocument doc; doc["name"] = F_(EMSESP); doc["uniq_id"] = F_(heatpump); doc["ic"] = F_(iconheatpump); diff --git a/src/devices/mixer.cpp b/src/devices/mixer.cpp index c95f1a06b..db96d2c0a 100644 --- a/src/devices/mixer.cpp +++ b/src/devices/mixer.cpp @@ -166,7 +166,7 @@ void Mixer::register_mqtt_ha_config() { } // Create the Master device - StaticJsonDocument doc; + StaticJsonDocument doc; char name[20]; snprintf_P(name, sizeof(name), PSTR("Mixer %02X"), device_id() - 0x20 + 1); diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index 438e179c8..c341e01c1 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -159,7 +159,7 @@ void Solar::register_mqtt_ha_config() { } // Create the Master device - StaticJsonDocument doc; + StaticJsonDocument doc; doc["name"] = F_(EMSESP); doc["uniq_id"] = F_(solar); doc["ic"] = F_(iconthermostat); From d585d7e4af29025cbcfb751b370c19c97a2dd072 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:18:57 +0100 Subject: [PATCH 6/9] console: watch input more flexible, json size --- src/console.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/console.cpp b/src/console.cpp index 6de5e153c..50edf08c0 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -307,11 +307,11 @@ 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 { watch_id = Helpers::hextoint(arguments[0].c_str()); + if ((emsesp::EMSESP::watch() == EMSESP::WATCH_OFF) && watch_id) { + emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on + } } if (arguments.size() == 2) { @@ -380,7 +380,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(); bool ok = false; From 9e6b2088aee69ca82948635a62a364f0db366d9d Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:41:26 +0100 Subject: [PATCH 7/9] addWM10 switch --- CHANGELOG_LATEST.md | 1 + src/devices/switch.cpp | 106 +++++++++++++++++++++++++++++++++++++++++ src/devices/switch.h | 12 +++++ 3 files changed, 119 insertions(+) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 795276cca..eb5e011a2 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -9,6 +9,7 @@ - SysLog has enable/disable flag in WebUI - Add solar configuration telegrams (#616) [thanks @hpanther] - log trace shows decoded telegrams, watch 0xFF for unknown telegrams +- WM10 switch ### Fixed - mixer IPM pumpstatus diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index f39d617d7..4faaff5cc 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -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 t) { process_WM10MonitorMessage(t); }); + register_telegram_type(0x9B, F("WM10SetMessage"), false, [&](std::shared_ptr t) { process_WM10SetMessage(t); }); } void Switch::device_info_web(JsonArray & root) { + // fetch the values into a JSON document + StaticJsonDocument doc; + JsonObject json = doc.to(); + 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 doc; + JsonObject json = doc.to(); + 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 doc; + JsonObject json_data = doc.to(); + if (export_values(json_data)) { + Mqtt::publish(F("switch_data"), doc.as()); + } } // 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 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(), true); // publish the config payload with retain flag + Mqtt::register_mqtt_ha_sensor(PSTR("switch"), nullptr, F_(flowTemp), this->device_type(), "activated", nullptr, nullptr); + Mqtt::register_mqtt_ha_sensor(PSTR("switch"), nullptr, F_(flowTemp), this->device_type(), "flowTemp", F_(degrees), F_(icontemperature)); + Mqtt::register_mqtt_ha_sensor(PSTR("switch"), nullptr, F_(flowTemp), this->device_type(), "status", nullptr, nullptr); + + mqtt_ha_config_ = true; // done +} + +// message 0x9B switch on/off +void Switch::process_WM10SetMessage(std::shared_ptr telegram){ + changed_ |= telegram->read_value(activated_, 0); +} + +// message 0x9C holds flowtemp and unknown statusvalue +void Switch::process_WM10MonitorMessage(std::shared_ptr telegram){ + changed_ |= telegram->read_value(flowTemp_, 0); // is * 10 + changed_ |= telegram->read_value(status_, 2); +} + } // namespace emsesp \ No newline at end of file diff --git a/src/devices/switch.h b/src/devices/switch.h index e9fb33800..e7afaba2e 100644 --- a/src/devices/switch.h +++ b/src/devices/switch.h @@ -25,6 +25,7 @@ #include #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 telegram); + void process_WM10MonitorMessage(std::shared_ptr 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 From 0b19ea7a0d9205e462ddd4486609425f38965693 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 10:54:30 +0100 Subject: [PATCH 8/9] shell F-commands, remove double prompt output. --- lib/uuid-console/src/shell.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index 73258d398..c0c086080 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -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(); } @@ -496,7 +496,7 @@ void Shell::maximum_command_line_length(size_t length) { void Shell::process_command() { if (line_buffer_.empty()) { println(); - return; + return; } line_old_ = line_buffer_; while (!line_buffer_.empty()) { @@ -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) { From 780be4ff5794c84cc9303aa3555d6e9347dfa434 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Wed, 18 Nov 2020 13:33:04 +0100 Subject: [PATCH 9/9] watch unknown and fixes (see #619 comments) --- CHANGELOG_LATEST.md | 2 +- src/console.cpp | 9 ++++++++- src/devices/boiler.cpp | 3 +-- src/devices/switch.cpp | 6 +++--- src/emsesp.cpp | 2 +- src/emsesp.h | 2 +- src/locale_EN.h | 2 +- 7 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index eb5e011a2..867525787 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -8,7 +8,7 @@ - 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 0xFF for unknown telegrams +- `log trace` shows decoded telegrams, `watch unknown` for only unknown telegrams - WM10 switch ### Fixed diff --git a/src/console.cpp b/src/console.cpp index 50edf08c0..5b86d17b1 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -307,10 +307,15 @@ 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 (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; } } @@ -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(); diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index dd2115998..ee3f92e1d 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -1040,7 +1040,6 @@ void Boiler::process_UBAMaintenanceStatus(std::shared_ptr telegr } // 0x10, 0x11 -// not yet implemented void Boiler::process_UBAErrorMessage(std::shared_ptr telegram) { // data: displaycode(2), errornumber(2), year, month, hour, day, minute, duration(2), src-addr if (telegram->message_data[4] & 0x80) { // valid date @@ -1430,7 +1429,7 @@ bool Boiler::set_reset(const char * value, const int8_t id) { if (v == false) { return false; } - LOG_INFO(F("reseting boiler")); + LOG_INFO(F("restarting boiler")); write_command(0x05, 0x08, 0xFF); return true; diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index 4faaff5cc..62ad9904f 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -140,9 +140,9 @@ void Switch::register_mqtt_ha_config() { ids.add("ems-esp-switch"); Mqtt::publish_retain(F("homeassistant/sensor/ems-esp/switch/config"), doc.as(), true); // publish the config payload with retain flag - Mqtt::register_mqtt_ha_sensor(PSTR("switch"), nullptr, F_(flowTemp), this->device_type(), "activated", nullptr, nullptr); - Mqtt::register_mqtt_ha_sensor(PSTR("switch"), nullptr, F_(flowTemp), this->device_type(), "flowTemp", F_(degrees), F_(icontemperature)); - Mqtt::register_mqtt_ha_sensor(PSTR("switch"), nullptr, F_(flowTemp), this->device_type(), "status", nullptr, nullptr); + 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 } diff --git a/src/emsesp.cpp b/src/emsesp.cpp index f3ff5e3ab..c2bc01e9c 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -648,7 +648,7 @@ bool EMSESP::process_telegram(std::shared_ptr 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_ON) && (watch_id_ == 0xFF)) { + if (watch() == WATCH_UNKNOWN) { LOG_NOTICE(pretty_telegram(telegram).c_str()); } } diff --git a/src/emsesp.h b/src/emsesp.h index eaa887456..711f9e8cb 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -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_; diff --git a/src/locale_EN.h b/src/locale_EN.h index 51eec04d1..d86122de9 100644 --- a/src/locale_EN.h +++ b/src/locale_EN.h @@ -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, "%")