mirror of
https://github.com/natekspencer/hacs-oasis_mini.git
synced 2025-12-06 18:44:14 -05:00
Dynamically handle devices and other enhancements
This commit is contained in:
@@ -3,18 +3,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_EMAIL, Platform
|
from homeassistant.const import CONF_EMAIL, Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
import homeassistant.helpers.entity_registry as er
|
import homeassistant.helpers.entity_registry as er
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
from .coordinator import OasisDeviceCoordinator
|
from .coordinator import OasisDeviceCoordinator
|
||||||
|
from .entity import OasisDeviceEntity
|
||||||
from .helpers import create_client
|
from .helpers import create_client
|
||||||
from .pyoasiscontrol import OasisMqttClient, UnauthenticatedError
|
from .pyoasiscontrol import OasisDevice, OasisMqttClient, UnauthenticatedError
|
||||||
|
|
||||||
type OasisDeviceConfigEntry = ConfigEntry[OasisDeviceCoordinator]
|
type OasisDeviceConfigEntry = ConfigEntry[OasisDeviceCoordinator]
|
||||||
|
|
||||||
@@ -29,10 +33,47 @@ PLATFORMS = [
|
|||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
Platform.SELECT,
|
Platform.SELECT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
|
Platform.SWITCH,
|
||||||
Platform.UPDATE,
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform_from_coordinator(
|
||||||
|
entry: OasisDeviceConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
make_entities: Callable[[OasisDevice], Iterable[OasisDeviceEntity]],
|
||||||
|
update_before_add: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Generic pattern: add entities per device, including newly discovered ones."""
|
||||||
|
coordinator = entry.runtime_data
|
||||||
|
|
||||||
|
known_serials: set[str] = set()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _check_devices() -> None:
|
||||||
|
devices = coordinator.data or []
|
||||||
|
new_devices: list[OasisDevice] = []
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
serial = device.serial_number
|
||||||
|
if not serial or serial in known_serials:
|
||||||
|
continue
|
||||||
|
|
||||||
|
known_serials.add(serial)
|
||||||
|
new_devices.append(device)
|
||||||
|
|
||||||
|
if not new_devices:
|
||||||
|
return
|
||||||
|
|
||||||
|
if entities := make_entities(new_devices):
|
||||||
|
async_add_entities(entities, update_before_add)
|
||||||
|
|
||||||
|
# Initial population
|
||||||
|
_check_devices()
|
||||||
|
# Future updates (new devices discovered)
|
||||||
|
entry.async_on_unload(coordinator.async_add_listener(_check_devices))
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: OasisDeviceConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: OasisDeviceConfigEntry) -> bool:
|
||||||
"""Set up Oasis devices from a config entry."""
|
"""Set up Oasis devices from a config entry."""
|
||||||
cloud_client = create_client(hass, entry.data)
|
cloud_client = create_client(hass, entry.data)
|
||||||
@@ -148,3 +189,15 @@ async def async_migrate_entry(hass: HomeAssistant, entry: OasisDeviceConfigEntry
|
|||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_remove_config_entry_device(
|
||||||
|
hass: HomeAssistant, config_entry: OasisDeviceConfigEntry, device_entry: DeviceEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Remove a config entry from a device."""
|
||||||
|
current_serials = {d.serial_number for d in (config_entry.runtime_data.data or [])}
|
||||||
|
return not any(
|
||||||
|
identifier
|
||||||
|
for identifier in device_entry.identifiers
|
||||||
|
if identifier[0] == DOMAIN and identifier[1] in current_serials
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ from homeassistant.const import EntityCategory
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@@ -22,12 +22,15 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device sensors using config entry."""
|
"""Set up Oasis device sensors using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceBinarySensorEntity(coordinator, device, descriptor)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceBinarySensorEntity(entry.runtime_data, device, descriptor)
|
||||||
for descriptor in DESCRIPTORS
|
for device in new_devices
|
||||||
)
|
for descriptor in DESCRIPTORS
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTORS = {
|
DESCRIPTORS = {
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ from homeassistant.components.button import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
from .helpers import add_and_play_track
|
from .helpers import add_and_play_track
|
||||||
from .pyoasiscontrol import OasisDevice
|
from .pyoasiscontrol import OasisDevice
|
||||||
@@ -29,18 +29,24 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device button using config entry."""
|
"""Set up Oasis device button using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceButtonEntity(coordinator, device, descriptor)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceButtonEntity(entry.runtime_data, device, descriptor)
|
||||||
for descriptor in DESCRIPTORS
|
for device in new_devices
|
||||||
)
|
for descriptor in DESCRIPTORS
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
async def play_random_track(device: OasisDevice) -> None:
|
async def play_random_track(device: OasisDevice) -> None:
|
||||||
"""Play random track."""
|
"""Play random track."""
|
||||||
track = random.choice(list(TRACKS))
|
track = random.choice(list(TRACKS))
|
||||||
await add_and_play_track(device, track)
|
try:
|
||||||
|
await add_and_play_track(device, track)
|
||||||
|
except TimeoutError as err:
|
||||||
|
raise HomeAssistantError("Timeout adding track to queue") from err
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import logging
|
|||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@@ -46,34 +47,114 @@ class OasisDeviceCoordinator(DataUpdateCoordinator[list[OasisDevice]]):
|
|||||||
self.attempt += 1
|
self.attempt += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(10):
|
async with async_timeout.timeout(30):
|
||||||
if not self.data:
|
raw_devices = await self.cloud_client.async_get_devices()
|
||||||
raw_devices = await self.cloud_client.async_get_devices()
|
|
||||||
devices = [
|
existing_by_serial = {
|
||||||
OasisDevice(
|
d.serial_number: d for d in (self.data or []) if d.serial_number
|
||||||
model=raw_device.get("model", {}).get("name"),
|
}
|
||||||
serial_number=raw_device.get("serial_number"),
|
|
||||||
|
for raw in raw_devices:
|
||||||
|
if not (serial := raw.get("serial_number")):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if device := existing_by_serial.get(serial):
|
||||||
|
if name := raw.get("name"):
|
||||||
|
device.name = name
|
||||||
|
else:
|
||||||
|
device = OasisDevice(
|
||||||
|
model=(raw.get("model") or {}).get("name"),
|
||||||
|
serial_number=serial,
|
||||||
|
name=raw.get("name"),
|
||||||
cloud=self.cloud_client,
|
cloud=self.cloud_client,
|
||||||
)
|
)
|
||||||
for raw_device in raw_devices
|
|
||||||
]
|
devices.append(device)
|
||||||
else:
|
|
||||||
devices = self.data
|
new_serials = {d.serial_number for d in devices if d.serial_number}
|
||||||
for device in devices:
|
removed_serials = set(existing_by_serial) - new_serials
|
||||||
self.mqtt_client.register_device(device)
|
|
||||||
await self.mqtt_client.wait_until_ready(device, request_status=True)
|
if removed_serials:
|
||||||
if not await device.async_get_mac_address():
|
device_registry = dr.async_get(self.hass)
|
||||||
raise Exception(
|
for serial in removed_serials:
|
||||||
"Could not get mac address for %s", device.serial_number
|
_LOGGER.info(
|
||||||
|
"Oasis device %s removed from account; cleaning up in HA",
|
||||||
|
serial,
|
||||||
)
|
)
|
||||||
await self.cloud_client.async_get_playlists()
|
device_entry = device_registry.async_get_device(
|
||||||
self.attempt = 0
|
identifiers={(DOMAIN, serial)}
|
||||||
except Exception as ex: # pylint:disable=broad-except
|
)
|
||||||
|
if device_entry:
|
||||||
|
device_registry.async_update_device(
|
||||||
|
device_id=device_entry.id,
|
||||||
|
remove_config_entry_id=self.config_entry.entry_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ✅ Valid state: logged in but no devices on account
|
||||||
|
if not devices:
|
||||||
|
_LOGGER.debug("No Oasis devices found for account")
|
||||||
|
self.attempt = 0
|
||||||
|
if devices != self.data:
|
||||||
|
self.last_updated = dt_util.now()
|
||||||
|
return []
|
||||||
|
|
||||||
|
self.mqtt_client.register_devices(devices)
|
||||||
|
|
||||||
|
# Best-effort playlists
|
||||||
|
try:
|
||||||
|
await self.cloud_client.async_get_playlists()
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
_LOGGER.exception("Error fetching playlists from cloud")
|
||||||
|
|
||||||
|
any_success = False
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
try:
|
||||||
|
ready = await self.mqtt_client.wait_until_ready(
|
||||||
|
device, timeout=3, request_status=True
|
||||||
|
)
|
||||||
|
if not ready:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Timeout waiting for Oasis device %s to be ready",
|
||||||
|
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: # noqa: BLE001
|
||||||
|
_LOGGER.exception(
|
||||||
|
"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
|
||||||
|
except Exception as ex: # noqa: BLE001
|
||||||
if self.attempt > 2 or not (devices or self.data):
|
if self.attempt > 2 or not (devices or self.data):
|
||||||
raise UpdateFailed(
|
raise UpdateFailed(
|
||||||
f"Couldn't read from the Oasis device after {self.attempt} attempts"
|
"Unexpected error talking to Oasis devices "
|
||||||
|
f"after {self.attempt} attempts"
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
if devices != self.data:
|
if devices != self.data:
|
||||||
self.last_updated = dt_util.now()
|
self.last_updated = dt_util.now()
|
||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class OasisDeviceEntity(CoordinatorEntity[OasisDeviceCoordinator]):
|
|||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
connections=connections,
|
connections=connections,
|
||||||
identifiers={(DOMAIN, serial_number)},
|
identifiers={(DOMAIN, serial_number)},
|
||||||
name=f"{device.model} {serial_number}",
|
name=device.name,
|
||||||
manufacturer=device.manufacturer,
|
manufacturer=device.manufacturer,
|
||||||
model=device.model,
|
model=device.model,
|
||||||
serial_number=serial_number,
|
serial_number=serial_number,
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ from homeassistant.components.image import Image, ImageEntity, ImageEntityDescri
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import UNDEFINED
|
from homeassistant.helpers.typing import UNDEFINED
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
from .coordinator import OasisDeviceCoordinator
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
from .pyoasiscontrol import OasisDevice
|
from .pyoasiscontrol import OasisDevice
|
||||||
@@ -19,11 +20,14 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device image using config entry."""
|
"""Set up Oasis device image using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceImageEntity(coordinator, device, IMAGE)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceImageEntity(entry.runtime_data, device, IMAGE)
|
||||||
)
|
for device in new_devices
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
IMAGE = ImageEntityDescription(key="image", name=None)
|
IMAGE = ImageEntityDescription(key="image", name=None)
|
||||||
@@ -32,7 +36,6 @@ IMAGE = ImageEntityDescription(key="image", name=None)
|
|||||||
class OasisDeviceImageEntity(OasisDeviceEntity, ImageEntity):
|
class OasisDeviceImageEntity(OasisDeviceEntity, ImageEntity):
|
||||||
"""Oasis device image entity."""
|
"""Oasis device image entity."""
|
||||||
|
|
||||||
_attr_content_type = "image/svg+xml"
|
|
||||||
_track_id: int | None = None
|
_track_id: int | None = None
|
||||||
_progress: int = 0
|
_progress: int = 0
|
||||||
|
|
||||||
@@ -50,20 +53,29 @@ class OasisDeviceImageEntity(OasisDeviceEntity, ImageEntity):
|
|||||||
def image(self) -> bytes | None:
|
def image(self) -> bytes | None:
|
||||||
"""Return bytes of image."""
|
"""Return bytes of image."""
|
||||||
if not self._cached_image:
|
if not self._cached_image:
|
||||||
self._cached_image = Image(self.content_type, self.device.create_svg())
|
if (svg := self.device.create_svg()) is None:
|
||||||
|
self._attr_image_url = self.device.track_image_url
|
||||||
|
self._attr_image_last_updated = dt_util.now()
|
||||||
|
return None
|
||||||
|
self._attr_content_type = "image/svg+xml"
|
||||||
|
self._cached_image = Image(self.content_type, svg)
|
||||||
return self._cached_image.content
|
return self._cached_image.content
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
device = self.device
|
device = self.device
|
||||||
if (
|
|
||||||
self._track_id != device.track_id or self._progress != device.progress
|
track_changed = self._track_id != device.track_id
|
||||||
) and (device.status == "playing" or self._cached_image is None):
|
progress_changed = self._progress != device.progress
|
||||||
|
allow_update = device.status == "playing" or self._cached_image is None
|
||||||
|
|
||||||
|
if (track_changed or progress_changed) and allow_update:
|
||||||
self._attr_image_last_updated = self.coordinator.last_updated
|
self._attr_image_last_updated = self.coordinator.last_updated
|
||||||
self._track_id = device.track_id
|
self._track_id = device.track_id
|
||||||
self._progress = device.progress
|
self._progress = device.progress
|
||||||
self._cached_image = None
|
self._cached_image = None
|
||||||
|
|
||||||
if device.track and device.track.get("svg_content"):
|
if device.track and device.track.get("svg_content"):
|
||||||
self._attr_image_url = UNDEFINED
|
self._attr_image_url = UNDEFINED
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ from homeassistant.util.color import (
|
|||||||
value_to_brightness,
|
value_to_brightness,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
from .pyoasiscontrol.const import LED_EFFECTS
|
from .pyoasiscontrol.const import LED_EFFECTS
|
||||||
|
|
||||||
|
|
||||||
@@ -35,11 +35,14 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device lights using config entry."""
|
"""Set up Oasis device lights using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceLightEntity(coordinator, device, DESCRIPTOR)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceLightEntity(entry.runtime_data, device, DESCRIPTOR)
|
||||||
)
|
for device in new_devices
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = LightEntityDescription(key="led", translation_key="led")
|
DESCRIPTOR = LightEntityDescription(key="led", translation_key="led")
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
from .helpers import get_track_id
|
from .helpers import get_track_id
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@@ -31,11 +31,14 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device media_players using config entry."""
|
"""Set up Oasis device media_players using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceMediaPlayerEntity(coordinator, device, DESCRIPTOR)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceMediaPlayerEntity(entry.runtime_data, device, DESCRIPTOR)
|
||||||
)
|
for device in new_devices
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = MediaPlayerEntityDescription(key="oasis_mini", name=None)
|
DESCRIPTOR = MediaPlayerEntityDescription(key="oasis_mini", name=None)
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ from homeassistant.components.number import (
|
|||||||
NumberEntityDescription,
|
NumberEntityDescription,
|
||||||
NumberMode,
|
NumberMode,
|
||||||
)
|
)
|
||||||
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
from .pyoasiscontrol.device import (
|
from .pyoasiscontrol.device import (
|
||||||
BALL_SPEED_MAX,
|
BALL_SPEED_MAX,
|
||||||
BALL_SPEED_MIN,
|
BALL_SPEED_MIN,
|
||||||
@@ -27,18 +28,22 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device numbers using config entry."""
|
"""Set up Oasis device numbers using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceNumberEntity(coordinator, device, descriptor)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceNumberEntity(entry.runtime_data, device, descriptor)
|
||||||
for descriptor in DESCRIPTORS
|
for device in new_devices
|
||||||
)
|
for descriptor in DESCRIPTORS
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTORS = {
|
DESCRIPTORS = {
|
||||||
NumberEntityDescription(
|
NumberEntityDescription(
|
||||||
key="ball_speed",
|
key="ball_speed",
|
||||||
translation_key="ball_speed",
|
translation_key="ball_speed",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
mode=NumberMode.SLIDER,
|
mode=NumberMode.SLIDER,
|
||||||
native_max_value=BALL_SPEED_MAX,
|
native_max_value=BALL_SPEED_MAX,
|
||||||
native_min_value=BALL_SPEED_MIN,
|
native_min_value=BALL_SPEED_MIN,
|
||||||
@@ -46,6 +51,7 @@ DESCRIPTORS = {
|
|||||||
NumberEntityDescription(
|
NumberEntityDescription(
|
||||||
key="led_speed",
|
key="led_speed",
|
||||||
translation_key="led_speed",
|
translation_key="led_speed",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
mode=NumberMode.SLIDER,
|
mode=NumberMode.SLIDER,
|
||||||
native_max_value=LED_SPEED_MAX,
|
native_max_value=LED_SPEED_MAX,
|
||||||
native_min_value=LED_SPEED_MIN,
|
native_min_value=LED_SPEED_MIN,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
@@ -16,6 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
BASE_URL = "https://app.grounded.so"
|
BASE_URL = "https://app.grounded.so"
|
||||||
PLAYLISTS_REFRESH_LIMITER = timedelta(minutes=5)
|
PLAYLISTS_REFRESH_LIMITER = timedelta(minutes=5)
|
||||||
|
SOFTWARE_REFRESH_LIMITER = timedelta(hours=1)
|
||||||
|
|
||||||
|
|
||||||
class OasisCloudClient:
|
class OasisCloudClient:
|
||||||
@@ -32,15 +34,6 @@ class OasisCloudClient:
|
|||||||
* latest software metadata
|
* latest software metadata
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_session: ClientSession | None
|
|
||||||
_owns_session: bool
|
|
||||||
_access_token: str | None
|
|
||||||
|
|
||||||
# these are "cache" fields for tracks/playlists
|
|
||||||
_playlists_next_refresh: datetime
|
|
||||||
playlists: list[dict[str, Any]]
|
|
||||||
_playlist_details: dict[int, dict[str, str]]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -51,10 +44,17 @@ class OasisCloudClient:
|
|||||||
self._owns_session = session is None
|
self._owns_session = session is None
|
||||||
self._access_token = access_token
|
self._access_token = access_token
|
||||||
|
|
||||||
# simple in-memory caches
|
# playlists cache
|
||||||
|
self.playlists: list[dict[str, Any]] = []
|
||||||
self._playlists_next_refresh = now()
|
self._playlists_next_refresh = now()
|
||||||
self.playlists = []
|
self._playlists_lock = asyncio.Lock()
|
||||||
self._playlist_details = {}
|
|
||||||
|
self._playlist_details: dict[int, dict[str, str]] = {}
|
||||||
|
|
||||||
|
# software metadata cache
|
||||||
|
self._software_details: dict[str, int | str] | None = None
|
||||||
|
self._software_next_refresh = now()
|
||||||
|
self._software_lock = asyncio.Lock()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self) -> ClientSession:
|
def session(self) -> ClientSession:
|
||||||
@@ -105,15 +105,32 @@ class OasisCloudClient:
|
|||||||
self, personal_only: bool = False
|
self, personal_only: bool = False
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""Get playlists from the cloud (cached by PLAYLISTS_REFRESH_LIMITER)."""
|
"""Get playlists from the cloud (cached by PLAYLISTS_REFRESH_LIMITER)."""
|
||||||
if self._playlists_next_refresh <= now():
|
now_dt = now()
|
||||||
|
|
||||||
|
def _is_cache_valid() -> bool:
|
||||||
|
return self._playlists_next_refresh > now_dt and bool(self.playlists)
|
||||||
|
|
||||||
|
if _is_cache_valid():
|
||||||
|
return self.playlists
|
||||||
|
|
||||||
|
async with self._playlists_lock:
|
||||||
|
# Double-check in case another task just refreshed it
|
||||||
|
now_dt = now()
|
||||||
|
if _is_cache_valid():
|
||||||
|
return self.playlists
|
||||||
|
|
||||||
params = {"my_playlists": str(personal_only).lower()}
|
params = {"my_playlists": str(personal_only).lower()}
|
||||||
playlists = await self._async_auth_request(
|
playlists = await self._async_auth_request(
|
||||||
"GET", "api/playlist", params=params
|
"GET", "api/playlist", params=params
|
||||||
)
|
)
|
||||||
if playlists:
|
|
||||||
self.playlists = playlists
|
if not isinstance(playlists, list):
|
||||||
self._playlists_next_refresh = now() + PLAYLISTS_REFRESH_LIMITER
|
playlists = []
|
||||||
return self.playlists
|
|
||||||
|
self.playlists = playlists
|
||||||
|
self._playlists_next_refresh = now_dt + PLAYLISTS_REFRESH_LIMITER
|
||||||
|
|
||||||
|
return self.playlists
|
||||||
|
|
||||||
async def async_get_track_info(self, track_id: int) -> dict[str, Any] | None:
|
async def async_get_track_info(self, track_id: int) -> dict[str, Any] | None:
|
||||||
"""Get single track info from the cloud."""
|
"""Get single track info from the cloud."""
|
||||||
@@ -143,9 +160,37 @@ class OasisCloudClient:
|
|||||||
track_details += response.get("data", [])
|
track_details += response.get("data", [])
|
||||||
return track_details
|
return track_details
|
||||||
|
|
||||||
async def async_get_latest_software_details(self) -> dict[str, int | str]:
|
async def async_get_latest_software_details(
|
||||||
"""Get latest software metadata from cloud."""
|
self, *, force_refresh: bool = False
|
||||||
return await self._async_auth_request("GET", "api/software/last-version")
|
) -> dict[str, int | str] | None:
|
||||||
|
"""Get latest software metadata from cloud (cached)."""
|
||||||
|
now_dt = now()
|
||||||
|
|
||||||
|
def _is_cache_valid() -> bool:
|
||||||
|
return (
|
||||||
|
not force_refresh
|
||||||
|
and self._software_details is not None
|
||||||
|
and self._software_next_refresh > now_dt
|
||||||
|
)
|
||||||
|
|
||||||
|
if _is_cache_valid():
|
||||||
|
return self._software_details
|
||||||
|
|
||||||
|
async with self._software_lock:
|
||||||
|
# Double-check in case another task just refreshed it
|
||||||
|
now_dt = now()
|
||||||
|
if _is_cache_valid():
|
||||||
|
return self._software_details
|
||||||
|
|
||||||
|
details = await self._async_auth_request("GET", "api/software/last-version")
|
||||||
|
|
||||||
|
if not isinstance(details, dict):
|
||||||
|
details = {}
|
||||||
|
|
||||||
|
self._software_details = details
|
||||||
|
self._software_next_refresh = now_dt + SOFTWARE_REFRESH_LIMITER
|
||||||
|
|
||||||
|
return self._software_details
|
||||||
|
|
||||||
async def _async_auth_request(self, method: str, url: str, **kwargs: Any) -> Any:
|
async def _async_auth_request(self, method: str, url: str, **kwargs: Any) -> Any:
|
||||||
"""Perform an authenticated cloud request."""
|
"""Perform an authenticated cloud request."""
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import base64
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
import logging
|
import logging
|
||||||
import ssl
|
import ssl
|
||||||
from typing import Any, Final
|
from typing import Any, Final, Iterable
|
||||||
|
|
||||||
import aiomqtt
|
import aiomqtt
|
||||||
|
|
||||||
@@ -92,6 +92,11 @@ class OasisMqttClient(OasisClientProtocol):
|
|||||||
"Could not schedule subscription for %s (no running loop)", serial
|
"Could not schedule subscription for %s (no running loop)", serial
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def register_devices(self, devices: Iterable[OasisDevice]) -> None:
|
||||||
|
"""Convenience method to register multiple devices."""
|
||||||
|
for device in devices:
|
||||||
|
self.register_device(device)
|
||||||
|
|
||||||
def unregister_device(self, device: OasisDevice) -> None:
|
def unregister_device(self, device: OasisDevice) -> None:
|
||||||
serial = device.serial_number
|
serial = device.serial_number
|
||||||
if not serial:
|
if not serial:
|
||||||
@@ -260,6 +265,13 @@ class OasisMqttClient(OasisClientProtocol):
|
|||||||
|
|
||||||
return device.mac_address
|
return device.mac_address
|
||||||
|
|
||||||
|
async def async_send_auto_clean_command(
|
||||||
|
self, device: OasisDevice, auto_clean: bool
|
||||||
|
) -> None:
|
||||||
|
"""Send auto clean command."""
|
||||||
|
payload = f"WRIAUTOCLEAN={1 if auto_clean else 0}"
|
||||||
|
await self._publish_command(device, payload)
|
||||||
|
|
||||||
async def async_send_ball_speed_command(
|
async def async_send_ball_speed_command(
|
||||||
self,
|
self,
|
||||||
device: OasisDevice,
|
device: OasisDevice,
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ class OasisClientProtocol(Protocol):
|
|||||||
|
|
||||||
async def async_get_mac_address(self, device: OasisDevice) -> str | None: ...
|
async def async_get_mac_address(self, device: OasisDevice) -> str | None: ...
|
||||||
|
|
||||||
|
async def async_send_auto_clean_command(
|
||||||
|
self, device: OasisDevice, auto_clean: bool
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
async def async_send_ball_speed_command(
|
async def async_send_ball_speed_command(
|
||||||
self,
|
self,
|
||||||
device: OasisDevice,
|
device: OasisDevice,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
BALL_SPEED_MAX: Final = 400
|
BALL_SPEED_MAX: Final = 400
|
||||||
BALL_SPEED_MIN: Final = 100
|
BALL_SPEED_MIN: Final = 100
|
||||||
|
BRIGHTNESS_DEFAULT: Final = 100
|
||||||
LED_SPEED_MAX: Final = 90
|
LED_SPEED_MAX: Final = 90
|
||||||
LED_SPEED_MIN: Final = -90
|
LED_SPEED_MIN: Final = -90
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ class OasisDevice:
|
|||||||
*,
|
*,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
serial_number: str | None = None,
|
serial_number: str | None = None,
|
||||||
|
name: str | None = None,
|
||||||
ssid: str | None = None,
|
ssid: str | None = None,
|
||||||
ip_address: str | None = None,
|
ip_address: str | None = None,
|
||||||
cloud: OasisCloudClient | None = None,
|
cloud: OasisCloudClient | None = None,
|
||||||
@@ -73,10 +75,11 @@ class OasisDevice:
|
|||||||
self._listeners: list[Callable[[], None]] = []
|
self._listeners: list[Callable[[], None]] = []
|
||||||
|
|
||||||
# Details
|
# Details
|
||||||
self.model: str | None = model
|
self.model = model
|
||||||
self.serial_number: str | None = serial_number
|
self.serial_number = serial_number
|
||||||
self.ssid: str | None = ssid
|
self.name = name if name else f"{model} {serial_number}"
|
||||||
self.ip_address: str | None = ip_address
|
self.ssid = ssid
|
||||||
|
self.ip_address = ip_address
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
self.auto_clean: bool = False
|
self.auto_clean: bool = False
|
||||||
@@ -84,7 +87,7 @@ class OasisDevice:
|
|||||||
self.ball_speed: int = BALL_SPEED_MIN
|
self.ball_speed: int = BALL_SPEED_MIN
|
||||||
self._brightness: int = 0
|
self._brightness: int = 0
|
||||||
self.brightness_max: int = 200
|
self.brightness_max: int = 200
|
||||||
self.brightness_on: int = 0
|
self.brightness_on: int = BRIGHTNESS_DEFAULT
|
||||||
self.busy: bool = False
|
self.busy: bool = False
|
||||||
self.color: str | None = None
|
self.color: str | None = None
|
||||||
self.download_progress: int = 0
|
self.download_progress: int = 0
|
||||||
@@ -150,7 +153,8 @@ class OasisDevice:
|
|||||||
old = getattr(self, name, None)
|
old = getattr(self, name, None)
|
||||||
if old != value:
|
if old != value:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s changed: '%s' -> '%s'",
|
"%s %s changed: '%s' -> '%s'",
|
||||||
|
self.serial_number,
|
||||||
name.replace("_", " ").capitalize(),
|
name.replace("_", " ").capitalize(),
|
||||||
old,
|
old,
|
||||||
value,
|
value,
|
||||||
@@ -174,7 +178,7 @@ class OasisDevice:
|
|||||||
_LOGGER.warning("Unknown field: %s=%s", key, value)
|
_LOGGER.warning("Unknown field: %s=%s", key, value)
|
||||||
|
|
||||||
if playlist_or_index_changed:
|
if playlist_or_index_changed:
|
||||||
self._schedule_track_refresh()
|
self.schedule_track_refresh()
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
self._notify_listeners()
|
self._notify_listeners()
|
||||||
@@ -343,6 +347,10 @@ class OasisDevice:
|
|||||||
self._update_field("mac_address", mac)
|
self._update_field("mac_address", mac)
|
||||||
return mac
|
return mac
|
||||||
|
|
||||||
|
async def async_set_auto_clean(self, auto_clean: bool) -> None:
|
||||||
|
client = self._require_client()
|
||||||
|
await client.async_send_auto_clean_command(self, auto_clean)
|
||||||
|
|
||||||
async def async_set_ball_speed(self, speed: int) -> None:
|
async def async_set_ball_speed(self, speed: int) -> None:
|
||||||
if not BALL_SPEED_MIN <= speed <= BALL_SPEED_MAX:
|
if not BALL_SPEED_MIN <= speed <= BALL_SPEED_MAX:
|
||||||
raise ValueError("Invalid speed specified")
|
raise ValueError("Invalid speed specified")
|
||||||
@@ -438,7 +446,7 @@ class OasisDevice:
|
|||||||
client = self._require_client()
|
client = self._require_client()
|
||||||
await client.async_send_reboot_command(self)
|
await client.async_send_reboot_command(self)
|
||||||
|
|
||||||
def _schedule_track_refresh(self) -> None:
|
def schedule_track_refresh(self) -> None:
|
||||||
"""Schedule an async refresh of current track info if track_id changed."""
|
"""Schedule an async refresh of current track info if track_id changed."""
|
||||||
if not self._cloud:
|
if not self._cloud:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ from dataclasses import dataclass
|
|||||||
from typing import Any, Awaitable, Callable
|
from typing import Any, Awaitable, Callable
|
||||||
|
|
||||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
from .coordinator import OasisDeviceCoordinator
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
from .pyoasiscontrol import OasisDevice
|
from .pyoasiscontrol import OasisDevice
|
||||||
@@ -70,12 +71,15 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device select using config entry."""
|
"""Set up Oasis device select using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceSelectEntity(coordinator, device, descriptor)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceSelectEntity(entry.runtime_data, device, descriptor)
|
||||||
for descriptor in DESCRIPTORS
|
for device in new_devices
|
||||||
)
|
for descriptor in DESCRIPTORS
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@@ -91,6 +95,7 @@ DESCRIPTORS = (
|
|||||||
OasisDeviceSelectEntityDescription(
|
OasisDeviceSelectEntityDescription(
|
||||||
key="autoplay",
|
key="autoplay",
|
||||||
translation_key="autoplay",
|
translation_key="autoplay",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
options=AUTOPLAY_MAP_LIST,
|
options=AUTOPLAY_MAP_LIST,
|
||||||
current_value=lambda device: str(device.autoplay),
|
current_value=lambda device: str(device.autoplay),
|
||||||
select_fn=lambda device, index: (
|
select_fn=lambda device, index: (
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ from homeassistant.const import PERCENTAGE, EntityCategory
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@@ -22,12 +22,15 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device sensors using config entry."""
|
"""Set up Oasis device sensors using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
async_add_entities(
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceSensorEntity(coordinator, device, descriptor)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceSensorEntity(entry.runtime_data, device, descriptor)
|
||||||
for descriptor in DESCRIPTORS
|
for device in new_devices
|
||||||
)
|
for descriptor in DESCRIPTORS
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTORS = {
|
DESCRIPTORS = {
|
||||||
|
|||||||
@@ -144,6 +144,11 @@
|
|||||||
"live": "Live drawing"
|
"live": "Live drawing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"auto_clean": {
|
||||||
|
"name": "Auto-clean"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
|
|||||||
@@ -1,53 +1,57 @@
|
|||||||
# """Oasis Mini switch entity."""
|
"""Oasis device switch entity."""
|
||||||
|
|
||||||
# from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
# from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||||
# from homeassistant.core import HomeAssistant
|
from homeassistant.const import EntityCategory
|
||||||
# from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
# from . import OasisMiniConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
# from .entity import OasisMiniEntity
|
from .entity import OasisDeviceEntity
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
|
|
||||||
|
|
||||||
# async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
# hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
# entry: OasisMiniConfigEntry,
|
entry: OasisDeviceConfigEntry,
|
||||||
# async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
# ) -> None:
|
) -> None:
|
||||||
# """Set up Oasis Mini switchs using config entry."""
|
"""Set up Oasis device switchs using config entry."""
|
||||||
# async_add_entities(
|
|
||||||
# [
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
# OasisMiniSwitchEntity(entry.runtime_data, descriptor)
|
return [
|
||||||
# for descriptor in DESCRIPTORS
|
OasisDeviceSwitchEntity(entry.runtime_data, device, descriptor)
|
||||||
# ]
|
for device in new_devices
|
||||||
# )
|
for descriptor in DESCRIPTORS
|
||||||
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities)
|
||||||
|
|
||||||
|
|
||||||
# class OasisMiniSwitchEntity(OasisMiniEntity, SwitchEntity):
|
DESCRIPTORS = {
|
||||||
# """Oasis Mini switch entity."""
|
SwitchEntityDescription(
|
||||||
|
key="auto_clean",
|
||||||
# @property
|
translation_key="auto_clean",
|
||||||
# def is_on(self) -> bool:
|
entity_category=EntityCategory.CONFIG,
|
||||||
# """Return True if entity is on."""
|
),
|
||||||
# return int(getattr(self.device, self.entity_description.key))
|
}
|
||||||
|
|
||||||
# async def async_turn_off(self, **kwargs: Any) -> None:
|
|
||||||
# """Turn the entity off."""
|
|
||||||
# await self.device.async_set_repeat_playlist(False)
|
|
||||||
# await self.coordinator.async_request_refresh()
|
|
||||||
|
|
||||||
# async def async_turn_on(self, **kwargs: Any) -> None:
|
|
||||||
# """Turn the entity on."""
|
|
||||||
# await self.device.async_set_repeat_playlist(True)
|
|
||||||
# await self.coordinator.async_request_refresh()
|
|
||||||
|
|
||||||
|
|
||||||
# DESCRIPTORS = {
|
class OasisDeviceSwitchEntity(OasisDeviceEntity, SwitchEntity):
|
||||||
# SwitchEntityDescription(
|
"""Oasis device switch entity."""
|
||||||
# key="repeat_playlist",
|
|
||||||
# name="Repeat playlist",
|
@property
|
||||||
# ),
|
def is_on(self) -> bool:
|
||||||
# }
|
"""Return True if entity is on."""
|
||||||
|
return bool(getattr(self.device, self.entity_description.key))
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
await self.device.async_set_auto_clean(False)
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity on."""
|
||||||
|
await self.device.async_set_auto_clean(True)
|
||||||
|
|||||||
@@ -144,6 +144,11 @@
|
|||||||
"live": "Live drawing"
|
"live": "Live drawing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"auto_clean": {
|
||||||
|
"name": "Auto-clean"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ from homeassistant.components.update import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import OasisDeviceConfigEntry
|
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
|
||||||
from .coordinator import OasisDeviceCoordinator
|
|
||||||
from .entity import OasisDeviceEntity
|
from .entity import OasisDeviceEntity
|
||||||
|
from .pyoasiscontrol import OasisDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -30,12 +30,14 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Oasis device updates using config entry."""
|
"""Set up Oasis device updates using config entry."""
|
||||||
coordinator: OasisDeviceCoordinator = entry.runtime_data
|
|
||||||
entities = (
|
def make_entities(new_devices: list[OasisDevice]):
|
||||||
OasisDeviceUpdateEntity(coordinator, device, DESCRIPTOR)
|
return [
|
||||||
for device in coordinator.data
|
OasisDeviceUpdateEntity(entry.runtime_data, device, DESCRIPTOR)
|
||||||
)
|
for device in new_devices
|
||||||
async_add_entities(entities, True)
|
]
|
||||||
|
|
||||||
|
setup_platform_from_coordinator(entry, async_add_entities, make_entities, True)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = UpdateEntityDescription(
|
DESCRIPTOR = UpdateEntityDescription(
|
||||||
|
|||||||
Reference in New Issue
Block a user