This commit is contained in:
Paulus Schoutsen 2022-08-25 15:51:17 -04:00 committed by GitHub
commit f2e177a5af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 332 additions and 73 deletions

View File

@ -90,6 +90,7 @@ class AladdinDevice(CoverEntity):
self._number = device["door_number"]
self._name = device["name"]
self._serial = device["serial"]
self._model = device["model"]
self._attr_unique_id = f"{self._device_id}-{self._number}"
self._attr_has_entity_name = True
@ -97,9 +98,10 @@ class AladdinDevice(CoverEntity):
def device_info(self) -> DeviceInfo | None:
"""Device information for Aladdin Connect cover."""
return DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
name=self._name,
manufacturer="Overhead Door",
model=self._model,
)
async def async_added_to_hass(self) -> None:

View File

@ -12,3 +12,4 @@ class DoorDevice(TypedDict):
name: str
status: str
serial: str
model: str

View File

@ -56,6 +56,15 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_rssi_status,
),
AccSensorEntityDescription(
key="ble_strength",
name="BLE Strength",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_ble_strength,
),
)
@ -89,22 +98,26 @@ class AladdinConnectSensor(SensorEntity):
device: DoorDevice,
description: AccSensorEntityDescription,
) -> None:
"""Initialize a sensor for an Abode device."""
"""Initialize a sensor for an Aladdin Connect device."""
self._device_id = device["device_id"]
self._number = device["door_number"]
self._name = device["name"]
self._model = device["model"]
self._acc = acc
self.entity_description = description
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
self._attr_has_entity_name = True
if self._model == "01" and description.key in ("battery_level", "ble_strength"):
self._attr_entity_registry_enabled_default = True
@property
def device_info(self) -> DeviceInfo | None:
"""Device information for Aladdin Connect sensors."""
return DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
name=self._name,
manufacturer="Overhead Door",
model=self._model,
)
@property

View File

