Skip to content

Commit 759ac7e

Browse files
sebrclaude
andauthored
Add fault binary sensor for sprinkler devices (#375)
* Add fault binary sensor for sprinkler devices Handles websocket "fault" events with station_faults data, exposing a PROBLEM-class binary sensor that turns on when any station reports a fault. Fault details (station, timestamp, no_flow) are available as attributes. Also refactors binary sensor setup to use a device_types field on each description, replacing the hardcoded DEVICE_FLOOD filter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Include device name in binary sensor entity names Sets _attr_name to "{device_name} {description_name}" so entity IDs include the device identity (e.g. binary_sensor.garden_sprinkler_fault) instead of generic names like binary_sensor.fault. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use has_entity_name for entity naming across all platforms Adopts _attr_has_entity_name so Home Assistant automatically prefixes entity names with the device name, removing manual prefixing from sensors, switches, and binary sensors. Updates tests accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove unused variable --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f6be5df commit 759ac7e

10 files changed

Lines changed: 288 additions & 98 deletions

File tree

custom_components/bhyve/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def __init__(
197197
"Creating %s: %s - %s",
198198
self.__class__.__name__,
199199
self._device_name,
200-
self._device_type,
200+
self.name,
201201
)
202202

203203
@property

custom_components/bhyve/binary_sensor.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414

1515
from . import BHyveCoordinatorEntity
16-
from .const import DEVICE_FLOOD, DOMAIN
16+
from .const import DEVICE_FLOOD, DEVICE_SPRINKLER, DOMAIN
1717

1818
if TYPE_CHECKING:
1919
from homeassistant.config_entries import ConfigEntry
@@ -31,6 +31,9 @@ class BHyveBinarySensorEntityDescription(BinarySensorEntityDescription):
3131
"""Describes BHyve binary sensor entity."""
3232

3333
unique_id_suffix: str
34+
name: str = ""
35+
# Device types this sensor applies to (e.g., DEVICE_FLOOD, DEVICE_SPRINKLER)
36+
device_types: tuple[str, ...] = ()
3437
# Callable that takes device_data and returns bool
3538
value_fn: Any = None
3639
# Callable that takes device_data and returns attributes
@@ -41,9 +44,10 @@ class BHyveBinarySensorEntityDescription(BinarySensorEntityDescription):
4144
BHyveBinarySensorEntityDescription(
4245
key="flood",
4346
translation_key="flood",
44-
name="flood sensor",
47+
name="Flood sensor",
4548
device_class=BinarySensorDeviceClass.MOISTURE,
4649
unique_id_suffix="water",
50+
device_types=(DEVICE_FLOOD,),
4751
value_fn=lambda data: (
4852
data.get("status", {}).get("flood_alarm_status") == "alarm"
4953
),
@@ -56,14 +60,27 @@ class BHyveBinarySensorEntityDescription(BinarySensorEntityDescription):
5660
BHyveBinarySensorEntityDescription(
5761
key="temperature_alert",
5862
translation_key="temperature_alert",
59-
name="temperature alert",
63+
name="Temperature alert",
6064
device_class=BinarySensorDeviceClass.HEAT,
6165
unique_id_suffix="tempalert",
66+
device_types=(DEVICE_FLOOD,),
6267
value_fn=lambda data: (
6368
"alarm" in data.get("status", {}).get("temp_alarm_status", "")
6469
),
6570
attributes_fn=lambda data: data.get("temp_alarm_thresholds", {}),
6671
),
72+
BHyveBinarySensorEntityDescription(
73+
key="fault",
74+
translation_key="fault",
75+
name="Fault",
76+
device_class=BinarySensorDeviceClass.PROBLEM,
77+
unique_id_suffix="fault",
78+
device_types=(DEVICE_SPRINKLER,),
79+
value_fn=lambda data: bool(data.get("status", {}).get("station_faults")),
80+
attributes_fn=lambda data: {
81+
"station_faults": data.get("status", {}).get("station_faults", []),
82+
},
83+
),
6784
)
6885

6986

@@ -77,8 +94,8 @@ async def async_setup_entry(
7794
entities = [
7895
BHyveBinarySensor(coordinator, device, description)
7996
for device in devices
80-
if device.get("type") == DEVICE_FLOOD
8197
for description in BINARY_SENSOR_TYPES
98+
if device.get("type") in description.device_types
8299
]
83100

84101
async_add_entities(entities)
@@ -88,6 +105,7 @@ class BHyveBinarySensor(BHyveCoordinatorEntity, BinarySensorEntity):
88105
"""Define a BHyve binary sensor."""
89106

90107
entity_description: BHyveBinarySensorEntityDescription
108+
_attr_has_entity_name = True
91109

92110
def __init__(
93111
self,
@@ -97,6 +115,7 @@ def __init__(
97115
) -> None:
98116
"""Initialize the sensor."""
99117
self.entity_description = description
118+
self._attr_name = description.name
100119
super().__init__(coordinator, device)
101120
self._attr_unique_id = (
102121
f"{self._mac_address}:{self._device_id}:{description.unique_id_suffix}"

custom_components/bhyve/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
DEVICE_FLOOD = "flood_sensor"
2121

2222
EVENT_BATTERY_STATUS = "battery_status"
23+
EVENT_FAULT = "fault"
2324
EVENT_CHANGE_MODE = "change_mode"
2425
EVENT_DEVICE_IDLE = "device_idle"
2526
EVENT_FS_ALARM = "fs_status_update"

custom_components/bhyve/coordinator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
EVENT_BATTERY_STATUS,
1616
EVENT_CHANGE_MODE,
1717
EVENT_DEVICE_IDLE,
18+
EVENT_FAULT,
1819
EVENT_FS_ALARM,
1920
EVENT_RAIN_DELAY,
2021
EVENT_SET_MANUAL_PRESET_TIME,
@@ -260,6 +261,14 @@ async def async_handle_device_event(self, event_data: dict[str, Any]) -> None:
260261
device_data["status"] = {}
261262
device_data["status"]["rain_delay"] = event_data.get("delay", 0)
262263

264+
elif event == EVENT_FAULT:
265+
# Update station fault information
266+
if "status" not in device_data:
267+
device_data["status"] = {}
268+
device_data["status"]["station_faults"] = event_data.get(
269+
"station_faults", []
270+
)
271+
263272
elif event == EVENT_FS_ALARM:
264273
# Update flood sensor status
265274
if "status" not in device_data:

custom_components/bhyve/sensor.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class BHyveSensorEntityDescription(SensorEntityDescription):
8181
"""Describes BHyve sensor entity."""
8282

8383
unique_id_suffix: str
84+
name: str = ""
8485
# Callable that takes device_data and returns value
8586
value_fn: Any = None
8687
# Callable that takes device_data and returns attributes
@@ -95,7 +96,7 @@ class BHyveSensorEntityDescription(SensorEntityDescription):
9596
BHyveSensorEntityDescription(
9697
key="state",
9798
translation_key="state",
98-
name="state",
99+
name="State",
99100
icon="mdi:information",
100101
unique_id_suffix="state",
101102
entity_category=EntityCategory.DIAGNOSTIC,
@@ -107,7 +108,7 @@ class BHyveSensorEntityDescription(SensorEntityDescription):
107108
BHyveSensorEntityDescription(
108109
key="temperature",
109110
translation_key="temperature",
110-
name="temperature sensor",
111+
name="Temperature sensor",
111112
icon="mdi:thermometer",
112113
unique_id_suffix="temp",
113114
device_class=SensorDeviceClass.TEMPERATURE,
@@ -131,7 +132,7 @@ class BHyveSensorEntityDescription(SensorEntityDescription):
131132
BHyveSensorEntityDescription(
132133
key="battery",
133134
translation_key="battery",
134-
name="battery level",
135+
name="Battery level",
135136
icon="mdi:battery",
136137
unique_id_suffix="battery",
137138
device_class=SensorDeviceClass.BATTERY,
@@ -168,15 +169,13 @@ async def async_setup_entry(
168169
sensors = []
169170

170171
for device in devices:
171-
device_name = device.get("name", "Unknown Device")
172-
173172
if device.get("type") == DEVICE_SPRINKLER:
174173
# Add state sensors
175174
for base_description in SENSOR_TYPES_SPRINKLER:
176175
description = BHyveSensorEntityDescription(
177176
key=base_description.key,
178177
translation_key=base_description.translation_key,
179-
name=f"{device_name} {base_description.name}",
178+
name=base_description.name,
180179
icon=base_description.icon,
181180
unique_id_suffix=base_description.unique_id_suffix,
182181
entity_category=base_description.entity_category,
@@ -218,7 +217,7 @@ async def async_setup_entry(
218217
description = BHyveSensorEntityDescription(
219218
key=base_description.key,
220219
translation_key=base_description.translation_key,
221-
name=f"{device_name} {base_description.name}",
220+
name=base_description.name,
222221
icon=base_description.icon,
223222
unique_id_suffix=base_description.unique_id_suffix,
224223
device_class=base_description.device_class,
@@ -238,7 +237,7 @@ async def async_setup_entry(
238237
description = BHyveSensorEntityDescription(
239238
key=base_description.key,
240239
translation_key=base_description.translation_key,
241-
name=f"{device_name} {base_description.name}",
240+
name=base_description.name,
242241
icon=base_description.icon,
243242
unique_id_suffix=base_description.unique_id_suffix,
244243
device_class=base_description.device_class,
@@ -256,7 +255,7 @@ async def async_setup_entry(
256255
description = BHyveSensorEntityDescription(
257256
key=base_description.key,
258257
translation_key=base_description.translation_key,
259-
name=f"{device_name} {base_description.name}",
258+
name=base_description.name,
260259
icon=base_description.icon,
261260
unique_id_suffix=base_description.unique_id_suffix,
262261
device_class=base_description.device_class,
@@ -277,6 +276,7 @@ class BHyveSensor(BHyveCoordinatorEntity, SensorEntity):
277276
"""Define a BHyve sensor."""
278277

279278
entity_description: BHyveSensorEntityDescription
279+
_attr_has_entity_name = True
280280

281281
def __init__(
282282
self,
@@ -286,6 +286,7 @@ def __init__(
286286
) -> None:
287287
"""Initialize the sensor."""
288288
self.entity_description = description
289+
self._attr_name = description.name
289290
super().__init__(coordinator, device)
290291
self._attr_unique_id = (
291292
f"{self._mac_address}:{self._device_id}:{description.unique_id_suffix}"

custom_components/bhyve/switch.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,23 @@
6969
class BHyveSwitchEntityDescription(SwitchEntityDescription):
7070
"""Describes BHyve switch entity."""
7171

72+
name: str = ""
73+
7274

7375
def _create_program_switch(
7476
coordinator: BHyveDataUpdateCoordinator,
7577
device: BHyveDevice,
7678
program: BHyveTimerProgram,
7779
) -> BHyveProgramSwitch:
7880
"""Create a program switch entity."""
79-
device_name = device.get("name", "Unknown switch")
8081
program_name = program.get("name", "unknown")
8182
return BHyveProgramSwitch(
8283
coordinator,
8384
device,
8485
program,
8586
BHyveSwitchEntityDescription(
8687
key="program",
87-
name=f"{device_name} {program_name} program",
88+
name=f"{program_name} program",
8889
icon="mdi:bulletin-board",
8990
entity_category=EntityCategory.CONFIG,
9091
),
@@ -122,7 +123,7 @@ async def async_setup_entry(
122123
device,
123124
BHyveSwitchEntityDescription(
124125
key="rain_delay",
125-
name=f"{device.get('name')} rain delay",
126+
name="Rain delay",
126127
icon="mdi:weather-pouring",
127128
entity_category=EntityCategory.CONFIG,
128129
),
@@ -170,6 +171,7 @@ class BHyveProgramSwitch(BHyveCoordinatorEntity, SwitchEntity):
170171
"""Define a BHyve program switch."""
171172

172173
entity_description: BHyveSwitchEntityDescription
174+
_attr_has_entity_name = True
173175

174176
def __init__(
175177
self,
@@ -180,6 +182,8 @@ def __init__(
180182
) -> None:
181183
"""Initialize the switch."""
182184
self.entity_description = description
185+
self._attr_name = description.name
186+
183187
super().__init__(coordinator, device)
184188

185189
self._program_id = program.get("id", "0")
@@ -253,6 +257,7 @@ class BHyveRainDelaySwitch(BHyveCoordinatorEntity, SwitchEntity):
253257
"""Define a BHyve rain delay switch."""
254258

255259
entity_description: BHyveSwitchEntityDescription
260+
_attr_has_entity_name = True
256261

257262
def __init__(
258263
self,
@@ -262,8 +267,10 @@ def __init__(
262267
) -> None:
263268
"""Initialize the switch."""
264269
self.entity_description = description
270+
265271
super().__init__(coordinator, device)
266272

273+
self._attr_name = description.name
267274
self._attr_unique_id = f"{self._mac_address}:{self._device_id}:rain_delay"
268275

269276
@property

custom_components/bhyve/valve.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,17 +245,16 @@ def __init__(
245245
) -> None:
246246
"""Initialize the valve."""
247247
self.entity_description = VALVE_DESCRIPTION
248-
super().__init__(coordinator, device)
248+
self._attr_name = f"{zone_name} zone"
249249

250-
name = f"{zone_name} zone"
250+
super().__init__(coordinator, device)
251251

252252
self._zone: BHyveZone = zone
253253
self._zone_id: str = zone.get("station", "")
254254

255255
self._attr_unique_id = (
256256
f"{self._mac_address}:{self._device_id}:{self._zone_id}:valve"
257257
)
258-
self._attr_name = name
259258

260259
self._entity_picture: str | None = zone.get("image_url")
261260
self._zone_name: str = zone_name

0 commit comments

Comments
 (0)