Przeglądaj źródła

Remove merge layer and sensor defaults

Store is now the single source of truth. CalDAV result is authoritative
when reachable (clears slot on empty fetch). Thin updates apply_partial
onto the stored record directly. Options flow is a single CalDAV step.
ZiB 4 miesięcy temu
rodzic
commit
000d5e1e3a

+ 2 - 25
custom_components/room_cal_ha/config_flow.py

@@ -28,8 +28,6 @@ from .const import (
   DEFAULT_NEXT_NAME,
   DEFAULT_ROOM_STATE_NAME,
   DOMAIN,
-  SENSOR_KEYS,
-  default_option_key,
 )
 
 
@@ -129,9 +127,8 @@ class RoomCalOptionsFlow(OptionsFlow):
           errors["base"] = "cannot_connect"
 
       if not errors:
-        self._pending_options = dict(self.config_entry.options)
-        self._pending_options.update(user_input)
-        return await self.async_step_defaults()
+        merged = {**self.config_entry.options, **user_input}
+        return self.async_create_entry(data=merged)
 
     opts = self.config_entry.options
     return self.async_show_form(
@@ -144,26 +141,6 @@ class RoomCalOptionsFlow(OptionsFlow):
       errors=errors,
     )
 
-  async def async_step_defaults(
-    self, user_input: dict[str, Any] | None = None
-  ) -> ConfigFlowResult:
-    if user_input is not None:
-      self._pending_options.update(
-        {default_option_key(k): v or None for k, v in user_input.items()}
-      )
-      return self.async_create_entry(data=self._pending_options)
-
-    opts = self.config_entry.options
-    schema_dict: dict[Any, Any] = {}
-    for key in SENSOR_KEYS:
-      opt_key = default_option_key(key)
-      schema_dict[vol.Optional(key, default=opts.get(opt_key, ""))] = str
-
-    return self.async_show_form(
-      step_id="defaults",
-      data_schema=vol.Schema(schema_dict),
-    )
-
 
 def _test_caldav_auth(url: str, username: str, password: str) -> None:
   """Blocking: verify credentials by fetching the principal."""

+ 0 - 6
custom_components/room_cal_ha/const.py

@@ -30,12 +30,6 @@ SENSOR_KEYS: list[str] = [
   "door_code",
 ]
 
-# entry.options key prefix for admin-configured sensor defaults
-DEFAULT_KEY_PREFIX = "default_"
-
-def default_option_key(sensor_key: str) -> str:
-  return f"{DEFAULT_KEY_PREFIX}{sensor_key}"
-
 # Default device names (suggested in config flow)
 DEFAULT_ROOM_STATE_NAME = "Room State"
 DEFAULT_CURRENT_NAME = "Current"

+ 10 - 32
custom_components/room_cal_ha/coordinator.py

@@ -25,13 +25,11 @@ from .const import (
   DOMAIN,
   RESERVATION_TITLE_PREFIX,
   SCAN_INTERVAL_MINUTES,
-  SENSOR_KEYS,
   STORE_CURRENT,
   STORE_NEXT,
   X_DATA_PROP,
-  default_option_key,
 )
-from .models import ReservationData, merge
+from .models import ReservationData
 from .store import ReservationStore
 
 _LOGGER = logging.getLogger(__name__)
@@ -68,49 +66,34 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
   def inject_guest_mode_switch(self, switch: Any) -> None:
     self._guest_mode_switch = switch
 
-  def _option_defaults(self) -> ReservationData:
-    opts = self.entry.options
-    kwargs: dict[str, Any] = {}
-    for key in SENSOR_KEYS:
-      kwargs[key] = opts.get(default_option_key(key))
-    return ReservationData(**kwargs)
-
   async def _async_update_data(self) -> CoordinatorData:
-    option_defaults = self._option_defaults()
-
     caldav_current: ReservationData | None = None
     caldav_next: ReservationData | None = None
+    caldav_fetched = False
 
     if self.entry.options.get(CONF_CALDAV_ENABLED):
       try:
         caldav_current, caldav_next = await self.hass.async_add_executor_job(
           self._fetch_caldav
         )
+        caldav_fetched = True
       except caldav_error.AuthorizationError:
         self.entry.async_start_reauth(self.hass)
         raise UpdateFailed("CalDAV authorization failed; re-auth required")
       except Exception as exc:
         _LOGGER.warning("CalDAV fetch failed, using cached data: %s", exc)
 
