Skip to content

Commit

Permalink
Merge pull request #100 from imhotep/thumbnail-error
Browse files Browse the repository at this point in the history
error handling for thumbnails
  • Loading branch information
imhotep authored Feb 12, 2025
2 parents 2b89cd5 + c023a4c commit 0b3fb37
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 36 deletions.
21 changes: 21 additions & 0 deletions custom_components/unifi_access/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

from __future__ import annotations

import logging

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
import homeassistant.helpers.entity_registry as er

from .const import DOMAIN
from .coordinator import UnifiAccessCoordinator
Expand All @@ -20,6 +23,22 @@
Platform.SENSOR,
Platform.SWITCH,
]
_LOGGER = logging.getLogger(__name__)


async def remove_stale_entities(hass: HomeAssistant, entry_id: str):
"""Remove entities that are stale."""
_LOGGER.debug("Removing stale entities")
registry = er.async_get(hass)
config_entry_entities = registry.entities.get_entries_for_config_entry_id(entry_id)
stale_entities = [
entity
for entity in config_entry_entities
if (entity.disabled or not hass.states.get(entity.entity_id))
]
for entity in stale_entities:
_LOGGER.debug("Removing stale entity: %s", entity.entity_id)
registry.async_remove(entity.entity_id)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand All @@ -38,6 +57,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

await remove_stale_entities(hass, entry.entry_id)

return True


Expand Down
2 changes: 1 addition & 1 deletion custom_components/unifi_access/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, hass: HomeAssistant, hub) -> None:
hass,
_LOGGER,
name="Unifi Access Coordinator",
always_update=False,
always_update=True,
update_interval=update_interval,
)
self.hub = hub
Expand Down
18 changes: 18 additions & 0 deletions custom_components/unifi_access/door.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,21 @@ async def trigger_event(self, event: str, data: dict[str, str]):
_LOGGER.info(
"Event %s type %s for door %s fired", event, data["type"], self.name
)

def __eq__(self, value) -> bool:
"""Check if two doors are equal."""
if isinstance(value, UnifiAccessDoor):
return (
self.id == value.id
and self.name == value.name
and self.hub_type == value.hub_type
and self.door_position_status == value.door_position_status
and self.door_lock_relay_status == value.door_lock_relay_status
and self.lock_rule == value.lock_rule
and self.lock_rule_ended_time == value.lock_rule_ended_time
)
return False

def __repr__(self):
"""Return string representation of door."""
return f"<UnifiAccessDoor id={self.id} name={self.name} hub_type={self.hub_type} door_position_status={self.door_position_status} door_lock_relay_status={self.door_lock_relay_status} lock_rule={self.lock_rule} lock_rule_ended_time={self.lock_rule_ended_time}>"
20 changes: 13 additions & 7 deletions custom_components/unifi_access/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
DOORBELL_STOP_EVENT,
)
from .door import UnifiAccessDoor
from .hub import UnifiAccessHub

_LOGGER = logging.getLogger(__name__)

