Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for frozen EntityDescription in 2024.1 #53

Merged
merged 3 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ where `<config>` is your Home Assistant configuration directory.

### Versions

This custom integration supports HomeAssistant versions 2021.12 or newer, using Python 3.9 or newer.
This custom integration supports HomeAssistant versions 2022.11 or newer.

### numpy on Raspberry Pi

Expand Down
46 changes: 23 additions & 23 deletions custom_components/composite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@

import voluptuous as vol

from homeassistant.config import load_yaml_config_file
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.requirements import async_process_requirements, RequirementsNotFound
from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN
from homeassistant.components.device_tracker.legacy import YAML_DEVICES
from homeassistant.components.persistent_notification import (
async_create as pn_async_create,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.config import load_yaml_config_file
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_ID, CONF_NAME, CONF_PLATFORM, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.requirements import RequirementsNotFound, async_process_requirements
from homeassistant.util import slugify

from .config_flow import split_conf
from .const import (
CONF_DEFAULT_OPTIONS,
CONF_REQ_MOVEMENT,
Expand All @@ -36,7 +36,6 @@
TZ_DEVICE_LOCAL,
TZ_DEVICE_UTC,
)
from .config_flow import split_conf
from .device_tracker import COMPOSITE_TRACKER

CONF_TZ_FINDER = "tz_finder"
Expand Down Expand Up @@ -111,7 +110,7 @@ def _defaults(config: dict) -> dict:


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Setup composite integration."""
"""Set up composite integration."""
hass.data[DOMAIN] = {DATA_LEGACY_WARNED: False}

# Get a list of all the object IDs in known_devices.yaml to see if any were created
Expand All @@ -125,8 +124,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
legacy_devices = {}
try:
legacy_ids = [
cv.slugify(id)
for id, dev in legacy_devices.items()
cv.slugify(obj_id)
for obj_id, dev in legacy_devices.items()
if cv.boolean(dev.get("track", False))
]
except vol.Invalid:
Expand All @@ -144,13 +143,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
tracker_configs: list[dict[str, Any]] = config[DOMAIN][CONF_TRACKERS]
conflict_ids: list[str] = []
for conf in tracker_configs:
id: str = conf[CONF_ID]
obj_id: str = conf[CONF_ID]

if id in legacy_ids:
conflict_ids.append(id)
elif id in cfg_entries:
if obj_id in legacy_ids:
conflict_ids.append(obj_id)
elif obj_id in cfg_entries:
hass.config_entries.async_update_entry(
cfg_entries[id], **split_conf(conf) # type: ignore[arg-type]
cfg_entries[obj_id], **split_conf(conf) # type: ignore[arg-type]
)
else:
hass.async_create_task(
Expand Down Expand Up @@ -192,8 +191,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
except RequirementsNotFound:
_LOGGER.debug("Process requirements failed: %s", pkg)
return False
else:
_LOGGER.debug("Process requirements suceeded: %s", pkg)
_LOGGER.debug("Process requirements suceeded: %s", pkg)

def create_timefinder() -> None:
"""Create timefinder object."""
Expand All @@ -202,15 +200,21 @@ def create_timefinder() -> None:
# does file I/O.

if pkg.split("==")[0].strip().endswith("L"):
from timezonefinderL import TimezoneFinder
from timezonefinderL import ( # pylint: disable=import-outside-toplevel
TimezoneFinder,
)

tf = TimezoneFinder()
elif config[DOMAIN][CONF_TZ_FINDER_CLASS] == "TimezoneFinder":
from timezonefinder import TimezoneFinder
from timezonefinder import ( # pylint: disable=import-outside-toplevel
TimezoneFinder,
)

tf = TimezoneFinder()
else:
from timezonefinder import TimezoneFinderL
from timezonefinder import ( # pylint: disable=import-outside-toplevel
TimezoneFinderL,
)

tf = TimezoneFinderL()
hass.data[DOMAIN][DATA_TF] = tf
Expand All @@ -222,11 +226,7 @@ def create_timefinder() -> None:

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up config entry."""
# async_forward_entry_setups was new in 2022.8
try:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
except AttributeError:
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


Expand Down
2 changes: 1 addition & 1 deletion custom_components/composite/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from typing import Any

from homeassistant.config_entries import ConfigFlow
from homeassistant.data_entry_flow import FlowResult
from homeassistant.const import CONF_ENTITY_ID, CONF_ID, CONF_NAME
from homeassistant.data_entry_flow import FlowResult

from .const import CONF_REQ_MOVEMENT, CONF_TIME_AS, DOMAIN

Expand Down
67 changes: 22 additions & 45 deletions custom_components/composite/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,13 @@
ATTR_SOURCE_TYPE,
DOMAIN as DT_DOMAIN,
PLATFORM_SCHEMA as DT_PLATFORM_SCHEMA,
SourceType,
)

