1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-12-06 18:44:14 -05:00

Merge pull request #102 from natekspencer/device-initialized

Add helper to check for device initialization instead of first status
This commit is contained in:
Nathan Spencer
2025-11-24 13:55:28 -07:00
committed by GitHub
4 changed files with 27 additions and 43 deletions

View File

@@ -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)

View File

@@ -129,29 +129,18 @@ class OasisDeviceCoordinator(DataUpdateCoordinator[list[OasisDevice]]):
except Exception:
_LOGGER.exception("Error fetching playlists from cloud")
any_success = False
for device in devices:
try:
ready = await self.mqtt_client.wait_until_ready(
device, request_status=True
)
if not ready:
_LOGGER.warning(
"Timeout waiting for Oasis device %s to be ready",
_LOGGER.debug(
"Oasis device %s not ready yet; will retry on next update",
device.serial_number,
)
continue
mac = await device.async_get_mac_address()
if not mac:
_LOGGER.warning(
"Could not get MAC address for Oasis device %s",
device.serial_number,
)
continue
any_success = True
device.schedule_track_refresh()
except Exception:
@@ -159,17 +148,7 @@ class OasisDeviceCoordinator(DataUpdateCoordinator[list[OasisDevice]]):
"Error preparing Oasis device %s", device.serial_number
)
if any_success:
self.attempt = 0
else:
if self.attempt > 2 or not self.data:
raise UpdateFailed(
"Couldn't read from any Oasis device "
f"after {self.attempt} attempts"
)
except UpdateFailed:
raise
self.attempt = 0
except Exception as ex:
if self.attempt > 2 or not (devices or self.data):
raise UpdateFailed(

View File

@@ -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,17 +324,18 @@ class OasisMqttClient(OasisClientProtocol):
serial,
)
# Wait for first status
# Wait for initialization
try:
await asyncio.wait_for(first_status_event.wait(), timeout=timeout)
return True
await asyncio.wait_for(is_initialized_event.wait(), timeout=timeout)
except asyncio.TimeoutError:
_LOGGER.debug(
"Timeout (%.1fs) waiting for first STATUS message from %s",
"Timeout (%.1fs) waiting for initialization from %s",
timeout,
serial,
)
return False
else:
return True
async def async_get_mac_address(self, device: OasisDevice) -> str | None:
"""
@@ -765,7 +765,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 +865,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()

View File

@@ -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:
"""