Skip to content

Commit 78a6f95

Browse files
andrew-codechimpgithub-actions[bot]slampToMoHHmichaeldelc
authored
Watcher (#665)
* WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * Start of a device class * WIP * WIP * WIP * WIP * Tidy up naming * Xiaomi gateway (#473) * Add Xiaomi Gateway 3 devices * Apply automatic changes --------- Co-authored-by: andrew-codechimp <[email protected]> * Update device: 27 by ThermoBeacon (#475) Co-authored-by: slamp <[email protected]> * Apply automatic changes * Update device: SML002 by Philips (#477) * Apply automatic changes * Update device: HM_Sec_SCo by Homematic (#480) * Apply automatic changes * Update device: HM_RC_4_3 by Homematic (#482) * Apply automatic changes * Update device: HmIP_DSD_PCB by Homematic (#484) * Apply automatic changes * Update device: ME167 by AVATTO (#486) * Apply automatic changes * Update device: TS0601 by TuYa (#488) * Apply automatic changes * Update device: TREDANSEN_cellular_blind_E2103 by Ikea (#492) * Apply automatic changes * Update device: SmartCode_traditional_electronic_deadbolt_99140_031 by Kwikset (#494) * Apply automatic changes * Update device: Water_leak_sensor_LS21001 by Linkind (#496) * Apply automatic changes * Update device: ZSMOKE by BRK_Brands_Inc (#498) * Apply automatic changes * Update device: Multi_Sensor_2015_model_3321_S by SmartThings (#500) * Apply automatic changes * Update device: TH6320ZW by Honeywell (#502) * Apply automatic changes * Update device: TS0042 by TuYa (#504) * Apply automatic changes * Update device: VALLHORN_Wireless_Motion_Sensor by IKEA_of_Sweden (#506) * Apply automatic changes * Device: Xiaomi - DJT11LM (#490) * Update device: DJT11LM by Xiaomi * Apply automatic changes --------- Co-authored-by: ToMoHH <[email protected]> Co-authored-by: Andrew Jackson <[email protected]> Co-authored-by: andrew-codechimp <[email protected]> * Update device: XHS2_SE by Sercomm_Corp (#509) Co-authored-by: michaeldelc <[email protected]> * Apply automatic changes * Update device: URC4470BC0_X_R by Universal_Electronics_Inc (#511) Co-authored-by: michaeldelc <[email protected]> * Apply automatic changes * Update device: View_Radon by Airthings (#513) Co-authored-by: michaeldelc <[email protected]> * Apply automatic changes * Update device: ZSE43 by Zooz (#515) Co-authored-by: jeffjensen <[email protected]> * Apply automatic changes * Update device: ZSE44 by Zooz (#518) Co-authored-by: jeffjensen <[email protected]> * Apply automatic changes * Update device: Smoke_sensor_ZSS_HM_SSD01 by Moes (#520) Co-authored-by: mhaket <[email protected]> * Apply automatic changes * library.json: Add Xiaomi MJYD02YL (#521) * Apply automatic changes * library.json: Fix MAX! Window Sensor (#522) * Apply automatic changes * library.json: Add MAX Wall Thermostat (#523) * Apply automatic changes * library.json: Add MAX Eco Switch (#524) * Apply automatic changes * library.json: add Xiaomi MUE4094RT (#525) * Apply automatic changes * library.json: fix MJYD02YL-A (#526) * Apply automatic changes * Update device: ZW187 by Aeotec_Ltd (#528) Co-authored-by: allanjamesvestal <[email protected]> * Apply automatic changes * Update device: 4AK1SZ by Ring (#530) Co-authored-by: allanjamesvestal <[email protected]> * Apply automatic changes * Update device: Roam by Sonos (#532) Co-authored-by: allanjamesvestal <[email protected]> * Apply automatic changes * Change Airthings Wave Plus and Allegion BE469ZP to AA, not AAA (#533) * Change Airthings Wave Plus and Allegion BE469ZP to AA, not AAA * Oops — actually change the JSON, not just the table. * Revert changes to the auto-generated library.md file * Remove trailing whitespace (I hope) * Apply automatic changes * Update non-standard spellings of "Rechargeable". (#534) * Update mis-spellings of "Rechargeable". * Revert changes to library.md * Update library.json * Apply automatic changes * Update hacs.json Make data directory persistent to stop overriding user library * Update device: Alarm_control_panel by Simplisafe (#539) * Apply automatic changes * Update device: Entry by Simplisafe (#541) * Apply automatic changes * Update device: Leak by Simplisafe (#543) * Apply automatic changes * Update device: Smoke by Simplisafe (#545) * Apply automatic changes * Update device: Motion by SimpliSafe (#547) * Apply automatic changes * Update device: Keypad by SimpliSafe (#549) * Apply automatic changes * Update device: Siren by SimpliSafe (#551) * Apply automatic changes * Update device: SNZB_02D by SONOFF (#555) * Apply automatic changes * Device: eWeLink - SNZB_01P (#553) * Update device: SNZB_01P by eWeLink * Apply automatic changes --------- Co-authored-by: richardmac64 <[email protected]> Co-authored-by: Andrew Jackson <[email protected]> Co-authored-by: andrew-codechimp <[email protected]> * Fixed wrong models (#556) * Apply automatic changes * Update device: SYMFONISK_sound_remote_gen2_E2123 by IKEA (#558) Co-authored-by: o0shojo0o <[email protected]> * Apply automatic changes * Update device: DIYRuZ_Flower by modkam_ru (#562) * Apply automatic changes * Update device: Smart_button_MINI_ZSB by TuYa (#564) * Apply automatic changes * Update device: lumi_remote_b28ac1 by unk_manufacturer (#566) * Apply automatic changes * WIP * WIP * WIP * WIP * WIP * Translations * Translations * WIP * WIP * WIP * Add type & quantity attribute * Fix threshold * WIP * Fix service coordinator * Error handling for service * Battery Low Event * Change event data * Typo * Add replaced automation * Typo * WIP * WIP on battery jitter * Rename to battery plus * Optional hide batter * Add service icon * Battery increased event * Docs * docs * lint * Tidy remove * Tidying * Add beta tag to docs * Better handle custom entity names * Version bump * Better naming * Tidy up naming * Use friendly device name if available * Work on naming * Community automations * Docs * Docs * Docs * Docs * Docs * Docs * Docs * Change to raw for images on readme * Lint * Docs link * Lint * Lint * Lint * Remove time check * Handle unavailable * Handle battery going unavailable * Docs * Fix HA warning * Fix unknown low * Logging & docs * Floats * Docs * Docs * Lint fixes --------- Co-authored-by: andrew-codechimp <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: slamp <[email protected]> Co-authored-by: ToMoHH <[email protected]> Co-authored-by: michaeldelc <[email protected]> Co-authored-by: jeffjensen <[email protected]> Co-authored-by: mhaket <[email protected]> Co-authored-by: Daniel Rheinbay <[email protected]> Co-authored-by: allanjamesvestal <[email protected]> Co-authored-by: Allan James Vestal <[email protected]> Co-authored-by: richardmac64 <[email protected]> Co-authored-by: Dennis Rathjen <[email protected]> Co-authored-by: o0shojo0o <[email protected]>
1 parent a4aeb1e commit 78a6f95

26 files changed

+1523
-140
lines changed

.devcontainer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"name": "andrew-codechimp/battery-notes",
33
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye",
44
"postCreateCommand": "scripts/setup",
5+
"runArgs": [
6+
"--network=host"
7+
],
58
"forwardPorts": [
69
8123
710
],

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"[python]": {
3-
"editor.defaultFormatter": "charliermarsh.ruff"
3+
"editor.defaultFormatter": "ms-python.black-formatter"
44
},
55
"python.formatting.provider": "none"
66
}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![hacs][hacsbadge]][hacs]
88

