refactor device value rendering (to Web, Console or MQTT) to base class #632

This commit is contained in:
proddy
2020-12-13 22:52:34 +01:00
parent f72e549850
commit ffa313ebe4
60 changed files with 2579 additions and 3367 deletions

View File

@@ -55,103 +55,50 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
}
}
// output json to web UI
void Mixer::device_info_web(JsonArray & root) {
if (type() == Type::NONE) {
return; // don't have any values yet
// 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
}
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
// store the heating circuit and type
hc_ = hc + 1;
type_ = type;
if (!export_values_format(Mqtt::Format::SINGLE, json)) {
return; // empty
}
std::string prefix(10, '\0');
snprintf_P(&prefix[0], sizeof(prefix), PSTR("%s%d"), (type_ == Type::HC) ? "hc" : "wwc", hc + 1);
char prefix_str[10];
if (type() == Type::HC) {
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc_);
create_value_json(root, F("flowTemp"), FPSTR(prefix_str), F_(flowTemp), F_(degrees), json);
create_value_json(root, F("flowSetTemp"), FPSTR(prefix_str), F_(flowSetTemp), F_(degrees), json);
create_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, json);
create_value_json(root, F("valveStatus"), FPSTR(prefix_str), F_(valveStatus), F_(percent), json);
} else {
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(wwc %d) "), hc_);
create_value_json(root, F("wwTemp"), FPSTR(prefix_str), F_(wwTemp), F_(degrees), json);
create_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, json);
create_value_json(root, F("tempStatus"), FPSTR(prefix_str), F_(tempStatus), nullptr, json);
}
register_device_value(
prefix, &flowTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES);
register_device_value(prefix, &flowSetTemp_, DeviceValueType::UINT, {}, F("flowSetTemp"), F("Setpoint flow temperature"), DeviceValueUOM::DEGREES);
register_device_value(prefix, &pumpStatus_, DeviceValueType::BOOL, {}, F("pumpStatus"), F("Pump/Valve status"), DeviceValueUOM::NONE);
register_device_value(prefix, &status_, DeviceValueType::INT, {}, F("status"), F("Current status"), DeviceValueUOM::NONE);
}
// check to see if values have been updated
bool Mixer::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// publish values via MQTT
// topic is mixer_data<id>
void Mixer::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values_format(Mqtt::mqtt_format(), json_data)) {
char topic[30];
if (type() == Type::HC) {
snprintf_P(topic, 30, PSTR("mixer_data_hc%d"), hc_);
} else {
snprintf_P(topic, 30, PSTR("mixer_data_wwc%d"), hc_);
}
Mqtt::publish(topic, doc.as<JsonObject>());
}
} else {
// format is HA or Nested. This is bundled together and sent in emsesp.cpp
export_values_format(Mqtt::mqtt_format(), json);
}
}
// publish config topic for HA MQTT Discovery
void Mixer::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// publish HA config
bool Mixer::publish_ha_config() {
// if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(pumpStatus_)) {
return;
return false;
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
char name[20];
snprintf_P(name, sizeof(name), PSTR("Mixer %02X"), device_id() - 0x20 + 1);
doc["name"] = name;
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
char uniq_id[20];
snprintf_P(uniq_id, sizeof(uniq_id), PSTR("mixer%02X"), device_id() - 0x20 + 1);
snprintf_P(uniq_id, sizeof(uniq_id), PSTR("Mixer%02X"), device_id() - 0x20 + 1);
doc["uniq_id"] = uniq_id;
doc["ic"] = FJSON("mdi:home-thermometer-outline");
char stat_t[50];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), System::hostname().c_str());
doc["stat_t"] = stat_t;
doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc
char name[20];
snprintf_P(name, sizeof(name), PSTR("Mixer %02X Type"), device_id() - 0x20 + 1);
doc["name"] = name;
doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc
JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Mixer");
dev["sw"] = EMSESP_APP_VERSION;
@@ -160,127 +107,45 @@ void Mixer::register_mqtt_ha_config() {
JsonArray ids = dev.createNestedArray("ids");
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');
if (type() == Type::HC) {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_hc%d/config"), hc_);
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
char hc_name[10];
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(flowTemp), device_type(), "flowTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(flowSetTemp), device_type(), "flowSetTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(pumpStatus), device_type(), "pumpStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(valveStatus), device_type(), "valveStatus", F_(percent), F_(iconpercent));
if (type_ == Type::HC) {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_hc%d/config"), System::hostname().c_str(), hc_);
} else {
// WWC
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_wwc%d/config"), hc_);
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
char wwc_name[10];
snprintf_P(wwc_name, sizeof(wwc_name), PSTR("wwc%d"), hc_);
Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(wwTemp), device_type(), "wwTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(pumpStatus), device_type(), "pumpStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(tempStatus), device_type(), "tempStatus", nullptr, nullptr);
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_wwc%d/config"), System::hostname().c_str(), hc_); // WWC
}
mqtt_ha_config_ = true; // done
}
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
bool Mixer::export_values(JsonObject & json) {
return export_values_format(Mqtt::Format::NESTED, json);
}
// creates JSON doc from values
// returns false if empty
bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) {
// check if there is data for the mixer unit
if (type() == Type::NONE) {
return 0;
}
JsonObject json_hc;
char hc_name[10]; // hc{1-4}
if (type() == Type::HC) {
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
if (mqtt_format == Mqtt::Format::SINGLE) {
json_hc = json;
json["type"] = FJSON("hc");
} else if (mqtt_format == Mqtt::Format::HA) {
json_hc = json.createNestedObject(hc_name);
json_hc["type"] = FJSON("hc");
} else {
json_hc = json.createNestedObject(hc_name);
}
if (Helpers::hasValue(flowTemp_)) {
json_hc["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowSetTemp_)) {
json_hc["flowSetTemp"] = flowSetTemp_;
}
if (Helpers::hasValue(pumpStatus_)) {
char s[7];
json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
json_hc["valveStatus"] = status_;
}
return json_hc.size();
}
// WWC
snprintf_P(hc_name, sizeof(hc_name), PSTR("wwc%d"), hc_);
if (mqtt_format == Mqtt::Format::SINGLE) {
json_hc = json;
json["type"] = FJSON("wwc");
} else if (mqtt_format == Mqtt::Format::HA) {
json_hc = json.createNestedObject(hc_name);
json_hc["type"] = FJSON("wwc");
} else {
json_hc = json.createNestedObject(hc_name);
}
if (Helpers::hasValue(flowTemp_)) {
json_hc["wwTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pumpStatus_)) {
char s[7];
json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
json_hc["tempStatus"] = status_;
}
return json_hc.size();
return true;
}
// heating circuits 0x02D7, 0x02D8 etc...
// 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<const Telegram> telegram) {
type(Type::HC);
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
changed_ |= telegram->read_value(flowSetTemp_, 5);
changed_ |= telegram->read_bitvalue(pumpStatus_, 0, 0);
changed_ |= telegram->read_value(status_, 2); // valve status
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));
has_update(telegram->read_value(status_, 2)); // valve status
}
// Mixer warm water loading/DHW - 0x0331, 0x0332
// 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<const Telegram> telegram) {
type(Type::WWC);
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
changed_ |= telegram->read_bitvalue(pumpStatus_, 2, 0);
changed_ |= telegram->read_value(status_, 11); // temp status
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
}
// Mixer IMP - 0x010C
// 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<const Telegram> telegram) {
type(Type::HC);
hc_ = device_id() - 0x20 + 1;
register_values(Type::HC, device_id() - 0x20);
// check if circuit is active, 0-off, 1-unmixed, 2-mixed
uint8_t ismixed = 0;
@@ -291,28 +156,27 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// do we have a mixed circuit
if (ismixed == 2) {
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
changed_ |= telegram->read_value(flowSetTemp_, 5);
changed_ |= telegram->read_value(status_, 2); // valve status
has_update(telegram->read_value(flowTemp_, 3)); // is * 10
has_update(telegram->read_value(flowSetTemp_, 5));
has_update(telegram->read_value(status_, 2)); // valve status
}
changed_ |= telegram->read_bitvalue(pumpStatus_, 1, 0); // pump is also in unmixed circuits
has_update(telegram->read_bitvalue(pumpStatus_, 1, 0)); // pump is also in unmixed circuits
}
// Mixer on a MM10 - 0xAB
// e.g. Mixer Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
// see also https://github.com/proddy/EMS-ESP/issues/386
void Mixer::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
type(Type::HC);
// 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
hc_ = device_id() - 0x20 + 1;
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
changed_ |= telegram->read_bitvalue(pumpStatus_, 3, 2); // is 0 or 0x64 (100%), check only bit 2
changed_ |= telegram->read_value(flowSetTemp_, 0);
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
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
has_update(telegram->read_value(flowSetTemp_, 0));
has_update(telegram->read_value(status_, 4)); // valve status -100 to 100
}
#pragma GCC diagnostic push
@@ -321,7 +185,7 @@ void Mixer::process_MMStatusMessage(std::shared_ptr<const Telegram> 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<const Telegram> telegram) {
hc_ = device_id() - 0x20 + 1;
register_values(Type::HC, device_id() - 0x20);
// pos 0: active FF = on
// pos 1: valve runtime 0C = 120 sec in units of 10 sec
}
@@ -329,7 +193,7 @@ void Mixer::process_MMConfigMessage(std::shared_ptr<const Telegram> 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<const Telegram> telegram) {
hc_ = device_id() - 0x20 + 1;
register_values(Type::HC, device_id() - 0x20);
// pos 0: flowtemp setpoint 1E = 30°C
// pos 1: position in %
}