forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Deako integration (home-assistant#121132)
* Deako integration using pydeako * fix: address feedback - make unit tests more e2e - use runtime_data to store connection * fix: address feedback part 2 - added better type safety for Deako config entries - refactored the config flow tests to use a conftest mock instead of directly patching - removed pytest.mark.asyncio test decorators * fix: address feedback pt 3 - simplify config entry type - add test for single_instance_allowed - remove light.py get_state(), only used once, no need to be separate function * fix: ruff format * Update homeassistant/components/deako/__init__.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
- Loading branch information
Showing
20 changed files
with
817 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""The deako integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
from pydeako.deako import Deako, DeviceListTimeout, FindDevicesTimeout | ||
from pydeako.discover import DeakoDiscoverer | ||
|
||
from homeassistant.components import zeroconf | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
|
||
_LOGGER: logging.Logger = logging.getLogger(__name__) | ||
|
||
PLATFORMS: list[Platform] = [Platform.LIGHT] | ||
|
||
type DeakoConfigEntry = ConfigEntry[Deako] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: DeakoConfigEntry) -> bool: | ||
"""Set up deako.""" | ||
_zc = await zeroconf.async_get_instance(hass) | ||
discoverer = DeakoDiscoverer(_zc) | ||
|
||
connection = Deako(discoverer.get_address) | ||
|
||
await connection.connect() | ||
try: | ||
await connection.find_devices() | ||
except DeviceListTimeout as exc: # device list never received | ||
_LOGGER.warning("Device not responding to device list") | ||
await connection.disconnect() | ||
raise ConfigEntryNotReady(exc) from exc | ||
except FindDevicesTimeout as exc: # total devices expected not received | ||
_LOGGER.warning("Device not responding to device requests") | ||
await connection.disconnect() | ||
raise ConfigEntryNotReady(exc) from exc | ||
|
||
# If deako devices are advertising on mdns, we should be able to get at least one device | ||
devices = connection.get_devices() | ||
if len(devices) == 0: | ||
await connection.disconnect() | ||
raise ConfigEntryNotReady(devices) | ||
|
||
entry.runtime_data = connection | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: DeakoConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
await entry.runtime_data.disconnect() | ||
|
||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
"""Config flow for deako.""" | ||
|
||
from pydeako.discover import DeakoDiscoverer, DevicesNotFoundException | ||
|
||
from homeassistant.components import zeroconf | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers import config_entry_flow | ||
|
||
from .const import DOMAIN, NAME | ||
|
||
|
||
async def _async_has_devices(hass: HomeAssistant) -> bool: | ||
"""Return if there are devices that can be discovered.""" | ||
_zc = await zeroconf.async_get_instance(hass) | ||
discoverer = DeakoDiscoverer(_zc) | ||
|
||
try: | ||
await discoverer.get_address() | ||
except DevicesNotFoundException: | ||
return False | ||
else: | ||
# address exists, there's at least one device | ||
return True | ||
|
||
|
||
config_entry_flow.register_discovery_flow(DOMAIN, NAME, _async_has_devices) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Constants for Deako.""" | ||
|
||
# Base component constants | ||
NAME = "Deako" | ||
DOMAIN = "deako" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
"""Binary sensor platform for integration_blueprint.""" | ||
|
||
from typing import Any | ||
|
||
from pydeako.deako import Deako | ||
|
||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
|
||
from . import DeakoConfigEntry | ||
from .const import DOMAIN | ||
|
||
# Model names | ||
MODEL_SMART = "smart" | ||
MODEL_DIMMER = "dimmer" | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
config: DeakoConfigEntry, | ||
add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Configure the platform.""" | ||
client = config.runtime_data | ||
|
||
add_entities([DeakoLightEntity(client, uuid) for uuid in client.get_devices()]) | ||
|
||
|
||
class DeakoLightEntity(LightEntity): | ||
"""Deako LightEntity class.""" | ||
|
||
_attr_has_entity_name = True | ||
_attr_name = None | ||
_attr_is_on = False | ||
_attr_available = True | ||
|
||
client: Deako | ||
|
||
def __init__(self, client: Deako, uuid: str) -> None: | ||
"""Save connection reference.""" | ||
self.client = client | ||
self._attr_unique_id = uuid | ||
|
||
dimmable = client.is_dimmable(uuid) | ||
|
||
model = MODEL_SMART | ||
self._attr_color_mode = ColorMode.ONOFF | ||
if dimmable: | ||
model = MODEL_DIMMER | ||
self._attr_color_mode = ColorMode.BRIGHTNESS | ||
|
||
self._attr_supported_color_modes = {self._attr_color_mode} | ||
|
||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, uuid)}, | ||
name=client.get_name(uuid), | ||
manufacturer="Deako", | ||
model=model, | ||
) | ||
|
||
client.set_state_callback(uuid, self.on_update) | ||
self.update() # set initial state | ||
|
||
def on_update(self) -> None: | ||
"""State update callback.""" | ||
self.update() | ||
self.schedule_update_ha_state() | ||
|
||
async def control_device(self, power: bool, dim: int | None = None) -> None: | ||
"""Control entity state via client.""" | ||
assert self._attr_unique_id is not None | ||
await self.client.control_device(self._attr_unique_id, power, dim) | ||
|
||
async def async_turn_on(self, **kwargs: Any) -> None: | ||
"""Turn on the light.""" | ||
dim = None | ||
if ATTR_BRIGHTNESS in kwargs: | ||
dim = round(kwargs[ATTR_BRIGHTNESS] / 2.55, 0) | ||
await self.control_device(True, dim) | ||
|
||
async def async_turn_off(self, **kwargs: Any) -> None: | ||
"""Turn off the device.""" | ||
await self.control_device(False) | ||
|
||
def update(self) -> None: | ||
"""Call to update state.""" | ||
assert self._attr_unique_id is not None | ||
state = self.client.get_state(self._attr_unique_id) or {} | ||
self._attr_is_on = bool(state.get("power", False)) | ||
if ( | ||
self._attr_supported_color_modes is not None | ||
and ColorMode.BRIGHTNESS in self._attr_supported_color_modes | ||
): | ||
self._attr_brightness = int(round(state.get("dim", 0) * 2.55)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"domain": "deako", | ||
"name": "Deako", | ||
"codeowners": ["@sebirdman", "@balake", "@deakolights"], | ||
"config_flow": true, | ||
"dependencies": ["zeroconf"], | ||
"documentation": "https://www.home-assistant.io/integrations/deako", | ||
"iot_class": "local_polling", | ||
"loggers": ["pydeako"], | ||
"requirements": ["pydeako==0.4.0"], | ||
"single_config_entry": true, | ||
"zeroconf": ["_deako._tcp.local."] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"config": { | ||
"step": { | ||
"confirm": { | ||
"description": "Please confirm setting up the Deako integration" | ||
} | ||
}, | ||
"abort": { | ||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", | ||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,6 +114,7 @@ | |
"cpuspeed", | ||
"crownstone", | ||
"daikin", | ||
"deako", | ||
"deconz", | ||
"deluge", | ||
"denonavr", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Tests for the Deako integration.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"""deako session fixtures.""" | ||
|
||
from collections.abc import Generator | ||
from unittest.mock import MagicMock, patch | ||
|
||
import pytest | ||
|
||
from homeassistant.components.deako.const import DOMAIN | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
|
||
@pytest.fixture | ||
def mock_config_entry() -> MockConfigEntry: | ||
"""Return the default mocked config entry.""" | ||
return MockConfigEntry( | ||
domain=DOMAIN, | ||
) | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def pydeako_deako_mock() -> Generator[MagicMock]: | ||
"""Mock pydeako deako client.""" | ||
with patch("homeassistant.components.deako.Deako", autospec=True) as mock: | ||
yield mock | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def pydeako_discoverer_mock(mock_async_zeroconf: MagicMock) -> Generator[MagicMock]: | ||
"""Mock pydeako discovery client.""" | ||
with ( | ||
patch("homeassistant.components.deako.DeakoDiscoverer", autospec=True) as mock, | ||
patch("homeassistant.components.deako.config_flow.DeakoDiscoverer", new=mock), | ||
): | ||
yield mock | ||
|
||
|
||
@pytest.fixture | ||
def mock_deako_setup() -> Generator[MagicMock]: | ||
"""Mock async_setup_entry for config flow tests.""" | ||
with patch( | ||
"homeassistant.components.deako.async_setup_entry", | ||
return_value=True, | ||
) as mock_setup: | ||
yield mock_setup |
Oops, something went wrong.