diff --git a/custom_components/battery_notes/__init__.py b/custom_components/battery_notes/__init__.py index 4329d184..3853042e 100644 --- a/custom_components/battery_notes/__init__.py +++ b/custom_components/battery_notes/__init__.py @@ -136,7 +136,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[DOMAIN][DATA_LIBRARY_UPDATER] = library_updater if domain_config.get(CONF_ENABLE_AUTODISCOVERY): - discovery_manager = DiscoveryManager(hass, config) + discovery_manager = DiscoveryManager(hass, domain_config) await discovery_manager.start_discovery() else: _LOGGER.debug("Auto discovery disabled") @@ -181,7 +181,7 @@ async def async_remove_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return device: BatteryNotesDevice = hass.data[DOMAIN][DATA].devices[config_entry.entry_id] - if not device: + if not device or not device.coordinator.device_id: return data = {ATTR_REMOVE: True} @@ -221,7 +221,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): # Version 1 had a single config for qty & type, split them _LOGGER.debug("Migrating config entry from version %s", config_entry.version) - matches: re.Match = re.search( + matches = re.search( r"^(\d+)(?=x)(?:x\s)(\w+$)|([\s\S]+)", config_entry.data[CONF_BATTERY_TYPE] ) if matches: diff --git a/custom_components/battery_notes/binary_sensor.py b/custom_components/battery_notes/binary_sensor.py index 8d2bfe21..bfd37cd1 100644 --- a/custom_components/battery_notes/binary_sensor.py +++ b/custom_components/battery_notes/binary_sensor.py @@ -77,7 +77,7 @@ _LOGGER = logging.getLogger(__name__) -@dataclass +@dataclass(frozen=True, kw_only=True) class BatteryNotesBinarySensorEntityDescription( BatteryNotesEntityDescription, BinarySensorEntityDescription, @@ -123,7 +123,7 @@ async def async_setup_entry( device_id = config_entry.data.get(CONF_DEVICE_ID) - async def async_registry_updated(event: Event) -> None: + async def async_registry_updated(event: Event[er.EventEntityRegistryUpdatedData]) -> None: """Handle entity registry update.""" data = event.data if data["action"] == "remove": @@ -177,8 +177,6 @@ async def async_registry_updated(event: Event) -> None: device_class=BinarySensorDeviceClass.BATTERY, ) - device: BatteryNotesDevice = hass.data[DOMAIN][DATA].devices[config_entry.entry_id] - if coordinator.battery_low_template is not None: async_add_entities( [ @@ -314,6 +312,7 @@ class BatteryNotesBatteryLowTemplateSensor( """Represents a low battery threshold binary sensor.""" _attr_should_poll = False + _self_ref_update_count = 0 def __init__( self, @@ -587,6 +586,8 @@ def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" if ( + not self.coordinator.wrapped_battery + or ( wrapped_battery_state := self.hass.states.get( self.coordinator.wrapped_battery.entity_id @@ -616,7 +617,7 @@ def _handle_coordinator_update(self) -> None: ) @property - def extra_state_attributes(self) -> dict[str, str] | None: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of battery low.""" attrs = { diff --git a/custom_components/battery_notes/button.py b/custom_components/battery_notes/button.py index 5952b576..df809c7b 100644 --- a/custom_components/battery_notes/button.py +++ b/custom_components/battery_notes/button.py @@ -58,7 +58,7 @@ _LOGGER = logging.getLogger(__name__) -@dataclass +@dataclass(frozen=True, kw_only=True) class BatteryNotesButtonEntityDescription( BatteryNotesEntityDescription, ButtonEntityDescription, @@ -104,7 +104,7 @@ async def async_setup_entry( device_id = config_entry.data.get(CONF_DEVICE_ID, None) - async def async_registry_updated(event: Event) -> None: + async def async_registry_updated(event: Event[er.EventEntityRegistryUpdatedData]) -> None: """Handle entity registry update.""" data = event.data if data["action"] == "remove": diff --git a/custom_components/battery_notes/config_flow.py b/custom_components/battery_notes/config_flow.py index 3816555e..3a7305ec 100644 --- a/custom_components/battery_notes/config_flow.py +++ b/custom_components/battery_notes/config_flow.py @@ -515,11 +515,12 @@ async def save_options( errors["base"] = "orphaned_battery_note" return errors + title: Any = "" if CONF_NAME in user_input: title = user_input.get(CONF_NAME) - elif source_entity_id: + elif source_entity_id and entity_entry: title = entity_entry.name or entity_entry.original_name - else: + elif device_entry: title = device_entry.name_by_user or device_entry.name self._process_user_input(user_input, schema) diff --git a/custom_components/battery_notes/const.py b/custom_components/battery_notes/const.py index b8123096..5797074a 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.9" +MIN_HA_VERSION = "2024.10" manifestfile = Path(__file__).parent / "manifest.json" with open(file=manifestfile, encoding="UTF-8") as json_file: @@ -30,7 +30,7 @@ DEFAULT_BATTERY_LOW_THRESHOLD = 10 DEFAULT_BATTERY_INCREASE_THRESHOLD = 25 -DEFAULT_LIBRARY_URL = "https://raw.githubusercontent.com/andrew-codechimp/HA-Battery-Notes/main/custom_components/battery_notes/data/library.json" # pylint: disable=line-too-long +DEFAULT_LIBRARY_URL = "https://battery-notes-data.codechimp.org/library.json" CONF_SOURCE_ENTITY_ID = "source_entity_id" CONF_BATTERY_TYPE = "battery_type" diff --git a/custom_components/battery_notes/coordinator.py b/custom_components/battery_notes/coordinator.py index f0283153..7cf3a0f7 100644 --- a/custom_components/battery_notes/coordinator.py +++ b/custom_components/battery_notes/coordinator.py @@ -48,25 +48,25 @@ class BatteryNotesCoordinator(DataUpdateCoordinator): """Define an object to hold Battery Notes device.""" - device_id: str = None - source_entity_id: str = None + device_id: str | None = None + source_entity_id: str | None = None device_name: str battery_type: str battery_quantity: int battery_low_threshold: int battery_low_template: str | None - wrapped_battery: RegistryEntry - _current_battery_level: str = None + wrapped_battery: RegistryEntry | None = None + _current_battery_level: str | None = None enable_replaced: bool = True _round_battery: bool = False - _previous_battery_low: bool = None - _previous_battery_level: str = None + _previous_battery_low: bool | None = None + _previous_battery_level: str | None = None _battery_low_template_state: bool = False - _previous_battery_low_template_state: bool = None - _source_entity_name: str = None + _previous_battery_low_template_state: bool | None = None + _source_entity_name: str | None = None def __init__( - self, hass, store: BatteryNotesStorage, wrapped_battery: RegistryEntry + self, hass, store: BatteryNotesStorage, wrapped_battery: RegistryEntry | None ): """Initialize.""" self.store = store @@ -242,13 +242,13 @@ def last_replaced(self) -> datetime | None: return None @last_replaced.setter - def last_replaced(self, value): + def last_replaced(self, value: datetime): """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: + elif self.device_id: self.async_update_device_config(device_id=self.device_id, data=entry) @property diff --git a/custom_components/battery_notes/device.py b/custom_components/battery_notes/device.py index 2e02c007..429f4a90 100644 --- a/custom_components/battery_notes/device.py +++ b/custom_components/battery_notes/device.py @@ -2,6 +2,7 @@ import logging from datetime import datetime +from typing import cast from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import ( @@ -41,11 +42,11 @@ class BatteryNotesDevice: """Manages a Battery Note device.""" - config: ConfigEntry = None - store: BatteryNotesStorage = None - coordinator: BatteryNotesCoordinator = None - wrapped_battery: RegistryEntry = None - device_name: str = None + config: ConfigEntry + store: BatteryNotesStorage + coordinator: BatteryNotesCoordinator + wrapped_battery: RegistryEntry | None = None + device_name: str | None = None def __init__(self, hass: HomeAssistant, config: ConfigEntry) -> None: """Initialize the device.""" @@ -205,9 +206,9 @@ async def async_setup(self) -> bool: self.coordinator.device_id = device_id self.coordinator.source_entity_id = source_entity_id self.coordinator.device_name = self.device_name - self.coordinator.battery_type = config.data.get(CONF_BATTERY_TYPE) + self.coordinator.battery_type = cast(str, config.data.get(CONF_BATTERY_TYPE)) try: - self.coordinator.battery_quantity = int( + self.coordinator.battery_quantity = cast(int, config.data.get(CONF_BATTERY_QUANTITY) ) except ValueError: @@ -255,7 +256,7 @@ async def async_setup(self) -> bool: last_replaced, ) - self.coordinator.last_replaced = last_replaced + self.coordinator.last_replaced = datetime.fromisoformat(last_replaced) if last_replaced else None # If there is not a last_reported set to now if not self.coordinator.last_reported: diff --git a/custom_components/battery_notes/diagnostics.py b/custom_components/battery_notes/diagnostics.py index 6b7fa2c1..917cc716 100644 --- a/custom_components/battery_notes/diagnostics.py +++ b/custom_components/battery_notes/diagnostics.py @@ -23,15 +23,16 @@ async def async_get_config_entry_diagnostics( ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - device_id = config_entry.data.get(CONF_DEVICE_ID, None) - source_entity_id = config_entry.data.get(CONF_SOURCE_ENTITY_ID, None) - device_registry = dr.async_get(hass) entity_registry = er.async_get(hass) + device_id = config_entry.data.get(CONF_DEVICE_ID, None) + source_entity_id = config_entry.data.get(CONF_SOURCE_ENTITY_ID, None) + if source_entity_id: entity = entity_registry.async_get(source_entity_id) - device_id = entity.device_id + if entity: + device_id = entity.device_id diagnostics = {"entry": config_entry.as_dict()} diff --git a/custom_components/battery_notes/discovery.py b/custom_components/battery_notes/discovery.py index 7913e147..1fd15974 100644 --- a/custom_components/battery_notes/discovery.py +++ b/custom_components/battery_notes/discovery.py @@ -53,7 +53,7 @@ async def autodiscover_model( async def get_model_information( device_entry: dr.DeviceEntry, -) -> DeviceBatteryDetails | None: +) -> ModelInfo | None: """See if we have enough information to automatically setup the battery type.""" manufacturer = device_entry.manufacturer @@ -129,7 +129,7 @@ def should_process_device(self, device_entry: dr.DeviceEntry) -> bool: def _init_entity_discovery( self, device_entry: dr.DeviceEntry, - device_battery_details: DeviceBatteryDetails | None, + device_battery_details: DeviceBatteryDetails, ) -> None: """Dispatch the discovery flow for a given entity.""" existing_entries = [ diff --git a/custom_components/battery_notes/entity.py b/custom_components/battery_notes/entity.py index 1380eaf3..b6f621a7 100644 --- a/custom_components/battery_notes/entity.py +++ b/custom_components/battery_notes/entity.py @@ -7,13 +7,13 @@ from homeassistant.helpers.entity import EntityDescription -@dataclass +@dataclass(frozen=True, kw_only=True) class BatteryNotesRequiredKeysMixin: """Mixin for required keys.""" unique_id_suffix: str -@dataclass +@dataclass(frozen=True, kw_only=True) class BatteryNotesEntityDescription(EntityDescription, BatteryNotesRequiredKeysMixin): """Generic Battery Notes entity description.""" diff --git a/custom_components/battery_notes/library.py b/custom_components/battery_notes/library.py index 26410a5d..e1fd6e42 100644 --- a/custom_components/battery_notes/library.py +++ b/custom_components/battery_notes/library.py @@ -34,7 +34,7 @@ class Library: # pylint: disable=too-few-public-methods """Hold all known battery types.""" - _devices = [] + _devices: list = [] def __init__(self, hass: HomeAssistant) -> None: """Init.""" diff --git a/custom_components/battery_notes/library_updater.py b/custom_components/battery_notes/library_updater.py index 5c03a8bc..18b2ea7a 100644 --- a/custom_components/battery_notes/library_updater.py +++ b/custom_components/battery_notes/library_updater.py @@ -60,12 +60,12 @@ def __init__(self, hass: HomeAssistant): ) @callback - async def timer_update(self, time): + async def timer_update(self, now: datetime): """Need to update the library.""" if await self.time_to_update_library() is False: return - await self.get_library_updates(time) + await self.get_library_updates(now) if DOMAIN_CONFIG not in self.hass.data[DOMAIN]: return @@ -73,17 +73,17 @@ async def timer_update(self, time): domain_config: dict = self.hass.data[DOMAIN][DOMAIN_CONFIG] if domain_config.get(CONF_ENABLE_AUTODISCOVERY): - discovery_manager = DiscoveryManager(self.hass, self.hass.config) + discovery_manager = DiscoveryManager(self.hass, domain_config) await discovery_manager.start_discovery() else: _LOGGER.debug("Auto discovery disabled") @callback - async def get_library_updates(self, time): + async def get_library_updates(self, now: datetime): # pylint: disable=unused-argument """Make a call to GitHub to get the latest library.json.""" - def _update_library_json(library_file: str, content: str) -> dict[str, Any]: + def _update_library_json(library_file: str, content: str) -> None: with open(library_file, mode="w", encoding="utf-8") as file: file.write(content) file.close() @@ -160,8 +160,8 @@ def __init__( self._library_url = library_url self._session = session - async def async_get_data(self) -> any: - """Get data from the hosted library.""" + async def async_get_data(self) -> Any: + """Get data from the API.""" _LOGGER.debug(f"Updating library from {self._library_url}") return await self._api_wrapper(method="get", url=self._library_url) @@ -169,7 +169,7 @@ async def _api_wrapper( self, method: str, url: str, - ) -> any: + ) -> Any: """Get information from the API.""" try: async with async_timeout.timeout(10): diff --git a/custom_components/battery_notes/manifest.json b/custom_components/battery_notes/manifest.json index 97a7f2c6..5092f995 100644 --- a/custom_components/battery_notes/manifest.json +++ b/custom_components/battery_notes/manifest.json @@ -9,5 +9,5 @@ "integration_type": "device", "iot_class": "calculated", "issue_tracker": "https://github.com/andrew-codechimp/ha-battery-notes/issues", - "version": "2.0.0" + "version": "2.0.0-dev" } \ No newline at end of file diff --git a/custom_components/battery_notes/repairs.py b/custom_components/battery_notes/repairs.py index 11c894a4..70372687 100644 --- a/custom_components/battery_notes/repairs.py +++ b/custom_components/battery_notes/repairs.py @@ -2,21 +2,26 @@ from __future__ import annotations +from typing import cast + import voluptuous as vol from homeassistant import data_entry_flow from homeassistant.components.repairs import RepairsFlow from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir +REQUIRED_KEYS = ("entry_id", "device_id", "source_entity_id") class MissingDeviceRepairFlow(RepairsFlow): """Handler for an issue fixing flow.""" def __init__(self, data: dict[str, str | int | float | None] | None) -> None: """Initialize.""" - self.entry_id = data["entry_id"] - self.device_id = data["device_id"] - self.source_entity_id = data["source_entity_id"] + if not data or any(key not in data for key in REQUIRED_KEYS): + raise ValueError("Missing data") + self.entry_id = cast(str, data["entry_id"]) + self.device_id = cast(str, data["device_id"]) + self.source_entity_id = cast(str, data["source_entity_id"]) async def async_step_init( self, user_input: dict[str, str] | None = None diff --git a/custom_components/battery_notes/sensor.py b/custom_components/battery_notes/sensor.py index cbaa4786..4ee1c02e 100644 --- a/custom_components/battery_notes/sensor.py +++ b/custom_components/battery_notes/sensor.py @@ -83,7 +83,7 @@ ) -@dataclass +@dataclass(frozen=True, kw_only=True) class BatteryNotesSensorEntityDescription( BatteryNotesEntityDescription, SensorEntityDescription, @@ -133,7 +133,7 @@ async def async_setup_entry( device_id = config_entry.data.get(CONF_DEVICE_ID, None) - async def async_registry_updated(event: Event) -> None: + async def async_registry_updated(event: Event[er.EventEntityRegistryUpdatedData]) -> None: """Handle entity registry update.""" data = event.data if data["action"] == "remove": @@ -320,12 +320,6 @@ def __init__( f"sensor.{coordinator.device_name.lower()}_{description.key}" ) - _LOGGER.debug( - "Setting up %s with wrapped battery %s", - self.entity_id, - self.coordinator.wrapped_battery.entity_id, - ) - self.entity_description = description self._attr_unique_id = unique_id self.device = device @@ -361,7 +355,7 @@ async def async_state_changed_listener( # pylint: disable=unused-argument """Handle child updates.""" - if not self.coordinator.wrapped_battery.entity_id: + if not self.coordinator.wrapped_battery: return if ( @@ -423,13 +417,14 @@ async def _entity_rename_listener(event: Event) -> None: self.coordinator.wrapped_battery = new_wrapped_battery # Create a listener for the newly named battery entity - self.async_on_remove( - async_track_state_change_event( - self.hass, - [self.coordinator.wrapped_battery.entity_id], - self.async_state_changed_listener, + if self.coordinator.wrapped_battery: + self.async_on_remove( + async_track_state_change_event( + self.hass, + [self.coordinator.wrapped_battery.entity_id], + self.async_state_changed_listener, + ) ) - ) @callback def _filter_entity_id(event_data: Mapping[str, Any]) -> bool: @@ -457,7 +452,7 @@ async def _async_state_changed_listener( """Handle child updates.""" await self.async_state_changed_listener(event) - if self.coordinator.wrapped_battery.entity_id: + if self.coordinator.wrapped_battery: self.async_on_remove( async_track_state_change_event( self.hass, @@ -466,10 +461,10 @@ async def _async_state_changed_listener( ) ) - await self._register_entity_id_change_listener( - self.entity_id, - self.coordinator.wrapped_battery.entity_id, - ) + await self._register_entity_id_change_listener( + self.entity_id, + self.coordinator.wrapped_battery.entity_id, + ) # Call once on adding await _async_state_changed_listener() @@ -478,7 +473,7 @@ async def _async_state_changed_listener( registry = er.async_get(self.hass) if ( registry.async_get(self.entity_id) is not None - and self.coordinator.wrapped_battery.entity_id + and self.coordinator.wrapped_battery ): registry.async_update_entity_options( self.entity_id, @@ -526,7 +521,7 @@ def _handle_coordinator_update(self) -> None: self.async_write_ha_state() @property - def extra_state_attributes(self) -> dict[str, str] | None: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the battery type.""" # Battery related attributes @@ -556,7 +551,7 @@ def extra_state_attributes(self) -> dict[str, str] | None: return attrs @property - def native_value(self) -> StateType: + def native_value(self) -> StateType | Any | datetime: """Return the value reported by the sensor.""" return self._attr_native_value @@ -647,7 +642,7 @@ def native_value(self) -> str: return self.coordinator.battery_type_and_quantity @property - def extra_state_attributes(self) -> dict[str, str] | None: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the battery type.""" attrs = { @@ -711,7 +706,7 @@ def __init__( self._device_id = coordinator.device_id self._source_entity_id = coordinator.source_entity_id self.entity_description = description - self._native_value = None + self._native_value: datetime | None = None self._set_native_value(log_on_error=False) diff --git a/custom_components/battery_notes/services.py b/custom_components/battery_notes/services.py index 91b72b3e..99993eac 100644 --- a/custom_components/battery_notes/services.py +++ b/custom_components/battery_notes/services.py @@ -2,6 +2,7 @@ import logging from datetime import datetime +from typing import cast from homeassistant.core import ( HomeAssistant, @@ -110,6 +111,7 @@ async def handle_battery_replaced(call: ServiceCall) -> ServiceResponse: return None _LOGGER.error("Entity %s not configured in Battery Notes", source_entity_id) + return None else: device_entry = device_registry.async_get(device_id) @@ -168,7 +170,7 @@ async def handle_battery_replaced(call: ServiceCall) -> ServiceResponse: async def handle_battery_last_reported(call: ServiceCall) -> ServiceResponse: """Handle the service call.""" - days_last_reported = call.data.get(SERVICE_DATA_DAYS_LAST_REPORTED) + days_last_reported = cast(int, call.data.get(SERVICE_DATA_DAYS_LAST_REPORTED)) device: BatteryNotesDevice for device in hass.data[DOMAIN][DATA].devices.values(): diff --git a/custom_components/battery_notes/store.py b/custom_components/battery_notes/store.py index 0663f8cc..6320ec57 100644 --- a/custom_components/battery_notes/store.py +++ b/custom_components/battery_notes/store.py @@ -6,7 +6,7 @@ from collections import OrderedDict from collections.abc import MutableMapping from datetime import datetime -from typing import cast +from typing import Any, cast import attr from homeassistant.core import HomeAssistant, callback @@ -121,7 +121,7 @@ async def async_delete(self): self.devices = {} @callback - def async_get_device(self, device_id) -> DeviceEntry: + def async_get_device(self, device_id)-> dict[str, Any] | None: """Get an existing DeviceEntry by id.""" res = self.devices.get(device_id) return attr.asdict(res) if res else None @@ -135,17 +135,17 @@ def async_get_devices(self): return res @callback - def async_create_device(self, device_id: str, data: dict) -> DeviceEntry: + def async_create_device(self, device_id: str, data: dict) -> DeviceEntry | None: """Create a new DeviceEntry.""" if device_id in self.devices: - return False + return None new_device = DeviceEntry(**data, device_id=device_id) self.devices[device_id] = new_device self.async_schedule_save() return new_device @callback - def async_delete_device(self, device_id: str) -> None: + def async_delete_device(self, device_id: str) -> bool: """Delete DeviceEntry.""" if device_id in self.devices: del self.devices[device_id] @@ -162,31 +162,31 @@ def async_update_device(self, device_id: str, changes: dict) -> DeviceEntry: return new @callback - def async_get_entity(self, entity_id) -> DeviceEntry: + def async_get_entity(self, entity_id) -> dict[str, Any] | None: """Get an existing EntityEntry by id.""" res = self.entities.get(entity_id) return attr.asdict(res) if res else None @callback def async_get_entities(self): - """Get an existing EntityEntry by id.""" + """Get all entities.""" res = {} for key, val in self.entities.items(): res[key] = attr.asdict(val) return res @callback - def async_create_entity(self, entity_id: str, data: dict) -> EntityEntry: + def async_create_entity(self, entity_id: str, data: dict) -> EntityEntry | None: """Create a new EntityEntry.""" if entity_id in self.entities: - return False + return None new_entity = EntityEntry(**data, entity_id=entity_id) self.entities[entity_id] = new_entity self.async_schedule_save() return new_entity @callback - def async_delete_entity(self, entity_id: str) -> None: + def async_delete_entity(self, entity_id: str) -> bool: """Delete EntityEntry.""" if entity_id in self.entities: del self.entities[entity_id] diff --git a/hacs.json b/hacs.json index 01ab8296..e079150f 100644 --- a/hacs.json +++ b/hacs.json @@ -2,7 +2,7 @@ "name": "Battery Notes", "filename": "battery_notes.zip", "hide_default_branch": true, - "homeassistant": "2024.9.0", + "homeassistant": "2024.10.0", "render_readme": true, "zip_release": true, "persistent_directory": "data" diff --git a/pyproject.toml b/pyproject.toml index 79a88351..51fe9dd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ repository = "https://github.com/andrew-codechimp/HA-Battery-Notes" version = "0.0.0" [tool.poetry.dependencies] -homeassistant = "2024.9.0" +homeassistant = "2024.10.0" python = ">=3.12,<3.13" [tool.poetry.group.dev.dependencies] diff --git a/requirements.txt b/requirements.txt index bc7c770d..8cfe88ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ colorlog>=6.8.2,<7.0 -homeassistant==2024.9.0 +homeassistant==2024.10.0 ruff>=0.5.0,<0.8