parent
15b7564171
commit
9b88b77b66
|
@ -1,5 +1,9 @@
|
|||
"""The WiLight integration."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pywilight.wilight_device import Device as PyWiLightDevice
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
@ -51,61 +55,43 @@ class WiLightDevice(Entity):
|
|||
Contains the common logic for WiLight entities.
|
||||
"""
|
||||
|
||||
def __init__(self, api_device, index, item_name):
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, api_device: PyWiLightDevice, index: str, item_name: str) -> None:
|
||||
"""Initialize the device."""
|
||||
# WiLight specific attributes for every component type
|
||||
self._device_id = api_device.device_id
|
||||
self._sw_version = api_device.swversion
|
||||
self._client = api_device.client
|
||||
self._model = api_device.model
|
||||
self._name = item_name
|
||||
self._index = index
|
||||
self._unique_id = f"{self._device_id}_{self._index}"
|
||||
self._status = {}
|
||||
self._status: dict[str, Any] = {}
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return a name for this WiLight item."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID for this WiLight item."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return DeviceInfo(
|
||||
name=self._name,
|
||||
identifiers={(DOMAIN, self._unique_id)},
|
||||
model=self._model,
|
||||
self._attr_name = item_name
|
||||
self._attr_unique_id = f"{self._device_id}_{index}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
name=item_name,
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
model=api_device.model,
|
||||
manufacturer="WiLight",
|
||||
sw_version=self._sw_version,
|
||||
sw_version=api_device.swversion,
|
||||
via_device=(DOMAIN, self._device_id),
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return bool(self._client.is_connected)
|
||||
|
||||
@callback
|
||||
def handle_event_callback(self, states):
|
||||
def handle_event_callback(self, states: dict[str, Any]) -> None:
|
||||
"""Propagate changes through ha."""
|
||||
self._status = states
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
async def async_update(self) -> None:
|
||||
"""Synchronize state with api_device."""
|
||||
await self._client.status(self._index)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register update callback."""
|
||||
self._client.register_status_callback(self.handle_event_callback, self._index)
|
||||
await self._client.status(self._index)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for WiLight Cover."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pywilight.const import (
|
||||
|
@ -18,16 +20,18 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import DOMAIN, WiLightDevice
|
||||
from .parent_device import WiLightParent
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up WiLight covers from a config entry."""
|
||||
parent = hass.data[DOMAIN][entry.entry_id]
|
||||
parent: WiLightParent = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
# Handle a discovered WiLight device.
|
||||
entities = []
|
||||
assert parent.api
|
||||
for item in parent.api.items:
|
||||
if item["type"] != ITEM_COVER:
|
||||
continue
|
||||
|
@ -35,18 +39,17 @@ async def async_setup_entry(
|
|||
item_name = item["name"]
|
||||
if item["sub_type"] != COVER_V1:
|
||||
continue
|
||||
entity = WiLightCover(parent.api, index, item_name)
|
||||
entities.append(entity)
|
||||
entities.append(WiLightCover(parent.api, index, item_name))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
def wilight_to_hass_position(value):
|
||||
def wilight_to_hass_position(value: int) -> int:
|
||||
"""Convert wilight position 1..255 to hass format 0..100."""
|
||||
return min(100, round((value * 100) / 255))
|
||||
|
||||
|
||||
def hass_to_wilight_position(value):
|
||||
def hass_to_wilight_position(value: int) -> int:
|
||||
"""Convert hass position 0..100 to wilight 1..255 scale."""
|
||||
return min(255, round((value * 255) / 100))
|
||||
|
||||
|
@ -55,7 +58,7 @@ class WiLightCover(WiLightDevice, CoverEntity):
|
|||
"""Representation of a WiLights cover."""
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
def current_cover_position(self) -> int | None:
|
||||
"""Return current position of cover.
|
||||
|
||||
None is unknown, 0 is closed, 100 is fully open.
|
||||
|
@ -65,21 +68,21 @@ class WiLightCover(WiLightDevice, CoverEntity):
|
|||
return None
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
def is_opening(self) -> bool | None:
|
||||
"""Return if the cover is opening or not."""
|
||||
if "motor_state" not in self._status:
|
||||
return None
|
||||
return self._status["motor_state"] == WL_OPENING
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
def is_closing(self) -> bool | None:
|
||||
"""Return if the cover is closing or not."""
|
||||
if "motor_state" not in self._status:
|
||||
return None
|
||||
return self._status["motor_state"] == WL_CLOSING
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed or not."""
|
||||
if "motor_state" not in self._status or "position_current" not in self._status:
|
||||
return None
|
||||
|
|
|
@ -13,6 +13,7 @@ from pywilight.const import (
|
|||
WL_SPEED_LOW,
|
||||
WL_SPEED_MEDIUM,
|
||||
)
|
||||
from pywilight.wilight_device import Device as PyWiLightDevice
|
||||
|
||||
from homeassistant.components.fan import DIRECTION_FORWARD, FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -24,6 +25,7 @@ from homeassistant.util.percentage import (
|
|||
)
|
||||
|
||||
from . import DOMAIN, WiLightDevice
|
||||
from .parent_device import WiLightParent
|
||||
|
||||
ORDERED_NAMED_FAN_SPEEDS = [WL_SPEED_LOW, WL_SPEED_MEDIUM, WL_SPEED_HIGH]
|
||||
|
||||
|
@ -32,10 +34,11 @@ async def async_setup_entry(
|
|||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up WiLight lights from a config entry."""
|
||||
parent = hass.data[DOMAIN][entry.entry_id]
|
||||
parent: WiLightParent = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
# Handle a discovered WiLight device.
|
||||
entities = []
|
||||
assert parent.api
|
||||
for item in parent.api.items:
|
||||
if item["type"] != ITEM_FAN:
|
||||
continue
|
||||
|
@ -43,8 +46,7 @@ async def async_setup_entry(
|
|||
item_name = item["name"]
|
||||
if item["sub_type"] != FAN_V1:
|
||||
continue
|
||||
entity = WiLightFan(parent.api, index, item_name)
|
||||
entities.append(entity)
|
||||
entities.append(WiLightFan(parent.api, index, item_name))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
@ -52,19 +54,16 @@ async def async_setup_entry(
|
|||
class WiLightFan(WiLightDevice, FanEntity):
|
||||
"""Representation of a WiLights fan."""
|
||||
|
||||
_attr_icon = "mdi:fan"
|
||||
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
|
||||
_attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION
|
||||
|
||||
def __init__(self, api_device, index, item_name):
|
||||
def __init__(self, api_device: PyWiLightDevice, index: str, item_name: str) -> None:
|
||||
"""Initialize the device."""
|
||||
super().__init__(api_device, index, item_name)
|
||||
# Initialize the WiLights fan.
|
||||
self._direction = WL_DIRECTION_FORWARD
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of device based on its type."""
|
||||
return "mdi:fan"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if device is on."""
|
||||
|
@ -83,11 +82,6 @@ class WiLightFan(WiLightDevice, FanEntity):
|
|||
return None
|
||||
return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, wl_speed)
|
||||
|
||||
@property
|
||||
def speed_count(self) -> int:
|
||||
"""Return the number of speeds the fan supports."""
|
||||
return len(ORDERED_NAMED_FAN_SPEEDS)
|
||||
|
||||
@property
|
||||
def current_direction(self) -> str:
|
||||
"""Return the current direction of the fan."""
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
"""Support for WiLight lights."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pywilight.const import ITEM_LIGHT, LIGHT_COLOR, LIGHT_DIMMER, LIGHT_ON_OFF
|
||||
from pywilight.wilight_device import Device as PyWiLightDevice
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
|
@ -12,25 +17,23 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import DOMAIN, WiLightDevice
|
||||
from .parent_device import WiLightParent
|
||||
|
||||
|
||||
def entities_from_discovered_wilight(hass, api_device):
|
||||
def entities_from_discovered_wilight(api_device: PyWiLightDevice) -> list[LightEntity]:
|
||||
"""Parse configuration and add WiLight light entities."""
|
||||
entities = []
|
||||
entities: list[LightEntity] = []
|
||||
for item in api_device.items:
|
||||
if item["type"] != ITEM_LIGHT:
|
||||
continue
|
||||
index = item["index"]
|
||||
item_name = item["name"]
|
||||
if item["sub_type"] == LIGHT_ON_OFF:
|
||||
entity = WiLightLightOnOff(api_device, index, item_name)
|
||||
entities.append(WiLightLightOnOff(api_device, index, item_name))
|
||||
elif item["sub_type"] == LIGHT_DIMMER:
|
||||
entity = WiLightLightDimmer(api_device, index, item_name)
|
||||
entities.append(WiLightLightDimmer(api_device, index, item_name))
|
||||
elif item["sub_type"] == LIGHT_COLOR:
|
||||
entity = WiLightLightColor(api_device, index, item_name)
|
||||
else:
|
||||
continue
|
||||
entities.append(entity)
|
||||
entities.append(WiLightLightColor(api_device, index, item_name))
|
||||
|
||||
return entities
|
||||
|
||||
|
@ -39,10 +42,11 @@ async def async_setup_entry(
|
|||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up WiLight lights from a config entry."""
|
||||
parent = hass.data[DOMAIN][entry.entry_id]
|
||||
parent: WiLightParent = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
# Handle a discovered WiLight device.
|
||||
entities = entities_from_discovered_wilight(hass, parent.api)
|
||||
assert parent.api
|
||||
entities = entities_from_discovered_wilight(parent.api)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
|
@ -53,15 +57,15 @@ class WiLightLightOnOff(WiLightDevice, LightEntity):
|
|||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if device is on."""
|
||||
return self._status.get("on")
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
await self._client.turn_on(self._index)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
await self._client.turn_off(self._index)
|
||||
|
||||
|
@ -73,16 +77,16 @@ class WiLightLightDimmer(WiLightDevice, LightEntity):
|
|||
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
def brightness(self) -> int:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return int(self._status.get("brightness", 0))
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if device is on."""
|
||||
return self._status.get("on")
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on,set brightness if needed."""
|
||||
# Dimmer switches use a range of [0, 255] to control
|
||||
# brightness. Level 255 might mean to set it to previous value
|
||||
|
@ -92,27 +96,27 @@ class WiLightLightDimmer(WiLightDevice, LightEntity):
|
|||
else:
|
||||
await self._client.turn_on(self._index)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
await self._client.turn_off(self._index)
|
||||
|
||||
|
||||
def wilight_to_hass_hue(value):
|
||||
def wilight_to_hass_hue(value: int) -> float:
|
||||
"""Convert wilight hue 1..255 to hass 0..360 scale."""
|
||||
return min(360, round((value * 360) / 255, 3))
|
||||
|
||||
|
||||
def hass_to_wilight_hue(value):
|
||||
def hass_to_wilight_hue(value: float) -> int:
|
||||
"""Convert hass hue 0..360 to wilight 1..255 scale."""
|
||||
return min(255, round((value * 255) / 360))
|
||||
|
||||
|
||||
def wilight_to_hass_saturation(value):
|
||||
def wilight_to_hass_saturation(value: int) -> float:
|
||||
"""Convert wilight saturation 1..255 to hass 0..100 scale."""
|
||||
return min(100, round((value * 100) / 255, 3))
|
||||
|
||||
|
||||
def hass_to_wilight_saturation(value):
|
||||
def hass_to_wilight_saturation(value: float) -> int:
|
||||
"""Convert hass saturation 0..100 to wilight 1..255 scale."""
|
||||
return min(255, round((value * 255) / 100))
|
||||
|
||||
|
@ -124,24 +128,24 @@ class WiLightLightColor(WiLightDevice, LightEntity):
|
|||
_attr_supported_color_modes = {ColorMode.HS}
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
def brightness(self) -> int:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return int(self._status.get("brightness", 0))
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
def hs_color(self) -> tuple[float, float]:
|
||||
"""Return the hue and saturation color value [float, float]."""
|
||||
return [
|
||||
return (
|
||||
wilight_to_hass_hue(int(self._status.get("hue", 0))),
|
||||
wilight_to_hass_saturation(int(self._status.get("saturation", 0))),
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if device is on."""
|
||||
return self._status.get("on")
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on,set brightness if needed."""
|
||||
# Brightness use a range of [0, 255] to control
|
||||
# Hue use a range of [0, 360] to control
|
||||
|
@ -161,6 +165,6 @@ class WiLightLightColor(WiLightDevice, LightEntity):
|
|||
else:
|
||||
await self._client.turn_on(self._index)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
await self._client.turn_off(self._index)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
"""The WiLight Device integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import pywilight
|
||||
from pywilight.wilight_device import Device as PyWiLightDevice
|
||||
import requests
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -15,23 +19,23 @@ _LOGGER = logging.getLogger(__name__)
|
|||
class WiLightParent:
|
||||
"""Manages a single WiLight Parent Device."""
|
||||
|
||||
def __init__(self, hass, config_entry):
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize the system."""
|
||||
self._host = config_entry.data[CONF_HOST]
|
||||
self._host: str = config_entry.data[CONF_HOST]
|
||||
self._hass = hass
|
||||
self._api = None
|
||||
self._api: PyWiLightDevice | None = None
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
def host(self) -> str:
|
||||
"""Return the host of this parent."""
|
||||
return self._host
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
def api(self) -> PyWiLightDevice | None:
|
||||
"""Return the api of this parent."""
|
||||
return self._api
|
||||
|
||||
async def async_setup(self):
|
||||
async def async_setup(self) -> bool:
|
||||
"""Set up a WiLight Parent Device based on host parameter."""
|
||||
host = self._host
|
||||
hass = self._hass
|
||||
|
@ -42,7 +46,7 @@ class WiLightParent:
|
|||
return False
|
||||
|
||||
@callback
|
||||
def disconnected():
|
||||
def disconnected() -> None:
|
||||
# Schedule reconnect after connection has been lost.
|
||||
_LOGGER.warning("WiLight %s disconnected", api_device.device_id)
|
||||
async_dispatcher_send(
|
||||
|
@ -50,14 +54,14 @@ class WiLightParent:
|
|||
)
|
||||
|
||||
@callback
|
||||
def reconnected():
|
||||
def reconnected() -> None:
|
||||
# Schedule reconnect after connection has been lost.
|
||||
_LOGGER.warning("WiLight %s reconnect", api_device.device_id)
|
||||
async_dispatcher_send(
|
||||
hass, f"wilight_device_available_{api_device.device_id}", True
|
||||
)
|
||||
|
||||
async def connect(api_device):
|
||||
async def connect(api_device: PyWiLightDevice) -> None:
|
||||
# Set up connection and hook it into HA for reconnect/shutdown.
|
||||
_LOGGER.debug("Initiating connection to %s", api_device.device_id)
|
||||
|
||||
|
@ -81,7 +85,7 @@ class WiLightParent:
|
|||
|
||||
return True
|
||||
|
||||
async def async_reset(self):
|
||||
async def async_reset(self) -> None:
|
||||
"""Reset api."""
|
||||
|
||||
# If the initialization was not wrong.
|
||||
|
@ -89,15 +93,13 @@ class WiLightParent:
|
|||
self._api.client.stop()
|
||||
|
||||
|
||||
def create_api_device(host):
|
||||
def create_api_device(host: str) -> PyWiLightDevice:
|
||||
"""Create an API Device."""
|
||||
try:
|
||||
device = pywilight.device_from_host(host)
|
||||
return pywilight.device_from_host(host)
|
||||
except (
|
||||
requests.exceptions.ConnectionError,
|
||||
requests.exceptions.Timeout,
|
||||
) as err:
|
||||
_LOGGER.error("Unable to access WiLight at %s (%s)", host, err)
|
||||
return None
|
||||
|
||||
return device
|
||||
|
|
Loading…
Reference in New Issue