From 921dd13b82f384a4f186300a0fbbea45395db4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Beye?= Date: Mon, 15 Sep 2025 21:58:31 +0200 Subject: [PATCH] feat(mqtt): Add missing deviceClass + stateClass attributes, update enums and fix units for HA --- .../ConsumableMonitoringCapabilityMqttHandle.js | 6 +++++- .../CurrentStatisticsCapabilityMqttHandle.js | 10 ++++++++-- .../TotalStatisticsCapabilityMqttHandle.js | 13 ++++++++++--- .../WifiConfigurationCapabilityMqttHandle.js | 4 +++- backend/lib/mqtt/common/Unit.js | 7 ++++--- backend/lib/mqtt/homeassistant/ComponentType.js | 14 ++++++++++++-- backend/lib/mqtt/homeassistant/DeviceClass.js | 13 +++++++++++-- backend/lib/mqtt/homeassistant/EntityCategory.js | 5 ++--- backend/lib/mqtt/homeassistant/StateClass.js | 13 +++++++++++++ backend/lib/mqtt/status/BatteryStateMqttHandle.js | 4 +++- 10 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 backend/lib/mqtt/homeassistant/StateClass.js diff --git a/backend/lib/mqtt/capabilities/ConsumableMonitoringCapabilityMqttHandle.js b/backend/lib/mqtt/capabilities/ConsumableMonitoringCapabilityMqttHandle.js index c6ebc911..e5afb7ca 100644 --- a/backend/lib/mqtt/capabilities/ConsumableMonitoringCapabilityMqttHandle.js +++ b/backend/lib/mqtt/capabilities/ConsumableMonitoringCapabilityMqttHandle.js @@ -3,10 +3,12 @@ const CapabilityMqttHandle = require("./CapabilityMqttHandle"); const Commands = require("../common/Commands"); const ComponentType = require("../homeassistant/ComponentType"); const DataType = require("../homie/DataType"); +const DeviceClass = require("../homeassistant/DeviceClass"); const EntityCategory = require("../homeassistant/EntityCategory"); const HassAnchor = require("../homeassistant/HassAnchor"); const InLineHassComponent = require("../homeassistant/components/InLineHassComponent"); const PropertyMqttHandle = require("../handles/PropertyMqttHandle"); +const StateClass = require("../homeassistant/StateClass"); const Unit = require("../common/Unit"); const ValetudoConsumable = require("../../entities/core/ValetudoConsumable"); @@ -124,7 +126,9 @@ class ConsumableMonitoringCapabilityMqttHandle extends CapabilityMqttHandle { ), unit_of_measurement: unit === ValetudoConsumable.UNITS.PERCENT ? Unit.PERCENT : Unit.MINUTES, icon: "mdi:progress-wrench", - entity_category: EntityCategory.DIAGNOSTIC + entity_category: EntityCategory.DIAGNOSTIC, + state_class: StateClass.MEASUREMENT, + device_class: unit === ValetudoConsumable.UNITS.MINUTES ? DeviceClass.DURATION : undefined }, topics: { "": this.controller.hassAnchorProvider.getAnchor( diff --git a/backend/lib/mqtt/capabilities/CurrentStatisticsCapabilityMqttHandle.js b/backend/lib/mqtt/capabilities/CurrentStatisticsCapabilityMqttHandle.js index e3233990..7c2e6b27 100644 --- a/backend/lib/mqtt/capabilities/CurrentStatisticsCapabilityMqttHandle.js +++ b/backend/lib/mqtt/capabilities/CurrentStatisticsCapabilityMqttHandle.js @@ -1,11 +1,13 @@ const CapabilityMqttHandle = require("./CapabilityMqttHandle"); const ComponentType = require("../homeassistant/ComponentType"); const DataType = require("../homie/DataType"); +const DeviceClass = require("../homeassistant/DeviceClass"); const EntityCategory = require("../homeassistant/EntityCategory"); const HassAnchor = require("../homeassistant/HassAnchor"); const InLineHassComponent = require("../homeassistant/components/InLineHassComponent"); const Logger = require("../../Logger"); const PropertyMqttHandle = require("../handles/PropertyMqttHandle"); +const StateClass = require("../homeassistant/StateClass"); const Unit = require("../common/Unit"); const ValetudoDataPoint = require("../../entities/core/ValetudoDataPoint"); @@ -56,7 +58,9 @@ class CurrentStatisticsCapabilityMqttHandle extends CapabilityMqttHandle { state_topic: prop.getBaseTopic(), icon: "mdi:equalizer", entity_category: EntityCategory.DIAGNOSTIC, - unit_of_measurement: Unit.SECONDS + unit_of_measurement: Unit.SECONDS, + device_class: DeviceClass.DURATION, + state_class: StateClass.MEASUREMENT } }) ); @@ -92,7 +96,9 @@ class CurrentStatisticsCapabilityMqttHandle extends CapabilityMqttHandle { state_topic: prop.getBaseTopic(), icon: "mdi:equalizer", entity_category: EntityCategory.DIAGNOSTIC, - unit_of_measurement: Unit.SQUARE_CENTIMETER + unit_of_measurement: Unit.SQUARE_CENTIMETER, + device_class: DeviceClass.AREA, + state_class: StateClass.MEASUREMENT } }) ); diff --git a/backend/lib/mqtt/capabilities/TotalStatisticsCapabilityMqttHandle.js b/backend/lib/mqtt/capabilities/TotalStatisticsCapabilityMqttHandle.js index a3559b11..f00b5e06 100644 --- a/backend/lib/mqtt/capabilities/TotalStatisticsCapabilityMqttHandle.js +++ b/backend/lib/mqtt/capabilities/TotalStatisticsCapabilityMqttHandle.js @@ -1,11 +1,13 @@ const CapabilityMqttHandle = require("./CapabilityMqttHandle"); const ComponentType = require("../homeassistant/ComponentType"); const DataType = require("../homie/DataType"); +const DeviceClass = require("../homeassistant/DeviceClass"); const EntityCategory = require("../homeassistant/EntityCategory"); const HassAnchor = require("../homeassistant/HassAnchor"); const InLineHassComponent = require("../homeassistant/components/InLineHassComponent"); const Logger = require("../../Logger"); const PropertyMqttHandle = require("../handles/PropertyMqttHandle"); +const StateClass = require("../homeassistant/StateClass"); const Unit = require("../common/Unit"); const ValetudoDataPoint = require("../../entities/core/ValetudoDataPoint"); @@ -56,7 +58,9 @@ class TotalStatisticsCapabilityMqttHandle extends CapabilityMqttHandle { state_topic: prop.getBaseTopic(), icon: "mdi:equalizer", entity_category: EntityCategory.DIAGNOSTIC, - unit_of_measurement: Unit.SECONDS + unit_of_measurement: Unit.SECONDS, + device_class: DeviceClass.DURATION, + state_class: StateClass.TOTAL_INCREASING } }) ); @@ -92,7 +96,9 @@ class TotalStatisticsCapabilityMqttHandle extends CapabilityMqttHandle { state_topic: prop.getBaseTopic(), icon: "mdi:equalizer", entity_category: EntityCategory.DIAGNOSTIC, - unit_of_measurement: Unit.SQUARE_CENTIMETER + unit_of_measurement: Unit.SQUARE_CENTIMETER, + device_class: DeviceClass.AREA, + state_class: StateClass.TOTAL_INCREASING } }) ); @@ -126,7 +132,8 @@ class TotalStatisticsCapabilityMqttHandle extends CapabilityMqttHandle { autoconf: { state_topic: prop.getBaseTopic(), icon: "mdi:equalizer", - entity_category: EntityCategory.DIAGNOSTIC + entity_category: EntityCategory.DIAGNOSTIC, + state_class: StateClass.TOTAL_INCREASING } }) ); diff --git a/backend/lib/mqtt/capabilities/WifiConfigurationCapabilityMqttHandle.js b/backend/lib/mqtt/capabilities/WifiConfigurationCapabilityMqttHandle.js index 0c78bb22..0b6caf2c 100644 --- a/backend/lib/mqtt/capabilities/WifiConfigurationCapabilityMqttHandle.js +++ b/backend/lib/mqtt/capabilities/WifiConfigurationCapabilityMqttHandle.js @@ -6,6 +6,7 @@ const EntityCategory = require("../homeassistant/EntityCategory"); const HassAnchor = require("../homeassistant/HassAnchor"); const InLineHassComponent = require("../homeassistant/components/InLineHassComponent"); const PropertyMqttHandle = require("../handles/PropertyMqttHandle"); +const StateClass = require("../homeassistant/StateClass"); const Unit = require("../common/Unit"); class WifiConfigurationCapabilityMqttHandle extends CapabilityMqttHandle { @@ -98,7 +99,8 @@ class WifiConfigurationCapabilityMqttHandle extends CapabilityMqttHandle { ), json_attributes_template: "{{ value_json.attributes | to_json }}", entity_category: EntityCategory.DIAGNOSTIC, - device_class: DeviceClass.SIGNAL_STRENGTH + device_class: DeviceClass.SIGNAL_STRENGTH, + state_class: StateClass.MEASUREMENT }, topics: { "": { diff --git a/backend/lib/mqtt/common/Unit.js b/backend/lib/mqtt/common/Unit.js index fc40ebd9..623bd2e9 100644 --- a/backend/lib/mqtt/common/Unit.js +++ b/backend/lib/mqtt/common/Unit.js @@ -22,9 +22,10 @@ const Unit = Object.freeze({ PASCAL: "Pa", PSI: "psi", AMOUNT: "#", - // Not part of the specification, but useful - SECONDS: "seconds", - MINUTES: "minutes", + + // Not part of the homie specification + SECONDS: "s", + MINUTES: "min", SQUARE_CENTIMETER: "cm²", SQUARE_METER: "m²", CUBE_METER: "m³", diff --git a/backend/lib/mqtt/homeassistant/ComponentType.js b/backend/lib/mqtt/homeassistant/ComponentType.js index ca402df4..d9683dbd 100644 --- a/backend/lib/mqtt/homeassistant/ComponentType.js +++ b/backend/lib/mqtt/homeassistant/ComponentType.js @@ -1,5 +1,5 @@ /** - * Retrieved from https://www.home-assistant.io/docs/mqtt/discovery/ on 2021-04-05. + * Retrieved from https://www.home-assistant.io/docs/mqtt/discovery/ on 2025-09-15. * * @enum {string} */ @@ -8,20 +8,30 @@ const ComponentType = Object.freeze({ BINARY_SENSOR: "binary_sensor", BUTTON: "button", CAMERA: "camera", + CLIMATE: "climate", COVER: "cover", DEVICE_TRACKER: "device_tracker", DEVICE_TRIGGER: "device_trigger", + EVENT: "event", FAN: "fan", - HVAC: "climate", + HUMIDIFIER: "humidifier", + IMAGE: "image", + LAWN_MOWER: "lawn_mower", LIGHT: "light", LOCK: "lock", + NOTIFY: "notify", NUMBER: "number", SCENE: "scene", SELECT: "select", SENSOR: "sensor", + SIREN: "siren", SWITCH: "switch", TAG_SCANNER: "tag", + TEXT: "text", + UPDATE: "update", VACUUM: "vacuum", + VALVE: "valve", + WATER_HEATER: "water_heater", }); module.exports = ComponentType; diff --git a/backend/lib/mqtt/homeassistant/DeviceClass.js b/backend/lib/mqtt/homeassistant/DeviceClass.js index edb7e387..69532d75 100644 --- a/backend/lib/mqtt/homeassistant/DeviceClass.js +++ b/backend/lib/mqtt/homeassistant/DeviceClass.js @@ -1,5 +1,5 @@ /** - * Retrieved from https://github.com/home-assistant/core/blob/8b1cfbc46cc79e676f75dfa4da097a2e47375b6f/homeassistant/components/sensor/const.py#L64-L416 on 2023-10-25. + * Retrieved from https://github.com/home-assistant/core/blob/4c22264b13bf4f7428ab9e911d58725dee512c78/homeassistant/components/sensor/const.py on 2025-09-15. * * See also https://developers.home-assistant.io/docs/core/entity/#generic-properties * @@ -9,18 +9,24 @@ const DeviceClass = Object.freeze({ DATE: "date", ENUM: "enum", TIMESTAMP: "timestamp", + + ABSOLUTE_HUMIDITY: "absolute_humidity", APPARENT_POWER: "apparent_power", AQI: "aqi", + AREA: "area", ATMOSPHERIC_PRESSURE: "atmospheric_pressure", BATTERY: "battery", + BLOOD_GLUCOSE_CONCENTRATION: "blood_glucose_concentration", CO: "carbon_monoxide", CO2: "carbon_dioxide", + CONDUCTIVITY: "conductivity", CURRENT: "current", DATA_RATE: "data_rate", DATA_SIZE: "data_size", DISTANCE: "distance", DURATION: "duration", ENERGY: "energy", + ENERGY_DISTANCE: "energy_distance", ENERGY_STORAGE: "energy_storage", FREQUENCY: "frequency", GAS: "gas", @@ -37,11 +43,12 @@ const DeviceClass = Object.freeze({ PM1: "pm1", PM10: "pm10", PM25: "pm25", - POWER_FACTOR: "power_factor", POWER: "power", + POWER_FACTOR: "power_factor", PRECIPITATION: "precipitation", PRECIPITATION_INTENSITY: "precipitation_intensity", PRESSURE: "pressure", + REACTIVE_ENERGY: "reactive_energy", REACTIVE_POWER: "reactive_power", SIGNAL_STRENGTH: "signal_strength", SOUND_PRESSURE: "sound_pressure", @@ -52,9 +59,11 @@ const DeviceClass = Object.freeze({ VOLATILE_ORGANIC_COMPOUNDS_PARTS: "volatile_organic_compounds_parts", VOLTAGE: "voltage", VOLUME: "volume", + VOLUME_FLOW_RATE: "volume_flow_rate", VOLUME_STORAGE: "volume_storage", WATER: "water", WEIGHT: "weight", + WIND_DIRECTION: "wind_direction", WIND_SPEED: "wind_speed" }); diff --git a/backend/lib/mqtt/homeassistant/EntityCategory.js b/backend/lib/mqtt/homeassistant/EntityCategory.js index 07c4e40e..e2cfa733 100644 --- a/backend/lib/mqtt/homeassistant/EntityCategory.js +++ b/backend/lib/mqtt/homeassistant/EntityCategory.js @@ -1,5 +1,5 @@ /** - * Retrieved from https://github.com/home-assistant/core/blob/7abf79d1f991d58051fea0afe56e714ce60d7fdb/homeassistant/const.py#L715-L717 on 2021-11-06. + * Retrieved from https://github.com/home-assistant/core/blob/c5fc1de3df3db1250e1e21d727bb5849408964a7/homeassistant/const.py#L1074-L1087 on 2025-09-15. * * See also https://developers.home-assistant.io/docs/core/entity/#generic-properties * @@ -7,8 +7,7 @@ */ const EntityCategory = Object.freeze({ CONFIG: "config", - DIAGNOSTIC: "diagnostic", - SYSTEM: "system" + DIAGNOSTIC: "diagnostic" }); module.exports = EntityCategory; diff --git a/backend/lib/mqtt/homeassistant/StateClass.js b/backend/lib/mqtt/homeassistant/StateClass.js new file mode 100644 index 00000000..19001b7c --- /dev/null +++ b/backend/lib/mqtt/homeassistant/StateClass.js @@ -0,0 +1,13 @@ +/** + * Retrieved from https://github.com/home-assistant/core/blob/c5fc1de3df3db1250e1e21d727bb5849408964a7/homeassistant/components/sensor/const.py#L509-L526 on 2025-09-15. + * + * @enum {string} + */ +const StateClass = Object.freeze({ + MEASUREMENT: "measurement", + MEASUREMENT_ANGLE: "measurement_angle", + TOTAL: "total", + TOTAL_INCREASING: "total_increasing", +}); + +module.exports = StateClass; diff --git a/backend/lib/mqtt/status/BatteryStateMqttHandle.js b/backend/lib/mqtt/status/BatteryStateMqttHandle.js index ff73570b..c7440d1d 100644 --- a/backend/lib/mqtt/status/BatteryStateMqttHandle.js +++ b/backend/lib/mqtt/status/BatteryStateMqttHandle.js @@ -6,6 +6,7 @@ const InLineHassComponent = require("../homeassistant/components/InLineHassCompo const PropertyMqttHandle = require("../handles/PropertyMqttHandle"); const RobotStateNodeMqttHandle = require("../handles/RobotStateNodeMqttHandle"); const stateAttrs = require("../../entities/state/attributes"); +const StateClass = require("../homeassistant/StateClass"); const Unit = require("../common/Unit"); class BatteryStateMqttHandle extends RobotStateNodeMqttHandle { @@ -51,7 +52,8 @@ class BatteryStateMqttHandle extends RobotStateNodeMqttHandle { icon: "mdi:battery", entity_category: EntityCategory.DIAGNOSTIC, unit_of_measurement: Unit.PERCENT, - device_class: DeviceClass.BATTERY + device_class: DeviceClass.BATTERY, + state_class: StateClass.MEASUREMENT } }) );