Expand All @@ -28,15 +29,20 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add event entity for passed config entry."""
hub: UnifiAccessHub = hass.data[DOMAIN][config_entry.entry_id]

coordinator = hass.data[DOMAIN]["coordinator"]
if hub.use_polling is False:
coordinator = hass.data[DOMAIN]["coordinator"]

async_add_entities(
(AccessEventEntity(hass, door) for door in coordinator.data.values()),
)
async_add_entities(
(DoorbellPressedEventEntity(hass, door) for door in coordinator.data.values()),
)
async_add_entities(
(AccessEventEntity(hass, door) for door in coordinator.data.values()),
)
async_add_entities(
(
DoorbellPressedEventEntity(hass, door)
for door in coordinator.data.values()
),
)


class AccessEventEntity(EventEntity):
Expand Down
59 changes: 36 additions & 23 deletions custom_components/unifi_access/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ def update(self):
"Getting door updates from Unifi Access %s Use Polling %s. Doors? %s",
self.host,
self.use_polling,
self.doors,
self.doors.keys(),
)
data = self._make_http_request(f"{self.host}{DOORS_URL}")

for _i, door in enumerate(data):
if door["is_bind_hub"]:
if door["is_bind_hub"] is True:
door_id = door["id"]
door_lock_rule = {"type": "", "ended_time": 0}
if self.supports_door_lock_rules:
Expand All @@ -138,6 +138,16 @@ def update(self):
]
existing_door.door_lock_rule = door_lock_rule["type"]
existing_door.door_lock_ended_time = door_lock_rule["ended_time"]
_LOGGER.debug(
"Updated existing door, id: %s, name: %s, dps: %s, door_lock_relay_status: %s, door lock rule: %s, door lock rule ended time: %s using polling %s",
door_id,
door["name"],
door["door_position_status"],
door["door_lock_relay_status"],
door_lock_rule["type"],
door_lock_rule["ended_time"],
self.use_polling,
)
else:
self._doors[door_id] = UnifiAccessDoor(
door_id=door["id"],
Expand All @@ -148,26 +158,26 @@ def update(self):
door_lock_rule_ended_time=door_lock_rule["ended_time"],
hub=self,
)
_LOGGER.debug(
"Found new door, id: %s, name: %s, dps: %s, door_lock_relay_status: %s, door lock rule: %s, door lock rule ended time: %s, using polling: %s",
door_id,
door["name"],
door["door_position_status"],
door["door_lock_relay_status"],
door_lock_rule["type"],
door_lock_rule["ended_time"],
self.use_polling,
)
else:
_LOGGER.debug("Door %s is not bound to a hub. Ignoring", door)

if self.update_t is None and self.use_polling is False:
_LOGGER.debug("Starting continuous updates. Polling disabled")
self.start_continuous_updates()

_LOGGER.debug("Got doors %s", self.doors)
return self._doors

def update_door(self, door_id: int) -> None:
"""Get latest door data for a specific door."""
_LOGGER.info("Getting door update from Unifi Access with id %s", door_id)
updated_door = self._make_http_request(f"{self.host}{DOORS_URL}/{door_id}")
door_id = updated_door["id"]
_LOGGER.debug("got door update %s", updated_door)
if door_id in self.doors:
existing_door: UnifiAccessDoor = self.doors[door_id]
existing_door.door_lock_relay_status = updated_door[
"door_lock_relay_status"
]
existing_door.door_position_status = updated_door["door_position_status"]
existing_door.name = updated_door["name"]
_LOGGER.debug("door %s updated", door_id)

def authenticate(self, api_token: str) -> str:
"""Test if we can authenticate with the host."""
self.set_api_token(api_token)
Expand Down Expand Up @@ -329,12 +339,15 @@ def _handle_location_update_v2(self, update):
lock_rule
]["until"]
if "thumbnail" in update["data"]:
existing_door.thumbnail = self._get_thumbnail_image(
f"{self.host}{STATIC_URL}{update['data']['thumbnail']['url']}"
)
existing_door.thumbnail_last_updated = datetime.fromtimestamp(
update["data"]["thumbnail"]["door_thumbnail_last_update"]
)
try:
existing_door.thumbnail = self._get_thumbnail_image(
f"{self.host}{STATIC_URL}{update['data']['thumbnail']['url']}"
)
existing_door.thumbnail_last_updated = datetime.fromtimestamp(
update["data"]["thumbnail"]["door_thumbnail_last_update"]
)
except (ApiError, ApiAuthError):
_LOGGER.error("Could not get thumbnail for door id %s", door_id)
return existing_door

def on_message(self, ws: websocket.WebSocketApp, message):
Expand Down
11 changes: 7 additions & 4 deletions custom_components/unifi_access/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from .const import DOMAIN
from .door import UnifiAccessDoor
from .hub import UnifiAccessHub

_LOGGER = logging.getLogger(__name__)

Expand All @@ -26,10 +27,12 @@ async def async_setup_entry(

coordinator = hass.data[DOMAIN]["coordinator"]
verify_ssl = config_entry.options.get("verify_ssl", False)
async_add_entities(
UnifiDoorImageEntity(hass, verify_ssl, door, config_entry.data["api_token"])
for door in coordinator.data.values()
)
hub: UnifiAccessHub = hass.data[DOMAIN][config_entry.entry_id]
if hub.use_polling is False:
async_add_entities(
UnifiDoorImageEntity(hass, verify_ssl, door, config_entry.data["api_token"])
for door in coordinator.data.values()
)


class UnifiDoorImageEntity(ImageEntity):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/unifi_access/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def _update_options(self):
):
self._attr_options.remove("lock_early")
else:
self._attr_options.add("lock_early")
self._attr_options.append("lock_early")

async def async_select_option(self, option: str) -> None:
"Select Door Lock Rule."
Expand Down

0 comments on commit 0b3fb37

Please sign in to comment.