1
mirror of https://github.com/home-assistant/core synced 2024-09-25 00:41:32 +02:00

Reflect changes to pydeconz v84 (#56361)

Mostly snake case conversions and typing
But also a change in retry mechanism
Added a more complete set_* call to most types to remove the direct relation to rest API of deCONZ
This commit is contained in:
Robert Svensson 2021-09-18 09:05:08 +02:00 committed by GitHub
parent 6947912fa9
commit 539ef31046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 87 additions and 85 deletions

View File

@ -55,7 +55,7 @@ DECONZ_TO_ALARM_STATE = {
def get_alarm_system_for_unique_id(gateway, unique_id: str):
"""Retrieve alarm system unique ID is registered to."""
for alarm_system in gateway.api.alarm_systems.values():
for alarm_system in gateway.api.alarmsystems.values():
if unique_id in alarm_system.devices:
return alarm_system
@ -77,8 +77,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
if (
sensor.type in AncillaryControl.ZHATYPE
and sensor.uniqueid not in gateway.entities[DOMAIN]
and get_alarm_system_for_unique_id(gateway, sensor.uniqueid)
and sensor.unique_id not in gateway.entities[DOMAIN]
and get_alarm_system_for_unique_id(gateway, sensor.unique_id)
):
entities.append(DeconzAlarmControlPanel(sensor, gateway))
@ -110,7 +110,7 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity):
def __init__(self, device, gateway) -> None:
"""Set up alarm control panel device."""
super().__init__(device, gateway)
self.alarm_system = get_alarm_system_for_unique_id(gateway, device.uniqueid)
self.alarm_system = get_alarm_system_for_unique_id(gateway, device.unique_id)
@callback
def async_update_callback(self, force_update: bool = False) -> None:

View File

@ -48,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if (
sensor.BINARY
and sensor.uniqueid not in gateway.entities[DOMAIN]
and sensor.unique_id not in gateway.entities[DOMAIN]
and (
gateway.option_allow_clip_sensor
or not sensor.type.startswith("CLIP")
@ -116,8 +116,8 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity):
elif self._device.type in Vibration.ZHATYPE:
attr[ATTR_ORIENTATION] = self._device.orientation
attr[ATTR_TILTANGLE] = self._device.tiltangle
attr[ATTR_VIBRATIONSTRENGTH] = self._device.vibrationstrength
attr[ATTR_TILTANGLE] = self._device.tilt_angle
attr[ATTR_VIBRATIONSTRENGTH] = self._device.vibration_strength
return attr

View File

@ -86,7 +86,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if (
sensor.type in Thermostat.ZHATYPE
and sensor.uniqueid not in gateway.entities[DOMAIN]
and sensor.unique_id not in gateway.entities[DOMAIN]
and (
gateway.option_allow_clip_sensor
or not sensor.type.startswith("CLIP")
@ -142,7 +142,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
def fan_mode(self) -> str:
"""Return fan operation."""
return DECONZ_TO_FAN_MODE.get(
self._device.fanmode, FAN_ON if self._device.state_on else FAN_OFF
self._device.fan_mode, FAN_ON if self._device.state_on else FAN_OFF
)
@property
@ -155,9 +155,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
if fan_mode not in FAN_MODE_TO_DECONZ:
raise ValueError(f"Unsupported fan mode {fan_mode}")
data = {"fanmode": FAN_MODE_TO_DECONZ[fan_mode]}
await self._device.async_set_config(data)
await self._device.set_config(fan_mode=FAN_MODE_TO_DECONZ[fan_mode])
# HVAC control
@ -186,7 +184,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
if len(self._hvac_mode_to_deconz) == 2: # Only allow turn on and off thermostat
data = {"on": self._hvac_mode_to_deconz[hvac_mode]}
await self._device.async_set_config(data)
await self._device.set_config(**data)
# Preset control
@ -205,9 +203,7 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
if preset_mode not in PRESET_MODE_TO_DECONZ:
raise ValueError(f"Unsupported preset mode {preset_mode}")
data = {"preset": PRESET_MODE_TO_DECONZ[preset_mode]}
await self._device.async_set_config(data)
await self._device.set_config(preset=PRESET_MODE_TO_DECONZ[preset_mode])
# Temperature control
@ -220,19 +216,19 @@ class DeconzThermostat(DeconzDevice, ClimateEntity):
def target_temperature(self) -> float:
"""Return the target temperature."""
if self._device.mode == "cool":
return self._device.coolsetpoint
return self._device.heatsetpoint
return self._device.cooling_setpoint
return self._device.heating_setpoint
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
if ATTR_TEMPERATURE not in kwargs:
raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}")
data = {"heatsetpoint": kwargs[ATTR_TEMPERATURE] * 100}
data = {"heating_setpoint": kwargs[ATTR_TEMPERATURE] * 100}
if self._device.mode == "cool":
data = {"coolsetpoint": kwargs[ATTR_TEMPERATURE] * 100}
data = {"cooling_setpoint": kwargs[ATTR_TEMPERATURE] * 100}
await self._device.async_set_config(data)
await self._device.set_config(**data)
@property
def extra_state_attributes(self):

View File

@ -5,10 +5,10 @@ from urllib.parse import urlparse
import async_timeout
from pydeconz.errors import RequestError, ResponseError
from pydeconz.gateway import DeconzSession
from pydeconz.utils import (
async_discovery,
async_get_api_key,
async_get_bridge_id,
discovery as deconz_discovery,
get_bridge_id as deconz_get_bridge_id,
normalize_bridge_id,
)
import voluptuous as vol
@ -86,7 +86,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
try:
with async_timeout.timeout(10):
self.bridges = await async_discovery(session)
self.bridges = await deconz_discovery(session)
except (asyncio.TimeoutError, ResponseError):
self.bridges = []
@ -134,10 +134,15 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None:
session = aiohttp_client.async_get_clientsession(self.hass)
deconz_session = DeconzSession(
session,
host=self.deconz_config[CONF_HOST],
port=self.deconz_config[CONF_PORT],
)
try:
with async_timeout.timeout(10):
api_key = await async_get_api_key(session, **self.deconz_config)
api_key = await deconz_session.get_api_key()
except (ResponseError, RequestError, asyncio.TimeoutError):
errors["base"] = "no_key"
@ -155,7 +160,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
try:
with async_timeout.timeout(10):
self.bridge_id = await async_get_bridge_id(
self.bridge_id = await deconz_get_bridge_id(
session, **self.deconz_config
)
await self.async_set_unique_id(self.bridge_id)

View File

@ -48,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for light in lights:
if (
light.type in COVER_TYPES
and light.uniqueid not in gateway.entities[DOMAIN]
and light.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzCover(light, gateway))

View File

@ -18,15 +18,15 @@ class DeconzBase:
@property
def unique_id(self):
"""Return a unique identifier for this device."""
return self._device.uniqueid
return self._device.unique_id
@property
def serial(self):
"""Return a serial number for this device."""
if self._device.uniqueid is None or self._device.uniqueid.count(":") != 7:
if self._device.unique_id is None or self._device.unique_id.count(":") != 7:
return None
return self._device.uniqueid.split("-", 1)[0]
return self._device.unique_id.split("-", 1)[0]
@property
def device_info(self):
@ -38,10 +38,10 @@ class DeconzBase:
"connections": {(CONNECTION_ZIGBEE, self.serial)},
"identifiers": {(DECONZ_DOMAIN, self.serial)},
"manufacturer": self._device.manufacturer,
"model": self._device.modelid,
"model": self._device.model_id,
"name": self._device.name,
"sw_version": self._device.swversion,
"via_device": (DECONZ_DOMAIN, self.gateway.api.config.bridgeid),
"sw_version": self._device.software_version,
"via_device": (DECONZ_DOMAIN, self.gateway.api.config.bridge_id),
}

View File

@ -47,7 +47,7 @@ async def async_setup_events(gateway) -> None:
if (
sensor.type not in Switch.ZHATYPE + AncillaryControl.ZHATYPE
or sensor.uniqueid in {event.unique_id for event in gateway.events}
or sensor.unique_id in {event.unique_id for event in gateway.events}
):
continue

View File

@ -39,7 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
for light in lights:
if light.type in FANS and light.uniqueid not in gateway.entities[DOMAIN]:
if light.type in FANS and light.unique_id not in gateway.entities[DOMAIN]:
entities.append(DeconzFan(light, gateway))
if entities:

View File

@ -151,11 +151,11 @@ class DeconzGateway:
# Gateway service
device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
identifiers={(DECONZ_DOMAIN, self.api.config.bridgeid)},
identifiers={(DECONZ_DOMAIN, self.api.config.bridge_id)},
manufacturer="Dresden Elektronik",
model=self.api.config.modelid,
model=self.api.config.model_id,
name=self.api.config.name,
sw_version=self.api.config.swversion,
sw_version=self.api.config.software_version,
via_device=(CONNECTION_NETWORK_MAC, self.api.config.mac),
)
@ -266,12 +266,12 @@ async def get_gateway(
config[CONF_HOST],
config[CONF_PORT],
config[CONF_API_KEY],
async_add_device=async_add_device_callback,
add_device=async_add_device_callback,
connection_status=async_connection_status_callback,
)
try:
with async_timeout.timeout(10):
await deconz.initialize()
await deconz.refresh_state()
return deconz
except errors.Unauthorized as err:

View File

@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for light in lights:
if (
light.type not in other_light_resource_types
and light.uniqueid not in gateway.entities[DOMAIN]
and light.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzLight(light, gateway))
@ -112,10 +112,10 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
self._attr_supported_color_modes = set()
if device.ct is not None:
if device.color_temp is not None:
self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
if device.hue is not None and device.sat is not None:
if device.hue is not None and device.saturation is not None:
self._attr_supported_color_modes.add(COLOR_MODE_HS)
if device.xy is not None:
@ -137,11 +137,11 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
@property
def color_mode(self) -> str:
"""Return the color mode of the light."""
if self._device.colormode == "ct":
if self._device.color_mode == "ct":
color_mode = COLOR_MODE_COLOR_TEMP
elif self._device.colormode == "hs":
elif self._device.color_mode == "hs":
color_mode = COLOR_MODE_HS
elif self._device.colormode == "xy":
elif self._device.color_mode == "xy":
color_mode = COLOR_MODE_XY
elif self._device.brightness is not None:
color_mode = COLOR_MODE_BRIGHTNESS
@ -162,12 +162,12 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
@property
def color_temp(self):
"""Return the CT color value."""
return self._device.ct
return self._device.color_temp
@property
def hs_color(self) -> tuple:
"""Return the hs color value."""
return (self._device.hue / 65535 * 360, self._device.sat / 255 * 100)
return (self._device.hue / 65535 * 360, self._device.saturation / 255 * 100)
@property
def xy_color(self) -> tuple | None:
@ -184,25 +184,25 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
data = {"on": True}
if ATTR_BRIGHTNESS in kwargs:
data["bri"] = kwargs[ATTR_BRIGHTNESS]
data["brightness"] = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP in kwargs:
data["ct"] = kwargs[ATTR_COLOR_TEMP]
data["color_temperature"] = kwargs[ATTR_COLOR_TEMP]
if ATTR_HS_COLOR in kwargs:
if COLOR_MODE_XY in self._attr_supported_color_modes:
data["xy"] = color_hs_to_xy(*kwargs[ATTR_HS_COLOR])
else:
data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535)
data["sat"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
data["saturation"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
if ATTR_XY_COLOR in kwargs:
data["xy"] = kwargs[ATTR_XY_COLOR]
if ATTR_TRANSITION in kwargs:
data["transitiontime"] = int(kwargs[ATTR_TRANSITION] * 10)
data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10)
elif "IKEA" in self._device.manufacturer:
data["transitiontime"] = 0
data["transition_time"] = 0
if ATTR_FLASH in kwargs:
if kwargs[ATTR_FLASH] == FLASH_SHORT:
@ -218,7 +218,7 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
else:
data["effect"] = "none"
await self._device.async_set_state(data)
await self._device.set_state(**data)
async def async_turn_off(self, **kwargs):
"""Turn off light."""
@ -228,8 +228,8 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
data = {"on": False}
if ATTR_TRANSITION in kwargs:
data["bri"] = 0
data["transitiontime"] = int(kwargs[ATTR_TRANSITION] * 10)
data["brightness"] = 0
data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10)
if ATTR_FLASH in kwargs:
if kwargs[ATTR_FLASH] == FLASH_SHORT:
@ -239,7 +239,7 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
data["alert"] = "lselect"
del data["on"]
await self._device.async_set_state(data)
await self._device.set_state(**data)
@property
def extra_state_attributes(self):
@ -253,12 +253,12 @@ class DeconzLight(DeconzBaseLight):
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
return self._device.ctmax or super().max_mireds
return self._device.max_color_temp or super().max_mireds
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
return self._device.ctmin or super().min_mireds
return self._device.min_color_temp or super().min_mireds
class DeconzGroup(DeconzBaseLight):
@ -282,7 +282,7 @@ class DeconzGroup(DeconzBaseLight):
"manufacturer": "Dresden Elektronik",
"model": "deCONZ group",
"name": self._device.name,
"via_device": (DECONZ_DOMAIN, self.gateway.api.config.bridgeid),
"via_device": (DECONZ_DOMAIN, self.gateway.api.config.bridge_id),
}
@property