@ -48,7 +48,7 @@ def get_alarm_system_id_for_unique_id(
gateway: DeconzGateway, unique_id: str
) -> str | None:
"""Retrieve alarm system ID the unique ID is registered to."""
for alarm_system in gateway.api.alarmsystems.values():
for alarm_system in gateway.api.alarm_systems.values():
if unique_id in alarm_system.devices:
return alarm_system.resource_id
return None
@ -123,27 +123,27 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity):
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
if code:
await self.gateway.api.alarmsystems.arm(
await self.gateway.api.alarm_systems.arm(
self.alarm_system_id, AlarmSystemArmAction.AWAY, code
)
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
if code:
await self.gateway.api.alarmsystems.arm(
await self.gateway.api.alarm_systems.arm(
self.alarm_system_id, AlarmSystemArmAction.STAY, code
)
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
if code:
await self.gateway.api.alarmsystems.arm(
await self.gateway.api.alarm_systems.arm(
self.alarm_system_id, AlarmSystemArmAction.NIGHT, code
)
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
if code:
await self.gateway.api.alarmsystems.arm(
await self.gateway.api.alarm_systems.arm(
self.alarm_system_id, AlarmSystemArmAction.DISARM, code
)

View File

@ -159,7 +159,7 @@ ENTITY_DESCRIPTIONS = {
],
}
BINARY_SENSOR_DESCRIPTIONS = [
COMMON_BINARY_SENSOR_DESCRIPTIONS = [
DeconzBinarySensorDescription(
key="tampered",
value_fn=lambda device: device.tampered,
@ -215,7 +215,8 @@ async def async_setup_entry(
sensor = gateway.api.sensors[sensor_id]
for description in (
ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS
ENTITY_DESCRIPTIONS.get(type(sensor), [])
+ COMMON_BINARY_SENSOR_DESCRIPTIONS
):
if (
not hasattr(sensor, description.key)
@ -284,8 +285,8 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity):
if self._device.on is not None:
attr[ATTR_ON] = self._device.on
if self._device.secondary_temperature is not None:
attr[ATTR_TEMPERATURE] = self._device.secondary_temperature
if self._device.internal_temperature is not None:
attr[ATTR_TEMPERATURE] = self._device.internal_temperature
if isinstance(self._device, Presence):

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any, cast
from pydeconz.interfaces.lights import CoverAction
from pydeconz.models import ResourceType
from pydeconz.models.event import EventType
from pydeconz.models.light.cover import Cover
@ -23,9 +24,9 @@ from .deconz_device import DeconzDevice
from .gateway import DeconzGateway, get_gateway_from_config_entry
DECONZ_TYPE_TO_DEVICE_CLASS = {
"Level controllable output": CoverDeviceClass.DAMPER,
"Window covering controller": CoverDeviceClass.SHADE,
"Window covering device": CoverDeviceClass.SHADE,
ResourceType.LEVEL_CONTROLLABLE_OUTPUT.value: CoverDeviceClass.DAMPER,
ResourceType.WINDOW_COVERING_CONTROLLER.value: CoverDeviceClass.SHADE,
ResourceType.WINDOW_COVERING_DEVICE.value: CoverDeviceClass.SHADE,
}
@ -72,6 +73,8 @@ class DeconzCover(DeconzDevice, CoverEntity):
self._attr_device_class = DECONZ_TYPE_TO_DEVICE_CLASS.get(cover.type)
self.legacy_mode = cover.type == ResourceType.LEVEL_CONTROLLABLE_OUTPUT.value
@property
def current_cover_position(self) -> int:
"""Return the current position of the cover."""
@ -88,6 +91,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
lift=position,
legacy_mode=self.legacy_mode,
)
async def async_open_cover(self, **kwargs: Any) -> None:
@ -95,6 +99,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
action=CoverAction.OPEN,
legacy_mode=self.legacy_mode,
)
async def async_close_cover(self, **kwargs: Any) -> None:
@ -102,6 +107,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
action=CoverAction.CLOSE,
legacy_mode=self.legacy_mode,
)
async def async_stop_cover(self, **kwargs: Any) -> None:
@ -109,6 +115,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
action=CoverAction.STOP,
legacy_mode=self.legacy_mode,
)
@property
@ -124,6 +131,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
tilt=position,
legacy_mode=self.legacy_mode,
)
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
@ -131,6 +139,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
tilt=0,
legacy_mode=self.legacy_mode,
)
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
@ -138,6 +147,7 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
tilt=100,
legacy_mode=self.legacy_mode,
)
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
@ -145,4 +155,5 @@ class DeconzCover(DeconzDevice, CoverEntity):
await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
action=CoverAction.STOP,
legacy_mode=self.legacy_mode,
)

View File

@ -26,7 +26,7 @@ async def async_get_config_entry_diagnostics(
gateway.api.config.raw, REDACT_DECONZ_CONFIG
)
diag["websocket_state"] = (
gateway.api.websocket.state if gateway.api.websocket else "Unknown"
gateway.api.websocket.state.value if gateway.api.websocket else "Unknown"
)
diag["deconz_ids"] = gateway.deconz_ids
diag["entities"] = gateway.entities
@ -37,7 +37,7 @@ async def async_get_config_entry_diagnostics(
}
for event in gateway.events
}
diag["alarm_systems"] = {k: v.raw for k, v in gateway.api.alarmsystems.items()}
diag["alarm_systems"] = {k: v.raw for k, v in gateway.api.alarm_systems.items()}
diag["groups"] = {k: v.raw for k, v in gateway.api.groups.items()}
diag["lights"] = {k: v.raw for k, v in gateway.api.lights.items()}
diag["scenes"] = {k: v.raw for k, v in gateway.api.scenes.items()}

View File

@ -169,7 +169,7 @@ class DeconzGateway:
)
)
for device_id in deconz_device_interface:
for device_id in sorted(deconz_device_interface, key=int):
async_add_device(EventType.ADDED, device_id)
initializing = False

View File

