Skip to content

Commit ed95c88

Browse files
Battery fixes (#911)
* Rename battery low entity id * Tidy rounding * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * Docs * Lint fixes
1 parent 3f56ca1 commit ed95c88

File tree

18 files changed

+277
-213
lines changed

18 files changed

+277
-213
lines changed

custom_components/battery_notes/binary_sensor.py

Lines changed: 42 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,13 @@
2121
BinarySensorEntityDescription,
2222
BinarySensorDeviceClass,
2323
)
24-
24+
from homeassistant.helpers.update_coordinator import (
25+
CoordinatorEntity,
26+
)
2527
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
2628
from homeassistant.helpers.event import (
27-
EventStateChangedData,
28-
async_track_state_change_event,
2929
async_track_entity_registry_updated_event,
3030
)
31-
from homeassistant.helpers.typing import EventType
3231
from homeassistant.helpers.reload import async_setup_reload_service
3332

3433
from homeassistant.const import (
@@ -42,27 +41,12 @@
4241

4342
from .const import (
4443
DOMAIN,
45-
DOMAIN_CONFIG,
4644
DATA,
47-
CONF_ENABLE_REPLACED,
48-
CONF_ROUND_BATTERY,
49-
CONF_BATTERY_INCREASE_THRESHOLD,
50-
EVENT_BATTERY_THRESHOLD,
51-
EVENT_BATTERY_INCREASED,
52-
DEFAULT_BATTERY_INCREASE_THRESHOLD,
53-
ATTR_DEVICE_ID,
54-
ATTR_BATTERY_QUANTITY,
55-
ATTR_BATTERY_TYPE,
56-
ATTR_BATTERY_TYPE_AND_QUANTITY,
57-
ATTR_BATTERY_LOW,
5845
ATTR_BATTERY_LOW_THRESHOLD,
59-
ATTR_DEVICE_NAME,
60-
ATTR_BATTERY_LEVEL,
61-
ATTR_PREVIOUS_BATTERY_LEVEL,
6246
)
6347

6448
from .common import isfloat
65-
from .device import BatteryNotesDevice
49+
6650
from .coordinator import BatteryNotesCoordinator
6751

6852
from .entity import (
@@ -147,21 +131,12 @@ async def async_registry_updated(event: Event) -> None:
147131

148132
device_id = async_add_to_device(hass, config_entry)
149133

150-
enable_replaced = True
151-
round_battery = False
152-
153-
if DOMAIN_CONFIG in hass.data[DOMAIN]:
154-
domain_config: dict = hass.data[DOMAIN][DOMAIN_CONFIG]
155-
enable_replaced = domain_config.get(CONF_ENABLE_REPLACED, True)
156-
round_battery = domain_config.get(CONF_ROUND_BATTERY, False)
157-
158134
description = BatteryNotesBinarySensorEntityDescription(
159135
unique_id_suffix="_battery_low",
160-
key="battery_low",
136+
key="_battery_plus_low",
161137
translation_key="battery_low",
162138
icon="mdi:battery-alert",
163139
entity_category=EntityCategory.DIAGNOSTIC,
164-
entity_registry_enabled_default=enable_replaced,
165140
device_class=BinarySensorDeviceClass.BATTERY,
166141
)
167142

@@ -175,8 +150,6 @@ async def async_registry_updated(event: Event) -> None:
175150
coordinator,
176151
description,
177152
f"{config_entry.entry_id}{description.unique_id_suffix}",
178-
device,
179-
round_battery,
180153
)
181154
]
182155
)
@@ -190,35 +163,28 @@ async def async_setup_platform(
190163
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
191164

192165

193-
class BatteryNotesBatteryLowSensor(BinarySensorEntity):
166+
class BatteryNotesBatteryLowSensor(BinarySensorEntity, CoordinatorEntity[BatteryNotesCoordinator]):
194167
"""Represents a low battery threshold binary sensor."""
195168

196169
_attr_should_poll = False
197-
_battery_entity_id = None
198-
device_name = None
199-
_previous_battery_low = None
200-
_previous_battery_level = None
201-
_previous_state_last_changed = None
202-
203-
entity_description: BatteryNotesBinarySensorEntityDescription
204170

205171
def __init__(
206172
self,
207173
hass: HomeAssistant,
208174
coordinator: BatteryNotesCoordinator,
209175
description: BatteryNotesBinarySensorEntityDescription,
210176
unique_id: str,
211-
device: BatteryNotesDevice,
212-
round_battery: bool,
213177
) -> None:
214178
"""Create a low battery binary sensor."""
179+
215180
device_registry = dr.async_get(hass)
216181

217182
self.coordinator = coordinator
218183
self.entity_description = description
219184
self._attr_unique_id = unique_id
220185
self._attr_has_entity_name = True
221-
self.round_battery = round_battery
186+
187+
super().__init__(coordinator=coordinator)
222188

223189
if coordinator.device_id and (
224190
device_entry := device_registry.async_get(coordinator.device_id)
@@ -228,141 +194,53 @@ def __init__(
228194
identifiers=device_entry.identifiers,
229195
)
230196

231-
self.entity_id = f"binary_sensor.{device.name.lower()}_{description.key}"
232-
self.device_name = device.name
233-
234-
self._battery_entity_id = (
235-
device.wrapped_battery.entity_id if device.wrapped_battery else None
236-
)
237-
238-
@callback
239-
async def async_state_changed_listener(
240-
self, event: EventType[EventStateChangedData] | None = None
241-
) -> None:
242-
# pylint: disable=unused-argument
243-
"""Handle child updates."""
244-
245-
if not self._battery_entity_id:
246-
return
247-
248-
if (
249-
wrapped_battery_state := self.hass.states.get(self._battery_entity_id)
250-
) is None or wrapped_battery_state.state in [STATE_UNAVAILABLE, STATE_UNKNOWN]:
251-
self._attr_is_on = False
252-
self._attr_available = True
253-
return
254-
255-
battery_low = bool(
256-
float(wrapped_battery_state.state) < self.coordinator.battery_low_threshold
257-
)
258-
259-
self.coordinator.set_battery_low(battery_low)
260-
261-
self._attr_is_on = self.coordinator.battery_low
262-
263-
self._attr_available = True
264-
265-
self.async_write_ha_state()
266-
267-
_LOGGER.debug(
268-
"%s battery_low changed: %s", self._battery_entity_id, battery_low
269-
)
270-
271-
await self.coordinator.async_request_refresh()
272-
273-
if isfloat(wrapped_battery_state.state):
274-
if self.round_battery:
275-
battery_level = round(float(wrapped_battery_state.state), 0)
276-
else:
277-
battery_level = round(float(wrapped_battery_state.state), 1)
278-
else:
279-
battery_level = wrapped_battery_state.state
280-
281-
if self._previous_state_last_changed:
282-
# Battery low event
283-
if battery_low != self._previous_battery_low:
284-
self.hass.bus.fire(
285-
EVENT_BATTERY_THRESHOLD,
286-
{
287-
ATTR_DEVICE_ID: self.coordinator.device_id,
288-
ATTR_DEVICE_NAME: self.device_name,
289-
ATTR_BATTERY_LOW: battery_low,
290-
ATTR_BATTERY_TYPE_AND_QUANTITY: self.coordinator.battery_type_and_quantity,
291-
ATTR_BATTERY_TYPE: self.coordinator.battery_type,
292-
ATTR_BATTERY_QUANTITY: self.coordinator.battery_quantity,
293-
ATTR_BATTERY_LEVEL: battery_level,
294-
ATTR_PREVIOUS_BATTERY_LEVEL: self._previous_battery_level,
295-
},
296-
)
297-
298-
_LOGGER.debug("battery_threshold event fired Low: %s", battery_low)
299-
300-
# Battery increased event
301-
increase_threshold = DEFAULT_BATTERY_INCREASE_THRESHOLD
302-
if DOMAIN_CONFIG in self.hass.data[DOMAIN]:
303-
domain_config: dict = self.hass.data[DOMAIN][DOMAIN_CONFIG]
304-
increase_threshold = domain_config.get(
305-
CONF_BATTERY_INCREASE_THRESHOLD, DEFAULT_BATTERY_INCREASE_THRESHOLD
306-
)
307-
308-
if wrapped_battery_state.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN]:
309-
if (
310-
wrapped_battery_state.state
311-
and self._previous_battery_level
312-
and float(wrapped_battery_state.state)
313-
>= (self._previous_battery_level + increase_threshold)
314-
):
315-
self.hass.bus.fire(
316-
EVENT_BATTERY_INCREASED,
317-
{
318-
ATTR_DEVICE_ID: self.coordinator.device_id,
319-
ATTR_DEVICE_NAME: self.device_name,
320-
ATTR_BATTERY_LOW: battery_low,
321-
ATTR_BATTERY_TYPE_AND_QUANTITY: self.coordinator.battery_type_and_quantity,
322-
ATTR_BATTERY_TYPE: self.coordinator.battery_type,
323-
ATTR_BATTERY_QUANTITY: self.coordinator.battery_quantity,
324-
ATTR_BATTERY_LEVEL: battery_level,
325-
ATTR_PREVIOUS_BATTERY_LEVEL: self._previous_battery_level,
326-
},
327-
)
328-
329-
_LOGGER.debug("battery_increased event fired")
330-
331-
self._previous_battery_level = battery_level
332-
self._previous_state_last_changed = wrapped_battery_state.last_changed
333-
self._previous_battery_low = battery_low
197+
self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{description.key}"
334198

335199
async def async_added_to_hass(self) -> None:
336200
"""Handle added to Hass."""
337201

338-
@callback
339-
async def _async_state_changed_listener(
340-
event: EventType[EventStateChangedData] | None = None,
341-
) -> None:
342-
"""Handle child updates."""
343-
await self.async_state_changed_listener(event)
344-
345-
if self._battery_entity_id:
346-
self.async_on_remove(
347-
async_track_state_change_event(
348-
self.hass, [self._battery_entity_id], _async_state_changed_listener
349-
)
350-
)
351-
352-
# Call once on adding
353-
await _async_state_changed_listener()
202+
await super().async_added_to_hass()
354203

355204
# Update entity options
356205
registry = er.async_get(self.hass)
357-
if registry.async_get(self.entity_id) is not None and self._battery_entity_id:
206+
if registry.async_get(self.entity_id) is not None and self.coordinator.wrapped_battery.entity_id:
358207
registry.async_update_entity_options(
359208
self.entity_id,
360209
DOMAIN,
361-
{"entity_id": self._battery_entity_id},
210+
{"entity_id": self.coordinator.wrapped_battery.entity_id},
362211
)
363212

364213
await self.coordinator.async_config_entry_first_refresh()
365214

215+
@callback
216+
def _handle_coordinator_update(self) -> None:
217+
"""Handle updated data from the coordinator."""
218+
219+
if (
220+
(
221+
wrapped_battery_state := self.hass.states.get(
222+
self.coordinator.wrapped_battery.entity_id
223+
)
224+
)
225+
is None
226+
or wrapped_battery_state.state
227+
in [
228+
STATE_UNAVAILABLE,
229+
STATE_UNKNOWN,
230+
]
231+
or not isfloat(wrapped_battery_state.state)
232+
):
233+
self._attr_is_on = None
234+
self._attr_available = False
235+
self.async_write_ha_state()
236+
return
237+
238+
self._attr_is_on = self.coordinator.battery_low
239+
240+
self.async_write_ha_state()
241+
242+
_LOGGER.debug("%s binary sensor battery_low set to: %s", self.coordinator.wrapped_battery.entity_id, self.coordinator.battery_low)
243+
366244
@property
367245
def extra_state_attributes(self) -> dict[str, str] | None:
368246
"""Return the state attributes of battery low."""

custom_components/battery_notes/button.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ def __init__(
174174
device_id: str,
175175
) -> None:
176176
"""Create a battery replaced button."""
177+
178+
super().__init__()
179+
177180
device_registry = dr.async_get(hass)
178181

179182
self.coordinator = coordinator
@@ -188,7 +191,7 @@ def __init__(
188191
identifiers=device.identifiers,
189192
)
190193

191-
self.entity_id = f"button.{device.name.lower()}_{description.key}"
194+
self.entity_id = f"button.{coordinator.device_name.lower()}_{description.key}"
192195

193196
async def async_added_to_hass(self) -> None:
194197
"""Handle added to Hass."""

custom_components/battery_notes/common.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
def isfloat(num):
55
"""Is the value a float."""
6-
try:
7-
float(num)
8-
return True
9-
except ValueError:
10-
return False
6+
if num:
7+
try:
8+
float(num)
9+
return True
10+
except ValueError:
11+
return False
12+
return False

custom_components/battery_notes/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
ATTR_BATTERY_LOW_THRESHOLD = "battery_low_threshold"
7171
ATTR_DEVICE_NAME = "device_name"
7272
ATTR_BATTERY_LEVEL = "battery_level"
73+
ATTR_BATTERY_LAST_REPORTED = "battery_last_reported"
74+
ATTR_BATTERY_LAST_REPORTED_LEVEL = "battery_last_reported_level"
7375
ATTR_PREVIOUS_BATTERY_LEVEL = "previous_battery_level"
7476

7577
SERVICE_BATTERY_REPLACED_SCHEMA = vol.Schema(

0 commit comments

Comments
 (0)