diff --git a/custom_components/flickerstrip/__init__.py b/custom_components/flickerstrip/__init__.py index 9ef9ae7..6a33060 100644 --- a/custom_components/flickerstrip/__init__.py +++ b/custom_components/flickerstrip/__init__.py @@ -1,23 +1,40 @@ -from homeassistant.config_entries import ConfigEntry +from dataclasses import dataclass +from flickerstrip_py import Flickerstrip +import logging + +from homeassistant.config_entries import ConfigEntry, ConfigEntryError from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from .common import Flickerstrip from .const import ( DOMAIN, + PLATFORMS, ) +_LOGGER = logging.getLogger(__name__) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Setup flickerstrip from config entry.""" - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = Flickerstrip( - hass=hass, + strip = Flickerstrip( host=entry.data[CONF_HOST], port=entry.data[CONF_PORT], ) + if not await strip.force_update(): + _LOGGER.error("Unable to connect to flickerstrip") + raise ConfigEntryError("Unable to connect to flickerstrip") + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = FlickerstripData(strip=strip) + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) + entry.async_on_unload(entry.add_update_listener(update_listener)) return True @@ -25,5 +42,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: """Update when config_entry options update.""" - if entry.options: - await hass.config_entries.async_reload(entry.entry_id) + await hass.config_entries.async_reload(entry.entry_id) + + +@dataclass +class FlickerstripData: + """Data for the Flickerstrip integration.""" + + strip: Flickerstrip diff --git a/custom_components/flickerstrip/common.py b/custom_components/flickerstrip/common.py index 5fedf91..abe4709 100644 --- a/custom_components/flickerstrip/common.py +++ b/custom_components/flickerstrip/common.py @@ -6,7 +6,5 @@ from homeassistant.helpers.entity import Entity class FlickerstripEntity(Entity): """Base flickerstrip entity.""" - def __init__(self, host: str, port: int = 80): - self.host: str = host - self.port: int = port - self.strip: Flickerstrip = Flickerstrip(host, port) + def __init__(self, strip: Flickerstrip): + self.strip: Flickerstrip = strip diff --git a/custom_components/flickerstrip/config_flow.py b/custom_components/flickerstrip/config_flow.py index 4ae720c..3b8e953 100644 --- a/custom_components/flickerstrip/config_flow.py +++ b/custom_components/flickerstrip/config_flow.py @@ -50,7 +50,7 @@ class FlickerstripConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def _async_create_entry(self) -> FlowResult: """Async create flow handler entry.""" return self.async_create_entry( - title=self._name, + title="Flickerstrip", data={ CONF_HOST: self._host, CONF_PORT: self._port, diff --git a/custom_components/flickerstrip/const.py b/custom_components/flickerstrip/const.py index ce10b73..ede5590 100644 --- a/custom_components/flickerstrip/const.py +++ b/custom_components/flickerstrip/const.py @@ -1,3 +1,14 @@ +from homeassistant.const import ( + Platform +) + DOMAIN = "flickerstrip" +DATA_UPDATED = "flickerstrip_data_updated" + +DEFAULT_NAME = "Flickerstrip" DEFAULT_PORT = 80 + +PLATFORMS: list[Platform] = [ + Platform.LIGHT, +] diff --git a/custom_components/flickerstrip/light.py b/custom_components/flickerstrip/light.py index 963ee6e..1273093 100644 --- a/custom_components/flickerstrip/light.py +++ b/custom_components/flickerstrip/light.py @@ -1,62 +1,167 @@ +from flickerstrip_py import Flickerstrip from typing import Any -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.components.light import ( LightEntity, ATTR_BRIGHTNESS, + ATTR_RGB_COLOR, + ATTR_EFFECT, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_EFFECT, ColorMode ) +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .common import FlickerstripEntity from .const import ( - DEFAULT_PORT, + DOMAIN, + DATA_UPDATED, + DEFAULT_NAME, ) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_devices: AddEntitiesCallback, +): + """Initialize flickerstrip light config entry.""" + + strip_data = hass.data[DOMAIN][config_entry.entry_id] + + async_add_devices([ + FlickerstripLight(hass, strip_data.strip) + ]) + + class FlickerstripLight(FlickerstripEntity, LightEntity): """Flickerstrip class.""" def __init__( self, hass: HomeAssistant, - host: str, - port: int = DEFAULT_PORT, + strip: Flickerstrip, ): - super().__init__(host, port) + super().__init__(strip) self.hass = hass @property - def color_mode(): + def device_info(self) -> DeviceInfo: + sw_version = None + if self.strip.status is not None: + sw_version = self.strip.status.firmware + + return DeviceInfo( + identifiers={ + (DOMAIN, self.strip.host), + }, + name=self.name, + default_name=DEFAULT_NAME, + manufacturer="HOhmBody", + model="Flickerstrip LED Strip", + sw_version=sw_version, + ) + + @property + def unique_id(self) -> str | None: + """Return the unique id of this light.""" + if self.strip.status is None: + return None + + return self.strip.status.mac + + @property + def color_mode(self) -> ColorMode | str | None: + """Return the color mode of this light.""" return ColorMode.RGB @property - def supported_color_mode(): + def supported_color_modes(self) -> set[ColorMode] | set[str] | None: + """Return the supported color modes of this light.""" return [ColorMode.RGB] @property - def name(self) -> str: - """Return the display name of this light.""" - return self.strip.status.name if len(self.strip.status.name) > 0 else "Flickerstrip" + def supported_features(self) -> int | None: + """Return the supported features of this light.""" + return SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT @property - def brightness(self): + def effect(self) -> str | None: + """Return the current effect of this light.""" + if self.strip.status is None: + return None + + return self.strip.status.get_current_pattern().name + + @property + def effect_list(self) -> list[str] | None: + """Return the effect list of this light.""" + if self.strip.status is None: + return [] + + return [p.name for p in self.strip.status.patterns] + + @property + def name(self) -> str | None: + """Return the display name of this light.""" + if self.strip.status is None: + return DEFAULT_NAME + + return self.strip.status.name if len(self.strip.status.name) > 0 else DEFAULT_NAME + + @property + def brightness(self) -> int | None: """Return the brightness of the light.""" + if self.strip.status is None: + return 0 + return self.strip.status.brightness @property def is_on(self) -> bool | None: """Return true if light is on.""" + if self.strip.status is None: + return False + return self.strip.status.power async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the light to turn on.""" - brightness = kwargs.get(ATTR_BRIGHTNESS, 100) - await self.strip.set_brightness(brightness) + if ATTR_EFFECT in kwargs: + for i, pattern in enumerate(self.strip.status.patterns): + if pattern.name == kwargs[ATTR_EFFECT]: + await self.strip.select_pattern(i) + break + elif ATTR_RGB_COLOR in kwargs: + [r, g, b] = kwargs[ATTR_RGB_COLOR] + await self.strip.set_color(r, g, b) + + if ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + await self.strip.set_brightness(brightness) + await self.strip.power_on() + await self.strip.force_update() async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the light to turn off.""" await self.strip.power_off() + await self.strip.force_update() + + async def async_added_to_hass(self) -> None: + """Update initial state.""" + async_dispatcher_connect( + self.hass, DATA_UPDATED, self._schedule_immediate_update + ) + + @callback + def _schedule_immediate_update(self): + self.async_schedule_update_ha_state(True) async def async_update(self) -> None: """Fetch new state data for this light. diff --git a/custom_components/flickerstrip/manifest.json b/custom_components/flickerstrip/manifest.json index 79af2cd..d1fb993 100644 --- a/custom_components/flickerstrip/manifest.json +++ b/custom_components/flickerstrip/manifest.json @@ -14,7 +14,7 @@ } ], "requirements": [ - "flickerstrip-py @ git+https://git.fifitido.net/lib/flickerstrip-py" + "flickerstrip-py @ git+https://git.fifitido.net/lib/flickerstrip-py@0.2.2" ], "version": "1.0.0" }