diff --git a/custom_components/reos_integration/__init__.py b/custom_components/reos_integration/__init__.py index 56f81e7..2968f5c 100755 --- a/custom_components/reos_integration/__init__.py +++ b/custom_components/reos_integration/__init__.py @@ -38,7 +38,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api.set_update_token_callback(__update_entry) - hass.data[DOMAIN][username] = {} hass.data[DOMAIN][username]["api"] = api @@ -48,4 +47,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() await hass.config_entries.async_forward_entry_setup(entry, "button") + await hass.config_entries.async_forward_entry_setup(entry, "sensor") + await hass.config_entries.async_forward_entry_setup(entry, "binary_sensor") return True diff --git a/custom_components/reos_integration/binary_sensor.py b/custom_components/reos_integration/binary_sensor.py new file mode 100644 index 0000000..fc0817e --- /dev/null +++ b/custom_components/reos_integration/binary_sensor.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from datetime import datetime +import logging + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .reos_api import ReosApi, ReosLockModel + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None: + coordinator = hass.data[DOMAIN][config_entry.data["username"]]["coordinator"] + + async_add_entities(ReosDoorIsFavorite(lock, coordinator) for _, lock in coordinator.data.items()) + +class BinarySensorBase(CoordinatorEntity, BinarySensorEntity): + + def update_value(self) -> None: + pass + + def __format_id(self, name: str) -> str: + return name.lower().replace(' ', '_') + + def __init__(self, model: ReosLockModel, coordinator, name: str) -> None: + super().__init__(coordinator, context=model.lock_id) + self._model = model + self.idx = self._model.lock_id + self._attr_unique_id = f"reos_lock_{self._model.lock_id}_{self.__format_id(name)}" + self._attr_name = f"{self._model.display} {name}" + self.update_value() + + @callback + def _handle_coordinator_update(self) -> None: + self._model = self.coordinator.data[self.idx] + self.update_value() + self.async_write_ha_state() + + @property + def device_info(self) -> DeviceInfo | None: + return DeviceInfo( + identifiers={(DOMAIN, self.idx)}, + ) + +class ReosDoorIsFavorite(BinarySensorBase): + + def update_value(self) -> None: + self._attr_is_on = self._model.is_favorite + + def __init__(self, model: ReosLockModel, coordinator) -> None: + super().__init__(model, coordinator, "Is Favorite") diff --git a/custom_components/reos_integration/button.py b/custom_components/reos_integration/button.py index 605c874..8a9526b 100644 --- a/custom_components/reos_integration/button.py +++ b/custom_components/reos_integration/button.py @@ -25,15 +25,15 @@ class ReosDoorButton(CoordinatorEntity, ButtonEntity): super().__init__(coordinator, context=model.lock_id) self._model = model self.idx = self._model.lock_id - self._attr_unique_id = f"reos_lock_{self._model.lock_id}" - self._attr_name = self._model.display + self._attr_unique_id = f"reos_lock_{self._model.lock_id}_buzzer" + self._attr_name = f"{self._model.display} Buzzer" self._attr_device_class = "door" self._attr_icon = "mdi:door" @callback def _handle_coordinator_update(self) -> None: self._model = self.coordinator.data[self.idx] - _LOGGER.info(f"_handle_coordinator_update for {self.idx}") + _LOGGER.info("_handle_coordinator_update for %s", self.idx) self.async_write_ha_state() def press(self, **kwargs) -> None: @@ -41,4 +41,7 @@ class ReosDoorButton(CoordinatorEntity, ButtonEntity): @property def device_info(self) -> DeviceInfo | None: - return None + return DeviceInfo( + identifiers={(DOMAIN, self.idx)}, + name=self._model.display + ) diff --git a/custom_components/reos_integration/coordinator.py b/custom_components/reos_integration/coordinator.py index 3d7d474..f43689e 100644 --- a/custom_components/reos_integration/coordinator.py +++ b/custom_components/reos_integration/coordinator.py @@ -23,7 +23,7 @@ class ReosCoordinator(DataUpdateCoordinator): hass, _LOGGER, name="Reos Locks", - update_interval=timedelta(hours=24), + update_interval=timedelta(hours=6), ) self.api = api diff --git a/custom_components/reos_integration/reos_api.py b/custom_components/reos_integration/reos_api.py index 7d97b33..60c99d7 100644 --- a/custom_components/reos_integration/reos_api.py +++ b/custom_components/reos_integration/reos_api.py @@ -1,4 +1,6 @@ from homeassistant.core import HomeAssistant +from datetime import datetime +import dateutil.parser import aiohttp import asyncio import logging @@ -72,6 +74,12 @@ class ReosApi: _json = await response.json() return _json["access_token"], _json["refresh_token"] + def __parse_datetime(self, dt: datetime) -> datetime | None: + if dt is not None: + return dateutil.parser.parse(str(dt)) + else: + return None + async def open_lock(self, lock_id: int) -> None: async with await self.__get_session() as session: await session.post(f"{PROTOCOL}://{self.host}{LOCK_OPEN_ENDPOINT}", json={"lockId": lock_id}) @@ -104,19 +112,27 @@ class ReosApi: _attr["title"], _attr["display_title"], _attr["is_favorite"], - self + self, + updated_at=self.__parse_datetime(_attr["updated_at"]), + created_at=self.__parse_datetime(_attr["created_at"]), + available_from=self.__parse_datetime(_attr["available_from"]), + available_to=self.__parse_datetime(_attr["available_to"]) ) locks.append(_lock) return locks class ReosLockModel: - def __init__(self, id: int, name: str, display: str, is_favorite: bool, api: ReosApi) -> None: + def __init__(self, id: int, name: str, display: str, is_favorite: bool, api: ReosApi, available_from: datetime = None, available_to: datetime = None, created_at: datetime = None, updated_at: datetime = None) -> None: self._id = id self.name = name self.display = display self._api = api self.is_favorite = is_favorite + self.available_from = available_from + self.available_to = available_to + self.created_at = created_at + self.updated_at = updated_at @property def lock_id(self) -> int: diff --git a/custom_components/reos_integration/sensor.py b/custom_components/reos_integration/sensor.py new file mode 100644 index 0000000..aa99c5b --- /dev/null +++ b/custom_components/reos_integration/sensor.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from datetime import datetime +import logging + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .reos_api import ReosApi, ReosLockModel + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None: + coordinator = hass.data[DOMAIN][config_entry.data["username"]]["coordinator"] + + async_add_entities(ReosDoorUpdatedAt(lock, coordinator) for _, lock in coordinator.data.items()) + async_add_entities(ReosDoorCreatedAt(lock, coordinator) for _, lock in coordinator.data.items()) + async_add_entities(ReosDoorAvailableFrom(lock, coordinator) for _, lock in coordinator.data.items()) + async_add_entities(ReosDoorAvailableTo(lock, coordinator) for _, lock in coordinator.data.items()) + +class SensorBase(CoordinatorEntity, SensorEntity): + + def update_value(self) -> None: + pass + + def __format_id(self, name: str) -> str: + return name.lower().replace(' ', '_') + + def __init__(self, model: ReosLockModel, coordinator, name: str) -> None: + super().__init__(coordinator, context=model.lock_id) + self._model = model + self.idx = self._model.lock_id + self._attr_unique_id = f"reos_lock_{self._model.lock_id}_{self.__format_id(name)}" + self._attr_name = f"{self._model.display} {name}" + self.update_value() + + @callback + def _handle_coordinator_update(self) -> None: + self._model = self.coordinator.data[self.idx] + self.update_value() + self.async_write_ha_state() + + @property + def device_info(self) -> DeviceInfo | None: + return DeviceInfo( + identifiers={(DOMAIN, self.idx)}, + ) + + +class TimestampSensorBase(SensorBase): + + def __init__(self, model: ReosLockModel, coordinator, name: str) -> None: + super().__init__(model, coordinator, name) + self._attr_device_class = SensorDeviceClass.TIMESTAMP + + +class ReosDoorUpdatedAt(TimestampSensorBase): + + def update_value(self) -> None: + self._attr_native_value = self._model.updated_at + + def __init__(self, model: ReosLockModel, coordinator) -> None: + super().__init__(model, coordinator, "Updated At") + +class ReosDoorCreatedAt(TimestampSensorBase): + + def update_value(self) -> None: + self._attr_native_value = self._model.created_at + + def __init__(self, model: ReosLockModel, coordinator) -> None: + super().__init__(model, coordinator, "Created At") + +class ReosDoorAvailableFrom(TimestampSensorBase): + + def update_value(self) -> None: + self._attr_native_value = self._model.available_from + + def __init__(self, model: ReosLockModel, coordinator) -> None: + super().__init__(model, coordinator, "Available From") + +class ReosDoorAvailableTo(TimestampSensorBase): + + def update_value(self) -> None: + self._attr_native_value = self._model.available_to + + def __init__(self, model: ReosLockModel, coordinator) -> None: + super().__init__(model, coordinator, "Available To")