Explorar o código

Parse reservation events using source event selection and xdata format

Select events by 'Guest: ' title prefix, timed (non-all-day), with
X-ROOMCAL-DATA present. Extract guest_name, wifi_key, door_code from
the JSON payload rather than individual X- properties.
ZiB hai 4 meses
pai
achega
946b3a5756

+ 4 - 0
custom_components/room_cal_ha/const.py

@@ -14,6 +14,10 @@ CONF_CALDAV_ENABLED = "caldav_enabled"
 CONF_CALDAV_URL = "caldav_url"
 CONF_CALENDAR_NAME = "calendar_name"
 
+# iCal event selection and data extraction
+RESERVATION_TITLE_PREFIX = "Guest: "
+X_DATA_PROP = "X-ROOMCAL-DATA"
+
 # Sensor field keys — order is significant (used in UI and store)
 SENSOR_KEYS: list[str] = [
   "guest_name",

+ 36 - 28
custom_components/room_cal_ha/coordinator.py

@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import json
 import logging
 from dataclasses import dataclass
 from datetime import datetime, timedelta
@@ -22,10 +23,12 @@ from .const import (
   CONF_PASSWORD,
   CONF_USERNAME,
   DOMAIN,
+  RESERVATION_TITLE_PREFIX,
   SCAN_INTERVAL_MINUTES,
   SENSOR_KEYS,
   STORE_CURRENT,
   STORE_NEXT,
+  X_DATA_PROP,
   default_option_key,
 )
 from .models import ReservationData, merge
@@ -251,50 +254,55 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
 
 
 def _parse_event(event: caldav.Event) -> ReservationData | None:
-  """Extract ReservationData from a caldav Event. Returns None if unusable."""
+  """Extract ReservationData from a caldav Event. Returns None if not a reservation event."""
   try:
     cal = Calendar.from_ical(event.data)
     for component in cal.walk():
       if component.name != "VEVENT":
         continue
 
-      def get(prop: str) -> str | None:
-        val = component.get(prop)
-        return str(val) if val is not None else None
+      title = str(component.get("SUMMARY", ""))
+      if not title.startswith(RESERVATION_TITLE_PREFIX):
+        return None
 
       dtstart = component.get("DTSTART")
+      if dtstart is None:
+        return None
+      dt = dtstart.dt
+      if not isinstance(dt, datetime):
+        return None  # all-day event, not a reservation
+
+      check_in_date = dt.date()
+      check_in_time = dt.time()
+      check_out_date = None
+      check_out_time = None
+
       dtend = component.get("DTEND")
+      if dtend is not None:
+        dt_end = dtend.dt
+        if isinstance(dt_end, datetime):
+          check_out_date = dt_end.date()
+          check_out_time = dt_end.time()
 
-      check_in_date: Any = None
-      check_in_time: Any = None
-      check_out_date: Any = None
-      check_out_time: Any = None
-
-      if dtstart:
-        dt = dtstart.dt
-        if isinstance(dt, datetime):
-          check_in_date = dt.date()
-          check_in_time = dt.time()
-        else:
-          check_in_date = dt
-
-      if dtend:
-        dt = dtend.dt
-        if isinstance(dt, datetime):
-          check_out_date = dt.date()
-          check_out_time = dt.time()
-        else:
-          check_out_date = dt
+      x_data_prop = component.get(X_DATA_PROP)
+      x_data_str = str(x_data_prop) if x_data_prop is not None else None
+      if not x_data_str:
+        return None
+
+      try:
+        d = json.loads(x_data_str)
+      except (ValueError, TypeError):
+        _LOGGER.debug("Failed to parse X-ROOMCAL-DATA JSON in event %r", title)
+        return None
 
       return ReservationData(
-        guest_name=get("SUMMARY"),
+        guest_name=d.get("guest_name"),
         check_in_date=check_in_date,
         check_in_time=check_in_time,
         check_out_date=check_out_date,
         check_out_time=check_out_time,
-        wifi_ssid=get("X-WIFI-SSID"),
-        wifi_key=get("X-WIFI-KEY"),
-        door_code=get("X-DOOR-CODE"),
+        wifi_key=d.get("wifi_key"),
+        door_code=d.get("door_code"),
       )
   except Exception:
     _LOGGER.debug("Failed to parse event", exc_info=True)