mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
2023.11.1 (#103301)
This commit is contained in:
commit
ce12d82624
@ -28,5 +28,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pubnub", "yalexs"],
|
"loggers": ["pubnub", "yalexs"],
|
||||||
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.3.1"]
|
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.3.2"]
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,6 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ecoforest",
|
"documentation": "https://www.home-assistant.io/integrations/ecoforest",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
"loggers": ["pyecoforest"],
|
||||||
"requirements": ["pyecoforest==0.3.0"]
|
"requirements": ["pyecoforest==0.3.0"]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyenphase"],
|
"loggers": ["pyenphase"],
|
||||||
"requirements": ["pyenphase==1.13.1"],
|
"requirements": ["pyenphase==1.14.1"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_enphase-envoy._tcp.local."
|
"type": "_enphase-envoy._tcp.local."
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
|||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -204,7 +204,7 @@ class FroniusSolarNet:
|
|||||||
|
|
||||||
# Only for re-scans. Initial setup adds entities through sensor.async_setup_entry
|
# Only for re-scans. Initial setup adds entities through sensor.async_setup_entry
|
||||||
if self.config_entry.state == ConfigEntryState.LOADED:
|
if self.config_entry.state == ConfigEntryState.LOADED:
|
||||||
dispatcher_send(self.hass, SOLAR_NET_DISCOVERY_NEW, _coordinator)
|
async_dispatcher_send(self.hass, SOLAR_NET_DISCOVERY_NEW, _coordinator)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"New inverter added (UID: %s)",
|
"New inverter added (UID: %s)",
|
||||||
|
@ -661,7 +661,7 @@ class _FroniusSensorEntity(CoordinatorEntity["FroniusCoordinatorBase"], SensorEn
|
|||||||
if new_value is None:
|
if new_value is None:
|
||||||
return self.entity_description.default_value
|
return self.entity_description.default_value
|
||||||
if self.entity_description.invalid_when_falsy and not new_value:
|
if self.entity_description.invalid_when_falsy and not new_value:
|
||||||
raise ValueError(f"Ignoring zero value for {self.entity_id}.")
|
return None
|
||||||
if isinstance(new_value, float):
|
if isinstance(new_value, float):
|
||||||
return round(new_value, 4)
|
return round(new_value, 4)
|
||||||
return new_value
|
return new_value
|
||||||
@ -671,10 +671,9 @@ class _FroniusSensorEntity(CoordinatorEntity["FroniusCoordinatorBase"], SensorEn
|
|||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
try:
|
try:
|
||||||
self._attr_native_value = self._get_entity_value()
|
self._attr_native_value = self._get_entity_value()
|
||||||
except (KeyError, ValueError):
|
except KeyError:
|
||||||
# sets state to `None` if no default_value is defined in entity description
|
# sets state to `None` if no default_value is defined in entity description
|
||||||
# KeyError: raised when omitted in response - eg. at night when no production
|
# KeyError: raised when omitted in response - eg. at night when no production
|
||||||
# ValueError: raised when invalid zero value received
|
|
||||||
self._attr_native_value = self.entity_description.default_value
|
self._attr_native_value = self.entity_description.default_value
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@ -88,7 +88,6 @@ DESCRIPTIONS = (
|
|||||||
GardenaBluetoothSensorEntityDescription(
|
GardenaBluetoothSensorEntityDescription(
|
||||||
key=Sensor.measurement_timestamp.uuid,
|
key=Sensor.measurement_timestamp.uuid,
|
||||||
translation_key="sensor_measurement_timestamp",
|
translation_key="sensor_measurement_timestamp",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
char=Sensor.measurement_timestamp,
|
char=Sensor.measurement_timestamp,
|
||||||
|
@ -353,6 +353,11 @@ class HoneywellUSThermostat(ClimateEntity):
|
|||||||
if mode == "heat":
|
if mode == "heat":
|
||||||
await self._device.set_setpoint_heat(temperature)
|
await self._device.set_setpoint_heat(temperature)
|
||||||
|
|
||||||
|
except UnexpectedResponse as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
"Honeywell set temperature failed: Invalid Response"
|
||||||
|
) from err
|
||||||
|
|
||||||
except SomeComfortError as err:
|
except SomeComfortError as err:
|
||||||
_LOGGER.error("Invalid temperature %.1f: %s", temperature, err)
|
_LOGGER.error("Invalid temperature %.1f: %s", temperature, err)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -369,6 +374,11 @@ class HoneywellUSThermostat(ClimateEntity):
|
|||||||
if temperature := kwargs.get(ATTR_TARGET_TEMP_LOW):
|
if temperature := kwargs.get(ATTR_TARGET_TEMP_LOW):
|
||||||
await self._device.set_setpoint_heat(temperature)
|
await self._device.set_setpoint_heat(temperature)
|
||||||
|
|
||||||
|
except UnexpectedResponse as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
"Honeywell set temperature failed: Invalid Response"
|
||||||
|
) from err
|
||||||
|
|
||||||
except SomeComfortError as err:
|
except SomeComfortError as err:
|
||||||
_LOGGER.error("Invalid temperature %.1f: %s", temperature, err)
|
_LOGGER.error("Invalid temperature %.1f: %s", temperature, err)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -78,7 +78,7 @@ class IslamicPrayerTimeSensor(
|
|||||||
"""Initialize the Islamic prayer time sensor."""
|
"""Initialize the Islamic prayer time sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = description.key
|
self._attr_unique_id = f"{coordinator.config_entry.entry_id}-{description.key}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
|
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
|
||||||
name=NAME,
|
name=NAME,
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
"""Matter lock."""
|
"""Matter lock."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import IntFlag
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from chip.clusters import Objects as clusters
|
from chip.clusters import Objects as clusters
|
||||||
|
|
||||||
from homeassistant.components.lock import LockEntity, LockEntityDescription
|
from homeassistant.components.lock import (
|
||||||
|
LockEntity,
|
||||||
|
LockEntityDescription,
|
||||||
|
LockEntityFeature,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_CODE, Platform
|
from homeassistant.const import ATTR_CODE, Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
@ -17,6 +20,8 @@ from .entity import MatterEntity
|
|||||||
from .helpers import get_matter
|
from .helpers import get_matter
|
||||||
from .models import MatterDiscoverySchema
|
from .models import MatterDiscoverySchema
|
||||||
|
|
||||||
|
DoorLockFeature = clusters.DoorLock.Bitmaps.Feature
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -61,6 +66,14 @@ class MatterLock(MatterEntity, LockEntity):
|
|||||||
|
|
||||||
return bool(self.features & DoorLockFeature.kDoorPositionSensor)
|
return bool(self.features & DoorLockFeature.kDoorPositionSensor)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supports_unbolt(self) -> bool:
|
||||||
|
"""Return True if the lock supports unbolt."""
|
||||||
|
if self.features is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return bool(self.features & DoorLockFeature.kUnbolt)
|
||||||
|
|
||||||
async def send_device_command(
|
async def send_device_command(
|
||||||
self,
|
self,
|
||||||
command: clusters.ClusterCommand,
|
command: clusters.ClusterCommand,
|
||||||
@ -92,6 +105,25 @@ class MatterLock(MatterEntity, LockEntity):
|
|||||||
self._lock_option_default_code,
|
self._lock_option_default_code,
|
||||||
)
|
)
|
||||||
code_bytes = code.encode() if code else None
|
code_bytes = code.encode() if code else None
|
||||||
|
if self.supports_unbolt:
|
||||||
|
# if the lock reports it has separate unbolt support,
|
||||||
|
# the unlock command should unbolt only on the unlock command
|
||||||
|
# and unlatch on the HA 'open' command.
|
||||||
|
await self.send_device_command(
|
||||||
|
command=clusters.DoorLock.Commands.UnboltDoor(code_bytes)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_device_command(
|
||||||
|
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_open(self, **kwargs: Any) -> None:
|
||||||
|
"""Open the door latch."""
|
||||||
|
code: str = kwargs.get(
|
||||||
|
ATTR_CODE,
|
||||||
|
self._lock_option_default_code,
|
||||||
|
)
|
||||||
|
code_bytes = code.encode() if code else None
|
||||||
await self.send_device_command(
|
await self.send_device_command(
|
||||||
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
|
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
|
||||||
)
|
)
|
||||||
@ -104,6 +136,8 @@ class MatterLock(MatterEntity, LockEntity):
|
|||||||
self.features = int(
|
self.features = int(
|
||||||
self.get_matter_attribute_value(clusters.DoorLock.Attributes.FeatureMap)
|
self.get_matter_attribute_value(clusters.DoorLock.Attributes.FeatureMap)
|
||||||
)
|
)
|
||||||
|
if self.supports_unbolt:
|
||||||
|
self._attr_supported_features = LockEntityFeature.OPEN
|
||||||
|
|
||||||
lock_state = self.get_matter_attribute_value(
|
lock_state = self.get_matter_attribute_value(
|
||||||
clusters.DoorLock.Attributes.LockState
|
clusters.DoorLock.Attributes.LockState
|
||||||
@ -144,26 +178,6 @@ class MatterLock(MatterEntity, LockEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DoorLockFeature(IntFlag):
|
|
||||||
"""Temp enum that represents the features of a door lock.
|
|
||||||
|
|
||||||
Should be replaced by the library provided one once that is released.
|
|
||||||
"""
|
|
||||||
|
|
||||||
kPinCredential = 0x1 # noqa: N815
|
|
||||||
kRfidCredential = 0x2 # noqa: N815
|
|
||||||
kFingerCredentials = 0x4 # noqa: N815
|
|
||||||
kLogging = 0x8 # noqa: N815
|
|
||||||
kWeekDayAccessSchedules = 0x10 # noqa: N815
|
|
||||||
kDoorPositionSensor = 0x20 # noqa: N815
|
|
||||||
kFaceCredentials = 0x40 # noqa: N815
|
|
||||||
kCredentialsOverTheAirAccess = 0x80 # noqa: N815
|
|
||||||
kUser = 0x100 # noqa: N815
|
|
||||||
kNotification = 0x200 # noqa: N815
|
|
||||||
kYearDayAccessSchedules = 0x400 # noqa: N815
|
|
||||||
kHolidaySchedules = 0x800 # noqa: N815
|
|
||||||
|
|
||||||
|
|
||||||
DISCOVERY_SCHEMAS = [
|
DISCOVERY_SCHEMAS = [
|
||||||
MatterDiscoverySchema(
|
MatterDiscoverySchema(
|
||||||
platform=Platform.LOCK,
|
platform=Platform.LOCK,
|
||||||
|
@ -31,13 +31,21 @@ from homeassistant.const import (
|
|||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er, sun
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||||
|
|
||||||
from . import MetDataUpdateCoordinator
|
from . import MetDataUpdateCoordinator
|
||||||
from .const import ATTR_MAP, CONDITIONS_MAP, CONF_TRACK_HOME, DOMAIN, FORECAST_MAP
|
from .const import (
|
||||||
|
ATTR_CONDITION_CLEAR_NIGHT,
|
||||||
|
ATTR_CONDITION_SUNNY,
|
||||||
|
ATTR_MAP,
|
||||||
|
CONDITIONS_MAP,
|
||||||
|
CONF_TRACK_HOME,
|
||||||
|
DOMAIN,
|
||||||
|
FORECAST_MAP,
|
||||||
|
)
|
||||||
|
|
||||||
DEFAULT_NAME = "Met.no"
|
DEFAULT_NAME = "Met.no"
|
||||||
|
|
||||||
@ -141,6 +149,10 @@ class MetWeather(SingleCoordinatorWeatherEntity[MetDataUpdateCoordinator]):
|
|||||||
condition = self.coordinator.data.current_weather_data.get("condition")
|
condition = self.coordinator.data.current_weather_data.get("condition")
|
||||||
if condition is None:
|
if condition is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if condition == ATTR_CONDITION_SUNNY and not sun.is_up(self.hass):
|
||||||
|
condition = ATTR_CONDITION_CLEAR_NIGHT
|
||||||
|
|
||||||
return format_condition(condition)
|
return format_condition(condition)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -42,6 +42,7 @@ from .mixins import (
|
|||||||
MqttAvailability,
|
MqttAvailability,
|
||||||
MqttEntity,
|
MqttEntity,
|
||||||
async_setup_entity_entry_helper,
|
async_setup_entity_entry_helper,
|
||||||
|
validate_sensor_entity_category,
|
||||||
write_state_on_attr_change,
|
write_state_on_attr_change,
|
||||||
)
|
)
|
||||||
from .models import MqttValueTemplate, ReceiveMessage
|
from .models import MqttValueTemplate, ReceiveMessage
|
||||||
@ -55,7 +56,7 @@ DEFAULT_PAYLOAD_ON = "ON"
|
|||||||
DEFAULT_FORCE_UPDATE = False
|
DEFAULT_FORCE_UPDATE = False
|
||||||
CONF_EXPIRE_AFTER = "expire_after"
|
CONF_EXPIRE_AFTER = "expire_after"
|
||||||
|
|
||||||
PLATFORM_SCHEMA_MODERN = MQTT_RO_SCHEMA.extend(
|
_PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
||||||
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
|
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
|
||||||
@ -67,7 +68,12 @@ PLATFORM_SCHEMA_MODERN = MQTT_RO_SCHEMA.extend(
|
|||||||
}
|
}
|
||||||
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
|
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
|
||||||
|
|
||||||
DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)
|
DISCOVERY_SCHEMA = vol.All(
|
||||||
|
validate_sensor_entity_category,
|
||||||
|
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
||||||
|
)
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA_MODERN = vol.All(validate_sensor_entity_category, _PLATFORM_SCHEMA_BASE)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -232,16 +232,16 @@ TOPIC_KEYS = (
|
|||||||
def valid_preset_mode_configuration(config: ConfigType) -> ConfigType:
|
def valid_preset_mode_configuration(config: ConfigType) -> ConfigType:
|
||||||
"""Validate that the preset mode reset payload is not one of the preset modes."""
|
"""Validate that the preset mode reset payload is not one of the preset modes."""
|
||||||
if PRESET_NONE in config[CONF_PRESET_MODES_LIST]:
|
if PRESET_NONE in config[CONF_PRESET_MODES_LIST]:
|
||||||
raise ValueError("preset_modes must not include preset mode 'none'")
|
raise vol.Invalid("preset_modes must not include preset mode 'none'")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def valid_humidity_range_configuration(config: ConfigType) -> ConfigType:
|
def valid_humidity_range_configuration(config: ConfigType) -> ConfigType:
|
||||||
"""Validate a target_humidity range configuration, throws otherwise."""
|
"""Validate a target_humidity range configuration, throws otherwise."""
|
||||||
if config[CONF_HUMIDITY_MIN] >= config[CONF_HUMIDITY_MAX]:
|
if config[CONF_HUMIDITY_MIN] >= config[CONF_HUMIDITY_MAX]:
|
||||||
raise ValueError("target_humidity_max must be > target_humidity_min")
|
raise vol.Invalid("target_humidity_max must be > target_humidity_min")
|
||||||
if config[CONF_HUMIDITY_MAX] > 100:
|
if config[CONF_HUMIDITY_MAX] > 100:
|
||||||
raise ValueError("max_humidity must be <= 100")
|
raise vol.Invalid("max_humidity must be <= 100")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -116,16 +116,16 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def valid_speed_range_configuration(config: ConfigType) -> ConfigType:
|
def valid_speed_range_configuration(config: ConfigType) -> ConfigType:
|
||||||
"""Validate that the fan speed_range configuration is valid, throws if it isn't."""
|
"""Validate that the fan speed_range configuration is valid, throws if it isn't."""
|
||||||
if config[CONF_SPEED_RANGE_MIN] == 0:
|
if config[CONF_SPEED_RANGE_MIN] == 0:
|
||||||
raise ValueError("speed_range_min must be > 0")
|
raise vol.Invalid("speed_range_min must be > 0")
|
||||||
if config[CONF_SPEED_RANGE_MIN] >= config[CONF_SPEED_RANGE_MAX]:
|
if config[CONF_SPEED_RANGE_MIN] >= config[CONF_SPEED_RANGE_MAX]:
|
||||||
raise ValueError("speed_range_max must be > speed_range_min")
|
raise vol.Invalid("speed_range_max must be > speed_range_min")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def valid_preset_mode_configuration(config: ConfigType) -> ConfigType:
|
def valid_preset_mode_configuration(config: ConfigType) -> ConfigType:
|
||||||
"""Validate that the preset mode reset payload is not one of the preset modes."""
|
"""Validate that the preset mode reset payload is not one of the preset modes."""
|
||||||
if config[CONF_PAYLOAD_RESET_PRESET_MODE] in config[CONF_PRESET_MODES_LIST]:
|
if config[CONF_PAYLOAD_RESET_PRESET_MODE] in config[CONF_PRESET_MODES_LIST]:
|
||||||
raise ValueError("preset_modes must not contain payload_reset_preset_mode")
|
raise vol.Invalid("preset_modes must not contain payload_reset_preset_mode")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def valid_mode_configuration(config: ConfigType) -> ConfigType:
|
def valid_mode_configuration(config: ConfigType) -> ConfigType:
|
||||||
"""Validate that the mode reset payload is not one of the available modes."""
|
"""Validate that the mode reset payload is not one of the available modes."""
|
||||||
if config[CONF_PAYLOAD_RESET_MODE] in config[CONF_AVAILABLE_MODES_LIST]:
|
if config[CONF_PAYLOAD_RESET_MODE] in config[CONF_AVAILABLE_MODES_LIST]:
|
||||||
raise ValueError("modes must not contain payload_reset_mode")
|
raise vol.Invalid("modes must not contain payload_reset_mode")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@ -113,9 +113,9 @@ def valid_humidity_range_configuration(config: ConfigType) -> ConfigType:
|
|||||||
throws if it isn't.
|
throws if it isn't.
|
||||||
"""
|
"""
|
||||||
if config[CONF_TARGET_HUMIDITY_MIN] >= config[CONF_TARGET_HUMIDITY_MAX]:
|
if config[CONF_TARGET_HUMIDITY_MIN] >= config[CONF_TARGET_HUMIDITY_MAX]:
|
||||||
raise ValueError("target_humidity_max must be > target_humidity_min")
|
raise vol.Invalid("target_humidity_max must be > target_humidity_min")
|
||||||
if config[CONF_TARGET_HUMIDITY_MAX] > 100:
|
if config[CONF_TARGET_HUMIDITY_MAX] > 100:
|
||||||
raise ValueError("max_humidity must be <= 100")
|
raise vol.Invalid("max_humidity must be <= 100")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import logging
|
|||||||
from typing import TYPE_CHECKING, Any, Protocol, cast, final
|
from typing import TYPE_CHECKING, Any, Protocol, cast, final
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import yaml
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -28,6 +27,7 @@ from homeassistant.const import (
|
|||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
CONF_VALUE_TEMPLATE,
|
CONF_VALUE_TEMPLATE,
|
||||||
|
EntityCategory,
|
||||||
)
|
)
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
@ -63,6 +63,7 @@ from homeassistant.helpers.typing import (
|
|||||||
UndefinedType,
|
UndefinedType,
|
||||||
)
|
)
|
||||||
from homeassistant.util.json import json_loads
|
from homeassistant.util.json import json_loads
|
||||||
|
from homeassistant.util.yaml import dump as yaml_dump
|
||||||
|
|
||||||
from . import debug_info, subscription
|
from . import debug_info, subscription
|
||||||
from .client import async_publish
|
from .client import async_publish
|
||||||
@ -207,6 +208,16 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_sensor_entity_category(config: ConfigType) -> ConfigType:
|
||||||
|
"""Check the sensor's entity category is not set to `config` which is invalid for sensors."""
|
||||||
|
if (
|
||||||
|
CONF_ENTITY_CATEGORY in config
|
||||||
|
and config[CONF_ENTITY_CATEGORY] == EntityCategory.CONFIG
|
||||||
|
):
|
||||||
|
raise vol.Invalid("Entity category `config` is invalid")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All(
|
MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE),
|
cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
@ -404,8 +415,8 @@ async def async_setup_entity_entry_helper(
|
|||||||
error = str(ex)
|
error = str(ex)
|
||||||
config_file = getattr(yaml_config, "__config_file__", "?")
|
config_file = getattr(yaml_config, "__config_file__", "?")
|
||||||
line = getattr(yaml_config, "__line__", "?")
|
line = getattr(yaml_config, "__line__", "?")
|
||||||
issue_id = hex(hash(frozenset(yaml_config.items())))
|
issue_id = hex(hash(frozenset(yaml_config)))
|
||||||
yaml_config_str = yaml.dump(dict(yaml_config))
|
yaml_config_str = yaml_dump(yaml_config)
|
||||||
learn_more_url = (
|
learn_more_url = (
|
||||||
f"https://www.home-assistant.io/integrations/{domain}.mqtt/"
|
f"https://www.home-assistant.io/integrations/{domain}.mqtt/"
|
||||||
)
|
)
|
||||||
@ -427,7 +438,7 @@ async def async_setup_entity_entry_helper(
|
|||||||
translation_key="invalid_platform_config",
|
translation_key="invalid_platform_config",
|
||||||
)
|
)
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"%s for manual configured MQTT %s item, in %s, line %s Got %s",
|
"%s for manually configured MQTT %s item, in %s, line %s Got %s",
|
||||||
error,
|
error,
|
||||||
domain,
|
domain,
|
||||||
config_file,
|
config_file,
|
||||||
|
@ -44,6 +44,7 @@ from .mixins import (
|
|||||||
MqttAvailability,
|
MqttAvailability,
|
||||||
MqttEntity,
|
MqttEntity,
|
||||||
async_setup_entity_entry_helper,
|
async_setup_entity_entry_helper,
|
||||||
|
validate_sensor_entity_category,
|
||||||
write_state_on_attr_change,
|
write_state_on_attr_change,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -70,7 +71,6 @@ MQTT_SENSOR_ATTRIBUTES_BLOCKED = frozenset(
|
|||||||
DEFAULT_NAME = "MQTT Sensor"
|
DEFAULT_NAME = "MQTT Sensor"
|
||||||
DEFAULT_FORCE_UPDATE = False
|
DEFAULT_FORCE_UPDATE = False
|
||||||
|
|
||||||
|
|
||||||
_PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
_PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
||||||
@ -88,6 +88,7 @@ PLATFORM_SCHEMA_MODERN = vol.All(
|
|||||||
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
|
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
|
||||||
# Removed in HA Core 2023.6.0
|
# Removed in HA Core 2023.6.0
|
||||||
cv.removed(CONF_LAST_RESET_TOPIC),
|
cv.removed(CONF_LAST_RESET_TOPIC),
|
||||||
|
validate_sensor_entity_category,
|
||||||
_PLATFORM_SCHEMA_BASE,
|
_PLATFORM_SCHEMA_BASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -95,6 +96,7 @@ DISCOVERY_SCHEMA = vol.All(
|
|||||||
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
|
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
|
||||||
# Removed in HA Core 2023.6.0
|
# Removed in HA Core 2023.6.0
|
||||||
cv.removed(CONF_LAST_RESET_TOPIC),
|
cv.removed(CONF_LAST_RESET_TOPIC),
|
||||||
|
validate_sensor_entity_category,
|
||||||
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,9 +71,9 @@ MQTT_TEXT_ATTRIBUTES_BLOCKED = frozenset(
|
|||||||
def valid_text_size_configuration(config: ConfigType) -> ConfigType:
|
def valid_text_size_configuration(config: ConfigType) -> ConfigType:
|
||||||
"""Validate that the text length configuration is valid, throws if it isn't."""
|
"""Validate that the text length configuration is valid, throws if it isn't."""
|
||||||
if config[CONF_MIN] >= config[CONF_MAX]:
|
if config[CONF_MIN] >= config[CONF_MAX]:
|
||||||
raise ValueError("text length min must be >= max")
|
raise vol.Invalid("text length min must be >= max")
|
||||||
if config[CONF_MAX] > MAX_LENGTH_STATE_STATE:
|
if config[CONF_MAX] > MAX_LENGTH_STATE_STATE:
|
||||||
raise ValueError(f"max text length must be <= {MAX_LENGTH_STATE_STATE}")
|
raise vol.Invalid(f"max text length must be <= {MAX_LENGTH_STATE_STATE}")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/nextbus",
|
"documentation": "https://www.home-assistant.io/integrations/nextbus",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["py_nextbus"],
|
"loggers": ["py_nextbus"],
|
||||||
"requirements": ["py-nextbusnext==1.0.0"]
|
"requirements": ["py-nextbusnext==1.0.2"]
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["opower"],
|
"loggers": ["opower"],
|
||||||
"requirements": ["opower==0.0.38"]
|
"requirements": ["opower==0.0.39"]
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ SELECT_TYPES = (
|
|||||||
key="select_schedule",
|
key="select_schedule",
|
||||||
translation_key="select_schedule",
|
translation_key="select_schedule",
|
||||||
icon="mdi:calendar-clock",
|
icon="mdi:calendar-clock",
|
||||||
command=lambda api, loc, opt: api.set_schedule_state(loc, opt, STATE_ON),
|
command=lambda api, loc, opt: api.set_schedule_state(loc, STATE_ON, opt),
|
||||||
options_key="available_schedules",
|
options_key="available_schedules",
|
||||||
),
|
),
|
||||||
PlugwiseSelectEntityDescription(
|
PlugwiseSelectEntityDescription(
|
||||||
|
@ -10,6 +10,7 @@ from typing import Literal
|
|||||||
|
|
||||||
from reolink_aio.api import RETRY_ATTEMPTS
|
from reolink_aio.api import RETRY_ATTEMPTS
|
||||||
from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError
|
from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError
|
||||||
|
from reolink_aio.software_version import NewSoftwareVersion
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
|
||||||
@ -45,7 +46,9 @@ class ReolinkData:
|
|||||||
|
|
||||||
host: ReolinkHost
|
host: ReolinkHost
|
||||||
device_coordinator: DataUpdateCoordinator[None]
|
device_coordinator: DataUpdateCoordinator[None]
|
||||||
firmware_coordinator: DataUpdateCoordinator[str | Literal[False]]
|
firmware_coordinator: DataUpdateCoordinator[
|
||||||
|
str | Literal[False] | NewSoftwareVersion
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
@ -86,7 +89,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
async with asyncio.timeout(host.api.timeout * (RETRY_ATTEMPTS + 2)):
|
async with asyncio.timeout(host.api.timeout * (RETRY_ATTEMPTS + 2)):
|
||||||
await host.renew()
|
await host.renew()
|
||||||
|
|
||||||
async def async_check_firmware_update() -> str | Literal[False]:
|
async def async_check_firmware_update() -> str | Literal[
|
||||||
|
False
|
||||||
|
] | NewSoftwareVersion:
|
||||||
"""Check for firmware updates."""
|
"""Check for firmware updates."""
|
||||||
if not host.api.supported(None, "update"):
|
if not host.api.supported(None, "update"):
|
||||||
return False
|
return False
|
||||||
@ -153,7 +158,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def entry_update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
|
async def entry_update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||||
"""Update the configuration of the host entity."""
|
"""Update the configuration of the host entity."""
|
||||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ from .entity import ReolinkChannelCoordinatorEntity
|
|||||||
class ReolinkBinarySensorEntityDescriptionMixin:
|
class ReolinkBinarySensorEntityDescriptionMixin:
|
||||||
"""Mixin values for Reolink binary sensor entities."""
|
"""Mixin values for Reolink binary sensor entities."""
|
||||||
|
|
||||||
value: Callable[[Host, int | None], bool]
|
value: Callable[[Host, int], bool]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -43,7 +43,7 @@ class ReolinkBinarySensorEntityDescription(
|
|||||||
|
|
||||||
icon: str = "mdi:motion-sensor"
|
icon: str = "mdi:motion-sensor"
|
||||||
icon_off: str = "mdi:motion-sensor-off"
|
icon_off: str = "mdi:motion-sensor-off"
|
||||||
supported: Callable[[Host, int | None], bool] = lambda host, ch: True
|
supported: Callable[[Host, int], bool] = lambda host, ch: True
|
||||||
|
|
||||||
|
|
||||||
BINARY_SENSORS = (
|
BINARY_SENSORS = (
|
||||||
@ -169,6 +169,6 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_handle_event(self, event):
|
async def _async_handle_event(self, event: str) -> None:
|
||||||
"""Handle incoming event for motion detection."""
|
"""Handle incoming event for motion detection."""
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, Literal
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from aiohttp.web import Request
|
from aiohttp.web import Request
|
||||||
@ -81,7 +81,7 @@ class ReolinkHost:
|
|||||||
return self._unique_id
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self) -> Host:
|
||||||
"""Return the API object."""
|
"""Return the API object."""
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ class ReolinkHost:
|
|||||||
"""Call the API of the camera device to update the internal states."""
|
"""Call the API of the camera device to update the internal states."""
|
||||||
await self._api.get_states()
|
await self._api.get_states()
|
||||||
|
|
||||||
async def disconnect(self):
|
async def disconnect(self) -> None:
|
||||||
"""Disconnect from the API, so the connection will be released."""
|
"""Disconnect from the API, so the connection will be released."""
|
||||||
try:
|
try:
|
||||||
await self._api.unsubscribe()
|
await self._api.unsubscribe()
|
||||||
@ -335,7 +335,7 @@ class ReolinkHost:
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_start_long_polling(self, initial=False):
|
async def _async_start_long_polling(self, initial=False) -> None:
|
||||||
"""Start ONVIF long polling task."""
|
"""Start ONVIF long polling task."""
|
||||||
if self._long_poll_task is None:
|
if self._long_poll_task is None:
|
||||||
try:
|
try:
|
||||||
@ -364,7 +364,7 @@ class ReolinkHost:
|
|||||||
self._lost_subscription = False
|
self._lost_subscription = False
|
||||||
self._long_poll_task = asyncio.create_task(self._async_long_polling())
|
self._long_poll_task = asyncio.create_task(self._async_long_polling())
|
||||||
|
|
||||||
async def _async_stop_long_polling(self):
|
async def _async_stop_long_polling(self) -> None:
|
||||||
"""Stop ONVIF long polling task."""
|
"""Stop ONVIF long polling task."""
|
||||||
if self._long_poll_task is not None:
|
if self._long_poll_task is not None:
|
||||||
self._long_poll_task.cancel()
|
self._long_poll_task.cancel()
|
||||||
@ -372,7 +372,7 @@ class ReolinkHost:
|
|||||||
|
|
||||||
await self._api.unsubscribe(sub_type=SubType.long_poll)
|
await self._api.unsubscribe(sub_type=SubType.long_poll)
|
||||||
|
|
||||||
async def stop(self, event=None):
|
async def stop(self, event=None) -> None:
|
||||||
"""Disconnect the API."""
|
"""Disconnect the API."""
|
||||||
if self._cancel_poll is not None:
|
if self._cancel_poll is not None:
|
||||||
self._cancel_poll()
|
self._cancel_poll()
|
||||||
@ -433,7 +433,7 @@ class ReolinkHost:
|
|||||||
else:
|
else:
|
||||||
self._lost_subscription = False
|
self._lost_subscription = False
|
||||||
|
|
||||||
async def _renew(self, sub_type: SubType) -> None:
|
async def _renew(self, sub_type: Literal[SubType.push, SubType.long_poll]) -> None:
|
||||||
"""Execute the renew of the subscription."""
|
"""Execute the renew of the subscription."""
|
||||||
if not self._api.subscribed(sub_type):
|
if not self._api.subscribed(sub_type):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@ -512,8 +512,10 @@ class ReolinkHost:
|
|||||||
|
|
||||||
_LOGGER.debug("Registered webhook: %s", event_id)
|
_LOGGER.debug("Registered webhook: %s", event_id)
|
||||||
|
|
||||||
def unregister_webhook(self):
|
def unregister_webhook(self) -> None:
|
||||||
"""Unregister the webhook for motion events."""
|
"""Unregister the webhook for motion events."""
|
||||||
|
if self.webhook_id is None:
|
||||||
|
return
|
||||||
_LOGGER.debug("Unregistering webhook %s", self.webhook_id)
|
_LOGGER.debug("Unregistering webhook %s", self.webhook_id)
|
||||||
webhook.async_unregister(self._hass, self.webhook_id)
|
webhook.async_unregister(self._hass, self.webhook_id)
|
||||||
self.webhook_id = None
|
self.webhook_id = None
|
||||||
|
@ -38,8 +38,8 @@ class ReolinkLightEntityDescription(
|
|||||||
"""A class that describes light entities."""
|
"""A class that describes light entities."""
|
||||||
|
|
||||||
supported_fn: Callable[[Host, int], bool] = lambda api, ch: True
|
supported_fn: Callable[[Host, int], bool] = lambda api, ch: True
|
||||||
get_brightness_fn: Callable[[Host, int], int] | None = None
|
get_brightness_fn: Callable[[Host, int], int | None] | None = None
|
||||||
set_brightness_fn: Callable[[Host, int, float], Any] | None = None
|
set_brightness_fn: Callable[[Host, int, int], Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
LIGHT_ENTITIES = (
|
LIGHT_ENTITIES = (
|
||||||
@ -127,13 +127,13 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
|
|||||||
if self.entity_description.get_brightness_fn is None:
|
if self.entity_description.get_brightness_fn is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return round(
|
bright_pct = self.entity_description.get_brightness_fn(
|
||||||
255
|
self._host.api, self._channel
|
||||||
* (
|
|
||||||
self.entity_description.get_brightness_fn(self._host.api, self._channel)
|
|
||||||
/ 100.0
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
if bright_pct is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return round(255 * bright_pct / 100.0)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn light off."""
|
"""Turn light off."""
|
||||||
|
@ -18,5 +18,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["reolink_aio"],
|
"loggers": ["reolink_aio"],
|
||||||
"requirements": ["reolink-aio==0.7.12"]
|
"requirements": ["reolink-aio==0.7.14"]
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ from .entity import ReolinkChannelCoordinatorEntity
|
|||||||
class ReolinkNumberEntityDescriptionMixin:
|
class ReolinkNumberEntityDescriptionMixin:
|
||||||
"""Mixin values for Reolink number entities."""
|
"""Mixin values for Reolink number entities."""
|
||||||
|
|
||||||
value: Callable[[Host, int], float]
|
value: Callable[[Host, int], float | None]
|
||||||
method: Callable[[Host, int, float], Any]
|
method: Callable[[Host, int, float], Any]
|
||||||
|
|
||||||
|
|
||||||
@ -354,7 +354,7 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float:
|
def native_value(self) -> float | None:
|
||||||
"""State of the number entity."""
|
"""State of the number entity."""
|
||||||
return self.entity_description.value(self._host.api, self._channel)
|
return self.entity_description.value(self._host.api, self._channel)
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class ReolinkSensorEntityDescription(
|
|||||||
class ReolinkHostSensorEntityDescriptionMixin:
|
class ReolinkHostSensorEntityDescriptionMixin:
|
||||||
"""Mixin values for Reolink host sensor entities."""
|
"""Mixin values for Reolink host sensor entities."""
|
||||||
|
|
||||||
value: Callable[[Host], int]
|
value: Callable[[Host], int | None]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -35,7 +35,8 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
|
|
||||||
class ReolinkUpdateEntity(
|
class ReolinkUpdateEntity(
|
||||||
ReolinkBaseCoordinatorEntity[str | Literal[False]], UpdateEntity
|
ReolinkBaseCoordinatorEntity[str | Literal[False] | NewSoftwareVersion],
|
||||||
|
UpdateEntity,
|
||||||
):
|
):
|
||||||
"""Update entity for a Netgear device."""
|
"""Update entity for a Netgear device."""
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ class IRobotEntity(Entity):
|
|||||||
@property
|
@property
|
||||||
def battery_stats(self):
|
def battery_stats(self):
|
||||||
"""Return the battery stats."""
|
"""Return the battery stats."""
|
||||||
return self.vacuum_state.get("bbchg3")
|
return self.vacuum_state.get("bbchg3", {})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _robot_state(self):
|
def _robot_state(self):
|
||||||
|
@ -106,7 +106,7 @@ SENSORS: list[RoombaSensorEntityDescription] = [
|
|||||||
),
|
),
|
||||||
RoombaSensorEntityDescription(
|
RoombaSensorEntityDescription(
|
||||||
key="scrubs_count",
|
key="scrubs_count",
|
||||||
translation_key="scrubs",
|
translation_key="scrubs_count",
|
||||||
icon="mdi:counter",
|
icon="mdi:counter",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement="Scrubs",
|
native_unit_of_measurement="Scrubs",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"token": "[%key:common::config_flow::data::api_token%]"
|
"token": "[%key:common::config_flow::data::api_token%]"
|
||||||
},
|
},
|
||||||
"description": "Please entry your API token from your [Todoist Settings page]({settings_url})"
|
"description": "Please enter your API token from your [Todoist Settings page]({settings_url})"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
@ -50,7 +50,8 @@ WEBHOOK_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(ATTR_BEARING): vol.Coerce(float),
|
vol.Optional(ATTR_BEARING): vol.Coerce(float),
|
||||||
vol.Optional(ATTR_SPEED): vol.Coerce(float),
|
vol.Optional(ATTR_SPEED): vol.Coerce(float),
|
||||||
vol.Optional(ATTR_TIMESTAMP): vol.Coerce(int),
|
vol.Optional(ATTR_TIMESTAMP): vol.Coerce(int),
|
||||||
}
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiounifi"],
|
"loggers": ["aiounifi"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aiounifi==64"],
|
"requirements": ["aiounifi==65"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Ubiquiti Networks",
|
"manufacturer": "Ubiquiti Networks",
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/waqi",
|
"documentation": "https://www.home-assistant.io/integrations/waqi",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aiowaqi"],
|
"loggers": ["aiowaqi"],
|
||||||
"requirements": ["aiowaqi==2.1.0"]
|
"requirements": ["aiowaqi==3.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["holidays"],
|
"loggers": ["holidays"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["holidays==0.28"]
|
"requirements": ["holidays==0.35"]
|
||||||
}
|
}
|
||||||
|
@ -12,5 +12,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/yalexs_ble",
|
"documentation": "https://www.home-assistant.io/integrations/yalexs_ble",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"requirements": ["yalexs-ble==2.3.1"]
|
"requirements": ["yalexs-ble==2.3.2"]
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,20 @@ The Z-Wave integration uses a discovery mechanism to create the necessary entiti
|
|||||||
|
|
||||||
In cases where an entity's functionality requires interaction with multiple Values, the discovery rule for that particular entity type is based on the primary Value, or the Value that must be there to indicate that this entity needs to be created, and then the rest of the Values required are discovered by the class instance for that entity. A good example of this is the discovery logic for the `climate` entity. Currently, the discovery logic is tied to the discovery of a Value with a property of `mode` and a command class of `Thermostat Mode`, but the actual entity uses many more Values than that to be fully functional as evident in the [code](./climate.py).
|
In cases where an entity's functionality requires interaction with multiple Values, the discovery rule for that particular entity type is based on the primary Value, or the Value that must be there to indicate that this entity needs to be created, and then the rest of the Values required are discovered by the class instance for that entity. A good example of this is the discovery logic for the `climate` entity. Currently, the discovery logic is tied to the discovery of a Value with a property of `mode` and a command class of `Thermostat Mode`, but the actual entity uses many more Values than that to be fully functional as evident in the [code](./climate.py).
|
||||||
|
|
||||||
There are several ways that device support can be improved within Home Assistant, but regardless of the reason, it is important to add device specific tests in these use cases. To do so, add the device's data (from device diagnostics) to the [fixtures folder](../../../tests/components/zwave_js/fixtures) and then define the new fixtures in [conftest.py](../../../tests/components/zwave_js/conftest.py). Use existing tests as the model but the tests can go in the [test_discovery.py module](../../../tests/components/zwave_js/test_discovery.py).
|
There are several ways that device support can be improved within Home Assistant, but regardless of the reason, it is important to add device specific tests in these use cases. To do so, add the device's data to the [fixtures folder](../../../tests/components/zwave_js/fixtures) and then define the new fixtures in [conftest.py](../../../tests/components/zwave_js/conftest.py). Use existing tests as the model but the tests can go in the [test_discovery.py module](../../../tests/components/zwave_js/test_discovery.py). To learn how to generate fixtures, see the following section.
|
||||||
|
|
||||||
|
### Generating device fixtures
|
||||||
|
|
||||||
|
To generate a device fixture, download a diagnostics dump of the device from your Home Assistant instance. The dumped data will need to be modified to match the expected format. You can always do this transformation by hand, but the integration provides a [helper script](scripts/convert_device_diagnostics_to_fixture.py) that will generate the appropriate fixture data from a device diagnostics dump for you. To use it, run the script with the path to the diagnostics dump you downloaded:
|
||||||
|
|
||||||
|
`python homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py <path/to/diagnostics/dump>`
|
||||||
|
|
||||||
|
The script will print the fixture data to standard output, and you can use Unix piping to create a file from the fixture data:
|
||||||
|
|
||||||
|
`python homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py <path/to/diagnostics/dump> > <path_to_fixture_output>`
|
||||||
|
|
||||||
|
You can alternatively pass the `--file` flag to the script and it will create the file for you in the [fixtures folder](../../../tests/components/zwave_js/fixtures):
|
||||||
|
`python homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py <path/to/diagnostics/dump> --file`
|
||||||
|
|
||||||
### Switching HA support for a device from one entity type to another.
|
### Switching HA support for a device from one entity type to another.
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ from zwave_js_server.const.command_class.multilevel_switch import (
|
|||||||
from zwave_js_server.const.command_class.window_covering import (
|
from zwave_js_server.const.command_class.window_covering import (
|
||||||
NO_POSITION_PROPERTY_KEYS,
|
NO_POSITION_PROPERTY_KEYS,
|
||||||
NO_POSITION_SUFFIX,
|
NO_POSITION_SUFFIX,
|
||||||
WINDOW_COVERING_OPEN_PROPERTY,
|
|
||||||
SlatStates,
|
SlatStates,
|
||||||
)
|
)
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
@ -370,7 +369,7 @@ class ZWaveWindowCovering(CoverPositionMixin, CoverTiltMixin):
|
|||||||
set_values_func(
|
set_values_func(
|
||||||
value,
|
value,
|
||||||
stop_value=self.get_zwave_value(
|
stop_value=self.get_zwave_value(
|
||||||
WINDOW_COVERING_OPEN_PROPERTY,
|
"levelChangeUp",
|
||||||
value_property_key=value.property_key,
|
value_property_key=value.property_key,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
1
homeassistant/components/zwave_js/scripts/__init__.py
Normal file
1
homeassistant/components/zwave_js/scripts/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Scripts module for Z-Wave JS."""
|
@ -0,0 +1,93 @@
|
|||||||
|
"""Script to convert a device diagnostics file to a fixture."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
|
|
||||||
|
def get_arguments() -> argparse.Namespace:
|
||||||
|
"""Get parsed passed in arguments."""
|
||||||
|
parser = argparse.ArgumentParser(description="Z-Wave JS Fixture generator")
|
||||||
|
parser.add_argument(
|
||||||
|
"diagnostics_file", type=Path, help="Device diagnostics file to convert"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--file",
|
||||||
|
action="store_true",
|
||||||
|
help=(
|
||||||
|
"Dump fixture to file in fixtures folder. By default, the fixture will be "
|
||||||
|
"printed to standard output."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
|
||||||
|
return arguments
|
||||||
|
|
||||||
|
|
||||||
|
def get_fixtures_dir_path(data: dict) -> Path:
|
||||||
|
"""Get path to fixtures directory."""
|
||||||
|
device_config = data["deviceConfig"]
|
||||||
|
filename = slugify(
|
||||||
|
f"{device_config['manufacturer']}-{device_config['label']}_state"
|
||||||
|
)
|
||||||
|
path = Path(__file__).parents[1]
|
||||||
|
index = path.parts.index("homeassistant")
|
||||||
|
return Path(
|
||||||
|
*path.parts[:index],
|
||||||
|
"tests",
|
||||||
|
*path.parts[index + 1 :],
|
||||||
|
"fixtures",
|
||||||
|
f"{filename}.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_file(path: Path) -> Any:
|
||||||
|
"""Load file from path."""
|
||||||
|
return json.loads(path.read_text("utf8"))
|
||||||
|
|
||||||
|
|
||||||
|
def extract_fixture_data(diagnostics_data: Any) -> dict:
|
||||||
|
"""Extract fixture data from file."""
|
||||||
|
if (
|
||||||
|
not isinstance(diagnostics_data, dict)
|
||||||
|
or "data" not in diagnostics_data
|
||||||
|
or "state" not in diagnostics_data["data"]
|
||||||
|
):
|
||||||
|
raise ValueError("Invalid diagnostics file format")
|
||||||
|
state: dict = diagnostics_data["data"]["state"]
|
||||||
|
if not isinstance(state["values"], list):
|
||||||
|
values_dict: dict[str, dict] = state.pop("values")
|
||||||
|
state["values"] = list(values_dict.values())
|
||||||
|
if not isinstance(state["endpoints"], list):
|
||||||
|
endpoints_dict: dict[str, dict] = state.pop("endpoints")
|
||||||
|
state["endpoints"] = list(endpoints_dict.values())
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def create_fixture_file(path: Path, state_text: str) -> None:
|
||||||
|
"""Create a file for the state dump in the fixtures directory."""
|
||||||
|
path.write_text(state_text, "utf8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Run the main script."""
|
||||||
|
args = get_arguments()
|
||||||
|
diagnostics_path: Path = args.diagnostics_file
|
||||||
|
diagnostics = load_file(diagnostics_path)
|
||||||
|
fixture_data = extract_fixture_data(diagnostics)
|
||||||
|
fixture_text = json.dumps(fixture_data, indent=2)
|
||||||
|
if args.file:
|
||||||
|
fixture_path = get_fixtures_dir_path(fixture_data)
|
||||||
|
create_fixture_file(fixture_path, fixture_text)
|
||||||
|
return
|
||||||
|
print(fixture_text) # noqa: T201
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ class ZWaveNodeFirmwareUpdateExtraStoredData(ExtraStoredData):
|
|||||||
def as_dict(self) -> dict[str, Any]:
|
def as_dict(self) -> dict[str, Any]:
|
||||||
"""Return a dict representation of the extra data."""
|
"""Return a dict representation of the extra data."""
|
||||||
return {
|
return {
|
||||||
ATTR_LATEST_VERSION_FIRMWARE: asdict(self.latest_version_firmware)
|
ATTR_LATEST_VERSION_FIRMWARE: self.latest_version_firmware.to_dict()
|
||||||
if self.latest_version_firmware
|
if self.latest_version_firmware
|
||||||
else None
|
else None
|
||||||
}
|
}
|
||||||
@ -339,19 +339,25 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
and (latest_version := state.attributes.get(ATTR_LATEST_VERSION))
|
and (latest_version := state.attributes.get(ATTR_LATEST_VERSION))
|
||||||
is not None
|
is not None
|
||||||
and (extra_data := await self.async_get_last_extra_data())
|
and (extra_data := await self.async_get_last_extra_data())
|
||||||
):
|
and (
|
||||||
self._attr_latest_version = latest_version
|
latest_version_firmware := ZWaveNodeFirmwareUpdateExtraStoredData.from_dict(
|
||||||
self._latest_version_firmware = (
|
|
||||||
ZWaveNodeFirmwareUpdateExtraStoredData.from_dict(
|
|
||||||
extra_data.as_dict()
|
extra_data.as_dict()
|
||||||
).latest_version_firmware
|
).latest_version_firmware
|
||||||
)
|
)
|
||||||
# If we have no state or latest version to restore, we can set the latest
|
):
|
||||||
|
self._attr_latest_version = latest_version
|
||||||
|
self._latest_version_firmware = latest_version_firmware
|
||||||
|
# If we have no state or latest version to restore, or the latest version is
|
||||||
|
# the same as the installed version, we can set the latest
|
||||||
# version to installed so that the entity starts as off. If we have partial
|
# version to installed so that the entity starts as off. If we have partial
|
||||||
# restore data due to an upgrade to an HA version where this feature is released
|
# restore data due to an upgrade to an HA version where this feature is released
|
||||||
# from one that is not the entity will start in an unknown state until we can
|
# from one that is not the entity will start in an unknown state until we can
|
||||||
# correct on next update
|
# correct on next update
|
||||||
elif not state or not latest_version:
|
elif (
|
||||||
|
not state
|
||||||
|
or not latest_version
|
||||||
|
or latest_version == self._attr_installed_version
|
||||||
|
):
|
||||||
self._attr_latest_version = self._attr_installed_version
|
self._attr_latest_version = self._attr_installed_version
|
||||||
|
|
||||||
# Spread updates out in 5 minute increments to avoid flooding the network
|
# Spread updates out in 5 minute increments to avoid flooding the network
|
||||||
|
@ -7,7 +7,7 @@ from typing import Final
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 11
|
MINOR_VERSION: Final = 11
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.11.0"
|
version = "2023.11.1"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -369,7 +369,7 @@ aiosyncthing==0.5.1
|
|||||||
aiotractive==0.5.6
|
aiotractive==0.5.6
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==64
|
aiounifi==65
|
||||||
|
|
||||||
# homeassistant.components.vlc_telnet
|
# homeassistant.components.vlc_telnet
|
||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
@ -378,7 +378,7 @@ aiovlc==0.1.0
|
|||||||
aiovodafone==0.4.2
|
aiovodafone==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==2.1.0
|
aiowaqi==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.watttime
|
# homeassistant.components.watttime
|
||||||
aiowatttime==0.1.1
|
aiowatttime==0.1.1
|
||||||
@ -1004,7 +1004,7 @@ hlk-sw16==0.0.9
|
|||||||
hole==0.8.0
|
hole==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.28
|
holidays==0.35
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20231030.1
|
home-assistant-frontend==20231030.1
|
||||||
@ -1394,7 +1394,7 @@ openwrt-luci-rpc==1.1.16
|
|||||||
openwrt-ubus-rpc==0.0.2
|
openwrt-ubus-rpc==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.0.38
|
opower==0.0.39
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1521,7 +1521,7 @@ py-improv-ble-client==1.0.3
|
|||||||
py-melissa-climate==2.1.4
|
py-melissa-climate==2.1.4
|
||||||
|
|
||||||
# homeassistant.components.nextbus
|
# homeassistant.components.nextbus
|
||||||
py-nextbusnext==1.0.0
|
py-nextbusnext==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.nightscout
|
# homeassistant.components.nightscout
|
||||||
py-nightscout==1.2.2
|
py-nightscout==1.2.2
|
||||||
@ -1693,7 +1693,7 @@ pyedimax==0.2.1
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.13.1
|
pyenphase==1.14.1
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
pyenvisalink==4.6
|
pyenvisalink==4.6
|
||||||
@ -2319,7 +2319,7 @@ renault-api==0.2.0
|
|||||||
renson-endura-delta==1.6.0
|
renson-endura-delta==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.7.12
|
reolink-aio==0.7.14
|
||||||
|
|
||||||
# homeassistant.components.idteck_prox
|
# homeassistant.components.idteck_prox
|
||||||
rfk101py==0.0.1
|
rfk101py==0.0.1
|
||||||
@ -2758,7 +2758,7 @@ yalesmartalarmclient==0.3.9
|
|||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
# homeassistant.components.yalexs_ble
|
# homeassistant.components.yalexs_ble
|
||||||
yalexs-ble==2.3.1
|
yalexs-ble==2.3.2
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.10.0
|
yalexs==1.10.0
|
||||||
|
@ -344,7 +344,7 @@ aiosyncthing==0.5.1
|
|||||||
aiotractive==0.5.6
|
aiotractive==0.5.6
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==64
|
aiounifi==65
|
||||||
|
|
||||||
# homeassistant.components.vlc_telnet
|
# homeassistant.components.vlc_telnet
|
||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
@ -353,7 +353,7 @@ aiovlc==0.1.0
|
|||||||
aiovodafone==0.4.2
|
aiovodafone==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==2.1.0
|
aiowaqi==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.watttime
|
# homeassistant.components.watttime
|
||||||
aiowatttime==0.1.1
|
aiowatttime==0.1.1
|
||||||
@ -793,7 +793,7 @@ hlk-sw16==0.0.9
|
|||||||
hole==0.8.0
|
hole==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.28
|
holidays==0.35
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20231030.1
|
home-assistant-frontend==20231030.1
|
||||||
@ -1072,7 +1072,7 @@ openerz-api==0.2.0
|
|||||||
openhomedevice==2.2.0
|
openhomedevice==2.2.0
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.0.38
|
opower==0.0.39
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1166,7 +1166,7 @@ py-improv-ble-client==1.0.3
|
|||||||
py-melissa-climate==2.1.4
|
py-melissa-climate==2.1.4
|
||||||
|
|
||||||
# homeassistant.components.nextbus
|
# homeassistant.components.nextbus
|
||||||
py-nextbusnext==1.0.0
|
py-nextbusnext==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.nightscout
|
# homeassistant.components.nightscout
|
||||||
py-nightscout==1.2.2
|
py-nightscout==1.2.2
|
||||||
@ -1275,7 +1275,7 @@ pyeconet==0.1.22
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.13.1
|
pyenphase==1.14.1
|
||||||
|
|
||||||
# homeassistant.components.everlights
|
# homeassistant.components.everlights
|
||||||
pyeverlights==0.1.0
|
pyeverlights==0.1.0
|
||||||
@ -1730,7 +1730,7 @@ renault-api==0.2.0
|
|||||||
renson-endura-delta==1.6.0
|
renson-endura-delta==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.7.12
|
reolink-aio==0.7.14
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.65
|
rflink==0.0.65
|
||||||
@ -2058,7 +2058,7 @@ yalesmartalarmclient==0.3.9
|
|||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
# homeassistant.components.yalexs_ble
|
# homeassistant.components.yalexs_ble
|
||||||
yalexs-ble==2.3.1
|
yalexs-ble==2.3.2
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.10.0
|
yalexs==1.10.0
|
||||||
|
@ -358,7 +358,24 @@ async def test_service_calls_off_mode(
|
|||||||
device.set_setpoint_heat.assert_called_with(77)
|
device.set_setpoint_heat.assert_called_with(77)
|
||||||
assert "Invalid temperature" in caplog.text
|
assert "Invalid temperature" in caplog.text
|
||||||
|
|
||||||
|
device.set_setpoint_heat.reset_mock()
|
||||||
|
device.set_setpoint_heat.side_effect = aiosomecomfort.UnexpectedResponse
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
ATTR_TARGET_TEMP_LOW: 25.0,
|
||||||
|
ATTR_TARGET_TEMP_HIGH: 35.0,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_setpoint_cool.assert_called_with(95)
|
||||||
|
device.set_setpoint_heat.assert_called_with(77)
|
||||||
|
|
||||||
reset_mock(device)
|
reset_mock(device)
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
CLIMATE_DOMAIN,
|
CLIMATE_DOMAIN,
|
||||||
@ -702,6 +719,17 @@ async def test_service_calls_heat_mode(
|
|||||||
device.set_hold_heat.reset_mock()
|
device.set_hold_heat.reset_mock()
|
||||||
assert "Invalid temperature" in caplog.text
|
assert "Invalid temperature" in caplog.text
|
||||||
|
|
||||||
|
device.set_hold_heat.side_effect = aiosomecomfort.UnexpectedResponse
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 15},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
device.set_hold_heat.assert_called_once_with(datetime.time(2, 30), 59)
|
||||||
|
device.set_hold_heat.reset_mock()
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
CLIMATE_DOMAIN,
|
CLIMATE_DOMAIN,
|
||||||
|
@ -223,6 +223,16 @@ async def door_lock_fixture(
|
|||||||
return await setup_integration_with_node_fixture(hass, "door-lock", matter_client)
|
return await setup_integration_with_node_fixture(hass, "door-lock", matter_client)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="door_lock_with_unbolt")
|
||||||
|
async def door_lock_with_unbolt_fixture(
|
||||||
|
hass: HomeAssistant, matter_client: MagicMock
|
||||||
|
) -> MatterNode:
|
||||||
|
"""Fixture for a door lock node with unbolt feature."""
|
||||||
|
return await setup_integration_with_node_fixture(
|
||||||
|
hass, "door-lock-with-unbolt", matter_client
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="eve_contact_sensor_node")
|
@pytest.fixture(name="eve_contact_sensor_node")
|
||||||
async def eve_contact_sensor_node_fixture(
|
async def eve_contact_sensor_node_fixture(
|
||||||
hass: HomeAssistant, matter_client: MagicMock
|
hass: HomeAssistant, matter_client: MagicMock
|
||||||
|
@ -0,0 +1,510 @@
|
|||||||
|
{
|
||||||
|
"node_id": 1,
|
||||||
|
"date_commissioned": "2023-03-07T09:06:06.059454",
|
||||||
|
"last_interview": "2023-03-07T09:06:06.059456",
|
||||||
|
"interview_version": 2,
|
||||||
|
"available": true,
|
||||||
|
"attributes": {
|
||||||
|
"0/29/0": [
|
||||||
|
{
|
||||||
|
"deviceType": 22,
|
||||||
|
"revision": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/29/1": [
|
||||||
|
29, 31, 40, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 60, 62,
|
||||||
|
63, 64, 65
|
||||||
|
],
|
||||||
|
"0/29/2": [41],
|
||||||
|
"0/29/3": [1],
|
||||||
|
"0/29/65532": 0,
|
||||||
|
"0/29/65533": 1,
|
||||||
|
"0/29/65528": [],
|
||||||
|
"0/29/65529": [],
|
||||||
|
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/31/0": [
|
||||||
|
{
|
||||||
|
"privilege": 5,
|
||||||
|
"authMode": 2,
|
||||||
|
"subjects": [112233],
|
||||||
|
"targets": null,
|
||||||
|
"fabricIndex": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/31/1": [],
|
||||||
|
"0/31/2": 4,
|
||||||
|
"0/31/3": 3,
|
||||||
|
"0/31/4": 4,
|
||||||
|
"0/31/65532": 0,
|
||||||
|
"0/31/65533": 1,
|
||||||
|
"0/31/65528": [],
|
||||||
|
"0/31/65529": [],
|
||||||
|
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/40/0": 1,
|
||||||
|
"0/40/1": "TEST_VENDOR",
|
||||||
|
"0/40/2": 65521,
|
||||||
|
"0/40/3": "Mock Door Lock",
|
||||||
|
"0/40/4": 32769,
|
||||||
|
"0/40/5": "Mock Door Lock",
|
||||||
|
"0/40/6": "**REDACTED**",
|
||||||
|
"0/40/7": 0,
|
||||||
|
"0/40/8": "TEST_VERSION",
|
||||||
|
"0/40/9": 1,
|
||||||
|
"0/40/10": "1.0",
|
||||||
|
"0/40/11": "20200101",
|
||||||
|
"0/40/12": "",
|
||||||
|
"0/40/13": "",
|
||||||
|
"0/40/14": "",
|
||||||
|
"0/40/15": "TEST_SN",
|
||||||
|
"0/40/16": false,
|
||||||
|
"0/40/17": true,
|
||||||
|
"0/40/18": "mock-door-lock",
|
||||||
|
"0/40/19": {
|
||||||
|
"caseSessionsPerFabric": 3,
|
||||||
|
"subscriptionsPerFabric": 65535
|
||||||
|
},
|
||||||
|
"0/40/65532": 0,
|
||||||
|
"0/40/65533": 1,
|
||||||
|
"0/40/65528": [],
|
||||||
|
"0/40/65529": [],
|
||||||
|
"0/40/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||||
|
65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"0/42/0": [],
|
||||||
|
"0/42/1": true,
|
||||||
|
"0/42/2": 0,
|
||||||
|
"0/42/3": 0,
|
||||||
|
"0/42/65532": 0,
|
||||||
|
"0/42/65533": 1,
|
||||||
|
"0/42/65528": [],
|
||||||
|
"0/42/65529": [0],
|
||||||
|
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/43/0": "en-US",
|
||||||
|
"0/43/1": [
|
||||||
|
"en-US",
|
||||||
|
"de-DE",
|
||||||
|
"fr-FR",
|
||||||
|
"en-GB",
|
||||||
|
"es-ES",
|
||||||
|
"zh-CN",
|
||||||
|
"it-IT",
|
||||||
|
"ja-JP"
|
||||||
|
],
|
||||||
|
"0/43/65532": 0,
|
||||||
|
"0/43/65533": 1,
|
||||||
|
"0/43/65528": [],
|
||||||
|
"0/43/65529": [],
|
||||||
|
"0/43/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/44/0": 0,
|
||||||
|
"0/44/1": 0,
|
||||||
|
"0/44/2": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 7],
|
||||||
|
"0/44/65532": 0,
|
||||||
|
"0/44/65533": 1,
|
||||||
|
"0/44/65528": [],
|
||||||
|
"0/44/65529": [],
|
||||||
|
"0/44/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/46/0": [0, 1],
|
||||||
|
"0/46/65532": 0,
|
||||||
|
"0/46/65533": 1,
|
||||||
|
"0/46/65528": [],
|
||||||
|
"0/46/65529": [],
|
||||||
|
"0/46/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/47/0": 1,
|
||||||
|
"0/47/1": 0,
|
||||||
|
"0/47/2": "USB",
|
||||||
|
"0/47/6": 0,
|
||||||
|
"0/47/65532": 1,
|
||||||
|
"0/47/65533": 1,
|
||||||
|
"0/47/65528": [],
|
||||||
|
"0/47/65529": [],
|
||||||
|
"0/47/65531": [0, 1, 2, 6, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/48/0": 0,
|
||||||
|
"0/48/1": {
|
||||||
|
"failSafeExpiryLengthSeconds": 60,
|
||||||
|
"maxCumulativeFailsafeSeconds": 900
|
||||||
|
},
|
||||||
|
"0/48/2": 0,
|
||||||
|
"0/48/3": 2,
|
||||||
|
"0/48/4": true,
|
||||||
|
"0/48/65532": 0,
|
||||||
|
"0/48/65533": 1,
|
||||||
|
"0/48/65528": [1, 3, 5],
|
||||||
|
"0/48/65529": [0, 2, 4],
|
||||||
|
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/49/0": 1,
|
||||||
|
"0/49/1": [],
|
||||||
|
"0/49/2": 10,
|
||||||
|
"0/49/3": 20,
|
||||||
|
"0/49/4": true,
|
||||||
|
"0/49/5": null,
|
||||||
|
"0/49/6": null,
|
||||||
|
"0/49/7": null,
|
||||||
|
"0/49/65532": 2,
|
||||||
|
"0/49/65533": 1,
|
||||||
|
"0/49/65528": [1, 5, 7],
|
||||||
|
"0/49/65529": [0, 3, 4, 6, 8],
|
||||||
|
"0/49/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"0/50/65532": 0,
|
||||||
|
"0/50/65533": 1,
|
||||||
|
"0/50/65528": [1],
|
||||||
|
"0/50/65529": [0],
|
||||||
|
"0/50/65531": [65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/51/0": [
|
||||||
|
{
|
||||||
|
"name": "eth0",
|
||||||
|
"isOperational": true,
|
||||||
|
"offPremiseServicesReachableIPv4": null,
|
||||||
|
"offPremiseServicesReachableIPv6": null,
|
||||||
|
"hardwareAddress": "/mQDt/2Q",
|
||||||
|
"IPv4Addresses": ["CjwBaQ=="],
|
||||||
|
"IPv6Addresses": [
|
||||||
|
"/VqgxiAxQib8ZAP//rf9kA==",
|
||||||
|
"IAEEcLs7AAb8ZAP//rf9kA==",
|
||||||
|
"/oAAAAAAAAD8ZAP//rf9kA=="
|
||||||
|
],
|
||||||
|
"type": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lo",
|
||||||
|
"isOperational": true,
|
||||||
|
"offPremiseServicesReachableIPv4": null,
|
||||||
|
"offPremiseServicesReachableIPv6": null,
|
||||||
|
"hardwareAddress": "AAAAAAAA",
|
||||||
|
"IPv4Addresses": ["fwAAAQ=="],
|
||||||
|
"IPv6Addresses": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
|
||||||
|
"type": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/51/1": 1,
|
||||||
|
"0/51/2": 25,
|
||||||
|
"0/51/3": 0,
|
||||||
|
"0/51/4": 0,
|
||||||
|
"0/51/5": [],
|
||||||
|
"0/51/6": [],
|
||||||
|
"0/51/7": [],
|
||||||
|
"0/51/8": false,
|
||||||
|
"0/51/65532": 0,
|
||||||
|
"0/51/65533": 1,
|
||||||
|
"0/51/65528": [],
|
||||||
|
"0/51/65529": [0],
|
||||||
|
"0/51/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"0/52/0": [
|
||||||
|
{
|
||||||
|
"id": 26957,
|
||||||
|
"name": "26957",
|
||||||
|
"stackFreeCurrent": null,
|
||||||
|
"stackFreeMinimum": null,
|
||||||
|
"stackSize": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26956,
|
||||||
|
"name": "26956",
|
||||||
|
"stackFreeCurrent": null,
|
||||||
|
"stackFreeMinimum": null,
|
||||||
|
"stackSize": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26955,
|
||||||
|
"name": "26955",
|
||||||
|
"stackFreeCurrent": null,
|
||||||
|
"stackFreeMinimum": null,
|
||||||
|
"stackSize": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26953,
|
||||||
|
"name": "26953",
|
||||||
|
"stackFreeCurrent": null,
|
||||||
|
"stackFreeMinimum": null,
|
||||||
|
"stackSize": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26952,
|
||||||
|
"name": "26952",
|
||||||
|
"stackFreeCurrent": null,
|
||||||
|
"stackFreeMinimum": null,
|
||||||
|
"stackSize": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/52/1": 351120,
|
||||||
|
"0/52/2": 529520,
|
||||||
|
"0/52/3": 529520,
|
||||||
|
"0/52/65532": 1,
|
||||||
|
"0/52/65533": 1,
|
||||||
|
"0/52/65528": [],
|
||||||
|
"0/52/65529": [0],
|
||||||
|
"0/52/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/53/0": null,
|
||||||
|
"0/53/1": null,
|
||||||
|
"0/53/2": null,
|
||||||
|
"0/53/3": null,
|
||||||
|
"0/53/4": null,
|
||||||
|
"0/53/5": null,
|
||||||
|
"0/53/6": 0,
|
||||||
|
"0/53/7": [],
|
||||||
|
"0/53/8": [],
|
||||||
|
"0/53/9": null,
|
||||||
|
"0/53/10": null,
|
||||||
|
"0/53/11": null,
|
||||||
|
"0/53/12": null,
|
||||||
|
"0/53/13": null,
|
||||||
|
"0/53/14": 0,
|
||||||
|
"0/53/15": 0,
|
||||||
|
"0/53/16": 0,
|
||||||
|
"0/53/17": 0,
|
||||||
|
"0/53/18": 0,
|
||||||
|
"0/53/19": 0,
|
||||||
|
"0/53/20": 0,
|
||||||
|
"0/53/21": 0,
|
||||||
|
"0/53/22": 0,
|
||||||
|
"0/53/23": 0,
|
||||||
|
"0/53/24": 0,
|
||||||
|
"0/53/25": 0,
|
||||||
|
"0/53/26": 0,
|
||||||
|
"0/53/27": 0,
|
||||||
|
"0/53/28": 0,
|
||||||
|
"0/53/29": 0,
|
||||||
|
"0/53/30": 0,
|
||||||
|
"0/53/31": 0,
|
||||||
|
"0/53/32": 0,
|
||||||
|
"0/53/33": 0,
|
||||||
|
"0/53/34": 0,
|
||||||
|
"0/53/35": 0,
|
||||||
|
"0/53/36": 0,
|
||||||
|
"0/53/37": 0,
|
||||||
|
"0/53/38": 0,
|
||||||
|
"0/53/39": 0,
|
||||||
|
"0/53/40": 0,
|
||||||
|
"0/53/41": 0,
|
||||||
|
"0/53/42": 0,
|
||||||
|
"0/53/43": 0,
|
||||||
|
"0/53/44": 0,
|
||||||
|
"0/53/45": 0,
|
||||||
|
"0/53/46": 0,
|
||||||
|
"0/53/47": 0,
|
||||||
|
"0/53/48": 0,
|
||||||
|
"0/53/49": 0,
|
||||||
|
"0/53/50": 0,
|
||||||
|
"0/53/51": 0,
|
||||||
|
"0/53/52": 0,
|
||||||
|
"0/53/53": 0,
|
||||||
|
"0/53/54": 0,
|
||||||
|
"0/53/55": 0,
|
||||||
|
"0/53/56": null,
|
||||||
|
"0/53/57": null,
|
||||||
|
"0/53/58": null,
|
||||||
|
"0/53/59": null,
|
||||||
|
"0/53/60": null,
|
||||||
|
"0/53/61": null,
|
||||||
|
"0/53/62": [],
|
||||||
|
"0/53/65532": 15,
|
||||||
|
"0/53/65533": 1,
|
||||||
|
"0/53/65528": [],
|
||||||
|
"0/53/65529": [0],
|
||||||
|
"0/53/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||||
|
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||||
|
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||||
|
57, 58, 59, 60, 61, 62, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"0/54/0": null,
|
||||||
|
"0/54/1": null,
|
||||||
|
"0/54/2": 3,
|
||||||
|
"0/54/3": null,
|
||||||
|
"0/54/4": null,
|
||||||
|
"0/54/5": null,
|
||||||
|
"0/54/6": null,
|
||||||
|
"0/54/7": null,
|
||||||
|
"0/54/8": null,
|
||||||
|
"0/54/9": null,
|
||||||
|
"0/54/10": null,
|
||||||
|
"0/54/11": null,
|
||||||
|
"0/54/12": null,
|
||||||
|
"0/54/65532": 3,
|
||||||
|
"0/54/65533": 1,
|
||||||
|
"0/54/65528": [],
|
||||||
|
"0/54/65529": [0],
|
||||||
|
"0/54/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 65530, 65531,
|
||||||
|
65532, 65533
|
||||||
|
],
|
||||||
|
"0/55/0": null,
|
||||||
|
"0/55/1": false,
|
||||||
|
"0/55/2": 823,
|
||||||
|
"0/55/3": 969,
|
||||||
|
"0/55/4": 0,
|
||||||
|
"0/55/5": 0,
|
||||||
|
"0/55/6": 0,
|
||||||
|
"0/55/7": null,
|
||||||
|
"0/55/8": 25,
|
||||||
|
"0/55/65532": 3,
|
||||||
|
"0/55/65533": 1,
|
||||||
|
"0/55/65528": [],
|
||||||
|
"0/55/65529": [0],
|
||||||
|
"0/55/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"0/60/0": 0,
|
||||||
|
"0/60/1": null,
|
||||||
|
"0/60/2": null,
|
||||||
|
"0/60/65532": 0,
|
||||||
|
"0/60/65533": 1,
|
||||||
|
"0/60/65528": [],
|
||||||
|
"0/60/65529": [0, 1, 2],
|
||||||
|
"0/60/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/62/0": [
|
||||||
|
{
|
||||||
|
"noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE55h6CbNLPZH/uM3/rDdA+jeuuD2QSPN8gBeEB0bmGJqWz/gCT4/ySB77rK3XiwVWVAmJhJ/eMcTIA0XXWMqKPDcKNQEoARgkAgE2AwQCBAEYMAQUqnKiC76YFhcTHt4AQ/kAbtrZ2MowBRSL6EWyWm8+uC0Puc2/BncMqYbpmhgwC0AA05Z+y1mcyHUeOFJ5kyDJJMN/oNCwN5h8UpYN/868iuQArr180/fbaN1+db9lab4D2lf0HK7wgHIR3HsOa2w9GA==",
|
||||||
|
"icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE5R1DrUQE/L8tx95WR1g1dZJf4d+6LEB7JAYZN/nw9ZBUg5VOHDrB1xIw5KguYJzt10K+0KqQBBEbuwW+wLLobTcKNQEpARgkAmAwBBSL6EWyWm8+uC0Puc2/BncMqYbpmjAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQHXLE0TgIDW6XOnvtsOJCyKoENts8d4TQWBgTKviv1LF/+MS9eFYi+kO+1Idq5mVgwN+lH7eyecShQR0iqq6WLUY",
|
||||||
|
"fabricIndex": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/62/1": [
|
||||||
|
{
|
||||||
|
"rootPublicKey": "BJ/jL2MdDrdq9TahKSa5c/dBc166NRCU0W9l7hK2kcuVtN915DLqiS+RAJ2iPEvWK5FawZHF/QdKLZmTkZHudxY=",
|
||||||
|
"vendorId": 65521,
|
||||||
|
"fabricId": 1,
|
||||||
|
"nodeId": 1,
|
||||||
|
"label": "",
|
||||||
|
"fabricIndex": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/62/2": 16,
|
||||||
|
"0/62/3": 1,
|
||||||
|
"0/62/4": [
|
||||||
|
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEn+MvYx0Ot2r1NqEpJrlz90FzXro1EJTRb2XuEraRy5W033XkMuqJL5EAnaI8S9YrkVrBkcX9B0otmZORke53FjcKNQEpARgkAmAwBBTNCOnzxc3zr9iFm19YbsW93ltH6jAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQILjpR3BTSHHl6DQtvwzWkjmA+i5jjXdc3qjemFGFjFVAnV6dPLQo7tctC8Y0uL4ZNERga2/NZAt1gRD72S0YR4Y"
|
||||||
|
],
|
||||||
|
"0/62/5": 1,
|
||||||
|
"0/62/65532": 0,
|
||||||
|
"0/62/65533": 1,
|
||||||
|
"0/62/65528": [1, 3, 5, 8],
|
||||||
|
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
|
||||||
|
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/63/0": [],
|
||||||
|
"0/63/1": [],
|
||||||
|
"0/63/2": 4,
|
||||||
|
"0/63/3": 3,
|
||||||
|
"0/63/65532": 0,
|
||||||
|
"0/63/65533": 1,
|
||||||
|
"0/63/65528": [2, 5],
|
||||||
|
"0/63/65529": [0, 1, 3, 4],
|
||||||
|
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/64/0": [
|
||||||
|
{
|
||||||
|
"label": "room",
|
||||||
|
"value": "bedroom 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "orientation",
|
||||||
|
"value": "North"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "floor",
|
||||||
|
"value": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "direction",
|
||||||
|
"value": "up"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/64/65532": 0,
|
||||||
|
"0/64/65533": 1,
|
||||||
|
"0/64/65528": [],
|
||||||
|
"0/64/65529": [],
|
||||||
|
"0/64/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"0/65/0": [],
|
||||||
|
"0/65/65532": 0,
|
||||||
|
"0/65/65533": 1,
|
||||||
|
"0/65/65528": [],
|
||||||
|
"0/65/65529": [],
|
||||||
|
"0/65/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"1/3/0": 0,
|
||||||
|
"1/3/1": 0,
|
||||||
|
"1/3/65532": 0,
|
||||||
|
"1/3/65533": 4,
|
||||||
|
"1/3/65528": [],
|
||||||
|
"1/3/65529": [0],
|
||||||
|
"1/3/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"1/6/0": false,
|
||||||
|
"1/6/16384": true,
|
||||||
|
"1/6/16385": 0,
|
||||||
|
"1/6/16386": 0,
|
||||||
|
"1/6/16387": 0,
|
||||||
|
"1/6/65532": 0,
|
||||||
|
"1/6/65533": 4,
|
||||||
|
"1/6/65528": [],
|
||||||
|
"1/6/65529": [0, 1, 2],
|
||||||
|
"1/6/65531": [
|
||||||
|
0, 16384, 16385, 16386, 16387, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"1/29/0": [
|
||||||
|
{
|
||||||
|
"deviceType": 10,
|
||||||
|
"revision": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"1/29/1": [3, 6, 29, 47, 257],
|
||||||
|
"1/29/2": [],
|
||||||
|
"1/29/3": [],
|
||||||
|
"1/29/65532": 0,
|
||||||
|
"1/29/65533": 1,
|
||||||
|
"1/29/65528": [],
|
||||||
|
"1/29/65529": [],
|
||||||
|
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
|
||||||
|
"1/47/0": 1,
|
||||||
|
"1/47/1": 1,
|
||||||
|
"1/47/2": "Battery",
|
||||||
|
"1/47/14": 0,
|
||||||
|
"1/47/15": false,
|
||||||
|
"1/47/16": 0,
|
||||||
|
"1/47/19": "",
|
||||||
|
"1/47/65532": 10,
|
||||||
|
"1/47/65533": 1,
|
||||||
|
"1/47/65528": [],
|
||||||
|
"1/47/65529": [],
|
||||||
|
"1/47/65531": [
|
||||||
|
0, 1, 2, 14, 15, 16, 19, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
],
|
||||||
|
"1/257/0": 1,
|
||||||
|
"1/257/1": 0,
|
||||||
|
"1/257/2": true,
|
||||||
|
"1/257/3": 1,
|
||||||
|
"1/257/17": 10,
|
||||||
|
"1/257/18": 10,
|
||||||
|
"1/257/19": 10,
|
||||||
|
"1/257/20": 10,
|
||||||
|
"1/257/21": 10,
|
||||||
|
"1/257/22": 10,
|
||||||
|
"1/257/23": 8,
|
||||||
|
"1/257/24": 6,
|
||||||
|
"1/257/25": 20,
|
||||||
|
"1/257/26": 10,
|
||||||
|
"1/257/27": 1,
|
||||||
|
"1/257/28": 5,
|
||||||
|
"1/257/33": "en",
|
||||||
|
"1/257/35": 60,
|
||||||
|
"1/257/36": 0,
|
||||||
|
"1/257/37": 0,
|
||||||
|
"1/257/38": 65526,
|
||||||
|
"1/257/41": false,
|
||||||
|
"1/257/43": false,
|
||||||
|
"1/257/48": 3,
|
||||||
|
"1/257/49": 10,
|
||||||
|
"1/257/51": false,
|
||||||
|
"1/257/65532": 7603,
|
||||||
|
"1/257/65533": 6,
|
||||||
|
"1/257/65528": [12, 15, 18, 28, 35, 37],
|
||||||
|
"1/257/65529": [
|
||||||
|
0, 1, 3, 11, 12, 13, 14, 15, 16, 17, 18, 19, 26, 27, 29, 34, 36, 38
|
||||||
|
],
|
||||||
|
"1/257/65531": [
|
||||||
|
0, 1, 2, 3, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 35, 36,
|
||||||
|
37, 38, 41, 43, 48, 49, 51, 65528, 65529, 65530, 65531, 65532, 65533
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"attribute_subscriptions": []
|
||||||
|
}
|
@ -10,6 +10,7 @@ from homeassistant.components.lock import (
|
|||||||
STATE_LOCKING,
|
STATE_LOCKING,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
STATE_UNLOCKING,
|
STATE_UNLOCKING,
|
||||||
|
LockEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN
|
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -135,3 +136,51 @@ async def test_lock_requires_pin(
|
|||||||
command=clusters.DoorLock.Commands.LockDoor(code.encode()),
|
command=clusters.DoorLock.Commands.LockDoor(code.encode()),
|
||||||
timed_request_timeout_ms=1000,
|
timed_request_timeout_ms=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# This tests needs to be adjusted to remove lingering tasks
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
async def test_lock_with_unbolt(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
matter_client: MagicMock,
|
||||||
|
door_lock_with_unbolt: MatterNode,
|
||||||
|
) -> None:
|
||||||
|
"""Test door lock."""
|
||||||
|
state = hass.states.get("lock.mock_door_lock")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_LOCKED
|
||||||
|
assert state.attributes["supported_features"] & LockEntityFeature.OPEN
|
||||||
|
# test unlock/unbolt
|
||||||
|
await hass.services.async_call(
|
||||||
|
"lock",
|
||||||
|
"unlock",
|
||||||
|
{
|
||||||
|
"entity_id": "lock.mock_door_lock",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert matter_client.send_device_command.call_count == 1
|
||||||
|
# unlock should unbolt on a lock with unbolt feature
|
||||||
|
assert matter_client.send_device_command.call_args == call(
|
||||||
|
node_id=door_lock_with_unbolt.node_id,
|
||||||
|
endpoint_id=1,
|
||||||
|
command=clusters.DoorLock.Commands.UnboltDoor(),
|
||||||
|
timed_request_timeout_ms=1000,
|
||||||
|
)
|
||||||
|
matter_client.send_device_command.reset_mock()
|
||||||
|
# test open / unlatch
|
||||||
|
await hass.services.async_call(
|
||||||
|
"lock",
|
||||||
|
"open",
|
||||||
|
{
|
||||||
|
"entity_id": "lock.mock_door_lock",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert matter_client.send_device_command.call_count == 1
|
||||||
|
assert matter_client.send_device_command.call_args == call(
|
||||||
|
node_id=door_lock_with_unbolt.node_id,
|
||||||
|
endpoint_id=1,
|
||||||
|
command=clusters.DoorLock.Commands.UnlockDoor(),
|
||||||
|
timed_request_timeout_ms=1000,
|
||||||
|
)
|
||||||
|
@ -1297,7 +1297,7 @@ async def test_reload_after_invalid_config(
|
|||||||
assert hass.states.get("alarm_control_panel.test") is None
|
assert hass.states.get("alarm_control_panel.test") is None
|
||||||
assert (
|
assert (
|
||||||
"extra keys not allowed @ data['invalid_topic'] for "
|
"extra keys not allowed @ data['invalid_topic'] for "
|
||||||
"manual configured MQTT alarm_control_panel item, "
|
"manually configured MQTT alarm_control_panel item, "
|
||||||
"in ?, line ? Got {'name': 'test', 'invalid_topic': 'test-topic'}"
|
"in ?, line ? Got {'name': 'test', 'invalid_topic': 'test-topic'}"
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
@ -139,7 +139,7 @@ async def test_preset_none_in_preset_modes(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test the preset mode payload reset configuration."""
|
"""Test the preset mode payload reset configuration."""
|
||||||
assert await mqtt_mock_entry()
|
assert await mqtt_mock_entry()
|
||||||
assert "not a valid value" in caplog.text
|
assert "preset_modes must not include preset mode 'none'" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1788,7 +1788,7 @@ async def test_attributes(
|
|||||||
},
|
},
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
"not a valid value",
|
"speed_range_max must be > speed_range_min",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"test14",
|
"test14",
|
||||||
@ -1805,7 +1805,7 @@ async def test_attributes(
|
|||||||
},
|
},
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
"not a valid value",
|
"speed_range_min must be > 0",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"test15",
|
"test15",
|
||||||
|
@ -2134,7 +2134,7 @@ async def test_setup_manual_mqtt_with_platform_key(
|
|||||||
"""Test set up a manual MQTT item with a platform key."""
|
"""Test set up a manual MQTT item with a platform key."""
|
||||||
assert await mqtt_mock_entry()
|
assert await mqtt_mock_entry()
|
||||||
assert (
|
assert (
|
||||||
"extra keys not allowed @ data['platform'] for manual configured MQTT light item"
|
"extra keys not allowed @ data['platform'] for manually configured MQTT light item"
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2151,6 +2151,42 @@ async def test_setup_manual_mqtt_with_invalid_config(
|
|||||||
assert "required key not provided" in caplog.text
|
assert "required key not provided" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
"sensor": {
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"entity_category": "config",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
"binary_sensor": {
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"entity_category": "config",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||||
|
)
|
||||||
|
async def test_setup_manual_mqtt_with_invalid_entity_category(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test set up a manual sensor item with an invalid entity category."""
|
||||||
|
assert await mqtt_mock_entry()
|
||||||
|
assert "Entity category `config` is invalid" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.mqtt.PLATFORMS", [])
|
@patch("homeassistant.components.mqtt.PLATFORMS", [])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("mqtt_config_entry_data", "protocol"),
|
("mqtt_config_entry_data", "protocol"),
|
||||||
|
@ -211,7 +211,7 @@ async def test_attribute_validation_max_greater_then_min(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test the validation of min and max configuration attributes."""
|
"""Test the validation of min and max configuration attributes."""
|
||||||
assert await mqtt_mock_entry()
|
assert await mqtt_mock_entry()
|
||||||
assert "not a valid value" in caplog.text
|
assert "text length min must be >= max" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -236,7 +236,7 @@ async def test_attribute_validation_max_not_greater_then_max_state_length(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test the max value of of max configuration attribute."""
|
"""Test the max value of of max configuration attribute."""
|
||||||
assert await mqtt_mock_entry()
|
assert await mqtt_mock_entry()
|
||||||
assert "not a valid value" in caplog.text
|
assert "max text length must be <= 255" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -40,5 +40,7 @@ async def test_adam_change_select_entity(
|
|||||||
|
|
||||||
assert mock_smile_adam.set_schedule_state.call_count == 1
|
assert mock_smile_adam.set_schedule_state.call_count == 1
|
||||||
mock_smile_adam.set_schedule_state.assert_called_with(
|
mock_smile_adam.set_schedule_state.assert_called_with(
|
||||||
"c50f167537524366a5af7aa3942feb1e", "Badkamer Schema", "on"
|
"c50f167537524366a5af7aa3942feb1e",
|
||||||
|
"on",
|
||||||
|
"Badkamer Schema",
|
||||||
)
|
)
|
||||||
|
@ -153,6 +153,7 @@ async def test_enter_with_attrs(hass: HomeAssistant, client, webhook_id) -> None
|
|||||||
"speed": 100,
|
"speed": 100,
|
||||||
"bearing": "105.32",
|
"bearing": "105.32",
|
||||||
"altitude": 102,
|
"altitude": 102,
|
||||||
|
"charge": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
req = await client.post(url, params=data)
|
req = await client.post(url, params=data)
|
||||||
@ -165,6 +166,7 @@ async def test_enter_with_attrs(hass: HomeAssistant, client, webhook_id) -> None
|
|||||||
assert state.attributes["speed"] == 100.0
|
assert state.attributes["speed"] == 100.0
|
||||||
assert state.attributes["bearing"] == 105.32
|
assert state.attributes["bearing"] == 105.32
|
||||||
assert state.attributes["altitude"] == 102.0
|
assert state.attributes["altitude"] == 102.0
|
||||||
|
assert "charge" not in state.attributes
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"lat": str(HOME_LATITUDE),
|
"lat": str(HOME_LATITUDE),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodeId": 12,
|
"nodeId": 131,
|
||||||
"index": 0,
|
"index": 0,
|
||||||
"installerIcon": 6656,
|
"installerIcon": 6656,
|
||||||
"userIcon": 6656,
|
"userIcon": 6656,
|
||||||
@ -7,12 +7,13 @@
|
|||||||
"ready": true,
|
"ready": true,
|
||||||
"isListening": false,
|
"isListening": false,
|
||||||
"isRouting": true,
|
"isRouting": true,
|
||||||
"isSecure": true,
|
"isSecure": false,
|
||||||
"manufacturerId": 647,
|
"manufacturerId": 647,
|
||||||
"productId": 114,
|
"productId": 114,
|
||||||
"productType": 4,
|
"productType": 4,
|
||||||
"firmwareVersion": "3.12.1",
|
"firmwareVersion": "3.12.1",
|
||||||
"zwavePlusVersion": 2,
|
"zwavePlusVersion": 2,
|
||||||
|
"name": "Blind West Bed 1",
|
||||||
"deviceConfig": {
|
"deviceConfig": {
|
||||||
"filename": "/data/db/devices/0x0287/iblindsv3.json",
|
"filename": "/data/db/devices/0x0287/iblindsv3.json",
|
||||||
"isEmbedded": true,
|
"isEmbedded": true,
|
||||||
@ -38,321 +39,61 @@
|
|||||||
"associations": {},
|
"associations": {},
|
||||||
"paramInformation": {
|
"paramInformation": {
|
||||||
"_map": {}
|
"_map": {}
|
||||||
|
},
|
||||||
|
"compat": {
|
||||||
|
"removeCCs": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"label": "iblinds V3",
|
"label": "iblinds V3",
|
||||||
"interviewAttempts": 1,
|
"interviewAttempts": 1,
|
||||||
"endpoints": [
|
"isFrequentListening": "1000ms",
|
||||||
{
|
"maxDataRate": 100000,
|
||||||
"nodeId": 12,
|
"supportedDataRates": [40000, 100000],
|
||||||
"index": 0,
|
"protocolVersion": 3,
|
||||||
"installerIcon": 6656,
|
"supportsBeaming": true,
|
||||||
"userIcon": 6656,
|
"supportsSecurity": false,
|
||||||
"deviceClass": {
|
"nodeType": 1,
|
||||||
"basic": {
|
"zwavePlusNodeType": 0,
|
||||||
"key": 4,
|
"zwavePlusRoleType": 7,
|
||||||
"label": "Routing Slave"
|
"deviceClass": {
|
||||||
},
|
"basic": {
|
||||||
"generic": {
|
"key": 4,
|
||||||
"key": 17,
|
"label": "Routing Slave"
|
||||||
"label": "Multilevel Switch"
|
},
|
||||||
},
|
"generic": {
|
||||||
"specific": {
|
"key": 17,
|
||||||
"key": 7,
|
"label": "Multilevel Switch"
|
||||||
"label": "Motor Control Class C"
|
},
|
||||||
},
|
"specific": {
|
||||||
"mandatorySupportedCCs": [32, 38, 37, 114, 134],
|
"key": 7,
|
||||||
"mandatoryControlledCCs": []
|
"label": "Motor Control Class C"
|
||||||
},
|
},
|
||||||
"commandClasses": [
|
"mandatorySupportedCCs": [32, 38, 37, 114, 134],
|
||||||
{
|
"mandatoryControlledCCs": []
|
||||||
"id": 38,
|
},
|
||||||
"name": "Multilevel Switch",
|
"interviewStage": "Complete",
|
||||||
"version": 4,
|
"deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0287:0x0004:0x0072:3.12.1",
|
||||||
"isSecure": true
|
"statistics": {
|
||||||
},
|
"commandsTX": 95,
|
||||||
{
|
"commandsRX": 110,
|
||||||
"id": 37,
|
"commandsDroppedRX": 0,
|
||||||
"name": "Binary Switch",
|
"commandsDroppedTX": 0,
|
||||||
"version": 2,
|
"timeoutResponse": 0,
|
||||||
"isSecure": true
|
"rtt": 1295.6,
|
||||||
},
|
"lastSeen": "2023-11-02T18:41:40.552Z",
|
||||||
{
|
"rssi": -69,
|
||||||
"id": 114,
|
"lwr": {
|
||||||
"name": "Manufacturer Specific",
|
"protocolDataRate": 2,
|
||||||
"version": 2,
|
"repeaters": [],
|
||||||
"isSecure": true
|
"rssi": -71,
|
||||||
},
|
"repeaterRSSI": []
|
||||||
{
|
|
||||||
"id": 134,
|
|
||||||
"name": "Version",
|
|
||||||
"version": 3,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 94,
|
|
||||||
"name": "Z-Wave Plus Info",
|
|
||||||
"version": 2,
|
|
||||||
"isSecure": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 133,
|
|
||||||
"name": "Association",
|
|
||||||
"version": 2,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 89,
|
|
||||||
"name": "Association Group Information",
|
|
||||||
"version": 3,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 85,
|
|
||||||
"name": "Transport Service",
|
|
||||||
"version": 2,
|
|
||||||
"isSecure": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 90,
|
|
||||||
"name": "Device Reset Locally",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 115,
|
|
||||||
"name": "Powerlevel",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 159,
|
|
||||||
"name": "Security 2",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 108,
|
|
||||||
"name": "Supervision",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 122,
|
|
||||||
"name": "Firmware Update Meta Data",
|
|
||||||
"version": 5,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 128,
|
|
||||||
"name": "Battery",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 112,
|
|
||||||
"name": "Configuration",
|
|
||||||
"version": 4,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 135,
|
|
||||||
"name": "Indicator",
|
|
||||||
"version": 3,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 142,
|
|
||||||
"name": "Multi Channel Association",
|
|
||||||
"version": 3,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 106,
|
|
||||||
"name": "Window Covering",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 152,
|
|
||||||
"name": "Security",
|
|
||||||
"version": 1,
|
|
||||||
"isSecure": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
|
"highestSecurityClass": -1,
|
||||||
|
"isControllerNode": false,
|
||||||
|
"keepAwake": false,
|
||||||
|
"lastSeen": "2023-11-02T18:41:40.552Z",
|
||||||
"values": [
|
"values": [
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 37,
|
|
||||||
"commandClassName": "Binary Switch",
|
|
||||||
"property": "currentValue",
|
|
||||||
"propertyName": "currentValue",
|
|
||||||
"ccVersion": 2,
|
|
||||||
"metadata": {
|
|
||||||
"type": "boolean",
|
|
||||||
"readable": true,
|
|
||||||
"writeable": false,
|
|
||||||
"label": "Current value",
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
},
|
|
||||||
"value": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 37,
|
|
||||||
"commandClassName": "Binary Switch",
|
|
||||||
"property": "targetValue",
|
|
||||||
"propertyName": "targetValue",
|
|
||||||
"ccVersion": 2,
|
|
||||||
"metadata": {
|
|
||||||
"type": "boolean",
|
|
||||||
"readable": true,
|
|
||||||
"writeable": true,
|
|
||||||
"label": "Target value",
|
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
},
|
|
||||||
"value": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 37,
|
|
||||||
"commandClassName": "Binary Switch",
|
|
||||||
"property": "duration",
|
|
||||||
"propertyName": "duration",
|
|
||||||
"ccVersion": 2,
|
|
||||||
"metadata": {
|
|
||||||
"type": "duration",
|
|
||||||
"readable": true,
|
|
||||||
"writeable": false,
|
|
||||||
"label": "Remaining duration",
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"value": 0,
|
|
||||||
"unit": "seconds"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 38,
|
|
||||||
"commandClassName": "Multilevel Switch",
|
|
||||||
"property": "targetValue",
|
|
||||||
"propertyName": "targetValue",
|
|
||||||
"ccVersion": 4,
|
|
||||||
"metadata": {
|
|
||||||
"type": "number",
|
|
||||||
"readable": true,
|
|
||||||
"writeable": true,
|
|
||||||
"label": "Target value",
|
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
|
||||||
"min": 0,
|
|
||||||
"max": 99,
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
},
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 38,
|
|
||||||
"commandClassName": "Multilevel Switch",
|
|
||||||
"property": "duration",
|
|
||||||
"propertyName": "duration",
|
|
||||||
"ccVersion": 4,
|
|
||||||
"metadata": {
|
|
||||||
"type": "duration",
|
|
||||||
"readable": true,
|
|
||||||
"writeable": false,
|
|
||||||
"label": "Remaining duration",
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"value": 0,
|
|
||||||
"unit": "seconds"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 38,
|
|
||||||
"commandClassName": "Multilevel Switch",
|
|
||||||
"property": "currentValue",
|
|
||||||
"propertyName": "currentValue",
|
|
||||||
"ccVersion": 4,
|
|
||||||
"metadata": {
|
|
||||||
"type": "number",
|
|
||||||
"readable": true,
|
|
||||||
"writeable": false,
|
|
||||||
"label": "Current value",
|
|
||||||
"min": 0,
|
|
||||||
"max": 99,
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
},
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 38,
|
|
||||||
"commandClassName": "Multilevel Switch",
|
|
||||||
"property": "Up",
|
|
||||||
"propertyName": "Up",
|
|
||||||
"ccVersion": 4,
|
|
||||||
"metadata": {
|
|
||||||
"type": "boolean",
|
|
||||||
"readable": false,
|
|
||||||
"writeable": true,
|
|
||||||
"label": "Perform a level change (Up)",
|
|
||||||
"ccSpecific": {
|
|
||||||
"switchType": 2
|
|
||||||
},
|
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 38,
|
|
||||||
"commandClassName": "Multilevel Switch",
|
|
||||||
"property": "Down",
|
|
||||||
"propertyName": "Down",
|
|
||||||
"ccVersion": 4,
|
|
||||||
"metadata": {
|
|
||||||
"type": "boolean",
|
|
||||||
"readable": false,
|
|
||||||
"writeable": true,
|
|
||||||
"label": "Perform a level change (Down)",
|
|
||||||
"ccSpecific": {
|
|
||||||
"switchType": 2
|
|
||||||
},
|
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 38,
|
|
||||||
"commandClassName": "Multilevel Switch",
|
|
||||||
"property": "restorePrevious",
|
|
||||||
"propertyName": "restorePrevious",
|
|
||||||
"ccVersion": 4,
|
|
||||||
"metadata": {
|
|
||||||
"type": "boolean",
|
|
||||||
"readable": false,
|
|
||||||
"writeable": true,
|
|
||||||
"label": "Restore previous value",
|
|
||||||
"stateful": true,
|
|
||||||
"secret": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
@ -361,7 +102,7 @@
|
|||||||
"propertyKey": 23,
|
"propertyKey": 23,
|
||||||
"propertyName": "currentValue",
|
"propertyName": "currentValue",
|
||||||
"propertyKeyName": "Horizontal Slats Angle",
|
"propertyKeyName": "Horizontal Slats Angle",
|
||||||
"ccVersion": 0,
|
"ccVersion": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"readable": true,
|
"readable": true,
|
||||||
@ -373,9 +114,9 @@
|
|||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 99,
|
"max": 99,
|
||||||
"states": {
|
"states": {
|
||||||
"0": "Closed (up)",
|
"0": "Closed (up inside)",
|
||||||
"50": "Open",
|
"50": "Open",
|
||||||
"99": "Closed (down)"
|
"99": "Closed (down inside)"
|
||||||
},
|
},
|
||||||
"stateful": true,
|
"stateful": true,
|
||||||
"secret": false
|
"secret": false
|
||||||
@ -390,7 +131,7 @@
|
|||||||
"propertyKey": 23,
|
"propertyKey": 23,
|
||||||
"propertyName": "targetValue",
|
"propertyName": "targetValue",
|
||||||
"propertyKeyName": "Horizontal Slats Angle",
|
"propertyKeyName": "Horizontal Slats Angle",
|
||||||
"ccVersion": 0,
|
"ccVersion": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"readable": true,
|
"readable": true,
|
||||||
@ -403,14 +144,14 @@
|
|||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 99,
|
"max": 99,
|
||||||
"states": {
|
"states": {
|
||||||
"0": "Closed (up)",
|
"0": "Closed (up inside)",
|
||||||
"50": "Open",
|
"50": "Open",
|
||||||
"99": "Closed (down)"
|
"99": "Closed (down inside)"
|
||||||
},
|
},
|
||||||
"stateful": true,
|
"stateful": true,
|
||||||
"secret": false
|
"secret": false
|
||||||
},
|
},
|
||||||
"value": 99
|
"value": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
@ -420,7 +161,7 @@
|
|||||||
"propertyKey": 23,
|
"propertyKey": 23,
|
||||||
"propertyName": "duration",
|
"propertyName": "duration",
|
||||||
"propertyKeyName": "Horizontal Slats Angle",
|
"propertyKeyName": "Horizontal Slats Angle",
|
||||||
"ccVersion": 0,
|
"ccVersion": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "duration",
|
"type": "duration",
|
||||||
"readable": true,
|
"readable": true,
|
||||||
@ -441,44 +182,24 @@
|
|||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
"commandClassName": "Window Covering",
|
"commandClassName": "Window Covering",
|
||||||
"property": "open",
|
"property": "levelChangeUp",
|
||||||
"propertyKey": 23,
|
"propertyKey": 23,
|
||||||
"propertyName": "open",
|
"propertyName": "levelChangeUp",
|
||||||
"propertyKeyName": "Horizontal Slats Angle",
|
"propertyKeyName": "Horizontal Slats Angle",
|
||||||
"ccVersion": 0,
|
"ccVersion": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"readable": false,
|
"readable": false,
|
||||||
"writeable": true,
|
"writeable": true,
|
||||||
"label": "Open - Horizontal Slats Angle",
|
"label": "Change tilt (down inside) - Horizontal Slats Angle",
|
||||||
"ccSpecific": {
|
"ccSpecific": {
|
||||||
"parameter": 23
|
"parameter": 23
|
||||||
},
|
},
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
"valueChangeOptions": ["transitionDuration"],
|
||||||
"stateful": true,
|
"states": {
|
||||||
"secret": false
|
"true": "Start",
|
||||||
},
|
"false": "Stop"
|
||||||
"nodeId": 12,
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoint": 0,
|
|
||||||
"commandClass": 106,
|
|
||||||
"commandClassName": "Window Covering",
|
|
||||||
"property": "close0",
|
|
||||||
"propertyKey": 23,
|
|
||||||
"propertyName": "close0",
|
|
||||||
"propertyKeyName": "Horizontal Slats Angle",
|
|
||||||
"ccVersion": 0,
|
|
||||||
"metadata": {
|
|
||||||
"type": "boolean",
|
|
||||||
"readable": false,
|
|
||||||
"writeable": true,
|
|
||||||
"label": "Close Up - Horizontal Slats Angle",
|
|
||||||
"ccSpecific": {
|
|
||||||
"parameter": 23
|
|
||||||
},
|
},
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
|
||||||
"stateful": true,
|
"stateful": true,
|
||||||
"secret": false
|
"secret": false
|
||||||
}
|
}
|
||||||
@ -487,25 +208,27 @@
|
|||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
"commandClassName": "Window Covering",
|
"commandClassName": "Window Covering",
|
||||||
"property": "close99",
|
"property": "levelChangeDown",
|
||||||
"propertyKey": 23,
|
"propertyKey": 23,
|
||||||
"propertyName": "close99",
|
"propertyName": "levelChangeDown",
|
||||||
"propertyKeyName": "Horizontal Slats Angle",
|
"propertyKeyName": "Horizontal Slats Angle",
|
||||||
"ccVersion": 0,
|
"ccVersion": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"readable": false,
|
"readable": false,
|
||||||
"writeable": true,
|
"writeable": true,
|
||||||
"label": "Close Down - Horizontal Slats Angle",
|
"label": "Change tilt (up inside) - Horizontal Slats Angle",
|
||||||
"ccSpecific": {
|
"ccSpecific": {
|
||||||
"parameter": 23
|
"parameter": 23
|
||||||
},
|
},
|
||||||
"valueChangeOptions": ["transitionDuration"],
|
"valueChangeOptions": ["transitionDuration"],
|
||||||
|
"states": {
|
||||||
|
"true": "Start",
|
||||||
|
"false": "Stop"
|
||||||
|
},
|
||||||
"stateful": true,
|
"stateful": true,
|
||||||
"secret": false
|
"secret": false
|
||||||
},
|
}
|
||||||
"nodeId": 12,
|
|
||||||
"value": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
@ -604,7 +327,7 @@
|
|||||||
"allowManualEntry": true,
|
"allowManualEntry": true,
|
||||||
"isFromConfig": true
|
"isFromConfig": true
|
||||||
},
|
},
|
||||||
"value": 50
|
"value": 45
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
@ -656,6 +379,32 @@
|
|||||||
},
|
},
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"endpoint": 0,
|
||||||
|
"commandClass": 112,
|
||||||
|
"commandClassName": "Configuration",
|
||||||
|
"property": 11,
|
||||||
|
"propertyName": "MC",
|
||||||
|
"ccVersion": 4,
|
||||||
|
"metadata": {
|
||||||
|
"type": "number",
|
||||||
|
"readable": true,
|
||||||
|
"writeable": true,
|
||||||
|
"description": "MC",
|
||||||
|
"label": "MC",
|
||||||
|
"default": 1,
|
||||||
|
"min": 0,
|
||||||
|
"max": 1,
|
||||||
|
"valueSize": 1,
|
||||||
|
"format": 0,
|
||||||
|
"noBulkSupport": true,
|
||||||
|
"isAdvanced": false,
|
||||||
|
"requiresReInclusion": false,
|
||||||
|
"allowManualEntry": true,
|
||||||
|
"isFromConfig": false
|
||||||
|
},
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 112,
|
"commandClass": 112,
|
||||||
@ -721,7 +470,9 @@
|
|||||||
"format": 0,
|
"format": 0,
|
||||||
"allowManualEntry": true,
|
"allowManualEntry": true,
|
||||||
"isFromConfig": true
|
"isFromConfig": true
|
||||||
}
|
},
|
||||||
|
"nodeId": 131,
|
||||||
|
"value": 99
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
@ -1169,7 +920,9 @@
|
|||||||
"max": 255,
|
"max": 255,
|
||||||
"stateful": true,
|
"stateful": true,
|
||||||
"secret": false
|
"secret": false
|
||||||
}
|
},
|
||||||
|
"nodeId": 131,
|
||||||
|
"value": 47
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
@ -1183,54 +936,209 @@
|
|||||||
"readable": false,
|
"readable": false,
|
||||||
"writeable": true,
|
"writeable": true,
|
||||||
"label": "Identify",
|
"label": "Identify",
|
||||||
|
"states": {
|
||||||
|
"true": "Identify"
|
||||||
|
},
|
||||||
"stateful": true,
|
"stateful": true,
|
||||||
"secret": false
|
"secret": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"endpoint": 0,
|
||||||
|
"commandClass": 135,
|
||||||
|
"commandClassName": "Indicator",
|
||||||
|
"property": "timeout",
|
||||||
|
"propertyName": "timeout",
|
||||||
|
"ccVersion": 3,
|
||||||
|
"metadata": {
|
||||||
|
"type": "string",
|
||||||
|
"readable": true,
|
||||||
|
"writeable": true,
|
||||||
|
"label": "Timeout",
|
||||||
|
"stateful": true,
|
||||||
|
"secret": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commandClassName": "Multilevel Switch",
|
||||||
|
"commandClass": 38,
|
||||||
|
"property": "targetValue",
|
||||||
|
"endpoint": 0,
|
||||||
|
"metadata": {
|
||||||
|
"type": "number",
|
||||||
|
"readable": true,
|
||||||
|
"writeable": true,
|
||||||
|
"label": "Target value",
|
||||||
|
"valueChangeOptions": ["transitionDuration"],
|
||||||
|
"min": 0,
|
||||||
|
"max": 99,
|
||||||
|
"stateful": true,
|
||||||
|
"secret": false
|
||||||
|
},
|
||||||
|
"propertyName": "targetValue",
|
||||||
|
"nodeId": 131,
|
||||||
|
"value": 45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commandClassName": "Multilevel Switch",
|
||||||
|
"commandClass": 38,
|
||||||
|
"property": "duration",
|
||||||
|
"endpoint": 0,
|
||||||
|
"metadata": {
|
||||||
|
"type": "duration",
|
||||||
|
"readable": true,
|
||||||
|
"writeable": false,
|
||||||
|
"label": "Remaining duration",
|
||||||
|
"stateful": true,
|
||||||
|
"secret": false
|
||||||
|
},
|
||||||
|
"propertyName": "duration",
|
||||||
|
"nodeId": 131,
|
||||||
|
"value": {
|
||||||
|
"value": 0,
|
||||||
|
"unit": "seconds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commandClassName": "Multilevel Switch",
|
||||||
|
"commandClass": 38,
|
||||||
|
"property": "currentValue",
|
||||||
|
"endpoint": 0,
|
||||||
|
"metadata": {
|
||||||
|
"type": "number",
|
||||||
|
"readable": true,
|
||||||
|
"writeable": false,
|
||||||
|
"label": "Current value",
|
||||||
|
"min": 0,
|
||||||
|
"max": 99,
|
||||||
|
"stateful": true,
|
||||||
|
"secret": false
|
||||||
|
},
|
||||||
|
"propertyName": "currentValue",
|
||||||
|
"nodeId": 131,
|
||||||
|
"value": 45
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"isFrequentListening": "1000ms",
|
"endpoints": [
|
||||||
"maxDataRate": 100000,
|
{
|
||||||
"supportedDataRates": [40000, 100000],
|
"nodeId": 131,
|
||||||
"protocolVersion": 3,
|
"index": 0,
|
||||||
"supportsBeaming": true,
|
"installerIcon": 6656,
|
||||||
"supportsSecurity": false,
|
"userIcon": 6656,
|
||||||
"nodeType": 1,
|
"deviceClass": {
|
||||||
"zwavePlusNodeType": 0,
|
"basic": {
|
||||||
"zwavePlusRoleType": 7,
|
"key": 4,
|
||||||
"deviceClass": {
|
"label": "Routing Slave"
|
||||||
"basic": {
|
},
|
||||||
"key": 4,
|
"generic": {
|
||||||
"label": "Routing Slave"
|
"key": 17,
|
||||||
},
|
"label": "Multilevel Switch"
|
||||||
"generic": {
|
},
|
||||||
"key": 17,
|
"specific": {
|
||||||
"label": "Multilevel Switch"
|
"key": 7,
|
||||||
},
|
"label": "Motor Control Class C"
|
||||||
"specific": {
|
},
|
||||||
"key": 7,
|
"mandatorySupportedCCs": [32, 38, 37, 114, 134],
|
||||||
"label": "Motor Control Class C"
|
"mandatoryControlledCCs": []
|
||||||
},
|
},
|
||||||
"mandatorySupportedCCs": [32, 38, 37, 114, 134],
|
"commandClasses": [
|
||||||
"mandatoryControlledCCs": []
|
{
|
||||||
},
|
"id": 114,
|
||||||
"interviewStage": "Complete",
|
"name": "Manufacturer Specific",
|
||||||
"deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0287:0x0004:0x0072:3.12.1",
|
"version": 2,
|
||||||
"statistics": {
|
"isSecure": false
|
||||||
"commandsTX": 109,
|
},
|
||||||
"commandsRX": 101,
|
{
|
||||||
"commandsDroppedRX": 2,
|
"id": 134,
|
||||||
"commandsDroppedTX": 0,
|
"name": "Version",
|
||||||
"timeoutResponse": 8,
|
"version": 3,
|
||||||
"rtt": 1217.2,
|
"isSecure": false
|
||||||
"rssi": -43,
|
},
|
||||||
"lwr": {
|
{
|
||||||
"protocolDataRate": 2,
|
"id": 94,
|
||||||
"repeaters": [],
|
"name": "Z-Wave Plus Info",
|
||||||
"rssi": -45,
|
"version": 2,
|
||||||
"repeaterRSSI": []
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 133,
|
||||||
|
"name": "Association",
|
||||||
|
"version": 2,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 89,
|
||||||
|
"name": "Association Group Information",
|
||||||
|
"version": 3,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 85,
|
||||||
|
"name": "Transport Service",
|
||||||
|
"version": 2,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 90,
|
||||||
|
"name": "Device Reset Locally",
|
||||||
|
"version": 1,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 115,
|
||||||
|
"name": "Powerlevel",
|
||||||
|
"version": 1,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 159,
|
||||||
|
"name": "Security 2",
|
||||||
|
"version": 1,
|
||||||
|
"isSecure": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 108,
|
||||||
|
"name": "Supervision",
|
||||||
|
"version": 1,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 122,
|
||||||
|
"name": "Firmware Update Meta Data",
|
||||||
|
"version": 5,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 128,
|
||||||
|
"name": "Battery",
|
||||||
|
"version": 1,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 112,
|
||||||
|
"name": "Configuration",
|
||||||
|
"version": 4,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 135,
|
||||||
|
"name": "Indicator",
|
||||||
|
"version": 3,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 142,
|
||||||
|
"name": "Multi Channel Association",
|
||||||
|
"version": 3,
|
||||||
|
"isSecure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 106,
|
||||||
|
"name": "Window Covering",
|
||||||
|
"version": 1,
|
||||||
|
"isSecure": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
"highestSecurityClass": 1,
|
|
||||||
"isControllerNode": false,
|
|
||||||
"keepAwake": false
|
|
||||||
}
|
}
|
||||||
|
2315
tests/components/zwave_js/fixtures/device_diagnostics.json
Normal file
2315
tests/components/zwave_js/fixtures/device_diagnostics.json
Normal file
File diff suppressed because it is too large
Load Diff
1330
tests/components/zwave_js/fixtures/zooz_zse44_state.json
Normal file
1330
tests/components/zwave_js/fixtures/zooz_zse44_state.json
Normal file
File diff suppressed because it is too large
Load Diff
1
tests/components/zwave_js/scripts/__init__.py
Normal file
1
tests/components/zwave_js/scripts/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for zwave_js scripts."""
|
@ -0,0 +1,83 @@
|
|||||||
|
"""Test convert_device_diagnostics_to_fixture script."""
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.zwave_js.scripts.convert_device_diagnostics_to_fixture import (
|
||||||
|
extract_fixture_data,
|
||||||
|
get_fixtures_dir_path,
|
||||||
|
load_file,
|
||||||
|
main,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.common import load_fixture
|
||||||
|
|
||||||
|
|
||||||
|
def _minify(text: str) -> str:
|
||||||
|
"""Minify string by removing whitespace and new lines."""
|
||||||
|
return text.replace(" ", "").replace("\n", "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_functions() -> None:
|
||||||
|
"""Test functions related to the fixture."""
|
||||||
|
diagnostics_data = json.loads(load_fixture("zwave_js/device_diagnostics.json"))
|
||||||
|
state = extract_fixture_data(copy.deepcopy(diagnostics_data))
|
||||||
|
assert isinstance(state["values"], list)
|
||||||
|
assert (
|
||||||
|
get_fixtures_dir_path(state)
|
||||||
|
== Path(__file__).parents[1] / "fixtures" / "zooz_zse44_state.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
old_diagnostics_format_data = copy.deepcopy(diagnostics_data)
|
||||||
|
old_diagnostics_format_data["data"]["state"]["values"] = list(
|
||||||
|
old_diagnostics_format_data["data"]["state"]["values"].values()
|
||||||
|
)
|
||||||
|
old_diagnostics_format_data["data"]["state"]["endpoints"] = list(
|
||||||
|
old_diagnostics_format_data["data"]["state"]["endpoints"].values()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
extract_fixture_data(old_diagnostics_format_data)
|
||||||
|
== old_diagnostics_format_data["data"]["state"]
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
extract_fixture_data({})
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_file() -> None:
|
||||||
|
"""Test load file."""
|
||||||
|
assert load_file(
|
||||||
|
Path(__file__).parents[1] / "fixtures" / "device_diagnostics.json"
|
||||||
|
) == json.loads(load_fixture("zwave_js/device_diagnostics.json"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_main(capfd: pytest.CaptureFixture[str]) -> None:
|
||||||
|
"""Test main function."""
|
||||||
|
fixture_str = load_fixture("zwave_js/zooz_zse44_state.json")
|
||||||
|
fixture_dict = json.loads(fixture_str)
|
||||||
|
|
||||||
|
# Test dump to stdout
|
||||||
|
args = [
|
||||||
|
"homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py",
|
||||||
|
str(Path(__file__).parents[1] / "fixtures" / "device_diagnostics.json"),
|
||||||
|
]
|
||||||
|
with patch.object(sys, "argv", args):
|
||||||
|
main()
|
||||||
|
|
||||||
|
captured = capfd.readouterr()
|
||||||
|
assert _minify(captured.out) == _minify(fixture_str)
|
||||||
|
|
||||||
|
# Check file dump
|
||||||
|
args.append("--file")
|
||||||
|
with patch.object(sys, "argv", args), patch(
|
||||||
|
"homeassistant.components.zwave_js.scripts.convert_device_diagnostics_to_fixture.Path.write_text"
|
||||||
|
) as write_text_mock:
|
||||||
|
main()
|
||||||
|
|
||||||
|
assert len(write_text_mock.call_args_list) == 1
|
||||||
|
assert write_text_mock.call_args[0][0] == json.dumps(fixture_dict, indent=2)
|
@ -829,7 +829,7 @@ async def test_iblinds_v3_cover(
|
|||||||
hass: HomeAssistant, client, iblinds_v3, integration
|
hass: HomeAssistant, client, iblinds_v3, integration
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test iBlinds v3 cover which uses Window Covering CC."""
|
"""Test iBlinds v3 cover which uses Window Covering CC."""
|
||||||
entity_id = "cover.window_blind_controller_horizontal_slats_angle"
|
entity_id = "cover.blind_west_bed_1_horizontal_slats_angle"
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
assert state
|
assert state
|
||||||
# This device has no state because there is no position value
|
# This device has no state because there is no position value
|
||||||
@ -854,7 +854,7 @@ async def test_iblinds_v3_cover(
|
|||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
args = client.async_send_command.call_args[0][0]
|
args = client.async_send_command.call_args[0][0]
|
||||||
assert args["command"] == "node.set_value"
|
assert args["command"] == "node.set_value"
|
||||||
assert args["nodeId"] == 12
|
assert args["nodeId"] == 131
|
||||||
assert args["valueId"] == {
|
assert args["valueId"] == {
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
@ -875,7 +875,7 @@ async def test_iblinds_v3_cover(
|
|||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
args = client.async_send_command.call_args[0][0]
|
args = client.async_send_command.call_args[0][0]
|
||||||
assert args["command"] == "node.set_value"
|
assert args["command"] == "node.set_value"
|
||||||
assert args["nodeId"] == 12
|
assert args["nodeId"] == 131
|
||||||
assert args["valueId"] == {
|
assert args["valueId"] == {
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
@ -896,7 +896,7 @@ async def test_iblinds_v3_cover(
|
|||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
args = client.async_send_command.call_args[0][0]
|
args = client.async_send_command.call_args[0][0]
|
||||||
assert args["command"] == "node.set_value"
|
assert args["command"] == "node.set_value"
|
||||||
assert args["nodeId"] == 12
|
assert args["nodeId"] == 131
|
||||||
assert args["valueId"] == {
|
assert args["valueId"] == {
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
@ -917,11 +917,11 @@ async def test_iblinds_v3_cover(
|
|||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
args = client.async_send_command.call_args[0][0]
|
args = client.async_send_command.call_args[0][0]
|
||||||
assert args["command"] == "node.set_value"
|
assert args["command"] == "node.set_value"
|
||||||
assert args["nodeId"] == 12
|
assert args["nodeId"] == 131
|
||||||
assert args["valueId"] == {
|
assert args["valueId"] == {
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
"commandClass": 106,
|
"commandClass": 106,
|
||||||
"property": "open",
|
"property": "levelChangeUp",
|
||||||
"propertyKey": 23,
|
"propertyKey": 23,
|
||||||
}
|
}
|
||||||
assert args["value"] is False
|
assert args["value"] is False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user