diff --git a/.devcontainer/integration/devcontainer.json b/.devcontainer/integration/devcontainer.json index 3b1e9c5b7..fb98ffca0 100644 --- a/.devcontainer/integration/devcontainer.json +++ b/.devcontainer/integration/devcontainer.json @@ -1,6 +1,6 @@ { "name": "BN Integration", - "image": "mcr.microsoft.com/vscode/devcontainers/python:3.12-bullseye", + "image": "mcr.microsoft.com/devcontainers/python:3.12", "postCreateCommand": "scripts/setup", "runArgs": [ "--network=host" diff --git a/.ruff.toml b/.ruff.toml index d370c7934..1445f9325 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,6 +1,6 @@ # The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml -target-version = "py310" +target-version = "py312" [lint] select = [ diff --git a/custom_components/battery_notes/__init__.py b/custom_components/battery_notes/__init__.py index c52092012..5e0ca54aa 100644 --- a/custom_components/battery_notes/__init__.py +++ b/custom_components/battery_notes/__init__.py @@ -56,6 +56,7 @@ DOMAIN, DOMAIN_CONFIG, EVENT_BATTERY_NOT_REPORTED, + EVENT_BATTERY_REPLACED, EVENT_BATTERY_THRESHOLD, MIN_HA_VERSION, PLATFORMS, @@ -306,11 +307,7 @@ async def handle_battery_replaced(call): hass.data[DOMAIN][DATA].devices[config_entry_id].coordinator ) - entry = {"battery_last_replaced": datetime_replaced} - - coordinator.async_update_entity_config( - entity_id=source_entity_id, data=entry - ) + coordinator.last_replaced =datetime_replaced await coordinator.async_request_refresh() _LOGGER.debug( @@ -319,6 +316,24 @@ async def handle_battery_replaced(call): str(datetime_replaced), ) + hass.bus.async_fire( + EVENT_BATTERY_REPLACED, + { + ATTR_DEVICE_ID: coordinator.device_id or "", + ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id + or "", + ATTR_DEVICE_NAME: coordinator.device_name, + ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, + ATTR_BATTERY_TYPE: coordinator.battery_type, + ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, + }, + ) + + _LOGGER.debug( + "Raised event battery replaced %s", + coordinator.device_id, + ) + return _LOGGER.error("Entity %s not configured in Battery Notes", source_entity_id) @@ -340,11 +355,7 @@ async def handle_battery_replaced(call): hass.data[DOMAIN][DATA].devices[entry.entry_id].coordinator ) - device_entry = {"battery_last_replaced": datetime_replaced} - - coordinator.async_update_device_config( - device_id=device_id, data=device_entry - ) + coordinator.last_replaced =datetime_replaced await coordinator.async_request_refresh() @@ -354,6 +365,24 @@ async def handle_battery_replaced(call): str(datetime_replaced), ) + hass.bus.async_fire( + EVENT_BATTERY_REPLACED, + { + ATTR_DEVICE_ID: coordinator.device_id or "", + ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id + or "", + ATTR_DEVICE_NAME: coordinator.device_name, + ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, + ATTR_BATTERY_TYPE: coordinator.battery_type, + ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, + }, + ) + + _LOGGER.debug( + "Raised event battery replaced %s", + coordinator.device_id, + ) + # Found and dealt with, exit return @@ -362,6 +391,7 @@ async def handle_battery_replaced(call): device_id, ) + async def handle_battery_last_reported(call): """Handle the service call.""" days_last_reported = call.data.get(SERVICE_DATA_DAYS_LAST_REPORTED) diff --git a/custom_components/battery_notes/button.py b/custom_components/battery_notes/button.py index 522c8aacb..5952b576c 100644 --- a/custom_components/battery_notes/button.py +++ b/custom_components/battery_notes/button.py @@ -2,6 +2,7 @@ from __future__ import annotations +import logging from dataclasses import dataclass from datetime import datetime @@ -35,11 +36,18 @@ from . import PLATFORMS from .const import ( + ATTR_BATTERY_QUANTITY, + ATTR_BATTERY_TYPE, + ATTR_BATTERY_TYPE_AND_QUANTITY, + ATTR_DEVICE_ID, + ATTR_DEVICE_NAME, + ATTR_SOURCE_ENTITY_ID, CONF_ENABLE_REPLACED, CONF_SOURCE_ENTITY_ID, DATA, DOMAIN, DOMAIN_CONFIG, + EVENT_BATTERY_REPLACED, ) from .coordinator import BatteryNotesCoordinator from .device import BatteryNotesDevice @@ -47,6 +55,8 @@ BatteryNotesEntityDescription, ) +_LOGGER = logging.getLogger(__name__) + @dataclass class BatteryNotesButtonEntityDescription( @@ -241,15 +251,24 @@ async def async_added_to_hass(self) -> None: async def async_press(self) -> None: """Press the button.""" - device_id = self._device_id - - entry = {"battery_last_replaced": datetime.utcnow()} + self.coordinator.last_replaced = datetime.utcnow() + + self.hass.bus.async_fire( + EVENT_BATTERY_REPLACED, + { + ATTR_DEVICE_ID: self.coordinator.device_id or "", + ATTR_SOURCE_ENTITY_ID: self.coordinator.source_entity_id + or "", + ATTR_DEVICE_NAME: self.coordinator.device_name, + ATTR_BATTERY_TYPE_AND_QUANTITY: self.coordinator.battery_type_and_quantity, + ATTR_BATTERY_TYPE: self.coordinator.battery_type, + ATTR_BATTERY_QUANTITY: self.coordinator.battery_quantity, + }, + ) - if self._source_entity_id: - self.coordinator.async_update_entity_config( - entity_id=self.coordinator.source_entity_id, data=entry - ) - else: - self.coordinator.async_update_device_config(device_id=device_id, data=entry) + _LOGGER.debug( + "Raised event battery replaced %s", + self.coordinator.device_id, + ) await self.coordinator.async_request_refresh() diff --git a/custom_components/battery_notes/const.py b/custom_components/battery_notes/const.py index 7dc83cde6..9a3e608ff 100644 --- a/custom_components/battery_notes/const.py +++ b/custom_components/battery_notes/const.py @@ -11,7 +11,7 @@ LOGGER: Logger = getLogger(__package__) -MIN_HA_VERSION = "2024.7" +MIN_HA_VERSION = "2024.9" manifestfile = Path(__file__).parent / "manifest.json" with open(file=manifestfile, encoding="UTF-8") as json_file: @@ -71,6 +71,7 @@ EVENT_BATTERY_THRESHOLD = "battery_notes_battery_threshold" EVENT_BATTERY_INCREASED = "battery_notes_battery_increased" EVENT_BATTERY_NOT_REPORTED = "battery_notes_battery_not_reported" +EVENT_BATTERY_REPLACED = "battery_notes_battery_replaced" ATTR_DEVICE_ID = "device_id" ATTR_SOURCE_ENTITY_ID = "source_entity_id" diff --git a/custom_components/battery_notes/coordinator.py b/custom_components/battery_notes/coordinator.py index 536b2ce70..f0283153e 100644 --- a/custom_components/battery_notes/coordinator.py +++ b/custom_components/battery_notes/coordinator.py @@ -241,6 +241,16 @@ def last_replaced(self) -> datetime | None: return last_replaced_date return None + @last_replaced.setter + def last_replaced(self, value): + """Set the last replaced datetime and store it.""" + entry = {LAST_REPLACED: value} + + if self.source_entity_id: + self.async_update_entity_config(entity_id=self.source_entity_id, data=entry) + else: + self.async_update_device_config(device_id=self.device_id, data=entry) + @property def last_reported(self) -> datetime | None: """Get the last reported datetime.""" @@ -257,12 +267,13 @@ def last_reported(self) -> datetime | None: str(entry[LAST_REPORTED]) + "+00:00" ) return last_reported_date + return None @last_reported.setter def last_reported(self, value): """Set the last reported datetime and store it.""" - entry = {"battery_last_reported": value} + entry = {LAST_REPORTED: value} if self.source_entity_id: self.async_update_entity_config(entity_id=self.source_entity_id, data=entry) diff --git a/custom_components/battery_notes/device.py b/custom_components/battery_notes/device.py index 050d94457..95a299619 100644 --- a/custom_components/battery_notes/device.py +++ b/custom_components/battery_notes/device.py @@ -1,6 +1,7 @@ """Battery Notes device, contains device level details.""" import logging +from datetime import datetime from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import ( @@ -186,6 +187,37 @@ async def async_setup(self) -> bool: self.coordinator.battery_low_threshold, ) + # If there is not a last replaced, set to device created date if not epoch + if not self.coordinator.last_replaced: + last_replaced = None + if entity.device_id: + device_entry = device_registry.async_get(entity.device_id) + + if device_entry.created_at.year > 1970: + last_replaced = device_entry.created_at.strftime("%Y-%m-%dT%H:%M:%S:%f") + else: + entity = entity_registry.async_get(source_entity_id) + if entity.created_at.year > 1970: + last_replaced = entity.created_at.strftime("%Y-%m-%dT%H:%M:%S:%f") + + _LOGGER.debug( + "Defaulting %s battery last replaced to %s", + source_entity_id or device_id, + last_replaced, + ) + + self.coordinator.last_replaced = last_replaced + + # If there is not a last_reported set to now + if not self.coordinator.last_reported: + last_reported = datetime.utcnow() + _LOGGER.debug( + "Defaulting %s battery last reported to %s", + source_entity_id or device_id, + last_replaced, + ) + self.coordinator.last_reported = last_reported + self.hass.data[DOMAIN][DATA].devices[config.entry_id] = self self.reset_jobs.append(config.add_update_listener(self.async_update)) diff --git a/custom_components/battery_notes/icons.json b/custom_components/battery_notes/icons.json index 65f7f0ad6..92b70a212 100644 --- a/custom_components/battery_notes/icons.json +++ b/custom_components/battery_notes/icons.json @@ -20,8 +20,14 @@ } }, "services": { - "set_battery_replaced": "mdi:battery-sync", - "check_battery_last_reported": "mdi:battery-unknown", - "check_battery_low": "mdi:battery-alert" + "set_battery_replaced": { + "service": "mdi:battery-sync" + }, + "check_battery_last_reported": { + "service": "mdi:battery-unknown" + }, + "check_battery_low": { + "service": "mdi:battery-alert" + } } } \ No newline at end of file diff --git a/custom_components/battery_notes/library_updater.py b/custom_components/battery_notes/library_updater.py index 15f810f68..cb3f772a5 100644 --- a/custom_components/battery_notes/library_updater.py +++ b/custom_components/battery_notes/library_updater.py @@ -2,7 +2,6 @@ from __future__ import annotations -import asyncio import json import logging import os @@ -172,7 +171,7 @@ async def _api_wrapper( # response.raise_for_status() return await response.text() - except asyncio.TimeoutError as exception: + except TimeoutError as exception: raise LibraryUpdaterClientCommunicationError( "Timeout error fetching information", ) from exception diff --git a/hacs.json b/hacs.json index 028896e95..01ab8296a 100644 --- a/hacs.json +++ b/hacs.json @@ -2,7 +2,7 @@ "name": "Battery Notes", "filename": "battery_notes.zip", "hide_default_branch": true, - "homeassistant": "2024.7.0", + "homeassistant": "2024.9.0", "render_readme": true, "zip_release": true, "persistent_directory": "data" diff --git a/requirements.txt b/requirements.txt index ff63af47b..bc7c770d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ colorlog>=6.8.2,<7.0 -homeassistant==2024.7.0 -ruff>=0.5.0,<0.8 \ No newline at end of file +homeassistant==2024.9.0 +ruff>=0.5.0,<0.8