Skip to content

Commit

Permalink
Move unavailable status to separate binary sensor (#27)
Browse files Browse the repository at this point in the history
Also, workaround bug in device tracker component & entity registry that can cause entities to not appear at restart.
  • Loading branch information
pnbruckner authored Apr 24, 2024
1 parent ddb1eaf commit 058393e
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 9 deletions.
33 changes: 28 additions & 5 deletions custom_components/google_maps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,40 @@

from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.typing import ConfigType

from .const import CONF_COOKIES_FILE, CONF_CREATE_ACCT_ENTITY, DOMAIN, NAME_PREFIX
from .coordinator import GMDataUpdateCoordinator, GMIntegData
from .helpers import ConfigID, ConfigUniqueIDs, cookies_file_path

_LOGGER = logging.getLogger(__name__)
_PLATFORMS = [Platform.DEVICE_TRACKER]
_PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER]


async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
"""Set up integration."""
hass.data[DOMAIN] = GMIntegData(ConfigUniqueIDs(hass))
ent_reg = er.async_get(hass)

async def device_work_around(_: Event) -> None:
"""Work around for device tracker component deleting devices.
The device tracker component level code, at startup, deletes devices that are
associated only with device_tracker entities. Not only that, it will delete
those device_tracker entities from the entity registry as well. So, when HA
shuts down, remove references to devices from our device_tracker entity registry
entries. They'll get set back up automatically the next time our config is
loaded (i.e., setup.)
"""
for c_entry in hass.config_entries.async_entries(DOMAIN):
for r_entry in er.async_entries_for_config_entry(ent_reg, c_entry.entry_id):
ent_reg.async_update_entity(r_entry.entity_id, device_id=None)

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device_work_around)
return True


async def entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
Expand Down Expand Up @@ -55,8 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
username = entry.data[CONF_USERNAME]
create_acct_entity = entry.options[CONF_CREATE_ACCT_ENTITY]

if not (gmi_data := cast(GMIntegData | None, hass.data.get(DOMAIN))):
hass.data[DOMAIN] = gmi_data = GMIntegData(ConfigUniqueIDs(hass))
gmi_data = cast(GMIntegData, hass.data.get(DOMAIN))

# For "account person", unique ID is username (which is also returned in person.id.)
ent_reg = er.async_get(hass)
Expand Down
56 changes: 56 additions & 0 deletions custom_components/google_maps/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Google Maps binary sensor."""
from __future__ import annotations

from typing import cast

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import ATTRIBUTION, DOMAIN, NAME_PREFIX
from .coordinator import GMDataUpdateCoordinator, GMIntegData
from .helpers import ConfigID


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the binary sensor platform."""
cid = cast(ConfigID, entry.entry_id)
gmi_data = cast(GMIntegData, hass.data[DOMAIN])
coordinator = gmi_data.coordinators[cid]

async_add_entities([GoogleMapsBinarySensor(coordinator)])


class GoogleMapsBinarySensor(
CoordinatorEntity[GMDataUpdateCoordinator], BinarySensorEntity
):
"""Google Maps Binary Sensor."""

_attr_attribution = ATTRIBUTION
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY

def __init__(self, coordinator: GMDataUpdateCoordinator) -> None:
"""Initialize Google Maps Binary Sensor."""
super().__init__(coordinator)
entry = coordinator.config_entry
self._attr_unique_id = entry.entry_id
self._attr_name = f"{NAME_PREFIX} {entry.title} online"
self._attr_is_on = super().available

@property
def available(self) -> bool:
"""Return if entity is available."""
return True

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_is_on = super().available
super()._handle_coordinator_update()
5 changes: 5 additions & 0 deletions custom_components/google_maps/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@ def entity_picture(self) -> str | None:
return None
return self._misc.entity_picture

@property
def available(self) -> bool:
"""Return if entity is available."""
return True

@property
def force_update(self) -> bool:
"""Return True if state updates should be forced."""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/google_maps/gm_loc_sharing.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def cookies_expiration(self) -> datetime | None:
expirations: list[int] = []
for name in _VALID_COOKIE_NAMES:
if (data := cookie_data.get(name)) and (expiration := data[0]):
expirations.append(expiration)
expirations.append(expiration) # noqa: PERF401
if not expirations:
return None
return datetime.fromtimestamp(min(expirations)).astimezone()
Expand Down
2 changes: 2 additions & 0 deletions custom_components/google_maps/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path
from typing import Any, NewType, Self, cast

from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.restore_state import ExtraStoredData
Expand Down Expand Up @@ -185,6 +186,7 @@ def __init__(self, hass: HomeAssistant) -> None:
cfg_uids = {
cast(UniqueID, ent.unique_id)
for ent in er.async_entries_for_config_entry(ent_reg, cid)
if ent.domain == DT_DOMAIN
}
self._all_uids.update(cfg_uids)
self._cfg_uids[cid] = cfg_uids
Expand Down
6 changes: 3 additions & 3 deletions custom_components/google_maps/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"codeowners": ["@pnbruckner"],
"config_flow": true,
"dependencies": ["file_upload"],
"documentation": "https://github.com/pnbruckner/ha-google-maps/blob/1.3.1/README.md",
"documentation": "https://github.com/pnbruckner/ha-google-maps/blob/1.3.2b3/README.md",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/pnbruckner/ha-google-maps/issues",
"loggers": [],
"requirements": [],
"version": "1.3.1"
"requirements": ["locationsharinglib==5.0.1"],
"version": "1.3.2b3"
}

0 comments on commit 058393e

Please sign in to comment.