99
Integration to add battery notes to a device, with automatic discovery via a growing [battery library](library.md) for devices.
10-
Track both the battery type and also when the battery was replaced.
10+
Track the battery type, when the battery was replaced and also when a battery is low based on device or global thresholds.
1111

1212
*Please :star: this repo if you find it useful*
1313
*If you want to show your support please*
@@ -56,7 +56,7 @@ To get full use of the integration, please visit the [docs](https://andrew-codec
5656

5757
## Contributing to the Battery Library
5858

59-
[!["New Device Request"](https://github.com/andrew-codechimp/HA-Battery-Notes/blob/main/docs/assets/new-device-request.png)](https://github.com/andrew-codechimp/HA-Battery-Notes/issues/new?template=new_device_request.yml&title=[Device]%3A+)
59+
[!["New Device Request"](https://raw.githubusercontent.com/andrew-codechimp/ha-battery-notes/main/docs/assets/new-device-request.png)](https://github.com/andrew-codechimp/HA-Battery-Notes/issues/new?template=new_device_request.yml&title=[Device]%3A+)
6060

6161
To add a device definition to the battery library so that it will be automatically configured fill out the above form or see the [docs](https://andrew-codechimp.github.io/HA-Battery-Notes/library) for adding via pull request.
6262

custom_components/battery_notes/__init__.py

Lines changed: 103 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@
66
from __future__ import annotations
77

88
import logging
9+
import re
910
from datetime import datetime
11+
from dataclasses import dataclass, field
1012

1113
import homeassistant.helpers.config_validation as cv
1214
import voluptuous as vol
13-
import re
1415

1516
from awesomeversion.awesomeversion import AwesomeVersion
1617
from homeassistant.config_entries import ConfigEntry
1718
from homeassistant.core import HomeAssistant, callback
1819
from homeassistant.const import __version__ as HA_VERSION # noqa: N812
1920
from homeassistant.helpers.typing import ConfigType
2021
from homeassistant.helpers import device_registry as dr
22+
from homeassistant.helpers import entity_registry as er
2123
from homeassistant.util import dt as dt_util
2224

2325
from .config_flow import CONFIG_VERSION
2426

27+
from .device import BatteryNotesDevice
2528
from .discovery import DiscoveryManager
2629
from .library_updater import (
2730
LibraryUpdater,
2831
)
29-
from .coordinator import BatteryNotesCoordinator
3032
from .store import (
3133
async_get_registry,
3234
)
@@ -37,15 +39,21 @@
3739
PLATFORMS,
3840
CONF_ENABLE_AUTODISCOVERY,
3941
CONF_USER_LIBRARY,
42+
DATA,
4043
DATA_LIBRARY_UPDATER,
4144
CONF_SHOW_ALL_DEVICES,
4245
CONF_ENABLE_REPLACED,
46+
CONF_DEFAULT_BATTERY_LOW_THRESHOLD,
47+
CONF_BATTERY_INCREASE_THRESHOLD,
48+
CONF_HIDE_BATTERY,
49+
DEFAULT_BATTERY_LOW_THRESHOLD,
50+
DEFAULT_BATTERY_INCREASE_THRESHOLD,
4351
SERVICE_BATTERY_REPLACED,
4452
SERVICE_BATTERY_REPLACED_SCHEMA,
45-
DATA_COORDINATOR,
53+
SERVICE_DATA_DATE_TIME_REPLACED,
54+
DATA_STORE,
4655
ATTR_REMOVE,
4756
ATTR_DEVICE_ID,
48-
ATTR_DATE_TIME_REPLACED,
4957
CONF_BATTERY_TYPE,
5058
CONF_BATTERY_QUANTITY,
5159
)
@@ -63,13 +71,31 @@
6371
vol.Optional(CONF_USER_LIBRARY, default=""): cv.string,
6472
vol.Optional(CONF_SHOW_ALL_DEVICES, default=False): cv.boolean,
6573
vol.Optional(CONF_ENABLE_REPLACED, default=True): cv.boolean,
74+
vol.Optional(CONF_HIDE_BATTERY, default=False): cv.boolean,
75+
vol.Optional(
76+
CONF_DEFAULT_BATTERY_LOW_THRESHOLD,
77+
default=DEFAULT_BATTERY_LOW_THRESHOLD,
78+
): cv.positive_int,
79+
vol.Optional(
80+
CONF_BATTERY_INCREASE_THRESHOLD,
81+
default=DEFAULT_BATTERY_INCREASE_THRESHOLD,
82+
): cv.positive_int,
6683
},
6784
),
6885
),
6986
},
7087
extra=vol.ALLOW_EXTRA,
7188
)
7289

