Merge branch 'dev_no_master_thermostat' into idf4_no_master

This commit is contained in:
MichaelDvP
2022-05-06 12:01:38 +02:00
27 changed files with 479 additions and 645 deletions

View File

@@ -60,19 +60,18 @@ AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic
// static/common variables
uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = true;
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
uint8_t EMSESP::unique_id_count_ = 0;
bool EMSESP::trace_raw_ = false;
uint16_t EMSESP::wait_validate_ = 0;
bool EMSESP::wait_km_ = true;
// for a specific EMS device go and request data values
// or if device_id is 0 it will fetch from all our known and active devices
@@ -168,63 +167,6 @@ void EMSESP::scan_devices() {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
}
/**
* if thermostat master is 0x18 it handles only ww and hc1, hc2..hc8 handled by devices 0x19..0x1F
* we send to right device and match all reads to 0x18
*/
uint8_t EMSESP::check_master_device(const uint8_t device_id, const uint16_t type_id, const bool read) {
if (device_id != 0x10 && (device_id < 0x18 || device_id > 0x1F)) {
return device_id;
}
if (actual_master_thermostat_ == 0x18) {
uint16_t mon_ids[] = {0x02A5, 0x02A6, 0x02A7, 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC};
uint16_t set_ids[] = {0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF, 0x02C0};
uint16_t summer_ids[] = {0x02AF, 0x02B0, 0x02B1, 0x02B2, 0x02B3, 0x02B4, 0x02B5, 0x02B6};
uint16_t curve_ids[] = {0x029B, 0x029C, 0x029D, 0x029E, 0x029F, 0x02A0, 0x02A1, 0x02A2};
uint16_t summer2_ids[] = {0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0476, 0x0477, 0x0478};
uint16_t master_ids[] = {0x02F5, 0x031B, 0x031D, 0x031E, 0x023A, 0x0267, 0x0240};
// look for heating circuits
for (uint8_t i = 0; i < sizeof(mon_ids) / 2; i++) {
if (type_id == mon_ids[i] || type_id == set_ids[i] || type_id == summer_ids[i] || type_id == curve_ids[i] || type_id == summer2_ids[i]) {
if (read) {
// receiving telegrams and map all to master thermostat at 0x18 (src manipulated)
return 0x18;
} else {
// sending telegrams to the individual thermostats (dst manipulated)
return 0x18 + i;
}
}
}
// look for ids that are only handled by master
for (uint8_t i = 0; i < sizeof(master_ids) / 2; i++) {
if (type_id == master_ids[i]) {
return 0x18;
}
}
} else if (actual_master_thermostat_ == 0x10) {
// Junkers FW200 supports hc1/hc2, hc3/hc4 handled by devices 0x1A...
// see https://github.com/emsesp/EMS-ESP32/issues/336
uint16_t mon_ids[] = {0x0171, 0x0172};
uint16_t set_ids[] = {0x0167, 0x0168};
for (uint8_t i = 0; i < sizeof(mon_ids) / 2; i++) {
if (type_id == mon_ids[i] || type_id == set_ids[i]) {
// reads to master thermostat, writes to remote thermostats
return (read ? actual_master_thermostat_ : 0x1A + i);
}
}
}
return device_id;
}
void EMSESP::actual_master_thermostat(const uint8_t device_id) {
actual_master_thermostat_ = device_id;
}
uint8_t EMSESP::actual_master_thermostat() {
return actual_master_thermostat_;
}
// to watch both type IDs and deviceIDs
void EMSESP::watch_id(uint16_t watch_id) {
watch_id_ = watch_id;
@@ -560,13 +502,13 @@ void EMSESP::publish_device_values(uint8_t device_type) {
bool nested = (Mqtt::is_nested());
// group by device type
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
// specially for MQTT Discovery
// we may have some RETAINED /config topics that reference fields in the data payloads that no longer exist
// remove them immediately to prevent HA from complaining
// we need to do this first before the data payload is published, and only done once!
if (Mqtt::ha_enabled()) {
if (Mqtt::ha_enabled()) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
// specially for MQTT Discovery
// we may have some RETAINED /config topics that reference fields in the data payloads that no longer exist
// remove them immediately to prevent HA from complaining
// we need to do this first before the data payload is published, and only done once!
if (emsdevice->ha_config_firstrun()) {
emsdevice->ha_config_clear();
emsdevice->ha_config_firstrun(false);
@@ -575,79 +517,40 @@ void EMSESP::publish_device_values(uint8_t device_type) {
emsdevice->mqtt_ha_entity_config_remove();
}
}
// if its a boiler, generate json for each group and publish it directly. not nested
if (device_type == DeviceType::BOILER) {
json = doc.to<JsonObject>();
if (emsdevice->generate_values(json, DeviceValueTAG::TAG_BOILER_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_BOILER_DATA), json);
}
json = doc.to<JsonObject>();
if (emsdevice->generate_values(json, DeviceValueTAG::TAG_DEVICE_DATA_WW, false, EMSdevice::OUTPUT_TARGET::MQTT)) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_DEVICE_DATA_WW), json);
}
need_publish = false;
}
// Thermostat
else if (device_type == DeviceType::THERMOSTAT) {
// only publish the single master thermostat
if (emsdevice->device_id() == EMSESP::actual_master_thermostat()) {
if (nested) {
json = doc.to<JsonObject>();
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
} else {
json = doc.to<JsonObject>();
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_THERMOSTAT_DATA, false, EMSdevice::OUTPUT_TARGET::MQTT); // not nested
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_DEVICE_DATA_WW, false, EMSdevice::OUTPUT_TARGET::MQTT);
if (need_publish) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
}
for (uint8_t hc_tag = DeviceValueTAG::TAG_HC1; hc_tag <= DeviceValueTAG::TAG_HC8; hc_tag++) {
json = doc.to<JsonObject>();
if (emsdevice->generate_values(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
}
}
need_publish = false;
}
}
}
// Mixer
else if (device_type == DeviceType::MIXER) {
if (nested) {
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
} else {
for (uint8_t hc_tag = DeviceValueTAG::TAG_HC1; hc_tag <= DeviceValueTAG::TAG_WWC10; hc_tag++) {
json = doc.to<JsonObject>();
if (emsdevice->generate_values(json, hc_tag, false, EMSdevice::OUTPUT_TARGET::MQTT)) { // not nested
Mqtt::publish(Mqtt::tag_to_topic(device_type, hc_tag), json);
}
}
need_publish = false;
}
} else {
// for all other devices add the values to the json
need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested
}
// we want to create the /config topic after the data payload to prevent HA from throwing up a warning
if (Mqtt::ha_enabled()) {
emsdevice->mqtt_ha_entity_config_create();
}
}
}
// publish it under a single topic, only if we have data to publish
for (uint8_t tag = DeviceValueTAG::TAG_BOILER_DATA_WW; tag <= DeviceValueTAG::TAG_HS16; tag++) {
JsonObject json_hc = json;
bool nest_created = false;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
if (nested && !nest_created && emsdevice->has_tag(tag)) {
json_hc = doc.createNestedObject(EMSdevice::tag_to_string(tag));
nest_created = true;
}
need_publish |= emsdevice->generate_values(json_hc, tag, false, EMSdevice::OUTPUT_TARGET::MQTT);
}
}
if (need_publish && ((!nested && tag >= DeviceValueTAG::TAG_DEVICE_DATA_WW) || (tag == DeviceValueTAG::TAG_BOILER_DATA_WW))) {
Mqtt::publish(Mqtt::tag_to_topic(device_type, tag), json);
json = doc.to<JsonObject>();
need_publish = false;
}
}
if (need_publish) {
if (doc.overflowed()) {
LOG_WARNING(F("MQTT buffer overflow, please use individual topics"));
}
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "%s_data", EMSdevice::device_type_2_device_name(device_type).c_str());
Mqtt::publish(topic, json);
Mqtt::publish(Mqtt::tag_to_topic(device_type, DeviceValueTAG::TAG_NONE), json);
}
// we want to create the /config topic after the data payload to prevent HA from throwing up a warning
if (Mqtt::ha_enabled()) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
emsdevice->mqtt_ha_entity_config_create();
}
}
}
}
@@ -1007,10 +910,6 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_class.first)) {
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
if ((num_thermostats > 1) && (emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT)
&& (emsdevice->device_id() == actual_master_thermostat())) {
shell.printf(F(" **master device**"));
}
shell.println();
emsdevice->show_telegram_handlers(shell);
@@ -1203,23 +1102,36 @@ bool EMSESP::command_commands(uint8_t device_type, JsonObject & output, const in
bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t id, const uint8_t output_target) {
bool has_value = false;
uint8_t tag;
if (id >= 1 && id <= 8) {
tag = DeviceValueTAG::TAG_HC1 + id - 1;
} else if (id >= 9 && id <= 19) {
tag = DeviceValueTAG::TAG_WWC1 + id - 9;
if (id >= 1 && id <= 34) {
tag = DeviceValueTAG::TAG_HC1 + id - 1; // this sets also WWC and HS
} else if (id == -1 || id == 0) {
tag = DeviceValueTAG::TAG_NONE;
} else {
return false;
}
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(output, tag, (id < 1), output_target); // use nested for id -1 and 0
if (id > 0 || output_target == EMSdevice::OUTPUT_TARGET::API_VERBOSE) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
has_value |= emsdevice->generate_values(output, tag, (id < 1), output_target); // use nested for id -1 and 0
}
}
return has_value;
}
// for nested output add for each tag
for (tag = DeviceValueTAG::TAG_BOILER_DATA_WW; tag <= DeviceValueTAG::TAG_HS16; tag++) {
JsonObject output_hc = output;
bool nest_created = false;
for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) {
if (!nest_created && emsdevice->has_tag(tag)) {
output_hc = output.createNestedObject(EMSdevice::tag_to_string(tag));
nest_created = true;
}
has_value |= emsdevice->generate_values(output_hc, tag, true, output_target); // use nested for id -1 and 0
}
}
}
return has_value;
}