View File

@ -22,7 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if (
light.type in LOCK_TYPES
and light.uniqueid not in gateway.entities[DOMAIN]
and light.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzLock(light, gateway))
@ -44,7 +44,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if (
sensor.type in LOCK_TYPES
and sensor.uniqueid not in gateway.entities[DOMAIN]
and sensor.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzLock(sensor, gateway))

View File

@ -153,9 +153,9 @@ def async_describe_events(
interface = None
data = event.data.get(CONF_EVENT) or event.data.get(CONF_GESTURE, "")
if data and deconz_event.device.modelid in REMOTES:
if data and deconz_event.device.model_id in REMOTES:
action, interface = _get_device_event_description(
deconz_event.device.modelid, data
deconz_event.device.model_id, data
)
# Unknown event

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/deconz",
"requirements": [
"pydeconz==83"
"pydeconz==84"
],
"ssdp": [
{

View File

@ -51,4 +51,4 @@ class DeconzScene(Scene):
async def async_activate(self, **kwargs: Any) -> None:
"""Activate the scene."""
await self._scene.async_set_state({})
await self._scene.recall()

View File

@ -124,7 +124,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
+ DoorLock.ZHATYPE
+ Switch.ZHATYPE
+ Thermostat.ZHATYPE
and sensor.uniqueid not in gateway.entities[DOMAIN]
and sensor.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzSensor(sensor, gateway))
@ -273,7 +273,7 @@ class DeconzBattery(DeconzDevice, SensorEntity):
Normally there should only be one battery sensor per device from deCONZ.
With specific Danfoss devices each endpoint can report its own battery state.
"""
if self._device.manufacturer == "Danfoss" and self._device.modelid in [
if self._device.manufacturer == "Danfoss" and self._device.model_id in [
"0x8030",
"0x8031",
"0x8034",

View File

@ -185,7 +185,7 @@ async def async_remove_orphaned_entries_service(gateway):
# Don't remove the Gateway service entry
gateway_service = device_registry.async_get_device(
identifiers={(DOMAIN, gateway.api.config.bridgeid)}, connections=set()
identifiers={(DOMAIN, gateway.api.config.bridge_id)}, connections=set()
)
if gateway_service.id in devices_to_be_removed:
devices_to_be_removed.remove(gateway_service.id)

View File

@ -25,12 +25,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
if (
light.type in POWER_PLUGS
and light.uniqueid not in gateway.entities[DOMAIN]
and light.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzPowerPlug(light, gateway))
elif (
light.type in SIRENS and light.uniqueid not in gateway.entities[DOMAIN]
light.type in SIRENS and light.unique_id not in gateway.entities[DOMAIN]
):
entities.append(DeconzSiren(light, gateway))
@ -58,13 +58,11 @@ class DeconzPowerPlug(DeconzDevice, SwitchEntity):
async def async_turn_on(self, **kwargs):
"""Turn on switch."""
data = {"on": True}
await self._device.async_set_state(data)
await self._device.set_state(on=True)
async def async_turn_off(self, **kwargs):
"""Turn off switch."""
data = {"on": False}
await self._device.async_set_state(data)
await self._device.set_state(on=False)
class DeconzSiren(DeconzDevice, SwitchEntity):

View File

@ -1415,7 +1415,7 @@ pydaikin==2.4.4
pydanfossair==0.1.0
# homeassistant.components.deconz
pydeconz==83
pydeconz==84
# homeassistant.components.delijn
pydelijn==0.6.1

View File

@ -815,7 +815,7 @@ pycoolmasternet-async==0.1.2
pydaikin==2.4.4
# homeassistant.components.deconz
pydeconz==83
pydeconz==84
# homeassistant.components.dexcom
pydexcom==0.2.0

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from unittest.mock import patch
from pydeconz.websocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA
import pytest
from tests.components.light.conftest import mock_light_profiles # noqa: F401
@ -19,10 +20,10 @@ def mock_deconz_websocket():
if data:
mock.return_value.data = data
await pydeconz_gateway_session_handler(signal="data")
await pydeconz_gateway_session_handler(signal=SIGNAL_DATA)
elif state:
mock.return_value.state = state
await pydeconz_gateway_session_handler(signal="state")
await pydeconz_gateway_session_handler(signal=SIGNAL_CONNECTION_STATE)
else:
raise NotImplementedError

View File

@ -267,14 +267,14 @@ async def test_reset_after_successful_setup(hass, aioclient_mock):
async def test_get_gateway(hass):
"""Successful call."""
with patch("pydeconz.DeconzSession.initialize", return_value=True):
with patch("pydeconz.DeconzSession.refresh_state", return_value=True):
assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock())
async def test_get_gateway_fails_unauthorized(hass):
"""Failed call."""
with patch(
"pydeconz.DeconzSession.initialize",
"pydeconz.DeconzSession.refresh_state",
side_effect=pydeconz.errors.Unauthorized,
), pytest.raises(AuthenticationRequired):
assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) is False
@ -283,7 +283,7 @@ async def test_get_gateway_fails_unauthorized(hass):
async def test_get_gateway_fails_cannot_connect(hass):
"""Failed call."""
with patch(
"pydeconz.DeconzSession.initialize",
"pydeconz.DeconzSession.refresh_state",
side_effect=pydeconz.errors.RequestError,
), pytest.raises(CannotConnect):
assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) is False

View File

@ -44,14 +44,16 @@ async def setup_entry(hass, entry):
async def test_setup_entry_fails(hass):
"""Test setup entry fails if deCONZ is not available."""
with patch("pydeconz.DeconzSession.initialize", side_effect=Exception):
with patch("pydeconz.DeconzSession.refresh_state", side_effect=Exception):
await setup_deconz_integration(hass)
assert not hass.data[DECONZ_DOMAIN]
async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available."""
with patch("pydeconz.DeconzSession.initialize", side_effect=asyncio.TimeoutError):
with patch(
"pydeconz.DeconzSession.refresh_state", side_effect=asyncio.TimeoutError
):
await setup_deconz_integration(hass)
assert not hass.data[DECONZ_DOMAIN]