From e6e84f8984d529cf70e8908940d0c305c2cab3d9 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Mon, 24 Nov 2025 20:31:57 +0000 Subject: [PATCH] Add helper to check for device initializtion instead of first status --- custom_components/oasis_mini/__init__.py | 2 +- .../pyoasiscontrol/clients/mqtt_client.py | 33 +++++++++---------- .../oasis_mini/pyoasiscontrol/device.py | 5 +++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/custom_components/oasis_mini/__init__.py b/custom_components/oasis_mini/__init__.py index 312ef3d..a871c6e 100644 --- a/custom_components/oasis_mini/__init__.py +++ b/custom_components/oasis_mini/__init__.py @@ -74,7 +74,7 @@ def setup_platform_from_coordinator( for device in devices: serial = device.serial_number - if not serial or serial in known_serials: + if not device.is_initialized or not serial or serial in known_serials: continue known_serials.add(serial) diff --git a/custom_components/oasis_mini/pyoasiscontrol/clients/mqtt_client.py b/custom_components/oasis_mini/pyoasiscontrol/clients/mqtt_client.py index 0e10113..a3b6ffb 100644 --- a/custom_components/oasis_mini/pyoasiscontrol/clients/mqtt_client.py +++ b/custom_components/oasis_mini/pyoasiscontrol/clients/mqtt_client.py @@ -54,7 +54,7 @@ class OasisMqttClient(OasisClientProtocol): _connected_event: Event signaled when a connection is established. _stop_event: Event signaled to request the loop to stop. _devices: Mapping of device serial to OasisDevice instances. - _first_status_events: Per-serial events signaled on receiving the first STATUS message. + _initialized_events: Per-serial events signaled on receiving the full device initialization. _mac_events: Per-serial events signaled when a device MAC address is received. _subscribed_serials: Set of serials currently subscribed to STATUS topics. _subscription_lock: Lock protecting subscribe/unsubscribe operations. @@ -71,7 +71,7 @@ class OasisMqttClient(OasisClientProtocol): self._devices: dict[str, OasisDevice] = {} # Per-device events - self._first_status_events: dict[str, asyncio.Event] = {} + self._initialized_events: dict[str, asyncio.Event] = {} self._mac_events: dict[str, asyncio.Event] = {} # Subscription bookkeeping @@ -101,7 +101,7 @@ class OasisMqttClient(OasisClientProtocol): """ Register an OasisDevice so MQTT messages for its serial are routed to that device. - Ensures the device has a serial_number (raises ValueError if not), stores the device in the client's registry, creates per-device asyncio.Events for first-status and MAC-address arrival, attaches this client to the device if it has no client, and schedules a subscription for the device's STATUS topics if the MQTT client is currently connected. + Ensures the device has a serial_number (raises ValueError if not), stores the device in the client's registry, creates per-device asyncio.Events for device initialization and MAC-address arrival, attaches this client to the device if it has no client, and schedules a subscription for the device's STATUS topics if the MQTT client is currently connected. Parameters: device (OasisDevice): The device instance to register. @@ -116,7 +116,7 @@ class OasisMqttClient(OasisClientProtocol): self._devices[serial] = device # Ensure we have per-device events - self._first_status_events.setdefault(serial, asyncio.Event()) + self._initialized_events.setdefault(serial, asyncio.Event()) self._mac_events.setdefault(serial, asyncio.Event()) # Attach ourselves as the client if the device doesn't already have one @@ -148,7 +148,7 @@ class OasisMqttClient(OasisClientProtocol): """ Unregisters a device from MQTT routing and cleans up related per-device state. - Removes the device's registration, first-status and MAC events. If there is an active MQTT client and the device's serial is currently subscribed, schedules an asynchronous unsubscription task. If the device has no serial_number, the call is a no-op. + Removes the device's registration, initialization and MAC events. If there is an active MQTT client and the device's serial is currently subscribed, schedules an asynchronous unsubscription task. If the device has no serial_number, the call is a no-op. Parameters: device (OasisDevice): The device to unregister; must have `serial_number` set. @@ -158,7 +158,7 @@ class OasisMqttClient(OasisClientProtocol): return self._devices.pop(serial, None) - self._first_status_events.pop(serial, None) + self._initialized_events.pop(serial, None) self._mac_events.pop(serial, None) # If connected and we were subscribed, unsubscribe @@ -286,11 +286,11 @@ class OasisMqttClient(OasisClientProtocol): Parameters: device (OasisDevice): The device to wait for; must have `serial_number` set. - timeout (float): Maximum seconds to wait for connection and for the first STATUS message. + timeout (float): Maximum seconds to wait for connection and for the device to be initialized. request_status (bool): If True, issue a status refresh after connection to encourage a STATUS update. Returns: - bool: `True` if the device's first STATUS message was observed within the timeout, `False` otherwise. + bool: `True` if the device was initialized within the timeout, `False` otherwise. Raises: RuntimeError: If the provided device does not have a `serial_number`. @@ -299,7 +299,7 @@ class OasisMqttClient(OasisClientProtocol): if not serial: raise RuntimeError("Device has no serial_number set") - first_status_event = self._first_status_events.setdefault( + is_initialized_event = self._initialized_events.setdefault( serial, asyncio.Event() ) @@ -317,7 +317,6 @@ class OasisMqttClient(OasisClientProtocol): # Optionally request a status refresh if request_status: try: - first_status_event.clear() await self.async_get_status(device) except Exception: # noqa: BLE001 _LOGGER.debug( @@ -325,13 +324,13 @@ class OasisMqttClient(OasisClientProtocol): serial, ) - # Wait for first status + # Wait for initialization try: - await asyncio.wait_for(first_status_event.wait(), timeout=timeout) + await asyncio.wait_for(is_initialized_event.wait(), timeout=timeout) return True except asyncio.TimeoutError: _LOGGER.debug( - "Timeout (%.1fs) waiting for first STATUS message from %s", + "Timeout (%.1fs) waiting for initialization from %s", timeout, serial, ) @@ -765,7 +764,7 @@ class OasisMqttClient(OasisClientProtocol): If the topic corresponds to a registered device, extracts the relevant status field and calls the device's update_from_status_dict with a mapping of the parsed values. For the "MAC_ADDRESS" status, sets the per-device MAC event to signal arrival of the MAC address. Always sets the - per-device first-status event once any status is processed for that serial. + per-device initialization event once the appropriate messages are processed for that serial. Parameters: msg (aiomqtt.Message): Incoming MQTT message; topic identifies device serial and status. @@ -865,8 +864,8 @@ class OasisMqttClient(OasisClientProtocol): if data: device.update_from_status_dict(data) - first_status_event = self._first_status_events.setdefault( + is_initialized_event = self._initialized_events.setdefault( serial, asyncio.Event() ) - if not first_status_event.is_set(): - first_status_event.set() + if not is_initialized_event.is_set() and device.is_initialized: + is_initialized_event.set() diff --git a/custom_components/oasis_mini/pyoasiscontrol/device.py b/custom_components/oasis_mini/pyoasiscontrol/device.py index bf9a543..dde90f6 100644 --- a/custom_components/oasis_mini/pyoasiscontrol/device.py +++ b/custom_components/oasis_mini/pyoasiscontrol/device.py @@ -155,6 +155,11 @@ class OasisDevice: if value: self.brightness_on = value + @property + def is_initialized(self) -> bool: + """Return `True` if the device is fully identified.""" + return bool(self.serial_number and self.mac_address and self.software_version) + @property def is_sleeping(self) -> bool: """