90+
91+
@dataclass
92+
class BatteryNotesData:
93+
"""Class for sharing data within the BatteryNotes integration."""
94+
95+
devices: dict[str, BatteryNotesDevice] = field(default_factory=dict)
96+
platforms: dict = field(default_factory=dict)
97+
98+
7399
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
74100
"""Integration setup."""
75101

@@ -86,60 +112,96 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
86112
CONF_ENABLE_AUTODISCOVERY: True,
87113
CONF_SHOW_ALL_DEVICES: False,
88114
CONF_ENABLE_REPLACED: True,
115+
CONF_HIDE_BATTERY: False,
116+
CONF_DEFAULT_BATTERY_LOW_THRESHOLD: DEFAULT_BATTERY_LOW_THRESHOLD,
117+
CONF_BATTERY_INCREASE_THRESHOLD: DEFAULT_BATTERY_INCREASE_THRESHOLD,
89118
}
90119

91120
hass.data[DOMAIN] = {
92121
DOMAIN_CONFIG: domain_config,
93122
}
94123

95124
store = await async_get_registry(hass)
125+
hass.data[DOMAIN][DATA_STORE] = store
96126

97-
coordinator = BatteryNotesCoordinator(hass, store)
98-
hass.data[DOMAIN][DATA_COORDINATOR] = coordinator
127+
hass.data[DOMAIN][DATA] = BatteryNotesData()
99128

