| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- from __future__ import annotations
- import re
- from datetime import date, datetime, time
- from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
- from homeassistant.config_entries import ConfigEntry
- from homeassistant.core import HomeAssistant
- from homeassistant.helpers.device_registry import DeviceInfo
- from homeassistant.helpers.entity_platform import AddEntitiesCallback
- from homeassistant.helpers.update_coordinator import CoordinatorEntity
- from .const import (
- CONF_API_BASE_URL,
- CONF_CALDAV_URL,
- CONF_ROOM_NAME,
- CONF_SYNC_MODE,
- DOMAIN,
- STORE_CURRENT,
- STORE_NEXT,
- SYNC_MODE_API,
- SYNC_MODE_CALDAV,
- SYNC_MODE_MANUAL,
- )
- from .coordinator import CoordinatorData, RoomCalCoordinator
- from .models import ReservationData
- async def async_setup_entry(
- hass: HomeAssistant,
- entry: ConfigEntry,
- async_add_entities: AddEntitiesCallback,
- ) -> None:
- coordinator: RoomCalCoordinator = hass.data[DOMAIN][entry.entry_id]["coordinator"]
- room_name = entry.data.get(CONF_ROOM_NAME, entry.title)
- room_state_device = DeviceInfo(
- identifiers={(DOMAIN, f"{entry.entry_id}_room_state")},
- name=f"{room_name} Room State",
- )
- entities: list[SensorEntity] = [
- SyncSourceSensor(entry, room_name, room_state_device),
- ]
- if entry.options.get(CONF_SYNC_MODE) == SYNC_MODE_API:
- entities.append(ApiHealthSensor(coordinator, entry, room_state_device))
- for slot, device_name in (
- (STORE_CURRENT, f"{room_name} Current"),
- (STORE_NEXT, f"{room_name} Next"),
- ):
- device_id = f"{entry.entry_id}_{slot}"
- device_info = DeviceInfo(
- identifiers={(DOMAIN, device_id)},
- name=device_name,
- )
- entities += [
- GuestNameSensor(coordinator, entry, slot, device_info, "Guest Name", "guest_name", "mdi:account-convert"),
- ReservationDateTimeSensor(coordinator, entry, slot, device_info, "Arrival", "check_in_date", "check_in_time", "mdi:calendar-start"),
- ReservationTimeSensor(coordinator, entry, slot, device_info, "Check In", "check_in_time", "mdi:timer-check-outline"),
- ReservationDateTimeSensor(coordinator, entry, slot, device_info, "Departure", "check_out_date", "check_out_time", "mdi:calendar-end"),
- ReservationTimeSensor(coordinator, entry, slot, device_info, "Check Out", "check_out_time", "mdi:timer-cancel-outline"),
- ReservationStrSensor(coordinator, entry, slot, device_info, "WiFi SSID", "wifi_ssid", "mdi:wifi"),
- WiFiKeySensor(coordinator, entry, slot, device_info),
- ReservationStrSensor(coordinator, entry, slot, device_info, "Door Code", "door_code", "mdi:door-closed-lock"),
- LastRefreshSensor(coordinator, entry, slot, device_info),
- ]
- async_add_entities(entities)
- class SyncSourceSensor(SensorEntity):
- _attr_has_entity_name = True
- _attr_name = "Sync Source"
- _attr_icon = "mdi:sync"
- def __init__(self, entry: ConfigEntry, room_name: str, device_info: DeviceInfo) -> None:
- self._entry = entry
- self._attr_unique_id = f"{entry.entry_id}_sync_source"
- self._attr_device_info = device_info
- @property
- def native_value(self) -> str:
- return self._entry.options.get(CONF_SYNC_MODE, SYNC_MODE_MANUAL)
- @property
- def extra_state_attributes(self) -> dict[str, str | None]:
- mode = self._entry.options.get(CONF_SYNC_MODE, SYNC_MODE_MANUAL)
- if mode == SYNC_MODE_CALDAV:
- url = self._entry.options.get(CONF_CALDAV_URL)
- elif mode == SYNC_MODE_API:
- url = self._entry.options.get(CONF_API_BASE_URL)
- else:
- url = None
- return {"server_url": url}
- class _ReservationSensor(CoordinatorEntity[RoomCalCoordinator], SensorEntity):
- _attr_has_entity_name = True
- def __init__(
- self,
- coordinator: RoomCalCoordinator,
- entry: ConfigEntry,
- slot: str,
- device_info: DeviceInfo,
- name: str,
- field: str,
- icon: str | None = None,
- ) -> None:
- super().__init__(coordinator)
- self._slot = slot
- self._field = field
- self._attr_name = name
- self._attr_unique_id = f"{entry.entry_id}_{slot}_{field}"
- self._attr_device_info = device_info
- self._attr_icon = icon
- def _reservation(self) -> ReservationData:
- data: CoordinatorData = self.coordinator.data
- return data.current if self._slot == STORE_CURRENT else data.next
- class ReservationStrSensor(_ReservationSensor):
- @property
- def native_value(self) -> str | None:
- return getattr(self._reservation(), self._field)
- class GuestNameSensor(ReservationStrSensor):
- @property
- def extra_state_attributes(self) -> dict[str, str | None] | None:
- full: str | None = self._reservation().guest_name
- if full is None:
- return None
- parts = full.rsplit(" ", 1)
- first_name = parts[0]
- last_name = parts[1] if len(parts) > 1 else None
- first_abbr = first_name[0].upper() if first_name else None
- last_abbr = last_name[0].upper() if last_name else None
- public = f"{first_name} {last_abbr}." if last_abbr else full
- return {
- "first_name": first_name,
- "first_abbr": first_abbr,
- "last_name": last_name,
- "last_abbr": last_abbr,
- "public": public,
- }
- class WiFiKeySensor(_ReservationSensor):
- def __init__(self, coordinator, entry, slot, device_info) -> None:
- super().__init__(coordinator, entry, slot, device_info, "WiFi Key", "wifi_key", "mdi:wifi-lock")
- @property
- def native_value(self) -> str | None:
- return self._reservation().wifi_key
- @property
- def extra_state_attributes(self) -> dict[str, str] | None:
- res = self._reservation()
- if res.wifi_ssid is None or res.wifi_key is None:
- return None
- return {"qr": f"WIFI:S:{res.wifi_ssid};T:WPA;P:{res.wifi_key};;"}
- class ReservationTimeSensor(_ReservationSensor):
- """Time sensor. HA has no TIME device class for sensors; we expose as string ISO."""
- @property
- def native_value(self) -> str | None:
- val: time | None = getattr(self._reservation(), self._field)
- return val.isoformat() if val is not None else None
- def _dt_attrs(dt: datetime) -> dict[str, str]:
- return {
- "datetime": dt.isoformat(),
- "datetime_abbr": dt.strftime("%Y-%m-%d @ %-I %p"),
- "date": dt.strftime("%Y-%m-%d"),
- "date_weekday": dt.strftime("%a, %b %-d"),
- "datetime_weekday": dt.strftime("%a, %b %-d @ %-I %p"),
- "time": dt.strftime("%H:%M:%S"),
- "time_abbr": dt.strftime("%H:%M"),
- "hour": re.sub("^0", " ", dt.strftime("%I %p")),
- "weekday": dt.strftime("%A"),
- "weekday_abbr": dt.strftime("%a"),
- "month": dt.strftime("%B"),
- "month_abbr": dt.strftime("%b"),
- }
- class ReservationDateTimeSensor(_ReservationSensor):
- """Date sensor that also exposes combined datetime attributes."""
- _attr_device_class = SensorDeviceClass.DATE
- def __init__(
- self,
- coordinator: RoomCalCoordinator,
- entry: ConfigEntry,
- slot: str,
- device_info: DeviceInfo,
- name: str,
- field: str,
- time_field: str,
- icon: str | None = None,
- ) -> None:
- super().__init__(coordinator, entry, slot, device_info, name, field, icon)
- self._time_field = time_field
- @property
- def native_value(self) -> date | None:
- return getattr(self._reservation(), self._field)
- @property
- def extra_state_attributes(self) -> dict[str, str] | None:
- res = self._reservation()
- d: date | None = getattr(res, self._field)
- t: time | None = getattr(res, self._time_field)
- if d is None or t is None:
- return None
- return _dt_attrs(datetime.combine(d, t))
- class LastRefreshSensor(CoordinatorEntity[RoomCalCoordinator], SensorEntity):
- _attr_has_entity_name = True
- _attr_name = "Last Refresh"
- _attr_device_class = SensorDeviceClass.TIMESTAMP
- _attr_icon = "mdi:timer-sync"
- def __init__(
- self,
- coordinator: RoomCalCoordinator,
- entry: ConfigEntry,
- slot: str,
- device_info: DeviceInfo,
- ) -> None:
- super().__init__(coordinator)
- self._slot = slot
- self._attr_unique_id = f"{entry.entry_id}_{slot}_last_refresh"
- self._attr_device_info = device_info
- @property
- def native_value(self) -> datetime | None:
- data: CoordinatorData = self.coordinator.data
- return (
- data.current_last_refresh
- if self._slot == STORE_CURRENT
- else data.next_last_refresh
- )
- class ApiHealthSensor(CoordinatorEntity[RoomCalCoordinator], SensorEntity):
- _attr_has_entity_name = True
- _attr_name = "API Health"
- _attr_icon = "mdi:api"
- def __init__(
- self,
- coordinator: RoomCalCoordinator,
- entry: ConfigEntry,
- device_info: DeviceInfo,
- ) -> None:
- super().__init__(coordinator)
- self._attr_unique_id = f"{entry.entry_id}_api_health"
- self._attr_device_info = device_info
- @property
- def native_value(self) -> str | None:
- api_status = self.coordinator.data.api_status
- if api_status is None:
- return None
- return "OK" if api_status.status == "OK" else "Degraded"
- @property
- def extra_state_attributes(self) -> dict | None:
- api_status = self.coordinator.data.api_status
- if api_status is None or not isinstance(api_status.services, dict) or not api_status.services:
- return None
- return dict(api_status.services)
|