@ -3,7 +3,7 @@
"name": "deCONZ",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/deconz",
"requirements": ["pydeconz==102"],
"requirements": ["pydeconz==104"],
"ssdp": [
{
"manufacturer": "Royal Philips Electronics",

View File

@ -6,7 +6,7 @@ from collections.abc import Callable
from dataclasses import dataclass
from pydeconz.models.event import EventType
from pydeconz.models.sensor.presence import PRESENCE_DELAY, Presence
from pydeconz.models.sensor.presence import Presence
from homeassistant.components.number import (
DOMAIN,
@ -42,7 +42,7 @@ ENTITY_DESCRIPTIONS = {
key="delay",
value_fn=lambda device: device.delay,
suffix="Delay",
update_key=PRESENCE_DELAY,
update_key="delay",
native_max_value=65535,
native_min_value=0,
native_step=1,

View File

@ -209,7 +209,7 @@ ENTITY_DESCRIPTIONS = {
}
SENSOR_DESCRIPTIONS = [
COMMON_SENSOR_DESCRIPTIONS = [
DeconzSensorDescription(
key="battery",
value_fn=lambda device: device.battery,
@ -221,8 +221,8 @@ SENSOR_DESCRIPTIONS = [
entity_category=EntityCategory.DIAGNOSTIC,
),
DeconzSensorDescription(
key="secondary_temperature",
value_fn=lambda device: device.secondary_temperature,
key="internal_temperature",
value_fn=lambda device: device.internal_temperature,
suffix="Temperature",
update_key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
@ -253,7 +253,7 @@ async def async_setup_entry(
known_entities = set(gateway.entities[DOMAIN])
for description in (
ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS
ENTITY_DESCRIPTIONS.get(type(sensor), []) + COMMON_SENSOR_DESCRIPTIONS
):
if (
not hasattr(sensor, description.key)
@ -342,8 +342,8 @@ class DeconzSensor(DeconzDevice, SensorEntity):
if self._device.on is not None:
attr[ATTR_ON] = self._device.on
if self._device.secondary_temperature is not None:
attr[ATTR_TEMPERATURE] = self._device.secondary_temperature
if self._device.internal_temperature is not None:
attr[ATTR_TEMPERATURE] = self._device.internal_temperature
if isinstance(self._device, Consumption):
attr[ATTR_POWER] = self._device.power
@ -384,14 +384,16 @@ class DeconzBatteryTracker:
self.sensor = gateway.api.sensors[sensor_id]
self.gateway = gateway
self.async_add_entities = async_add_entities
self.unsub = self.sensor.subscribe(self.async_update_callback)
self.unsubscribe = self.sensor.subscribe(self.async_update_callback)
@callback
def async_update_callback(self) -> None:
"""Update the device's state."""
if "battery" in self.sensor.changed_keys:
self.unsub()
self.unsubscribe()
known_entities = set(self.gateway.entities[DOMAIN])
entity = DeconzSensor(self.sensor, self.gateway, SENSOR_DESCRIPTIONS[0])
entity = DeconzSensor(
self.sensor, self.gateway, COMMON_SENSOR_DESCRIPTIONS[0]
)
if entity.unique_id not in known_entities:
self.async_add_entities([entity])

View File

@ -22,6 +22,7 @@ from homeassistant.const import (
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
ENERGY_WATT_HOUR,
FREQUENCY_HERTZ,
POWER_WATT,
)
from homeassistant.core import HomeAssistant, callback
@ -252,6 +253,7 @@ SENSOR_UNIT_MAPPING = {
"A": ELECTRIC_CURRENT_AMPERE,
"V": ELECTRIC_POTENTIAL_VOLT,
"°": DEGREE,
"Hz": FREQUENCY_HERTZ,
}

View File

@ -25,6 +25,7 @@ class GoodweNumberEntityDescriptionBase:
getter: Callable[[Inverter], Awaitable[int]]
setter: Callable[[Inverter, int], Awaitable[None]]
filter: Callable[[Inverter], bool]
@dataclass
@ -35,17 +36,33 @@ class GoodweNumberEntityDescription(
NUMBERS = (
# non DT inverters (limit in W)
GoodweNumberEntityDescription(
key="grid_export_limit",
name="Grid export limit",
icon="mdi:transmission-tower",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=POWER_WATT,
getter=lambda inv: inv.get_grid_export_limit(),
setter=lambda inv, val: inv.set_grid_export_limit(val),
native_step=100,
native_min_value=0,
native_max_value=10000,
getter=lambda inv: inv.get_grid_export_limit(),
setter=lambda inv, val: inv.set_grid_export_limit(val),
filter=lambda inv: type(inv).__name__ != "DT",
),
# DT inverters (limit is in %)
GoodweNumberEntityDescription(
key="grid_export_limit",
name="Grid export limit",
icon="mdi:transmission-tower",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
native_step=1,
native_min_value=0,
native_max_value=100,
getter=lambda inv: inv.get_grid_export_limit(),
setter=lambda inv, val: inv.set_grid_export_limit(val),
filter=lambda inv: type(inv).__name__ == "DT",
),
GoodweNumberEntityDescription(
key="battery_discharge_depth",
@ -53,11 +70,12 @@ NUMBERS = (
icon="mdi:battery-arrow-down",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
getter=lambda inv: inv.get_ongrid_battery_dod(),
setter=lambda inv, val: inv.set_ongrid_battery_dod(val),
native_step=1,
native_min_value=0,
native_max_value=99,
getter=lambda inv: inv.get_ongrid_battery_dod(),
setter=lambda inv, val: inv.set_ongrid_battery_dod(val),
filter=lambda inv: True,
),
)
@ -73,7 +91,7 @@ async def async_setup_entry(
entities = []
for description in NUMBERS:
for description in filter(lambda dsc: dsc.filter(inverter), NUMBERS):
try:
current_value = await description.getter(inverter)
except (InverterError, ValueError):
@ -82,7 +100,7 @@ async def async_setup_entry(
continue
entities.append(
InverterNumberEntity(device_info, description, inverter, current_value),
InverterNumberEntity(device_info, description, inverter, current_value)
)
async_add_entities(entities)

View File

@ -8,11 +8,15 @@ DEFAULT_PLANT_ID = "0"
DEFAULT_NAME = "Growatt"
SERVER_URLS = [
"https://server.growatt.com/",
"https://server-api.growatt.com/",
"https://server-us.growatt.com/",
"http://server.smten.com/",
]
DEPRECATED_URLS = [
"https://server.growatt.com/",
]
DEFAULT_URL = SERVER_URLS[0]
DOMAIN = "growatt_server"

View File

@ -19,6 +19,7 @@ from .const import (
CONF_PLANT_ID,
DEFAULT_PLANT_ID,
DEFAULT_URL,
DEPRECATED_URLS,
DOMAIN,
LOGIN_INVALID_AUTH_CODE,
)
@ -62,12 +63,23 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Growatt sensor."""
config = config_entry.data
config = {**config_entry.data}
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
url = config.get(CONF_URL, DEFAULT_URL)
name = config[CONF_NAME]
# If the URL has been deprecated then change to the default instead
if url in DEPRECATED_URLS:
_LOGGER.info(
"URL: %s has been deprecated, migrating to the latest default: %s",
url,
DEFAULT_URL,
)
url = DEFAULT_URL
config[CONF_URL] = url
hass.config_entries.async_update_entry(config_entry, data=config)
api = growattServer.GrowattApi()
api.server_url = url

View File

@ -3,7 +3,7 @@
"name": "KNX",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/knx",
"requirements": ["xknx==1.0.0"],
"requirements": ["xknx==1.0.1"],
"codeowners": ["@Julius2342", "@farmio", "@marvin-w"],
"quality_scale": "platinum",
"iot_class": "local_push",

View File

@ -13,7 +13,7 @@ from homeassistant.const import (
CONF_NAME,
LENGTH_MILLIMETERS,
PRESSURE_HPA,
SPEED_METERS_PER_SECOND,
SPEED_KILOMETERS_PER_HOUR,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
@ -58,7 +58,7 @@ class MetEireannWeather(CoordinatorEntity, WeatherEntity):
_attr_native_precipitation_unit = LENGTH_MILLIMETERS
_attr_native_pressure_unit = PRESSURE_HPA
_attr_native_temperature_unit = TEMP_CELSIUS
_attr_native_wind_speed_unit = SPEED_METERS_PER_SECOND
_attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
def __init__(self, coordinator, config, hourly):
"""Initialise the platform with a data instance and site."""

View File

@ -188,7 +188,10 @@ class BlockSleepingClimate(
def hvac_mode(self) -> HVACMode:
"""HVAC current mode."""
if self.device_block is None:
return HVACMode(self.last_state.state) if self.last_state else HVACMode.OFF
if self.last_state and self.last_state.state in list(HVACMode):
return HVACMode(self.last_state.state)
return HVACMode.OFF
if self.device_block.mode is None or self._check_is_off():
return HVACMode.OFF

View File

@ -2,7 +2,7 @@
"domain": "switchbot",
"name": "SwitchBot",
"documentation": "https://www.home-assistant.io/integrations/switchbot",
"requirements": ["PySwitchbot==0.18.10"],
"requirements": ["PySwitchbot==0.18.14"],
"config_flow": true,
"dependencies": ["bluetooth"],
"codeowners": [

View File

@ -4,15 +4,15 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zha",
"requirements": [
"bellows==0.32.0",
"bellows==0.33.1",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.78",
"zigpy-deconz==0.18.0",
"zigpy==0.49.1",
"zigpy==0.50.2",
"zigpy-xbee==0.15.0",
"zigpy-zigate==0.9.1",
"zigpy-znp==0.8.1"
"zigpy-zigate==0.9.2",
"zigpy-znp==0.8.2"
],
"usb": [
{

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 8
PATCH_VERSION: Final = "6"
PATCH_VERSION: Final = "7"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2022.8.6"
version = "2022.8.7"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"

View File

@ -37,7 +37,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1
# homeassistant.components.switchbot
PySwitchbot==0.18.10
PySwitchbot==0.18.14
# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1
@ -396,7 +396,7 @@ beautifulsoup4==4.11.1
# beewi_smartclim==0.0.10
# homeassistant.components.zha
bellows==0.32.0
bellows==0.33.1
# homeassistant.components.bmw_connected_drive
bimmer_connected==0.10.2
@ -1455,7 +1455,7 @@ pydaikin==2.7.0
pydanfossair==0.1.0
# homeassistant.components.deconz
pydeconz==102
pydeconz==104
# homeassistant.components.delijn
pydelijn==1.0.0
@ -2473,7 +2473,7 @@ xboxapi==2.0.1
xiaomi-ble==0.6.4
# homeassistant.components.knx
xknx==1.0.0
xknx==1.0.1
# homeassistant.components.bluesound
# homeassistant.components.fritz
@ -2529,13 +2529,13 @@ zigpy-deconz==0.18.0
zigpy-xbee==0.15.0
# homeassistant.components.zha
zigpy-zigate==0.9.1
zigpy-zigate==0.9.2
# homeassistant.components.zha
zigpy-znp==0.8.1
zigpy-znp==0.8.2
# homeassistant.components.zha
zigpy==0.49.1
zigpy==0.50.2
# homeassistant.components.zoneminder
zm-py==0.5.2

View File

@ -33,7 +33,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1
# homeassistant.components.switchbot
PySwitchbot==0.18.10
PySwitchbot==0.18.14
# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1
@ -320,7 +320,7 @@ base36==0.1.1
beautifulsoup4==4.11.1
# homeassistant.components.zha
bellows==0.32.0
bellows==0.33.1
# homeassistant.components.bmw_connected_drive
bimmer_connected==0.10.2
@ -1001,7 +1001,7 @@ pycoolmasternet-async==0.1.2
pydaikin==2.7.0
# homeassistant.components.deconz
pydeconz==102
pydeconz==104
# homeassistant.components.dexcom
pydexcom==0.2.3
@ -1665,7 +1665,7 @@ xbox-webapi==2.0.11
xiaomi-ble==0.6.4
# homeassistant.components.knx
xknx==1.0.0
xknx==1.0.1
# homeassistant.components.bluesound
# homeassistant.components.fritz
@ -1703,13 +1703,13 @@ zigpy-deconz==0.18.0
zigpy-xbee==0.15.0
# homeassistant.components.zha
zigpy-zigate==0.9.1
zigpy-zigate==0.9.2
# homeassistant.components.zha
zigpy-znp==0.8.1
zigpy-znp==0.8.2
# homeassistant.components.zha
zigpy==0.49.1
zigpy==0.50.2
# homeassistant.components.zwave_js
zwave-js-server-python==0.39.0

View File

@ -11,6 +11,7 @@ DEVICE_CONFIG_OPEN = {
"status": "open",
"link_status": "Connected",
"serial": "12345",
"model": "02",
}
@ -31,6 +32,8 @@ def fixture_mock_aladdinconnect_api():
mock_opener.get_battery_status.return_value = "99"
mock_opener.async_get_rssi_status = AsyncMock(return_value="-55")
mock_opener.get_rssi_status.return_value = "-55"
mock_opener.async_get_ble_strength = AsyncMock(return_value="-45")
mock_opener.get_ble_strength.return_value = "-45"
mock_opener.get_doors = AsyncMock(return_value=[DEVICE_CONFIG_OPEN])
mock_opener.register_callback = mock.Mock(return_value=True)

View File

@ -1,6 +1,6 @@
"""Test the Aladdin Connect Sensors."""
from datetime import timedelta
from unittest.mock import MagicMock, patch
from unittest.mock import AsyncMock, MagicMock, patch
from homeassistant.components.aladdin_connect.const import DOMAIN
from homeassistant.components.aladdin_connect.cover import SCAN_INTERVAL
@ -10,6 +10,17 @@ from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed
DEVICE_CONFIG_MODEL_01 = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "closed",
"link_status": "Connected",
"serial": "12345",
"model": "01",
}
CONFIG = {"username": "test-user", "password": "test-password"}
RELOAD_AFTER_UPDATE_DELAY = timedelta(seconds=31)
@ -83,3 +94,71 @@ async def test_sensors(
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state
async def test_sensors_model_01(
hass: HomeAssistant,
mock_aladdinconnect_api: MagicMock,
) -> None:
"""Test Sensors for AladdinConnect."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id="test-id",
)
config_entry.add_to_hass(hass)
await hass.async_block_till_done()
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
mock_aladdinconnect_api.get_doors = AsyncMock(
return_value=[DEVICE_CONFIG_MODEL_01]
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
registry = entity_registry.async_get(hass)
entry = registry.async_get("sensor.home_battery_level")
assert entry
assert entry.disabled is False
assert entry.disabled_by is None
state = hass.states.get("sensor.home_battery_level")
assert state
entry = registry.async_get("sensor.home_wi_fi_rssi")
await hass.async_block_till_done()
assert entry
assert entry.disabled
assert entry.disabled_by is entity_registry.RegistryEntryDisabler.INTEGRATION
update_entry = registry.async_update_entity(
entry.entity_id, **{"disabled_by": None}
)
await hass.async_block_till_done()
assert update_entry != entry
assert update_entry.disabled is False
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state is None
update_entry = registry.async_update_entity(
entry.entity_id, **{"disabled_by": None}
)
await hass.async_block_till_done()
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state
entry = registry.async_get("sensor.home_ble_strength")
await hass.async_block_till_done()
assert entry
assert entry.disabled is False
assert entry.disabled_by is None
state = hass.states.get("sensor.home_ble_strength")
assert state

View File

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

View File

@ -213,3 +213,111 @@ async def test_tilt_cover(hass, aioclient_mock):
blocking=True,
)
assert aioclient_mock.mock_calls[4][2] == {"stop": True}
async def test_level_controllable_output_cover(hass, aioclient_mock):
"""Test that tilting a cover works."""
data = {
"lights": {
"0": {
"etag": "4cefc909134c8e99086b55273c2bde67",
"hascolor": False,
"lastannounced": "2022-08-08T12:06:18Z",
"lastseen": "2022-08-14T14:22Z",
"manufacturername": "Keen Home Inc",
"modelid": "SV01-410-MP-1.0",
"name": "Vent",
"state": {
"alert": "none",
"bri": 242,
"on": False,
"reachable": True,
"sat": 10,
},
"swversion": "0x00000012",
"type": "Level controllable output",
"uniqueid": "00:22:a3:00:00:00:00:00-01",
}
}
}
with patch.dict(DECONZ_WEB_REQUEST, data):
config_entry = await setup_deconz_integration(hass, aioclient_mock)
assert len(hass.states.async_all()) == 1
covering_device = hass.states.get("cover.vent")
assert covering_device.state == STATE_OPEN
assert covering_device.attributes[ATTR_CURRENT_TILT_POSITION] == 97
# Verify service calls for tilting cover
mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state")
# Service open cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: "cover.vent"},
blocking=True,
)
assert aioclient_mock.mock_calls[1][2] == {"on": False}
# Service close cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.vent"},
blocking=True,
)
assert aioclient_mock.mock_calls[2][2] == {"on": True}
# Service set cover position
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_ENTITY_ID: "cover.vent", ATTR_POSITION: 40},
blocking=True,
)
assert aioclient_mock.mock_calls[3][2] == {"bri": 152}
# Service set tilt cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_TILT_POSITION,
{ATTR_ENTITY_ID: "cover.vent", ATTR_TILT_POSITION: 40},
blocking=True,
)
assert aioclient_mock.mock_calls[4][2] == {"sat": 152}
# Service open tilt cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER_TILT,
{ATTR_ENTITY_ID: "cover.vent"},
blocking=True,
)
assert aioclient_mock.mock_calls[5][2] == {"sat": 0}
# Service close tilt cover
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER_TILT,
{ATTR_ENTITY_ID: "cover.vent"},
blocking=True,
)
assert aioclient_mock.mock_calls[6][2] == {"sat": 254}
# Service stop cover movement
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER_TILT,
{ATTR_ENTITY_ID: "cover.vent"},
blocking=True,
)
assert aioclient_mock.mock_calls[7][2] == {"bri_inc": 0}

View File

@ -1,6 +1,6 @@
"""Test deCONZ diagnostics."""
from pydeconz.websocket import STATE_RUNNING
from pydeconz.websocket import State
from homeassistant.components.deconz.const import CONF_MASTER_GATEWAY
from homeassistant.components.diagnostics import REDACTED
@ -17,7 +17,7 @@ async def test_entry_diagnostics(
"""Test config entry diagnostics."""
config_entry = await setup_deconz_integration(hass, aioclient_mock)
await mock_deconz_websocket(state=STATE_RUNNING)
await mock_deconz_websocket(state=State.RUNNING)
await hass.async_block_till_done()
assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {
@ -44,7 +44,7 @@ async def test_entry_diagnostics(
"uuid": "1234",
"websocketport": 1234,
},
"websocket_state": STATE_RUNNING,
"websocket_state": State.RUNNING.value,
"deconz_ids": {},
"entities": {
str(Platform.ALARM_CONTROL_PANEL): [],

View File

@ -5,7 +5,7 @@ from copy import deepcopy
from unittest.mock import patch
import pydeconz
from pydeconz.websocket import STATE_RETRYING, STATE_RUNNING
from pydeconz.websocket import State
import pytest
from homeassistant.components import ssdp
@ -223,12 +223,12 @@ async def test_connection_status_signalling(
assert hass.states.get("binary_sensor.presence").state == STATE_OFF
await mock_deconz_websocket(state=STATE_RETRYING)
await mock_deconz_websocket(state=State.RETRYING)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.presence").state == STATE_UNAVAILABLE
await mock_deconz_websocket(state=STATE_RUNNING)
await mock_deconz_websocket(state=State.RUNNING)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.presence").state == STATE_OFF