100129
library_updater = LibraryUpdater(hass)
101130

102131
await library_updater.get_library_updates(dt_util.utcnow())
103132

104133
hass.data[DOMAIN][DATA_LIBRARY_UPDATER] = library_updater
105134

106-
await coordinator.async_refresh()
107-
108135
if domain_config.get(CONF_ENABLE_AUTODISCOVERY):
109136
discovery_manager = DiscoveryManager(hass, config)
110137
await discovery_manager.start_discovery()
111138
else:
112139
_LOGGER.debug("Auto discovery disabled")
113140

141+
# Register custom services
142+
register_services(hass)
143+
114144
return True
115145

116-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
117-
"""Set up a config entry."""
118146

119-
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
147+
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
148+
"""Set up a config entry."""
120149

121-
entry.async_on_unload(entry.add_update_listener(async_update_options))
150+
device: BatteryNotesDevice = BatteryNotesDevice(hass, config_entry)
122151

123-
# Register custom services
124-
register_services(hass)
152+
if not await device.async_setup():
153+
return False
125154

126155
return True
127156

128157

158+
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
159+
"""Unload a config entry."""
160+
data: BatteryNotesData = hass.data[DOMAIN][DATA]
161+
162+
device = data.devices.pop(config_entry.entry_id)
163+
await device.async_unload()
164+
165+
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
166+
167+
129168
async def async_remove_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
130169
"""Device removed, tidy up store."""
131170

132171
if "device_id" not in config_entry.data:
133172
return
134173

135-
device_id = config_entry.data["device_id"]
174+
device: BatteryNotesDevice = hass.data[DOMAIN][DATA].devices[config_entry.entry_id]
175+
if not device:
176+
return
136177

