diff --git a/custom_components/connectlife/data_dictionaries/README.md b/custom_components/connectlife/data_dictionaries/README.md index 779cd07..fae90b0 100644 --- a/custom_components/connectlife/data_dictionaries/README.md +++ b/custom_components/connectlife/data_dictionaries/README.md @@ -1,37 +1,61 @@ # Data dictionaries Data dictionaries for known appliances are located in this directory. Appliances without data dictionary will be still -be loaded, but with a warning in the log. Also, all unknown properties are mapped to hidden status entities. +be loaded, but with a warning in the log. Their properties will all be mapped to [sensor](#type-sensor) entities, +with `hidden` set to `true` and `state_class` set to `measurement` (to enable +[long-term statistics](https://developers.home-assistant.io/docs/core/entity/sensor/#long-term-statistics)). -To make a property visible by default, just add the property to the list (without setting `hide`). +## Create your own mapping file -File name: `-.yaml` +To map you device, create a file with the name `-.yaml` in this directory. When done, +or if you need help with the mapping, please open a PR on GitHub with the file! The file contains two top level items: - `device_type`: string - `properties`: list of [`Property`](#property) -Each property is mapped to _one_ entity or (in the cases of `climate` and `humidifier`) _one_ target property. +To make a property visible by default, just add the property to the list. Note that properties you do not map are still +mapped to [sensor](#type-sensor) entities, but _without_ `state_class`. This is done as some devices supports a lot +of properties, which will take up processing time and storage. Makes sure to include all properties of interest when +mapping a device! + +Each property is mapped to _one_ entity or _one_ target property. If you change the type of mapping, the old entity or state attribute will change to unavailable in Home Assistant. +You can bulk remove the old entities in on the [entities page](https://my.home-assistant.io/redirect/entities/) +by filtering on the device and status. + +If you change unit or state class for sensors, you will need to fix the history in +[Home Assistant - Statistics](https://my.home-assistant.io/redirect/developer_statistics/). You need to restart Home Assistant to load mapping changes. +### Mapping tips and tricks: + +- Inspect the existing mappings files in this directory. +- Change settings in the ConnectLife app while monitoring value changes in Home Assistant. Take a note of which + property is changes, what the value is, and what the button or action is named in the ConnectLife app. +- Be aware that `true`, `false`, `yes`, `no`, `on`, and `off` are all interpreted as boolean values in YAML, + and must be quoted (e.g. `"off"`) to be interpreted as a string, e.g. in option lists. Note that some options + expects boolean (unquoted) values. +- Validate your mapping file with the [JSON schema](properties-schema.json). +- Remember to add translation strings. + ## Property -| Item | Type | Description | -|-----------------|------------------------------------|---------------------------------------------------------------------------------------------------| -| `property` | string | Name of status/property. | -| `hide` | `true`, `false` | If Home Assistant should initially hide the sensor entity for this property. Defaults to `false`. | -| `icon` | `mdi:eye`, etc. | Icon to use for the entity. | -| `binary_sensor` | [BinarySensor](#type-binarysensor) | Create a binary sensor of the property. | -| `climate` | [Climate](#type-climate) | Map the property to a climate entity for the device. | -| `humidifier` | [Humidifier](#type-humidifier) | Map the property to a humidifier entity for the device. | -| `number` | [Number](#type-number) | Create a number entity of the property. | -| `select` | [Select](#type-select) | Create a selector of the property. | -| `sensor` | [Sensor](#type-sensor) | Create a sensor of the property. This is the default. | -| `switch` | [Switch](#type-switch) | Create a switch of the property. | -| `water_heater` | [WaterHeater](#type-waterheater) | Map the property to a water heater entity for the device. | +| Item | Type | Description | +|-----------------|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| `property` | string | Name of status/property. | +| `hide` | `true`, `false` | If Home Assistant should initially hide the sensor entity for this property. Defaults to `false`, but it set to `true` for unknown properties. | +| `icon` | `mdi:eye`, etc. | Icon to use for the entity. | +| `binary_sensor` | [BinarySensor](#type-binarysensor) | Create a binary sensor of the property. | +| `climate` | [Climate](#type-climate) | Map the property to a climate entity for the device. | +| `humidifier` | [Humidifier](#type-humidifier) | Map the property to a humidifier entity for the device. | +| `number` | [Number](#type-number) | Create a number entity of the property. | +| `select` | [Select](#type-select) | Create a selector of the property. | +| `sensor` | [Sensor](#type-sensor) | Create a sensor of the property. This is the default. | +| `switch` | [Switch](#type-switch) | Create a switch of the property. | +| `water_heater` | [WaterHeater](#type-waterheater) | Map the property to a water heater entity for the device. | If an entity mapping is not given, the property is mapped to a sensor entity. @@ -101,8 +125,8 @@ Number entities can be set by the user. | Item | Type | Description | |-----------------|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| -| `min_value` | integer | Minimum value. | -| `max_value` | integer | Maximum value. | +| `min_value` | integer | Minimum value. | +| `max_value` | integer | Maximum value. | | `device_class` | `duration`, `energy`, `water`, etc. | Name of any [NumberDeviceClass enum](https://developers.home-assistant.io/docs/core/entity/number/#available-device-classes). | | `unit` | `min`, `°C`, `°F`, etc. | Required if `device_class` is set, except not allowed when `device_class` is `aqi` or `ph`. | @@ -119,14 +143,14 @@ Remember to add options to [translation strings](#translation-strings). Sensor entities are usually read-only, but this integration provides a `set_value` service that can be applied on the `sensor.connectlife` entities, unless the sensor is set to `read_only: true`. -| Item | Type | Description | -|-----------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `read_only` | `true`, `false` | If this property is known to be read-only (prevents `set_value` service). | -| `state_class` | `measurement`, `total`, `total_increasing` | Name of any [SensorStateClass enum](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes). For integer properties, defaults to `measurement`. Not allowed when `device_class` is `enum`. | -| `device_class` | `duration`, `energy`, `water`, etc. | Name of any [SensorDeviceClass enum](https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes). | -| `unit` | `min`, `kWh`, `L`, etc. | Required if `device_class` is set, except not allowed when `device_class` is `aqi`, `ph` or `enum`. | -| `options` | dictionary of integer to string | Required if `device_class` is set to `enum`. | -| `unknown_value` | integer | The value used by the API to signal unknown value. | +| Item | Type | Description | +|-----------------|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `read_only` | `true`, `false` | If this property is known to be read-only (prevents `set_value` service). | +| `state_class` | `measurement`, `total`, `total_increasing` | Name of any [SensorStateClass enum](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes). For integer properties. Not allowed when `device_class` is `enum`. | +| `device_class` | `duration`, `energy`, `water`, etc. | Name of any [SensorDeviceClass enum](https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes). | +| `unit` | `min`, `kWh`, `L`, etc. | Required if `device_class` is set, except not allowed when `device_class` is `aqi`, `ph` or `enum`. | +| `options` | dictionary of integer to string | Required if `device_class` is set to `enum`. | +| `unknown_value` | integer | The value used by the API to signal unknown value. | For device class `enum`, remember to add options to [translation strings](#translation-strings). diff --git a/custom_components/connectlife/dictionaries.py b/custom_components/connectlife/dictionaries.py index b064267..a2482b8 100644 --- a/custom_components/connectlife/dictionaries.py +++ b/custom_components/connectlife/dictionaries.py @@ -285,13 +285,22 @@ def get_dictionary(cls, appliance: ConnectLifeAppliance) -> dict[str, Property]: key = f"{appliance.device_type_code}-{appliance.device_feature_code}" if key in Dictionaries.dictionaries: return Dictionaries.dictionaries[key] - dictionary = defaultdict(lambda: Property({PROPERTY: "default", HIDE: True})) try: + dictionary = defaultdict(lambda: Property({PROPERTY: "unknown_property", HIDE: True})) data = pkgutil.get_data(__name__, f"data_dictionaries/{key}.yaml") parsed = yaml.safe_load(data) for prop in parsed[PROPERTIES]: dictionary[prop[PROPERTY]] = Property(prop) except FileNotFoundError: _LOGGER.warning("No data dictionary found for %s (%s)", appliance.device_nickname, key) + dictionary = defaultdict( + lambda: Property( + { + PROPERTY: "unknown_device", + HIDE: True, + Platform.SENSOR: {STATE_CLASS: SensorStateClass.MEASUREMENT} + } + ) + ) Dictionaries.dictionaries[key] = dictionary return dictionary diff --git a/custom_components/connectlife/sensor.py b/custom_components/connectlife/sensor.py index 2af5e51..d57466d 100644 --- a/custom_components/connectlife/sensor.py +++ b/custom_components/connectlife/sensor.py @@ -70,11 +70,12 @@ def __init__( elif (device_class is None and isinstance(self.coordinator.appliances[self.device_id].status_list[status], datetime.datetime)): device_class = SensorDeviceClass.TIMESTAMP - state_class = dd_entry.sensor.state_class - if (state_class is None - and isinstance(self.coordinator.appliances[self.device_id].status_list[status], int) - and device_class != SensorDeviceClass.ENUM): - state_class = SensorStateClass.MEASUREMENT + state_class = ( + dd_entry.sensor.state_class + if isinstance(self.coordinator.appliances[self.device_id].status_list[status], int) + and device_class != SensorDeviceClass.ENUM + else None + ) self.entity_description = SensorEntityDescription( key=self._attr_unique_id, device_class=device_class,