diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 3ffef4fdf..f2169a253 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -4,6 +4,8 @@ ## **IMPORTANT! BREAKING CHANGES** +Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`...). You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer. + ## Added - humidity for ventilation devices @@ -11,7 +13,8 @@ - names for BC400, GB192i.2, read temperatures for low loss header and heatblock [#1317](https://github.com/emsesp/EMS-ESP32/discussions/1317) - option for `forceheatingoff` [#1262](https://github.com/emsesp/EMS-ESP32/issues/1262) - remote thermostat emulation RC100H for RC3xx [#1278](https://github.com/emsesp/EMS-ESP32/discussions/1278) -- publish time for shower +- shower_data MQTT payload contains the timestamp [#1329](https://github.com/emsesp/EMS-ESP32/issues/1329) +- HA discovery for writeable text entities [#1337](https://github.com/emsesp/EMS-ESP32/pull/1377) - autodetect board_profile, store in nvs, add telnet command option, add E32V2 - heatpump high res energy counters [#1348, #1349. #1350](https://github.com/emsesp/EMS-ESP32/issues/1348) - optional bssid in network settings diff --git a/interface/package.json b/interface/package.json index bcc223484..55aa635ae 100644 --- a/interface/package.json +++ b/interface/package.json @@ -29,7 +29,7 @@ "@types/imagemin": "^8.0.3", "@types/lodash-es": "^4.17.10", "@types/node": "^20.8.10", - "@types/react": "^18.2.34", + "@types/react": "^18.2.35", "@types/react-dom": "^18.2.14", "@types/react-router-dom": "^5.3.3", "alova": "^2.13.1", @@ -54,7 +54,7 @@ "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", "concurrently": "^8.2.2", - "eslint": "^8.52.0", + "eslint": "^8.53.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.0.0", @@ -65,7 +65,7 @@ "eslint-plugin-prettier": "alpha", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "preact": "^10.18.1", + "preact": "^10.18.2", "prettier": "^3.0.3", "rollup-plugin-visualizer": "^5.9.2", "terser": "^5.24.0", diff --git a/interface/yarn.lock b/interface/yarn.lock index cc0410c85..8aaf2043c 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -641,9 +641,9 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.1.2": - version: 2.1.2 - resolution: "@eslint/eslintrc@npm:2.1.2" +"@eslint/eslintrc@npm:^2.1.3": + version: 2.1.3 + resolution: "@eslint/eslintrc@npm:2.1.3" dependencies: ajv: "npm:^6.12.4" debug: "npm:^4.3.2" @@ -654,14 +654,14 @@ __metadata: js-yaml: "npm:^4.1.0" minimatch: "npm:^3.1.2" strip-json-comments: "npm:^3.1.1" - checksum: fa25638f2666cac6810f98ee7d0f4b912f191806467c1b40d72bac759fffef0b3357f12a1869817286837b258e4de3517e0c7408520e156ca860fc53a1fbaed9 + checksum: 77b70a89232fe702c2f765b5b92970f5e4224b55363b923238b996c66fcd991504f40d3663c0543ae17d6c5049ab9b07ab90b65d7601e6f25e8bcd4caf69ac75 languageName: node linkType: hard -"@eslint/js@npm:8.52.0": - version: 8.52.0 - resolution: "@eslint/js@npm:8.52.0" - checksum: 86beff213d0ae4ced203a922b74e2cc4d767d109e7815f985bf648946ba072198977102e32afc9fa04f7825a6de83a831874f6b6675ba0c1d0743ade2dc2d53d +"@eslint/js@npm:8.53.0": + version: 8.53.0 + resolution: "@eslint/js@npm:8.53.0" + checksum: a372d55aa2bbe0d9399acc8de3c892dcfe507fd914d29fde6826ae54a13452619be626aa7eb70b1ec4d4da5302b6ed8e8ac9bf1f830003f15c0ad56c30b4f520 languageName: node linkType: hard @@ -1370,14 +1370,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.34": - version: 18.2.34 - resolution: "@types/react@npm:18.2.34" +"@types/react@npm:^18.2.35": + version: 18.2.35 + resolution: "@types/react@npm:18.2.35" dependencies: "@types/prop-types": "npm:*" "@types/scheduler": "npm:*" csstype: "npm:^3.0.2" - checksum: 6d16f86b384e829edc3710b1dd9ec4eb1d6b26bc079c5cf605bd0cbf77ae6224f15c76949afadb7f53df4544cfe4025c1111fbe36732cd7f660a320fbc2e5866 + checksum: 3c8c752d21856f74ddb96b9dd13cdd70c0ce1522808f20511f0220f392ea727fae9388db4da3ebe317d9bac98a0d930566d4277b22e92c1cad414429739e1d76 languageName: node linkType: hard @@ -1558,7 +1558,7 @@ __metadata: "@types/imagemin": "npm:^8.0.3" "@types/lodash-es": "npm:^4.17.10" "@types/node": "npm:^20.8.10" - "@types/react": "npm:^18.2.34" + "@types/react": "npm:^18.2.35" "@types/react-dom": "npm:^18.2.14" "@types/react-router-dom": "npm:^5.3.3" "@typescript-eslint/eslint-plugin": "npm:^6.9.1" @@ -1566,7 +1566,7 @@ __metadata: alova: "npm:^2.13.1" async-validator: "npm:^4.2.5" concurrently: "npm:^8.2.2" - eslint: "npm:^8.52.0" + eslint: "npm:^8.53.0" eslint-config-airbnb: "npm:^19.0.4" eslint-config-airbnb-typescript: "npm:^17.1.0" eslint-config-prettier: "npm:^9.0.0" @@ -1581,7 +1581,7 @@ __metadata: jwt-decode: "npm:^4.0.0" lodash-es: "npm:^4.17.21" mime-types: "npm:^2.1.35" - preact: "npm:^10.18.1" + preact: "npm:^10.18.2" prettier: "npm:^3.0.3" react: "npm:latest" react-dom: "npm:latest" @@ -3654,14 +3654,14 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.52.0": - version: 8.52.0 - resolution: "eslint@npm:8.52.0" +"eslint@npm:^8.53.0": + version: 8.53.0 + resolution: "eslint@npm:8.53.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^2.1.2" - "@eslint/js": "npm:8.52.0" + "@eslint/eslintrc": "npm:^2.1.3" + "@eslint/js": "npm:8.53.0" "@humanwhocodes/config-array": "npm:^0.11.13" "@humanwhocodes/module-importer": "npm:^1.0.1" "@nodelib/fs.walk": "npm:^1.2.8" @@ -3698,7 +3698,7 @@ __metadata: text-table: "npm:^0.2.0" bin: eslint: bin/eslint.js - checksum: 01784ab15351d749bc95446039ed7acd5124f7cc84acdbf98c7199272eae06212a8f3ea4a9b47e7cc54ab17ca094c3a664bbfc3002c7de27936220e278b5028a + checksum: e305a71ce2b9a8631b293266fe53e346c76f28bc8d004af33f10e537cf133db1fb87af3599376e70ed6e0f89a78be10c4f08ddd0c1c9c0c497cd143b4a270420 languageName: node linkType: hard @@ -6661,10 +6661,10 @@ __metadata: languageName: node linkType: hard -"preact@npm:^10.18.1": - version: 10.18.1 - resolution: "preact@npm:10.18.1" - checksum: 587c4634b310efc306ef9315f849b8e4ff538435087a1dca626e1394b98570af1ecdc254b7f0bb3060fc7ab87456c5f891f9b8a167d5c34dbbcfcf60b6e993f4 +"preact@npm:^10.18.2": + version: 10.18.2 + resolution: "preact@npm:10.18.2" + checksum: c7dcd6ea812adb0bdc215366b14aadc44724b6dd6c4e9aadd986126d94abde62f3e02e18d6157a9984873be9877f206c0afa10a09346178c4c828a103a66a0e1 languageName: node linkType: hard diff --git a/src/devices/alert.cpp b/src/devices/alert.cpp index bb1ef3af3..c297192c2 100644 --- a/src/devices/alert.cpp +++ b/src/devices/alert.cpp @@ -24,7 +24,6 @@ REGISTER_FACTORY(Alert, EMSdevice::DeviceType::ALERT); Alert::Alert(uint8_t device_type, uint8_t device_id, uint8_t product_id, const char * version, const char * name, uint8_t flags, uint8_t brand) : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { - // EM10, device-id 0x12, listens to error messages, measures voltage input and send 0x1A to the boiler. // values already shown in boiler // see https://github.com/emsesp/EMS-ESP32/issues/575 @@ -32,11 +31,10 @@ Alert::Alert(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setFlowTemp_, DeviceValueType::UINT, FL_(setFlowTemp), DeviceValueUOM::DEGREES); register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &setBurnPow_, DeviceValueType::UINT, FL_(setBurnPow), DeviceValueUOM::PERCENT); - } // UBASetPoint 0x1A void Alert::process_UBASetPoints(std::shared_ptr telegram) { - has_update(telegram, setFlowTemp_, 0); // boiler set temp from thermostat - has_update(telegram, setBurnPow_, 1); // max burner power in % + has_update(telegram, setFlowTemp_, 0); // boiler set temp from thermostat + has_update(telegram, setBurnPow_, 1); // max burner power in % } } // namespace emsesp \ No newline at end of file diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 330eb6cef..199dfeb51 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -1269,7 +1269,7 @@ void Boiler::process_UBAMonitorFastPlus(std::shared_ptr telegram has_update(telegram, heatblock_, 23); // see #1317 has_update(telegram, headertemp_, 25); // see #1317 //has_update(telegram, temperatur_, 27); // unknown temperature - telegram->read_value(exhaustTemp1_ , 31); + telegram->read_value(exhaustTemp1_, 31); if (Helpers::hasValue(exhaustTemp1_)) { has_update(exhaustTemp_, exhaustTemp1_); } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index d700f4f32..bf9a9fad9 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -753,7 +753,8 @@ bool Mqtt::publish_ha_sensor_config(DeviceValue & dv, const char * model, const // determine if we're creating the command topics which we use special HA configs // unless the entity has been marked as read-only and so it'll default to using the sensor/ type - bool has_cmd = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY); + // or we're dealing with Energy sensors that must have "diagnostic" as an entity category (e.g. negheat & nrgww) + bool has_cmd = dv.has_cmd && !dv.has_state(DeviceValueState::DV_READONLY) && (dv.uom != DeviceValueUOM::KWH); return publish_ha_sensor_config(dv.type, dv.tag, @@ -846,7 +847,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev char config_topic[70]; snprintf(config_topic, sizeof(config_topic), "%s/%s_%s/config", mqtt_basename_.c_str(), device_name, entity_with_tag); - bool set_ha_classes = false; // set to true if we want to set the state class and device class + bool readonly_sensors = true; // create the topic // depending on the type and whether the device entity is writable (a command) @@ -859,42 +860,41 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev case DeviceValueType::UINT: case DeviceValueType::SHORT: case DeviceValueType::USHORT: + case DeviceValueType::ULONG: + // number - https://www.home-assistant.io/integrations/number.mqtt + // Domoticz does not support number, use sensor if (discovery_type() == discoveryType::HOMEASSISTANT) { - // number - https://www.home-assistant.io/integrations/number.mqtt snprintf(topic, sizeof(topic), "number/%s", config_topic); + readonly_sensors = false; } else { - // Domoticz does not support number, use sensor snprintf(topic, sizeof(topic), "sensor/%s", config_topic); } break; case DeviceValueType::BOOL: // switch - https://www.home-assistant.io/integrations/switch.mqtt snprintf(topic, sizeof(topic), "switch/%s", config_topic); + readonly_sensors = false; break; case DeviceValueType::ENUM: // select - https://www.home-assistant.io/integrations/select.mqtt snprintf(topic, sizeof(topic), "select/%s", config_topic); - break; - case DeviceValueType::ULONG: - snprintf(topic, sizeof(topic), "sensor/%s", config_topic); - set_ha_classes = true; + readonly_sensors = false; break; case DeviceValueType::STRING: + // text - https://www.home-assistant.io/integrations/text.mqtt snprintf(topic, sizeof(topic), "text/%s", config_topic); // e.g. set_datetime, set_holiday, set_wwswitchtime + readonly_sensors = false; break; default: - // plain old sensor - snprintf(topic, sizeof(topic), "sensor/%s", config_topic); + // plain old sensor, and make read-only break; } - } else { - set_ha_classes = true; // these are Read only sensors. We can set the device class and state class - // plain old read only device entity - if (type == DeviceValueType::BOOL) { - snprintf(topic, sizeof(topic), "binary_sensor/%s", config_topic); // binary sensor (for booleans) - } else { - snprintf(topic, sizeof(topic), "sensor/%s", config_topic); // normal HA sensor - } + } + + // For read-only sensors there are either sensor or binary_sensor + // for both we also set the device class and state class + if (readonly_sensors) { + snprintf(topic, sizeof(topic), (type == DeviceValueType::BOOL) ? "binary_sensor/%s" : "sensor/%s", config_topic); // binary sensor (for booleans) } // if we're asking to remove this topic, send an empty payload and exit @@ -910,13 +910,15 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev doc["obj_id"] = uniq_id; // same as unique_id const char * ic_ha = "ic"; // icon - only set this if there is no device class - const char * sc_ha = "stat_cla"; // state class const char * uom_ha = "unit_of_meas"; // unit of measure char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet // we add the command topic parameter for commands if (has_cmd) { + // add category + doc["ent_cat"] = "config"; // for writeable entities, like switch, number, text, select + char command_topic[MQTT_TOPIC_MAX_SIZE]; // add command topic if (tag >= DeviceValueTAG::TAG_HC1) { @@ -1038,12 +1040,16 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev doc[uom_ha] = EMSdevice::uom_to_string(uom); // default } } + doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}"; - // this next section is adding the state class, device class and sometimes the icon - // used for Sensor and Binary Sensor Entities in HA - if (set_ha_classes) { - const char * dc_ha = "dev_cla"; // device class + // Add the state class, device class and sometimes the icon. Used only for read-only sensors Sensor and Binary Sensor + if (readonly_sensors) { + // first set the catagory + doc["ent_cat"] = "diagnostic"; + + const char * dc_ha = "dev_cla"; // device class + const char * sc_ha = "stat_cla"; // state class switch (uom) { case DeviceValueUOM::DEGREES: @@ -1079,11 +1085,11 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev } else { doc[sc_ha] = F_(measurement); } - doc[dc_ha] = "energy"; // no icon needed + doc[dc_ha] = "energy"; break; case DeviceValueUOM::KWH: doc[sc_ha] = F_(total_increasing); - doc[dc_ha] = "energy"; // no icon needed + doc[dc_ha] = "energy"; break; case DeviceValueUOM::UA: doc[ic_ha] = F_(iconua); @@ -1127,15 +1133,8 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev } } - // add category "diagnostic" for system entities - // config for writeable entities, like switches. diagnostic for read only sensors. - doc["ent_cat"] = (has_cmd && !set_ha_classes) ? "config" : "diagnostic"; - - // add the dev json object to the end - doc["dev"] = dev_json; - - // add "availability" section - add_avty_to_doc(stat_t, doc.as(), val_cond); + doc["dev"] = dev_json; // add the dev json object to the end + add_avty_to_doc(stat_t, doc.as(), val_cond); // add "availability" section return queue_ha(topic, doc.as()); } @@ -1309,5 +1308,4 @@ void Mqtt::add_avty_to_doc(const char * state_t, const JsonObject & doc, const c doc["avty_mode"] = "all"; } - -} // namespace emsesp +} // namespace emsesp \ No newline at end of file diff --git a/src/roomcontrol.cpp b/src/roomcontrol.cpp index b16407867..f3faece46 100644 --- a/src/roomcontrol.cpp +++ b/src/roomcontrol.cpp @@ -108,7 +108,7 @@ void Roomctrl::send(const uint8_t addr) { } else if (type_ == FB10) { rc_time_[hc] = uuid::get_uptime(); temperature(addr, 0x10, hc); // send to master-thermostat (https://github.com/emsesp/EMS-ESP32/issues/336) - } else { // type==RC20 + } else { // type==RC20 rc_time_[hc] = uuid::get_uptime(); temperature(addr, 0x00, hc); // send to all } diff --git a/src/version.h b/src/version.h index 6aabb9bbe..8f4e48e87 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.3-dev.5b" +#define EMSESP_APP_VERSION "3.6.3-test.6" diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 06714343b..59272fc50 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -36,7 +36,9 @@ void WebStatusService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { switch (event) { case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - EMSESP::logger().warning("WiFi disconnected. Reason: %s (%d)", disconnectReason(info.wifi_sta_disconnected.reason), info.wifi_sta_disconnected.reason); // IDF 4.0 + EMSESP::logger().warning("WiFi disconnected. Reason: %s (%d)", + disconnectReason(info.wifi_sta_disconnected.reason), + info.wifi_sta_disconnected.reason); // IDF 4.0 EMSESP::system_.has_ipv6(false); break;