-    stored_current = self.store.get(STORE_CURRENT)
-    stored_next = self.store.get(STORE_NEXT)
-
     now = dt_util.utcnow()
 
-    # When CalDAV returned data, always persist it and refresh the timestamp.
-    # When CalDAV was skipped/failed, serve from store without touching the timestamp.
-    if caldav_current is not None:
+    if caldav_fetched:
+      # Calendar is authoritative: write its result including None (clears the slot).
       await self.store.async_set(STORE_CURRENT, caldav_current)
-      stored_current = caldav_current
-      self._current_last_refresh = now
-
-    if caldav_next is not None:
       await self.store.async_set(STORE_NEXT, caldav_next)
-      stored_next = caldav_next
+      self._current_last_refresh = now
       self._next_last_refresh = now
 
-    new_current = merge(option_defaults, stored_current)
-    new_next = merge(option_defaults, stored_next)
+    new_current = self.store.get(STORE_CURRENT) or ReservationData()
+    new_next = self.store.get(STORE_NEXT) or ReservationData()
 
     self._reschedule_mode_sync(new_current)
 
@@ -229,20 +212,15 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
     updated = stored.apply_partial(partial)
     await self.store.async_set(target, updated)
 
-    option_defaults = self._option_defaults()
     now = dt_util.utcnow()
-
     if target == STORE_CURRENT:
       self._current_last_refresh = now
     else:
       self._next_last_refresh = now
 
-    current = merge(option_defaults, self.store.get(STORE_CURRENT))
-    nxt = merge(option_defaults, self.store.get(STORE_NEXT))
-
     self.async_set_updated_data(CoordinatorData(
-      current=current,
-      next=nxt,
+      current=self.store.get(STORE_CURRENT) or ReservationData(),
+      next=self.store.get(STORE_NEXT) or ReservationData(),
       current_last_refresh=self._current_last_refresh,
       next_last_refresh=self._next_last_refresh,
     ))

+ 0 - 27
custom_components/room_cal_ha/models.py

@@ -56,30 +56,3 @@ class ReservationData:
       else:
         current[k] = v
     return ReservationData.from_dict(current)
-
-
-def merge(
-  option_defaults: ReservationData,
-  caldav_data: ReservationData | None,
-  overrides: ReservationData | None = None,
-) -> ReservationData:
-  """Merge three layers lowest-to-highest: option_defaults < caldav_data < overrides.
-
-  A None field at a given layer means "not set at this layer"; the next lower layer's
-  value is used. Explicit None stored in a lower layer can be overridden by a non-None
-  value in a higher layer.
-  """
-  result: dict[str, Any] = {}
-  for f in fields(ReservationData):
-    key = f.name
-    val = getattr(option_defaults, key)
-    if caldav_data is not None:
-      layer = getattr(caldav_data, key)
-      if layer is not None:
-        val = layer
-    if overrides is not None:
-      layer = getattr(overrides, key)
-      if layer is not None:
-        val = layer
-    result[key] = val
-  return ReservationData(**result)

+ 2 - 2
custom_components/room_cal_ha/store.py

@@ -36,6 +36,6 @@ class ReservationStore:
       return None
     return ReservationData.from_dict(raw)
 
-  async def async_set(self, key: str, data: ReservationData) -> None:
-    self._data[key] = data.to_dict()
+  async def async_set(self, key: str, data: ReservationData | None) -> None:
+    self._data[key] = data.to_dict() if data is not None else None
     await self.async_save()

+ 0 - 14
custom_components/room_cal_ha/strings.json

@@ -36,20 +36,6 @@
           "caldav_url": "CalDAV URL",
           "calendar_name": "Calendar name"
         }
-      },
-      "defaults": {
-        "title": "Sensor Defaults",
-        "description": "Optional fallback values used when a field is not provided by the calendar or a thin update.",
-        "data": {
-          "guest_name": "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": "WiFi SSID",
-          "wifi_key": "WiFi Key",
-          "door_code": "Door Code"
-        }
       }
     },
     "error": {

+ 0 - 14
custom_components/room_cal_ha/translations/en.json

@@ -36,20 +36,6 @@
           "caldav_url": "CalDAV URL",
           "calendar_name": "Calendar name"
         }
-      },
-      "defaults": {
-        "title": "Sensor Defaults",
-        "description": "Optional fallback values used when a field is not provided by the calendar or a thin update.",
-        "data": {
-          "guest_name": "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": "WiFi SSID",
-          "wifi_key": "WiFi Key",
-          "door_code": "Door Code"
-        }
       }
     },
     "error": {