From 65c4e63e301eac9330b4c43f2463ace278a9262a Mon Sep 17 00:00:00 2001 From: Jon Caruana Date: Tue, 17 Jan 2023 07:53:16 -0800 Subject: [PATCH] Bump pylitejet to 0.4.6 (now with async!) (#85493) --- homeassistant/components/litejet/__init__.py | 13 ++++++-- .../components/litejet/config_flow.py | 7 ++-- homeassistant/components/litejet/light.py | 32 +++++++++---------- .../components/litejet/manifest.json | 2 +- homeassistant/components/litejet/scene.py | 22 ++++++------- homeassistant/components/litejet/switch.py | 24 +++++++------- homeassistant/components/litejet/trigger.py | 3 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/litejet/conftest.py | 26 ++++++++++----- tests/components/litejet/test_light.py | 8 ++--- 11 files changed, 78 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py index 5131ee52e67..e2396073fdd 100644 --- a/homeassistant/components/litejet/__init__.py +++ b/homeassistant/components/litejet/__init__.py @@ -6,7 +6,7 @@ from serial import SerialException import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv @@ -52,11 +52,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: port = entry.data[CONF_PORT] try: - system = pylitejet.LiteJet(port) + system = await pylitejet.open(port) except SerialException as ex: _LOGGER.error("Error connecting to the LiteJet MCP at %s", port, exc_info=ex) raise ConfigEntryNotReady from ex + async def handle_stop(event) -> None: + await system.close() + + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop) + ) + hass.data[DOMAIN] = system await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -69,7 +76,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - hass.data[DOMAIN].close() + await hass.data[DOMAIN].close() hass.data.pop(DOMAIN) return unload_ok diff --git a/homeassistant/components/litejet/config_flow.py b/homeassistant/components/litejet/config_flow.py index e14eda1b745..25d454071cc 100644 --- a/homeassistant/components/litejet/config_flow.py +++ b/homeassistant/components/litejet/config_flow.py @@ -59,15 +59,12 @@ class LiteJetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: port = user_input[CONF_PORT] - await self.async_set_unique_id(port) - self._abort_if_unique_id_configured() - try: - system = pylitejet.LiteJet(port) - system.close() + system = await pylitejet.open(port) except SerialException: errors[CONF_PORT] = "open_failed" else: + await system.close() return self.async_create_entry( title=port, data={CONF_PORT: port}, diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index a41a34016d9..573e2fd5e4f 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -33,14 +33,12 @@ async def async_setup_entry( system: LiteJet = hass.data[DOMAIN] - def get_entities(system: LiteJet) -> list[LiteJetLight]: - entities = [] - for index in system.loads(): - name = system.get_load_name(index) - entities.append(LiteJetLight(config_entry, system, index, name)) - return entities + entities = [] + for index in system.loads(): + name = await system.get_load_name(index) + entities.append(LiteJetLight(config_entry, system, index, name)) - async_add_entities(await hass.async_add_executor_job(get_entities, system), True) + async_add_entities(entities, True) class LiteJetLight(LightEntity): @@ -73,19 +71,19 @@ class LiteJetLight(LightEntity): """Entity being removed from hass.""" self._lj.unsubscribe(self._on_load_changed) - def _on_load_changed(self) -> None: + def _on_load_changed(self, level) -> None: """Handle state changes.""" _LOGGER.debug("Updating due to notification for %s", self.name) self.schedule_update_ha_state(True) - def turn_on(self, **kwargs: Any) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" # If neither attribute is specified then the simple activate load # LiteJet API will use the per-light default brightness and # transition values programmed in the LiteJet system. if ATTR_BRIGHTNESS not in kwargs and ATTR_TRANSITION not in kwargs: - self._lj.activate_load(self._index) + await self._lj.activate_load(self._index) return # If either attribute is specified then Home Assistant must @@ -94,20 +92,22 @@ class LiteJetLight(LightEntity): transition = kwargs.get(ATTR_TRANSITION, default_transition) brightness = int(kwargs.get(ATTR_BRIGHTNESS, 255) / 255 * 99) - self._lj.activate_load_at(self._index, brightness, int(transition)) + await self._lj.activate_load_at(self._index, brightness, int(transition)) - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" if ATTR_TRANSITION in kwargs: - self._lj.activate_load_at(self._index, 0, kwargs[ATTR_TRANSITION]) + await self._lj.activate_load_at(self._index, 0, kwargs[ATTR_TRANSITION]) return # If transition attribute is not specified then the simple # deactivate load LiteJet API will use the per-light default # transition value programmed in the LiteJet system. - self._lj.deactivate_load(self._index) + await self._lj.deactivate_load(self._index) - def update(self) -> None: + async def async_update(self) -> None: """Retrieve the light's brightness from the LiteJet system.""" - self._attr_brightness = int(self._lj.get_load_level(self._index) / 99 * 255) + self._attr_brightness = int( + await self._lj.get_load_level(self._index) / 99 * 255 + ) self._attr_is_on = self.brightness != 0 diff --git a/homeassistant/components/litejet/manifest.json b/homeassistant/components/litejet/manifest.json index c6e958d3a10..ffc3e214fef 100644 --- a/homeassistant/components/litejet/manifest.json +++ b/homeassistant/components/litejet/manifest.json @@ -2,7 +2,7 @@ "domain": "litejet", "name": "LiteJet", "documentation": "https://www.home-assistant.io/integrations/litejet", - "requirements": ["pylitejet==0.3.0"], + "requirements": ["pylitejet==0.4.6"], "codeowners": ["@joncar"], "config_flow": true, "iot_class": "local_push", diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index 0a091d7e729..7a37d24230f 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -1,6 +1,8 @@ """Support for LiteJet scenes.""" from typing import Any +from pylitejet import LiteJet + from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -18,22 +20,20 @@ async def async_setup_entry( ) -> None: """Set up entry.""" - system = hass.data[DOMAIN] + system: LiteJet = hass.data[DOMAIN] - def get_entities(system): - entities = [] - for i in system.scenes(): - name = system.get_scene_name(i) - entities.append(LiteJetScene(config_entry.entry_id, system, i, name)) - return entities + entities = [] + for i in system.scenes(): + name = await system.get_scene_name(i) + entities.append(LiteJetScene(config_entry.entry_id, system, i, name)) - async_add_entities(await hass.async_add_executor_job(get_entities, system), True) + async_add_entities(entities, True) class LiteJetScene(Scene): """Representation of a single LiteJet scene.""" - def __init__(self, entry_id, lj, i, name): # pylint: disable=invalid-name + def __init__(self, entry_id, lj: LiteJet, i, name): # pylint: disable=invalid-name """Initialize the scene.""" self._entry_id = entry_id self._lj = lj @@ -55,9 +55,9 @@ class LiteJetScene(Scene): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} - def activate(self, **kwargs: Any) -> None: + async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" - self._lj.activate_scene(self._index) + await self._lj.activate_scene(self._index) @property def entity_registry_enabled_default(self) -> bool: diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index 375e3dd9f46..f31d9be04e3 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -2,6 +2,8 @@ import logging from typing import Any +from pylitejet import LiteJet + from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -21,16 +23,14 @@ async def async_setup_entry( ) -> None: """Set up entry.""" - system = hass.data[DOMAIN] + system: LiteJet = hass.data[DOMAIN] - def get_entities(system): - entities = [] - for i in system.button_switches(): - name = system.get_switch_name(i) - entities.append(LiteJetSwitch(config_entry.entry_id, system, i, name)) - return entities + entities = [] + for i in system.button_switches(): + name = await system.get_switch_name(i) + entities.append(LiteJetSwitch(config_entry.entry_id, system, i, name)) - async_add_entities(await hass.async_add_executor_job(get_entities, system), True) + async_add_entities(entities, True) class LiteJetSwitch(SwitchEntity): @@ -86,13 +86,13 @@ class LiteJetSwitch(SwitchEntity): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} - def turn_on(self, **kwargs: Any) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Press the switch.""" - self._lj.press_switch(self._index) + await self._lj.press_switch(self._index) - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Release the switch.""" - self._lj.release_switch(self._index) + await self._lj.release_switch(self._index) @property def entity_registry_enabled_default(self) -> bool: diff --git a/homeassistant/components/litejet/trigger.py b/homeassistant/components/litejet/trigger.py index a0cdeaf9a01..502d84693c6 100644 --- a/homeassistant/components/litejet/trigger.py +++ b/homeassistant/components/litejet/trigger.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable +from pylitejet import LiteJet import voluptuous as vol from homeassistant.const import CONF_PLATFORM @@ -104,7 +105,7 @@ async def async_attach_trigger( ): hass.add_job(call_action) - system = hass.data[DOMAIN] + system: LiteJet = hass.data[DOMAIN] system.on_switch_pressed(number, pressed) system.on_switch_released(number, released) diff --git a/requirements_all.txt b/requirements_all.txt index 64af72d81a9..994b2ae4ea2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1741,7 +1741,7 @@ pylgnetcast==0.3.7 pylibrespot-java==0.1.1 # homeassistant.components.litejet -pylitejet==0.3.0 +pylitejet==0.4.6 # homeassistant.components.litterrobot pylitterbot==2023.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33ab374cfb4..a288476de8a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1248,7 +1248,7 @@ pylaunches==1.3.0 pylibrespot-java==0.1.1 # homeassistant.components.litejet -pylitejet==0.3.0 +pylitejet==0.4.6 # homeassistant.components.litterrobot pylitterbot==2023.1.1 diff --git a/tests/components/litejet/conftest.py b/tests/components/litejet/conftest.py index 00b1eb92190..0805fd20231 100644 --- a/tests/components/litejet/conftest.py +++ b/tests/components/litejet/conftest.py @@ -1,6 +1,6 @@ """Fixtures for LiteJet testing.""" from datetime import timedelta -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest @@ -12,13 +12,13 @@ def mock_litejet(): """Mock LiteJet system.""" with patch("pylitejet.LiteJet") as mock_pylitejet: - def get_load_name(number): + async def get_load_name(number): return f"Mock Load #{number}" - def get_scene_name(number): + async def get_scene_name(number): return f"Mock Scene #{number}" - def get_switch_name(number): + async def get_switch_name(number): return f"Mock Switch #{number}" mock_lj = mock_pylitejet.return_value @@ -45,16 +45,26 @@ def mock_litejet(): mock_lj.on_load_activated.side_effect = on_load_activated mock_lj.on_load_deactivated.side_effect = on_load_deactivated + mock_lj.open = AsyncMock() + mock_lj.close = AsyncMock() + mock_lj.loads.return_value = range(1, 3) - mock_lj.get_load_name.side_effect = get_load_name - mock_lj.get_load_level.return_value = 0 + mock_lj.get_load_name = AsyncMock(side_effect=get_load_name) + mock_lj.get_load_level = AsyncMock(return_value=0) + mock_lj.activate_load = AsyncMock() + mock_lj.activate_load_at = AsyncMock() + mock_lj.deactivate_load = AsyncMock() mock_lj.button_switches.return_value = range(1, 3) mock_lj.all_switches.return_value = range(1, 6) - mock_lj.get_switch_name.side_effect = get_switch_name + mock_lj.get_switch_name = AsyncMock(side_effect=get_switch_name) + mock_lj.press_switch = AsyncMock() + mock_lj.release_switch = AsyncMock() mock_lj.scenes.return_value = range(1, 3) - mock_lj.get_scene_name.side_effect = get_scene_name + mock_lj.get_scene_name = AsyncMock(side_effect=get_scene_name) + mock_lj.activate_scene = AsyncMock() + mock_lj.deactivate_scene = AsyncMock() mock_lj.start_time = dt_util.utcnow() mock_lj.last_delta = timedelta(0) diff --git a/tests/components/litejet/test_light.py b/tests/components/litejet/test_light.py index 86b3dd84367..ca80df2b6dd 100644 --- a/tests/components/litejet/test_light.py +++ b/tests/components/litejet/test_light.py @@ -113,7 +113,7 @@ async def test_activated_event(hass, mock_litejet): # Light 1 mock_litejet.get_load_level.return_value = 99 mock_litejet.get_load_level.reset_mock() - mock_litejet.load_activated_callbacks[ENTITY_LIGHT_NUMBER]() + mock_litejet.load_activated_callbacks[ENTITY_LIGHT_NUMBER](99) await hass.async_block_till_done() mock_litejet.get_load_level.assert_called_once_with(ENTITY_LIGHT_NUMBER) @@ -128,7 +128,7 @@ async def test_activated_event(hass, mock_litejet): mock_litejet.get_load_level.return_value = 40 mock_litejet.get_load_level.reset_mock() - mock_litejet.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() + mock_litejet.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER](40) await hass.async_block_till_done() mock_litejet.get_load_level.assert_called_once_with(ENTITY_OTHER_LIGHT_NUMBER) @@ -147,7 +147,7 @@ async def test_deactivated_event(hass, mock_litejet): # Initial state is on. mock_litejet.get_load_level.return_value = 99 - mock_litejet.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() + mock_litejet.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER](99) await hass.async_block_till_done() assert light.is_on(hass, ENTITY_OTHER_LIGHT) @@ -157,7 +157,7 @@ async def test_deactivated_event(hass, mock_litejet): mock_litejet.get_load_level.reset_mock() mock_litejet.get_load_level.return_value = 0 - mock_litejet.load_deactivated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() + mock_litejet.load_deactivated_callbacks[ENTITY_OTHER_LIGHT_NUMBER](0) await hass.async_block_till_done() # (Requesting the level is not strictly needed with a deactivated