From 3f9995340c05189c3c923c202f277f32731e9b92 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Mon, 25 Jan 2021 11:53:01 +0100 Subject: [PATCH] add mqtt base, fix mixer, fix masterthermostat, dallas fails, fix mult. roomctrl. --- CHANGELOG_LATEST.md | 4 ++ interface/src/mqtt/MqttSettingsForm.tsx | 14 ++++++- interface/src/mqtt/types.ts | 1 + interface/src/validators/index.ts | 1 + interface/src/validators/isPath.ts | 6 +++ lib/framework/MqttSettingsService.cpp | 2 + lib/framework/MqttSettingsService.h | 5 +++ src/dallassensor.cpp | 4 ++ src/dallassensor.h | 15 ++++--- src/devices/boiler.cpp | 12 ++++-- src/devices/heatpump.cpp | 8 ++-- src/devices/mixer.cpp | 55 +++++++++---------------- src/devices/mixer.h | 1 - src/devices/solar.cpp | 8 ++-- src/devices/switch.cpp | 8 ++-- src/devices/thermostat.cpp | 38 ++++++++++------- src/emsesp.cpp | 15 ++++--- src/emsesp.h | 6 ++- src/mqtt.cpp | 38 ++++++++--------- src/mqtt.h | 8 +++- src/roomcontrol.cpp | 53 +++++++++++++----------- src/roomcontrol.h | 6 +++ src/system.cpp | 20 +++++---- 23 files changed, 192 insertions(+), 136 deletions(-) create mode 100644 interface/src/validators/isPath.ts diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 8d1d545a8..a0f144814 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -9,11 +9,15 @@ See https://github.com/proddy/EMS-ESP/issues/632 ### Fixed - telegrams matched to masterthermostat 0x18 +- multible roomcontrollers + ### Changed - split `show values` in smaller packages (edited) - extended length of IP/hostname from 32 to 48 chars (#676) - check flowsensor for `tap_water_active` +- mqtt prefixed with `Base` +- count Dallas sensor fails ### Removed diff --git a/interface/src/mqtt/MqttSettingsForm.tsx b/interface/src/mqtt/MqttSettingsForm.tsx index 0b0e1aaff..a9768bda8 100644 --- a/interface/src/mqtt/MqttSettingsForm.tsx +++ b/interface/src/mqtt/MqttSettingsForm.tsx @@ -6,7 +6,7 @@ import SaveIcon from '@material-ui/icons/Save'; import MenuItem from '@material-ui/core/MenuItem'; import { RestFormProps, FormActions, FormButton, BlockFormControlLabel, PasswordValidator } from '../components'; -import { isIP, isHostname, or } from '../validators'; +import { isIP, isHostname, or, isPath } from '../validators'; import { MqttSettings } from './types'; @@ -16,6 +16,7 @@ class MqttSettingsForm extends React.Component { componentDidMount() { ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); + ValidatorForm.addValidationRule('isPath', isPath); } render() { @@ -55,6 +56,17 @@ class MqttSettingsForm extends React.Component { onChange={handleValueChange('port')} margin="normal" /> + sensors() const; + uint32_t fails() { + return sensorfails_; + } + private: static constexpr uint8_t MAX_SENSORS = 20; @@ -110,11 +114,12 @@ class DallasSensor { bool registered_ha_[MAX_SENSORS]; - int8_t scancnt_ = -3; - uint8_t firstscan_ = 0; - uint8_t dallas_gpio_ = 0; - bool parasite_ = false; - bool changed_ = false; + int8_t scancnt_ = -3; + uint8_t firstscan_ = 0; + uint8_t dallas_gpio_ = 0; + bool parasite_ = false; + bool changed_ = false; + uint32_t sensorfails_ = 0; }; } // namespace emsesp diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index cd7f42281..acc92d476 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -190,8 +190,8 @@ bool Boiler::publish_ha_config() { StaticJsonDocument doc; doc["uniq_id"] = F_(boiler); - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/boiler_data"), System::hostname().c_str()); + char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/boiler_data"), Mqtt::base().c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("Service Code"); @@ -204,8 +204,8 @@ bool Boiler::publish_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-boiler"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/boiler/config"), System::hostname().c_str()); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/boiler/config"), Mqtt::base().c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag @@ -304,7 +304,9 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr telegram) { // read the service code / installation status as appears on the display if ((telegram->message_length > 18) && (telegram->offset == 0)) { + serviceCode_[0] = (serviceCode_[0] == '~') ? 0xF0 : serviceCode_[0]; has_update(telegram->read_value(serviceCode_[0], 18)); + serviceCode_[0] = (serviceCode_[0] == 0xF0) ? '~' : serviceCode_[0]; has_update(telegram->read_value(serviceCode_[1], 19)); serviceCode_[2] = '\0'; // null terminate string } @@ -386,7 +388,9 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram // read 3 char service code / installation status as appears on the display if ((telegram->message_length > 3) && (telegram->offset == 0)) { + serviceCode_[0] = (serviceCode_[0] == '~') ? 0xF0 : serviceCode_[0]; has_update(telegram->read_value(serviceCode_[0], 1)); + serviceCode_[0] = (serviceCode_[0] == 0xF0) ? '~' : serviceCode_[0]; has_update(telegram->read_value(serviceCode_[1], 2)); has_update(telegram->read_value(serviceCode_[2], 3)); serviceCode_[3] = '\0'; diff --git a/src/devices/heatpump.cpp b/src/devices/heatpump.cpp index 6f0be9067..e7f4e9778 100644 --- a/src/devices/heatpump.cpp +++ b/src/devices/heatpump.cpp @@ -43,8 +43,8 @@ bool Heatpump::publish_ha_config() { doc["uniq_id"] = F_(heatpump); doc["ic"] = F_(iconvalve); - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heatpump_data"), System::hostname().c_str()); + char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heatpump_data"), Mqtt::base().c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("Humidity"); @@ -58,8 +58,8 @@ bool Heatpump::publish_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-heatpump"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/heatpump/config"), System::hostname().c_str()); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/heatpump/config"), Mqtt::base().c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag return true; diff --git a/src/devices/mixer.cpp b/src/devices/mixer.cpp index db135bcf3..49944cccd 100644 --- a/src/devices/mixer.cpp +++ b/src/devices/mixer.cpp @@ -49,34 +49,26 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) { register_telegram_type(0x010C, F("IPMSetMessage"), false, [&](std::shared_ptr t) { process_IPMStatusMessage(t); }); } -} - -// register values, depending on type (hc or wwc) -void Mixer::register_values(const Type type, uint16_t hc) { - if (type == Type::NONE) { - return; // already done - } - - // store the heating circuit and type - hc_ = hc + 1; - type_ = type; - - // with hc or wwc - uint8_t tag = TAG_NONE; - if (type_ == Type::HC) { - tag = TAG_HC1 + hc - 1; + // register the device values and set hc_ and type_ + if (device_id <= 0x27) { + type_ = Type::HC; + hc_ = device_id - 0x20 + 1; + uint8_t tag = TAG_HC1 + hc_ - 1; + register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, F("flowSetTemp"), F("Setpoint flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(tag, &flowTemp_, DeviceValueType::USHORT, FL_(div10), F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, F("pumpStatus"), F("Pump status"), DeviceValueUOM::NONE); + register_device_value(tag, &status_, DeviceValueType::INT, nullptr, F("status"), F("Valve status"), DeviceValueUOM::PERCENT); } else { - tag = TAG_WWC1 + hc - 1; + type_ = Type::WWC; + hc_ = device_id - 0x28 + 1; + uint8_t tag = TAG_WWC1 + hc_ - 1; + register_device_value(tag, &flowTemp_, DeviceValueType::USHORT, FL_(div10), F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES); + register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, F("pumpStatus"), F("Pump/Valve status"), DeviceValueUOM::NONE); + register_device_value(tag, &status_, DeviceValueType::INT, nullptr, F("status"), F("Current status"), DeviceValueUOM::NONE); } - - register_device_value(tag, &flowTemp_, DeviceValueType::USHORT, FL_(div10), F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES); - register_device_value(tag, &flowSetTemp_, DeviceValueType::UINT, nullptr, F("flowSetTemp"), F("Setpoint flow temperature"), DeviceValueUOM::DEGREES); - register_device_value(tag, &pumpStatus_, DeviceValueType::BOOL, nullptr, F("pumpStatus"), F("Pump/Valve status"), DeviceValueUOM::NONE); - register_device_value(tag, &status_, DeviceValueType::INT, nullptr, F("status"), F("Current status"), DeviceValueUOM::NONE); } - // publish HA config bool Mixer::publish_ha_config() { // if we don't have valid values for this HC don't add it ever again @@ -90,8 +82,8 @@ bool Mixer::publish_ha_config() { snprintf_P(uniq_id, sizeof(uniq_id), PSTR("Mixer%02X"), device_id() - 0x20 + 1); doc["uniq_id"] = uniq_id; - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), System::hostname().c_str()); + char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), Mqtt::base().c_str()); doc["stat_t"] = stat_t; char name[20]; @@ -108,11 +100,11 @@ bool Mixer::publish_ha_config() { ids.add("ems-esp-mixer"); // determine the topic, if its HC and WWC. This is determined by the incoming telegram types. - std::string topic(100, '\0'); + std::string topic(Mqtt::MQTT_TOPIC_MAX_SIZE, '\0'); if (type_ == Type::HC) { - snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_hc%d/config"), System::hostname().c_str(), hc_); + snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_hc%d/config"), Mqtt::base().c_str(), hc_); } else { - snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_wwc%d/config"), System::hostname().c_str(), hc_); // WWC + snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_wwc%d/config"), Mqtt::base().c_str(), hc_); // WWC } Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag @@ -124,7 +116,6 @@ bool Mixer::publish_ha_config() { // e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5 // A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80 void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr telegram) { - register_values(Type::HC, telegram->type_id - 0x02D7); has_update(telegram->read_value(flowTemp_, 3)); // is * 10 has_update(telegram->read_value(flowSetTemp_, 5)); has_update(telegram->read_bitvalue(pumpStatus_, 0, 0)); @@ -135,7 +126,6 @@ void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr teleg // e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28 // A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29 void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr telegram) { - register_values(Type::WWC, telegram->type_id - 0x0331); has_update(telegram->read_value(flowTemp_, 0)); // is * 10 has_update(telegram->read_bitvalue(pumpStatus_, 2, 0)); has_update(telegram->read_value(status_, 11)); // temp status @@ -145,8 +135,6 @@ void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr tele // e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54 // A1 00 FF 00 00 0C 02 04 00 01 1D 00 82 void Mixer::process_IPMStatusMessage(std::shared_ptr telegram) { - register_values(Type::HC, device_id() - 0x20); - // check if circuit is active, 0-off, 1-unmixed, 2-mixed uint8_t ismixed = 0; telegram->read_value(ismixed, 0); @@ -171,7 +159,6 @@ void Mixer::process_MMStatusMessage(std::shared_ptr telegram) { // the heating circuit is determine by which device_id it is, 0x20 - 0x23 // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 - register_values(Type::HC, device_id() - 0x20); has_update(telegram->read_value(flowTemp_, 1)); // is * 10 has_update(telegram->read_bitvalue(pumpStatus_, 3, 2)); // is 0 or 0x64 (100%), check only bit 2 @@ -185,7 +172,6 @@ void Mixer::process_MMStatusMessage(std::shared_ptr telegram) { // Mixer on a MM10 - 0xAA // e.g. Thermostat -> Mixer Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx void Mixer::process_MMConfigMessage(std::shared_ptr telegram) { - register_values(Type::HC, device_id() - 0x20); // pos 0: active FF = on // pos 1: valve runtime 0C = 120 sec in units of 10 sec } @@ -193,7 +179,6 @@ void Mixer::process_MMConfigMessage(std::shared_ptr telegram) { // Mixer on a MM10 - 0xAC // e.g. Thermostat -> Mixer Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB void Mixer::process_MMSetMessage(std::shared_ptr telegram) { - register_values(Type::HC, device_id() - 0x20); // pos 0: flowtemp setpoint 1E = 30°C // pos 1: position in % } diff --git a/src/devices/mixer.h b/src/devices/mixer.h index 64f035218..c8e6004a9 100644 --- a/src/devices/mixer.h +++ b/src/devices/mixer.h @@ -51,7 +51,6 @@ class Mixer : public EMSdevice { int8_t status_; uint8_t flowSetTemp_; - void register_values(const Type type, const uint16_t hc); Type type_ = Type::NONE; uint16_t hc_ = EMS_VALUE_USHORT_NOTSET; }; diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index e49c4d0bc..5b5aabff7 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -95,8 +95,8 @@ bool Solar::publish_ha_config() { doc["name"] = FJSON("Solar Status"); doc["uniq_id"] = F_(solar); - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/solar_data"), System::hostname().c_str()); + char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/solar_data"), Mqtt::base().c_str()); doc["stat_t"] = stat_t; doc["val_tpl"] = FJSON("{{value_json.solarPump}}"); @@ -108,8 +108,8 @@ bool Solar::publish_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-solar"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/solar/config"), System::hostname().c_str()); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/solar/config"), Mqtt::base().c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag return true; diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index f4947c0f3..318ff6977 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -49,8 +49,8 @@ bool Switch::publish_ha_config() { StaticJsonDocument doc; doc["uniq_id"] = F_(switch); - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), System::hostname().c_str()); + char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), Mqtt::base().c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("Type"); @@ -63,8 +63,8 @@ bool Switch::publish_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-switch"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/switch/config"), System::hostname().c_str()); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/switch/config"), Mqtt::base().c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag return true; diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 7ba92a47c..5e53109eb 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -151,23 +151,24 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i } } - // register device values for common values (not heating circuit) - register_device_values(); - // reserve some memory for the heating circuits (max 4 to start with) heating_circuits_.reserve(4); + strlcpy(status_, "offline", sizeof(status_)); + if (actual_master_thermostat != device_id) { LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id); return; // don't fetch data if more than 1 thermostat } - strlcpy(status_, "offline", sizeof(status_)); - // // this next section is only for the master thermostat.... // LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X (as master)"), device_id); + + // register device values for common values (not heating circuit) + register_device_values(); + add_commands(); // only for for the master-thermostat, go a query all the heating circuits. This is only done once. @@ -183,8 +184,8 @@ bool Thermostat::publish_ha_config() { StaticJsonDocument doc; doc["uniq_id"] = F_(thermostat); - char stat_t[50]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/thermostat_data"), System::hostname().c_str()); + char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/thermostat_data"), Mqtt::base().c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("Thermostat Status"); @@ -197,8 +198,8 @@ bool Thermostat::publish_ha_config() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-thermostat"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/thermostat/config"), System::hostname().c_str()); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/thermostat/config"), Mqtt::base().c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag return true; @@ -230,6 +231,7 @@ std::shared_ptr Thermostat::heating_circuit(const ui // returns pointer to the HeatingCircuit or nullptr if it can't be found // if its a new one, the object will be created and also the fetch flags set std::shared_ptr Thermostat::heating_circuit(std::shared_ptr telegram) { + // look through the Monitor and Set arrays to see if there is a match uint8_t hc_num = 0; bool toggle_ = false; @@ -362,7 +364,7 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) { doc["uniq_id"] = str2; doc["mode_cmd_t"] = str3; doc["temp_cmd_t"] = str3; - doc["~"] = System::hostname(); // ems-esp + doc["~"] = Mqtt::base(); // ems-esp doc["mode_stat_t"] = FJSON("~/thermostat_data"); doc["temp_stat_t"] = FJSON("~/thermostat_data"); doc["curr_temp_t"] = FJSON("~/thermostat_data"); @@ -397,8 +399,8 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-thermostat"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/climate/%s/thermostat_hc%d/config"), System::hostname().c_str(), hc_num); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/climate/%s/thermostat_hc%d/config"), Mqtt::base().c_str(), hc_num); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag // enable the a special "thermostat_hc" topic to take both mode strings and floats for each of the heating circuits @@ -2088,9 +2090,6 @@ void Thermostat::register_device_values() { register_device_value(TAG_NONE, &ibaLanguage_, DeviceValueType::ENUM, FL_(enum_ibaLanguage), F("ibaLanguage"), F("Language"), DeviceValueUOM::NONE); register_device_value(TAG_NONE, &ibaClockOffset_, DeviceValueType::UINT, nullptr, F("ibaClockOffset"), F("Clock offset"), DeviceValueUOM::NONE); // offset (in sec) to clock, 0xff=-1s, 0x02=2s - register_device_value(TAG_NONE, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), F("intoffset"), F("Offset int. temperature"), DeviceValueUOM::DEGREES); - register_device_value(TAG_NONE, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, F("minexttemp"), F("Min ext. temperature"), - DeviceValueUOM::DEGREES); // min ext temp for heating curve, in deg. } // RC300 and RC100 @@ -2109,9 +2108,12 @@ void Thermostat::register_device_values() { // RC30 and RC35 if (model == EMS_DEVICE_FLAG_RC35 || model == EMS_DEVICE_FLAG_RC30_1) { + register_device_value(TAG_NONE, &ibaCalIntTemperature_, DeviceValueType::INT, FL_(div2), F("intoffset"), F("Offset int. temperature"), DeviceValueUOM::DEGREES); + register_device_value(TAG_NONE, &ibaMinExtTemperature_, DeviceValueType::INT, nullptr, F("minexttemp"), F("Min ext. temperature"), + DeviceValueUOM::DEGREES); // min ext temp for heating curve, in deg. register_device_value(TAG_NONE, &tempsensor1_, DeviceValueType::USHORT, FL_(div10), F("inttemp1"), F("Temperature sensor 1"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &tempsensor2_, DeviceValueType::USHORT, FL_(div10), F("inttemp2"), F("Temperature sensor 2"), DeviceValueUOM::DEGREES); - register_device_value(TAG_NONE, &dampedoutdoortemp_, DeviceValueType::SHORT, nullptr, F("dampedtemp"), F("Damped outdoor temperature"), DeviceValueUOM::DEGREES); + register_device_value(TAG_NONE, &dampedoutdoortemp_, DeviceValueType::INT, nullptr, F("dampedtemp"), F("Damped outdoor temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_NONE, &ibaBuildingType_, DeviceValueType::ENUM, FL_(enum_ibaBuildingType2), F("building"), F("Building"), DeviceValueUOM::NONE); register_device_value(TAG_NONE, &wwMode_, DeviceValueType::ENUM, FL_(enum_wwMode2), F("wwmode"), F("Warm water mode"), DeviceValueUOM::NONE); register_device_value(TAG_NONE, &wwCircMode_, DeviceValueType::ENUM, FL_(enum_wwCircMode2), F("wwcircmode"), F("Warm water circulation mode"), DeviceValueUOM::NONE); @@ -2142,6 +2144,10 @@ void Thermostat::register_device_values_hc(std::shared_ptrsetpoint_roomTemp, DeviceValueType::SHORT, setpoint_temp_divider, F("seltemp"), F("Setpoint room temperature"), DeviceValueUOM::DEGREES); register_device_value(tag, &hc->curr_roomTemp, DeviceValueType::SHORT, curr_temp_divider, F("currtemp"), F("Current room temperature"), DeviceValueUOM::DEGREES); + if (device_id() != EMSESP::actual_master_thermostat()) { + return; + } + // special handling for Home Assistant // we create special values called hatemp and hamode, which have empty fullnames so not shown in the web or console if (Mqtt::ha_enabled()) { diff --git a/src/emsesp.cpp b/src/emsesp.cpp index afc081c39..ea33edb28 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -441,8 +441,10 @@ void EMSESP::publish_device_values(uint8_t device_type) { return; } - // for all other devices add the values to the json, without verbose mode - has_value |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE); + if ((device_type != DeviceType::THERMOSTAT) || (emsdevice->device_id() == EMSESP::actual_master_thermostat())) { + // for all other devices add the values to the json, without verbose mode + has_value |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE); + } } } @@ -862,18 +864,19 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: return true; } - Command::add_with_json(device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json); }); + Command::add_with_json(device_type, F_(info), [device_type](const char * value, const int8_t id, JsonObject & json) { return command_info(device_type, json, id); }); return true; } // export all values to info command // value and id are ignored -bool EMSESP::command_info(uint8_t device_type, JsonObject & json) { +bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t id) { bool has_value = false; for (const auto & emsdevice : emsdevices) { - if (emsdevice && (emsdevice->device_type() == device_type)) { - has_value |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // verbose mode + if (emsdevice && (emsdevice->device_type() == device_type) && + ((device_type != DeviceType::THERMOSTAT) || (emsdevice->device_id() == EMSESP::actual_master_thermostat()))) { + has_value |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, (id != 0)); // verbose mode } } diff --git a/src/emsesp.h b/src/emsesp.h index a3b384b52..243816db7 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -127,6 +127,10 @@ class EMSESP { return (!(dallassensor_.sensors().empty())); } + static uint32_t sensor_fails() { + return dallassensor_.fails(); + } + enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW, WATCH_UNKNOWN }; static void watch_id(uint16_t id); static uint16_t watch_id() { @@ -204,7 +208,7 @@ class EMSESP { static void process_version(std::shared_ptr telegram); static void publish_response(std::shared_ptr telegram); static void publish_all_loop(); - static bool command_info(uint8_t device_type, JsonObject & json); + static bool command_info(uint8_t device_type, JsonObject & json, const int8_t id); static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute static uint32_t last_fetch_; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 102e7df1f..a3cfa1739 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -25,7 +25,7 @@ namespace emsesp { AsyncMqttClient * Mqtt::mqttClient_; // static parameters we make global -std::string Mqtt::hostname_; +std::string Mqtt::mqtt_base_; uint8_t Mqtt::mqtt_qos_; bool Mqtt::mqtt_retain_; uint32_t Mqtt::publish_time_boiler_; @@ -339,12 +339,10 @@ void Mqtt::reset_mqtt() { void Mqtt::start() { mqttClient_ = EMSESP::esp8266React.getMqttClient(); - // get the hostname, which we'll use to prefix to all topics - EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { hostname_ = networkSettings.hostname.c_str(); }); - // fetch MQTT settings, to see if MQTT is enabled EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { mqtt_enabled_ = mqttSettings.enabled; + mqtt_base_ = mqttSettings.base.c_str(); if (!mqtt_enabled_) { return; // quit, not using MQTT } @@ -377,11 +375,9 @@ void Mqtt::start() { } }); - // create will_topic with the hostname prefixed. It has to be static because asyncmqttclient destroys the reference + // create will_topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference static char will_topic[MQTT_TOPIC_MAX_SIZE]; - strlcpy(will_topic, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE); - strlcat(will_topic, "/", MQTT_TOPIC_MAX_SIZE); - strlcat(will_topic, "status", MQTT_TOPIC_MAX_SIZE); + snprintf_P(will_topic, MQTT_TOPIC_MAX_SIZE, PSTR("%s/status"), mqtt_base_.c_str()); mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true mqttClient_->onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { @@ -512,7 +508,7 @@ void Mqtt::ha_status() { StaticJsonDocument doc; doc["uniq_id"] = FJSON("ems-esp-system"); - doc["~"] = System::hostname(); // default ems-esp + doc["~"] = mqtt_base_; // default ems-esp // doc["avty_t"] = FJSON("~/status"); // commented out, as it causes errors in HA sometimes // doc["json_attr_t"] = FJSON("~/heartbeat"); // store also as HA attributes doc["stat_t"] = FJSON("~/heartbeat"); @@ -527,8 +523,8 @@ void Mqtt::ha_status() { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-system"); - char topic[100]; - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/system/config"), System::hostname().c_str()); + char topic[MQTT_TOPIC_MAX_SIZE]; + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/system/config"), mqtt_base_.c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag // create the sensors @@ -545,22 +541,22 @@ void Mqtt::ha_status() { } // add sub or pub task to the queue. -// a fully-qualified topic is created by prefixing the hostname, unless it's HA +// a fully-qualified topic is created by prefixing the base, unless it's HA // returns a pointer to the message created std::shared_ptr Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain) { if (topic.empty()) { return nullptr; } - // take the topic and prefix the hostname, unless its for HA + // take the topic and prefix the base, unless its for HA std::shared_ptr message; if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0)) { // leave topic as it is message = std::make_shared(operation, topic, payload, retain); } else { - // prefix the hostname - std::string full_topic(100, '\0'); - snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), hostname_.c_str(), topic.c_str()); + // prefix the base + std::string full_topic(MQTT_TOPIC_MAX_SIZE, '\0'); + snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), mqtt_base_.c_str(), topic.c_str()); message = std::make_shared(operation, full_topic, payload, retain); } @@ -795,11 +791,11 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice // if its a boiler we use the tag char stat_t[MQTT_TOPIC_MAX_SIZE]; if (device_type == EMSdevice::DeviceType::BOILER) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), hostname_.c_str(), EMSdevice::tag_to_string(tag).c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), mqtt_base_.c_str(), EMSdevice::tag_to_string(tag).c_str()); } else if (device_type == EMSdevice::DeviceType::SYSTEM) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heartbeat"), hostname_.c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heartbeat"), mqtt_base_.c_str()); } else { - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), mqtt_base_.c_str(), device_name); } doc["stat_t"] = stat_t; @@ -823,7 +819,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice // look at the device value type if (type == DeviceValueType::BOOL) { // binary sensor - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/%s/config"), System::hostname().c_str(), uniq.c_str()); // topic + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic // how to render boolean EMSESP::webSettingsService.read([&](WebSettings & settings) { @@ -833,7 +829,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice }); } else { // normal HA sensor, not a boolean one - snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/%s/config"), System::hostname().c_str(), uniq.c_str()); // topic + snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic // unit of measure and map the HA icon if (uom != DeviceValueUOM::NONE) { diff --git a/src/mqtt.h b/src/mqtt.h index d48948b6e..69391ef8d 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -88,7 +88,7 @@ class Mqtt { enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER }; enum HA_Climate_Format : uint8_t { CURRENT = 1, SETPOINT, ZERO }; - static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength + static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = FACTORY_MQTT_MAX_TOPIC_LENGTH; // note this should really match the user setting in mqttSettings.maxTopicLength static void on_connect(); @@ -138,6 +138,10 @@ class Mqtt { return mqtt_enabled_; } + static std::string base() { + return mqtt_base_; + } + static uint32_t publish_fails() { return mqtt_publish_fails_; } @@ -249,7 +253,7 @@ class Mqtt { static uint8_t connectcount_; // settings, copied over - static std::string hostname_; + static std::string mqtt_base_; static uint8_t mqtt_qos_; static bool mqtt_retain_; static uint32_t publish_time_; diff --git a/src/roomcontrol.cpp b/src/roomcontrol.cpp index a476d2872..40df370d1 100644 --- a/src/roomcontrol.cpp +++ b/src/roomcontrol.cpp @@ -20,35 +20,42 @@ namespace emsesp { -static uint32_t rc_time_ = 0; -static int16_t remotetemp[4] = {EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET}; +// init statics +bool Roomctrl::switch_off_[HCS] = {false, false, false, false}; +uint32_t Roomctrl::rc_time_[HCS] = {0, 0, 0, 0}; +int16_t Roomctrl::remotetemp_[HCS] = {EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET}; /** * set the temperature, */ void Roomctrl::set_remotetemp(const uint8_t hc, const int16_t temp) { - if (hc > 3) { + if (hc >= HCS) { return; } - remotetemp[hc] = temp; + if (remotetemp_[hc] != EMS_VALUE_SHORT_NOTSET && temp == EMS_VALUE_SHORT_NOTSET) { + switch_off_[hc] = true; + } + remotetemp_[hc] = temp; } /** * if remote control is active send the temperature every minute */ void Roomctrl::send(const uint8_t addr) { - uint8_t hc_ = addr - ADDR; + uint8_t hc = addr - ADDR; // check address, reply only on addresses 0x18..0x1B - if (hc_ > 3) { + if (hc >= HCS) { return; } // no reply if the temperature is not set - if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) { + if (remotetemp_[hc] == EMS_VALUE_SHORT_NOTSET && !switch_off_[hc]) { return; } - if (uuid::get_uptime() - rc_time_ > SEND_INTERVAL) { // send every minute - rc_time_ = uuid::get_uptime(); // use EMS-ESP's millis() to prevent overhead - temperature(addr, 0x00); // send to all + + if (uuid::get_uptime() - rc_time_[hc] > SEND_INTERVAL) { // send every minute + rc_time_[hc] = uuid::get_uptime(); // use EMS-ESP's millis() to prevent overhead + temperature(addr, 0x00); // send to all + switch_off_[hc] = false; } else { // acknowledge every poll, otherwise the master shows error A22-816 EMSuart::send_poll(addr); @@ -59,14 +66,14 @@ void Roomctrl::send(const uint8_t addr) { * check if there is a message for the remote room controller */ void Roomctrl::check(const uint8_t addr, const uint8_t * data) { - uint8_t hc_ = (addr & 0x7F) - ADDR; + uint8_t hc = (addr & 0x7F) - ADDR; // check address, reply only on addresses 0x18..0x1B - if (hc_ > 3) { + if (hc >= HCS) { return; } // no reply if the temperature is not set - if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) { + if (remotetemp_[hc] == EMS_VALUE_SHORT_NOTSET) { return; } // reply to writes with write nack byte @@ -78,7 +85,7 @@ void Roomctrl::check(const uint8_t addr, const uint8_t * data) { // empty message back if temperature not set or unknown message type if (data[2] == 0x02) { version(addr, data[0]); - } else if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) { + } else if (remotetemp_[hc] == EMS_VALUE_SHORT_NOTSET) { unknown(addr, data[0], data[2], data[3]); } else if (data[2] == 0xAF && data[3] == 0) { temperature(addr, data[0]); @@ -121,15 +128,15 @@ void Roomctrl::unknown(uint8_t addr, uint8_t dst, uint8_t type, uint8_t offset) */ void Roomctrl::temperature(uint8_t addr, uint8_t dst) { uint8_t data[10]; - uint8_t hc_ = addr - ADDR; - data[0] = addr; - data[1] = dst; - data[2] = 0xAF; - data[3] = 0; - data[4] = (uint8_t)(remotetemp[hc_] >> 8); - data[5] = (uint8_t)(remotetemp[hc_] & 0xFF); - data[6] = 0; - data[7] = EMSbus::calculate_crc(data, 7); // apppend CRC + uint8_t hc = addr - ADDR; + data[0] = addr; + data[1] = dst; + data[2] = 0xAF; + data[3] = 0; + data[4] = (uint8_t)(remotetemp_[hc] >> 8); + data[5] = (uint8_t)(remotetemp_[hc] & 0xFF); + data[6] = 0; + data[7] = EMSbus::calculate_crc(data, 7); // apppend CRC EMSuart::transmit(data, 8); } /** diff --git a/src/roomcontrol.h b/src/roomcontrol.h index b332f9164..8d034b8d9 100644 --- a/src/roomcontrol.h +++ b/src/roomcontrol.h @@ -32,11 +32,17 @@ class Roomctrl { private: static constexpr uint8_t ADDR = 0x18; static constexpr uint32_t SEND_INTERVAL = 60000; // 1 minute + static constexpr uint8_t HCS = 4; // max 4 heating circuits static void version(uint8_t addr, uint8_t dst); static void unknown(uint8_t addr, uint8_t dst, uint8_t type, uint8_t offset); static void temperature(uint8_t addr, uint8_t dst); static void nack_write(); + + static bool switch_off_[HCS]; + static uint32_t rc_time_[HCS]; + static int16_t remotetemp_[HCS]; + }; } // namespace emsesp diff --git a/src/system.cpp b/src/system.cpp index 322f50c57..df8c77251 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -392,15 +392,16 @@ void System::send_heartbeat() { doc["status"] = FJSON("disconnected"); } - doc["rssi"] = rssi; - doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); - doc["uptime_sec"] = uuid::get_uptime_sec(); - doc["mqttfails"] = Mqtt::publish_fails(); - doc["rxsent"] = EMSESP::rxservice_.telegram_count(); - doc["rxfails"] = EMSESP::rxservice_.telegram_error_count(); - doc["txread"] = EMSESP::txservice_.telegram_read_count(); - doc["txwrite"] = EMSESP::txservice_.telegram_write_count(); - doc["txfails"] = EMSESP::txservice_.telegram_fail_count(); + doc["rssi"] = rssi; + doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3); + doc["uptime_sec"] = uuid::get_uptime_sec(); + doc["mqttfails"] = Mqtt::publish_fails(); + doc["rxsent"] = EMSESP::rxservice_.telegram_count(); + doc["rxfails"] = EMSESP::rxservice_.telegram_error_count(); + doc["txread"] = EMSESP::txservice_.telegram_read_count(); + doc["txwrite"] = EMSESP::txservice_.telegram_write_count(); + doc["txfails"] = EMSESP::txservice_.telegram_fail_count(); + doc["dallasfails"] = EMSESP::sensor_fails(); #ifndef EMSESP_STANDALONE doc["freemem"] = ESP.getFreeHeap(); #endif @@ -1079,6 +1080,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json node["tx line quality"] = EMSESP::txservice_.quality(); node["#MQTT publish fails"] = Mqtt::publish_fails(); node["#dallas sensors"] = EMSESP::sensor_devices().size(); + node["#dallas fails"] = EMSESP::sensor_fails(); } JsonArray devices2 = json.createNestedArray("Devices");