137-
coordinator: BatteryNotesCoordinator = hass.data[DOMAIN][DATA_COORDINATOR]
138178
data = {ATTR_REMOVE: True}
139179

140-
coordinator.async_update_device_config(device_id=device_id, data=data)
180+
device.coordinator.async_update_device_config(
181+
device_id=device.coordinator.device_id, data=data
182+
)
183+
184+
_LOGGER.debug("Removed Device %s", device.coordinator.device_id)
185+
186+
# Unhide the battery
187+
entity_registry = er.async_get(hass)
188+
if not device.wrapped_battery:
189+
return
190+
191+
if not (
192+
wrapped_battery_entity_entry := entity_registry.async_get(
193+
device.wrapped_battery.entity_id
194+
)
195+
):
196+
return
141197

142-
_LOGGER.debug("Removed Device %s", device_id)
198+
if wrapped_battery_entity_entry.hidden_by == er.RegistryEntryHider.INTEGRATION:
199+
entity_registry.async_update_entity(
200+
device.wrapped_battery.entity_id, hidden_by=None
201+
)
202+
_LOGGER.debug(
203+
"Unhidden Original Battery for device%s", device.coordinator.device_id
204+
)
143205

144206

145207
async def async_migrate_entry(hass, config_entry: ConfigEntry):
@@ -180,43 +242,47 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
180242

181243
return True
182244

245+
183246
@callback
184247
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
185248
"""Update options."""
186249
await hass.config_entries.async_reload(entry.entry_id)
187250

188251

189-
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
190-
"""Unload a config entry."""
191-
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
192-
193-
194252
@callback
195253
def register_services(hass):
196254
"""Register services used by battery notes component."""
197255

198256
async def handle_battery_replaced(call):
199257
"""Handle the service call."""
200258
device_id = call.data.get(ATTR_DEVICE_ID, "")
201-
datetime_replaced_entry = call.data.get(ATTR_DATE_TIME_REPLACED)
259+
datetime_replaced_entry = call.data.get(SERVICE_DATA_DATE_TIME_REPLACED)
202260

203261
if datetime_replaced_entry:
204-
datetime_replaced = dt_util.as_utc(datetime_replaced_entry).replace(tzinfo=None)
262+
datetime_replaced = dt_util.as_utc(datetime_replaced_entry).replace(
263+
tzinfo=None
264+
)
205265
else:
206266
datetime_replaced = datetime.utcnow()
207267

208268
device_registry = dr.async_get(hass)
209269

210270
device_entry = device_registry.async_get(device_id)
211271
if not device_entry:
272+
_LOGGER.error(
273+
"Device %s not found",
274+
device_id,
275+
)
212276
return
213277

214278
for entry_id in device_entry.config_entries:
215279
if (
216280
entry := hass.config_entries.async_get_entry(entry_id)
217281
) and entry.domain == DOMAIN:
282+
coordinator = (
283+
hass.data[DOMAIN][DATA].devices[entry.entry_id].coordinator
284+
)
218285

219-
coordinator: BatteryNotesCoordinator = hass.data[DOMAIN][DATA_COORDINATOR]
220286
device_entry = {"battery_last_replaced": datetime_replaced}
221287

222288
coordinator.async_update_device_config(
@@ -226,9 +292,19 @@ async def handle_battery_replaced(call):
226292
await coordinator.async_request_refresh()
227293

228294
_LOGGER.debug(
229-
"Device %s battery replaced on %s", device_id, str(datetime_replaced)
295+
"Device %s battery replaced on %s",
296+
device_id,
297+
str(datetime_replaced),
230298
)
231299

300+
# Found and dealt with, exit
301+
return
302+
303+
_LOGGER.error(
304+
"Device %s not configured in Battery Notes",
305+
device_id,
306+
)
307+
232308
hass.services.async_register(
233309
DOMAIN,
234310
SERVICE_BATTERY_REPLACED,

0 commit comments

Comments
 (0)