Skip to content

Commit d175fb6

Browse files
authored
Version 0.6.0 (#26)
* Fixes: - Support more than one climate device - Support more than one humidifier device - Improve logging - Add unknown_value to climate targets - Support target action for humidifier - 009-109: f_humidity unknown_value * Version 0.6.0
1 parent c0224b7 commit d175fb6

File tree

9 files changed

+88
-40
lines changed

9 files changed

+88
-40
lines changed

custom_components/connectlife/climate.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ class ConnectLifeClimate(ConnectLifeEntity, ClimateEntity):
6161
_attr_target_temperature_step = 1
6262
_attr_temperature_unit = UnitOfTemperature.CELSIUS
6363
_attr_hvac_mode = None
64-
target_map = {}
65-
fan_mode_map: dict[int, str] = {}
66-
fan_mode_reverse_map: dict[str, int] = {}
67-
hvac_action_map: dict[int, HVACAction] = {}
68-
swing_mode_map: dict[int, str] = {}
69-
swing_mode_reverse_map: dict[str, int] = {}
70-
temperature_unit_map: dict[int, UnitOfTemperature] = {}
64+
unknown_values: dict[str, int]
65+
target_map: dict[str, str]
66+
fan_mode_map: dict[int, str]
67+
fan_mode_reverse_map: dict[str, int]
68+
hvac_action_map: dict[int, HVACAction]
69+
swing_mode_map: dict[int, str]
70+
swing_mode_reverse_map: dict[str, int]
71+
temperature_unit_map: dict[int, UnitOfTemperature]
7172

7273
def __init__(
7374
self,
@@ -85,6 +86,15 @@ def __init__(
8586
translation_key=DOMAIN
8687
)
8788

89+
self.target_map = {}
90+
self.fan_mode_map = {}
91+
self.fan_mode_reverse_map = {}
92+
self.hvac_action_map = {}
93+
self.swing_mode_map = {}
94+
self.swing_mode_reverse_map = {}
95+
self.temperature_unit_map = {}
96+
self.unknown_values = {}
97+
8898
for dd_entry in data_dictionary.values():
8999
if hasattr(dd_entry, Platform.CLIMATE):
90100
self.target_map[dd_entry.climate.target] = dd_entry.name
@@ -120,12 +130,13 @@ def __init__(
120130
self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
121131
self._attr_swing_mode = None
122132
elif target == HVAC_ACTION:
123-
actions = set(action.value for action in HVACAction)
133+
actions = [action.value for action in HVACAction]
124134
for (k, v) in data_dictionary[status].climate.options.items():
125135
if v in actions:
126136
self.hvac_action_map[k] = HVACAction(v)
127137
else:
128138
_LOGGER.warning("Not mapping %d to unknown HVACAction %s", k, v)
139+
self.unknown_values[status] = data_dictionary[status].climate.unknown_value
129140

130141
self._attr_hvac_modes = hvac_modes
131142
self.update_state()
@@ -153,19 +164,21 @@ def update_state(self) -> None:
153164
self._attr_fan_mode = self.fan_mode_map[value]
154165
else:
155166
self._attr_fan_mode = None
156-
_LOGGER.warning("Got unexpected value %d for %s", value, status)
167+
_LOGGER.warning("Got unexpected value %d for %s (%s)", value, status, self.nickname)
157168
elif target == SWING_MODE:
158169
if value in self.swing_mode_map:
159170
self._attr_swing_mode = self.swing_mode_map[value]
160171
else:
161172
self._attr_swing_mode = None
162-
_LOGGER.warning("Got unexpected value %d for %s", value, status)
173+
_LOGGER.warning("Got unexpected value %d for %s (%s)", value, status, self.nickname)
163174
elif target == TEMPERATURE_UNIT:
164175
if value in self.temperature_unit_map:
165176
self._attr_temperature_unit = self.temperature_unit_map[value]
166177
else:
167-
_LOGGER.warning("Got unexpected value %d for %s", value, status)
178+
_LOGGER.warning("Got unexpected value %d for %s (%s)", value, status, self.nickname)
168179
else:
180+
if value == self.unknown_values[status]:
181+
value = None
169182
setattr(self, f"_attr_{target}", value)
170183
self._attr_available = self.coordinator.appliances[self.device_id].offline_state == 1
171184

custom_components/connectlife/data_dictionaries/009-109.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ properties:
2222
- property: f_humidity
2323
climate:
2424
target: current_humidity
25+
unknown_value: 128
2526
- property: t_power
2627
climate:
2728
target: is_on

custom_components/connectlife/data_dictionaries/README.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ Domain `binary_sensor` can be used for read only properties where `0` is not ava
4545
Domain `climate` can be used to map the property to a target property in a climate entity. If at least one property has
4646
type `climate`, a climate entity is created for the appliance.
4747

48-
| Item | Type | Description |
49-
|-----------|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
50-
| `target` | string | Any of these [climate entity](https://developers.home-assistant.io/docs/core/entity/climate#properties) attributes: `current_humidity`, `fan_mode`, `hvac_action`, `swing_mode`, `current_temperature`, `target_humidity`, `target_temperature`, `temperature_unit`, or the special target `is_on` |
51-
| `options` | dictionary of integer to string | Required for `fan_mode`, `hvac_action`, `swing_mode`, and `temperature_unit` |
48+
| Item | Type | Description |
49+
|-----------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
50+
| `target` | string | Any of these [climate entity](https://developers.home-assistant.io/docs/core/entity/climate#properties) attributes: `current_humidity`, `fan_mode`, `hvac_action`, `swing_mode`, `current_temperature`, `target_humidity`, `target_temperature`, `temperature_unit`, or the special target `is_on`. |
51+
| `options` | dictionary of integer to string | Required for `fan_mode`, `hvac_action`, `swing_mode`, and `temperature_unit` |
52+
| `unknown_value` | integer | The value used by the API to signal unknown value. |
5253

5354
`temperature_unit` defaults to Celsius.
5455

@@ -63,14 +64,18 @@ For `fan_mode` and `swing_mode`, remember to add [translation strings](#translat
6364
Domain `humidifier` can be used to map the property to a target property in a humidifier entity. If at least one property has
6465
type `humidifier`, a humidifier entity is created for the appliance.
6566

66-
| Item | Type | Description |
67-
|----------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
68-
| `target` | string | Any of these [humidifier entity](https://developers.home-assistant.io/docs/core/entity/humidifier#properties) attributes: `is_on`, `current_humidity`, `target_humidity`, `mode`. |
69-
| `options` | dictionary of integer to string | Required for `mode`. |
70-
| `device_class` | string | Name of any [HumidifierDeviceClass enum](https://developers.home-assistant.io/docs/core/entity/humidifier#available-device-classes). |
67+
| Item | Type | Description |
68+
|----------------|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
69+
| `target` | string | Any of these [humidifier entity](https://developers.home-assistant.io/docs/core/entity/humidifier#properties) attributes: `action`, `is_on`, `current_humidity`, `target_humidity`, `mode`. |
70+
| `options` | dictionary of integer to string | Required for `action` and `mode`. |
71+
| `device_class` | string | Name of any [HumidifierDeviceClass enum](https://developers.home-assistant.io/docs/core/entity/humidifier#available-device-classes). |
7172

7273
It is sufficient to set `device_class` on one property. The value of the first encountered property is used.
7374

75+
Note that `action` can only be mapped to [pre-defined actions](https://developers.home-assistant.io/docs/core/entity/humidifier/#action).
76+
If a value does not have a sensible mapping, leave it out to set `action` to `None` for that value, or consider mapping
77+
to a sensor `enum` instead.
78+
7479
For mode, remember to add [translation strings](#translation-strings).
7580

7681
## Type `Select`

custom_components/connectlife/data_dictionaries/properties-schema.json

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,15 @@
9797
"target": {
9898
"$ref": "#/definitions/ClimateTarget"
9999
},
100+
"unknown_value": {
101+
"$ref": "#/definitions/UnknownValue"
102+
},
100103
"options": {
101104
"type": "object",
102105
"additionalProperties": {
103106
"type": "string"
104107
},
105-
"description": "Map of integer to string. Required for targets fan_mode, swing_mode, and temeprature_unit."
108+
"description": "Map of integer to string. Required for targets fan_mode, swing_mode, and temperature_unit."
106109
}
107110
}
108111
},
@@ -160,12 +163,7 @@
160163
]
161164
},
162165
"unknown_value": {
163-
"type": "integer",
164-
"description": "The value used by the API to signal unknown value.",
165-
"examples": [
166-
255,
167-
65535
168-
]
166+
"$ref": "#/definitions/UnknownValue"
169167
},
170168
"max_value": {
171169
"type": "integer",
@@ -353,6 +351,14 @@
353351
],
354352
"title": "Device class",
355353
"description": "Name of any SwitchDeviceClass enum."
354+
},
355+
"UnknownValue": {
356+
"type": "integer",
357+
"description": "The value used by the API to signal unknown value.",
358+
"examples": [
359+
255,
360+
65535
361+
]
356362
}
357363
}
358364
}

custom_components/connectlife/dictionaries.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self, name: str, binary_sensor: dict | None):
5151
class Climate:
5252
target: str
5353
options: dict | None
54+
unknown_value: int | None
5455

5556
def __init__(self, name: str, climate: dict | None):
5657
if climate is None:
@@ -61,6 +62,7 @@ def __init__(self, name: str, climate: dict | None):
6162
self.options = climate[OPTIONS] if OPTIONS in climate else None
6263
if self.options is None and self.target in [FAN_MODE, HVAC_ACTION, SWING_MODE, TEMPERATURE_UNIT]:
6364
_LOGGER.warning("Missing climate.options for %s", name)
65+
self.unknown_value = climate[UNKNOWN_VALUE] if UNKNOWN_VALUE in climate and climate[UNKNOWN_VALUE] else None
6466

6567

6668
class Humidifier:
@@ -199,6 +201,6 @@ def get_dictionary(cls, appliance: ConnectLifeAppliance) -> dict[str, Property]:
199201
for prop in parsed[PROPERTIES]:
200202
dictionary[prop[PROPERTY]] = Property(prop)
201203
except FileNotFoundError:
202-
_LOGGER.warning("No data dictionary found for %s", key)
204+
_LOGGER.warning("No data dictionary found for %s (%s)", appliance.device_nickname, key)
203205
Dictionaries.dictionaries[key] = dictionary
204206
return dictionary

custom_components/connectlife/humidifier.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33

44
from homeassistant.components.humidifier import (
5+
HumidifierAction,
56
HumidifierEntity,
67
HumidifierEntityDescription,
78
HumidifierEntityFeature,
@@ -12,6 +13,7 @@
1213
from homeassistant.helpers.entity_platform import AddEntitiesCallback
1314

1415
from .const import (
16+
ACTION,
1517
DOMAIN,
1618
MODE,
1719
IS_ON,
@@ -51,9 +53,10 @@ class ConnectLifeHumidifier(ConnectLifeEntity, HumidifierEntity):
5153
"""Humidifier class for ConnectLife."""
5254

5355
_attr_name = None
54-
target_map = {}
55-
mode_map: dict[int, str] = {}
56-
mode_reverse_map: dict[str, int] = {}
56+
target_map: dict[str, str]
57+
mode_map: dict[int, str]
58+
mode_reverse_map: dict[str, int]
59+
action_map: dict[int, HumidifierAction]
5760

5861
def __init__(
5962
self,
@@ -65,6 +68,11 @@ def __init__(
6568
super().__init__(coordinator, appliance)
6669
self._attr_unique_id = f"{appliance.device_id}-humidifier"
6770

71+
self.target_map = {}
72+
self.mode_map = {}
73+
self.mode_reverse_map = {}
74+
self.action_map = {}
75+
6876
device_class = None
6977
for prop in data_dictionary.values():
7078
if hasattr(prop, Platform.HUMIDIFIER):
@@ -84,7 +92,14 @@ def __init__(
8492
self.target_map[dd_entry.humidifier.target] = dd_entry.name
8593

8694
for target, status in self.target_map.items():
87-
if target == MODE:
95+
if target == ACTION:
96+
actions = [action.value for action in HumidifierAction]
97+
for (k, v) in data_dictionary[status].humidifier.options.items():
98+
if v in actions:
99+
self.action_map[k] = HumidifierAction(v)
100+
else:
101+
_LOGGER.warning("Not mapping %d to unknown HumidifierAction %s", k, v)
102+
elif target == MODE:
88103
self.mode_map = data_dictionary[status].humidifier.options
89104
self.mode_reverse_map = {v: k for k, v in self.mode_map.items()}
90105
self._attr_available_modes = list(self.mode_map.values())
@@ -101,12 +116,18 @@ def update_state(self) -> None:
101116
if target == IS_ON:
102117
# TODO: Support value mapping
103118
self._attr_is_on = value == 1
119+
elif target == ACTION:
120+
if value in self.action_map:
121+
self._attr_action = self.action_map[value]
122+
else:
123+
# Map to None as we cannot add custom humidifier actions.
124+
self._attr_action = None
104125
elif target == MODE:
105126
if value in self.mode_map:
106127
self._attr_mode = self.mode_map[value]
107128
else:
108129
self._attr_mode = None
109-
_LOGGER.warning("Got unexpected value %d for %s", value, status)
130+
_LOGGER.warning("Got unexpected value %d for %s (%s)", value, status, self.nickname)
110131
else:
111132
setattr(self, f"_attr_{target}", value)
112133
self._attr_available = self.coordinator.appliances[self.device_id].offline_state == 1

custom_components/connectlife/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"documentation": "https://github.com/oyvindwe/connectlife-ha",
77
"iot_class": "cloud_polling",
88
"requirements": ["connectlife==0.2.0"],
9-
"version": "0.1.0"
9+
"version": "0.6.0"
1010
}

custom_components/connectlife/select.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def update_state(self):
6868
if value in self.options_map:
6969
value = self.options_map[value]
7070
else:
71-
_LOGGER.warning("Got unexpected value %d for %s", value, self.status)
71+
_LOGGER.warning("Got unexpected value %d for %s (%s)", value, self.status, self.nickname)
7272
_value = None
7373
self._attr_current_option = value
7474

custom_components/connectlife/sensor.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,20 +97,20 @@ def update_state(self):
9797
if value in self.options_map:
9898
value = self.options_map[value]
9999
else:
100-
_LOGGER.warning("Got unexpected value %d for %s", value, self.status)
100+
_LOGGER.warning("Got unexpected value %d for %s (%s)", value, self.status, self.nickname)
101101
value = None
102102
self._attr_native_value = value if value != self.unknown_value else None
103103
self._attr_available = self.coordinator.appliances[self.device_id].offline_state == 1
104104

105105
async def async_set_value(self, value: int) -> None:
106106
"""Set value for this sensor."""
107-
_LOGGER.debug("Setting %s to %d", self.status, value)
107+
_LOGGER.debug("Setting %s to %d on %s", self.status, value, self.nickname)
108108
if self.writable is None:
109-
_LOGGER.warning("%s may not be writable", self._attr_name)
109+
_LOGGER.warning("%s may not be writable on %s", self._attr_name, self.nickname)
110110
elif not self.writable:
111-
raise ServiceValidationError(f"{self._attr_name} is read only")
111+
raise ServiceValidationError(f"{self._attr_name} is read only on {self.nickname}")
112112
if self.max_value is not None and value > self.max_value:
113-
raise ServiceValidationError(f"Max value for {self._attr_name} is {self.max_value}")
113+
raise ServiceValidationError(f"Max value for {self._attr_name} is {self.max_value} on {self.nickname}")
114114
try:
115115
await self.coordinator.api.update_appliance(self.puid, {self.status: str(value)})
116116
except LifeConnectError as api_error:

0 commit comments

Comments
 (0)