diff --git a/README.md b/README.md index f2c5972..afeaeae 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,10 @@ todo: service names and not just raw service identifiers? | Fan | [fan_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/fan_ctrl.ts) | | ✅ | [Fan](https://www.home-assistant.io/integrations/fan/) | | Light | [light](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/light.ts) | | | | Media player | [media_player](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/media_player.ts) | | ✅ | [Select](https://www.home-assistant.io/integrations/select/), [Number](https://www.home-assistant.io/integrations/number/), [Switch](https://www.home-assistant.io/integrations/switch/), [Image](https://www.home-assistant.io/integrations/image/), [Sensor](https://www.home-assistant.io/integrations/sensor/) | -| Meter (electricity) | [meter_elec](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/meter_elec.ts) | [HAN-Sensor](https://www.futurehome.io/en/shop/han-sensor) | | | +| Meter (electricity) | [meter_elec](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_gas](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_water](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_heating](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_cooling](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts) | [HAN-Sensor](https://www.futurehome.io/en/shop/han-sensor) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Button](https://www.home-assistant.io/integrations/button/) | | Binary switch | [out_bin_switch](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/out_bin_switch.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | ✅ | [Switch](https://www.home-assistant.io/integrations/switch/) | | Level switch | [out_lvl_switch](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/out_lvl_switch.ts) | [Smart LED Dimmer](https://www.futurehome.io/en_no/shop/smart-led-dimmer-polar-white) | ✅ | [Number](https://www.home-assistant.io/integrations/number/) | -| ??? | [power_regulator](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/power_regulator.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | | | -| ??? | [scene_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/scene_ctrl.ts) | [Modusbryter](https://www.futurehome.io/en_no/shop/modeswitch-white) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Select](https://www.home-assistant.io/integrations/select/) | +| Button | [scene_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/scene_ctrl.ts) | [Modusbryter](https://www.futurehome.io/en_no/shop/modeswitch-white) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Select](https://www.home-assistant.io/integrations/select/) | | Binary sensor | [sensor_contact](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts), [sensor_presence](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts) | | ✅ | [Binary sensor](https://www.home-assistant.io/integrations/binary_sensor/) | | Numeric sensor | [sensor_accelx](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accely](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accelz](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_anglepos](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_atmo](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_baro](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co2](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_current](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_dew](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_direct](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_distance](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_elresist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_freq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gust](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_humid](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_lumin](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_moist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_noise](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_power](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rain](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rotation](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicmag](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_solarrad](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tank](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_temp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tidelvl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_uv](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_veloc](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_voltage](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watpressure](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wattemp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_weight](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wind](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/) | | Siren | [siren_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/siren_ctrl.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/) | @@ -69,10 +68,6 @@ todo: service names and not just raw service identifiers? | Service | Description | | --- | --- | -| meter_cooling | | -| meter_gas | | -| meter_water | | -| meter_heating | | | schedule | | | schedule_entry | | | sound_switch | | @@ -98,6 +93,7 @@ todo: service names and not just raw service identifiers? | indicator_ctrl | ✅ | Identify devices | | ota | | | | parameters | | | +| Power regulator | [power_regulator](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/power_regulator.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | | | | technology_specific | | | | time | | | | version | | | diff --git a/futurehome/CHANGELOG.md b/futurehome/CHANGELOG.md index 4dc1575..9ee7b30 100644 --- a/futurehome/CHANGELOG.md +++ b/futurehome/CHANGELOG.md @@ -4,6 +4,7 @@ - Improved MQTT components interfaces. - Refactored sensors. +- Added support for 'meter_*' services (electricity meter, gas meter, water meter, heating meter, cooling meter). ## 0.1.5 (25.07.2025) diff --git a/futurehome/README.md b/futurehome/README.md index 8fa6cb3..69131b1 100644 --- a/futurehome/README.md +++ b/futurehome/README.md @@ -52,11 +52,10 @@ todo: service names and not just raw service identifiers? | Fan | [fan_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/fan_ctrl.ts) | | ✅ | [Fan](https://www.home-assistant.io/integrations/fan/) | | Light | [light](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/light.ts) | | | | Media player | [media_player](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/media_player.ts) | | ✅ | [Select](https://www.home-assistant.io/integrations/select/), [Number](https://www.home-assistant.io/integrations/number/), [Switch](https://www.home-assistant.io/integrations/switch/), [Image](https://www.home-assistant.io/integrations/image/), [Sensor](https://www.home-assistant.io/integrations/sensor/) | -| Meter (electricity) | [meter_elec](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/meter_elec.ts) | [HAN-Sensor](https://www.futurehome.io/en/shop/han-sensor) | | | +| Meter (electricity) | [meter_elec](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_gas](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_water](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_heating](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts), [meter_cooling](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_meter.ts) | [HAN-Sensor](https://www.futurehome.io/en/shop/han-sensor) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Button](https://www.home-assistant.io/integrations/button/) | | Binary switch | [out_bin_switch](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/out_bin_switch.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | ✅ | [Switch](https://www.home-assistant.io/integrations/switch/) | | Level switch | [out_lvl_switch](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/out_lvl_switch.ts) | [Smart LED Dimmer](https://www.futurehome.io/en_no/shop/smart-led-dimmer-polar-white) | ✅ | [Number](https://www.home-assistant.io/integrations/number/) | -| ??? | [power_regulator](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/power_regulator.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | | | -| ??? | [scene_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/scene_ctrl.ts) | [Modusbryter](https://www.futurehome.io/en_no/shop/modeswitch-white) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Select](https://www.home-assistant.io/integrations/select/) | +| Button | [scene_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/scene_ctrl.ts) | [Modusbryter](https://www.futurehome.io/en_no/shop/modeswitch-white) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/), [Select](https://www.home-assistant.io/integrations/select/) | | Binary sensor | [sensor_contact](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts), [sensor_presence](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_binary.ts) | | ✅ | [Binary sensor](https://www.home-assistant.io/integrations/binary_sensor/) | | Numeric sensor | [sensor_accelx](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accely](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_accelz](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_airq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_anglepos](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_atmo](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_baro](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_co2](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_current](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_dew](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_direct](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_distance](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_elresist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_freq](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_gust](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_humid](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_lumin](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_moist](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_noise](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_power](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rain](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_rotation](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicint](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_seismicmag](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_solarrad](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tank](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_temp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_tidelvl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_uv](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_veloc](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_voltage](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watflow](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_watpressure](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wattemp](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_weight](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts), [sensor_wind](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/_sensor_numeric.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Sensor](https://www.home-assistant.io/integrations/sensor/) | | Siren | [siren_ctrl](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/siren_ctrl.ts) | [Brannvarsler](https://www.futurehome.io/en_no/shop/brannvarsler-230v) | ✅ | [Siren](https://www.home-assistant.io/integrations/siren/) | @@ -68,10 +67,6 @@ todo: service names and not just raw service identifiers? | Service | Description | | --- | --- | -| meter_cooling | | -| meter_gas | | -| meter_water | | -| meter_heating | | | schedule | | | schedule_entry | | | sound_switch | | @@ -97,6 +92,7 @@ todo: service names and not just raw service identifiers? | indicator_ctrl | ✅ | Identify devices | | ota | | | | parameters | | | +| Power regulator | [power_regulator](https://github.com/adrianjagielak/home-assistant-futurehome/blob/master/futurehome/src/services/power_regulator.ts) | [16A Puck Relé](https://www.futurehome.io/en_no/shop/puck-relay-16a) | | | | technology_specific | | | | time | | | | version | | | diff --git a/futurehome/src/ha/mqtt_components/_base_component.ts b/futurehome/src/ha/mqtt_components/_base_component.ts index 342fb65..ea0c3b0 100644 --- a/futurehome/src/ha/mqtt_components/_base_component.ts +++ b/futurehome/src/ha/mqtt_components/_base_component.ts @@ -12,7 +12,7 @@ export interface BaseComponent { * The [category](https://developers.home-assistant.io/docs/core/entity#generic-properties) of the entity. * When set, the entity category must be `diagnostic` for sensors. */ - entity_category?: string; + entity_category?: null | 'config' | 'diagnostic'; /** * Picture URL for the entity. diff --git a/futurehome/src/ha/publish_device.ts b/futurehome/src/ha/publish_device.ts index 210fab2..14fcf2a 100644 --- a/futurehome/src/ha/publish_device.ts +++ b/futurehome/src/ha/publish_device.ts @@ -5,6 +5,7 @@ import { } from '../fimp/vinculum_pd7_device'; import { log } from '../logger'; import { _alarm__components } from '../services/_alarm'; +import { _meter__components } from '../services/_meter'; import { _sensor_binary__components } from '../services/_sensor_binary'; import { _sensor_numeric__components } from '../services/_sensor_numeric'; import { barrier_ctrl__components } from '../services/barrier_ctrl'; @@ -150,6 +151,11 @@ const serviceHandlers: { fan_ctrl: fan_ctrl__components, indicator_ctrl: indicator_ctrl__components, media_player: media_player__components, + meter_elec: _meter__components, + meter_gas: _meter__components, + meter_water: _meter__components, + meter_heating: _meter__components, + meter_cooling: _meter__components, out_bin_switch: out_bin_switch__components, out_lvl_switch: out_lvl_switch__components, scene_ctrl: scene_ctrl__components, diff --git a/futurehome/src/ha/update_state.ts b/futurehome/src/ha/update_state.ts index c5bf65b..5eb4371 100644 --- a/futurehome/src/ha/update_state.ts +++ b/futurehome/src/ha/update_state.ts @@ -177,13 +177,32 @@ const haStateCache: Record< const attributeTypeKeyMap: Record = { alarm: 'event', + meter: 'props.unit', + meter_export: 'props.unit', }; +function getNestedValue(obj: any, path: string): any { + if (!obj) return undefined; + return path + .split('.') + .reduce((acc, key) => (acc != null ? acc[key] : undefined), obj); +} + function getTypeKey(attrName: string): string { - // Default key is 'type', but override for certain attributes return attributeTypeKeyMap[attrName] || 'type'; } +function extractTypeDiscriminator( + entry: any, + typeKeyPath: string, +): string | undefined { + // Try to read the discriminator from the whole entry first (meter.props.unit), + // then fall back to inside val (e.g. val.type) + return ( + getNestedValue(entry, typeKeyPath) ?? getNestedValue(entry.val, typeKeyPath) + ); +} + /** * Helper function to process multiple values for an attribute, handling typed values */ @@ -199,27 +218,28 @@ function processAttributeValues(values: any[], attrName?: string): any { return tsB - tsA; // Latest first }); - const typeKey = getTypeKey(attrName || ''); + const typeKeyPath = getTypeKey(attrName || ''); - const hasTypedValues = sortedValues.some( - (v) => v.val && typeof v.val === 'object' && v.val[typeKey], - ); + // Build list of entries that carry a discriminator + const entriesWithType = sortedValues + .map((v) => ({ v, key: extractTypeDiscriminator(v, typeKeyPath) })) + .filter((x) => !!x.key) as Array<{ v: any; key: string }>; - if (!hasTypedValues) { - // No typed values, return the latest value + if (entriesWithType.length === 0) { + // Not a typed attribute → just return latest value return sortedValues[0].val; } - // Group by type, keeping only the latest value for each type + // Group by (normalized) discriminator, keeping only the latest per type const typeMap: Record = {}; - for (const value of sortedValues) { - if (value.val && typeof value.val === 'object' && value.val[typeKey]) { - const key = value.val[typeKey]; - if (!typeMap[key]) { - const { [typeKey]: _, ...valueWithoutType } = value.val; - typeMap[key] = valueWithoutType; - } + for (const { v, key } of entriesWithType) { + if (!typeMap[key]) { + const payload = + v && typeof v.val === 'object' && v.val !== null + ? { ...v.val } + : { val: v.val }; // wrap primitives like meter readings + typeMap[key] = payload; } } @@ -284,6 +304,14 @@ export function haUpdateStateValueReport(parameters: { value: any; attrName: string; }) { + if ( + parameters.attrName === 'meter' || + parameters.attrName === 'meter_export' + ) { + // Ignore meter readings for now, relying on periodical site state updates + return; + } + // Strip the FIMP envelope so we end up with "/rt:dev/…/ad:x_y" const addr = parameters.topic.replace(/^pt:j1\/mt:evt/, ''); const typeKey = getTypeKey(parameters.attrName); @@ -295,13 +323,14 @@ export function haUpdateStateValueReport(parameters: { if ( parameters.value && typeof parameters.value === 'object' && - parameters.value[typeKey] + getNestedValue(parameters.value, typeKey) ) { - // Handle typed value update - const key = parameters.value[typeKey]; - const { [typeKey]: _, ...valueWithoutType } = parameters.value; + const key = getNestedValue(parameters.value, typeKey); + const valueWithoutType = + typeof parameters.value === 'object' && parameters.value !== null + ? { ...parameters.value } + : { val: parameters.value }; - // Get current attribute value const currentAttrValue = payload[addr][parameters.attrName]; if ( diff --git a/futurehome/src/mqtt/demo_data/device.json b/futurehome/src/mqtt/demo_data/device.json index b085014..0846edb 100644 --- a/futurehome/src/mqtt/demo_data/device.json +++ b/futurehome/src/mqtt/demo_data/device.json @@ -2165,5 +2165,176 @@ }, "type": null } + }, + { + "client": { + "name": "Smart Electric Meter" + }, + "id": 145, + "model": "zb - _TZ3040_bb6xaihh - TS0601", + "modelAlias": "Smart Electric Meter", + "type": { + "subtype": "main_elec", + "type": "meter" + }, + "services": { + "meter_elec": { + "name": "meter_elec", + "addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_elec/ad:145_1", + "enabled": true, + "props": { + "sup_units": ["kWh", "W"], + "sup_extended_vals": ["p_import", "u1", "i1", "freq", "p_factor"] + }, + "intf": [ + "cmd.meter.get_report", + "evt.meter.report", + "cmd.meter_ext.get_report", + "evt.meter_ext.report", + "cmd.meter.reset" + ] + } + } + }, + { + "client": { + "name": "3-Phase Smart Meter" + }, + "id": 246, + "model": "Tibber - Tibber - Pulse Bridge", + "modelAlias": "Tibber Pulse Bridge", + "type": { + "subtype": "main_elec", + "type": "meter" + }, + "services": { + "meter_elec": { + "name": "meter_elec", + "addr": "/rt:dev/rn:tibber/ad:1/sv:meter_elec/ad:246", + "enabled": true, + "props": { + "sup_units": ["kWh", "W"], + "sup_export_units": ["kWh", "W"], + "sup_extended_vals": [ + "p_import", + "p_export", + "u1", + "u2", + "u3", + "i1", + "i2", + "i3", + "p1", + "p2", + "p3", + "freq", + "p_factor", + "e_import", + "e_export" + ] + }, + "intf": [ + "cmd.meter.get_report", + "evt.meter.report", + "cmd.meter_export.get_report", + "evt.meter_export.report", + "cmd.meter_ext.get_report", + "evt.meter_ext.report", + "cmd.meter.reset" + ] + } + } + }, + { + "client": { + "name": "Smart Gas Meter" + }, + "id": 189, + "model": "zb - _TZ3000_h4yw2xn6 - TS0601", + "modelAlias": "Smart Gas Meter", + "type": { + "subtype": null, + "type": "meter" + }, + "services": { + "meter_gas": { + "name": "meter_gas", + "addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_gas/ad:189", + "enabled": true, + "props": { + "sup_units": ["cub_m", "pulse_c"] + }, + "intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"] + } + } + }, + { + "client": { + "name": "Smart Water Meter" + }, + "id": 222, + "model": "zw - Qubino - ZMNHWD1", + "modelAlias": "Qubino Water Meter", + "type": { + "subtype": null, + "type": "meter" + }, + "services": { + "meter_water": { + "name": "meter_water", + "addr": "/rt:dev/rn:zw/ad:1/sv:meter_water/ad:222", + "enabled": true, + "props": { + "sup_units": ["cub_m", "gallon"] + }, + "intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"] + } + } + }, + { + "client": { + "name": "Heat Meter" + }, + "id": 298, + "model": "Hoiax - Futurehome - Heat Meter", + "modelAlias": "Futurehome Heat Meter", + "type": { + "subtype": null, + "type": "meter" + }, + "services": { + "meter_heat": { + "name": "meter_heat", + "addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_heat/ad:298", + "enabled": true, + "props": { + "sup_units": ["kWh", "W"] + }, + "intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"] + } + } + }, + { + "client": { + "name": "Cooling Meter" + }, + "id": 312, + "model": "Hoiax - Futurehome - Cooling Meter", + "modelAlias": "Futurehome Cooling Meter", + "type": { + "subtype": null, + "type": "meter" + }, + "services": { + "meter_cooling": { + "name": "meter_cooling", + "addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_cooling/ad:312", + "enabled": true, + "props": { + "sup_units": ["kWh", "W"] + }, + "intf": ["cmd.meter.get_report", "evt.meter.report", "cmd.meter.reset"] + } + } } ] diff --git a/futurehome/src/mqtt/demo_data/state.json b/futurehome/src/mqtt/demo_data/state.json index dd4e294..b8b9351 100644 --- a/futurehome/src/mqtt/demo_data/state.json +++ b/futurehome/src/mqtt/demo_data/state.json @@ -1472,5 +1472,260 @@ "name": "alarm_heat" } ] + }, + { + "id": 145, + "services": [ + { + "addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_elec/ad:145_1", + "attributes": [ + { + "name": "meter", + "values": [ + { + "props": { + "unit": "kWh" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 1245.168, + "val_t": "float" + }, + { + "props": { + "unit": "W" + }, + "ts": "2025-01-15 10:30:05 +0100", + "val": 850, + "val_t": "int" + } + ] + }, + { + "name": "meter_ext", + "values": [ + { + "ts": "2025-01-15 10:30:05 +0100", + "val": { + "p_import": 850, + "u1": 236.5, + "i1": 3.6, + "freq": 50.02, + "p_factor": 0.98 + }, + "val_t": "float_map" + } + ] + } + ], + "name": "meter_elec" + } + ] + }, + { + "id": 246, + "services": [ + { + "addr": "/rt:dev/rn:tibber/ad:1/sv:meter_elec/ad:246", + "attributes": [ + { + "name": "meter", + "values": [ + { + "props": { + "unit": "kWh" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 15420.891, + "val_t": "float" + }, + { + "props": { + "unit": "W" + }, + "ts": "2025-01-15 10:30:05 +0100", + "val": 2450, + "val_t": "int" + } + ] + }, + { + "name": "meter_export", + "values": [ + { + "props": { + "unit": "kWh" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 892.456, + "val_t": "float" + }, + { + "props": { + "unit": "W" + }, + "ts": "2025-01-15 10:30:05 +0100", + "val": 0, + "val_t": "int" + } + ] + }, + { + "name": "meter_ext", + "values": [ + { + "ts": "2025-01-15 10:30:05 +0100", + "val": { + "p_import": 2450, + "p_export": 0, + "u1": 235.8, + "u2": 236.1, + "u3": 235.4, + "i1": 10.4, + "i2": 10.1, + "i3": 10.8, + "p1": 800, + "p2": 825, + "p3": 825, + "freq": 50.01, + "p_factor": 0.97, + "e_import": 15420891, + "e_export": 892456 + }, + "val_t": "float_map" + } + ] + } + ], + "name": "meter_elec" + } + ] + }, + { + "id": 189, + "services": [ + { + "addr": "/rt:dev/rn:zigbee/ad:1/sv:meter_gas/ad:189", + "attributes": [ + { + "name": "meter", + "values": [ + { + "props": { + "unit": "cub_m" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 1847.362, + "val_t": "float" + }, + { + "props": { + "unit": "pulse_c" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 184736, + "val_t": "int" + } + ] + } + ], + "name": "meter_gas" + } + ] + }, + { + "id": 222, + "services": [ + { + "addr": "/rt:dev/rn:zw/ad:1/sv:meter_water/ad:222", + "attributes": [ + { + "name": "meter", + "values": [ + { + "props": { + "unit": "cub_m" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 94.127, + "val_t": "float" + }, + { + "props": { + "unit": "gallon" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 24872.5, + "val_t": "float" + } + ] + } + ], + "name": "meter_water" + } + ] + }, + { + "id": 298, + "services": [ + { + "addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_heat/ad:298", + "attributes": [ + { + "name": "meter", + "values": [ + { + "props": { + "unit": "kWh" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 2847.92, + "val_t": "float" + }, + { + "props": { + "unit": "W" + }, + "ts": "2025-01-15 10:30:05 +0100", + "val": 12500, + "val_t": "int" + } + ] + } + ], + "name": "meter_heat" + } + ] + }, + { + "id": 312, + "services": [ + { + "addr": "/rt:dev/rn:hoiax/ad:1/sv:meter_cooling/ad:312", + "attributes": [ + { + "name": "meter", + "values": [ + { + "props": { + "unit": "kWh" + }, + "ts": "2025-01-15 10:30:00 +0100", + "val": 1456.73, + "val_t": "float" + }, + { + "props": { + "unit": "W" + }, + "ts": "2025-01-15 10:30:05 +0100", + "val": 8750, + "val_t": "int" + } + ] + } + ], + "name": "meter_cooling" + } + ] } ] diff --git a/futurehome/src/services/_meter.ts b/futurehome/src/services/_meter.ts new file mode 100644 index 0000000..0c10d9e --- /dev/null +++ b/futurehome/src/services/_meter.ts @@ -0,0 +1,610 @@ +import { + VinculumPd7Device, + VinculumPd7Service, +} from '../fimp/vinculum_pd7_device'; +import { SensorComponent } from '../ha/mqtt_components/sensor'; +import { + SensorDeviceClass, + SensorStateClass, +} from '../ha/mqtt_components/_enums'; +import { + CommandHandlers, + ServiceComponentsCreationResult, +} from '../ha/publish_device'; +import { sendFimpMsg } from '../fimp/fimp'; +import { HaMqttComponent } from '../ha/mqtt_components/_component'; + +// Define meter value to device class mapping +const METER_VALUE_DEVICE_CLASS_MAP: Record = { + // Power values + p_import: 'power', + p1: 'power', + p2: 'power', + p3: 'power', + p_export: 'power', + p1_export: 'power', + p2_export: 'power', + p3_export: 'power', + p_import_react: 'reactive_power', + p1_import_react: 'reactive_power', + p2_import_react: 'reactive_power', + p3_import_react: 'reactive_power', + p_export_react: 'reactive_power', + p1_export_react: 'reactive_power', + p2_export_react: 'reactive_power', + p3_export_react: 'reactive_power', + p_import_apparent: 'apparent_power', + p1_import_apparent: 'apparent_power', + p2_import_apparent: 'apparent_power', + p3_import_apparent: 'apparent_power', + p_export_apparent: 'apparent_power', + p1_export_apparent: 'apparent_power', + p2_export_apparent: 'apparent_power', + p3_export_apparent: 'apparent_power', + dc_p: 'power', + + // Energy values + e_import: 'energy', + e1_import: 'energy', + e2_import: 'energy', + e3_import: 'energy', + e_export: 'energy', + e1_export: 'energy', + e2_export: 'energy', + e3_export: 'energy', + e_import_react: 'reactive_energy', + e_export_react: 'reactive_energy', + e_import_apparent: 'energy', + e_export_apparent: 'energy', + + // Voltage values + u: 'voltage', + u1: 'voltage', + u2: 'voltage', + u3: 'voltage', + u_export: 'voltage', + u1_export: 'voltage', + u2_export: 'voltage', + u3_export: 'voltage', + dc_u: 'voltage', + + // Current values + i: 'current', + i1: 'current', + i2: 'current', + i3: 'current', + i_export: 'current', + i1_export: 'current', + i2_export: 'current', + i3_export: 'current', + dc_i: 'current', + + // Power factor + p_factor: 'power_factor', + p1_factor: 'power_factor', + p2_factor: 'power_factor', + p3_factor: 'power_factor', + p_factor_export: 'power_factor', + p1_factor_export: 'power_factor', + p2_factor_export: 'power_factor', + p3_factor_export: 'power_factor', + + // Frequency + freq: 'frequency', + + // Gas meter + // Using 'gas' device class for gas volumes + + // Water meter + // Using 'water' device class for water volumes +}; + +// Define meter value to state class mapping +const METER_VALUE_STATE_CLASS_MAP: Record = { + // Energy values are typically total_increasing + e_import: 'total_increasing', + e1_import: 'total_increasing', + e2_import: 'total_increasing', + e3_import: 'total_increasing', + e_export: 'total_increasing', + e1_export: 'total_increasing', + e2_export: 'total_increasing', + e3_export: 'total_increasing', + e_import_react: 'total_increasing', + e_export_react: 'total_increasing', + e_import_apparent: 'total_increasing', + e_export_apparent: 'total_increasing', + + // Power values are measurements + p_import: 'measurement', + p1: 'measurement', + p2: 'measurement', + p3: 'measurement', + p_export: 'measurement', + p1_export: 'measurement', + p2_export: 'measurement', + p3_export: 'measurement', + p_import_react: 'measurement', + p1_import_react: 'measurement', + p2_import_react: 'measurement', + p3_import_react: 'measurement', + p_export_react: 'measurement', + p1_export_react: 'measurement', + p2_export_react: 'measurement', + p3_export_react: 'measurement', + p_import_apparent: 'measurement', + p1_import_apparent: 'measurement', + p2_import_apparent: 'measurement', + p3_import_apparent: 'measurement', + p_export_apparent: 'measurement', + p1_export_apparent: 'measurement', + p2_export_apparent: 'measurement', + p3_export_apparent: 'measurement', + dc_p: 'measurement', + + // Voltage and current are measurements + u: 'measurement', + u1: 'measurement', + u2: 'measurement', + u3: 'measurement', + u_export: 'measurement', + u1_export: 'measurement', + u2_export: 'measurement', + u3_export: 'measurement', + dc_u: 'measurement', + i: 'measurement', + i1: 'measurement', + i2: 'measurement', + i3: 'measurement', + i_export: 'measurement', + i1_export: 'measurement', + i2_export: 'measurement', + i3_export: 'measurement', + dc_i: 'measurement', + + // Power factor is measurement + p_factor: 'measurement', + p1_factor: 'measurement', + p2_factor: 'measurement', + p3_factor: 'measurement', + p_factor_export: 'measurement', + p1_factor_export: 'measurement', + p2_factor_export: 'measurement', + p3_factor_export: 'measurement', + + // Frequency is measurement + freq: 'measurement', + + // Gas/water volumes are typically total_increasing + // (using generic names as they depend on meter configuration) +}; + +// Define friendly names for meter values +const METER_VALUE_NAMES: Record = { + // Power + p_import: 'Power Import', + p1: 'Power Phase 1', + p2: 'Power Phase 2', + p3: 'Power Phase 3', + p_export: 'Power Export', + p1_export: 'Power Export Phase 1', + p2_export: 'Power Export Phase 2', + p3_export: 'Power Export Phase 3', + p_import_react: 'Reactive Power Import', + p1_import_react: 'Reactive Power Import Phase 1', + p2_import_react: 'Reactive Power Import Phase 2', + p3_import_react: 'Reactive Power Import Phase 3', + p_export_react: 'Reactive Power Export', + p1_export_react: 'Reactive Power Export Phase 1', + p2_export_react: 'Reactive Power Export Phase 2', + p3_export_react: 'Reactive Power Export Phase 3', + p_import_apparent: 'Apparent Power Import', + p1_import_apparent: 'Apparent Power Import Phase 1', + p2_import_apparent: 'Apparent Power Import Phase 2', + p3_import_apparent: 'Apparent Power Import Phase 3', + p_export_apparent: 'Apparent Power Export', + p1_export_apparent: 'Apparent Power Export Phase 1', + p2_export_apparent: 'Apparent Power Export Phase 2', + p3_export_apparent: 'Apparent Power Export Phase 3', + dc_p: 'DC Power', + + // Energy + e_import: 'Energy Import', + e1_import: 'Energy Import Phase 1', + e2_import: 'Energy Import Phase 2', + e3_import: 'Energy Import Phase 3', + e_export: 'Energy Export', + e1_export: 'Energy Export Phase 1', + e2_export: 'Energy Export Phase 2', + e3_export: 'Energy Export Phase 3', + e_import_react: 'Reactive Energy Import', + e_export_react: 'Reactive Energy Export', + e_import_apparent: 'Apparent Energy Import', + e_export_apparent: 'Apparent Energy Export', + + // Voltage + u: 'Voltage', + u1: 'Voltage Phase 1', + u2: 'Voltage Phase 2', + u3: 'Voltage Phase 3', + u_export: 'Voltage Export', + u1_export: 'Voltage Export Phase 1', + u2_export: 'Voltage Export Phase 2', + u3_export: 'Voltage Export Phase 3', + dc_u: 'DC Voltage', + + // Current + i: 'Current', + i1: 'Current Phase 1', + i2: 'Current Phase 2', + i3: 'Current Phase 3', + i_export: 'Current Export', + i1_export: 'Current Export Phase 1', + i2_export: 'Current Export Phase 2', + i3_export: 'Current Export Phase 3', + dc_i: 'DC Current', + + // Power Factor + p_factor: 'Power Factor', + p1_factor: 'Power Factor Phase 1', + p2_factor: 'Power Factor Phase 2', + p3_factor: 'Power Factor Phase 3', + p_factor_export: 'Power Factor Export', + p1_factor_export: 'Power Factor Export Phase 1', + p2_factor_export: 'Power Factor Export Phase 2', + p3_factor_export: 'Power Factor Export Phase 3', + + // Frequency + freq: 'Frequency', +}; + +export function _meter__components( + topicPrefix: string, + device: VinculumPd7Device, + svc: VinculumPd7Service, + svcName: string, +): ServiceComponentsCreationResult | undefined { + const components: Record = {}; + const commandHandlers: CommandHandlers = {}; + + // Get supported units and extended values from service properties + const supportedUnits = svc.props?.sup_units || []; + const supportedExportUnits = svc.props?.sup_export_units || []; + const supportedExtendedValues = svc.props?.sup_extended_vals || []; + + // Handle regular meter readings (meter.{unit}.val) + // These typically come from evt.meter.report + if (svc.intf?.includes('evt.meter.report')) { + for (const unit of supportedUnits) { + const componentId = `${svc.addr}_${unit}`; + + let friendlyName = 'Meter'; + let deviceClass: SensorDeviceClass | undefined; + let stateClass: SensorStateClass = 'total_increasing'; // Default for meter readings + + // Set appropriate device class and name based on unit + switch (unit) { + case 'kWh': + friendlyName = 'Energy'; + deviceClass = 'energy'; + break; + case 'W': + friendlyName = 'Power'; + deviceClass = 'power'; + stateClass = 'measurement'; + break; + case 'A': + friendlyName = 'Current'; + deviceClass = 'current'; + stateClass = 'measurement'; + break; + case 'V': + friendlyName = 'Voltage'; + deviceClass = 'voltage'; + stateClass = 'measurement'; + break; + case 'VA': + friendlyName = 'Apparent Power'; + deviceClass = 'apparent_power'; + stateClass = 'measurement'; + break; + case 'kVAh': + friendlyName = 'Apparent Energy'; + deviceClass = 'energy'; + break; + case 'VAr': + friendlyName = 'Reactive Power'; + deviceClass = 'reactive_power'; + stateClass = 'measurement'; + break; + case 'kVArh': + friendlyName = 'Reactive Energy'; + deviceClass = 'reactive_energy'; + break; + case 'Hz': + friendlyName = 'Frequency'; + deviceClass = 'frequency'; + stateClass = 'measurement'; + break; + case 'power_factor': + friendlyName = 'Power Factor'; + deviceClass = 'power_factor'; + stateClass = 'measurement'; + break; + case 'pulse_c': + friendlyName = 'Pulse Count'; + stateClass = 'total_increasing'; + break; + case 'cub_m': + friendlyName = 'Volume'; + deviceClass = + svcName === 'meter_gas' + ? 'gas' + : svcName === 'meter_water' + ? 'water' + : 'volume'; + stateClass = 'total_increasing'; + break; + case 'cub_f': + friendlyName = 'Volume'; + deviceClass = + svcName === 'meter_gas' + ? 'gas' + : svcName === 'meter_water' + ? 'water' + : 'volume'; + stateClass = 'total_increasing'; + break; + case 'gallon': + friendlyName = 'Volume'; + deviceClass = + svcName === 'meter_gas' + ? 'gas' + : svcName === 'meter_water' + ? 'water' + : 'volume'; + stateClass = 'total_increasing'; + break; + default: + friendlyName = `Meter (${unit})`; + break; + } + + const component: SensorComponent = { + unique_id: componentId, + platform: 'sensor', + entity_category: 'diagnostic', + name: friendlyName, + unit_of_measurement: unit, + state_class: stateClass, + value_template: `{{ value_json['${svc.addr}'].meter.${unit}.val | default(0) }}`, + }; + + if (deviceClass) { + component.device_class = deviceClass; + } + + // Set suggested display precision based on unit + if (unit === 'kWh' || unit === 'kVAh' || unit === 'kVArh') { + component.suggested_display_precision = 3; + } else if ( + unit === 'W' || + unit === 'VA' || + unit === 'VAr' || + unit === 'V' || + unit === 'A' + ) { + component.suggested_display_precision = 1; + } else if (unit === 'Hz') { + component.suggested_display_precision = 2; + } else if (unit === 'power_factor') { + component.suggested_display_precision = 3; + } else if (unit === 'cub_m' || unit === 'cub_f' || unit === 'gallon') { + component.suggested_display_precision = 2; + } + + components[componentId] = component; + } + } + + // Handle export meter readings (meter.{unit}.val) for bidirectional meters + if (svc.intf?.includes('evt.meter_export.report')) { + for (const unit of supportedExportUnits) { + const componentId = `${svc.addr}_export_${unit}`; + + let friendlyName = 'Export Meter'; + let deviceClass: SensorDeviceClass | undefined; + let stateClass: SensorStateClass = 'total_increasing'; // Default for meter readings + + // Set appropriate device class and name based on unit + switch (unit) { + case 'kWh': + friendlyName = 'Energy Export'; + deviceClass = 'energy'; + break; + case 'W': + friendlyName = 'Power Export'; + deviceClass = 'power'; + stateClass = 'measurement'; + break; + case 'A': + friendlyName = 'Current Export'; + deviceClass = 'current'; + stateClass = 'measurement'; + break; + case 'V': + friendlyName = 'Voltage Export'; + deviceClass = 'voltage'; + stateClass = 'measurement'; + break; + case 'VA': + friendlyName = 'Apparent Power Export'; + deviceClass = 'apparent_power'; + stateClass = 'measurement'; + break; + case 'kVAh': + friendlyName = 'Apparent Energy Export'; + deviceClass = 'energy'; + break; + case 'VAr': + friendlyName = 'Reactive Power Export'; + deviceClass = 'reactive_power'; + stateClass = 'measurement'; + break; + case 'kVArh': + friendlyName = 'Reactive Energy Export'; + deviceClass = 'reactive_energy'; + break; + default: + friendlyName = `Export Meter (${unit})`; + break; + } + + const component: SensorComponent = { + unique_id: componentId, + platform: 'sensor', + entity_category: 'diagnostic', + name: friendlyName, + unit_of_measurement: unit, + state_class: stateClass, + value_template: `{{ value_json['${svc.addr}'].meter_export.${unit}.val | default(0) }}`, + }; + + if (deviceClass) { + component.device_class = deviceClass; + } + + // Set suggested display precision + if (unit === 'kWh' || unit === 'kVAh' || unit === 'kVArh') { + component.suggested_display_precision = 3; + } else if ( + unit === 'W' || + unit === 'VA' || + unit === 'VAr' || + unit === 'V' || + unit === 'A' + ) { + component.suggested_display_precision = 1; + } + + components[componentId] = component; + } + } + + // Handle extended meter values (meter_ext.{value_name}) + // These typically come from evt.meter_ext.report and include detailed power/voltage/current readings + if ( + svc.intf?.includes('evt.meter_ext.report') && + supportedExtendedValues.length > 0 + ) { + for (const valueName of supportedExtendedValues) { + const componentId = `${svc.addr}_ext_${valueName}`; + const deviceClass = METER_VALUE_DEVICE_CLASS_MAP[valueName]; + const stateClass = + METER_VALUE_STATE_CLASS_MAP[valueName] || 'measurement'; + const friendlyName = + METER_VALUE_NAMES[valueName] || + valueName + .replace(/_/g, ' ') + .replace(/\b\w/g, (l: string) => l.toUpperCase()); + + // Determine unit based on value name + let unit = ''; + if ( + valueName.startsWith('p_') || + valueName.startsWith('p1') || + valueName.startsWith('p2') || + valueName.startsWith('p3') || + valueName === 'dc_p' + ) { + unit = 'W'; + } else if ( + valueName.startsWith('e_') || + valueName.startsWith('e1') || + valueName.startsWith('e2') || + valueName.startsWith('e3') + ) { + unit = 'kWh'; + } else if ( + valueName.startsWith('u_') || + valueName.startsWith('u1') || + valueName.startsWith('u2') || + valueName.startsWith('u3') || + valueName === 'dc_u' + ) { + unit = 'V'; + } else if ( + valueName.startsWith('i_') || + valueName.startsWith('i1') || + valueName.startsWith('i2') || + valueName.startsWith('i3') || + valueName === 'dc_i' + ) { + unit = 'A'; + } else if (valueName.includes('factor')) { + unit = ''; + } else if (valueName === 'freq') { + unit = 'Hz'; + } + + const component: SensorComponent = { + unique_id: componentId, + platform: 'sensor', + entity_category: 'diagnostic', + name: friendlyName, + state_class: stateClass, + value_template: `{{ value_json['${svc.addr}'].meter_ext.${valueName} | default(0) }}`, + }; + + if (unit) { + component.unit_of_measurement = unit; + } + + if (deviceClass) { + component.device_class = deviceClass; + } + + // Set suggested display precision + if (unit === 'kWh') { + component.suggested_display_precision = 3; + } else if (unit === 'W' || unit === 'V' || unit === 'A') { + component.suggested_display_precision = 1; + } else if (unit === 'Hz') { + component.suggested_display_precision = 2; + } else if (valueName.includes('factor')) { + component.suggested_display_precision = 3; + } + + components[componentId] = component; + } + } + + // Handle meter reset button if supported + if (svc.intf?.includes('cmd.meter.reset')) { + const resetCommandTopic = `${topicPrefix}${svc.addr}/reset/command`; + + components[`${svc.addr}_reset`] = { + unique_id: `${svc.addr}_reset`, + platform: 'button', + name: 'Reset Meter', + entity_category: 'config', + icon: 'mdi:restart', + command_topic: resetCommandTopic, + }; + + commandHandlers[resetCommandTopic] = async (_payload: string) => { + await sendFimpMsg({ + address: svc.addr, + service: svcName, + cmd: 'cmd.meter.reset', + val_t: 'null', + val: null, + }); + }; + } + + return { + components, + commandHandlers, + }; +}