Просмотр исходного кода

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 месяцев назад
Родитель
Сommit
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": {