# SourceType was new in 2022.9
try:
from homeassistant.components.device_tracker import SourceType

source_type_bluetooth: SourceType | str = SourceType.BLUETOOTH
source_type_bluetooth_le: SourceType | str = SourceType.BLUETOOTH_LE
source_type_gps: SourceType | str = SourceType.GPS
source_type_router: SourceType | str = SourceType.ROUTER
except ImportError:
from homeassistant.components.device_tracker import (
SOURCE_TYPE_BLUETOOTH,
SOURCE_TYPE_BLUETOOTH_LE,
SOURCE_TYPE_GPS,
SOURCE_TYPE_ROUTER,
)

source_type_bluetooth = SOURCE_TYPE_BLUETOOTH
source_type_bluetooth_le = SOURCE_TYPE_BLUETOOTH_LE
source_type_gps = SOURCE_TYPE_GPS
source_type_router = SOURCE_TYPE_ROUTER

from homeassistant.components.device_tracker.config_entry import TrackerEntity
from homeassistant.components.persistent_notification import (
async_create as pn_async_create,
)
from homeassistant.components.zone import ENTITY_ID_HOME
from homeassistant.components.zone import async_active_zone
from homeassistant.components.zone import ENTITY_ID_HOME, async_active_zone
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_BATTERY_CHARGING,
Expand All @@ -76,7 +54,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import GPSType, UNDEFINED, UndefinedType
from homeassistant.helpers.typing import UNDEFINED, GPSType, UndefinedType
from homeassistant.util.async_ import run_callback_threadsafe
import homeassistant.util.dt as dt_util
from homeassistant.util.location import distance
Expand All @@ -90,8 +68,8 @@
CONF_USE_PICTURE,
DATA_LEGACY_WARNED,
DATA_TF,
DEF_TIME_AS,
DEF_REQ_MOVEMENT,
DEF_TIME_AS,
DOMAIN,
MIN_ANGLE_SPEED,
MIN_SPEED_SECONDS,
Expand Down Expand Up @@ -123,9 +101,9 @@

SOURCE_TYPE_NON_GPS = (
SOURCE_TYPE_BINARY_SENSOR,
source_type_bluetooth,
source_type_bluetooth_le,
source_type_router,
SourceType.BLUETOOTH,
SourceType.BLUETOOTH_LE,
SourceType.ROUTER,
)

