diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 1010b36a0..aad562ed1 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -13,7 +13,7 @@ - Support for chunked MQTT payloads to allow large data sets > 2kb - External Button support (#708) for resetting to factory defaults and other actions - new console set command in `system`, `set ethernet ` for quickly enabling cabled ethernet connections without using the captive wifi portal -- Added in MQTT nested mode, for thermostat and mixer, like we had in v2 +- Added in MQTT nested mode, for thermostat and mixer, like we had back in v2 - Cascade MC400 (product-id 210) (3.0.0b6) - values for wwMaxPower, wwFlowtempOffset diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index 370e453a9..ba09c7cfe 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -385,7 +385,7 @@ void DallasSensor::publish_values(const bool force) { char topic[100]; if (dallas_format == Mqtt::Dallas_Format::SENSORID) { // use '_' as HA doesn't like '-' in the topic name - std::string topicname = sensor.to_string(); + std::string topicname = sensor.to_string(); std::replace(topicname.begin(), topicname.end(), '-', '_'); snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallas_sensor%s/config"), Mqtt::base().c_str(), topicname); } else { diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 539b7ad3c..03c09513d 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -58,7 +58,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0x19, F("UBAMonitorSlow"), true, [&](std::shared_ptr t) { process_UBAMonitorSlow(t); }); register_telegram_type(0x1A, F("UBASetPoints"), false, [&](std::shared_ptr t) { process_UBASetPoints(t); }); register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, [&](std::shared_ptr t) { process_UBAMaintenanceStatus(t); }); - register_telegram_type(0x26, F("UBASettingsWW"), true, [&](std::shared_ptr t) { process_UBASettingsWW(t); }); register_telegram_type(0x2A, F("MC10Status"), false, [&](std::shared_ptr t) { process_MC10Status(t); }); register_telegram_type(0x33, F("UBAParameterWW"), true, [&](std::shared_ptr t) { process_UBAParameterWW(t); }); register_telegram_type(0x34, F("UBAMonitorWW"), false, [&](std::shared_ptr t) { process_UBAMonitorWW(t); }); @@ -73,21 +72,16 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_telegram_type(0x494, F("UBAEnergySupplied"), false, [&](std::shared_ptr t) { process_UBAEnergySupplied(t); }); register_telegram_type(0x495, F("UBAInformation"), false, [&](std::shared_ptr t) { process_UBAInformation(t); }); - 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) - EMSESP::send_read_request(0x15, - device_id); // read maintenace data on start (only published on change) - EMSESP::send_read_request(0x1C, - device_id); // read maintenace status on start (only published on change) + 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) + EMSESP::send_read_request(0x15, device_id); // read maintenace data on start (only published on change) + EMSESP::send_read_request(0x1C, device_id); // read maintenace status on start (only published on change) // MQTT commands for boiler topic register_mqtt_cmd(F("comfort"), [&](const char * value, const int8_t id) { return set_warmwater_mode(value, id); }); register_mqtt_cmd(F("wwactivated"), [&](const char * value, const int8_t id) { return set_warmwater_activated(value, id); }); register_mqtt_cmd(F("wwtapactivated"), [&](const char * value, const int8_t id) { return set_tapwarmwater_activated(value, id); }); register_mqtt_cmd(F("wwflowtempoffset"), [&](const char * value, const int8_t id) { return set_wWFlowTempOffset(value, id); }); - register_mqtt_cmd(F("wwmaxpower"), [&](const char * value, const int8_t id) { return set_warmwater_maxpower(value, id); }); register_mqtt_cmd(F("wwonetime"), [&](const char * value, const int8_t id) { return set_warmwater_onetime(value, id); }); register_mqtt_cmd(F("wwcircpump"), [&](const char * value, const int8_t id) { return set_warmwater_circulation_pump(value, id); }); register_mqtt_cmd(F("wwcirculation"), [&](const char * value, const int8_t id) { return set_warmwater_circulation(value, id); }); @@ -116,12 +110,10 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA, &heatingActive_, DeviceValueType::BOOL, nullptr, F("heatingActive"), F("heating active")); register_device_value(TAG_BOILER_DATA, &tapwaterActive_, DeviceValueType::BOOL, nullptr, F("tapwaterActive"), F("warm water active")); - register_device_value(TAG_BOILER_DATA, &selFlowTemp_, DeviceValueType::UINT, nullptr, F("selFlowTemp"), F("selected flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &selBurnPow_, DeviceValueType::UINT, nullptr, F("selBurnPow"), F("burner selected max power"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &heatingPumpMod_, DeviceValueType::UINT, nullptr, F("heatingPumpMod"), F("heating pump modulation"), DeviceValueUOM::PERCENT); register_device_value(TAG_BOILER_DATA, &heatingPump2Mod_, DeviceValueType::UINT, nullptr, F("heatingPump2Mod"), F("heating pump 2 modulation"), DeviceValueUOM::PERCENT); - register_device_value(TAG_BOILER_DATA, &outdoorTemp_, DeviceValueType::SHORT, FL_(div10), F("outdoorTemp"), F("outside temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &curFlowTemp_, DeviceValueType::USHORT, FL_(div10), F("curFlowTemp"), F("current flow temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &retTemp_, DeviceValueType::USHORT, FL_(div10), F("retTemp"), F("return temperature"), DeviceValueUOM::DEGREES); @@ -129,7 +121,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA, &sysPress_, DeviceValueType::UINT, FL_(div10), F("sysPress"), F("system pressure"), DeviceValueUOM::BAR); register_device_value(TAG_BOILER_DATA, &boilTemp_, DeviceValueType::USHORT, FL_(div10), F("boilTemp"), F("max boiler temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA, &exhaustTemp_, DeviceValueType::USHORT, FL_(div10), F("exhaustTemp"), F("exhaust temperature"), DeviceValueUOM::DEGREES); - register_device_value(TAG_BOILER_DATA, &burnGas_, DeviceValueType::BOOL, nullptr, F("burnGas"), F("gas")); register_device_value(TAG_BOILER_DATA, &flameCurr_, DeviceValueType::USHORT, FL_(div10), F("flameCurr"), F("flame current"), DeviceValueUOM::UA); register_device_value(TAG_BOILER_DATA, &heatingPump_, DeviceValueType::BOOL, nullptr, F("heatingPump"), F("heating pump"), DeviceValueUOM::PUMP); @@ -156,12 +147,35 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA, &serviceCode_, DeviceValueType::TEXT, nullptr, F("serviceCode"), F("service code")); register_device_value(TAG_BOILER_DATA, &serviceCodeNumber_, DeviceValueType::USHORT, nullptr, F("serviceCodeNumber"), F("service code number")); - // ww - boiler_data_ww topic + // info + register_device_value(TAG_BOILER_DATA, &upTimeControl_, DeviceValueType::TIME, FL_(div60), F("upTimeControl"), F("operating time total heat"), DeviceValueUOM::MINUTES); + register_device_value(TAG_BOILER_DATA, &upTimeCompHeating_, DeviceValueType::TIME, FL_(div60), F("upTimeCompHeating"), F("operating time compressor heating"), DeviceValueUOM::MINUTES); + register_device_value(TAG_BOILER_DATA, &upTimeCompCooling_, DeviceValueType::TIME, FL_(div60), F("upTimeCompCooling"), F("operating time compressor cooling"), DeviceValueUOM::MINUTES); + register_device_value(TAG_BOILER_DATA, &upTimeCompWw_, DeviceValueType::TIME, FL_(div60), F("upTimeCompWw"), F("operating time compressor warm water"), DeviceValueUOM::MINUTES); + register_device_value(TAG_BOILER_DATA, &heatingStarts_, DeviceValueType::ULONG, nullptr, F("heatingStarts"), F("# heating control starts")); + register_device_value(TAG_BOILER_DATA, &coolingStarts_, DeviceValueType::ULONG, nullptr, F("coolingStarts"), F("# cooling control starts")); + register_device_value(TAG_BOILER_DATA, &nrgConsTotal_, DeviceValueType::ULONG, nullptr, F("nrgConsTotal"), F("total energy consumption")); + register_device_value(TAG_BOILER_DATA, &nrgConsCompTotal_, DeviceValueType::ULONG, nullptr, F("nrgConsCompTotal"), F("energy consumption compressor total")); + register_device_value(TAG_BOILER_DATA, &nrgConsCompHeating_, DeviceValueType::ULONG, nullptr, F("nrgConsCompHeating"), F("energy consumption compressor heating")); + register_device_value(TAG_BOILER_DATA, &nrgConsCompWw_, DeviceValueType::ULONG, nullptr, F("nrgConsCompWw"), F("energy consumption compressor warm water")); + register_device_value(TAG_BOILER_DATA, &nrgConsCompCooling_, DeviceValueType::ULONG, nullptr, F("nrgConsCompCooling"), F("energy consumption compressor cooling")); + register_device_value(TAG_BOILER_DATA, &nrgSuppTotal_, DeviceValueType::ULONG, nullptr, F("nrgSuppTotal"), F("total energy supplied")); + register_device_value(TAG_BOILER_DATA, &nrgSuppHeating_, DeviceValueType::ULONG, nullptr, F("nrgSuppHeating"), F("total energy supplied heating")); + register_device_value(TAG_BOILER_DATA, &nrgSuppWw_, DeviceValueType::ULONG, nullptr, F("nrgSuppWw"), F("total energy warm supplied warm water")); + register_device_value(TAG_BOILER_DATA, &nrgSuppCooling_, DeviceValueType::ULONG, nullptr, F("nrgSuppCooling"), F("total energy supplied cooling")); + register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsTotal_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsTotal"), F("auxiliary electrical heater energy consumption total")); + register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsHeating_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsHeating"), F("auxiliary electrical heater energy consumption heating")); + register_device_value(TAG_BOILER_DATA, &auxElecHeatNrgConsDHW_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsDHW"), F("auxiliary electrical heater energy consumption DHW")); + register_device_value(TAG_BOILER_DATA, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, F("maintenanceMessage"), F("maintenance message")); + register_device_value(TAG_BOILER_DATA, &maintenanceDate_, DeviceValueType::TEXT, nullptr, F("maintenanceDate"), F("maintenance set date")); + register_device_value(TAG_BOILER_DATA, &maintenanceType_, DeviceValueType::ENUM, FL_(enum_off_time_date), F("maintenanceType"), F("maintenance scheduled")); + register_device_value(TAG_BOILER_DATA, &maintenanceTime_, DeviceValueType::USHORT, nullptr, F("maintenanceTime"), F("maintenance set time"), DeviceValueUOM::HOURS); + + // warm water - boiler_data_ww topic register_device_value(TAG_BOILER_DATA_WW, &wWSelTemp_, DeviceValueType::UINT, nullptr, F("wWSelTemp"), F("selected temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA_WW, &wWSetTemp_, DeviceValueType::UINT, nullptr, F("wWSetTemp"), F("set temperature"), DeviceValueUOM::DEGREES); register_device_value(TAG_BOILER_DATA_WW, &wWType_, DeviceValueType::ENUM, FL_(enum_flow), F("wWType"), F("type")); register_device_value(TAG_BOILER_DATA_WW, &wWComfort_, DeviceValueType::ENUM, FL_(enum_comfort), F("wWComfort"), F("comfort")); - register_device_value(TAG_BOILER_DATA_WW, &wWFlowTempOffset_, DeviceValueType::UINT, nullptr, F("wWFlowTempOffset"), F("flow offset temperature")); register_device_value(TAG_BOILER_DATA_WW, &wWCircPump_, DeviceValueType::BOOL, nullptr, F("wWCircPump"), F("circulation pump available")); register_device_value(TAG_BOILER_DATA_WW, &wWChargeType_, DeviceValueType::BOOL, FL_(enum_charge), F("wWChargeType"), F("charging type")); register_device_value(TAG_BOILER_DATA_WW, &wWDisinfectionTemp_, DeviceValueType::UINT, nullptr, F("wWDisinfectionTemp"), F("disinfection temperature"), DeviceValueUOM::DEGREES); @@ -186,31 +200,6 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value(TAG_BOILER_DATA_WW, &wWStarts_, DeviceValueType::ULONG, nullptr, F("wWStarts"), F("# starts")); register_device_value(TAG_BOILER_DATA_WW, &wWStarts2_, DeviceValueType::ULONG, nullptr, F("wWStarts2"), F("# control starts")); register_device_value(TAG_BOILER_DATA_WW, &wWWorkM_, DeviceValueType::TIME, nullptr, F("wWWorkM"), F("active time"), DeviceValueUOM::MINUTES); - register_device_value(TAG_BOILER_DATA_WW, &wWMaxPower_, DeviceValueType::UINT, nullptr, F("wWMaxPower"), F("max power"), DeviceValueUOM::PERCENT); - - // info - boiler_data_info topic - register_device_value(TAG_BOILER_DATA_INFO, &upTimeControl_, DeviceValueType::TIME, FL_(div60), F("upTimeControl"), F("operating time total heat"), DeviceValueUOM::MINUTES); - register_device_value(TAG_BOILER_DATA_INFO, &upTimeCompHeating_, DeviceValueType::TIME, FL_(div60), F("upTimeCompHeating"), F("operating time compressor heating"), DeviceValueUOM::MINUTES); - register_device_value(TAG_BOILER_DATA_INFO, &upTimeCompCooling_, DeviceValueType::TIME, FL_(div60), F("upTimeCompCooling"), F("operating time compressor cooling"), DeviceValueUOM::MINUTES); - register_device_value(TAG_BOILER_DATA_INFO, &upTimeCompWw_, DeviceValueType::TIME, FL_(div60), F("upTimeCompWw"), F("operating time compressor warm water"), DeviceValueUOM::MINUTES); - register_device_value(TAG_BOILER_DATA_INFO, &heatingStarts_, DeviceValueType::ULONG, nullptr, F("heatingStarts"), F("# heating starts (control)")); - register_device_value(TAG_BOILER_DATA_INFO, &coolingStarts_, DeviceValueType::ULONG, nullptr, F("coolingStarts"), F("# cooling starts (control)")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgConsTotal_, DeviceValueType::ULONG, nullptr, F("nrgConsTotal"), F("total energy consumption")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgConsCompTotal_, DeviceValueType::ULONG, nullptr, F("nrgConsCompTotal"), F("energy consumption compressor total")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgConsCompHeating_, DeviceValueType::ULONG, nullptr, F("nrgConsCompHeating"), F("energy consumption compressor heating")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgConsCompWw_, DeviceValueType::ULONG, nullptr, F("nrgConsCompWw"), F("energy consumption compressor warm water")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgConsCompCooling_, DeviceValueType::ULONG, nullptr, F("nrgConsCompCooling"), F("energy consumption compressor cooling")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgSuppTotal_, DeviceValueType::ULONG, nullptr, F("nrgSuppTotal"), F("total energy supplied")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgSuppHeating_, DeviceValueType::ULONG, nullptr, F("nrgSuppHeating"), F("total energy supplied heating")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgSuppWw_, DeviceValueType::ULONG, nullptr, F("nrgSuppWw"), F("total energy warm supplied warm water")); - register_device_value(TAG_BOILER_DATA_INFO, &nrgSuppCooling_, DeviceValueType::ULONG, nullptr, F("nrgSuppCooling"), F("total energy supplied cooling")); - register_device_value(TAG_BOILER_DATA_INFO, &auxElecHeatNrgConsTotal_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsTotal"), F("auxiliary electrical heater energy consumption total")); - register_device_value(TAG_BOILER_DATA_INFO, &auxElecHeatNrgConsHeating_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsHeating"), F("auxiliary electrical heater energy consumption heating")); - register_device_value(TAG_BOILER_DATA_INFO, &auxElecHeatNrgConsDHW_, DeviceValueType::ULONG, nullptr, F("auxElecHeatNrgConsDHW"), F("auxiliary electrical heater energy consumption DHW")); - register_device_value(TAG_BOILER_DATA_INFO, &maintenanceMessage_, DeviceValueType::TEXT, nullptr, F("maintenanceMessage"), F("maintenance message")); - register_device_value(TAG_BOILER_DATA_INFO, &maintenanceDate_, DeviceValueType::TEXT, nullptr, F("maintenanceDate"), F("maintenance set date")); - register_device_value(TAG_BOILER_DATA_INFO, &maintenanceType_, DeviceValueType::ENUM, FL_(enum_off_time_date), F("maintenanceType"), F("maintenance scheduled")); - register_device_value(TAG_BOILER_DATA_INFO, &maintenanceTime_, DeviceValueType::USHORT, nullptr, F("maintenanceTime"), F("maintenance set time"), DeviceValueUOM::HOURS); } // publish HA config @@ -219,7 +208,7 @@ bool Boiler::publish_ha_config() { doc["uniq_id"] = F_(boiler); char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/boiler_data"), Mqtt::base().c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("ID"); @@ -282,12 +271,10 @@ void Boiler::check_active(const bool force) { // 0x33 void Boiler::process_UBAParameterWW(std::shared_ptr telegram) { - has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on - has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on - has_update(telegram->read_value(wWCircPumpMode_, - 7)); // 1=1x3min... 6=6x3min, 7=continuous - has_update(telegram->read_value(wWChargeType_, - 10)); // 0 = charge pump, 0xff = 3-way valve + has_update(telegram->read_value(wWActivated_, 1)); // 0xFF means on + has_update(telegram->read_value(wWCircPump_, 6)); // 0xFF means on + has_update(telegram->read_value(wWCircPumpMode_, 7)); // 1=1x3min 6=6x3min 7=continuous + has_update(telegram->read_value(wWChargeType_, 10)); // 0 = charge pump, 0xff = 3-way valve has_update(telegram->read_value(wWSelTemp_, 2)); has_update(telegram->read_value(wWDisinfectionTemp_, 8)); has_update(telegram->read_value(wWFlowTempOffset_, 5)); @@ -321,9 +308,8 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr telegram) { // warm water storage sensors (if present) // wWStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/proddy/EMS-ESP/issues/206 - has_update(telegram->read_value(wWStorageTemp1_, 9)); // 0x8300 if not available - has_update(telegram->read_value(wWStorageTemp2_, - 11)); // 0x8000 if not available - this is boiler temp + has_update(telegram->read_value(wWStorageTemp1_, 9)); // 0x8300 if not available + has_update(telegram->read_value(wWStorageTemp2_, 11)); // 0x8000 if not available - this is boiler temp has_update(telegram->read_value(retTemp_, 13)); has_update(telegram->read_value(flameCurr_, 15)); @@ -356,7 +342,7 @@ void Boiler::process_UBATotalUptime(std::shared_ptr telegram) { /* * UBAParameters - type 0x16 * data: FF 5A 64 00 0A FA 0F 02 06 64 64 02 08 F8 0F 0F 0F 0F 1E 05 04 09 09 00 28 00 3C -*/ + */ void Boiler::process_UBAParameters(std::shared_ptr telegram) { has_update(telegram->read_value(heatingActivated_, 0)); has_update(telegram->read_value(heatingTemp_, 1)); @@ -370,13 +356,6 @@ void Boiler::process_UBAParameters(std::shared_ptr telegram) { has_update(telegram->read_value(pumpModMin_, 10)); } -/* - * UBASettingsWW - type 0x26 - max power on offset 7 - */ -void Boiler::process_UBASettingsWW(std::shared_ptr telegram) { - has_update(telegram->read_value(wWMaxPower_, 7)); -} - /* * UBAMonitorWW - type 0x34 - warm water monitor. 19 bytes long * received every 10 seconds @@ -456,8 +435,7 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr telegram) { has_update(telegram->read_value(outdoorTemp_, 0)); has_update(telegram->read_value(boilTemp_, 2)); has_update(telegram->read_value(exhaustTemp_, 4)); - has_update(telegram->read_value(switchTemp_, - 25)); // only if there is a mixer module present + has_update(telegram->read_value(switchTemp_, 25)); // only if there is a mixer module present has_update(telegram->read_value(heatingPumpMod_, 9)); has_update(telegram->read_value(burnStarts_, 10, 3)); // force to 3 bytes has_update(telegram->read_value(burnWorkMin_, 13, 3)); // force to 3 bytes @@ -491,7 +469,7 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr telegram } /* - * UBAParametersPlus - type 0xe6 + * UBAParametersPlus - type 0xE6 * parameters originaly taken from * https://github.com/Th3M3/buderus_ems-wiki/blob/master/Einstellungen%20des%20Regelger%C3%A4ts%20MC110.md * 88 0B E6 00 01 46 00 00 46 0A 00 01 06 FA 0A 01 02 64 01 00 00 1E 00 3C 01 00 00 00 01 00 9A @@ -708,28 +686,13 @@ bool Boiler::set_warmwater_temp(const char * value, const int8_t id) { if (get_toggle_fetch(EMS_TYPE_UBAParametersPlus)) { write_command(EMS_TYPE_UBAParameterWWPlus, 6, v, EMS_TYPE_UBAParameterWWPlus); } else { - write_command(EMS_TYPE_UBAFlags, 3, v, 0x34); // for i9000, see #397 - write_command(EMS_TYPE_UBAParameterWW, 2, v, - EMS_TYPE_UBAParameterWW); // read seltemp back + write_command(EMS_TYPE_UBAFlags, 3, v, 0x34); // for i9000, see #397 + write_command(EMS_TYPE_UBAParameterWW, 2, v, EMS_TYPE_UBAParameterWW); // read seltemp back } return true; } -// Set the warm water flow temperature offset 0x33 -bool Boiler::set_wWFlowTempOffset(const char * value, const int8_t id) { - int v = 0; - if (!Helpers::value2number(value, v)) { - LOG_WARNING(F("Set boiler warm water flow temperature offset: Invalid value")); - return false; - } - - LOG_INFO(F("Setting boiler warm water flow temperature offset to %d C"), v); - write_command(EMS_TYPE_UBAParameterWW, 5, v, EMS_TYPE_UBAParameterWW); - - return true; -} - // flow temp bool Boiler::set_flow_temp(const char * value, const int8_t id) { int v = 0; @@ -743,6 +706,19 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) { write_command(EMS_TYPE_UBASetPoints, 0, v, EMS_TYPE_UBASetPoints); // write_command(0x35, 3, v, 0x35); + return true; +} + +// Set the warm water flow temperature offset 0x33 +bool Boiler::set_wWFlowTempOffset(const char * value, const int8_t id) { + int v = 0; + if (!Helpers::value2number(value, v)) { + LOG_WARNING(F("Set boiler warm water flow temperature offset: Invalid value")); + return false; + } + + LOG_INFO(F("Setting boiler warm water flow temperature offset to %d C"), v); + write_command(EMS_TYPE_UBAParameterWW, 5, v, EMS_TYPE_UBAParameterWW); return true; } @@ -819,20 +795,6 @@ bool Boiler::set_max_power(const char * value, const int8_t id) { return true; } -// set warm water max power -bool Boiler::set_warmwater_maxpower(const char * value, const int8_t id) { - int v = 0; - if (!Helpers::value2number(value, v)) { - LOG_WARNING(F("Set warm water max power: Invalid value")); - return false; - } - - LOG_INFO(F("Setting warm water max power to %d %%"), v); - write_command(EMS_TYPE_UBASettingsWW, 7, v, EMS_TYPE_UBASettingsWW); - - return true; -} - // set min pump modulation bool Boiler::set_min_pump(const char * value, const int8_t id) { int v = 0; @@ -1042,8 +1004,7 @@ bool Boiler::set_warmwater_onetime(const char * value, const int8_t id) { LOG_INFO(F("Setting warm water OneTime loading %s"), v ? "on" : "off"); if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { - write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), - 0xE9); // not sure if this is in flags + write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0xE9); // not sure if this is in flags } else { write_command(EMS_TYPE_UBAFlags, 0, (v ? 0x22 : 0x02), 0x34); } @@ -1062,8 +1023,7 @@ bool Boiler::set_warmwater_circulation(const char * value, const int8_t id) { LOG_INFO(F("Setting warm water circulation %s"), v ? "on" : "off"); if (get_toggle_fetch(EMS_TYPE_UBAParameterWWPlus)) { - write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), - 0xE9); // not sure if this is in flags + write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0xE9); // not sure if this is in flags } else { write_command(EMS_TYPE_UBAFlags, 1, (v ? 0x22 : 0x02), 0x34); } diff --git a/src/devices/boiler.h b/src/devices/boiler.h index dc9e67d6f..679adbf7b 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -36,7 +36,6 @@ class Boiler : public EMSdevice { uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE - static constexpr uint8_t EMS_TYPE_UBASettingsWW = 0x26; static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; static constexpr uint8_t EMS_TYPE_UBAFlags = 0x35; @@ -74,9 +73,8 @@ class Boiler : public EMSdevice { uint8_t wWHeat_; // 3-way valve on WW uint8_t wWSetPumpPower_; // ww pump speed/power? uint8_t wWFlowTempOffset_; // Boiler offset for ww heating - uint8_t wWMaxPower_; // Warm Water maximum power uint32_t wWStarts_; // Warm Water # starts - uint32_t wWStarts2_; // Warm water starts (control) + uint32_t wWStarts2_; // Warm water control starts uint32_t wWWorkM_; // Warm Water # minutes uint16_t mixerTemp_; // mixing temperature @@ -128,8 +126,8 @@ class Boiler : public EMSdevice { uint32_t upTimeCompHeating_; // Operating time compressor heating uint32_t upTimeCompCooling_; // Operating time compressor cooling uint32_t upTimeCompWw_; // Operating time compressor warm water - uint32_t heatingStarts_; // Heating starts (control) - uint32_t coolingStarts_; // Cooling starts (control) + uint32_t heatingStarts_; // Heating control starts + uint32_t coolingStarts_; // Cooling control starts uint32_t nrgConsTotal_; // Energy consumption total uint32_t nrgConsCompTotal_; // Energy consumption compressor total uint32_t nrgConsCompHeating_; // Energy consumption compressor heating @@ -169,7 +167,6 @@ class Boiler : public EMSdevice { void process_UBAInformation(std::shared_ptr telegram); void process_UBAEnergySupplied(std::shared_ptr telegram); void process_CascadeMessage(std::shared_ptr telegram); - void process_UBASettingsWW(std::shared_ptr telegram); // commands - none of these use the additional id parameter bool set_warmwater_mode(const char * value, const int8_t id); @@ -180,7 +177,6 @@ class Boiler : public EMSdevice { bool set_warmwater_circulation_pump(const char * value, const int8_t id); bool set_warmwater_circulation_mode(const char * value, const int8_t id); bool set_warmwater_temp(const char * value, const int8_t id); - bool set_warmwater_maxpower(const char * value, const int8_t id); bool set_wWFlowTempOffset(const char * value, const int8_t id); bool set_flow_temp(const char * value, const int8_t id); bool set_heating_activated(const char * value, const int8_t id); diff --git a/src/devices/heatpump.cpp b/src/devices/heatpump.cpp index 4b25f0107..e11fcd6d4 100644 --- a/src/devices/heatpump.cpp +++ b/src/devices/heatpump.cpp @@ -47,7 +47,7 @@ bool Heatpump::publish_ha_config() { doc["ic"] = F_(iconheatpump); char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heatpump_data"), Mqtt::base().c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("ID"); diff --git a/src/devices/mixer.cpp b/src/devices/mixer.cpp index c6956c938..d555dc388 100644 --- a/src/devices/mixer.cpp +++ b/src/devices/mixer.cpp @@ -87,7 +87,7 @@ bool Mixer::publish_ha_config() { doc["uniq_id"] = uniq_id; char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), Mqtt::base().c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str()); doc["stat_t"] = stat_t; char name[20]; diff --git a/src/devices/solar.cpp b/src/devices/solar.cpp index cfe3a28ab..cd18f746a 100644 --- a/src/devices/solar.cpp +++ b/src/devices/solar.cpp @@ -99,7 +99,7 @@ bool Solar::publish_ha_config() { doc["uniq_id"] = F_(solar); char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/solar_data"), Mqtt::base().c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str()); doc["stat_t"] = stat_t; doc["val_tpl"] = FJSON("{{value_json.id}}"); diff --git a/src/devices/switch.cpp b/src/devices/switch.cpp index 38e54329d..c7e00fd39 100644 --- a/src/devices/switch.cpp +++ b/src/devices/switch.cpp @@ -53,7 +53,7 @@ bool Switch::publish_ha_config() { doc["uniq_id"] = F_(switch); char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), Mqtt::base().c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("ID"); diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index 682e8a12f..456307f8c 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -191,7 +191,7 @@ bool Thermostat::publish_ha_config() { doc["uniq_id"] = F_(thermostat); char stat_t[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/thermostat_data"), Mqtt::base().c_str()); + snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), Mqtt::base().c_str(), Mqtt::tag_to_topic(device_type(), DeviceValueTAG::TAG_NONE).c_str()); doc["stat_t"] = stat_t; doc["name"] = FJSON("ID"); @@ -369,28 +369,41 @@ void Thermostat::register_mqtt_ha_config_hc(uint8_t hc_num) { char str3[25]; snprintf_P(str3, sizeof(str3), PSTR("~/%s"), str2); - doc["mode_cmd_t"] = str3; - doc["temp_cmd_t"] = str3; - doc["name"] = str1; - doc["uniq_id"] = str2; - doc["mode_cmd_t"] = str3; - doc["temp_cmd_t"] = str3; - 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"); + doc["mode_cmd_t"] = str3; + doc["temp_cmd_t"] = str3; + doc["name"] = str1; + doc["uniq_id"] = str2; + doc["mode_cmd_t"] = str3; + doc["temp_cmd_t"] = str3; + doc["~"] = Mqtt::base(); // ems-esp - char mode_str[30]; - snprintf_P(mode_str, sizeof(mode_str), PSTR("{{value_json.hc%d.hamode}}"), hc_num); - doc["mode_stat_tpl"] = mode_str; + char topic_t[80]; + if (Mqtt::nested_format()) { + snprintf_P(topic_t, sizeof(topic_t), PSTR("~/%s"), Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str()); - char seltemp_str[30]; - snprintf_P(seltemp_str, sizeof(seltemp_str), PSTR("{{value_json.hc%d.seltemp}}"), hc_num); - doc["temp_stat_tpl"] = seltemp_str; + char mode_str_tpl[40]; + snprintf_P(mode_str_tpl, sizeof(mode_str_tpl), PSTR("{{value_json.hc%d.hamode}}"), hc_num); + doc["mode_stat_tpl"] = mode_str_tpl; - char currtemp_str[30]; - snprintf_P(currtemp_str, sizeof(currtemp_str), PSTR("{{value_json.hc%d.hatemp}}"), hc_num); - doc["curr_temp_tpl"] = currtemp_str; + char seltemp_str[30]; + snprintf_P(seltemp_str, sizeof(seltemp_str), PSTR("{{value_json.hc%d.seltemp}}"), hc_num); + doc["temp_stat_tpl"] = seltemp_str; + + char currtemp_str[30]; + snprintf_P(currtemp_str, sizeof(currtemp_str), PSTR("{{value_json.hc%d.hatemp}}"), hc_num); + doc["curr_temp_tpl"] = currtemp_str; + + + } else { + snprintf_P(topic_t, sizeof(topic_t), PSTR("~/%s"), Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_HC1 + hc_num - 1).c_str()); + + doc["mode_stat_tpl"] = FJSON("{{value_json.hamode}}"); + doc["temp_stat_tpl"] = FJSON("{{value_json.seltemp}}"); + doc["curr_temp_tpl"] = FJSON("{{value_json.hatemp}}"); + } + doc["mode_stat_t"] = topic_t; + doc["temp_stat_t"] = topic_t; + doc["curr_temp_t"] = topic_t; doc["min_temp"] = FJSON("5"); doc["max_temp"] = FJSON("30"); diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 0c632e305..a23efce2d 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -41,22 +41,49 @@ static const __FlashStringHelper * DeviceValueUOM_s[] __attribute__((__aligned__ // must be an int of 4 bytes, 32bit aligned static const __FlashStringHelper * const DeviceValueTAG_s[] PROGMEM = { - F_(tag_none), - F_(tag_boiler_data), - F_(tag_boiler_data_ww), - F_(tag_boiler_data_info), - F_(tag_thermostat_data), - F_(tag_hc1), - F_(tag_hc2), - F_(tag_hc3), - F_(tag_hc4), - F_(tag_wwc1), - F_(tag_wwc2), - F_(tag_wwc3), - F_(tag_wwc4) + F_(tag_none), // "" + F_(tag_system_data), // "" + F_(tag_boiler_data), // "" + F_(tag_boiler_data_ww), // "warm water" + F_(tag_thermostat_data), // "" + F_(tag_hc1), // "hc1" + F_(tag_hc2), // "hc2" + F_(tag_hc3), // "hc3" + F_(tag_hc4), // "hc4" + F_(tag_wwc1), // "wwc1" + F_(tag_wwc2), // "Wwc2" + F_(tag_wwc3), // "wwc3" + F_(tag_wwc4) // "wwc4" }; +// MQTT topics derived from tags +static const __FlashStringHelper * const DeviceValueTAG_mqtt[] PROGMEM = { + + F_(tag_none), // "" + F_(tag_system_data_mqtt), // "heartbeat" + F_(tag_boiler_data_mqtt), // "" + F_(tag_boiler_data_ww_mqtt), // "ww" + F_(tag_thermostat_data), // "" + F_(tag_hc1), // "hc1" + F_(tag_hc2), // "hc2" + F_(tag_hc3), // "hc3" + F_(tag_hc4), // "hc4" + F_(tag_wwc1), // "wwc1" + F_(tag_wwc2), // "Wwc2" + F_(tag_wwc3), // "wwc3" + F_(tag_wwc4) // "wwc4" + +}; + +const std::string EMSdevice::tag_to_string(uint8_t tag) { + return uuid::read_flash_string(DeviceValueTAG_s[tag]); +} + +const std::string EMSdevice::tag_to_mqtt(uint8_t tag) { + return uuid::read_flash_string(DeviceValueTAG_mqtt[tag]); +} + const std::string EMSdevice::uom_to_string(uint8_t uom) { if (uom == DeviceValueUOM::NONE || uom >= DeviceValueUOM::PUMP) { return std::string{}; @@ -64,10 +91,6 @@ const std::string EMSdevice::uom_to_string(uint8_t uom) { return uuid::read_flash_string(DeviceValueUOM_s[uom - 1]); // offset by 1 to account for NONE } -const std::string EMSdevice::tag_to_string(uint8_t tag) { - return uuid::read_flash_string(DeviceValueTAG_s[tag]); -} - const std::vector EMSdevice::devicevalues() const { return devicevalues_; } diff --git a/src/emsdevice.h b/src/emsdevice.h index 0e2f8abc3..cd6b04b98 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -69,14 +69,12 @@ MAKE_PSTR(lmin, "l/min") enum DeviceValueUOM : uint8_t { NONE = 0, DEGREES, PERCENT, LMIN, KWH, WH, HOURS, MINUTES, UA, BAR, PUMP }; // TAG mapping - maps to DeviceValueTAG_s in emsdevice.cpp -MAKE_PSTR(tag_none, "") // use empty string if want to suppress showing tags -// MAKE_PSTR(tag_boiler_data, "boiler") -// MAKE_PSTR(tag_boiler_data_ww, "warm water") -// MAKE_PSTR(tag_boiler_data_info, "info") +// use empty string if want to suppress showing tags +MAKE_PSTR(tag_none, "") +MAKE_PSTR(tag_system_data, "") MAKE_PSTR(tag_boiler_data, "") -MAKE_PSTR(tag_boiler_data_ww, "") -MAKE_PSTR(tag_boiler_data_info, "") -MAKE_PSTR(tag_thermostat_data, "") // use empty string if want to suppress showing tags +MAKE_PSTR(tag_boiler_data_ww, "warm water") +MAKE_PSTR(tag_thermostat_data, "") MAKE_PSTR(tag_hc1, "hc1") MAKE_PSTR(tag_hc2, "hc2") MAKE_PSTR(tag_hc3, "hc3") @@ -85,11 +83,17 @@ MAKE_PSTR(tag_wwc1, "wwc1") MAKE_PSTR(tag_wwc2, "wwc2") MAKE_PSTR(tag_wwc3, "wwc3") MAKE_PSTR(tag_wwc4, "wwc4") + +// MQTT topic names +MAKE_PSTR(tag_system_data_mqtt, "heartbeat") +MAKE_PSTR(tag_boiler_data_mqtt, "") +MAKE_PSTR(tag_boiler_data_ww_mqtt, "ww") + enum DeviceValueTAG : uint8_t { TAG_NONE = 0, // wild card + TAG_SYSTEM_DATA, TAG_BOILER_DATA, TAG_BOILER_DATA_WW, - TAG_BOILER_DATA_INFO, TAG_THERMOSTAT_DATA, TAG_HC1, TAG_HC2, @@ -132,6 +136,7 @@ class EMSdevice { static const std::string uom_to_string(uint8_t uom); static const std::string tag_to_string(uint8_t tag); + static const std::string tag_to_mqtt(uint8_t tag); inline uint8_t product_id() const { return product_id_; diff --git a/src/emsesp.cpp b/src/emsesp.cpp index b3fd75890..600b8c82f 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -425,6 +425,7 @@ void EMSESP::publish_device_values(uint8_t device_type) { bool nested = Mqtt::nested_format(); + // group by device type for (const auto & emsdevice : emsdevices) { if (emsdevice && (emsdevice->device_type() == device_type)) { // if we're using HA and it's not already done, send the config topics first. only do this once @@ -434,14 +435,11 @@ void EMSESP::publish_device_values(uint8_t device_type) { // if its a boiler, generate json for each group and publish it directly if (device_type == DeviceType::BOILER) { - emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false); // not nested - Mqtt::publish("boiler_data", json); + emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA, false); + Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json); json.clear(); - emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_WW, false); // not nested - Mqtt::publish("boiler_data_ww", json); - json.clear(); - emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_INFO, false); // not nested - Mqtt::publish("boiler_data_info", json); + emsdevice->generate_values_json(json, DeviceValueTAG::TAG_BOILER_DATA_WW, false); + Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA_WW), json); need_publish = false; } @@ -453,14 +451,12 @@ void EMSESP::publish_device_values(uint8_t device_type) { need_publish |= emsdevice->generate_values_json(json, DeviceValueTAG::TAG_NONE, true); // nested } else { emsdevice->generate_values_json(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false); // not nested - Mqtt::publish("thermostat_data", json); + Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json); json.clear(); for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_HC4; hc_tag++) { emsdevice->generate_values_json(json, hc_tag, false); // not nested - char topic[20]; - snprintf_P(topic, sizeof(topic), PSTR("thermostat_data_%s"), EMSdevice::tag_to_string(hc_tag).c_str()); - Mqtt::publish(topic, json); + Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json); json.clear(); } need_publish = false; @@ -475,9 +471,7 @@ void EMSESP::publish_device_values(uint8_t device_type) { } else { for (uint8_t hc_tag = TAG_HC1; hc_tag <= DeviceValueTAG::TAG_WWC4; hc_tag++) { emsdevice->generate_values_json(json, hc_tag, false); // not nested - char topic[20]; - snprintf_P(topic, sizeof(topic), PSTR("mixer_data_%s"), EMSdevice::tag_to_string(hc_tag).c_str()); - Mqtt::publish(topic, json); + Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json); json.clear(); } need_publish = false; @@ -878,8 +872,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std:: // sometimes boilers share the same product id as controllers // so only add boilers if the device_id is 0x08, which is fixed for EMS if (device.device_type == DeviceType::BOILER) { - if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER || - (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1 && device_id <= EMSdevice::EMS_DEVICE_ID_BOILER_F)) { + if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER || (device_id >= EMSdevice::EMS_DEVICE_ID_BOILER_1 && device_id <= EMSdevice::EMS_DEVICE_ID_BOILER_F)) { device_p = &device; break; } @@ -933,7 +926,7 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & json, const int8_t i for (const auto & emsdevice : emsdevices) { 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, tag, true, (id == -1)); // nested, console as default + has_value |= emsdevice->generate_values_json(json, tag, (id == -1), true); // console & nested } } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index bd70ea08c..53bec453d 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -204,20 +204,27 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { for (const auto & message : mqtt_messages_) { auto content = message.content_; + char topic[MQTT_TOPIC_MAX_SIZE]; + if ((strncmp(content->topic.c_str(), "homeassistant/", 13) != 0)) { + snprintf_P(topic, sizeof(topic), PSTR("%s/%s"), Mqtt::base().c_str(), content->topic.c_str()); + } else { + snprintf_P(topic, sizeof(topic), PSTR("%s"), content->topic.c_str()); + } + if (content->operation == Operation::PUBLISH) { // Publish messages if (message.retry_count_ == 0) { if (message.packet_id_ == 0) { - shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s"), message.id_, content->topic.c_str(), content->payload.c_str()); + shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s"), message.id_, topic, content->payload.c_str()); } else { - shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d)"), message.id_, content->topic.c_str(), content->payload.c_str(), message.packet_id_); + shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d)"), message.id_, topic, content->payload.c_str(), message.packet_id_); } } else { - shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"), message.id_, content->topic.c_str(), content->payload.c_str(), message.packet_id_, message.retry_count_); + shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"), message.id_, topic, content->payload.c_str(), message.packet_id_, message.retry_count_); } } else { // Subscribe messages - shell.printfln(F(" [%02d] (Sub) topic=%s"), message.id_, content->topic.c_str()); + shell.printfln(F(" [%02d] (Sub) topic=%s"), message.id_, topic); } } shell.println(); @@ -370,17 +377,36 @@ void Mqtt::reset_mqtt() { } } +void Mqtt::load_settings() { + EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { + mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string + mqtt_qos_ = mqttSettings.mqtt_qos; + mqtt_retain_ = mqttSettings.mqtt_retain; + mqtt_enabled_ = mqttSettings.enabled; + ha_enabled_ = mqttSettings.ha_enabled; + ha_climate_format_ = mqttSettings.ha_climate_format; + dallas_format_ = mqttSettings.dallas_format; + bool_format_ = mqttSettings.bool_format; + nested_format_ = mqttSettings.nested_format; + + // convert to milliseconds + publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; + publish_time_thermostat_ = mqttSettings.publish_time_thermostat * 1000; + publish_time_solar_ = mqttSettings.publish_time_solar * 1000; + publish_time_mixer_ = mqttSettings.publish_time_mixer * 1000; + publish_time_other_ = mqttSettings.publish_time_other * 1000; + publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000; + }); +} + void Mqtt::start() { mqttClient_ = EMSESP::esp8266React.getMqttClient(); - // 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(); // Convert String to std::string - if (!mqtt_enabled_) { - return; // quit, not using MQTT - } - }); + load_settings(); // fetch MQTT settings + + if (!mqtt_enabled_) { + return; // quit, not using MQTT + } // if already initialized, don't do it again if (initialized_) { @@ -498,23 +524,7 @@ void Mqtt::on_connect() { connecting_ = true; connectcount_++; - // fetch MQTT settings - EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { - publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; // convert to milliseconds - publish_time_thermostat_ = mqttSettings.publish_time_thermostat * 1000; - publish_time_solar_ = mqttSettings.publish_time_solar * 1000; - publish_time_mixer_ = mqttSettings.publish_time_mixer * 1000; - publish_time_other_ = mqttSettings.publish_time_other * 1000; - publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000; - mqtt_qos_ = mqttSettings.mqtt_qos; - mqtt_retain_ = mqttSettings.mqtt_retain; - mqtt_enabled_ = mqttSettings.enabled; - ha_enabled_ = mqttSettings.ha_enabled; - ha_climate_format_ = mqttSettings.ha_climate_format; - dallas_format_ = mqttSettings.dallas_format; - bool_format_ = mqttSettings.bool_format; - nested_format_ = mqttSettings.nested_format; - }); + load_settings(); // reload MQTT settings - in case they have changes // send info topic appended with the version information as JSON StaticJsonDocument doc; @@ -589,16 +599,16 @@ void Mqtt::ha_status() { Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag // create the sensors - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("Wifi strength"), EMSdevice::DeviceType::SYSTEM, F("rssi")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("Uptime (sec)"), EMSdevice::DeviceType::SYSTEM, F("uptime_sec")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("Free heap memory"), EMSdevice::DeviceType::SYSTEM, F("freemem")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("# Failed MQTT publishes"), EMSdevice::DeviceType::SYSTEM, F("mqttfails")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("# Rx Sent"), EMSdevice::DeviceType::SYSTEM, F("rxsent")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("# Rx Fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("# Tx Reads"), EMSdevice::DeviceType::SYSTEM, F("txread")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("# Tx Writes"), EMSdevice::DeviceType::SYSTEM, F("txwrite")); - publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_NONE, F("# Tx Fails"), EMSdevice::DeviceType::SYSTEM, F("txfails")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("Wifi strength"), EMSdevice::DeviceType::SYSTEM, F("rssi")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("Uptime"), EMSdevice::DeviceType::SYSTEM, F("uptime")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("Uptime (sec)"), EMSdevice::DeviceType::SYSTEM, F("uptime_sec")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("Free heap memory"), EMSdevice::DeviceType::SYSTEM, F("freemem")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("# Failed MQTT publishes"), EMSdevice::DeviceType::SYSTEM, F("mqttfails")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("# Rx Sent"), EMSdevice::DeviceType::SYSTEM, F("rxsent")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("# Rx Fails"), EMSdevice::DeviceType::SYSTEM, F("rxfails")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("# Tx Reads"), EMSdevice::DeviceType::SYSTEM, F("txread")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("# Tx Writes"), EMSdevice::DeviceType::SYSTEM, F("txwrite")); + publish_mqtt_ha_sensor(DeviceValueType::INT, DeviceValueTAG::TAG_SYSTEM_DATA, F("# Tx Fails"), EMSdevice::DeviceType::SYSTEM, F("txfails")); } // add sub or pub task to the queue. @@ -792,11 +802,11 @@ void Mqtt::process_queue() { // // note: some string copying here into chars, it looks messy but does help with heap fragmentation issues void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice::DeviceValueType - uint8_t tag, + uint8_t tag, // EMSdevice::DeviceValueTAG const __FlashStringHelper * name, const uint8_t device_type, // EMSdevice::DeviceType const __FlashStringHelper * entity, - const uint8_t uom) { // DeviceValueUOM (0=NONE) + const uint8_t uom) { // EMSdevice::DeviceValueUOM (0=NONE) // ignore if name (fullname) is empty if (name == nullptr) { return; @@ -804,13 +814,12 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG); - // special case for boiler - don't use the prefix - bool have_prefix = ((tag != DeviceValueTAG::TAG_NONE) && (device_type != EMSdevice::DeviceType::BOILER)); + bool have_tag = !EMSdevice::tag_to_string(tag).empty() && (device_type != EMSdevice::DeviceType::BOILER); // ignore boiler + bool is_nested = nested_format_ || (device_type == EMSdevice::DeviceType::BOILER); // boiler never uses nested - // create entity by inserting any given prefix - // we ignore the tag if BOILER + // create entity by add the tag if present, seperating with a . char new_entity[50]; - if (have_prefix) { + if (have_tag) { snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), EMSdevice::tag_to_string(tag).c_str(), uuid::read_flash_string(entity).c_str()); } else { snprintf_P(new_entity, sizeof(new_entity), PSTR("%s"), uuid::read_flash_string(entity).c_str()); @@ -830,28 +839,13 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice doc["~"] = mqtt_base_; // state topic - // if its a boiler we use the tag char stat_t[MQTT_TOPIC_MAX_SIZE]; - if (device_type == EMSdevice::DeviceType::BOILER) { - if (tag == DeviceValueTAG::TAG_BOILER_DATA) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("~/boiler_data")); - } else if (tag == DeviceValueTAG::TAG_BOILER_DATA_WW) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("~/boiler_data_ww")); - } else { - snprintf_P(stat_t, sizeof(stat_t), PSTR("~/boiler_data_info")); - } - } else if (device_type == EMSdevice::DeviceType::SYSTEM) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("~/heartbeat")); - } else if (nested_format_ || !have_prefix) { - snprintf_P(stat_t, sizeof(stat_t), PSTR("~/%s_data"), device_name); - } else { - snprintf_P(stat_t, sizeof(stat_t), PSTR("~/%s_data_%s"), device_name, EMSdevice::tag_to_string(tag).c_str()); - } + snprintf_P(stat_t, sizeof(stat_t), PSTR("~/%s"), tag_to_topic(device_type, tag).c_str()); doc["stat_t"] = stat_t; // name - char new_name[50]; - if (have_prefix) { + char new_name[80]; + if (have_tag) { snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, EMSdevice::tag_to_string(tag).c_str(), uuid::read_flash_string(name).c_str()); } else { snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name, uuid::read_flash_string(name).c_str()); @@ -861,22 +855,21 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice // value template char val_tpl[50]; - if (nested_format_) { + if (is_nested) { snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), new_entity); } else { snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), uuid::read_flash_string(entity).c_str()); } doc["val_tpl"] = val_tpl; - char topic[MQTT_TOPIC_MAX_SIZE]; // reserved for topic + char topic[MQTT_TOPIC_MAX_SIZE]; // look at the device value type if (type == DeviceValueType::BOOL) { // binary sensor snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/%s/config"), mqtt_base_.c_str(), uniq.c_str()); // topic - // how to render boolean - // HA only accepts String values + // how to render boolean. HA only accepts String values char result[10]; doc[F("payload_on")] = Helpers::render_boolean(result, true); doc[F("payload_off")] = Helpers::render_boolean(result, false); @@ -905,8 +898,7 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice } JsonObject dev = doc.createNestedObject("dev"); - - JsonArray ids = dev.createNestedArray("ids"); + JsonArray ids = dev.createNestedArray("ids"); // for System commands we'll use the ID EMS-ESP if (device_type == EMSdevice::DeviceType::SYSTEM) { @@ -920,4 +912,21 @@ void Mqtt::publish_mqtt_ha_sensor(uint8_t type, // EMSdevice publish_ha(topic, doc.as()); } +// based on the device and tag, create the MQTT topic name (without the basename) +// differs based on whether MQTT nested is enabled +// tag = EMSdevice::DeviceValueTAG +const std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) { + if (device_type == EMSdevice::DeviceType::SYSTEM) { + return EMSdevice::tag_to_mqtt(tag); + } + + // if there is a tag add it + if ((EMSdevice::tag_to_mqtt(tag).empty()) || (nested_format_ && (device_type != EMSdevice::DeviceType::BOILER))) { + return EMSdevice::device_type_2_device_name(device_type) + "_data"; + } else { + return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag); + } +} + + } // namespace emsesp diff --git a/src/mqtt.h b/src/mqtt.h index 2e40576a5..3f2ae8a4d 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -71,6 +71,8 @@ class Mqtt { void loop(); void start(); + static void load_settings(); + void set_publish_time_boiler(uint16_t publish_time); void set_publish_time_thermostat(uint16_t publish_time); void set_publish_time_solar(uint16_t publish_time); @@ -192,6 +194,8 @@ class Mqtt { return mqtt_messages_.empty(); } + static const std::string tag_to_topic(uint8_t device_type, uint8_t tag); + struct QueuedMqttMessage { const uint16_t id_; const std::shared_ptr content_; @@ -208,6 +212,7 @@ class Mqtt { }; static std::deque mqtt_messages_; + private: static uuid::log::Logger logger_; diff --git a/src/test/test.cpp b/src/test/test.cpp index 014a7855b..cef9ffebf 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -388,6 +388,21 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) { shell.invoke_command("show devices"); } + if (command == "ha") { + shell.printfln(F("Testing HA discovery")); + Mqtt::ha_enabled(true); + // Mqtt::nested_format(true); + Mqtt::nested_format(false); + + // run_test("boiler"); + run_test("thermostat"); + // run_test("solar"); + // run_test("mixer"); + + shell.invoke_command("call system publish"); + shell.invoke_command("show mqtt"); + } + if (command == "mqtt_nested") { shell.printfln(F("Testing nested MQTT")); Mqtt::ha_enabled(false); // turn off HA Discovery to stop the chatter diff --git a/src/test/test.h b/src/test/test.h index 5cbf62d09..dfb8d8b1f 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -33,6 +33,7 @@ namespace emsesp { // #define EMSESP_TEST_DEFAULT "boiler" // #define EMSESP_TEST_DEFAULT "mqtt2" #define EMSESP_TEST_DEFAULT "mqtt_nested" +// #define EMSESP_TEST_DEFAULT "ha" class Test { public: diff --git a/src/version.h b/src/version.h index dd8d40d04..c266689bc 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.0.0b6" +#define EMSESP_APP_VERSION "3.0.0b7"