mirror of
https://github.com/home-assistant/core.git
synced 2025-07-09 22:37:11 +00:00
2023.9.2 (#100223)
This commit is contained in:
commit
c6ed235010
@ -5,25 +5,35 @@ import logging
|
||||
|
||||
from airthings_ble import AirthingsDevice
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
Platform,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_BLUETOOTH,
|
||||
DeviceInfo,
|
||||
async_get as device_async_get,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
RegistryEntry,
|
||||
async_entries_for_device,
|
||||
async_get as entity_async_get,
|
||||
)
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
@ -107,9 +117,43 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None:
|
||||
"""Migrate entities to new unique ids (with BLE Address)."""
|
||||
ent_reg = entity_async_get(hass)
|
||||
unique_id_trailer = f"_{sensor_name}"
|
||||
new_unique_id = f"{address}{unique_id_trailer}"
|
||||
if ent_reg.async_get_entity_id(DOMAIN, Platform.SENSOR, new_unique_id):
|
||||
# New unique id already exists
|
||||
return
|
||||
dev_reg = device_async_get(hass)
|
||||
if not (
|
||||
device := dev_reg.async_get_device(
|
||||
connections={(CONNECTION_BLUETOOTH, address)}
|
||||
)
|
||||
):
|
||||
return
|
||||
entities = async_entries_for_device(
|
||||
ent_reg,
|
||||
device_id=device.id,
|
||||
include_disabled_entities=True,
|
||||
)
|
||||
matching_reg_entry: RegistryEntry | None = None
|
||||
for entry in entities:
|
||||
if entry.unique_id.endswith(unique_id_trailer) and (
|
||||
not matching_reg_entry or "(" not in entry.unique_id
|
||||
):
|
||||
matching_reg_entry = entry
|
||||
if not matching_reg_entry:
|
||||
return
|
||||
entity_id = matching_reg_entry.entity_id
|
||||
ent_reg.async_update_entity(entity_id=entity_id, new_unique_id=new_unique_id)
|
||||
_LOGGER.debug("Migrated entity '%s' to unique id '%s'", entity_id, new_unique_id)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: config_entries.ConfigEntry,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Airthings BLE sensors."""
|
||||
@ -137,6 +181,7 @@ async def async_setup_entry(
|
||||
sensor_value,
|
||||
)
|
||||
continue
|
||||
async_migrate(hass, coordinator.data.address, sensor_type)
|
||||
entities.append(
|
||||
AirthingsSensor(coordinator, coordinator.data, sensors_mapping[sensor_type])
|
||||
)
|
||||
@ -165,7 +210,7 @@ class AirthingsSensor(
|
||||
if identifier := airthings_device.identifier:
|
||||
name += f" ({identifier})"
|
||||
|
||||
self._attr_unique_id = f"{name}_{entity_description.key}"
|
||||
self._attr_unique_id = f"{airthings_device.address}_{entity_description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={
|
||||
(
|
||||
|
@ -14,10 +14,10 @@
|
||||
],
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"bleak==0.21.0",
|
||||
"bleak==0.21.1",
|
||||
"bleak-retry-connector==3.1.3",
|
||||
"bluetooth-adapters==0.16.1",
|
||||
"bluetooth-auto-recovery==1.2.2",
|
||||
"bluetooth-auto-recovery==1.2.3",
|
||||
"bluetooth-data-tools==1.11.0",
|
||||
"dbus-fast==1.95.2"
|
||||
]
|
||||
|
@ -47,7 +47,6 @@ from .const import (
|
||||
CONF_FILTER,
|
||||
CONF_GOOGLE_ACTIONS,
|
||||
CONF_RELAYER_SERVER,
|
||||
CONF_REMOTE_SNI_SERVER,
|
||||
CONF_REMOTESTATE_SERVER,
|
||||
CONF_SERVICEHANDLERS_SERVER,
|
||||
CONF_THINGTALK_SERVER,
|
||||
@ -115,7 +114,6 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_ALEXA_SERVER): str,
|
||||
vol.Optional(CONF_CLOUDHOOK_SERVER): str,
|
||||
vol.Optional(CONF_RELAYER_SERVER): str,
|
||||
vol.Optional(CONF_REMOTE_SNI_SERVER): str,
|
||||
vol.Optional(CONF_REMOTESTATE_SERVER): str,
|
||||
vol.Optional(CONF_THINGTALK_SERVER): str,
|
||||
vol.Optional(CONF_SERVICEHANDLERS_SERVER): str,
|
||||
|
@ -55,7 +55,6 @@ CONF_ACME_SERVER = "acme_server"
|
||||
CONF_ALEXA_SERVER = "alexa_server"
|
||||
CONF_CLOUDHOOK_SERVER = "cloudhook_server"
|
||||
CONF_RELAYER_SERVER = "relayer_server"
|
||||
CONF_REMOTE_SNI_SERVER = "remote_sni_server"
|
||||
CONF_REMOTESTATE_SERVER = "remotestate_server"
|
||||
CONF_THINGTALK_SERVER = "thingtalk_server"
|
||||
CONF_SERVICEHANDLERS_SERVER = "servicehandlers_server"
|
||||
|
@ -8,5 +8,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["hass_nabucasa"],
|
||||
"requirements": ["hass-nabucasa==0.70.0"]
|
||||
"requirements": ["hass-nabucasa==0.71.0"]
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ class ComelitSerialBridge(DataUpdateCoordinator):
|
||||
raise ConfigEntryAuthFailed
|
||||
|
||||
devices_data = await self.api.get_all_devices()
|
||||
alarm_data = await self.api.get_alarm_config()
|
||||
await self.api.logout()
|
||||
|
||||
return devices_data | alarm_data
|
||||
return devices_data
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HOP_SCAN_INTERVAL = timedelta(hours=2)
|
||||
HOP_SCAN_INTERVAL = timedelta(minutes=20)
|
||||
|
||||
|
||||
class ElectricKiwiHOPDataCoordinator(DataUpdateCoordinator[Hop]):
|
||||
|
@ -3,7 +3,7 @@
|
||||
"flow_title": "{serial} ({host})",
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "For firmware version 7.0 and later, enter the Enphase cloud credentials, for older models models, enter username `installer` without a password.",
|
||||
"description": "For firmware version 7.0 and later, enter the Enphase cloud credentials, for older models, enter username `installer` without a password.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
|
@ -1096,7 +1096,7 @@ class FritzBoxBaseEntity:
|
||||
class FritzRequireKeysMixin:
|
||||
"""Fritz entity description mix in."""
|
||||
|
||||
value_fn: Callable[[FritzStatus, Any], Any]
|
||||
value_fn: Callable[[FritzStatus, Any], Any] | None
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -1118,9 +1118,12 @@ class FritzBoxBaseCoordinatorEntity(update_coordinator.CoordinatorEntity[AvmWrap
|
||||
) -> None:
|
||||
"""Init device info class."""
|
||||
super().__init__(avm_wrapper)
|
||||
self.async_on_remove(
|
||||
avm_wrapper.register_entity_updates(description.key, description.value_fn)
|
||||
)
|
||||
if description.value_fn is not None:
|
||||
self.async_on_remove(
|
||||
avm_wrapper.register_entity_updates(
|
||||
description.key, description.value_fn
|
||||
)
|
||||
)
|
||||
self.entity_description = description
|
||||
self._device_name = device_name
|
||||
self._attr_unique_id = f"{avm_wrapper.unique_id}-{description.key}"
|
||||
|
@ -1,20 +1,31 @@
|
||||
"""Support for AVM FRITZ!Box update platform."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
|
||||
from homeassistant.components.update import (
|
||||
UpdateEntity,
|
||||
UpdateEntityDescription,
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .common import AvmWrapper, FritzBoxBaseEntity
|
||||
from .common import AvmWrapper, FritzBoxBaseCoordinatorEntity, FritzEntityDescription
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FritzUpdateEntityDescription(UpdateEntityDescription, FritzEntityDescription):
|
||||
"""Describes Fritz update entity."""
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
@ -27,11 +38,13 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class FritzBoxUpdateEntity(FritzBoxBaseEntity, UpdateEntity):
|
||||
class FritzBoxUpdateEntity(FritzBoxBaseCoordinatorEntity, UpdateEntity):
|
||||
"""Mixin for update entity specific attributes."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_supported_features = UpdateEntityFeature.INSTALL
|
||||
_attr_title = "FRITZ!OS"
|
||||
entity_description: FritzUpdateEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -39,29 +52,30 @@ class FritzBoxUpdateEntity(FritzBoxBaseEntity, UpdateEntity):
|
||||
device_friendly_name: str,
|
||||
) -> None:
|
||||
"""Init FRITZ!Box connectivity class."""
|
||||
self._attr_name = f"{device_friendly_name} FRITZ!OS"
|
||||
self._attr_unique_id = f"{avm_wrapper.unique_id}-update"
|
||||
super().__init__(avm_wrapper, device_friendly_name)
|
||||
description = FritzUpdateEntityDescription(
|
||||
key="update", name="FRITZ!OS", value_fn=None
|
||||
)
|
||||
super().__init__(avm_wrapper, device_friendly_name, description)
|
||||
|
||||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""Version currently in use."""
|
||||
return self._avm_wrapper.current_firmware
|
||||
return self.coordinator.current_firmware
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str | None:
|
||||
"""Latest version available for install."""
|
||||
if self._avm_wrapper.update_available:
|
||||
return self._avm_wrapper.latest_firmware
|
||||
return self._avm_wrapper.current_firmware
|
||||
if self.coordinator.update_available:
|
||||
return self.coordinator.latest_firmware
|
||||
return self.coordinator.current_firmware
|
||||
|
||||
@property
|
||||
def release_url(self) -> str | None:
|
||||
"""URL to the full release notes of the latest version available."""
|
||||
return self._avm_wrapper.release_url
|
||||
return self.coordinator.release_url
|
||||
|
||||
async def async_install(
|
||||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
) -> None:
|
||||
"""Install an update."""
|
||||
await self._avm_wrapper.async_trigger_firmware_update()
|
||||
await self.coordinator.async_trigger_firmware_update()
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20230908.0"]
|
||||
"requirements": ["home-assistant-frontend==20230911.0"]
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ from contextlib import suppress
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
import voluptuous as vol
|
||||
@ -149,10 +150,12 @@ SERVICE_BACKUP_PARTIAL = "backup_partial"
|
||||
SERVICE_RESTORE_FULL = "restore_full"
|
||||
SERVICE_RESTORE_PARTIAL = "restore_partial"
|
||||
|
||||
VALID_ADDON_SLUG = vol.Match(re.compile(r"^[-_.A-Za-z0-9]+$"))
|
||||
|
||||
|
||||
def valid_addon(value: Any) -> str:
|
||||
"""Validate value is a valid addon slug."""
|
||||
value = cv.slug(value)
|
||||
value = VALID_ADDON_SLUG(value)
|
||||
|
||||
hass: HomeAssistant | None = None
|
||||
with suppress(HomeAssistantError):
|
||||
|
@ -116,8 +116,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
name = discovery_info.name.replace(f".{zctype}", "")
|
||||
tls = zctype == "_ipps._tcp.local."
|
||||
base_path = discovery_info.properties.get("rp", "ipp/print")
|
||||
|
||||
self.context.update({"title_placeholders": {"name": name}})
|
||||
unique_id = discovery_info.properties.get("UUID")
|
||||
|
||||
self.discovery_info.update(
|
||||
{
|
||||
@ -127,10 +126,18 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_VERIFY_SSL: False,
|
||||
CONF_BASE_PATH: f"/{base_path}",
|
||||
CONF_NAME: name,
|
||||
CONF_UUID: discovery_info.properties.get("UUID"),
|
||||
CONF_UUID: unique_id,
|
||||
}
|
||||
)
|
||||
|
||||
if unique_id:
|
||||
# If we already have the unique id, try to set it now
|
||||
# so we can avoid probing the device if its already
|
||||
# configured or ignored
|
||||
await self._async_set_unique_id_and_abort_if_already_configured(unique_id)
|
||||
|
||||
self.context.update({"title_placeholders": {"name": name}})
|
||||
|
||||
try:
|
||||
info = await validate_input(self.hass, self.discovery_info)
|
||||
except IPPConnectionUpgradeRequired:
|
||||
@ -147,7 +154,6 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.debug("IPP Error", exc_info=True)
|
||||
return self.async_abort(reason="ipp_error")
|
||||
|
||||
unique_id = self.discovery_info[CONF_UUID]
|
||||
if not unique_id and info[CONF_UUID]:
|
||||
_LOGGER.debug(
|
||||
"Printer UUID is missing from discovery info. Falling back to IPP UUID"
|
||||
@ -164,18 +170,24 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"Unable to determine unique id from discovery info and IPP response"
|
||||
)
|
||||
|
||||
if unique_id:
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={
|
||||
CONF_HOST: self.discovery_info[CONF_HOST],
|
||||
CONF_NAME: self.discovery_info[CONF_NAME],
|
||||
},
|
||||
)
|
||||
if unique_id and self.unique_id != unique_id:
|
||||
await self._async_set_unique_id_and_abort_if_already_configured(unique_id)
|
||||
|
||||
await self._async_handle_discovery_without_unique_id()
|
||||
return await self.async_step_zeroconf_confirm()
|
||||
|
||||
async def _async_set_unique_id_and_abort_if_already_configured(
|
||||
self, unique_id: str
|
||||
) -> None:
|
||||
"""Set the unique ID and abort if already configured."""
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={
|
||||
CONF_HOST: self.discovery_info[CONF_HOST],
|
||||
CONF_NAME: self.discovery_info[CONF_NAME],
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_zeroconf_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["usb"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/landisgyr_heat_meter",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["ultraheat-api==0.5.1"]
|
||||
"requirements": ["ultraheat-api==0.5.7"]
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ from homeassistant.helpers.event import async_call_later, async_track_time_inter
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import (
|
||||
ACTIVE_SCAN_INTERVAL,
|
||||
CALL_TYPE_COIL,
|
||||
CALL_TYPE_DISCRETE,
|
||||
CALL_TYPE_REGISTER_HOLDING,
|
||||
@ -116,8 +115,9 @@ class BasePlatform(Entity):
|
||||
def async_run(self) -> None:
|
||||
"""Remote start entity."""
|
||||
self.async_hold(update=False)
|
||||
if self._scan_interval == 0 or self._scan_interval > ACTIVE_SCAN_INTERVAL:
|
||||
self._cancel_call = async_call_later(self.hass, 1, self.async_update)
|
||||
self._cancel_call = async_call_later(
|
||||
self.hass, timedelta(milliseconds=100), self.async_update
|
||||
)
|
||||
if self._scan_interval > 0:
|
||||
self._cancel_timer = async_track_time_interval(
|
||||
self.hass, self.async_update, timedelta(seconds=self._scan_interval)
|
||||
@ -188,10 +188,14 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
||||
registers.reverse()
|
||||
return registers
|
||||
|
||||
def __process_raw_value(self, entry: float | int | str) -> float | int | str | None:
|
||||
def __process_raw_value(
|
||||
self, entry: float | int | str | bytes
|
||||
) -> float | int | str | bytes | None:
|
||||
"""Process value from sensor with NaN handling, scaling, offset, min/max etc."""
|
||||
if self._nan_value and entry in (self._nan_value, -self._nan_value):
|
||||
return None
|
||||
if isinstance(entry, bytes):
|
||||
return entry
|
||||
val: float | int = self._scale * entry + self._offset
|
||||
if self._min_value is not None and val < self._min_value:
|
||||
return self._min_value
|
||||
@ -232,14 +236,20 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
||||
if isinstance(v_temp, int) and self._precision == 0:
|
||||
v_result.append(str(v_temp))
|
||||
elif v_temp is None:
|
||||
v_result.append("") # pragma: no cover
|
||||
v_result.append("0")
|
||||
elif v_temp != v_temp: # noqa: PLR0124
|
||||
# NaN float detection replace with None
|
||||
v_result.append("nan") # pragma: no cover
|
||||
v_result.append("0")
|
||||
else:
|
||||
v_result.append(f"{float(v_temp):.{self._precision}f}")
|
||||
return ",".join(map(str, v_result))
|
||||
|
||||
# NaN float detection replace with None
|
||||
if val[0] != val[0]: # noqa: PLR0124
|
||||
return None
|
||||
if byte_string == b"nan\x00":
|
||||
return None
|
||||
|
||||
# Apply scale, precision, limits to floats and ints
|
||||
val_result = self.__process_raw_value(val[0])
|
||||
|
||||
@ -249,15 +259,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
||||
|
||||
if val_result is None:
|
||||
return None
|
||||
# NaN float detection replace with None
|
||||
if val_result != val_result: # noqa: PLR0124
|
||||
return None # pragma: no cover
|
||||
if isinstance(val_result, int) and self._precision == 0:
|
||||
return str(val_result)
|
||||
if isinstance(val_result, str):
|
||||
if val_result == "nan":
|
||||
val_result = None # pragma: no cover
|
||||
return val_result
|
||||
if isinstance(val_result, bytes):
|
||||
return val_result.decode()
|
||||
return f"{float(val_result):.{self._precision}f}"
|
||||
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pymodbus"],
|
||||
"quality_scale": "gold",
|
||||
"requirements": ["pymodbus==3.5.1"]
|
||||
"requirements": ["pymodbus==3.5.2"]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Support for Modbus Register sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@ -19,6 +19,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
@ -106,12 +107,16 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
||||
"""Update the state of the sensor."""
|
||||
# remark "now" is a dummy parameter to avoid problems with
|
||||
# async_track_time_interval
|
||||
self._cancel_call = None
|
||||
raw_result = await self._hub.async_pb_call(
|
||||
self._slave, self._address, self._count, self._input_type
|
||||
)
|
||||
if raw_result is None:
|
||||
if self._lazy_errors:
|
||||
self._lazy_errors -= 1
|
||||
self._cancel_call = async_call_later(
|
||||
self.hass, timedelta(seconds=1), self.async_update
|
||||
)
|
||||
return
|
||||
self._lazy_errors = self._lazy_error_count
|
||||
self._attr_available = False
|
||||
|
@ -1135,6 +1135,11 @@ class MqttEntity(
|
||||
elif not self._default_to_device_class_name():
|
||||
# Assign the default name
|
||||
self._attr_name = self._default_name
|
||||
elif hasattr(self, "_attr_name"):
|
||||
# An entity name was not set in the config
|
||||
# don't set the name attribute and derive
|
||||
# the name from the device_class
|
||||
delattr(self, "_attr_name")
|
||||
if CONF_DEVICE in config:
|
||||
device_name: str
|
||||
if CONF_NAME not in config[CONF_DEVICE]:
|
||||
|
@ -7,6 +7,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["crcmod", "plugwise"],
|
||||
"requirements": ["plugwise==0.31.9"],
|
||||
"requirements": ["plugwise==0.32.2"],
|
||||
"zeroconf": ["_plugwise._tcp.local."]
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ from .entity import PlugwiseEntity
|
||||
class PlugwiseEntityDescriptionMixin:
|
||||
"""Mixin values for Plugwise entities."""
|
||||
|
||||
command: Callable[[Smile, str, float], Awaitable[None]]
|
||||
command: Callable[[Smile, str, str, float], Awaitable[None]]
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -43,7 +43,9 @@ NUMBER_TYPES = (
|
||||
PlugwiseNumberEntityDescription(
|
||||
key="maximum_boiler_temperature",
|
||||
translation_key="maximum_boiler_temperature",
|
||||
command=lambda api, number, value: api.set_number_setpoint(number, value),
|
||||
command=lambda api, number, dev_id, value: api.set_number_setpoint(
|
||||
number, dev_id, value
|
||||
),
|
||||
device_class=NumberDeviceClass.TEMPERATURE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
@ -51,7 +53,9 @@ NUMBER_TYPES = (
|
||||
PlugwiseNumberEntityDescription(
|
||||
key="max_dhw_temperature",
|
||||
translation_key="max_dhw_temperature",
|
||||
command=lambda api, number, value: api.set_number_setpoint(number, value),
|
||||
command=lambda api, number, dev_id, value: api.set_number_setpoint(
|
||||
number, dev_id, value
|
||||
),
|
||||
device_class=NumberDeviceClass.TEMPERATURE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
@ -94,6 +98,7 @@ class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity):
|
||||
) -> None:
|
||||
"""Initiate Plugwise Number."""
|
||||
super().__init__(coordinator, device_id)
|
||||
self.device_id = device_id
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{device_id}-{description.key}"
|
||||
self._attr_mode = NumberMode.BOX
|
||||
@ -109,6 +114,6 @@ class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity):
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Change to the new setpoint value."""
|
||||
await self.entity_description.command(
|
||||
self.coordinator.api, self.entity_description.key, value
|
||||
self.coordinator.api, self.entity_description.key, self.device_id, value
|
||||
)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/python_script",
|
||||
"loggers": ["RestrictedPython"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["RestrictedPython==6.1"]
|
||||
"requirements": ["RestrictedPython==6.2"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": ["python-roborock==0.32.3"]
|
||||
"requirements": ["python-roborock==0.33.2"]
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ class SomaTilt(SomaEntity, CoverEntity):
|
||||
| CoverEntityFeature.STOP_TILT
|
||||
| CoverEntityFeature.SET_TILT_POSITION
|
||||
)
|
||||
CLOSED_UP_THRESHOLD = 80
|
||||
CLOSED_DOWN_THRESHOLD = 20
|
||||
|
||||
@property
|
||||
def current_cover_tilt_position(self) -> int:
|
||||
@ -60,7 +62,12 @@ class SomaTilt(SomaEntity, CoverEntity):
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
"""Return if the cover tilt is closed."""
|
||||
return self.current_position == 0
|
||||
if (
|
||||
self.current_position < self.CLOSED_DOWN_THRESHOLD
|
||||
or self.current_position > self.CLOSED_UP_THRESHOLD
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def close_cover_tilt(self, **kwargs: Any) -> None:
|
||||
"""Close the cover tilt."""
|
||||
|
@ -10,6 +10,6 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["systembridgeconnector"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["systembridgeconnector==3.4.9"],
|
||||
"requirements": ["systembridgeconnector==3.8.2"],
|
||||
"zeroconf": ["_system-bridge._tcp.local."]
|
||||
}
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["hatasmota"],
|
||||
"mqtt": ["tasmota/discovery/#"],
|
||||
"requirements": ["HATasmota==0.7.1"]
|
||||
"requirements": ["HATasmota==0.7.3"]
|
||||
}
|
||||
|
@ -88,12 +88,10 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
|
||||
hc.SENSOR_COLOR_GREEN: {ICON: "mdi:palette"},
|
||||
hc.SENSOR_COLOR_RED: {ICON: "mdi:palette"},
|
||||
hc.SENSOR_CURRENT: {
|
||||
ICON: "mdi:alpha-a-circle-outline",
|
||||
DEVICE_CLASS: SensorDeviceClass.CURRENT,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
hc.SENSOR_CURRENTNEUTRAL: {
|
||||
ICON: "mdi:alpha-a-circle-outline",
|
||||
DEVICE_CLASS: SensorDeviceClass.CURRENT,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
@ -103,11 +101,14 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
hc.SENSOR_DISTANCE: {
|
||||
ICON: "mdi:leak",
|
||||
DEVICE_CLASS: SensorDeviceClass.DISTANCE,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
hc.SENSOR_ECO2: {ICON: "mdi:molecule-co2"},
|
||||
hc.SENSOR_ENERGY: {
|
||||
DEVICE_CLASS: SensorDeviceClass.ENERGY,
|
||||
STATE_CLASS: SensorStateClass.TOTAL,
|
||||
},
|
||||
hc.SENSOR_FREQUENCY: {
|
||||
DEVICE_CLASS: SensorDeviceClass.FREQUENCY,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
@ -122,10 +123,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
hc.SENSOR_STATUS_IP: {ICON: "mdi:ip-network"},
|
||||
hc.SENSOR_STATUS_LINK_COUNT: {ICON: "mdi:counter"},
|
||||
hc.SENSOR_MOISTURE: {
|
||||
DEVICE_CLASS: SensorDeviceClass.MOISTURE,
|
||||
ICON: "mdi:cup-water",
|
||||
},
|
||||
hc.SENSOR_MOISTURE: {DEVICE_CLASS: SensorDeviceClass.MOISTURE},
|
||||
hc.SENSOR_STATUS_MQTT_COUNT: {ICON: "mdi:counter"},
|
||||
hc.SENSOR_PB0_3: {ICON: "mdi:flask"},
|
||||
hc.SENSOR_PB0_5: {ICON: "mdi:flask"},
|
||||
@ -146,7 +144,6 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
hc.SENSOR_POWERFACTOR: {
|
||||
ICON: "mdi:alpha-f-circle-outline",
|
||||
DEVICE_CLASS: SensorDeviceClass.POWER_FACTOR,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
@ -162,7 +159,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
|
||||
DEVICE_CLASS: SensorDeviceClass.PRESSURE,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
hc.SENSOR_PROXIMITY: {DEVICE_CLASS: SensorDeviceClass.DISTANCE, ICON: "mdi:ruler"},
|
||||
hc.SENSOR_PROXIMITY: {ICON: "mdi:ruler"},
|
||||
hc.SENSOR_REACTIVE_ENERGYEXPORT: {STATE_CLASS: SensorStateClass.TOTAL},
|
||||
hc.SENSOR_REACTIVE_ENERGYIMPORT: {STATE_CLASS: SensorStateClass.TOTAL},
|
||||
hc.SENSOR_REACTIVE_POWERUSAGE: {
|
||||
@ -195,11 +192,10 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = {
|
||||
hc.SENSOR_TOTAL_START_TIME: {ICON: "mdi:progress-clock"},
|
||||
hc.SENSOR_TVOC: {ICON: "mdi:air-filter"},
|
||||
hc.SENSOR_VOLTAGE: {
|
||||
ICON: "mdi:alpha-v-circle-outline",
|
||||
DEVICE_CLASS: SensorDeviceClass.VOLTAGE,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
hc.SENSOR_WEIGHT: {
|
||||
ICON: "mdi:scale",
|
||||
DEVICE_CLASS: SensorDeviceClass.WEIGHT,
|
||||
STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
@ -220,7 +216,6 @@ SENSOR_UNIT_MAP = {
|
||||
hc.LIGHT_LUX: LIGHT_LUX,
|
||||
hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS,
|
||||
hc.PERCENTAGE: PERCENTAGE,
|
||||
hc.POWER_FACTOR: None,
|
||||
hc.POWER_WATT: UnitOfPower.WATT,
|
||||
hc.PRESSURE_HPA: UnitOfPressure.HPA,
|
||||
hc.REACTIVE_POWER: POWER_VOLT_AMPERE_REACTIVE,
|
||||
|
@ -23,8 +23,7 @@ class TriggerEntity(TriggerBaseEntity, CoordinatorEntity[TriggerUpdateCoordinato
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle being added to Home Assistant."""
|
||||
await TriggerBaseEntity.async_added_to_hass(self)
|
||||
await CoordinatorEntity.async_added_to_hass(self) # type: ignore[arg-type]
|
||||
await super().async_added_to_hass()
|
||||
if self.coordinator.data is not None:
|
||||
self._process_data()
|
||||
|
||||
|
@ -139,7 +139,7 @@ class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
||||
"""Device tracker local functions."""
|
||||
|
||||
heartbeat_timedelta_fn: Callable[[UniFiController, str], timedelta]
|
||||
ip_address_fn: Callable[[aiounifi.Controller, str], str]
|
||||
ip_address_fn: Callable[[aiounifi.Controller, str], str | None]
|
||||
is_connected_fn: Callable[[UniFiController, str], bool]
|
||||
hostname_fn: Callable[[aiounifi.Controller, str], str | None]
|
||||
|
||||
@ -249,7 +249,7 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
|
||||
return self.entity_description.hostname_fn(self.controller.api, self._obj_id)
|
||||
|
||||
@property
|
||||
def ip_address(self) -> str:
|
||||
def ip_address(self) -> str | None:
|
||||
"""Return the primary ip address of the device."""
|
||||
return self.entity_description.ip_address_fn(self.controller.api, self._obj_id)
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiounifi"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiounifi==61"],
|
||||
"requirements": ["aiounifi==62"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiovodafone"],
|
||||
"requirements": ["aiovodafone==0.1.0"]
|
||||
"requirements": ["aiovodafone==0.2.0"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/waze_travel_time",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pywaze", "homeassistant.helpers.location"],
|
||||
"requirements": ["pywaze==0.3.0"]
|
||||
"requirements": ["pywaze==0.4.0"]
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Support for Zigbee Home Automation devices."""
|
||||
import asyncio
|
||||
import contextlib
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
@ -33,13 +34,16 @@ from .core.const import (
|
||||
CONF_ZIGPY,
|
||||
DATA_ZHA,
|
||||
DATA_ZHA_CONFIG,
|
||||
DATA_ZHA_DEVICE_TRIGGER_CACHE,
|
||||
DATA_ZHA_GATEWAY,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
SIGNAL_ADD_ENTITIES,
|
||||
RadioType,
|
||||
)
|
||||
from .core.device import get_device_automation_triggers
|
||||
from .core.discovery import GROUP_PROBE
|
||||
from .radio_manager import ZhaRadioManager
|
||||
|
||||
DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({vol.Optional(CONF_TYPE): cv.string})
|
||||
ZHA_CONFIG_SCHEMA = {
|
||||
@ -134,9 +138,43 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
else:
|
||||
_LOGGER.debug("ZHA storage file does not exist or was already removed")
|
||||
|
||||
# Re-use the gateway object between ZHA reloads
|
||||
if (zha_gateway := zha_data.get(DATA_ZHA_GATEWAY)) is None:
|
||||
zha_gateway = ZHAGateway(hass, config, config_entry)
|
||||
# Load and cache device trigger information early
|
||||
zha_data.setdefault(DATA_ZHA_DEVICE_TRIGGER_CACHE, {})
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
radio_mgr = ZhaRadioManager.from_config_entry(hass, config_entry)
|
||||
|
||||
async with radio_mgr.connect_zigpy_app() as app:
|
||||
for dev in app.devices.values():
|
||||
dev_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, str(dev.ieee))},
|
||||
connections={(dr.CONNECTION_ZIGBEE, str(dev.ieee))},
|
||||
)
|
||||
|
||||
if dev_entry is None:
|
||||
continue
|
||||
|
||||
zha_data[DATA_ZHA_DEVICE_TRIGGER_CACHE][dev_entry.id] = (
|
||||
str(dev.ieee),
|
||||
get_device_automation_triggers(dev),
|
||||
)
|
||||
|
||||
_LOGGER.debug("Trigger cache: %s", zha_data[DATA_ZHA_DEVICE_TRIGGER_CACHE])
|
||||
|
||||
zha_gateway = ZHAGateway(hass, config, config_entry)
|
||||
|
||||
async def async_zha_shutdown():
|
||||
"""Handle shutdown tasks."""
|
||||
await zha_gateway.shutdown()
|
||||
# clean up any remaining entity metadata
|
||||
# (entities that have been discovered but not yet added to HA)
|
||||
# suppress KeyError because we don't know what state we may
|
||||
# be in when we get here in failure cases
|
||||
with contextlib.suppress(KeyError):
|
||||
for platform in PLATFORMS:
|
||||
del hass.data[DATA_ZHA][platform]
|
||||
|
||||
config_entry.async_on_unload(async_zha_shutdown)
|
||||
|
||||
try:
|
||||
await zha_gateway.async_initialize()
|
||||
@ -155,9 +193,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
|
||||
repairs.async_delete_blocking_issues(hass)
|
||||
|
||||
config_entry.async_on_unload(zha_gateway.shutdown)
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_ZIGBEE, str(zha_gateway.coordinator_ieee))},
|
||||
|
@ -186,6 +186,7 @@ DATA_ZHA = "zha"
|
||||
DATA_ZHA_CONFIG = "config"
|
||||
DATA_ZHA_BRIDGE_ID = "zha_bridge_id"
|
||||
DATA_ZHA_CORE_EVENTS = "zha_core_events"
|
||||
DATA_ZHA_DEVICE_TRIGGER_CACHE = "zha_device_trigger_cache"
|
||||
DATA_ZHA_GATEWAY = "zha_gateway"
|
||||
|
||||
DEBUG_COMP_BELLOWS = "bellows"
|
||||
|
@ -93,6 +93,16 @@ _UPDATE_ALIVE_INTERVAL = (60, 90)
|
||||
_CHECKIN_GRACE_PERIODS = 2
|
||||
|
||||
|
||||
def get_device_automation_triggers(
|
||||
device: zigpy.device.Device,
|
||||
) -> dict[tuple[str, str], dict[str, str]]:
|
||||
"""Get the supported device automation triggers for a zigpy device."""
|
||||
return {
|
||||
("device_offline", "device_offline"): {"device_event_type": "device_offline"},
|
||||
**getattr(device, "device_automation_triggers", {}),
|
||||
}
|
||||
|
||||
|
||||
class DeviceStatus(Enum):
|
||||
"""Status of a device."""
|
||||
|
||||
@ -311,16 +321,7 @@ class ZHADevice(LogMixin):
|
||||
@cached_property
|
||||
def device_automation_triggers(self) -> dict[tuple[str, str], dict[str, str]]:
|
||||
"""Return the device automation triggers for this device."""
|
||||
triggers = {
|
||||
("device_offline", "device_offline"): {
|
||||
"device_event_type": "device_offline"
|
||||
}
|
||||
}
|
||||
|
||||
if hasattr(self._zigpy_device, "device_automation_triggers"):
|
||||
triggers.update(self._zigpy_device.device_automation_triggers)
|
||||
|
||||
return triggers
|
||||
return get_device_automation_triggers(self._zigpy_device)
|
||||
|
||||
@property
|
||||
def available_signal(self) -> str:
|
||||
|
@ -149,12 +149,6 @@ class ZHAGateway:
|
||||
self.config_entry = config_entry
|
||||
self._unsubs: list[Callable[[], None]] = []
|
||||
|
||||
discovery.PROBE.initialize(self._hass)
|
||||
discovery.GROUP_PROBE.initialize(self._hass)
|
||||
|
||||
self.ha_device_registry = dr.async_get(self._hass)
|
||||
self.ha_entity_registry = er.async_get(self._hass)
|
||||
|
||||
def get_application_controller_data(self) -> tuple[ControllerApplication, dict]:
|
||||
"""Get an uninitialized instance of a zigpy `ControllerApplication`."""
|
||||
radio_type = self.config_entry.data[CONF_RADIO_TYPE]
|
||||
@ -197,6 +191,12 @@ class ZHAGateway:
|
||||
|
||||
async def async_initialize(self) -> None:
|
||||
"""Initialize controller and connect radio."""
|
||||
discovery.PROBE.initialize(self._hass)
|
||||
discovery.GROUP_PROBE.initialize(self._hass)
|
||||
|
||||
self.ha_device_registry = dr.async_get(self._hass)
|
||||
self.ha_entity_registry = er.async_get(self._hass)
|
||||
|
||||
app_controller_cls, app_config = self.get_application_controller_data()
|
||||
self.application_controller = await app_controller_cls.new(
|
||||
config=app_config,
|
||||
@ -204,23 +204,6 @@ class ZHAGateway:
|
||||
start_radio=False,
|
||||
)
|
||||
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
|
||||
|
||||
self.async_load_devices()
|
||||
|
||||
# Groups are attached to the coordinator device so we need to load it early
|
||||
coordinator = self._find_coordinator_device()
|
||||
loaded_groups = False
|
||||
|
||||
# We can only load groups early if the coordinator's model info has been stored
|
||||
# in the zigpy database
|
||||
if coordinator.model is not None:
|
||||
self.coordinator_zha_device = self._async_get_or_create_device(
|
||||
coordinator, restored=True
|
||||
)
|
||||
self.async_load_groups()
|
||||
loaded_groups = True
|
||||
|
||||
for attempt in range(STARTUP_RETRIES):
|
||||
try:
|
||||
await self.application_controller.startup(auto_form=True)
|
||||
@ -242,14 +225,15 @@ class ZHAGateway:
|
||||
else:
|
||||
break
|
||||
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(self.coordinator_ieee)
|
||||
|
||||
self.coordinator_zha_device = self._async_get_or_create_device(
|
||||
self._find_coordinator_device(), restored=True
|
||||
)
|
||||
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(self.coordinator_ieee)
|
||||
|
||||
# If ZHA groups could not load early, we can safely load them now
|
||||
if not loaded_groups:
|
||||
self.async_load_groups()
|
||||
self.async_load_devices()
|
||||
self.async_load_groups()
|
||||
|
||||
self.application_controller.add_listener(self)
|
||||
self.application_controller.groups.add_listener(self)
|
||||
@ -766,7 +750,15 @@ class ZHAGateway:
|
||||
unsubscribe()
|
||||
for device in self.devices.values():
|
||||
device.async_cleanup_handles()
|
||||
await self.application_controller.shutdown()
|
||||
# shutdown is called when the config entry unloads are processed
|
||||
# there are cases where unloads are processed because of a failure of
|
||||
# some sort and the application controller may not have been
|
||||
# created yet
|
||||
if (
|
||||
hasattr(self, "application_controller")
|
||||
and self.application_controller is not None
|
||||
):
|
||||
await self.application_controller.shutdown()
|
||||
|
||||
def handle_message(
|
||||
self,
|
||||
|
@ -9,12 +9,12 @@ from homeassistant.components.device_automation.exceptions import (
|
||||
from homeassistant.components.homeassistant.triggers import event as event_trigger
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, IntegrationError
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import DOMAIN as ZHA_DOMAIN
|
||||
from .core.const import ZHA_EVENT
|
||||
from .core.const import DATA_ZHA, DATA_ZHA_DEVICE_TRIGGER_CACHE, ZHA_EVENT
|
||||
from .core.helpers import async_get_zha_device
|
||||
|
||||
CONF_SUBTYPE = "subtype"
|
||||
@ -26,21 +26,32 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
def _get_device_trigger_data(hass: HomeAssistant, device_id: str) -> tuple[str, dict]:
|
||||
"""Get device trigger data for a device, falling back to the cache if possible."""
|
||||
|
||||
# First, try checking to see if the device itself is accessible
|
||||
try:
|
||||
zha_device = async_get_zha_device(hass, device_id)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return str(zha_device.ieee), zha_device.device_automation_triggers
|
||||
|
||||
# If not, check the trigger cache but allow any `KeyError`s to propagate
|
||||
return hass.data[DATA_ZHA][DATA_ZHA_DEVICE_TRIGGER_CACHE][device_id]
|
||||
|
||||
|
||||
async def async_validate_trigger_config(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
|
||||
# Trigger validation will not occur if the config entry is not loaded
|
||||
_, triggers = _get_device_trigger_data(hass, config[CONF_DEVICE_ID])
|
||||
|
||||
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||
try:
|
||||
zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID])
|
||||
except (KeyError, AttributeError, IntegrationError) as err:
|
||||
raise InvalidDeviceAutomationConfig from err
|
||||
if (
|
||||
zha_device.device_automation_triggers is None
|
||||
or trigger not in zha_device.device_automation_triggers
|
||||
):
|
||||
if trigger not in triggers:
|
||||
raise InvalidDeviceAutomationConfig(f"device does not have trigger {trigger}")
|
||||
|
||||
return config
|
||||
@ -53,26 +64,26 @@ async def async_attach_trigger(
|
||||
trigger_info: TriggerInfo,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Listen for state changes based on configuration."""
|
||||
trigger_key: tuple[str, str] = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||
|
||||
try:
|
||||
zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID])
|
||||
except (KeyError, AttributeError) as err:
|
||||
ieee, triggers = _get_device_trigger_data(hass, config[CONF_DEVICE_ID])
|
||||
except KeyError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Unable to get zha device {config[CONF_DEVICE_ID]}"
|
||||
) from err
|
||||
|
||||
if trigger_key not in zha_device.device_automation_triggers:
|
||||
trigger_key: tuple[str, str] = (config[CONF_TYPE], config[CONF_SUBTYPE])
|
||||
|
||||
if trigger_key not in triggers:
|
||||
raise HomeAssistantError(f"Unable to find trigger {trigger_key}")
|
||||
|
||||
trigger = zha_device.device_automation_triggers[trigger_key]
|
||||
|
||||
event_config = {
|
||||
event_trigger.CONF_PLATFORM: "event",
|
||||
event_trigger.CONF_EVENT_TYPE: ZHA_EVENT,
|
||||
event_trigger.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger},
|
||||
}
|
||||
|
||||
event_config = event_trigger.TRIGGER_SCHEMA(event_config)
|
||||
event_config = event_trigger.TRIGGER_SCHEMA(
|
||||
{
|
||||
event_trigger.CONF_PLATFORM: "event",
|
||||
event_trigger.CONF_EVENT_TYPE: ZHA_EVENT,
|
||||
event_trigger.CONF_EVENT_DATA: {DEVICE_IEEE: ieee, **triggers[trigger_key]},
|
||||
}
|
||||
)
|
||||
return await event_trigger.async_attach_trigger(
|
||||
hass, event_config, action, trigger_info, platform_type="device"
|
||||
)
|
||||
@ -83,24 +94,20 @@ async def async_get_triggers(
|
||||
) -> list[dict[str, str]]:
|
||||
"""List device triggers.
|
||||
|
||||
Make sure the device supports device automations and
|
||||
if it does return the trigger list.
|
||||
Make sure the device supports device automations and return the trigger list.
|
||||
"""
|
||||
zha_device = async_get_zha_device(hass, device_id)
|
||||
try:
|
||||
_, triggers = _get_device_trigger_data(hass, device_id)
|
||||
except KeyError as err:
|
||||
raise InvalidDeviceAutomationConfig from err
|
||||
|
||||
if not zha_device.device_automation_triggers:
|
||||
return []
|
||||
|
||||
triggers = []
|
||||
for trigger, subtype in zha_device.device_automation_triggers:
|
||||
triggers.append(
|
||||
{
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: ZHA_DOMAIN,
|
||||
CONF_PLATFORM: DEVICE,
|
||||
CONF_TYPE: trigger,
|
||||
CONF_SUBTYPE: subtype,
|
||||
}
|
||||
)
|
||||
|
||||
return triggers
|
||||
return [
|
||||
{
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: ZHA_DOMAIN,
|
||||
CONF_PLATFORM: DEVICE,
|
||||
CONF_TYPE: trigger,
|
||||
CONF_SUBTYPE: subtype,
|
||||
}
|
||||
for trigger, subtype in triggers
|
||||
]
|
||||
|
@ -25,9 +25,9 @@
|
||||
"pyserial==3.5",
|
||||
"pyserial-asyncio==0.6",
|
||||
"zha-quirks==0.0.103",
|
||||
"zigpy-deconz==0.21.0",
|
||||
"zigpy-deconz==0.21.1",
|
||||
"zigpy==0.57.1",
|
||||
"zigpy-xbee==0.18.1",
|
||||
"zigpy-xbee==0.18.2",
|
||||
"zigpy-zigate==0.11.0",
|
||||
"zigpy-znp==0.11.4",
|
||||
"universal-silabs-flasher==0.0.13"
|
||||
|
@ -8,7 +8,7 @@ import copy
|
||||
import enum
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
from typing import Any, Self
|
||||
|
||||
from bellows.config import CONF_USE_THREAD
|
||||
import voluptuous as vol
|
||||
@ -127,8 +127,21 @@ class ZhaRadioManager:
|
||||
self.backups: list[zigpy.backups.NetworkBackup] = []
|
||||
self.chosen_backup: zigpy.backups.NetworkBackup | None = None
|
||||
|
||||
@classmethod
|
||||
def from_config_entry(
|
||||
cls, hass: HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||
) -> Self:
|
||||
"""Create an instance from a config entry."""
|
||||
mgr = cls()
|
||||
mgr.hass = hass
|
||||
mgr.device_path = config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
|
||||
mgr.device_settings = config_entry.data[CONF_DEVICE]
|
||||
mgr.radio_type = RadioType[config_entry.data[CONF_RADIO_TYPE]]
|
||||
|
||||
return mgr
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def _connect_zigpy_app(self) -> ControllerApplication:
|
||||
async def connect_zigpy_app(self) -> ControllerApplication:
|
||||
"""Connect to the radio with the current config and then clean up."""
|
||||
assert self.radio_type is not None
|
||||
|
||||
@ -155,10 +168,9 @@ class ZhaRadioManager:
|
||||
)
|
||||
|
||||
try:
|
||||
await app.connect()
|
||||
yield app
|
||||
finally:
|
||||
await app.disconnect()
|
||||
await app.shutdown()
|
||||
await asyncio.sleep(CONNECT_DELAY_S)
|
||||
|
||||
async def restore_backup(
|
||||
@ -170,7 +182,8 @@ class ZhaRadioManager:
|
||||
):
|
||||
return
|
||||
|
||||
async with self._connect_zigpy_app() as app:
|
||||
async with self.connect_zigpy_app() as app:
|
||||
await app.connect()
|
||||
await app.backups.restore_backup(backup, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
@ -218,7 +231,9 @@ class ZhaRadioManager:
|
||||
"""Connect to the radio and load its current network settings."""
|
||||
backup = None
|
||||
|
||||
async with self._connect_zigpy_app() as app:
|
||||
async with self.connect_zigpy_app() as app:
|
||||
await app.connect()
|
||||
|
||||
# Check if the stick has any settings and load them
|
||||
try:
|
||||
await app.load_network_info()
|
||||
@ -241,12 +256,14 @@ class ZhaRadioManager:
|
||||
|
||||
async def async_form_network(self) -> None:
|
||||
"""Form a brand-new network."""
|
||||
async with self._connect_zigpy_app() as app:
|
||||
async with self.connect_zigpy_app() as app:
|
||||
await app.connect()
|
||||
await app.form_network()
|
||||
|
||||
async def async_reset_adapter(self) -> None:
|
||||
"""Reset the current adapter."""
|
||||
async with self._connect_zigpy_app() as app:
|
||||
async with self.connect_zigpy_app() as app:
|
||||
await app.connect()
|
||||
await app.reset_network_info()
|
||||
|
||||
async def async_restore_backup_step_1(self) -> bool:
|
||||
|
@ -9,7 +9,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["zwave_js_server"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyserial==3.5", "zwave-js-server-python==0.51.1"],
|
||||
"requirements": ["pyserial==3.5", "zwave-js-server-python==0.51.2"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "0658",
|
||||
|
@ -2,7 +2,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
from zwave_js_server.model.node import Node
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||
@ -14,10 +13,10 @@ from .helpers import async_get_node_from_device_id
|
||||
class DeviceConfigFileChangedFlow(RepairsFlow):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
def __init__(self, node: Node, device_name: str) -> None:
|
||||
def __init__(self, data: dict[str, str]) -> None:
|
||||
"""Initialize."""
|
||||
self.node = node
|
||||
self.device_name = device_name
|
||||
self.device_name: str = data["device_name"]
|
||||
self.device_id: str = data["device_id"]
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
@ -30,7 +29,14 @@ class DeviceConfigFileChangedFlow(RepairsFlow):
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is not None:
|
||||
self.hass.async_create_task(self.node.async_refresh_info())
|
||||
try:
|
||||
node = async_get_node_from_device_id(self.hass, self.device_id)
|
||||
except ValueError:
|
||||
return self.async_abort(
|
||||
reason="cannot_connect",
|
||||
description_placeholders={"device_name": self.device_name},
|
||||
)
|
||||
self.hass.async_create_task(node.async_refresh_info())
|
||||
return self.async_create_entry(title="", data={})
|
||||
|
||||
return self.async_show_form(
|
||||
@ -41,15 +47,11 @@ class DeviceConfigFileChangedFlow(RepairsFlow):
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
data: dict[str, str] | None,
|
||||
hass: HomeAssistant, issue_id: str, data: dict[str, str] | None
|
||||
) -> RepairsFlow:
|
||||
"""Create flow."""
|
||||
|
||||
if issue_id.split(".")[0] == "device_config_file_changed":
|
||||
assert data
|
||||
return DeviceConfigFileChangedFlow(
|
||||
async_get_node_from_device_id(hass, data["device_id"]), data["device_name"]
|
||||
)
|
||||
return DeviceConfigFileChangedFlow(data)
|
||||
return ConfirmRepairFlow()
|
||||
|
@ -170,6 +170,9 @@
|
||||
"title": "Z-Wave device configuration file changed: {device_name}",
|
||||
"description": "Z-Wave JS discovers a lot of device metadata by interviewing the device. However, some of the information has to be loaded from a configuration file. Some of this information is only evaluated once, during the device interview.\n\nWhen a device config file is updated, this information may be stale and and the device must be re-interviewed to pick up the changes.\n\n This is not a required operation and device functionality will be impacted during the re-interview process, but you may see improvements for your device once it is complete.\n\nIf you'd like to proceed, click on SUBMIT below. The re-interview will take place in the background."
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"cannot_connect": "Cannot connect to {device_name}. Please try again later after confirming that your Z-Wave network is up and connected to Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ from typing import Final
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 9
|
||||
PATCH_VERSION: Final = "1"
|
||||
PATCH_VERSION: Final = "2"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||
|
@ -9,9 +9,9 @@ attrs==23.1.0
|
||||
awesomeversion==22.9.0
|
||||
bcrypt==4.0.1
|
||||
bleak-retry-connector==3.1.3
|
||||
bleak==0.21.0
|
||||
bleak==0.21.1
|
||||
bluetooth-adapters==0.16.1
|
||||
bluetooth-auto-recovery==1.2.2
|
||||
bluetooth-auto-recovery==1.2.3
|
||||
bluetooth-data-tools==1.11.0
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.3.0
|
||||
@ -19,10 +19,10 @@ cryptography==41.0.3
|
||||
dbus-fast==1.95.2
|
||||
fnv-hash-fast==0.4.1
|
||||
ha-av==10.1.1
|
||||
hass-nabucasa==0.70.0
|
||||
hass-nabucasa==0.71.0
|
||||
hassil==1.2.5
|
||||
home-assistant-bluetooth==1.10.3
|
||||
home-assistant-frontend==20230908.0
|
||||
home-assistant-frontend==20230911.0
|
||||
home-assistant-intents==2023.8.2
|
||||
httpx==0.24.1
|
||||
ifaddr==0.2.0
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.9.1"
|
||||
version = "2023.9.2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -29,7 +29,7 @@ DoorBirdPy==2.1.0
|
||||
HAP-python==4.7.1
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
HATasmota==0.7.1
|
||||
HATasmota==0.7.3
|
||||
|
||||
# homeassistant.components.mastodon
|
||||
Mastodon.py==1.5.1
|
||||
@ -121,7 +121,7 @@ PyXiaomiGateway==0.14.3
|
||||
RachioPy==1.0.3
|
||||
|
||||
# homeassistant.components.python_script
|
||||
RestrictedPython==6.1
|
||||
RestrictedPython==6.2
|
||||
|
||||
# homeassistant.components.remember_the_milk
|
||||
RtmAPI==0.7.2
|
||||
@ -363,13 +363,13 @@ aiosyncthing==0.5.1
|
||||
aiotractive==0.5.6
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==61
|
||||
aiounifi==62
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
|
||||
# homeassistant.components.vodafone_station
|
||||
aiovodafone==0.1.0
|
||||
aiovodafone==0.2.0
|
||||
|
||||
# homeassistant.components.waqi
|
||||
aiowaqi==0.2.1
|
||||
@ -521,7 +521,7 @@ bizkaibus==0.1.1
|
||||
bleak-retry-connector==3.1.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak==0.21.0
|
||||
bleak==0.21.1
|
||||
|
||||
# homeassistant.components.blebox
|
||||
blebox-uniapi==2.1.4
|
||||
@ -543,7 +543,7 @@ bluemaestro-ble==0.2.3
|
||||
bluetooth-adapters==0.16.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-auto-recovery==1.2.2
|
||||
bluetooth-auto-recovery==1.2.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
# homeassistant.components.esphome
|
||||
@ -958,7 +958,7 @@ ha-philipsjs==3.1.0
|
||||
habitipy==0.2.0
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.70.0
|
||||
hass-nabucasa==0.71.0
|
||||
|
||||
# homeassistant.components.splunk
|
||||
hass-splunk==0.1.1
|
||||
@ -994,7 +994,7 @@ hole==0.8.0
|
||||
holidays==0.28
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20230908.0
|
||||
home-assistant-frontend==20230911.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.8.2
|
||||
@ -1438,7 +1438,7 @@ plexauth==0.0.6
|
||||
plexwebsocket==0.0.13
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
plugwise==0.31.9
|
||||
plugwise==0.32.2
|
||||
|
||||
# homeassistant.components.plum_lightpad
|
||||
plumlightpad==0.0.11
|
||||
@ -1851,7 +1851,7 @@ pymitv==1.4.3
|
||||
pymochad==0.2.0
|
||||
|
||||
# homeassistant.components.modbus
|
||||
pymodbus==3.5.1
|
||||
pymodbus==3.5.2
|
||||
|
||||
# homeassistant.components.monoprice
|
||||
pymonoprice==0.4
|
||||
@ -2159,7 +2159,7 @@ python-qbittorrent==0.4.3
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.32.3
|
||||
python-roborock==0.33.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
@ -2231,7 +2231,7 @@ pyvlx==0.2.20
|
||||
pyvolumio==0.1.5
|
||||
|
||||
# homeassistant.components.waze_travel_time
|
||||
pywaze==0.3.0
|
||||
pywaze==0.4.0
|
||||
|
||||
# homeassistant.components.html5
|
||||
pywebpush==1.9.2
|
||||
@ -2504,7 +2504,7 @@ swisshydrodata==0.1.0
|
||||
synology-srm==0.2.0
|
||||
|
||||
# homeassistant.components.system_bridge
|
||||
systembridgeconnector==3.4.9
|
||||
systembridgeconnector==3.8.2
|
||||
|
||||
# homeassistant.components.tailscale
|
||||
tailscale==0.2.0
|
||||
@ -2603,7 +2603,7 @@ twitchAPI==3.10.0
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.1
|
||||
ultraheat-api==0.5.7
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
unifi-discovery==1.1.7
|
||||
@ -2781,10 +2781,10 @@ zhong-hong-hvac==1.0.9
|
||||
ziggo-mediabox-xl==1.1.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-deconz==0.21.0
|
||||
zigpy-deconz==0.21.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-xbee==0.18.1
|
||||
zigpy-xbee==0.18.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-zigate==0.11.0
|
||||
@ -2799,7 +2799,7 @@ zigpy==0.57.1
|
||||
zm-py==0.5.2
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.51.1
|
||||
zwave-js-server-python==0.51.2
|
||||
|
||||
# homeassistant.components.zwave_me
|
||||
zwave-me-ws==0.4.3
|
||||
|
@ -28,7 +28,7 @@ DoorBirdPy==2.1.0
|
||||
HAP-python==4.7.1
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
HATasmota==0.7.1
|
||||
HATasmota==0.7.3
|
||||
|
||||
# homeassistant.components.doods
|
||||
# homeassistant.components.generic
|
||||
@ -108,7 +108,7 @@ PyXiaomiGateway==0.14.3
|
||||
RachioPy==1.0.3
|
||||
|
||||
# homeassistant.components.python_script
|
||||
RestrictedPython==6.1
|
||||
RestrictedPython==6.2
|
||||
|
||||
# homeassistant.components.remember_the_milk
|
||||
RtmAPI==0.7.2
|
||||
@ -338,13 +338,13 @@ aiosyncthing==0.5.1
|
||||
aiotractive==0.5.6
|
||||
|
||||
# homeassistant.components.unifi
|
||||
aiounifi==61
|
||||
aiounifi==62
|
||||
|
||||
# homeassistant.components.vlc_telnet
|
||||
aiovlc==0.1.0
|
||||
|
||||
# homeassistant.components.vodafone_station
|
||||
aiovodafone==0.1.0
|
||||
aiovodafone==0.2.0
|
||||
|
||||
# homeassistant.components.watttime
|
||||
aiowatttime==0.1.1
|
||||
@ -439,7 +439,7 @@ bimmer-connected==0.14.0
|
||||
bleak-retry-connector==3.1.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak==0.21.0
|
||||
bleak==0.21.1
|
||||
|
||||
# homeassistant.components.blebox
|
||||
blebox-uniapi==2.1.4
|
||||
@ -454,7 +454,7 @@ bluemaestro-ble==0.2.3
|
||||
bluetooth-adapters==0.16.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-auto-recovery==1.2.2
|
||||
bluetooth-auto-recovery==1.2.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
# homeassistant.components.esphome
|
||||
@ -753,7 +753,7 @@ ha-philipsjs==3.1.0
|
||||
habitipy==0.2.0
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.70.0
|
||||
hass-nabucasa==0.71.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
hassil==1.2.5
|
||||
@ -777,7 +777,7 @@ hole==0.8.0
|
||||
holidays==0.28
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20230908.0
|
||||
home-assistant-frontend==20230911.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.8.2
|
||||
@ -1086,7 +1086,7 @@ plexauth==0.0.6
|
||||
plexwebsocket==0.0.13
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
plugwise==0.31.9
|
||||
plugwise==0.32.2
|
||||
|
||||
# homeassistant.components.plum_lightpad
|
||||
plumlightpad==0.0.11
|
||||
@ -1370,7 +1370,7 @@ pymeteoclimatic==0.0.6
|
||||
pymochad==0.2.0
|
||||
|
||||
# homeassistant.components.modbus
|
||||
pymodbus==3.5.1
|
||||
pymodbus==3.5.2
|
||||
|
||||
# homeassistant.components.monoprice
|
||||
pymonoprice==0.4
|
||||
@ -1585,7 +1585,7 @@ python-picnic-api==1.1.0
|
||||
python-qbittorrent==0.4.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.32.3
|
||||
python-roborock==0.33.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
@ -1639,7 +1639,7 @@ pyvizio==0.1.61
|
||||
pyvolumio==0.1.5
|
||||
|
||||
# homeassistant.components.waze_travel_time
|
||||
pywaze==0.3.0
|
||||
pywaze==0.4.0
|
||||
|
||||
# homeassistant.components.html5
|
||||
pywebpush==1.9.2
|
||||
@ -1837,7 +1837,7 @@ sunwatcher==0.2.1
|
||||
surepy==0.8.0
|
||||
|
||||
# homeassistant.components.system_bridge
|
||||
systembridgeconnector==3.4.9
|
||||
systembridgeconnector==3.8.2
|
||||
|
||||
# homeassistant.components.tailscale
|
||||
tailscale==0.2.0
|
||||
@ -1903,7 +1903,7 @@ twitchAPI==3.10.0
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.1
|
||||
ultraheat-api==0.5.7
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
unifi-discovery==1.1.7
|
||||
@ -2045,10 +2045,10 @@ zeversolar==0.3.1
|
||||
zha-quirks==0.0.103
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-deconz==0.21.0
|
||||
zigpy-deconz==0.21.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-xbee==0.18.1
|
||||
zigpy-xbee==0.18.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-zigate==0.11.0
|
||||
@ -2060,7 +2060,7 @@ zigpy-znp==0.11.4
|
||||
zigpy==0.57.1
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.51.1
|
||||
zwave-js-server-python==0.51.2
|
||||
|
||||
# homeassistant.components.zwave_me
|
||||
zwave-me-ws==0.4.3
|
||||
|
@ -5,8 +5,11 @@ from unittest.mock import patch
|
||||
|
||||
from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice
|
||||
|
||||
from homeassistant.components.airthings_ble.const import DOMAIN
|
||||
from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH
|
||||
|
||||
from tests.common import MockConfigEntry, MockEntity
|
||||
from tests.components.bluetooth import generate_advertisement_data, generate_ble_device
|
||||
|
||||
|
||||
@ -36,18 +39,52 @@ def patch_airthings_ble(return_value=AirthingsDevice, side_effect=None):
|
||||
)
|
||||
|
||||
|
||||
def patch_airthings_device_update():
|
||||
"""Patch airthings-ble device."""
|
||||
return patch(
|
||||
"homeassistant.components.airthings_ble.AirthingsBluetoothDeviceData.update_device",
|
||||
return_value=WAVE_DEVICE_INFO,
|
||||
)
|
||||
|
||||
|
||||
WAVE_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="cc-cc-cc-cc-cc-cc",
|
||||
address="cc:cc:cc:cc:cc:cc",
|
||||
device=generate_ble_device(
|
||||
address="cc:cc:cc:cc:cc:cc",
|
||||
name="Airthings Wave+",
|
||||
),
|
||||
rssi=-61,
|
||||
manufacturer_data={820: b"\xe4/\xa5\xae\t\x00"},
|
||||
service_data={},
|
||||
service_uuids=["b42e1c08-ade7-11e4-89d3-123b93f75cba"],
|
||||
service_data={
|
||||
# Sensor data
|
||||
"b42e2a68-ade7-11e4-89d3-123b93f75cba": bytearray(
|
||||
b"\x01\x02\x03\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0A"
|
||||
),
|
||||
# Manufacturer
|
||||
"00002a29-0000-1000-8000-00805f9b34fb": bytearray(b"Airthings AS"),
|
||||
# Model
|
||||
"00002a24-0000-1000-8000-00805f9b34fb": bytearray(b"2930"),
|
||||
# Identifier
|
||||
"00002a25-0000-1000-8000-00805f9b34fb": bytearray(b"123456"),
|
||||
# SW Version
|
||||
"00002a26-0000-1000-8000-00805f9b34fb": bytearray(b"G-BLE-1.5.3-master+0"),
|
||||
# HW Version
|
||||
"00002a27-0000-1000-8000-00805f9b34fb": bytearray(b"REV A"),
|
||||
# Command
|
||||
"b42e2d06-ade7-11e4-89d3-123b93f75cba": bytearray(b"\x00"),
|
||||
},
|
||||
service_uuids=[
|
||||
"b42e1c08-ade7-11e4-89d3-123b93f75cba",
|
||||
"b42e2a68-ade7-11e4-89d3-123b93f75cba",
|
||||
"00002a29-0000-1000-8000-00805f9b34fb",
|
||||
"00002a24-0000-1000-8000-00805f9b34fb",
|
||||
"00002a25-0000-1000-8000-00805f9b34fb",
|
||||
"00002a26-0000-1000-8000-00805f9b34fb",
|
||||
"00002a27-0000-1000-8000-00805f9b34fb",
|
||||
"b42e2d06-ade7-11e4-89d3-123b93f75cba",
|
||||
],
|
||||
source="local",
|
||||
device=generate_ble_device(
|
||||
"cc:cc:cc:cc:cc:cc",
|
||||
"cc-cc-cc-cc-cc-cc",
|
||||
),
|
||||
advertisement=generate_advertisement_data(
|
||||
manufacturer_data={820: b"\xe4/\xa5\xae\t\x00"},
|
||||
service_uuids=["b42e1c08-ade7-11e4-89d3-123b93f75cba"],
|
||||
@ -99,3 +136,62 @@ WAVE_DEVICE_INFO = AirthingsDevice(
|
||||
},
|
||||
address="cc:cc:cc:cc:cc:cc",
|
||||
)
|
||||
|
||||
TEMPERATURE_V1 = MockEntity(
|
||||
unique_id="Airthings Wave Plus 123456_temperature",
|
||||
name="Airthings Wave Plus 123456 Temperature",
|
||||
)
|
||||
|
||||
HUMIDITY_V2 = MockEntity(
|
||||
unique_id="Airthings Wave Plus (123456)_humidity",
|
||||
name="Airthings Wave Plus (123456) Humidity",
|
||||
)
|
||||
|
||||
CO2_V1 = MockEntity(
|
||||
unique_id="Airthings Wave Plus 123456_co2",
|
||||
name="Airthings Wave Plus 123456 CO2",
|
||||
)
|
||||
|
||||
CO2_V2 = MockEntity(
|
||||
unique_id="Airthings Wave Plus (123456)_co2",
|
||||
name="Airthings Wave Plus (123456) CO2",
|
||||
)
|
||||
|
||||
VOC_V1 = MockEntity(
|
||||
unique_id="Airthings Wave Plus 123456_voc",
|
||||
name="Airthings Wave Plus 123456 CO2",
|
||||
)
|
||||
|
||||
VOC_V2 = MockEntity(
|
||||
unique_id="Airthings Wave Plus (123456)_voc",
|
||||
name="Airthings Wave Plus (123456) VOC",
|
||||
)
|
||||
|
||||
VOC_V3 = MockEntity(
|
||||
unique_id="cc:cc:cc:cc:cc:cc_voc",
|
||||
name="Airthings Wave Plus (123456) VOC",
|
||||
)
|
||||
|
||||
|
||||
def create_entry(hass):
|
||||
"""Create a config entry."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=WAVE_SERVICE_INFO.address,
|
||||
title="Airthings Wave Plus (123456)",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
return entry
|
||||
|
||||
|
||||
def create_device(hass, entry):
|
||||
"""Create a device for the given entry."""
|
||||
device_registry = hass.helpers.device_registry.async_get(hass)
|
||||
device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
connections={(CONNECTION_BLUETOOTH, WAVE_SERVICE_INFO.address)},
|
||||
manufacturer="Airthings AS",
|
||||
name="Airthings Wave Plus (123456)",
|
||||
model="Wave Plus",
|
||||
)
|
||||
return device
|
||||
|
213
tests/components/airthings_ble/test_sensor.py
Normal file
213
tests/components/airthings_ble/test_sensor.py
Normal file
@ -0,0 +1,213 @@
|
||||
"""Test the Airthings Wave sensor."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.airthings_ble.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.components.airthings_ble import (
|
||||
CO2_V1,
|
||||
CO2_V2,
|
||||
HUMIDITY_V2,
|
||||
TEMPERATURE_V1,
|
||||
VOC_V1,
|
||||
VOC_V2,
|
||||
VOC_V3,
|
||||
WAVE_DEVICE_INFO,
|
||||
WAVE_SERVICE_INFO,
|
||||
create_device,
|
||||
create_entry,
|
||||
patch_airthings_device_update,
|
||||
)
|
||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def test_migration_from_v1_to_v3_unique_id(hass: HomeAssistant):
|
||||
"""Verify that we can migrate from v1 (pre 2023.9.0) to the latest unique id format."""
|
||||
entry = create_entry(hass)
|
||||
device = create_device(hass, entry)
|
||||
|
||||
assert entry is not None
|
||||
assert device is not None
|
||||
|
||||
entity_registry = hass.helpers.entity_registry.async_get(hass)
|
||||
|
||||
sensor = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=TEMPERATURE_V1.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
WAVE_SERVICE_INFO,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch_airthings_device_update():
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) > 0
|
||||
|
||||
assert (
|
||||
entity_registry.async_get(sensor.entity_id).unique_id
|
||||
== WAVE_DEVICE_INFO.address + "_temperature"
|
||||
)
|
||||
|
||||
|
||||
async def test_migration_from_v2_to_v3_unique_id(hass: HomeAssistant):
|
||||
"""Verify that we can migrate from v2 (introduced in 2023.9.0) to the latest unique id format."""
|
||||
entry = create_entry(hass)
|
||||
device = create_device(hass, entry)
|
||||
|
||||
assert entry is not None
|
||||
assert device is not None
|
||||
|
||||
entity_registry = hass.helpers.entity_registry.async_get(hass)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
sensor = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=HUMIDITY_V2.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
WAVE_SERVICE_INFO,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch_airthings_device_update():
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) > 0
|
||||
|
||||
assert (
|
||||
entity_registry.async_get(sensor.entity_id).unique_id
|
||||
== WAVE_DEVICE_INFO.address + "_humidity"
|
||||
)
|
||||
|
||||
|
||||
async def test_migration_from_v1_and_v2_to_v3_unique_id(hass: HomeAssistant):
|
||||
"""Test if migration works when we have both v1 (pre 2023.9.0) and v2 (introduced in 2023.9.0) unique ids."""
|
||||
entry = create_entry(hass)
|
||||
device = create_device(hass, entry)
|
||||
|
||||
assert entry is not None
|
||||
assert device is not None
|
||||
|
||||
entity_registry = hass.helpers.entity_registry.async_get(hass)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
v2 = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=CO2_V2.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
v1 = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=CO2_V1.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
WAVE_SERVICE_INFO,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch_airthings_device_update():
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) > 0
|
||||
|
||||
assert (
|
||||
entity_registry.async_get(v1.entity_id).unique_id
|
||||
== WAVE_DEVICE_INFO.address + "_co2"
|
||||
)
|
||||
assert entity_registry.async_get(v2.entity_id).unique_id == v2.unique_id
|
||||
|
||||
|
||||
async def test_migration_with_all_unique_ids(hass: HomeAssistant):
|
||||
"""Test if migration works when we have all unique ids."""
|
||||
entry = create_entry(hass)
|
||||
device = create_device(hass, entry)
|
||||
|
||||
assert entry is not None
|
||||
assert device is not None
|
||||
|
||||
entity_registry = hass.helpers.entity_registry.async_get(hass)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
v1 = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=VOC_V1.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
v2 = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=VOC_V2.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
v3 = entity_registry.async_get_or_create(
|
||||
domain=DOMAIN,
|
||||
platform="sensor",
|
||||
unique_id=VOC_V3.unique_id,
|
||||
config_entry=entry,
|
||||
device_id=device.id,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
WAVE_SERVICE_INFO,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch_airthings_device_update():
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) > 0
|
||||
|
||||
assert entity_registry.async_get(v1.entity_id).unique_id == v1.unique_id
|
||||
assert entity_registry.async_get(v2.entity_id).unique_id == v2.unique_id
|
||||
assert entity_registry.async_get(v3.entity_id).unique_id == v3.unique_id
|
@ -32,7 +32,6 @@ async def test_constructor_loads_info_from_config(hass: HomeAssistant) -> None:
|
||||
"relayer_server": "test-relayer-server",
|
||||
"accounts_server": "test-acounts-server",
|
||||
"cloudhook_server": "test-cloudhook-server",
|
||||
"remote_sni_server": "test-remote-sni-server",
|
||||
"alexa_server": "test-alexa-server",
|
||||
"acme_server": "test-acme-server",
|
||||
"remotestate_server": "test-remotestate-server",
|
||||
|
@ -8,11 +8,25 @@ from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL, MOCK_USER_DATA
|
||||
from .const import (
|
||||
MOCK_FB_SERVICES,
|
||||
MOCK_FIRMWARE_AVAILABLE,
|
||||
MOCK_FIRMWARE_RELEASE_URL,
|
||||
MOCK_USER_DATA,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
AVAILABLE_UPDATE = {
|
||||
"UserInterface1": {
|
||||
"GetInfo": {
|
||||
"NewX_AVM-DE_Version": MOCK_FIRMWARE_AVAILABLE,
|
||||
"NewX_AVM-DE_InfoURL": MOCK_FIRMWARE_RELEASE_URL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_update_entities_initialized(
|
||||
hass: HomeAssistant,
|
||||
@ -41,23 +55,21 @@ async def test_update_available(
|
||||
) -> None:
|
||||
"""Test update entities."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fritz.common.FritzBoxTools._update_device_info",
|
||||
return_value=(True, MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL),
|
||||
):
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
fc_class_mock().override_services({**MOCK_FB_SERVICES, **AVAILABLE_UPDATE})
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
update = hass.states.get("update.mock_title_fritz_os")
|
||||
assert update is not None
|
||||
assert update.state == "on"
|
||||
assert update.attributes.get("installed_version") == "7.29"
|
||||
assert update.attributes.get("latest_version") == MOCK_FIRMWARE_AVAILABLE
|
||||
assert update.attributes.get("release_url") == MOCK_FIRMWARE_RELEASE_URL
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
update = hass.states.get("update.mock_title_fritz_os")
|
||||
assert update is not None
|
||||
assert update.state == "on"
|
||||
assert update.attributes.get("installed_version") == "7.29"
|
||||
assert update.attributes.get("latest_version") == MOCK_FIRMWARE_AVAILABLE
|
||||
assert update.attributes.get("release_url") == MOCK_FIRMWARE_RELEASE_URL
|
||||
|
||||
|
||||
async def test_no_update_available(
|
||||
@ -90,10 +102,9 @@ async def test_available_update_can_be_installed(
|
||||
) -> None:
|
||||
"""Test update entities."""
|
||||
|
||||
fc_class_mock().override_services({**MOCK_FB_SERVICES, **AVAILABLE_UPDATE})
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fritz.common.FritzBoxTools._update_device_info",
|
||||
return_value=(True, MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL),
|
||||
), patch(
|
||||
"homeassistant.components.fritz.common.FritzBoxTools.async_trigger_firmware_update",
|
||||
return_value=True,
|
||||
) as mocked_update_call:
|
||||
|
@ -633,6 +633,41 @@ async def test_invalid_service_calls(
|
||||
)
|
||||
|
||||
|
||||
async def test_addon_service_call_with_complex_slug(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Addon slugs can have ., - and _, confirm that passes validation."""
|
||||
supervisor_mock_data = {
|
||||
"version_latest": "1.0.0",
|
||||
"version": "1.0.0",
|
||||
"auto_update": True,
|
||||
"addons": [
|
||||
{
|
||||
"name": "test.a_1-2",
|
||||
"slug": "test.a_1-2",
|
||||
"state": "stopped",
|
||||
"update_available": False,
|
||||
"version": "1.0.0",
|
||||
"version_latest": "1.0.0",
|
||||
"repository": "core",
|
||||
"icon": False,
|
||||
},
|
||||
],
|
||||
}
|
||||
with patch.dict(os.environ, MOCK_ENVIRON), patch(
|
||||
"homeassistant.components.hassio.HassIO.is_connected",
|
||||
return_value=None,
|
||||
), patch(
|
||||
"homeassistant.components.hassio.HassIO.get_supervisor_info",
|
||||
return_value=supervisor_mock_data,
|
||||
):
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call("hassio", "addon_start", {"addon": "test.a_1-2"})
|
||||
|
||||
|
||||
async def test_service_calls_core(
|
||||
hassio_env, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
|
@ -23,7 +23,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]:
|
||||
with patch(
|
||||
"bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe
|
||||
), patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app",
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app",
|
||||
return_value=mock_connect_app,
|
||||
), patch(
|
||||
"homeassistant.components.zha.async_setup_entry",
|
||||
|
@ -25,7 +25,7 @@ def mock_zha():
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app",
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app",
|
||||
return_value=mock_connect_app,
|
||||
), patch(
|
||||
"homeassistant.components.zha.async_setup_entry",
|
||||
|
@ -45,7 +45,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]:
|
||||
with patch(
|
||||
"bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe
|
||||
), patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app",
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app",
|
||||
return_value=mock_connect_app,
|
||||
):
|
||||
yield
|
||||
|
@ -23,7 +23,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]:
|
||||
with patch(
|
||||
"bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe
|
||||
), patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app",
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app",
|
||||
return_value=mock_connect_app,
|
||||
), patch(
|
||||
"homeassistant.components.zha.async_setup_entry",
|
||||
|
35
tests/components/ipp/fixtures/printer_without_uuid.json
Normal file
35
tests/components/ipp/fixtures/printer_without_uuid.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"printer-state": "idle",
|
||||
"printer-name": "Test Printer",
|
||||
"printer-location": null,
|
||||
"printer-make-and-model": "Test HA-1000 Series",
|
||||
"printer-device-id": "MFG:TEST;CMD:ESCPL2,BDC,D4,D4PX,ESCPR7,END4,GENEP,URF;MDL:HA-1000 Series;CLS:PRINTER;DES:TEST HA-1000 Series;CID:EpsonRGB;FID:FXN,DPA,WFA,ETN,AFN,DAN,WRA;RID:20;DDS:022500;ELG:1000;SN:555534593035345555;URF:CP1,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7-6,V1.4,MT1-3-7-8-10-11-12;",
|
||||
"printer-uri-supported": [
|
||||
"ipps://192.168.1.31:631/ipp/print",
|
||||
"ipp://192.168.1.31:631/ipp/print"
|
||||
],
|
||||
"uri-authentication-supported": ["none", "none"],
|
||||
"uri-security-supported": ["tls", "none"],
|
||||
"printer-info": "Test HA-1000 Series",
|
||||
"printer-up-time": 30,
|
||||
"printer-firmware-string-version": "20.23.06HA",
|
||||
"printer-more-info": "http://192.168.1.31:80/PRESENTATION/BONJOUR",
|
||||
"marker-names": [
|
||||
"Black ink",
|
||||
"Photo black ink",
|
||||
"Cyan ink",
|
||||
"Yellow ink",
|
||||
"Magenta ink"
|
||||
],
|
||||
"marker-types": [
|
||||
"ink-cartridge",
|
||||
"ink-cartridge",
|
||||
"ink-cartridge",
|
||||
"ink-cartridge",
|
||||
"ink-cartridge"
|
||||
],
|
||||
"marker-colors": ["#000000", "#000000", "#00FFFF", "#FFFF00", "#FF00FF"],
|
||||
"marker-levels": [58, 98, 91, 95, 73],
|
||||
"marker-low-levels": [10, 10, 10, 10, 10],
|
||||
"marker-high-levels": [100, 100, 100, 100, 100]
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
"""Tests for the IPP config flow."""
|
||||
import dataclasses
|
||||
from unittest.mock import MagicMock
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from pyipp import (
|
||||
IPPConnectionError,
|
||||
@ -8,6 +9,7 @@ from pyipp import (
|
||||
IPPError,
|
||||
IPPParseError,
|
||||
IPPVersionNotSupportedError,
|
||||
Printer,
|
||||
)
|
||||
import pytest
|
||||
|
||||
@ -23,7 +25,7 @@ from . import (
|
||||
MOCK_ZEROCONF_IPPS_SERVICE_INFO,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
@ -316,6 +318,31 @@ async def test_zeroconf_with_uuid_device_exists_abort(
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_zeroconf_with_uuid_device_exists_abort_new_host(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_ipp_config_flow: MagicMock,
|
||||
) -> None:
|
||||
"""Test we abort zeroconf flow if printer already configured."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
discovery_info = dataclasses.replace(MOCK_ZEROCONF_IPP_SERVICE_INFO, host="1.2.3.9")
|
||||
discovery_info.properties = {
|
||||
**MOCK_ZEROCONF_IPP_SERVICE_INFO.properties,
|
||||
"UUID": "cfe92100-67c4-11d4-a45f-f8d027761251",
|
||||
}
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=discovery_info,
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert mock_config_entry.data[CONF_HOST] == "1.2.3.9"
|
||||
|
||||
|
||||
async def test_zeroconf_empty_unique_id(
|
||||
hass: HomeAssistant,
|
||||
mock_ipp_config_flow: MagicMock,
|
||||
@ -337,6 +364,21 @@ async def test_zeroconf_empty_unique_id(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "192.168.1.31", CONF_BASE_PATH: "/ipp/print"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "EPSON XP-6000 Series"
|
||||
|
||||
assert result["data"]
|
||||
assert result["data"][CONF_HOST] == "192.168.1.31"
|
||||
assert result["data"][CONF_UUID] == "cfe92100-67c4-11d4-a45f-f8d027761251"
|
||||
|
||||
assert result["result"]
|
||||
assert result["result"].unique_id == "cfe92100-67c4-11d4-a45f-f8d027761251"
|
||||
|
||||
|
||||
async def test_zeroconf_no_unique_id(
|
||||
hass: HomeAssistant,
|
||||
@ -355,6 +397,21 @@ async def test_zeroconf_no_unique_id(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "192.168.1.31", CONF_BASE_PATH: "/ipp/print"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "EPSON XP-6000 Series"
|
||||
|
||||
assert result["data"]
|
||||
assert result["data"][CONF_HOST] == "192.168.1.31"
|
||||
assert result["data"][CONF_UUID] == "cfe92100-67c4-11d4-a45f-f8d027761251"
|
||||
|
||||
assert result["result"]
|
||||
assert result["result"].unique_id == "cfe92100-67c4-11d4-a45f-f8d027761251"
|
||||
|
||||
|
||||
async def test_full_user_flow_implementation(
|
||||
hass: HomeAssistant,
|
||||
@ -448,3 +505,45 @@ async def test_full_zeroconf_tls_flow_implementation(
|
||||
|
||||
assert result["result"]
|
||||
assert result["result"].unique_id == "cfe92100-67c4-11d4-a45f-f8d027761251"
|
||||
|
||||
|
||||
async def test_zeroconf_empty_unique_id_uses_serial(hass: HomeAssistant) -> None:
|
||||
"""Test zeroconf flow if printer lacks (empty) unique identification with serial fallback."""
|
||||
fixture = await hass.async_add_executor_job(
|
||||
load_fixture, "ipp/printer_without_uuid.json"
|
||||
)
|
||||
mock_printer_without_uuid = Printer.from_dict(json.loads(fixture))
|
||||
mock_printer_without_uuid.unique_id = None
|
||||
|
||||
discovery_info = dataclasses.replace(MOCK_ZEROCONF_IPP_SERVICE_INFO)
|
||||
discovery_info.properties = {
|
||||
**MOCK_ZEROCONF_IPP_SERVICE_INFO.properties,
|
||||
"UUID": "",
|
||||
}
|
||||
with patch(
|
||||
"homeassistant.components.ipp.config_flow.IPP", autospec=True
|
||||
) as ipp_mock:
|
||||
client = ipp_mock.return_value
|
||||
client.printer.return_value = mock_printer_without_uuid
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=discovery_info,
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "192.168.1.31", CONF_BASE_PATH: "/ipp/print"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "EPSON XP-6000 Series"
|
||||
|
||||
assert result["data"]
|
||||
assert result["data"][CONF_HOST] == "192.168.1.31"
|
||||
assert result["data"][CONF_UUID] == ""
|
||||
|
||||
assert result["result"]
|
||||
assert result["result"].unique_id == "555534593035345555"
|
||||
|
@ -149,7 +149,7 @@ async def mock_do_cycle_fixture(
|
||||
mock_pymodbus_return,
|
||||
) -> FrozenDateTimeFactory:
|
||||
"""Trigger update call with time_changed event."""
|
||||
freezer.tick(timedelta(seconds=90))
|
||||
freezer.tick(timedelta(seconds=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
return freezer
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""The tests for the Modbus sensor component."""
|
||||
import struct
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
@ -267,7 +269,6 @@ async def test_config_wrong_struct_sensor(
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 51,
|
||||
CONF_SCAN_INTERVAL: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -625,6 +626,21 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("config_addon", "register_words", "do_exception", "expected"),
|
||||
[
|
||||
(
|
||||
{
|
||||
CONF_SLAVE_COUNT: 1,
|
||||
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||
CONF_DATA_TYPE: DataType.FLOAT32,
|
||||
},
|
||||
[
|
||||
0x5102,
|
||||
0x0304,
|
||||
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
|
||||
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
|
||||
],
|
||||
False,
|
||||
["34899771392", "0"],
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_SLAVE_COUNT: 0,
|
||||
@ -710,7 +726,6 @@ async def test_slave_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> Non
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 51,
|
||||
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
|
||||
CONF_SCAN_INTERVAL: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -902,6 +917,65 @@ async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None:
|
||||
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"do_config",
|
||||
[
|
||||
{
|
||||
CONF_SENSORS: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 51,
|
||||
CONF_SCAN_INTERVAL: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("config_addon", "register_words", "expected"),
|
||||
[
|
||||
(
|
||||
{
|
||||
CONF_DATA_TYPE: DataType.FLOAT32,
|
||||
},
|
||||
[
|
||||
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
|
||||
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
|
||||
],
|
||||
STATE_UNAVAILABLE,
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_DATA_TYPE: DataType.FLOAT32,
|
||||
},
|
||||
[0x6E61, 0x6E00],
|
||||
STATE_UNAVAILABLE,
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_DATA_TYPE: DataType.CUSTOM,
|
||||
CONF_COUNT: 2,
|
||||
CONF_STRUCTURE: "4s",
|
||||
},
|
||||
[0x6E61, 0x6E00],
|
||||
STATE_UNAVAILABLE,
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_DATA_TYPE: DataType.CUSTOM,
|
||||
CONF_COUNT: 2,
|
||||
CONF_STRUCTURE: "4s",
|
||||
},
|
||||
[0x6161, 0x6100],
|
||||
"aaa\x00",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_unpack_ok(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
||||
"""Run test for sensor."""
|
||||
assert hass.states.get(ENTITY_ID).state == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"do_config",
|
||||
[
|
||||
@ -918,27 +992,23 @@ async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None:
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("register_words", "do_exception", "start_expect", "end_expect"),
|
||||
("register_words", "do_exception"),
|
||||
[
|
||||
(
|
||||
[0x8000],
|
||||
True,
|
||||
"17",
|
||||
STATE_UNAVAILABLE,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_lazy_error_sensor(
|
||||
hass: HomeAssistant, mock_do_cycle: FrozenDateTimeFactory, start_expect, end_expect
|
||||
hass: HomeAssistant, mock_do_cycle: FrozenDateTimeFactory
|
||||
) -> None:
|
||||
"""Run test for sensor."""
|
||||
hass.states.async_set(ENTITY_ID, 17)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(ENTITY_ID).state == start_expect
|
||||
await do_next_cycle(hass, mock_do_cycle, 11)
|
||||
assert hass.states.get(ENTITY_ID).state == start_expect
|
||||
await do_next_cycle(hass, mock_do_cycle, 11)
|
||||
assert hass.states.get(ENTITY_ID).state == end_expect
|
||||
assert hass.states.get(ENTITY_ID).state == "17"
|
||||
await do_next_cycle(hass, mock_do_cycle, 5)
|
||||
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -965,10 +1035,35 @@ async def test_lazy_error_sensor(
|
||||
CONF_DATA_TYPE: DataType.CUSTOM,
|
||||
CONF_STRUCTURE: ">4f",
|
||||
},
|
||||
# floats: 7.931250095367432, 10.600000381469727,
|
||||
# floats: nan, 10.600000381469727,
|
||||
# 1.000879611487865e-28, 10.566553115844727
|
||||
[0x40FD, 0xCCCD, 0x4129, 0x999A, 0x10FD, 0xC0CD, 0x4129, 0x109A],
|
||||
"7.93,10.60,0.00,10.57",
|
||||
[
|
||||
int.from_bytes(struct.pack(">f", float("nan"))[0:2]),
|
||||
int.from_bytes(struct.pack(">f", float("nan"))[2:4]),
|
||||
0x4129,
|
||||
0x999A,
|
||||
0x10FD,
|
||||
0xC0CD,
|
||||
0x4129,
|
||||
0x109A,
|
||||
],
|
||||
"0,10.60,0.00,10.57",
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_COUNT: 4,
|
||||
CONF_DATA_TYPE: DataType.CUSTOM,
|
||||
CONF_STRUCTURE: ">2i",
|
||||
CONF_NAN_VALUE: 0x0000000F,
|
||||
},
|
||||
# int: nan, 10,
|
||||
[
|
||||
0x0000,
|
||||
0x000F,
|
||||
0x0000,
|
||||
0x000A,
|
||||
],
|
||||
"0,10",
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -988,6 +1083,18 @@ async def test_lazy_error_sensor(
|
||||
[0x0101],
|
||||
"257",
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_COUNT: 8,
|
||||
CONF_PRECISION: 2,
|
||||
CONF_DATA_TYPE: DataType.CUSTOM,
|
||||
CONF_STRUCTURE: ">4f",
|
||||
},
|
||||
# floats: 7.931250095367432, 10.600000381469727,
|
||||
# 1.000879611487865e-28, 10.566553115844727
|
||||
[0x40FD, 0xCCCD, 0x4129, 0x999A, 0x10FD, 0xC0CD, 0x4129, 0x109A],
|
||||
"7.93,10.60,0.00,10.57",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_struct_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
||||
@ -1003,7 +1110,6 @@ async def test_struct_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> No
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 201,
|
||||
CONF_SCAN_INTERVAL: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import pytest
|
||||
from homeassistant.components import mqtt, sensor
|
||||
from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
EVENT_STATE_CHANGED,
|
||||
Platform,
|
||||
@ -324,7 +325,6 @@ async def test_default_entity_and_device_name(
|
||||
|
||||
This is a test helper for the _setup_common_attributes_from_config mixin.
|
||||
"""
|
||||
# mqtt_mock = await mqtt_mock_entry()
|
||||
|
||||
events = async_capture_events(hass, ir.EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED)
|
||||
hass.state = CoreState.starting
|
||||
@ -352,3 +352,61 @@ async def test_default_entity_and_device_name(
|
||||
|
||||
# Assert that an issues ware registered
|
||||
assert len(events) == issue_events
|
||||
|
||||
|
||||
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR])
|
||||
async def test_name_attribute_is_set_or_not(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
) -> None:
|
||||
"""Test frendly name with device_class set.
|
||||
|
||||
This is a test helper for the _setup_common_attributes_from_config mixin.
|
||||
"""
|
||||
await mqtt_mock_entry()
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
'{ "name": "Gate", "state_topic": "test-topic", "device_class": "door", '
|
||||
'"object_id": "gate",'
|
||||
'"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}'
|
||||
"}",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.gate")
|
||||
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Gate"
|
||||
|
||||
# Remove the name in a discovery update
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
'{ "state_topic": "test-topic", "device_class": "door", '
|
||||
'"object_id": "gate",'
|
||||
'"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}'
|
||||
"}",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.gate")
|
||||
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Door"
|
||||
|
||||
# Set the name to `null` in a discovery update
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
'{ "name": null, "state_topic": "test-topic", "device_class": "door", '
|
||||
'"object_id": "gate",'
|
||||
'"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}'
|
||||
"}",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.gate")
|
||||
|
||||
assert state is not None
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) is None
|
||||
|
@ -20,6 +20,12 @@
|
||||
"setpoint": 13.0,
|
||||
"temperature": 24.2
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -43,6 +49,12 @@
|
||||
"temperature_difference": 2.0,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.1,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A07"
|
||||
},
|
||||
@ -60,6 +72,12 @@
|
||||
"temperature_difference": 1.7,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.1,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A05"
|
||||
},
|
||||
@ -99,6 +117,12 @@
|
||||
"setpoint": 13.0,
|
||||
"temperature": 30.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -122,6 +146,12 @@
|
||||
"temperature_difference": 1.8,
|
||||
"valve_position": 100
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.1,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A09"
|
||||
},
|
||||
@ -145,6 +175,12 @@
|
||||
"setpoint": 13.0,
|
||||
"temperature": 30.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -187,6 +223,12 @@
|
||||
"temperature_difference": 1.9,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.1,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A04"
|
||||
},
|
||||
@ -246,6 +288,12 @@
|
||||
"setpoint": 9.0,
|
||||
"temperature": 27.4
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 4.0,
|
||||
"resolution": 0.01,
|
||||
|
@ -95,6 +95,12 @@
|
||||
"temperature_difference": -0.4,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A17"
|
||||
},
|
||||
@ -123,6 +129,12 @@
|
||||
"setpoint": 15.0,
|
||||
"temperature": 17.2
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -200,6 +212,12 @@
|
||||
"temperature_difference": -0.2,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A09"
|
||||
},
|
||||
@ -217,6 +235,12 @@
|
||||
"temperature_difference": 3.5,
|
||||
"valve_position": 100
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A02"
|
||||
},
|
||||
@ -245,6 +269,12 @@
|
||||
"setpoint": 21.5,
|
||||
"temperature": 20.9
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -289,6 +319,12 @@
|
||||
"temperature_difference": 0.1,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A10"
|
||||
},
|
||||
@ -317,6 +353,12 @@
|
||||
"setpoint": 13.0,
|
||||
"temperature": 16.5
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -353,6 +395,12 @@
|
||||
"temperature_difference": 0.0,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
@ -387,6 +435,12 @@
|
||||
"setpoint": 14.0,
|
||||
"temperature": 18.9
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
|
@ -76,6 +76,12 @@
|
||||
"setpoint": 20.5,
|
||||
"temperature": 19.3
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": -0.5,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 4.0,
|
||||
"resolution": 0.1,
|
||||
|
@ -40,6 +40,12 @@
|
||||
"temperature_difference": 2.3,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.1,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A01"
|
||||
},
|
||||
@ -118,6 +124,12 @@
|
||||
"setpoint_low": 20.0,
|
||||
"temperature": 239
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
|
@ -45,6 +45,12 @@
|
||||
"temperature_difference": 2.3,
|
||||
"valve_position": 0.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.1,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A01"
|
||||
},
|
||||
@ -114,6 +120,12 @@
|
||||
"setpoint": 15.0,
|
||||
"temperature": 17.9
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
|
@ -78,6 +78,12 @@
|
||||
"setpoint_low": 20.5,
|
||||
"temperature": 26.3
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": -0.5,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 4.0,
|
||||
"resolution": 0.1,
|
||||
|
@ -78,6 +78,12 @@
|
||||
"setpoint_low": 20.5,
|
||||
"temperature": 23.0
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": -0.5,
|
||||
"upper_bound": 2.0
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 4.0,
|
||||
"resolution": 0.1,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"devices": {
|
||||
"03e65b16e4b247a29ae0d75a78cb492e": {
|
||||
"binary_sensors": {
|
||||
"plugwise_notification": false
|
||||
"plugwise_notification": true
|
||||
},
|
||||
"dev_class": "gateway",
|
||||
"firmware": "4.4.2",
|
||||
@ -51,7 +51,11 @@
|
||||
},
|
||||
"gateway": {
|
||||
"gateway_id": "03e65b16e4b247a29ae0d75a78cb492e",
|
||||
"notifications": {},
|
||||
"notifications": {
|
||||
"97a04c0c263049b29350a660b4cdd01e": {
|
||||
"warning": "The Smile P1 is not connected to a smart meter."
|
||||
}
|
||||
},
|
||||
"smile_name": "Smile P1"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,5 @@
|
||||
{}
|
||||
{
|
||||
"97a04c0c263049b29350a660b4cdd01e": {
|
||||
"warning": "The Smile P1 is not connected to a smart meter."
|
||||
}
|
||||
}
|
||||
|
@ -48,15 +48,6 @@
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A07"
|
||||
},
|
||||
"71e1944f2a944b26ad73323e399efef0": {
|
||||
"dev_class": "switching",
|
||||
"members": ["5ca521ac179d468e91d772eeeb8a2117"],
|
||||
"model": "Switchgroup",
|
||||
"name": "Test",
|
||||
"switches": {
|
||||
"relay": true
|
||||
}
|
||||
},
|
||||
"aac7b735042c4832ac9ff33aae4f453b": {
|
||||
"dev_class": "dishwasher",
|
||||
"firmware": "2011-06-27T10:52:18+02:00",
|
||||
|
@ -31,159 +31,141 @@ async def test_diagnostics(
|
||||
},
|
||||
},
|
||||
"devices": {
|
||||
"df4a4a8169904cdb9c03d61a21f42140": {
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-10-27T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"location": "12493538af164a409c6a1c79e38afe1c",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Lisa Bios",
|
||||
"zigbee_mac_address": "ABCD012345670A06",
|
||||
"vendor": "Plugwise",
|
||||
"thermostat": {
|
||||
"setpoint": 13.0,
|
||||
"lower_bound": 0.0,
|
||||
"upper_bound": 99.9,
|
||||
"resolution": 0.01,
|
||||
},
|
||||
"available": True,
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"active_preset": "away",
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
"GF7 Woonkamer",
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"select_schedule": "None",
|
||||
"last_used": "Badkamer Schema",
|
||||
"mode": "heat",
|
||||
"sensors": {"temperature": 16.5, "setpoint": 13.0, "battery": 67},
|
||||
},
|
||||
"b310b72a0e354bfab43089919b9a88bf": {
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "c50f167537524366a5af7aa3942feb1e",
|
||||
"model": "Tom/Floor",
|
||||
"name": "Floor kraan",
|
||||
"zigbee_mac_address": "ABCD012345670A02",
|
||||
"vendor": "Plugwise",
|
||||
"02cf28bfec924855854c544690a609ef": {
|
||||
"available": True,
|
||||
"dev_class": "vcr",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "NVR",
|
||||
"sensors": {
|
||||
"temperature": 26.0,
|
||||
"setpoint": 21.5,
|
||||
"temperature_difference": 3.5,
|
||||
"valve_position": 100,
|
||||
"electricity_consumed": 34.0,
|
||||
"electricity_consumed_interval": 9.15,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
},
|
||||
"a2c3583e0a6349358998b760cea82d2a": {
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "12493538af164a409c6a1c79e38afe1c",
|
||||
"model": "Tom/Floor",
|
||||
"name": "Bios Cv Thermostatic Radiator ",
|
||||
"zigbee_mac_address": "ABCD012345670A09",
|
||||
"switches": {"lock": True, "relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"temperature": 17.2,
|
||||
"setpoint": 13.0,
|
||||
"battery": 62,
|
||||
"temperature_difference": -0.2,
|
||||
"valve_position": 0.0,
|
||||
},
|
||||
},
|
||||
"b59bcebaf94b499ea7d46e4a66fb62d8": {
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-08-02T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"location": "c50f167537524366a5af7aa3942feb1e",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Lisa WK",
|
||||
"zigbee_mac_address": "ABCD012345670A07",
|
||||
"vendor": "Plugwise",
|
||||
"thermostat": {
|
||||
"setpoint": 21.5,
|
||||
"lower_bound": 0.0,
|
||||
"upper_bound": 99.9,
|
||||
"resolution": 0.01,
|
||||
},
|
||||
"available": True,
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"active_preset": "home",
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
"GF7 Woonkamer",
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"select_schedule": "GF7 Woonkamer",
|
||||
"last_used": "GF7 Woonkamer",
|
||||
"mode": "auto",
|
||||
"sensors": {"temperature": 20.9, "setpoint": 21.5, "battery": 34},
|
||||
},
|
||||
"fe799307f1624099878210aa0b9f1475": {
|
||||
"dev_class": "gateway",
|
||||
"firmware": "3.0.15",
|
||||
"hardware": "AME Smile 2.0 board",
|
||||
"location": "1f9dcf83fd4e4b66b72ff787957bfe5d",
|
||||
"mac_address": "012345670001",
|
||||
"model": "Gateway",
|
||||
"name": "Adam",
|
||||
"zigbee_mac_address": "ABCD012345670101",
|
||||
"vendor": "Plugwise",
|
||||
"select_regulation_mode": "heating",
|
||||
"binary_sensors": {"plugwise_notification": True},
|
||||
"sensors": {"outdoor_temperature": 7.81},
|
||||
},
|
||||
"d3da73bde12a47d5a6b8f9dad971f2ec": {
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "82fa13f017d240daa0d0ea1775420f24",
|
||||
"model": "Tom/Floor",
|
||||
"name": "Thermostatic Radiator Jessie",
|
||||
"zigbee_mac_address": "ABCD012345670A10",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"temperature": 17.1,
|
||||
"setpoint": 15.0,
|
||||
"battery": 62,
|
||||
"temperature_difference": 0.1,
|
||||
"valve_position": 0.0,
|
||||
},
|
||||
"zigbee_mac_address": "ABCD012345670A15",
|
||||
},
|
||||
"21f2b542c49845e6bb416884c55778d6": {
|
||||
"available": True,
|
||||
"dev_class": "game_console",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "Playstation Smart Plug",
|
||||
"zigbee_mac_address": "ABCD012345670A12",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"electricity_consumed": 82.6,
|
||||
"electricity_consumed_interval": 8.6,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True, "lock": False},
|
||||
"switches": {"lock": False, "relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A12",
|
||||
},
|
||||
"4a810418d5394b3f82727340b91ba740": {
|
||||
"available": True,
|
||||
"dev_class": "router",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "USG Smart Plug",
|
||||
"sensors": {
|
||||
"electricity_consumed": 8.5,
|
||||
"electricity_consumed_interval": 0.0,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"lock": True, "relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A16",
|
||||
},
|
||||
"675416a629f343c495449970e2ca37b5": {
|
||||
"available": True,
|
||||
"dev_class": "router",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "Ziggo Modem",
|
||||
"sensors": {
|
||||
"electricity_consumed": 12.2,
|
||||
"electricity_consumed_interval": 2.97,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"lock": True, "relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A01",
|
||||
},
|
||||
"680423ff840043738f42cc7f1ff97a36": {
|
||||
"available": True,
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "08963fec7c53423ca5680aa4cb502c63",
|
||||
"model": "Tom/Floor",
|
||||
"name": "Thermostatic Radiator Badkamer",
|
||||
"sensors": {
|
||||
"battery": 51,
|
||||
"setpoint": 14.0,
|
||||
"temperature": 19.1,
|
||||
"temperature_difference": -0.4,
|
||||
"valve_position": 0.0,
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A17",
|
||||
},
|
||||
"6a3bf693d05e48e0b460c815a4fdd09d": {
|
||||
"active_preset": "asleep",
|
||||
"available": True,
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
"GF7 Woonkamer",
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-10-27T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"last_used": "CV Jessie",
|
||||
"location": "82fa13f017d240daa0d0ea1775420f24",
|
||||
"mode": "auto",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Thermostat Jessie",
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"select_schedule": "CV Jessie",
|
||||
"sensors": {"battery": 37, "setpoint": 15.0, "temperature": 17.2},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
"setpoint": 15.0,
|
||||
"upper_bound": 99.9,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A03",
|
||||
},
|
||||
"78d1126fc4c743db81b61c20e88342a7": {
|
||||
"available": True,
|
||||
"dev_class": "central_heating_pump",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "c50f167537524366a5af7aa3942feb1e",
|
||||
"model": "Plug",
|
||||
"name": "CV Pomp",
|
||||
"zigbee_mac_address": "ABCD012345670A05",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"electricity_consumed": 35.6,
|
||||
"electricity_consumed_interval": 7.37,
|
||||
@ -191,153 +173,88 @@ async def test_diagnostics(
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A05",
|
||||
},
|
||||
"90986d591dcd426cae3ec3e8111ff730": {
|
||||
"binary_sensors": {"heating_state": True},
|
||||
"dev_class": "heater_central",
|
||||
"location": "1f9dcf83fd4e4b66b72ff787957bfe5d",
|
||||
"model": "Unknown",
|
||||
"name": "OnOff",
|
||||
"binary_sensors": {"heating_state": True},
|
||||
"sensors": {
|
||||
"water_temperature": 70.0,
|
||||
"intended_boiler_temperature": 70.0,
|
||||
"modulation_level": 1,
|
||||
"water_temperature": 70.0,
|
||||
},
|
||||
},
|
||||
"cd0ddb54ef694e11ac18ed1cbce5dbbd": {
|
||||
"dev_class": "vcr",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "NAS",
|
||||
"zigbee_mac_address": "ABCD012345670A14",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"electricity_consumed": 16.5,
|
||||
"electricity_consumed_interval": 0.5,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True, "lock": True},
|
||||
},
|
||||
"4a810418d5394b3f82727340b91ba740": {
|
||||
"dev_class": "router",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "USG Smart Plug",
|
||||
"zigbee_mac_address": "ABCD012345670A16",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"electricity_consumed": 8.5,
|
||||
"electricity_consumed_interval": 0.0,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True, "lock": True},
|
||||
},
|
||||
"02cf28bfec924855854c544690a609ef": {
|
||||
"dev_class": "vcr",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "NVR",
|
||||
"zigbee_mac_address": "ABCD012345670A15",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"electricity_consumed": 34.0,
|
||||
"electricity_consumed_interval": 9.15,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True, "lock": True},
|
||||
},
|
||||
"a28f588dc4a049a483fd03a30361ad3a": {
|
||||
"available": True,
|
||||
"dev_class": "settop",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "Fibaro HC2",
|
||||
"zigbee_mac_address": "ABCD012345670A13",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"sensors": {
|
||||
"electricity_consumed": 12.5,
|
||||
"electricity_consumed_interval": 3.8,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True, "lock": True},
|
||||
},
|
||||
"6a3bf693d05e48e0b460c815a4fdd09d": {
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-10-27T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"location": "82fa13f017d240daa0d0ea1775420f24",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Thermostat Jessie",
|
||||
"zigbee_mac_address": "ABCD012345670A03",
|
||||
"switches": {"lock": True, "relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"thermostat": {
|
||||
"setpoint": 15.0,
|
||||
"lower_bound": 0.0,
|
||||
"upper_bound": 99.9,
|
||||
"resolution": 0.01,
|
||||
},
|
||||
"available": True,
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"active_preset": "asleep",
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
"GF7 Woonkamer",
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"select_schedule": "CV Jessie",
|
||||
"last_used": "CV Jessie",
|
||||
"mode": "auto",
|
||||
"sensors": {"temperature": 17.2, "setpoint": 15.0, "battery": 37},
|
||||
"zigbee_mac_address": "ABCD012345670A13",
|
||||
},
|
||||
"680423ff840043738f42cc7f1ff97a36": {
|
||||
"a2c3583e0a6349358998b760cea82d2a": {
|
||||
"available": True,
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "08963fec7c53423ca5680aa4cb502c63",
|
||||
"location": "12493538af164a409c6a1c79e38afe1c",
|
||||
"model": "Tom/Floor",
|
||||
"name": "Thermostatic Radiator Badkamer",
|
||||
"zigbee_mac_address": "ABCD012345670A17",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"name": "Bios Cv Thermostatic Radiator ",
|
||||
"sensors": {
|
||||
"temperature": 19.1,
|
||||
"setpoint": 14.0,
|
||||
"battery": 51,
|
||||
"temperature_difference": -0.4,
|
||||
"battery": 62,
|
||||
"setpoint": 13.0,
|
||||
"temperature": 17.2,
|
||||
"temperature_difference": -0.2,
|
||||
"valve_position": 0.0,
|
||||
},
|
||||
},
|
||||
"f1fee6043d3642a9b0a65297455f008e": {
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-10-27T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"location": "08963fec7c53423ca5680aa4cb502c63",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Thermostat Badkamer",
|
||||
"zigbee_mac_address": "ABCD012345670A08",
|
||||
"vendor": "Plugwise",
|
||||
"thermostat": {
|
||||
"setpoint": 14.0,
|
||||
"lower_bound": 0.0,
|
||||
"upper_bound": 99.9,
|
||||
"resolution": 0.01,
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A09",
|
||||
},
|
||||
"b310b72a0e354bfab43089919b9a88bf": {
|
||||
"available": True,
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "c50f167537524366a5af7aa3942feb1e",
|
||||
"model": "Tom/Floor",
|
||||
"name": "Floor kraan",
|
||||
"sensors": {
|
||||
"setpoint": 21.5,
|
||||
"temperature": 26.0,
|
||||
"temperature_difference": 3.5,
|
||||
"valve_position": 100,
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A02",
|
||||
},
|
||||
"b59bcebaf94b499ea7d46e4a66fb62d8": {
|
||||
"active_preset": "home",
|
||||
"available": True,
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"active_preset": "away",
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
@ -345,46 +262,76 @@ async def test_diagnostics(
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"select_schedule": "Badkamer Schema",
|
||||
"last_used": "Badkamer Schema",
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-08-02T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"last_used": "GF7 Woonkamer",
|
||||
"location": "c50f167537524366a5af7aa3942feb1e",
|
||||
"mode": "auto",
|
||||
"sensors": {"temperature": 18.9, "setpoint": 14.0, "battery": 92},
|
||||
"model": "Lisa",
|
||||
"name": "Zone Lisa WK",
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"select_schedule": "GF7 Woonkamer",
|
||||
"sensors": {"battery": 34, "setpoint": 21.5, "temperature": 20.9},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
"setpoint": 21.5,
|
||||
"upper_bound": 99.9,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A07",
|
||||
},
|
||||
"675416a629f343c495449970e2ca37b5": {
|
||||
"dev_class": "router",
|
||||
"cd0ddb54ef694e11ac18ed1cbce5dbbd": {
|
||||
"available": True,
|
||||
"dev_class": "vcr",
|
||||
"firmware": "2019-06-21T02:00:00+02:00",
|
||||
"location": "cd143c07248f491493cea0533bc3d669",
|
||||
"model": "Plug",
|
||||
"name": "Ziggo Modem",
|
||||
"zigbee_mac_address": "ABCD012345670A01",
|
||||
"vendor": "Plugwise",
|
||||
"available": True,
|
||||
"name": "NAS",
|
||||
"sensors": {
|
||||
"electricity_consumed": 12.2,
|
||||
"electricity_consumed_interval": 2.97,
|
||||
"electricity_consumed": 16.5,
|
||||
"electricity_consumed_interval": 0.5,
|
||||
"electricity_produced": 0.0,
|
||||
"electricity_produced_interval": 0.0,
|
||||
},
|
||||
"switches": {"relay": True, "lock": True},
|
||||
"switches": {"lock": True, "relay": True},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A14",
|
||||
},
|
||||
"e7693eb9582644e5b865dba8d4447cf1": {
|
||||
"dev_class": "thermostatic_radiator_valve",
|
||||
"d3da73bde12a47d5a6b8f9dad971f2ec": {
|
||||
"available": True,
|
||||
"dev_class": "thermo_sensor",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"location": "446ac08dd04d4eff8ac57489757b7314",
|
||||
"location": "82fa13f017d240daa0d0ea1775420f24",
|
||||
"model": "Tom/Floor",
|
||||
"name": "CV Kraan Garage",
|
||||
"zigbee_mac_address": "ABCD012345670A11",
|
||||
"vendor": "Plugwise",
|
||||
"thermostat": {
|
||||
"setpoint": 5.5,
|
||||
"lower_bound": 0.0,
|
||||
"upper_bound": 100.0,
|
||||
"resolution": 0.01,
|
||||
"name": "Thermostatic Radiator Jessie",
|
||||
"sensors": {
|
||||
"battery": 62,
|
||||
"setpoint": 15.0,
|
||||
"temperature": 17.1,
|
||||
"temperature_difference": 0.1,
|
||||
"valve_position": 0.0,
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A10",
|
||||
},
|
||||
"df4a4a8169904cdb9c03d61a21f42140": {
|
||||
"active_preset": "away",
|
||||
"available": True,
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"active_preset": "no_frost",
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
@ -392,16 +339,123 @@ async def test_diagnostics(
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"select_schedule": "None",
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-10-27T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"last_used": "Badkamer Schema",
|
||||
"location": "12493538af164a409c6a1c79e38afe1c",
|
||||
"mode": "heat",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Lisa Bios",
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"select_schedule": "None",
|
||||
"sensors": {"battery": 67, "setpoint": 13.0, "temperature": 16.5},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
"setpoint": 13.0,
|
||||
"upper_bound": 99.9,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A06",
|
||||
},
|
||||
"e7693eb9582644e5b865dba8d4447cf1": {
|
||||
"active_preset": "no_frost",
|
||||
"available": True,
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
"GF7 Woonkamer",
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"dev_class": "thermostatic_radiator_valve",
|
||||
"firmware": "2019-03-27T01:00:00+01:00",
|
||||
"hardware": "1",
|
||||
"last_used": "Badkamer Schema",
|
||||
"location": "446ac08dd04d4eff8ac57489757b7314",
|
||||
"mode": "heat",
|
||||
"model": "Tom/Floor",
|
||||
"name": "CV Kraan Garage",
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"select_schedule": "None",
|
||||
"sensors": {
|
||||
"temperature": 15.6,
|
||||
"setpoint": 5.5,
|
||||
"battery": 68,
|
||||
"setpoint": 5.5,
|
||||
"temperature": 15.6,
|
||||
"temperature_difference": 0.0,
|
||||
"valve_position": 0.0,
|
||||
},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
"setpoint": 5.5,
|
||||
"upper_bound": 100.0,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A11",
|
||||
},
|
||||
"f1fee6043d3642a9b0a65297455f008e": {
|
||||
"active_preset": "away",
|
||||
"available": True,
|
||||
"available_schedules": [
|
||||
"CV Roan",
|
||||
"Bios Schema met Film Avond",
|
||||
"GF7 Woonkamer",
|
||||
"Badkamer Schema",
|
||||
"CV Jessie",
|
||||
],
|
||||
"dev_class": "zone_thermostat",
|
||||
"firmware": "2016-10-27T02:00:00+02:00",
|
||||
"hardware": "255",
|
||||
"last_used": "Badkamer Schema",
|
||||
"location": "08963fec7c53423ca5680aa4cb502c63",
|
||||
"mode": "auto",
|
||||
"model": "Lisa",
|
||||
"name": "Zone Thermostat Badkamer",
|
||||
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
|
||||
"select_schedule": "Badkamer Schema",
|
||||
"sensors": {"battery": 92, "setpoint": 14.0, "temperature": 18.9},
|
||||
"temperature_offset": {
|
||||
"lower_bound": -2.0,
|
||||
"resolution": 0.1,
|
||||
"setpoint": 0.0,
|
||||
"upper_bound": 2.0,
|
||||
},
|
||||
"thermostat": {
|
||||
"lower_bound": 0.0,
|
||||
"resolution": 0.01,
|
||||
"setpoint": 14.0,
|
||||
"upper_bound": 99.9,
|
||||
},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670A08",
|
||||
},
|
||||
"fe799307f1624099878210aa0b9f1475": {
|
||||
"binary_sensors": {"plugwise_notification": True},
|
||||
"dev_class": "gateway",
|
||||
"firmware": "3.0.15",
|
||||
"hardware": "AME Smile 2.0 board",
|
||||
"location": "1f9dcf83fd4e4b66b72ff787957bfe5d",
|
||||
"mac_address": "012345670001",
|
||||
"model": "Gateway",
|
||||
"name": "Adam",
|
||||
"select_regulation_mode": "heating",
|
||||
"sensors": {"outdoor_temperature": 7.81},
|
||||
"vendor": "Plugwise",
|
||||
"zigbee_mac_address": "ABCD012345670101",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ async def test_anna_max_boiler_temp_change(
|
||||
|
||||
assert mock_smile_anna.set_number_setpoint.call_count == 1
|
||||
mock_smile_anna.set_number_setpoint.assert_called_with(
|
||||
"maximum_boiler_temperature", 65.0
|
||||
"maximum_boiler_temperature", "1cbf783bb11e4a7c8a6843dee3a86927", 65.0
|
||||
)
|
||||
|
||||
|
||||
@ -67,5 +67,5 @@ async def test_adam_dhw_setpoint_change(
|
||||
|
||||
assert mock_smile_adam_2.set_number_setpoint.call_count == 1
|
||||
mock_smile_adam_2.set_number_setpoint.assert_called_with(
|
||||
"max_dhw_temperature", 55.0
|
||||
"max_dhw_temperature", "056ee145a816487eaa69243c3280f8bf", 55.0
|
||||
)
|
||||
|
@ -1835,3 +1835,35 @@ async def test_entity_id_update_discovery_update(
|
||||
await help_test_entity_id_update_discovery_update(
|
||||
hass, mqtt_mock, Platform.LIGHT, config
|
||||
)
|
||||
|
||||
|
||||
async def test_no_device_name(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
|
||||
) -> None:
|
||||
"""Test name of lights when no device name is set.
|
||||
|
||||
When the device name is not set, Tasmota uses friendly name 1 as device naem.
|
||||
This test ensures that case is handled correctly.
|
||||
"""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
config["dn"] = "Light 1"
|
||||
config["fn"][0] = "Light 1"
|
||||
config["fn"][1] = "Light 2"
|
||||
config["rl"][0] = 2
|
||||
config["rl"][1] = 2
|
||||
mac = config["mac"]
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
f"{DEFAULT_PREFIX}/{mac}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_1")
|
||||
assert state is not None
|
||||
assert state.attributes["friendly_name"] == "Light 1"
|
||||
|
||||
state = hass.states.get("light.light_1_light_2")
|
||||
assert state is not None
|
||||
assert state.attributes["friendly_name"] == "Light 1 Light 2"
|
||||
|
@ -137,6 +137,27 @@ DICT_SENSOR_CONFIG_2 = {
|
||||
}
|
||||
}
|
||||
|
||||
NUMBERED_SENSOR_CONFIG = {
|
||||
"sn": {
|
||||
"Time": "2020-09-25T12:47:15",
|
||||
"ANALOG": {
|
||||
"Temperature1": 2.4,
|
||||
"Temperature2": 2.4,
|
||||
"Illuminance3": 2.4,
|
||||
},
|
||||
"TempUnit": "C",
|
||||
}
|
||||
}
|
||||
|
||||
NUMBERED_SENSOR_CONFIG_2 = {
|
||||
"sn": {
|
||||
"Time": "2020-09-25T12:47:15",
|
||||
"ANALOG": {
|
||||
"CTEnergy1": {"Energy": 0.5, "Power": 2300, "Voltage": 230, "Current": 10},
|
||||
},
|
||||
"TempUnit": "C",
|
||||
}
|
||||
}
|
||||
|
||||
TEMPERATURE_SENSOR_CONFIG = {
|
||||
"sn": {
|
||||
@ -343,6 +364,118 @@ TEMPERATURE_SENSOR_CONFIG = {
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
NUMBERED_SENSOR_CONFIG,
|
||||
[
|
||||
"sensor.tasmota_analog_temperature1",
|
||||
"sensor.tasmota_analog_temperature2",
|
||||
"sensor.tasmota_analog_illuminance3",
|
||||
],
|
||||
(
|
||||
(
|
||||
'{"ANALOG":{"Temperature1":1.2,"Temperature2":3.4,'
|
||||
'"Illuminance3": 5.6}}'
|
||||
),
|
||||
(
|
||||
'{"StatusSNS":{"ANALOG":{"Temperature1": 7.8,"Temperature2": 9.0,'
|
||||
'"Illuminance3":1.2}}}'
|
||||
),
|
||||
),
|
||||
(
|
||||
{
|
||||
"sensor.tasmota_analog_temperature1": {
|
||||
"state": "1.2",
|
||||
"attributes": {
|
||||
"device_class": "temperature",
|
||||
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
"unit_of_measurement": "°C",
|
||||
},
|
||||
},
|
||||
"sensor.tasmota_analog_temperature2": {
|
||||
"state": "3.4",
|
||||
"attributes": {
|
||||
"device_class": "temperature",
|
||||
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
"unit_of_measurement": "°C",
|
||||
},
|
||||
},
|
||||
"sensor.tasmota_analog_illuminance3": {
|
||||
"state": "5.6",
|
||||
"attributes": {
|
||||
"device_class": "illuminance",
|
||||
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
"unit_of_measurement": "lx",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sensor.tasmota_analog_temperature1": {"state": "7.8"},
|
||||
"sensor.tasmota_analog_temperature2": {"state": "9.0"},
|
||||
"sensor.tasmota_analog_illuminance3": {"state": "1.2"},
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
NUMBERED_SENSOR_CONFIG_2,
|
||||
[
|
||||
"sensor.tasmota_analog_ctenergy1_energy",
|
||||
"sensor.tasmota_analog_ctenergy1_power",
|
||||
"sensor.tasmota_analog_ctenergy1_voltage",
|
||||
"sensor.tasmota_analog_ctenergy1_current",
|
||||
],
|
||||
(
|
||||
(
|
||||
'{"ANALOG":{"CTEnergy1":'
|
||||
'{"Energy":0.5,"Power":2300,"Voltage":230,"Current":10}}}'
|
||||
),
|
||||
(
|
||||
'{"StatusSNS":{"ANALOG":{"CTEnergy1":'
|
||||
'{"Energy":1.0,"Power":1150,"Voltage":230,"Current":5}}}}'
|
||||
),
|
||||
),
|
||||
(
|
||||
{
|
||||
"sensor.tasmota_analog_ctenergy1_energy": {
|
||||
"state": "0.5",
|
||||
"attributes": {
|
||||
"device_class": "energy",
|
||||
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
|
||||
"unit_of_measurement": "kWh",
|
||||
},
|
||||
},
|
||||
"sensor.tasmota_analog_ctenergy1_power": {
|
||||
"state": "2300",
|
||||
"attributes": {
|
||||
"device_class": "power",
|
||||
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
"unit_of_measurement": "W",
|
||||
},
|
||||
},
|
||||
"sensor.tasmota_analog_ctenergy1_voltage": {
|
||||
"state": "230",
|
||||
"attributes": {
|
||||
"device_class": "voltage",
|
||||
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
"unit_of_measurement": "V",
|
||||
},
|
||||
},
|
||||
"sensor.tasmota_analog_ctenergy1_current": {
|
||||
"state": "10",
|
||||
"attributes": {
|
||||
"device_class": "current",
|
||||
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||
"unit_of_measurement": "A",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sensor.tasmota_analog_ctenergy1_energy": {"state": "1.0"},
|
||||
"sensor.tasmota_analog_ctenergy1_power": {"state": "1150"},
|
||||
"sensor.tasmota_analog_ctenergy1_voltage": {"state": "230"},
|
||||
"sensor.tasmota_analog_ctenergy1_current": {"state": "5"},
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_controlling_state_via_mqtt(
|
||||
@ -409,6 +542,87 @@ async def test_controlling_state_via_mqtt(
|
||||
assert state.attributes.get(attribute) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("sensor_config", "entity_ids", "states"),
|
||||
[
|
||||
(
|
||||
# The AS33935 energy sensor is not reporting energy in W
|
||||
{"sn": {"Time": "2020-09-25T12:47:15", "AS3935": {"Energy": None}}},
|
||||
["sensor.tasmota_as3935_energy"],
|
||||
{
|
||||
"sensor.tasmota_as3935_energy": {
|
||||
"device_class": None,
|
||||
"state_class": None,
|
||||
"unit_of_measurement": None,
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
# The AS33935 energy sensor is not reporting energy in W
|
||||
{"sn": {"Time": "2020-09-25T12:47:15", "LD2410": {"Energy": None}}},
|
||||
["sensor.tasmota_ld2410_energy"],
|
||||
{
|
||||
"sensor.tasmota_ld2410_energy": {
|
||||
"device_class": None,
|
||||
"state_class": None,
|
||||
"unit_of_measurement": None,
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
# Check other energy sensors work
|
||||
{"sn": {"Time": "2020-09-25T12:47:15", "Other": {"Energy": None}}},
|
||||
["sensor.tasmota_other_energy"],
|
||||
{
|
||||
"sensor.tasmota_other_energy": {
|
||||
"device_class": "energy",
|
||||
"state_class": "total",
|
||||
"unit_of_measurement": "kWh",
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_quantity_override(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock: MqttMockHAClient,
|
||||
setup_tasmota,
|
||||
sensor_config,
|
||||
entity_ids,
|
||||
states,
|
||||
) -> None:
|
||||
"""Test quantity override for certain sensors."""
|
||||
entity_reg = er.async_get(hass)
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
sensor_config = copy.deepcopy(sensor_config)
|
||||
mac = config["mac"]
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
f"{DEFAULT_PREFIX}/{mac}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
f"{DEFAULT_PREFIX}/{mac}/sensors",
|
||||
json.dumps(sensor_config),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "unavailable"
|
||||
expected_state = states[entity_id]
|
||||
for attribute, expected in expected_state.get("attributes", {}).items():
|
||||
assert state.attributes.get(attribute) == expected
|
||||
|
||||
entry = entity_reg.async_get(entity_id)
|
||||
assert entry.disabled is False
|
||||
assert entry.disabled_by is None
|
||||
assert entry.entity_category is None
|
||||
|
||||
|
||||
async def test_bad_indexed_sensor_state_via_mqtt(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
|
||||
) -> None:
|
||||
|
@ -283,3 +283,35 @@ async def test_entity_id_update_discovery_update(
|
||||
await help_test_entity_id_update_discovery_update(
|
||||
hass, mqtt_mock, Platform.SWITCH, config
|
||||
)
|
||||
|
||||
|
||||
async def test_no_device_name(
|
||||
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
|
||||
) -> None:
|
||||
"""Test name of switches when no device name is set.
|
||||
|
||||
When the device name is not set, Tasmota uses friendly name 1 as device naem.
|
||||
This test ensures that case is handled correctly.
|
||||
"""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
config["dn"] = "Relay 1"
|
||||
config["fn"][0] = "Relay 1"
|
||||
config["fn"][1] = "Relay 2"
|
||||
config["rl"][0] = 1
|
||||
config["rl"][1] = 1
|
||||
mac = config["mac"]
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
f"{DEFAULT_PREFIX}/{mac}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("switch.relay_1")
|
||||
assert state is not None
|
||||
assert state.attributes["friendly_name"] == "Relay 1"
|
||||
|
||||
state = hass.states.get("switch.relay_1_relay_2")
|
||||
assert state is not None
|
||||
assert state.attributes["friendly_name"] == "Relay 1 Relay 2"
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""The test for the Template sensor platform."""
|
||||
from asyncio import Event
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@ -1140,6 +1140,48 @@ async def test_trigger_entity(
|
||||
assert state.context is context
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
{
|
||||
"template": [
|
||||
{
|
||||
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||
"sensors": {
|
||||
"hello": {
|
||||
"friendly_name": "Hello Name",
|
||||
"value_template": "{{ trigger.event.data.beer }}",
|
||||
"entity_picture_template": "{{ '/local/dogs.png' }}",
|
||||
"icon_template": "{{ 'mdi:pirate' }}",
|
||||
"attribute_templates": {
|
||||
"last": "{{now().strftime('%D %X')}}",
|
||||
"history_1": "{{this.attributes.last|default('Not yet set')}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_trigger_entity_runs_once(
|
||||
hass: HomeAssistant, start_ha, entity_registry: er.EntityRegistry
|
||||
) -> None:
|
||||
"""Test trigger entity handles a trigger once."""
|
||||
state = hass.states.get("sensor.hello_name")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
hass.bus.async_fire("test_event", {"beer": 2})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.hello_name")
|
||||
assert state.state == "2"
|
||||
assert state.attributes.get("last") == ANY
|
||||
assert state.attributes.get("history_1") == "Not yet set"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
|
@ -293,14 +293,20 @@ def zigpy_device_mock(zigpy_app_controller):
|
||||
return _mock_dev
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.setup_quirks", MagicMock(return_value=True))
|
||||
@pytest.fixture
|
||||
def zha_device_joined(hass, setup_zha):
|
||||
"""Return a newly joined ZHA device."""
|
||||
setup_zha_fixture = setup_zha
|
||||
|
||||
async def _zha_device(zigpy_dev):
|
||||
async def _zha_device(zigpy_dev, *, setup_zha: bool = True):
|
||||
zigpy_dev.last_seen = time.time()
|
||||
await setup_zha()
|
||||
|
||||
if setup_zha:
|
||||
await setup_zha_fixture()
|
||||
|
||||
zha_gateway = common.get_zha_gateway(hass)
|
||||
zha_gateway.application_controller.devices[zigpy_dev.ieee] = zigpy_dev
|
||||
await zha_gateway.async_device_initialized(zigpy_dev)
|
||||
await hass.async_block_till_done()
|
||||
return zha_gateway.get_device(zigpy_dev.ieee)
|
||||
@ -308,17 +314,21 @@ def zha_device_joined(hass, setup_zha):
|
||||
return _zha_device
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.setup_quirks", MagicMock(return_value=True))
|
||||
@pytest.fixture
|
||||
def zha_device_restored(hass, zigpy_app_controller, setup_zha):
|
||||
"""Return a restored ZHA device."""
|
||||
setup_zha_fixture = setup_zha
|
||||
|
||||
async def _zha_device(zigpy_dev, last_seen=None):
|
||||
async def _zha_device(zigpy_dev, *, last_seen=None, setup_zha: bool = True):
|
||||
zigpy_app_controller.devices[zigpy_dev.ieee] = zigpy_dev
|
||||
|
||||
if last_seen is not None:
|
||||
zigpy_dev.last_seen = last_seen
|
||||
|
||||
await setup_zha()
|
||||
if setup_zha:
|
||||
await setup_zha_fixture()
|
||||
|
||||
zha_gateway = hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY]
|
||||
return zha_gateway.get_device(zigpy_dev.ieee)
|
||||
|
||||
@ -376,3 +386,10 @@ def hass_disable_services(hass):
|
||||
hass, "services", MagicMock(has_service=MagicMock(return_value=True))
|
||||
):
|
||||
yield hass
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def speed_up_radio_mgr():
|
||||
"""Speed up the radio manager connection time by removing delays."""
|
||||
with patch("homeassistant.components.zha.radio_manager.CONNECT_DELAY_S", 0.00001):
|
||||
yield
|
||||
|
@ -62,13 +62,6 @@ def mock_multipan_platform():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reduce_reconnect_timeout():
|
||||
"""Reduces reconnect timeout to speed up tests."""
|
||||
with patch("homeassistant.components.zha.radio_manager.CONNECT_DELAY_S", 0.01):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_app():
|
||||
"""Mock zigpy app interface."""
|
||||
|
@ -9,6 +9,9 @@ import zigpy.zcl.clusters.general as general
|
||||
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.components.device_automation import DeviceAutomationType
|
||||
from homeassistant.components.device_automation.exceptions import (
|
||||
InvalidDeviceAutomationConfig,
|
||||
)
|
||||
from homeassistant.components.zha.core.const import ATTR_ENDPOINT_ID
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -20,6 +23,7 @@ from .common import async_enable_traffic
|
||||
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
async_fire_time_changed,
|
||||
async_get_device_automations,
|
||||
async_mock_service,
|
||||
@ -45,6 +49,16 @@ LONG_PRESS = "remote_button_long_press"
|
||||
LONG_RELEASE = "remote_button_long_release"
|
||||
|
||||
|
||||
SWITCH_SIGNATURE = {
|
||||
1: {
|
||||
SIG_EP_INPUT: [general.Basic.cluster_id],
|
||||
SIG_EP_OUTPUT: [general.OnOff.cluster_id],
|
||||
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.ON_OFF_SWITCH,
|
||||
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def sensor_platforms_only():
|
||||
"""Only set up the sensor platform and required base platforms to speed up tests."""
|
||||
@ -72,16 +86,7 @@ def calls(hass):
|
||||
async def mock_devices(hass, zigpy_device_mock, zha_device_joined_restored):
|
||||
"""IAS device fixture."""
|
||||
|
||||
zigpy_device = zigpy_device_mock(
|
||||
{
|
||||
1: {
|
||||
SIG_EP_INPUT: [general.Basic.cluster_id],
|
||||
SIG_EP_OUTPUT: [general.OnOff.cluster_id],
|
||||
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.ON_OFF_SWITCH,
|
||||
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
|
||||
}
|
||||
}
|
||||
)
|
||||
zigpy_device = zigpy_device_mock(SWITCH_SIGNATURE)
|
||||
|
||||
zha_device = await zha_device_joined_restored(zigpy_device)
|
||||
zha_device.update_available(True)
|
||||
@ -397,3 +402,108 @@ async def test_exception_bad_trigger(
|
||||
"Unnamed automation failed to setup triggers and has been disabled: "
|
||||
"device does not have trigger ('junk', 'junk')" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
async def test_validate_trigger_config_missing_info(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
zigpy_device_mock,
|
||||
mock_zigpy_connect,
|
||||
zha_device_joined,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test device triggers referring to a missing device."""
|
||||
|
||||
# Join a device
|
||||
switch = zigpy_device_mock(SWITCH_SIGNATURE)
|
||||
await zha_device_joined(switch)
|
||||
|
||||
# After we unload the config entry, trigger info was not cached on startup, nor can
|
||||
# it be pulled from the current device, making it impossible to validate triggers
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
ha_device_registry = dr.async_get(hass)
|
||||
reg_device = ha_device_registry.async_get_device(
|
||||
identifiers={("zha", str(switch.ieee))}
|
||||
)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"device_id": reg_device.id,
|
||||
"domain": "zha",
|
||||
"platform": "device",
|
||||
"type": "junk",
|
||||
"subtype": "junk",
|
||||
},
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
"data": {"message": "service called"},
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert "Unable to get zha device" in caplog.text
|
||||
|
||||
with pytest.raises(InvalidDeviceAutomationConfig):
|
||||
await async_get_device_automations(
|
||||
hass, DeviceAutomationType.TRIGGER, reg_device.id
|
||||
)
|
||||
|
||||
|
||||
async def test_validate_trigger_config_unloaded_bad_info(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
zigpy_device_mock,
|
||||
mock_zigpy_connect,
|
||||
zha_device_joined,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test device triggers referring to a missing device."""
|
||||
|
||||
# Join a device
|
||||
switch = zigpy_device_mock(SWITCH_SIGNATURE)
|
||||
await zha_device_joined(switch)
|
||||
|
||||
# After we unload the config entry, trigger info was not cached on startup, nor can
|
||||
# it be pulled from the current device, making it impossible to validate triggers
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
# Reload ZHA to persist the device info in the cache
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
ha_device_registry = dr.async_get(hass)
|
||||
reg_device = ha_device_registry.async_get_device(
|
||||
identifiers={("zha", str(switch.ieee))}
|
||||
)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"device_id": reg_device.id,
|
||||
"domain": "zha",
|
||||
"platform": "device",
|
||||
"type": "junk",
|
||||
"subtype": "junk",
|
||||
},
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
"data": {"message": "service called"},
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert "Unable to find trigger" in caplog.text
|
||||
|
@ -6,7 +6,6 @@ import pytest
|
||||
from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
||||
from zigpy.exceptions import TransientConnectionError
|
||||
|
||||
from homeassistant.components.zha import async_setup_entry
|
||||
from homeassistant.components.zha.core.const import (
|
||||
CONF_BAUDRATE,
|
||||
CONF_RADIO_TYPE,
|
||||
@ -22,7 +21,7 @@ from .test_light import LIGHT_ON_OFF
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
DATA_RADIO_TYPE = "deconz"
|
||||
DATA_RADIO_TYPE = "ezsp"
|
||||
DATA_PORT_PATH = "/dev/serial/by-id/FTDI_USB__-__Serial_Cable_12345678-if00-port0"
|
||||
|
||||
|
||||
@ -137,7 +136,7 @@ async def test_config_depreciation(hass: HomeAssistant, zha_config) -> None:
|
||||
"homeassistant.components.zha.websocket_api.async_load_api", Mock(return_value=True)
|
||||
)
|
||||
async def test_setup_with_v3_cleaning_uri(
|
||||
hass: HomeAssistant, path: str, cleaned_path: str
|
||||
hass: HomeAssistant, path: str, cleaned_path: str, mock_zigpy_connect
|
||||
) -> None:
|
||||
"""Test migration of config entry from v3, applying corrections to the port path."""
|
||||
config_entry_v3 = MockConfigEntry(
|
||||
@ -150,14 +149,9 @@ async def test_setup_with_v3_cleaning_uri(
|
||||
)
|
||||
config_entry_v3.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.ZHAGateway", return_value=AsyncMock()
|
||||
) as mock_gateway:
|
||||
mock_gateway.return_value.coordinator_ieee = "mock_ieee"
|
||||
mock_gateway.return_value.radio_description = "mock_radio"
|
||||
|
||||
assert await async_setup_entry(hass, config_entry_v3)
|
||||
hass.data[DOMAIN]["zha_gateway"] = mock_gateway.return_value
|
||||
await hass.config_entries.async_setup(config_entry_v3.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.config_entries.async_unload(config_entry_v3.entry_id)
|
||||
|
||||
assert config_entry_v3.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
|
||||
assert config_entry_v3.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path
|
||||
|
@ -32,9 +32,7 @@ def disable_platform_only():
|
||||
@pytest.fixture(autouse=True)
|
||||
def reduce_reconnect_timeout():
|
||||
"""Reduces reconnect timeout to speed up tests."""
|
||||
with patch(
|
||||
"homeassistant.components.zha.radio_manager.CONNECT_DELAY_S", 0.0001
|
||||
), patch("homeassistant.components.zha.radio_manager.RETRY_DELAY_S", 0.0001):
|
||||
with patch("homeassistant.components.zha.radio_manager.RETRY_DELAY_S", 0.0001):
|
||||
yield
|
||||
|
||||
|
||||
@ -99,7 +97,7 @@ def mock_connect_zigpy_app() -> Generator[MagicMock, None, None]:
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app",
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app",
|
||||
return_value=mock_connect_app,
|
||||
):
|
||||
yield mock_connect_app
|
||||
|
@ -22,16 +22,10 @@ import homeassistant.helpers.issue_registry as ir
|
||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||
|
||||
|
||||
async def test_device_config_file_changed(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
client,
|
||||
multisensor_6_state,
|
||||
integration,
|
||||
) -> None:
|
||||
"""Test the device_config_file_changed issue."""
|
||||
dev_reg = dr.async_get(hass)
|
||||
async def _trigger_repair_issue(
|
||||
hass: HomeAssistant, client, multisensor_6_state
|
||||
) -> Node:
|
||||
"""Trigger repair issue."""
|
||||
# Create a node
|
||||
node_state = deepcopy(multisensor_6_state)
|
||||
node = Node(client, node_state)
|
||||
@ -53,6 +47,23 @@ async def test_device_config_file_changed(
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
return node
|
||||
|
||||
|
||||
async def test_device_config_file_changed(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
client,
|
||||
multisensor_6_state,
|
||||
integration,
|
||||
) -> None:
|
||||
"""Test the device_config_file_changed issue."""
|
||||
dev_reg = dr.async_get(hass)
|
||||
node = await _trigger_repair_issue(hass, client, multisensor_6_state)
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
device = dev_reg.async_get_device(identifiers={get_device_id(client.driver, node)})
|
||||
assert device
|
||||
issue_id = f"device_config_file_changed.{device.id}"
|
||||
@ -157,3 +168,46 @@ async def test_invalid_issue(
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
assert len(msg["result"]["issues"]) == 0
|
||||
|
||||
|
||||
async def test_abort_confirm(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
client,
|
||||
multisensor_6_state,
|
||||
integration,
|
||||
) -> None:
|
||||
"""Test aborting device_config_file_changed issue in confirm step."""
|
||||
dev_reg = dr.async_get(hass)
|
||||
node = await _trigger_repair_issue(hass, client, multisensor_6_state)
|
||||
|
||||
device = dev_reg.async_get_device(identifiers={get_device_id(client.driver, node)})
|
||||
assert device
|
||||
issue_id = f"device_config_file_changed.{device.id}"
|
||||
|
||||
await async_process_repairs_platforms(hass)
|
||||
await hass_ws_client(hass)
|
||||
http_client = await hass_client()
|
||||
|
||||
url = RepairsFlowIndexView.url
|
||||
resp = await http_client.post(url, json={"handler": DOMAIN, "issue_id": issue_id})
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
# Unload config entry so we can't connect to the node
|
||||
await hass.config_entries.async_unload(integration.entry_id)
|
||||
|
||||
# Apply fix
|
||||
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
||||
resp = await http_client.post(url)
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
assert data["type"] == "abort"
|
||||
assert data["reason"] == "cannot_connect"
|
||||
assert data["description_placeholders"] == {"device_name": device.name}
|
||||
|
Loading…
x
Reference in New Issue
Block a user