LAST_SEEN_ATTRS = (ATTR_LAST_SEEN, ATTR_LAST_TIMESTAMP)
Expand All @@ -146,8 +124,7 @@ def _entities(entities: list[str | dict]) -> list[dict]:
"composite tracker",
path=[idx, CONF_USE_PICTURE],
)
else:
already_using_picture = True
already_using_picture = True
result.append(entity)
else:
result.append(
Expand Down Expand Up @@ -260,9 +237,9 @@ class CompositeDeviceTracker(TrackerEntity, RestoreEntity):
def __init__(self, entry: ConfigEntry) -> None:
"""Initialize Composite Device Tracker."""
self._attr_name: str = entry.data[CONF_NAME]
id: str = entry.data[CONF_ID]
self._attr_unique_id = id
self.entity_id = f"{DT_DOMAIN}.{id}"
obj_id: str = entry.data[CONF_ID]
self._attr_unique_id = obj_id
self.entity_id = f"{DT_DOMAIN}.{obj_id}"
self._scanner_config: dict | None = _config_from_entry(entry)
self._lock = asyncio.Lock()

Expand Down Expand Up @@ -348,13 +325,13 @@ async def _setup_scanner(self) -> None:
if not self._scanner_config or self._scanner:
return

def setup_scanner() -> None:
def setup_comp_scanner() -> None:
"""Set up device scanner."""
self._scanner = CompositeScanner(
self.hass, cast(dict, self._scanner_config), self._see
)

await self.hass.async_add_executor_job(setup_scanner)
await self.hass.async_add_executor_job(setup_comp_scanner)

async def _shutdown_scanner(self) -> None:
"""Shutdown device scanner."""
Expand Down Expand Up @@ -389,7 +366,7 @@ def _see(
gps_accuracy: int | None = None,
battery: int | None = None,
attributes: dict | None = None,
source_type: str | None = source_type_gps,
source_type: SourceType | str | None = SourceType.GPS,
picture: str | None | UndefinedType = UNDEFINED,
) -> None:
"""Process update from CompositeScanner."""
Expand Down Expand Up @@ -417,7 +394,7 @@ def _async_see(
gps_accuracy: int | None = None,
battery: int | None = None,
attributes: dict | None = None,
source_type: str | None = source_type_gps,
source_type: SourceType | str | None = SourceType.GPS,
picture: str | None | UndefinedType = UNDEFINED,
) -> None:
"""Process update from CompositeScanner."""
Expand Down Expand Up @@ -635,7 +612,7 @@ def _use_non_gps_data(self, entity_id: str, state: str) -> bool:
if state == STATE_HOME or self._entities[entity_id].use_all_states:
return True
entities = self._entities.values()
if any(entity.source_type == source_type_gps for entity in entities):
if any(entity.source_type == SourceType.GPS for entity in entities):
return False
return all(
cast(str, entity.data) != STATE_HOME
Expand All @@ -651,7 +628,7 @@ def _dt_attr_from_utc(self, utc: datetime, tzone: tzinfo | None) -> datetime:
return dt_util.as_local(utc)
return utc

def _update_info(
def _update_info( # noqa: C901
self, entity_id: str, old_state: State | None, new_state: State | None
) -> None:
"""Update composite tracker from input entity state change."""
Expand Down Expand Up @@ -705,7 +682,7 @@ def _update_info(

state = new_state.state

if source_type == source_type_gps:
if source_type == SourceType.GPS:
# GPS coordinates and accuracy are required.
if not gps:
self._bad_entity(entity_id, "missing gps attributes")
Expand Down Expand Up @@ -783,12 +760,12 @@ def _update_info(
if state == STATE_HOME and cur_gps_is_home:
gps = cast(GPSType, (cur_lat, cur_lon))
gps_accuracy = cur_acc
source_type = source_type_gps
source_type = SourceType.GPS
# Otherwise, if new GPS data is valid (which is unlikely if
# new state is not 'home'),
# use it and make source_type gps.
elif gps:
source_type = source_type_gps
source_type = SourceType.GPS
# Otherwise, if new state is 'home' and old state is not 'home'
# and no GPS data, then use HA's configured Home location and
# make source_type gps.
Expand All @@ -800,7 +777,7 @@ def _update_info(
(self._hass.config.latitude, self._hass.config.longitude),
)
gps_accuracy = 0
source_type = source_type_gps
source_type = SourceType.GPS
# Otherwise, don't use any GPS data, but set location_name to
# new state.
else:
Expand Down Expand Up @@ -831,7 +808,7 @@ def _update_info(
try:
# timezone_at will return a string or None.
tzname = self._tf.timezone_at(lng=gps[1], lat=gps[0])
except Exception as exc:
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.warning("Error while finding time zone: %s", exc)
else:
# get_time_zone will return a tzinfo or None.
Expand Down
Loading