1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-12-06 18:44:14 -05:00
Files
hacs-oasis_mini/custom_components/oasis_mini/light.py
Nathan Spencer 379b6f67f2 Swap out direct HTTP connection with server MQTT connection to handle firmware 2.60+ (#98)
* Switch to using mqtt

* Better mqtt handling when connection is interrupted

* Get track info from the cloud when playlist or index changes

* Add additional helpers

* Dynamically handle devices and other enhancements

* 📝 Add docstrings to `mqtt`

Docstrings generation was requested by @natekspencer.

* https://github.com/natekspencer/hacs-oasis_mini/pull/98#issuecomment-3568450288

The following files were modified:

* `custom_components/oasis_mini/__init__.py`
* `custom_components/oasis_mini/binary_sensor.py`
* `custom_components/oasis_mini/button.py`
* `custom_components/oasis_mini/config_flow.py`
* `custom_components/oasis_mini/coordinator.py`
* `custom_components/oasis_mini/entity.py`
* `custom_components/oasis_mini/helpers.py`
* `custom_components/oasis_mini/image.py`
* `custom_components/oasis_mini/light.py`
* `custom_components/oasis_mini/media_player.py`
* `custom_components/oasis_mini/number.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/cloud_client.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/http_client.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/mqtt_client.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/transport.py`
* `custom_components/oasis_mini/pyoasiscontrol/device.py`
* `custom_components/oasis_mini/pyoasiscontrol/utils.py`
* `custom_components/oasis_mini/select.py`
* `custom_components/oasis_mini/sensor.py`
* `custom_components/oasis_mini/switch.py`
* `custom_components/oasis_mini/update.py`
* `update_tracks.py`

* Fix formatting in transport.py

* Replace tabs with spaces

* Use tuples instead of sets for descriptors

* Encode svg in image entity

* Fix iot_class

* Fix tracks list url

* Ensure update_tracks closes the connection

* Fix number typing and docstring

* Fix docstring in update_tracks

* Cache playlist based on type

* Fix formatting in device.py

* Add missing async_send_auto_clean_command to http client

* Propagate UnauthenticatedError from async_get_track_info

* Adjust exceptions

* Move create_client outside of try block in config_flow

* Formatting

* Address PR comments

* Formatting

* Add noqa: ARG001 on unused hass

* Close cloud/MQTT clients if initial coordinator refresh fails.

* Address PR again

* PR fixes

* Pass config entry to coordinator

* Remove async_timeout (thanks ChatGPT... not)

* Address PR

* Replace magic numbers for status code

* Update autoplay wording/ordering

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-11-24 01:09:23 -07:00

156 lines
5.1 KiB
Python

"""Oasis device light entity."""
from __future__ import annotations
import math
from typing import Any
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_EFFECT,
ATTR_RGB_COLOR,
ColorMode,
LightEntity,
LightEntityDescription,
LightEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.color import (
brightness_to_value,
color_rgb_to_hex,
rgb_hex_to_rgb_list,
value_to_brightness,
)
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
from .entity import OasisDeviceEntity
from .pyoasiscontrol import OasisDevice
from .pyoasiscontrol.const import LED_EFFECTS
async def async_setup_entry(
hass: HomeAssistant, # noqa: ARG001
entry: OasisDeviceConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Oasis device lights using config entry."""
def make_entities(new_devices: list[OasisDevice]):
"""
Create OasisDeviceLightEntity instances for each provided Oasis device.
Parameters:
new_devices (list[OasisDevice]): Devices to wrap as light entities.
Returns:
list[OasisDeviceLightEntity]: A list of light entity instances corresponding to the input devices.
"""
return [
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")
class OasisDeviceLightEntity(OasisDeviceEntity, LightEntity):
"""Oasis device light entity."""
_attr_supported_features = LightEntityFeature.EFFECT
@property
def brightness(self) -> int:
"""
Get the light's brightness on a 0-255 scale.
Returns:
int: Brightness value between 0 and 255.
"""
scale = (1, self.device.brightness_max)
return value_to_brightness(scale, self.device.brightness)
@property
def color_mode(self) -> ColorMode:
"""Return the color mode of the light."""
if self.effect in (
"Rainbow",
"Glitter",
"Confetti",
"BPM",
"Juggle",
):
return ColorMode.BRIGHTNESS
return ColorMode.RGB
@property
def effect(self) -> str:
"""Return the current effect."""
return LED_EFFECTS.get(self.device.led_effect)
@property
def effect_list(self) -> list[str]:
"""Return the list of supported effects."""
return list(LED_EFFECTS.values())
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self.device.brightness > 0
@property
def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the rgb color value [int, int, int]."""
if not self.device.color:
return None
return rgb_hex_to_rgb_list(self.device.color.replace("#", ""))
@property
def supported_color_modes(self) -> set[ColorMode]:
"""Flag supported color modes."""
return {ColorMode.RGB}
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
await self.device.async_set_led(brightness=0)
async def async_turn_on(self, **kwargs: Any) -> None:
"""
Turn the light on and set its LED state.
Processes optional keyword arguments to compute the device-specific LED parameters, then updates the device's LEDs with the resulting brightness, color, and effect.
Parameters:
kwargs: Optional control parameters recognized by the method:
ATTR_BRIGHTNESS (int): Brightness in the 0-255 Home Assistant scale. When provided,
it is converted and rounded up to the device's brightness scale (1..device.brightness_max).
When omitted, uses self.device.brightness or self.device.brightness_on.
ATTR_RGB_COLOR (tuple[int, int, int]): RGB tuple (R, G, B). When provided, it is
converted to a hex color string prefixed with '#'.
ATTR_EFFECT (str): Human-readable effect name. When provided, it is mapped to the
device's internal effect key; if no mapping exists, `None` is used.
Side effects:
Updates the underlying device LED state with the computed `brightness`, `color`, and `led_effect`.
"""
if brightness := kwargs.get(ATTR_BRIGHTNESS):
scale = (1, self.device.brightness_max)
brightness = math.ceil(brightness_to_value(scale, brightness))
else:
brightness = self.device.brightness or self.device.brightness_on
if color := kwargs.get(ATTR_RGB_COLOR):
color = f"#{color_rgb_to_hex(*color)}"
if led_effect := kwargs.get(ATTR_EFFECT):
led_effect = next(
(k for k, v in LED_EFFECTS.items() if v == led_effect), None
)
await self.device.async_set_led(
brightness=brightness, color=color, led_effect=led_effect
)