Skip to content

Commit 39b64b0

Browse files
MartinHjelmarefrenck
authored andcommitted
Improve advanced Z-Wave battery discovery (home-assistant#147127)
1 parent c66d411 commit 39b64b0

File tree

10 files changed

+7825
-27
lines changed

10 files changed

+7825
-27
lines changed

homeassistant/components/zwave_js/binary_sensor.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,37 @@ class PropertyZWaveJSEntityDescription(BinarySensorEntityDescription):
318318

319319

320320
# Mappings for boolean sensors
321-
BOOLEAN_SENSOR_MAPPINGS: dict[int, BinarySensorEntityDescription] = {
322-
CommandClass.BATTERY: BinarySensorEntityDescription(
323-
key=str(CommandClass.BATTERY),
321+
BOOLEAN_SENSOR_MAPPINGS: dict[tuple[int, int | str], BinarySensorEntityDescription] = {
322+
(CommandClass.BATTERY, "backup"): BinarySensorEntityDescription(
323+
key="battery_backup",
324+
entity_category=EntityCategory.DIAGNOSTIC,
325+
entity_registry_enabled_default=False,
326+
),
327+
(CommandClass.BATTERY, "disconnected"): BinarySensorEntityDescription(
328+
key="battery_disconnected",
329+
entity_category=EntityCategory.DIAGNOSTIC,
330+
entity_registry_enabled_default=False,
331+
),
332+
(CommandClass.BATTERY, "isLow"): BinarySensorEntityDescription(
333+
key="battery_is_low",
324334
device_class=BinarySensorDeviceClass.BATTERY,
325335
entity_category=EntityCategory.DIAGNOSTIC,
326336
),
337+
(CommandClass.BATTERY, "lowFluid"): BinarySensorEntityDescription(
338+
key="battery_low_fluid",
339+
entity_category=EntityCategory.DIAGNOSTIC,
340+
entity_registry_enabled_default=False,
341+
),
342+
(CommandClass.BATTERY, "overheating"): BinarySensorEntityDescription(
343+
key="battery_overheating",
344+
entity_category=EntityCategory.DIAGNOSTIC,
345+
entity_registry_enabled_default=False,
346+
),
347+
(CommandClass.BATTERY, "rechargeable"): BinarySensorEntityDescription(
348+
key="battery_rechargeable",
349+
entity_category=EntityCategory.DIAGNOSTIC,
350+
entity_registry_enabled_default=False,
351+
),
327352
}
328353

329354

@@ -432,8 +457,9 @@ def __init__(
432457

433458
# Entity class attributes
434459
self._attr_name = self.generate_name(include_value_name=True)
460+
primary_value = self.info.primary_value
435461
if description := BOOLEAN_SENSOR_MAPPINGS.get(
436-
self.info.primary_value.command_class
462+
(primary_value.command_class, primary_value.property_)
437463
):
438464
self.entity_description = description
439465

homeassistant/components/zwave_js/const.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@
139139
ADDON_SLUG = "core_zwave_js"
140140

141141
# Sensor entity description constants
142-
ENTITY_DESC_KEY_BATTERY = "battery"
142+
ENTITY_DESC_KEY_BATTERY_LEVEL = "battery_level"
143+
ENTITY_DESC_KEY_BATTERY_LIST_STATE = "battery_list_state"
144+
ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY = "battery_maximum_capacity"
145+
ENTITY_DESC_KEY_BATTERY_TEMPERATURE = "battery_temperature"
143146
ENTITY_DESC_KEY_CURRENT = "current"
144147
ENTITY_DESC_KEY_VOLTAGE = "voltage"
145148
ENTITY_DESC_KEY_ENERGY_MEASUREMENT = "energy_measurement"

homeassistant/components/zwave_js/discovery.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,6 @@ class ZWaveDiscoverySchema:
913913
hint="numeric_sensor",
914914
primary_value=ZWaveValueDiscoverySchema(
915915
command_class={
916-
CommandClass.BATTERY,
917916
CommandClass.ENERGY_PRODUCTION,
918917
CommandClass.SENSOR_ALARM,
919918
CommandClass.SENSOR_MULTILEVEL,
@@ -922,6 +921,36 @@ class ZWaveDiscoverySchema:
922921
),
923922
data_template=NumericSensorDataTemplate(),
924923
),
924+
ZWaveDiscoverySchema(
925+
platform=Platform.SENSOR,
926+
hint="numeric_sensor",
927+
primary_value=ZWaveValueDiscoverySchema(
928+
command_class={CommandClass.BATTERY},
929+
type={ValueType.NUMBER},
930+
property={"level", "maximumCapacity"},
931+
),
932+
data_template=NumericSensorDataTemplate(),
933+
),
934+
ZWaveDiscoverySchema(
935+
platform=Platform.SENSOR,
936+
hint="numeric_sensor",
937+
primary_value=ZWaveValueDiscoverySchema(
938+
command_class={CommandClass.BATTERY},
939+
type={ValueType.NUMBER},
940+
property={"temperature"},
941+
),
942+
data_template=NumericSensorDataTemplate(),
943+
),
944+
ZWaveDiscoverySchema(
945+
platform=Platform.SENSOR,
946+
hint="list",
947+
primary_value=ZWaveValueDiscoverySchema(
948+
command_class={CommandClass.BATTERY},
949+
type={ValueType.NUMBER},
950+
property={"chargingStatus", "rechargeOrReplace"},
951+
),
952+
data_template=NumericSensorDataTemplate(),
953+
),
925954
ZWaveDiscoverySchema(
926955
platform=Platform.SENSOR,
927956
hint="numeric_sensor",

homeassistant/components/zwave_js/discovery_data_template.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@
133133
)
134134

135135
from .const import (
136-
ENTITY_DESC_KEY_BATTERY,
136+
ENTITY_DESC_KEY_BATTERY_LEVEL,
137+
ENTITY_DESC_KEY_BATTERY_LIST_STATE,
138+
ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY,
139+
ENTITY_DESC_KEY_BATTERY_TEMPERATURE,
137140
ENTITY_DESC_KEY_CO,
138141
ENTITY_DESC_KEY_CO2,
139142
ENTITY_DESC_KEY_CURRENT,
@@ -380,8 +383,31 @@ def find_key_from_matching_set[_T: Enum](
380383
def resolve_data(self, value: ZwaveValue) -> NumericSensorDataTemplateData:
381384
"""Resolve helper class data for a discovered value."""
382385

383-
if value.command_class == CommandClass.BATTERY:
384-
return NumericSensorDataTemplateData(ENTITY_DESC_KEY_BATTERY, PERCENTAGE)
386+
if value.command_class == CommandClass.BATTERY and value.property_ == "level":
387+
return NumericSensorDataTemplateData(
388+
ENTITY_DESC_KEY_BATTERY_LEVEL, PERCENTAGE
389+
)
390+
if value.command_class == CommandClass.BATTERY and value.property_ in (
391+
"chargingStatus",
392+
"rechargeOrReplace",
393+
):
394+
return NumericSensorDataTemplateData(
395+
ENTITY_DESC_KEY_BATTERY_LIST_STATE, None
396+
)
397+
if (
398+
value.command_class == CommandClass.BATTERY
399+
and value.property_ == "maximumCapacity"
400+
):
401+
return NumericSensorDataTemplateData(
402+
ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY, PERCENTAGE
403+
)
404+
if (
405+
value.command_class == CommandClass.BATTERY
406+
and value.property_ == "temperature"
407+
):
408+
return NumericSensorDataTemplateData(
409+
ENTITY_DESC_KEY_BATTERY_TEMPERATURE, UnitOfTemperature.CELSIUS
410+
)
385411

386412
if value.command_class == CommandClass.METER:
387413
try:

homeassistant/components/zwave_js/sensor.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@
5858
ATTR_VALUE,
5959
DATA_CLIENT,
6060
DOMAIN,
61-
ENTITY_DESC_KEY_BATTERY,
61+
ENTITY_DESC_KEY_BATTERY_LEVEL,
62+
ENTITY_DESC_KEY_BATTERY_LIST_STATE,
63+
ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY,
64+
ENTITY_DESC_KEY_BATTERY_TEMPERATURE,
6265
ENTITY_DESC_KEY_CO,
6366
ENTITY_DESC_KEY_CO2,
6467
ENTITY_DESC_KEY_CURRENT,
@@ -95,17 +98,33 @@
9598
PARALLEL_UPDATES = 0
9699

97100

98-
# These descriptions should include device class.
99-
ENTITY_DESCRIPTION_KEY_DEVICE_CLASS_MAP: dict[
100-
tuple[str, str], SensorEntityDescription
101-
] = {
102-
(ENTITY_DESC_KEY_BATTERY, PERCENTAGE): SensorEntityDescription(
103-
key=ENTITY_DESC_KEY_BATTERY,
101+
# These descriptions should have a non None unit of measurement.
102+
ENTITY_DESCRIPTION_KEY_UNIT_MAP: dict[tuple[str, str], SensorEntityDescription] = {
103+
(ENTITY_DESC_KEY_BATTERY_LEVEL, PERCENTAGE): SensorEntityDescription(
104+
key=ENTITY_DESC_KEY_BATTERY_LEVEL,
104105
device_class=SensorDeviceClass.BATTERY,
105106
entity_category=EntityCategory.DIAGNOSTIC,
106107
state_class=SensorStateClass.MEASUREMENT,
107108
native_unit_of_measurement=PERCENTAGE,
108109
),
110+
(ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY, PERCENTAGE): SensorEntityDescription(
111+
key=ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY,
112+
entity_category=EntityCategory.DIAGNOSTIC,
113+
state_class=SensorStateClass.MEASUREMENT,
114+
native_unit_of_measurement=PERCENTAGE,
115+
entity_registry_enabled_default=False,
116+
),
117+
(
118+
ENTITY_DESC_KEY_BATTERY_TEMPERATURE,
119+
UnitOfTemperature.CELSIUS,
120+
): SensorEntityDescription(
121+
key=ENTITY_DESC_KEY_BATTERY_TEMPERATURE,
122+
device_class=SensorDeviceClass.TEMPERATURE,
123+
entity_category=EntityCategory.DIAGNOSTIC,
124+
state_class=SensorStateClass.MEASUREMENT,
125+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
126+
entity_registry_enabled_default=False,
127+
),
109128
(ENTITY_DESC_KEY_CURRENT, UnitOfElectricCurrent.AMPERE): SensorEntityDescription(
110129
key=ENTITY_DESC_KEY_CURRENT,
111130
device_class=SensorDeviceClass.CURRENT,
@@ -285,8 +304,14 @@
285304
),
286305
}
287306

288-
# These descriptions are without device class.
307+
# These descriptions are without unit of measurement.
289308
ENTITY_DESCRIPTION_KEY_MAP = {
309+
ENTITY_DESC_KEY_BATTERY_LIST_STATE: SensorEntityDescription(
310+
key=ENTITY_DESC_KEY_BATTERY_LIST_STATE,
311+
device_class=SensorDeviceClass.ENUM,
312+
entity_category=EntityCategory.DIAGNOSTIC,
313+
entity_registry_enabled_default=False,
314+
),
290315
ENTITY_DESC_KEY_CO: SensorEntityDescription(
291316
key=ENTITY_DESC_KEY_CO,
292317
state_class=SensorStateClass.MEASUREMENT,
@@ -538,7 +563,7 @@ def get_entity_description(
538563
"""Return the entity description for the given data."""
539564
data_description_key = data.entity_description_key or ""
540565
data_unit = data.unit_of_measurement or ""
541-
return ENTITY_DESCRIPTION_KEY_DEVICE_CLASS_MAP.get(
566+
return ENTITY_DESCRIPTION_KEY_UNIT_MAP.get(
542567
(data_description_key, data_unit),
543568
ENTITY_DESCRIPTION_KEY_MAP.get(
544569
data_description_key,
@@ -588,6 +613,10 @@ def async_add_sensor(info: ZwaveDiscoveryInfo) -> None:
588613
entities.append(
589614
ZWaveListSensor(config_entry, driver, info, entity_description)
590615
)
616+
elif info.platform_hint == "list":
617+
entities.append(
618+
ZWaveListSensor(config_entry, driver, info, entity_description)
619+
)
591620
elif info.platform_hint == "config_parameter":
592621
entities.append(
593622
ZWaveConfigParameterSensor(

tests/components/zwave_js/common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
VOLTAGE_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed_3"
2222
CURRENT_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed_4"
2323
SWITCH_ENTITY = "switch.smart_plug_with_two_usb_ports"
24-
LOW_BATTERY_BINARY_SENSOR = "binary_sensor.multisensor_6_low_battery_level"
2524
ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any"
2625
DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any"
2726
NOTIFICATION_MOTION_BINARY_SENSOR = "binary_sensor.multisensor_6_motion_detection"

tests/components/zwave_js/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ def climate_heatit_z_trm3_no_value_state_fixture() -> dict[str, Any]:
199199
return load_json_object_fixture("climate_heatit_z_trm3_no_value_state.json", DOMAIN)
200200

201201

202+
@pytest.fixture(name="ring_keypad_state", scope="package")
203+
def ring_keypad_state_fixture() -> dict[str, Any]:
204+
"""Load the Ring keypad state fixture data."""
205+
return load_json_object_fixture("ring_keypad_state.json", DOMAIN)
206+
207+
202208
@pytest.fixture(name="nortek_thermostat_state", scope="package")
203209
def nortek_thermostat_state_fixture() -> dict[str, Any]:
204210
"""Load the nortek thermostat node state fixture data."""
@@ -876,6 +882,14 @@ def nortek_thermostat_removed_event_fixture(client) -> Node:
876882
return Event("node removed", event_data)
877883

878884

885+
@pytest.fixture(name="ring_keypad")
886+
def ring_keypad_fixture(client: MagicMock, ring_keypad_state: NodeDataType) -> Node:
887+
"""Mock a Ring keypad node."""
888+
node = Node(client, copy.deepcopy(ring_keypad_state))
889+
client.driver.controller.nodes[node.node_id] = node
890+
return node
891+
892+
879893
@pytest.fixture(name="integration")
880894
async def integration_fixture(
881895
hass: HomeAssistant,

0 commit comments

Comments
 (0)