mirror of
https://github.com/home-assistant/core.git
synced 2026-05-11 14:09:44 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd0cdc4fc4 | ||
|
|
18ea40c46d | ||
|
|
a23131efc8 | ||
|
|
4940a0abae | ||
|
|
5f98d5ae52 | ||
|
|
ba18cded30 | ||
|
|
fb7504e9df | ||
|
|
106f815a1e | ||
|
|
167757762b | ||
|
|
3a902e1a16 | ||
|
|
85c11672d8 | ||
|
|
89649df20d | ||
|
|
7b749b95ce | ||
|
|
cc140be85c | ||
|
|
e1ad765414 | ||
|
|
44b1fea745 | ||
|
|
5dd04363b2 | ||
|
|
03aa979309 | ||
|
|
6fabbb354b | ||
|
|
f644448d0f | ||
|
|
4e61581cd8 | ||
|
|
6f87d02b72 | ||
|
|
348f6149b4 | ||
|
|
a4227ef1bc | ||
|
|
aac49a567f | ||
|
|
76b878b136 | ||
|
|
2d05931683 |
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["serialx==1.7.0"]
|
||||
"requirements": ["serialx==1.7.1"]
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["axis"],
|
||||
"requirements": ["axis==69"],
|
||||
"requirements": ["axis==70"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "AXIS"
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["blebox_uniapi"],
|
||||
"requirements": ["blebox-uniapi==2.5.2"],
|
||||
"requirements": ["blebox-uniapi==2.5.3"],
|
||||
"zeroconf": ["_bbxsrv._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["bsblan"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["python-bsblan==5.2.0"],
|
||||
"requirements": ["python-bsblan==5.2.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "bsb-lan*",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["duco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["python-duco-client==0.3.10"],
|
||||
"requirements": ["python-duco-client==0.4.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "duco [[][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][]].*",
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||
"requirements": ["py-sucks==0.9.11", "deebot-client==18.2.0"]
|
||||
"requirements": ["py-sucks==0.9.11", "deebot-client==18.3.0"]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ from functools import partial
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, TypedDict, cast
|
||||
from xml.etree.ElementTree import ParseError
|
||||
|
||||
from fritzconnection import FritzConnection
|
||||
from fritzconnection.core.exceptions import FritzActionError
|
||||
@@ -26,7 +27,7 @@ from homeassistant.components.device_tracker import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
@@ -228,7 +229,13 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
self.fritz_guest_wifi = FritzGuestWLAN(fc=self.connection)
|
||||
self.fritz_status = FritzStatus(fc=self.connection)
|
||||
self.fritz_call = FritzCall(fc=self.connection)
|
||||
info = self.fritz_status.get_device_info()
|
||||
try:
|
||||
info = self.fritz_status.get_device_info()
|
||||
except ParseError as ex:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_parse_device_info",
|
||||
) from ex
|
||||
|
||||
_LOGGER.debug(
|
||||
"gathered device info of %s %s",
|
||||
|
||||
@@ -185,6 +185,9 @@
|
||||
"config_entry_not_found": {
|
||||
"message": "Failed to perform action \"{service}\". Config entry for target not found"
|
||||
},
|
||||
"error_parse_device_info": {
|
||||
"message": "Error parsing device info. Please check the system event log of your FRITZ!Box for malformed data and clear the event list."
|
||||
},
|
||||
"error_refresh_hosts_info": {
|
||||
"message": "Error refreshing hosts info"
|
||||
},
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"],
|
||||
"requirements": ["gardena-bluetooth==2.4.0"]
|
||||
"requirements": ["gardena-bluetooth==2.8.1"]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import voluptuous as vol
|
||||
from homeassistant.auth.models import User
|
||||
from homeassistant.auth.providers import homeassistant as auth_ha
|
||||
from homeassistant.components.http import KEY_HASS, KEY_HASS_USER, HomeAssistantView
|
||||
from homeassistant.components.http.const import is_supervisor_unix_socket_request
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -41,14 +42,18 @@ class HassIOBaseAuth(HomeAssistantView):
|
||||
|
||||
def _check_access(self, request: web.Request) -> None:
|
||||
"""Check if this call is from Supervisor."""
|
||||
# Check caller IP
|
||||
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
|
||||
assert request.transport
|
||||
if ip_address(request.transport.get_extra_info("peername")[0]) != ip_address(
|
||||
hassio_ip
|
||||
):
|
||||
_LOGGER.error("Invalid auth request from %s", request.remote)
|
||||
raise HTTPUnauthorized
|
||||
# Requests over the Supervisor Unix socket are authenticated by the
|
||||
# http auth middleware as the Supervisor user, so the caller-IP check
|
||||
# below does not apply (and would crash, since `peername` is empty for
|
||||
# Unix sockets). The user-ID check still runs to ensure only the
|
||||
# Supervisor user can reach this endpoint.
|
||||
if not is_supervisor_unix_socket_request(request):
|
||||
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
|
||||
assert request.transport
|
||||
peername = request.transport.get_extra_info("peername")
|
||||
if not peername or ip_address(peername[0]) != ip_address(hassio_ip):
|
||||
_LOGGER.error("Invalid auth request from %s", request.remote)
|
||||
raise HTTPUnauthorized
|
||||
|
||||
# Check caller token
|
||||
if request[KEY_HASS_USER].id != self.user.id:
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.95", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.96", "babel==2.15.0"]
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["automower-ble==0.2.8", "gardena-bluetooth==2.4.0"]
|
||||
"requirements": ["automower-ble==0.2.8", "gardena-bluetooth==2.8.1"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import aiohttp
|
||||
from intellifire4py import UnifiedFireplace
|
||||
from intellifire4py.cloud_interface import IntelliFireCloudInterface
|
||||
from intellifire4py.const import IntelliFireApiMode
|
||||
@@ -155,6 +156,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: IntellifireConfigEntry)
|
||||
raise ConfigEntryNotReady(
|
||||
"Initialization of fireplace timed out after 10 minutes"
|
||||
) from err
|
||||
except (aiohttp.ClientConnectionError, ConnectionError) as err:
|
||||
raise ConfigEntryNotReady(
|
||||
"Error communicating with fireplace during initialization"
|
||||
) from err
|
||||
|
||||
# Construct coordinator
|
||||
data_update_coordinator = IntellifireDataUpdateCoordinator(hass, entry, fireplace)
|
||||
|
||||
@@ -15,6 +15,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_REAUTH,
|
||||
ConfigEntryState,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
@@ -289,10 +290,8 @@ class IntelliFireOptionsFlowHandler(OptionsFlow):
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
# Validate connectivity for requested modes if runtime data is available
|
||||
coordinator = self.config_entry.runtime_data
|
||||
if coordinator is not None:
|
||||
fireplace = coordinator.fireplace
|
||||
if self.config_entry.state is ConfigEntryState.LOADED:
|
||||
fireplace = self.config_entry.runtime_data.fireplace
|
||||
|
||||
# Refresh connectivity status before validating
|
||||
await fireplace.async_validate_connectivity()
|
||||
|
||||
@@ -101,14 +101,21 @@ COVER_DESCRIPTIONS: list[OverkizCoverDescription] = [
|
||||
close_tilt_command=OverkizCommand.LOWER_CLOSE,
|
||||
stop_tilt_command=OverkizCommand.STOP,
|
||||
),
|
||||
# Needs override to remove open/close commands
|
||||
# Needs override to add support for very specific tilt commands
|
||||
# uiClass is VenetianBlind
|
||||
OverkizCoverDescription(
|
||||
key=UIWidget.TILT_ONLY_VENETIAN_BLIND,
|
||||
device_class=CoverDeviceClass.BLIND,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
||||
# Position commands fully open/close the tilt
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
# Tilt commands move the tilt with a few degrees
|
||||
open_tilt_command=OverkizCommand.TILT_POSITIVE,
|
||||
open_tilt_command_args=(1, 0),
|
||||
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
|
||||
close_tilt_command_args=(1, 0),
|
||||
stop_tilt_command=OverkizCommand.STOP,
|
||||
),
|
||||
# Needs override to support very specific tilt commands (rts:ExteriorVenetianBlindRTSComponent)
|
||||
@@ -125,6 +132,57 @@ COVER_DESCRIPTIONS: list[OverkizCoverDescription] = [
|
||||
close_tilt_command_args=(15, 1), # position (1-127), speed (1-15)
|
||||
stop_tilt_command=OverkizCommand.STOP,
|
||||
),
|
||||
# Needs override to support very specific tilt commands (rts:VenetianBlindRTSComponent)
|
||||
# uiClass is VenetianBlind
|
||||
OverkizCoverDescription(
|
||||
key=UIWidget.UP_DOWN_VENETIAN_BLIND,
|
||||
device_class=CoverDeviceClass.BLIND,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
open_tilt_command=OverkizCommand.TILT_POSITIVE,
|
||||
open_tilt_command_args=(15, 1), # position (1-127), speed (1-15)
|
||||
close_tilt_command=OverkizCommand.TILT_NEGATIVE,
|
||||
close_tilt_command_args=(15, 1), # position (1-127), speed (1-15)
|
||||
stop_tilt_command=OverkizCommand.STOP,
|
||||
),
|
||||
# Needs override since PositionableGarageDoor reports
|
||||
# core:OpenClosedUnknownState instead of core:OpenClosedState
|
||||
# uiClass is GarageDoor
|
||||
OverkizCoverDescription(
|
||||
key=UIWidget.POSITIONABLE_GARAGE_DOOR,
|
||||
device_class=CoverDeviceClass.GARAGE,
|
||||
current_position_state=OverkizState.CORE_CLOSURE,
|
||||
set_position_command=OverkizCommand.SET_CLOSURE,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED_UNKNOWN,
|
||||
),
|
||||
# Needs override since PositionableGarageDoorWithPartialPosition reports
|
||||
# core:OpenClosedPartialState instead of core:OpenClosedState
|
||||
# uiClass is GarageDoor
|
||||
OverkizCoverDescription(
|
||||
key=UIWidget.POSITIONABLE_GARAGE_DOOR_WITH_PARTIAL_POSITION,
|
||||
device_class=CoverDeviceClass.GARAGE,
|
||||
current_position_state=OverkizState.CORE_CLOSURE,
|
||||
set_position_command=OverkizCommand.SET_CLOSURE,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PARTIAL,
|
||||
),
|
||||
# Needs override since DiscreteGateWithPedestrianPosition reports
|
||||
# core:OpenClosedPedestrianState instead of core:OpenClosedState
|
||||
# uiClass is Gate
|
||||
OverkizCoverDescription(
|
||||
key=UIWidget.DISCRETE_GATE_WITH_PEDESTRIAN_POSITION,
|
||||
device_class=CoverDeviceClass.GATE,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
),
|
||||
# Needs override to support this Generic device (rts:GenericRTSComponent)
|
||||
# uiClass is Generic (not mapped to cover as this is a Generic device class)
|
||||
OverkizCoverDescription(
|
||||
@@ -201,7 +259,7 @@ COVER_DESCRIPTIONS: list[OverkizCoverDescription] = [
|
||||
set_position_command=OverkizCommand.SET_CLOSURE,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED_UNKNOWN,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
),
|
||||
OverkizCoverDescription(
|
||||
@@ -209,12 +267,15 @@ COVER_DESCRIPTIONS: list[OverkizCoverDescription] = [
|
||||
device_class=CoverDeviceClass.GATE,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN,
|
||||
is_closed_state=OverkizState.CORE_OPEN_CLOSED,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
),
|
||||
OverkizCoverDescription(
|
||||
key=UIClass.PERGOLA,
|
||||
device_class=CoverDeviceClass.AWNING,
|
||||
open_command=OverkizCommand.OPEN,
|
||||
close_command=OverkizCommand.CLOSE,
|
||||
stop_command=OverkizCommand.STOP,
|
||||
is_closed_state=OverkizState.CORE_SLATS_OPEN_CLOSED,
|
||||
current_tilt_position_state=OverkizState.CORE_SLATE_ORIENTATION,
|
||||
set_tilt_position_command=OverkizCommand.SET_ORIENTATION,
|
||||
@@ -392,6 +453,8 @@ class OverkizCover(OverkizDescriptiveEntity, CoverEntity):
|
||||
"""Return if the cover is closed."""
|
||||
if is_closed_state := self.entity_description.is_closed_state:
|
||||
if state := self.device.states.get(is_closed_state):
|
||||
if state.value == OverkizCommandParam.UNKNOWN:
|
||||
return None
|
||||
return state.value == OverkizCommandParam.CLOSED
|
||||
|
||||
if (position := self.current_cover_position) is not None:
|
||||
|
||||
@@ -22,6 +22,8 @@ COMMANDS_WITHOUT_DELAY = [
|
||||
OverkizCommand.ON,
|
||||
OverkizCommand.ON_WITH_TIMER,
|
||||
OverkizCommand.TEST,
|
||||
OverkizCommand.TILT_POSITIVE,
|
||||
OverkizCommand.TILT_NEGATIVE,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
|
||||
"requirements": ["pyoverkiz==1.20.0"],
|
||||
"requirements": ["pyoverkiz==1.20.3"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "gateway*",
|
||||
|
||||
@@ -10,6 +10,7 @@ from pyoverkiz.enums import OverkizAttribute, OverkizState, UIWidget
|
||||
from pyoverkiz.types import StateType as OverkizStateType
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DEVICE_CLASS_UNITS,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
@@ -606,10 +607,25 @@ class OverkizStateSensor(OverkizDescriptiveEntity, SensorEntity):
|
||||
if (unit := attrs[OverkizAttribute.CORE_MEASURED_VALUE_TYPE]) and (
|
||||
unit_value := unit.value_as_str
|
||||
):
|
||||
return OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)
|
||||
ha_unit = OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)
|
||||
if self._is_unit_valid_for_device_class(ha_unit):
|
||||
return ha_unit
|
||||
|
||||
return default_unit
|
||||
|
||||
def _is_unit_valid_for_device_class(self, unit: str) -> bool:
|
||||
"""Check if a unit is valid for this sensor's device class.
|
||||
|
||||
The device-level core:MeasuredValueType attribute describes the primary
|
||||
sensor (e.g. luminance/temperature), but must not override the unit of
|
||||
unrelated sensors on the same device (e.g. RSSI).
|
||||
"""
|
||||
if not (device_class := self.entity_description.device_class):
|
||||
return True
|
||||
if (valid_units := DEVICE_CLASS_UNITS.get(device_class)) is None:
|
||||
return True
|
||||
return unit in valid_units
|
||||
|
||||
|
||||
class OverkizHomeKitSetupCodeSensor(OverkizEntity, SensorEntity):
|
||||
"""Representation of an Overkiz HomeKit Setup Code."""
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
"codeowners": ["@fabaff"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/serial",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["serialx==1.7.0"]
|
||||
"requirements": ["serialx==1.7.1"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["tibber"],
|
||||
"requirements": ["pyTibber==0.37.4"]
|
||||
"requirements": ["pyTibber==0.37.5"]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, cast, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_TARGET
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_OPTIONS, CONF_TARGET
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback, split_entity_id
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -25,6 +25,7 @@ from .const import DATA_COMPONENT, DOMAIN, TodoItemStatus
|
||||
ITEM_TRIGGER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_TARGET): cv.TARGET_FIELDS,
|
||||
vol.Required(CONF_OPTIONS, default={}): {},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["aiousbwatcher==1.1.2", "serialx==1.7.0"]
|
||||
"requirements": ["aiousbwatcher==1.1.2", "serialx==1.7.1"]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from pywizlight import PilotParser, wizlight
|
||||
from pywizlight.bulb import PIR_SOURCE
|
||||
|
||||
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
@@ -20,6 +19,7 @@ from .const import (
|
||||
DISCOVER_SCAN_TIMEOUT,
|
||||
DISCOVERY_INTERVAL,
|
||||
DOMAIN,
|
||||
OCCUPANCY_SOURCES,
|
||||
SIGNAL_WIZ_PIR,
|
||||
WIZ_CONNECT_EXCEPTIONS,
|
||||
)
|
||||
@@ -101,7 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: WizConfigEntry) -> bool:
|
||||
"""Receive a push update."""
|
||||
_LOGGER.debug("%s: Got push update: %s", bulb.mac, state.pilotResult)
|
||||
coordinator.async_set_updated_data(coordinator.data)
|
||||
if state.get_source() == PIR_SOURCE:
|
||||
if state.get_source() in OCCUPANCY_SOURCES:
|
||||
async_dispatcher_send(hass, SIGNAL_WIZ_PIR.format(bulb.mac))
|
||||
|
||||
await bulb.start_push(_async_push_update)
|
||||
|
||||
@@ -4,8 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from pywizlight.bulb import PIR_SOURCE
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
@@ -16,7 +14,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, SIGNAL_WIZ_PIR
|
||||
from .const import DOMAIN, OCCUPANCY_SOURCES, SIGNAL_WIZ_PIR
|
||||
from .coordinator import WizConfigEntry, WizData
|
||||
from .entity import WizEntity
|
||||
|
||||
@@ -75,5 +73,5 @@ class WizOccupancyEntity(WizEntity, BinarySensorEntity):
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Handle updating _attr values."""
|
||||
if self._device.state.get_source() == PIR_SOURCE:
|
||||
if self._device.state.get_source() in OCCUPANCY_SOURCES:
|
||||
self._attr_is_on = self._device.status
|
||||
|
||||
@@ -81,6 +81,8 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
exc_info=True,
|
||||
)
|
||||
raise AbortFlow("cannot_connect") from ex
|
||||
finally:
|
||||
await bulb.async_close()
|
||||
self._name = name_from_bulb_type_and_mac(bulbtype, device.mac_address)
|
||||
|
||||
async def async_step_discovery_confirm(
|
||||
@@ -118,6 +120,8 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
bulbtype = await bulb.get_bulbtype()
|
||||
except WIZ_CONNECT_EXCEPTIONS:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
finally:
|
||||
await bulb.async_close()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=name_from_bulb_type_and_mac(bulbtype, device.mac_address),
|
||||
@@ -182,6 +186,8 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
title=name,
|
||||
data=user_input,
|
||||
)
|
||||
finally:
|
||||
await bulb.async_close()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from pywizlight.bulb import PIR_SOURCE
|
||||
from pywizlight.exceptions import (
|
||||
WizLightConnectionError,
|
||||
WizLightNotKnownBulb,
|
||||
@@ -24,3 +25,4 @@ WIZ_EXCEPTIONS = (
|
||||
WIZ_CONNECT_EXCEPTIONS = (WizLightNotKnownBulb, *WIZ_EXCEPTIONS)
|
||||
|
||||
SIGNAL_WIZ_PIR = "wiz_pir_{}"
|
||||
OCCUPANCY_SOURCES = frozenset({PIR_SOURCE, "wfsens"})
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.95"]
|
||||
"requirements": ["holidays==0.96"]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"universal_silabs_flasher",
|
||||
"serialx"
|
||||
],
|
||||
"requirements": ["zha==1.3.0"],
|
||||
"requirements": ["zha==1.3.1"],
|
||||
"usb": [
|
||||
{
|
||||
"description": "*2652*",
|
||||
|
||||
@@ -1284,19 +1284,19 @@ def async_discover_single_value(
|
||||
continue
|
||||
|
||||
# check firmware_version_range
|
||||
if schema.firmware_version_range is not None and (
|
||||
(
|
||||
if schema.firmware_version_range is not None:
|
||||
# skip schema if device firmware version is unknown
|
||||
if value.node.firmware_version is None:
|
||||
continue
|
||||
node_firmware = AwesomeVersion(value.node.firmware_version)
|
||||
if (
|
||||
schema.firmware_version_range.min is not None
|
||||
and schema.firmware_version_range.min_ver
|
||||
> AwesomeVersion(value.node.firmware_version)
|
||||
)
|
||||
or (
|
||||
and schema.firmware_version_range.min_ver > node_firmware
|
||||
) or (
|
||||
schema.firmware_version_range.max is not None
|
||||
and schema.firmware_version_range.max_ver
|
||||
< AwesomeVersion(value.node.firmware_version)
|
||||
)
|
||||
):
|
||||
continue
|
||||
and schema.firmware_version_range.max_ver < node_firmware
|
||||
):
|
||||
continue
|
||||
|
||||
# check device_class_generic
|
||||
# If the value has an endpoint but it is missing on the node
|
||||
|
||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2026
|
||||
MINOR_VERSION: Final = 5
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)
|
||||
|
||||
@@ -63,7 +63,7 @@ PyTurboJPEG==1.8.3
|
||||
PyYAML==6.0.3
|
||||
requests==2.33.1
|
||||
securetar==2026.4.1
|
||||
serialx==1.7.0
|
||||
serialx==1.7.1
|
||||
SQLAlchemy==2.0.49
|
||||
standard-aifc==3.13.0
|
||||
standard-telnetlib==3.13.0
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2026.5.0"
|
||||
version = "2026.5.1"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
||||
22
requirements_all.txt
generated
22
requirements_all.txt
generated
@@ -600,7 +600,7 @@ avea==1.6.1
|
||||
# avion==0.10
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==69
|
||||
axis==70
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.7
|
||||
@@ -654,7 +654,7 @@ bleak-retry-connector==4.6.0
|
||||
bleak==2.1.1
|
||||
|
||||
# homeassistant.components.blebox
|
||||
blebox-uniapi==2.5.2
|
||||
blebox-uniapi==2.5.3
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.25.2
|
||||
@@ -794,7 +794,7 @@ debugpy==1.8.17
|
||||
decora-wifi==1.4
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==18.2.0
|
||||
deebot-client==18.3.0
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.ohmconnect
|
||||
@@ -1048,7 +1048,7 @@ gTTS==2.5.3
|
||||
|
||||
# homeassistant.components.gardena_bluetooth
|
||||
# homeassistant.components.husqvarna_automower_ble
|
||||
gardena-bluetooth==2.4.0
|
||||
gardena-bluetooth==2.8.1
|
||||
|
||||
# homeassistant.components.google_assistant_sdk
|
||||
gassist-text==0.0.14
|
||||
@@ -1242,7 +1242,7 @@ hole==0.9.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.95
|
||||
holidays==0.96
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260429.3
|
||||
@@ -1941,7 +1941,7 @@ pyRFXtrx==0.31.1
|
||||
pySDCP==1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.37.4
|
||||
pyTibber==0.37.5
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.8.0
|
||||
@@ -2386,7 +2386,7 @@ pyotgw==2.2.3
|
||||
pyotp==2.9.0
|
||||
|
||||
# homeassistant.components.overkiz
|
||||
pyoverkiz==1.20.0
|
||||
pyoverkiz==1.20.3
|
||||
|
||||
# homeassistant.components.palazzetti
|
||||
pypalazzetti==0.1.20
|
||||
@@ -2566,7 +2566,7 @@ python-awair==0.2.5
|
||||
python-blockchain-api==0.0.2
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
python-bsblan==5.2.0
|
||||
python-bsblan==5.2.1
|
||||
|
||||
# homeassistant.components.citybikes
|
||||
python-citybikes==0.3.3
|
||||
@@ -2581,7 +2581,7 @@ python-digitalocean==1.13.2
|
||||
python-dropbox-api==0.1.3
|
||||
|
||||
# homeassistant.components.duco
|
||||
python-duco-client==0.3.10
|
||||
python-duco-client==0.4.1
|
||||
|
||||
# homeassistant.components.ecobee
|
||||
python-ecobee-api==0.3.2
|
||||
@@ -2945,7 +2945,7 @@ sentry-sdk==2.48.0
|
||||
# homeassistant.components.acer_projector
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.usb
|
||||
serialx==1.7.0
|
||||
serialx==1.7.1
|
||||
|
||||
# homeassistant.components.sfr_box
|
||||
sfrbox-api==0.1.1
|
||||
@@ -3404,7 +3404,7 @@ zeroconf==0.148.0
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==1.3.0
|
||||
zha==1.3.1
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
||||
22
requirements_test_all.txt
generated
22
requirements_test_all.txt
generated
@@ -552,7 +552,7 @@ autoskope_client==1.4.1
|
||||
av==16.0.1
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==69
|
||||
axis==70
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.7
|
||||
@@ -591,7 +591,7 @@ bleak-retry-connector==4.6.0
|
||||
bleak==2.1.1
|
||||
|
||||
# homeassistant.components.blebox
|
||||
blebox-uniapi==2.5.2
|
||||
blebox-uniapi==2.5.3
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.25.2
|
||||
@@ -706,7 +706,7 @@ debugpy==1.8.17
|
||||
decora-wifi==1.4
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==18.2.0
|
||||
deebot-client==18.3.0
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.ohmconnect
|
||||
@@ -930,7 +930,7 @@ gTTS==2.5.3
|
||||
|
||||
# homeassistant.components.gardena_bluetooth
|
||||
# homeassistant.components.husqvarna_automower_ble
|
||||
gardena-bluetooth==2.4.0
|
||||
gardena-bluetooth==2.8.1
|
||||
|
||||
# homeassistant.components.google_assistant_sdk
|
||||
gassist-text==0.0.14
|
||||
@@ -1106,7 +1106,7 @@ hole==0.9.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.95
|
||||
holidays==0.96
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260429.3
|
||||
@@ -1684,7 +1684,7 @@ pyHomee==1.3.8
|
||||
pyRFXtrx==0.31.1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.37.4
|
||||
pyTibber==0.37.5
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.8.0
|
||||
@@ -2045,7 +2045,7 @@ pyotgw==2.2.3
|
||||
pyotp==2.9.0
|
||||
|
||||
# homeassistant.components.overkiz
|
||||
pyoverkiz==1.20.0
|
||||
pyoverkiz==1.20.3
|
||||
|
||||
# homeassistant.components.palazzetti
|
||||
pypalazzetti==0.1.20
|
||||
@@ -2198,7 +2198,7 @@ python-MotionMount==2.3.0
|
||||
python-awair==0.2.5
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
python-bsblan==5.2.0
|
||||
python-bsblan==5.2.1
|
||||
|
||||
# homeassistant.components.citybikes
|
||||
python-citybikes==0.3.3
|
||||
@@ -2207,7 +2207,7 @@ python-citybikes==0.3.3
|
||||
python-dropbox-api==0.1.3
|
||||
|
||||
# homeassistant.components.duco
|
||||
python-duco-client==0.3.10
|
||||
python-duco-client==0.4.1
|
||||
|
||||
# homeassistant.components.ecobee
|
||||
python-ecobee-api==0.3.2
|
||||
@@ -2511,7 +2511,7 @@ sentry-sdk==2.48.0
|
||||
# homeassistant.components.acer_projector
|
||||
# homeassistant.components.serial
|
||||
# homeassistant.components.usb
|
||||
serialx==1.7.0
|
||||
serialx==1.7.1
|
||||
|
||||
# homeassistant.components.sfr_box
|
||||
sfrbox-api==0.1.1
|
||||
@@ -2895,7 +2895,7 @@ zeroconf==0.148.0
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==1.3.0
|
||||
zha==1.3.1
|
||||
|
||||
# homeassistant.components.zinvolt
|
||||
zinvolt==0.4.3
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
'board_info': dict({
|
||||
'box_name': 'SILENT_CONNECT',
|
||||
'box_sub_type_name': 'Eu',
|
||||
'public_api_version': None,
|
||||
'serial_board_box': '**REDACTED**',
|
||||
'serial_board_comm': '**REDACTED**',
|
||||
'serial_duco_box': '**REDACTED**',
|
||||
'serial_duco_comm': '**REDACTED**',
|
||||
'software_version': None,
|
||||
}),
|
||||
'duco_diagnostics': list([
|
||||
dict({
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
from xml.etree.ElementTree import ParseError
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
@@ -116,6 +117,25 @@ async def test_setup_fail(hass: HomeAssistant, error) -> None:
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_setup_fail_parse_error(hass: HomeAssistant, fc_class_mock) -> None:
|
||||
"""Test setup failure due to parse error while fetching device data."""
|
||||
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.fritz.coordinator.FritzStatus.get_device_info"
|
||||
) as fs_mock,
|
||||
):
|
||||
fs_mock.side_effect = ParseError("boom")
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert entry.error_reason_translation_key == "error_parse_device_info"
|
||||
|
||||
|
||||
async def test_upnp_missing(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
"""The tests for the hassio component."""
|
||||
|
||||
from contextlib import AbstractContextManager, ExitStack as DefaultContext
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
||||
import pytest
|
||||
|
||||
from homeassistant.auth.providers.homeassistant import InvalidAuth
|
||||
from homeassistant.components.hassio.auth import HassIOBaseAuth
|
||||
from homeassistant.components.hassio.const import DATA_CONFIG_STORE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_auth_success(hassio_client_supervisor: TestClient) -> None:
|
||||
@@ -162,6 +168,45 @@ async def test_password_fails_no_auth(hassio_noauth_client: TestClient) -> None:
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("peername", "unix_socket", "expectation"),
|
||||
[
|
||||
# Unix socket transports report an empty string for peername. Before
|
||||
# the fix this raised IndexError on `peername[0]`.
|
||||
("", True, DefaultContext()),
|
||||
# Defensive: a TCP transport with no peername at all should be
|
||||
# rejected, not crash.
|
||||
(None, False, pytest.raises(HTTPUnauthorized)),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("hassio_stubs")
|
||||
async def test_check_access_unix_socket_or_missing_peername(
|
||||
hass: HomeAssistant,
|
||||
peername: str | None,
|
||||
unix_socket: bool,
|
||||
expectation: AbstractContextManager,
|
||||
) -> None:
|
||||
"""Test _check_access handles Unix socket requests and missing peername."""
|
||||
hassio_user_id = hass.data[DATA_CONFIG_STORE].data.hassio_user
|
||||
assert hassio_user_id is not None
|
||||
user = await hass.auth.async_get_user(hassio_user_id)
|
||||
assert user is not None
|
||||
|
||||
auth_view = HassIOBaseAuth(hass, user)
|
||||
request = MagicMock()
|
||||
request.transport.get_extra_info.return_value = peername
|
||||
request.__getitem__.return_value = user
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hassio.auth.is_supervisor_unix_socket_request",
|
||||
return_value=unix_socket,
|
||||
),
|
||||
expectation,
|
||||
):
|
||||
auth_view._check_access(request)
|
||||
|
||||
|
||||
async def test_password_no_user(hassio_client_supervisor: TestClient) -> None:
|
||||
"""Test changing password for invalid user."""
|
||||
resp = await hassio_client_supervisor.post(
|
||||
|
||||
@@ -272,6 +272,38 @@ async def test_options_flow(
|
||||
}
|
||||
|
||||
|
||||
async def test_options_flow_allows_submit_when_not_loaded(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_current: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test options flow allows submit when runtime data is missing."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
version=mock_config_entry_current.version,
|
||||
minor_version=mock_config_entry_current.minor_version,
|
||||
data=dict(mock_config_entry_current.data),
|
||||
options=dict(mock_config_entry_current.options),
|
||||
unique_id=mock_config_entry_current.unique_id,
|
||||
state=config_entries.ConfigEntryState.SETUP_ERROR,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_READ_MODE: API_MODE_CLOUD, CONF_CONTROL_MODE: API_MODE_LOCAL},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
CONF_READ_MODE: API_MODE_CLOUD,
|
||||
CONF_CONTROL_MODE: API_MODE_LOCAL,
|
||||
}
|
||||
|
||||
|
||||
async def test_options_flow_local_read_unavailable(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_current: MockConfigEntry,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Test the IntelliFire config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import aiohttp
|
||||
from intellifire4py.const import IntelliFireApiMode
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.intellifire import CONF_USER_ID
|
||||
from homeassistant.components.intellifire.const import (
|
||||
@@ -159,22 +161,28 @@ async def test_init_with_no_username(hass: HomeAssistant, mock_apis_single_fp) -
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
async def test_connectivity_bad(
|
||||
@pytest.mark.parametrize(
|
||||
"setup_error",
|
||||
[aiohttp.ClientConnectionError, ConnectionError, TimeoutError],
|
||||
)
|
||||
async def test_connectivity_error_during_setup_retries(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_current,
|
||||
mock_apis_single_fp,
|
||||
mock_config_entry_current: MockConfigEntry,
|
||||
mock_apis_single_fp: tuple[AsyncMock, AsyncMock, MagicMock],
|
||||
setup_error: type[Exception],
|
||||
) -> None:
|
||||
"""Test a timeout error on the setup flow."""
|
||||
"""Test a connection error during setup retries the config entry."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.intellifire.UnifiedFireplace.build_fireplace_from_common",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=TimeoutError,
|
||||
side_effect=setup_error,
|
||||
):
|
||||
mock_config_entry_current.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_current.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry_current.state is ConfigEntryState.SETUP_RETRY
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
|
||||
@@ -323,6 +323,147 @@
|
||||
"type": 1,
|
||||
"oid": "ef870fe7-daf3-4f33-b91e-62b2a809ef4e",
|
||||
"uiClass": "RollerShutter"
|
||||
},
|
||||
{
|
||||
"creationTime": 1613676700000,
|
||||
"lastUpdateTime": 1613676700000,
|
||||
"label": "Jaloezie",
|
||||
"deviceURL": "rts://1234-1234-6362/16730044",
|
||||
"shortcut": false,
|
||||
"controllableName": "rts:TiltOnlyVenetianBlindRTSComponent",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "tiltPositive",
|
||||
"nparams": 2
|
||||
},
|
||||
{
|
||||
"commandName": "tiltNegative",
|
||||
"nparams": 2
|
||||
},
|
||||
{
|
||||
"commandName": "my",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "identify",
|
||||
"nparams": 0
|
||||
}
|
||||
],
|
||||
"states": [],
|
||||
"attributes": [],
|
||||
"dataProperties": [],
|
||||
"widgetName": "TiltOnlyVenetianBlind",
|
||||
"uiProfiles": ["OpenClose"],
|
||||
"uiClass": "VenetianBlind",
|
||||
"qualifiedName": "rts:TiltOnlyVenetianBlindRTSComponent",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"states": [],
|
||||
"attributes": [],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
|
||||
"widget": "TiltOnlyVenetianBlind",
|
||||
"type": 1,
|
||||
"oid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"uiClass": "VenetianBlind"
|
||||
},
|
||||
{
|
||||
"creationTime": 1613676700000,
|
||||
"lastUpdateTime": 1613676700000,
|
||||
"label": "Office Venetian Blind",
|
||||
"deviceURL": "rts://1234-1234-6362/16747291",
|
||||
"shortcut": false,
|
||||
"controllableName": "rts:VenetianBlindRTSComponent",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "down",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "identify",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "my",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "rest",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "test",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "up",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "moveOf",
|
||||
"nparams": 2
|
||||
},
|
||||
{
|
||||
"commandName": "openConfiguration",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "tiltNegative",
|
||||
"nparams": 2
|
||||
},
|
||||
{
|
||||
"commandName": "tiltPositive",
|
||||
"nparams": 2
|
||||
}
|
||||
],
|
||||
"states": [],
|
||||
"dataProperties": [
|
||||
{
|
||||
"value": "0",
|
||||
"qualifiedName": "core:identifyInterval"
|
||||
}
|
||||
],
|
||||
"widgetName": "UpDownVenetianBlind",
|
||||
"uiProfiles": ["OpenCloseBlind", "OpenClose"],
|
||||
"uiClass": "VenetianBlind",
|
||||
"qualifiedName": "rts:VenetianBlindRTSComponent",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"attributes": [],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "6133b4a0-f514-4553-b635-d1b7beb7e7b2",
|
||||
"widget": "UpDownVenetianBlind",
|
||||
"type": 1,
|
||||
"oid": "3496a041-cafc-4d5d-ab3b-7947985812dc",
|
||||
"uiClass": "VenetianBlind"
|
||||
}
|
||||
],
|
||||
"zones": [],
|
||||
|
||||
@@ -644,6 +644,76 @@
|
||||
"oid": "0df95043-359c-4b3d-9147-f49b2b35053c",
|
||||
"uiClass": "GarageDoor"
|
||||
},
|
||||
{
|
||||
"creationTime": 1521964729000,
|
||||
"lastUpdateTime": 1521964729000,
|
||||
"label": "Basement Garage Door",
|
||||
"deviceURL": "io://1234-1234-6233/1166864",
|
||||
"shortcut": false,
|
||||
"controllableName": "io:GarageOpenerIOComponent",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "setClosure",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 0
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"type": "ContinuousState",
|
||||
"qualifiedName": "core:ClosureState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["closed", "open", "unknown"],
|
||||
"qualifiedName": "core:OpenClosedUnknownState"
|
||||
}
|
||||
],
|
||||
"widgetName": "PositionableGarageDoor",
|
||||
"uiProfiles": [
|
||||
"StatefulCloseableGarageOpener",
|
||||
"StatefulCloseable",
|
||||
"Closeable",
|
||||
"OpenClose"
|
||||
],
|
||||
"uiClass": "GarageDoor",
|
||||
"qualifiedName": "io:GarageOpenerIOComponent",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "core:OpenClosedUnknownState",
|
||||
"type": 3,
|
||||
"value": "unknown"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"name": "core:Manufacturer",
|
||||
"type": 3,
|
||||
"value": "Somfy"
|
||||
}
|
||||
],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "bcbb34ef-2241-43a1-9c5b-523aa0563ec3",
|
||||
"widget": "PositionableGarageDoor",
|
||||
"type": 1,
|
||||
"oid": "1df95043-359c-4b3d-9147-f49b2b35053d",
|
||||
"uiClass": "GarageDoor"
|
||||
},
|
||||
{
|
||||
"creationTime": 1552163547000,
|
||||
"lastUpdateTime": 1552163547000,
|
||||
@@ -7370,6 +7440,338 @@
|
||||
"widget": "DiscreteGateWithPedestrianPosition",
|
||||
"oid": "6ba9b1de-8037-41d7-9150-21f7d5f49a3f",
|
||||
"uiClass": "Gate"
|
||||
},
|
||||
{
|
||||
"creationTime": 1521964729000,
|
||||
"lastUpdateTime": 1521964729000,
|
||||
"label": "Garage Door",
|
||||
"deviceURL": "io://1234-1234-6233/16730050",
|
||||
"shortcut": false,
|
||||
"controllableName": "io:DynamicGarageDoorComponent",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "getName",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "identify",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 0
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["closed", "open"],
|
||||
"qualifiedName": "core:OpenClosedState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["available", "unavailable"],
|
||||
"qualifiedName": "core:StatusState"
|
||||
}
|
||||
],
|
||||
"widgetName": "DynamicGarageDoor",
|
||||
"uiProfiles": ["OpenClose"],
|
||||
"uiClass": "GarageDoor",
|
||||
"qualifiedName": "io:DynamicGarageDoorComponent",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "core:OpenClosedState",
|
||||
"type": 3,
|
||||
"value": "closed"
|
||||
},
|
||||
{
|
||||
"name": "core:StatusState",
|
||||
"type": 3,
|
||||
"value": "available"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"name": "core:Manufacturer",
|
||||
"type": 3,
|
||||
"value": "Somfy"
|
||||
}
|
||||
],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "bcbb34ef-2241-43a1-9c5b-523aa0563ec3",
|
||||
"widget": "DynamicGarageDoor",
|
||||
"type": 1,
|
||||
"oid": "a5d47351-52d0-4a4c-a52d-abcdef123456",
|
||||
"uiClass": "GarageDoor"
|
||||
},
|
||||
{
|
||||
"creationTime": 1521964729000,
|
||||
"lastUpdateTime": 1521964729000,
|
||||
"label": "OGP Garage Door",
|
||||
"deviceURL": "ogp://1234-1234-6233/6632544",
|
||||
"shortcut": false,
|
||||
"controllableName": "ogp:GarageDoor",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "identify",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 0
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["available", "unavailable"],
|
||||
"qualifiedName": "core:AvailabilityState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["closed", "open"],
|
||||
"qualifiedName": "core:OpenClosedState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["available", "unavailable"],
|
||||
"qualifiedName": "core:StatusState"
|
||||
}
|
||||
],
|
||||
"widgetName": "DynamicGarageDoor",
|
||||
"uiProfiles": ["StatefulOpenClose", "OpenClose"],
|
||||
"uiClass": "GarageDoor",
|
||||
"qualifiedName": "ogp:GarageDoor",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "core:AvailabilityState",
|
||||
"type": 3,
|
||||
"value": "available"
|
||||
},
|
||||
{
|
||||
"name": "core:StatusState",
|
||||
"type": 3,
|
||||
"value": "available"
|
||||
},
|
||||
{
|
||||
"name": "core:OpenClosedState",
|
||||
"type": 3,
|
||||
"value": "closed"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"name": "core:Manufacturer",
|
||||
"type": 3,
|
||||
"value": "Somfy"
|
||||
},
|
||||
{
|
||||
"name": "core:Technology",
|
||||
"type": 3,
|
||||
"value": "io2way"
|
||||
}
|
||||
],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "bcbb34ef-2241-43a1-9c5b-523aa0563ec3",
|
||||
"widget": "DynamicGarageDoor",
|
||||
"type": 1,
|
||||
"oid": "2492f7ae-3711-4160-9dae-e8910b708ce1",
|
||||
"uiClass": "GarageDoor"
|
||||
},
|
||||
{
|
||||
"creationTime": 1521964729000,
|
||||
"lastUpdateTime": 1521964729000,
|
||||
"label": "Partial Garage Door",
|
||||
"deviceURL": "io://1234-1234-6233/7433515",
|
||||
"shortcut": false,
|
||||
"controllableName": "io:DiscreteGarageOpenerWithPartialPositionIOComponent",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "partialPosition",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 0
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["closed", "open", "partial"],
|
||||
"qualifiedName": "core:OpenClosedPartialState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["available", "unavailable"],
|
||||
"qualifiedName": "core:StatusState"
|
||||
}
|
||||
],
|
||||
"widgetName": "PositionableGarageDoorWithPartialPosition",
|
||||
"uiProfiles": ["OpenClose"],
|
||||
"uiClass": "GarageDoor",
|
||||
"qualifiedName": "io:DiscreteGarageOpenerWithPartialPositionIOComponent",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "core:StatusState",
|
||||
"type": 3,
|
||||
"value": "available"
|
||||
},
|
||||
{
|
||||
"name": "core:OpenClosedPartialState",
|
||||
"type": 3,
|
||||
"value": "closed"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"name": "core:Manufacturer",
|
||||
"type": 3,
|
||||
"value": "Somfy"
|
||||
}
|
||||
],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "bcbb34ef-2241-43a1-9c5b-523aa0563ec3",
|
||||
"widget": "PositionableGarageDoorWithPartialPosition",
|
||||
"type": 1,
|
||||
"oid": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
|
||||
"uiClass": "GarageDoor"
|
||||
},
|
||||
{
|
||||
"creationTime": 1660551260000,
|
||||
"lastUpdateTime": 1660551260000,
|
||||
"label": "OGP Gate",
|
||||
"deviceURL": "ogp://1234-1234-6233/10410217",
|
||||
"shortcut": false,
|
||||
"controllableName": "ogp:Gate",
|
||||
"definition": {
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "close",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "identify",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "open",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"commandName": "setName",
|
||||
"nparams": 1
|
||||
},
|
||||
{
|
||||
"commandName": "stop",
|
||||
"nparams": 0
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["available", "unavailable"],
|
||||
"qualifiedName": "core:AvailabilityState"
|
||||
},
|
||||
{
|
||||
"type": "DataState",
|
||||
"qualifiedName": "core:NameState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["closed", "open"],
|
||||
"qualifiedName": "core:OpenClosedState"
|
||||
},
|
||||
{
|
||||
"type": "DiscreteState",
|
||||
"values": ["available", "unavailable"],
|
||||
"qualifiedName": "core:StatusState"
|
||||
}
|
||||
],
|
||||
"dataProperties": [],
|
||||
"widgetName": "DynamicGate",
|
||||
"uiProfiles": ["StatefulOpenClose", "OpenClose"],
|
||||
"uiClass": "Gate",
|
||||
"qualifiedName": "ogp:Gate",
|
||||
"type": "ACTUATOR"
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "core:NameState",
|
||||
"type": 3,
|
||||
"value": "OGP Gate"
|
||||
},
|
||||
{
|
||||
"name": "core:AvailabilityState",
|
||||
"type": 3,
|
||||
"value": "available"
|
||||
},
|
||||
{
|
||||
"name": "core:StatusState",
|
||||
"type": 3,
|
||||
"value": "available"
|
||||
},
|
||||
{
|
||||
"name": "core:OpenClosedState",
|
||||
"type": 3,
|
||||
"value": "open"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"name": "core:Technology",
|
||||
"type": 3,
|
||||
"value": "io2way"
|
||||
},
|
||||
{
|
||||
"name": "core:Manufacturer",
|
||||
"type": 3,
|
||||
"value": "Somfy"
|
||||
}
|
||||
],
|
||||
"available": true,
|
||||
"enabled": true,
|
||||
"placeOID": "bcbb34ef-2241-43a1-9c5b-523aa0563ec3",
|
||||
"type": 1,
|
||||
"widget": "DynamicGate",
|
||||
"oid": "a8d3e9f1-4b2c-4d5e-8f6a-1234567890ab",
|
||||
"uiClass": "Gate"
|
||||
}
|
||||
],
|
||||
"zones": [],
|
||||
|
||||
@@ -741,6 +741,71 @@
|
||||
"uiClass": "Pergola"
|
||||
}
|
||||
},
|
||||
{
|
||||
"deviceURL": "rts://1234-5678-3293/16757826",
|
||||
"available": true,
|
||||
"synced": true,
|
||||
"type": 1,
|
||||
"states": [],
|
||||
"label": "Kitchen Pergola",
|
||||
"subsystemId": 0,
|
||||
"attributes": [],
|
||||
"enabled": true,
|
||||
"controllableName": "rts:BioclimaticPergolaRTSComponent",
|
||||
"definition": {
|
||||
"states": [],
|
||||
"widgetName": "UpDownBioclimaticPergola",
|
||||
"type": "ACTUATOR",
|
||||
"attributes": [],
|
||||
"commands": [
|
||||
{
|
||||
"commandName": "identify",
|
||||
"nparams": 0
|
||||
},
|
||||
{
|
||||
"nparams": 2,
|
||||
"commandName": "tiltPositive",
|
||||
"paramsSig": "p1,p2"
|
||||
},
|
||||
{
|
||||
"nparams": 0,
|
||||
"commandName": "my",
|
||||
"paramsSig": "*p1"
|
||||
},
|
||||
{
|
||||
"nparams": 0,
|
||||
"commandName": "stop",
|
||||
"paramsSig": "*p1"
|
||||
},
|
||||
{
|
||||
"nparams": 2,
|
||||
"commandName": "tiltNegative",
|
||||
"paramsSig": "p1,p2"
|
||||
},
|
||||
{
|
||||
"nparams": 0,
|
||||
"commandName": "close",
|
||||
"paramsSig": "*p1"
|
||||
},
|
||||
{
|
||||
"nparams": 0,
|
||||
"commandName": "down",
|
||||
"paramsSig": "*p1"
|
||||
},
|
||||
{
|
||||
"nparams": 0,
|
||||
"commandName": "open",
|
||||
"paramsSig": "*p1"
|
||||
},
|
||||
{
|
||||
"nparams": 0,
|
||||
"commandName": "up",
|
||||
"paramsSig": "*p1"
|
||||
}
|
||||
],
|
||||
"uiClass": "Pergola"
|
||||
}
|
||||
},
|
||||
{
|
||||
"creationTime": 1686173907452,
|
||||
"deviceURL": "rts://1234-5678-3293/16757362",
|
||||
|
||||
@@ -323,6 +323,60 @@
|
||||
'state': 'open',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_connexoon_rts_asia.json][cover.jaloezie-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.jaloezie',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.BLIND: 'blind'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 123>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'rts://1234-1234-6362/16730044',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_connexoon_rts_asia.json][cover.jaloezie-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'assumed_state': True,
|
||||
'device_class': 'blind',
|
||||
'friendly_name': 'Jaloezie',
|
||||
'is_closed': None,
|
||||
'supported_features': <CoverEntityFeature: 123>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.jaloezie',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_connexoon_rts_asia.json][cover.kitchen_shutter-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -377,6 +431,60 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_connexoon_rts_asia.json][cover.office_venetian_blind-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.office_venetian_blind',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.BLIND: 'blind'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 123>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'rts://1234-1234-6362/16747291',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_connexoon_rts_asia.json][cover.office_venetian_blind-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'assumed_state': True,
|
||||
'device_class': 'blind',
|
||||
'friendly_name': 'Office Venetian Blind',
|
||||
'is_closed': None,
|
||||
'supported_features': <CoverEntityFeature: 123>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.office_venetian_blind',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_connexoon_rts_asia.json][cover.patio_shutter-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -919,6 +1027,59 @@
|
||||
'state': 'open',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.basement_garage_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.basement_garage_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.GARAGE: 'garage'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 15>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'io://1234-1234-6233/1166864',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.basement_garage_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'garage',
|
||||
'friendly_name': 'Basement Garage Door',
|
||||
'is_closed': None,
|
||||
'supported_features': <CoverEntityFeature: 15>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.basement_garage_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.bedroom_blinds-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1081,6 +1242,59 @@
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.garage_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.garage_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.GARAGE: 'garage'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'io://1234-1234-6233/16730050',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.garage_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'garage',
|
||||
'friendly_name': 'Garage Door',
|
||||
'is_closed': True,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.garage_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.garden_gate-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1512,6 +1726,165 @@
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.ogp_garage_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.ogp_garage_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.GARAGE: 'garage'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'ogp://1234-1234-6233/6632544',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.ogp_garage_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'garage',
|
||||
'friendly_name': 'OGP Garage Door',
|
||||
'is_closed': True,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.ogp_garage_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.ogp_gate-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.ogp_gate',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.GATE: 'gate'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'ogp://1234-1234-6233/10410217',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.ogp_gate-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'gate',
|
||||
'friendly_name': 'OGP Gate',
|
||||
'is_closed': False,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.ogp_gate',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'open',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.partial_garage_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.partial_garage_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.GARAGE: 'garage'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'io://1234-1234-6233/7433515',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.partial_garage_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'garage',
|
||||
'friendly_name': 'Partial Garage Door',
|
||||
'is_closed': True,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.partial_garage_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[cloud_somfy_tahoma_v2_europe.json][cover.patio_shutter-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -3090,6 +3463,60 @@
|
||||
'state': 'closed',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[local_somfy_tahoma_v2_europe.json][cover.kitchen_pergola-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'cover',
|
||||
'entity_category': None,
|
||||
'entity_id': 'cover.kitchen_pergola',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <CoverDeviceClass.AWNING: 'awning'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'overkiz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'rts://1234-5678-3293/16757826',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[local_somfy_tahoma_v2_europe.json][cover.kitchen_pergola-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'assumed_state': True,
|
||||
'device_class': 'awning',
|
||||
'friendly_name': 'Kitchen Pergola',
|
||||
'is_closed': None,
|
||||
'supported_features': <CoverEntityFeature: 11>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.kitchen_pergola',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_entities_snapshot[local_somfy_tahoma_v2_europe.json][cover.living_room_curtain-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -1810,7 +1810,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'io://1234-5678-1698/15702199#2-core:RSSILevelState',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'unit_of_measurement': 'dB',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.garden_radiator_bathroom_temperature_sensor_rssi_level-state]
|
||||
@@ -1819,7 +1819,7 @@
|
||||
'device_class': 'signal_strength',
|
||||
'friendly_name': 'Garden Radiator Bathroom Temperature Sensor RSSI level',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'unit_of_measurement': 'dB',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.garden_radiator_bathroom_temperature_sensor_rssi_level',
|
||||
@@ -2804,7 +2804,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'io://1234-5678-1698/9253412#2-core:RSSILevelState',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'unit_of_measurement': 'dB',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.living_room_radiator_study_temp_probe_rssi_level-state]
|
||||
@@ -2813,7 +2813,7 @@
|
||||
'device_class': 'signal_strength',
|
||||
'friendly_name': 'Living Room Radiator Study Temp Probe RSSI level',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'unit_of_measurement': 'dB',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.living_room_radiator_study_temp_probe_rssi_level',
|
||||
@@ -4182,7 +4182,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'io://1234-5678-1698/9187218#2-core:RSSILevelState',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'unit_of_measurement': 'dB',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_snapshot[cloud_nexity_rail_din_europe.json][sensor.study_radiator_nursery_temp_probe_rssi_level-state]
|
||||
@@ -4191,7 +4191,7 @@
|
||||
'device_class': 'signal_strength',
|
||||
'friendly_name': 'Study Radiator Nursery Temp Probe RSSI level',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'unit_of_measurement': 'dB',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.study_radiator_nursery_temp_probe_rssi_level',
|
||||
|
||||
@@ -29,7 +29,12 @@ from homeassistant.components.cover import (
|
||||
CoverEntityFeature,
|
||||
CoverState,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
@@ -53,6 +58,11 @@ PERGOLA = FixtureDevice(
|
||||
"io://1234-5678-3293/7614902",
|
||||
"cover.garden_pergola",
|
||||
)
|
||||
UP_DOWN_BIOCLIMATIC_PERGOLA = FixtureDevice(
|
||||
"setup/local_somfy_tahoma_v2_europe.json",
|
||||
"rts://1234-5678-3293/16757826",
|
||||
"cover.kitchen_pergola",
|
||||
)
|
||||
RTS = FixtureDevice(
|
||||
"setup/cloud_somfy_connexoon_rts_asia.json",
|
||||
"rts://1234-1234-6362/16730022",
|
||||
@@ -90,6 +100,36 @@ POSITIONABLE_DUAL_ROLLER_SHUTTER = FixtureDevice(
|
||||
"io://1234-5678-5010/12931361",
|
||||
"cover.basement_roller_shutter",
|
||||
)
|
||||
TILT_ONLY_VENETIAN_BLIND = FixtureDevice(
|
||||
"setup/cloud_somfy_connexoon_rts_asia.json",
|
||||
"rts://1234-1234-6362/16730044",
|
||||
"cover.jaloezie",
|
||||
)
|
||||
UP_DOWN_VENETIAN_BLIND = FixtureDevice(
|
||||
"setup/cloud_somfy_connexoon_rts_asia.json",
|
||||
"rts://1234-1234-6362/16747291",
|
||||
"cover.office_venetian_blind",
|
||||
)
|
||||
DYNAMIC_GARAGE_DOOR = FixtureDevice(
|
||||
"setup/cloud_somfy_tahoma_v2_europe.json",
|
||||
"io://1234-1234-6233/16730050",
|
||||
"cover.garage_door",
|
||||
)
|
||||
DYNAMIC_GARAGE_DOOR_OGP = FixtureDevice(
|
||||
"setup/cloud_somfy_tahoma_v2_europe.json",
|
||||
"ogp://1234-1234-6233/6632544",
|
||||
"cover.ogp_garage_door",
|
||||
)
|
||||
PARTIAL_GARAGE_DOOR = FixtureDevice(
|
||||
"setup/cloud_somfy_tahoma_v2_europe.json",
|
||||
"io://1234-1234-6233/7433515",
|
||||
"cover.partial_garage_door",
|
||||
)
|
||||
DYNAMIC_GATE = FixtureDevice(
|
||||
"setup/cloud_somfy_tahoma_v2_europe.json",
|
||||
"ogp://1234-1234-6233/10410217",
|
||||
"cover.ogp_gate",
|
||||
)
|
||||
|
||||
SNAPSHOT_FIXTURES = [
|
||||
AWNING,
|
||||
@@ -130,28 +170,148 @@ async def test_cover_entities_snapshot(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("device", "service", "command_name", "expected_state"),
|
||||
("device", "service", "command_name", "parameters", "expected_state"),
|
||||
[
|
||||
(SHUTTER, SERVICE_OPEN_COVER, "open", CoverState.OPENING),
|
||||
(AWNING, SERVICE_OPEN_COVER, "deploy", CoverState.OPENING),
|
||||
(GARAGE, SERVICE_OPEN_COVER, "open", CoverState.OPENING),
|
||||
(SHUTTER, SERVICE_CLOSE_COVER, "close", CoverState.CLOSING),
|
||||
(AWNING, SERVICE_CLOSE_COVER, "undeploy", CoverState.CLOSING),
|
||||
(GARAGE, SERVICE_CLOSE_COVER, "close", CoverState.CLOSING),
|
||||
(SHUTTER, SERVICE_STOP_COVER, "stop", CoverState.CLOSED),
|
||||
(AWNING, SERVICE_STOP_COVER, "stop", CoverState.CLOSED),
|
||||
(GARAGE, SERVICE_STOP_COVER, "stop", CoverState.CLOSED),
|
||||
(SHUTTER, SERVICE_OPEN_COVER, "open", None, CoverState.OPENING),
|
||||
(AWNING, SERVICE_OPEN_COVER, "deploy", None, CoverState.OPENING),
|
||||
(GARAGE, SERVICE_OPEN_COVER, "open", None, CoverState.OPENING),
|
||||
(DYNAMIC_GARAGE_DOOR, SERVICE_OPEN_COVER, "open", None, CoverState.OPENING),
|
||||
(DYNAMIC_GARAGE_DOOR_OGP, SERVICE_OPEN_COVER, "open", None, CoverState.OPENING),
|
||||
(DYNAMIC_GATE, SERVICE_OPEN_COVER, "open", None, CoverState.OPENING),
|
||||
(PARTIAL_GARAGE_DOOR, SERVICE_OPEN_COVER, "open", None, CoverState.OPENING),
|
||||
(
|
||||
UP_DOWN_BIOCLIMATIC_PERGOLA,
|
||||
SERVICE_OPEN_COVER,
|
||||
"open",
|
||||
[0],
|
||||
CoverState.OPENING,
|
||||
),
|
||||
(TILT_ONLY_VENETIAN_BLIND, SERVICE_OPEN_COVER, "open", [0], CoverState.OPENING),
|
||||
(UP_DOWN_VENETIAN_BLIND, SERVICE_OPEN_COVER, "open", [0], CoverState.OPENING),
|
||||
(SHUTTER, SERVICE_CLOSE_COVER, "close", None, CoverState.CLOSING),
|
||||
(AWNING, SERVICE_CLOSE_COVER, "undeploy", None, CoverState.CLOSING),
|
||||
(GARAGE, SERVICE_CLOSE_COVER, "close", None, CoverState.CLOSING),
|
||||
(DYNAMIC_GARAGE_DOOR, SERVICE_CLOSE_COVER, "close", None, CoverState.CLOSING),
|
||||
(
|
||||
DYNAMIC_GARAGE_DOOR_OGP,
|
||||
SERVICE_CLOSE_COVER,
|
||||
"close",
|
||||
None,
|
||||
CoverState.CLOSING,
|
||||
),
|
||||
(DYNAMIC_GATE, SERVICE_CLOSE_COVER, "close", None, CoverState.CLOSING),
|
||||
(PARTIAL_GARAGE_DOOR, SERVICE_CLOSE_COVER, "close", None, CoverState.CLOSING),
|
||||
(
|
||||
UP_DOWN_BIOCLIMATIC_PERGOLA,
|
||||
SERVICE_CLOSE_COVER,
|
||||
"close",
|
||||
[0],
|
||||
CoverState.CLOSING,
|
||||
),
|
||||
(
|
||||
TILT_ONLY_VENETIAN_BLIND,
|
||||
SERVICE_CLOSE_COVER,
|
||||
"close",
|
||||
[0],
|
||||
CoverState.CLOSING,
|
||||
),
|
||||
(UP_DOWN_VENETIAN_BLIND, SERVICE_CLOSE_COVER, "close", [0], CoverState.CLOSING),
|
||||
(SHUTTER, SERVICE_STOP_COVER, "stop", None, CoverState.CLOSED),
|
||||
(AWNING, SERVICE_STOP_COVER, "stop", None, CoverState.CLOSED),
|
||||
(GARAGE, SERVICE_STOP_COVER, "stop", None, CoverState.CLOSED),
|
||||
(DYNAMIC_GARAGE_DOOR, SERVICE_STOP_COVER, "stop", None, CoverState.CLOSED),
|
||||
(DYNAMIC_GARAGE_DOOR_OGP, SERVICE_STOP_COVER, "stop", None, CoverState.CLOSED),
|
||||
(DYNAMIC_GATE, SERVICE_STOP_COVER, "stop", None, CoverState.OPEN),
|
||||
(PARTIAL_GARAGE_DOOR, SERVICE_STOP_COVER, "stop", None, CoverState.CLOSED),
|
||||
(
|
||||
UP_DOWN_BIOCLIMATIC_PERGOLA,
|
||||
SERVICE_STOP_COVER,
|
||||
"stop",
|
||||
[0],
|
||||
STATE_UNKNOWN,
|
||||
),
|
||||
(TILT_ONLY_VENETIAN_BLIND, SERVICE_STOP_COVER, "stop", [0], STATE_UNKNOWN),
|
||||
(
|
||||
TILT_ONLY_VENETIAN_BLIND,
|
||||
SERVICE_OPEN_COVER_TILT,
|
||||
"tiltPositive",
|
||||
[1, 0],
|
||||
CoverState.OPENING,
|
||||
),
|
||||
(
|
||||
TILT_ONLY_VENETIAN_BLIND,
|
||||
SERVICE_CLOSE_COVER_TILT,
|
||||
"tiltNegative",
|
||||
[1, 0],
|
||||
CoverState.CLOSING,
|
||||
),
|
||||
(
|
||||
TILT_ONLY_VENETIAN_BLIND,
|
||||
SERVICE_STOP_COVER_TILT,
|
||||
"stop",
|
||||
[0],
|
||||
STATE_UNKNOWN,
|
||||
),
|
||||
(UP_DOWN_VENETIAN_BLIND, SERVICE_STOP_COVER, "stop", [0], STATE_UNKNOWN),
|
||||
(
|
||||
UP_DOWN_VENETIAN_BLIND,
|
||||
SERVICE_OPEN_COVER_TILT,
|
||||
"tiltPositive",
|
||||
[15, 1],
|
||||
CoverState.OPENING,
|
||||
),
|
||||
(
|
||||
UP_DOWN_VENETIAN_BLIND,
|
||||
SERVICE_CLOSE_COVER_TILT,
|
||||
"tiltNegative",
|
||||
[15, 1],
|
||||
CoverState.CLOSING,
|
||||
),
|
||||
(
|
||||
UP_DOWN_VENETIAN_BLIND,
|
||||
SERVICE_STOP_COVER_TILT,
|
||||
"stop",
|
||||
[0],
|
||||
STATE_UNKNOWN,
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"open-roller-shutter",
|
||||
"open-awning",
|
||||
"open-garage-door",
|
||||
"open-dynamic-garage-door",
|
||||
"open-dynamic-garage-door-ogp",
|
||||
"open-dynamic-gate",
|
||||
"open-partial-garage-door",
|
||||
"open-up-down-bioclimatic-pergola",
|
||||
"open-tilt-only-venetian-blind",
|
||||
"open-venetian-blind-rts",
|
||||
"close-roller-shutter",
|
||||
"close-awning",
|
||||
"close-garage-door",
|
||||
"close-dynamic-garage-door",
|
||||
"close-dynamic-garage-door-ogp",
|
||||
"close-dynamic-gate",
|
||||
"close-partial-garage-door",
|
||||
"close-up-down-bioclimatic-pergola",
|
||||
"close-tilt-only-venetian-blind",
|
||||
"close-venetian-blind-rts",
|
||||
"stop-roller-shutter",
|
||||
"stop-awning",
|
||||
"stop-garage-door",
|
||||
"stop-dynamic-garage-door",
|
||||
"stop-dynamic-garage-door-ogp",
|
||||
"stop-dynamic-gate",
|
||||
"stop-partial-garage-door",
|
||||
"stop-up-down-bioclimatic-pergola",
|
||||
"stop-tilt-only-venetian-blind",
|
||||
"open-tilt-tilt-only-venetian-blind",
|
||||
"close-tilt-tilt-only-venetian-blind",
|
||||
"stop-tilt-tilt-only-venetian-blind",
|
||||
"stop-venetian-blind-rts",
|
||||
"open-tilt-venetian-blind-rts",
|
||||
"close-tilt-venetian-blind-rts",
|
||||
"stop-tilt-venetian-blind-rts",
|
||||
],
|
||||
)
|
||||
async def test_cover_service_actions(
|
||||
@@ -161,9 +321,10 @@ async def test_cover_service_actions(
|
||||
device: FixtureDevice,
|
||||
service: str,
|
||||
command_name: str,
|
||||
expected_state: CoverState,
|
||||
parameters: list[Any] | None,
|
||||
expected_state: CoverState | str,
|
||||
) -> None:
|
||||
"""Test open, close, and stop cover services."""
|
||||
"""Test open, close, and stop cover and tilt services."""
|
||||
await setup_overkiz_integration(fixture=device.fixture)
|
||||
|
||||
await hass.services.async_call(
|
||||
@@ -180,6 +341,7 @@ async def test_cover_service_actions(
|
||||
mock_client,
|
||||
device_url=device.device_url,
|
||||
command_name=command_name,
|
||||
parameters=parameters,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ from homeassistant.setup import async_setup_component
|
||||
from . import MockTodoListEntity, create_mock_platform
|
||||
|
||||
from tests.common import async_mock_service, mock_device_registry
|
||||
from tests.components.common import (
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
assert_trigger_options_supported,
|
||||
)
|
||||
|
||||
TODO_ENTITY_ID1 = "todo.list_one"
|
||||
TODO_ENTITY_ID2 = "todo.list_two"
|
||||
@@ -122,6 +126,47 @@ def service_calls(hass: HomeAssistant) -> list[ServiceCall]:
|
||||
return async_mock_service(hass, "test", "item_added")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trigger_key",
|
||||
[
|
||||
"todo.item_added",
|
||||
"todo.item_completed",
|
||||
"todo.item_removed",
|
||||
],
|
||||
)
|
||||
async def test_todo_triggers_gated_by_labs_flag(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
|
||||
) -> None:
|
||||
"""Test the todo triggers are gated by the labs flag."""
|
||||
await assert_trigger_gated_by_labs_flag(hass, caplog, trigger_key)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@pytest.mark.parametrize(
|
||||
("trigger_key", "base_options", "supports_behavior", "supports_duration"),
|
||||
[
|
||||
("todo.item_added", None, False, False),
|
||||
("todo.item_completed", None, False, False),
|
||||
("todo.item_removed", None, False, False),
|
||||
],
|
||||
)
|
||||
async def test_todo_trigger_options_validation(
|
||||
hass: HomeAssistant,
|
||||
trigger_key: str,
|
||||
base_options: dict[str, Any] | None,
|
||||
supports_behavior: bool,
|
||||
supports_duration: bool,
|
||||
) -> None:
|
||||
"""Test that todo triggers support the expected options."""
|
||||
await assert_trigger_options_supported(
|
||||
hass,
|
||||
trigger_key,
|
||||
base_options,
|
||||
supports_behavior=supports_behavior,
|
||||
supports_duration=supports_duration,
|
||||
)
|
||||
|
||||
|
||||
def _assert_service_calls(
|
||||
service_calls: list[ServiceCall], expected_calls: list[dict[str, Any]]
|
||||
) -> None:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Tests for WiZ binary_sensor platform."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import wiz
|
||||
from homeassistant.components.wiz.binary_sensor import OCCUPANCY_UNIQUE_ID
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@@ -21,20 +23,27 @@ from . import (
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize("occupancy_source", ["pir", "wfsens"])
|
||||
async def test_binary_sensor_created_from_push_updates(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
occupancy_source: str,
|
||||
) -> None:
|
||||
"""Test a binary sensor created from push updates."""
|
||||
bulb, _ = await async_setup_integration(hass)
|
||||
|
||||
await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": True})
|
||||
await async_push_update(
|
||||
hass, bulb, {"mac": FAKE_MAC, "src": occupancy_source, "state": True}
|
||||
)
|
||||
|
||||
entity_id = "binary_sensor.mock_title_occupancy"
|
||||
assert entity_registry.async_get(entity_id).unique_id == f"{FAKE_MAC}_occupancy"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
await async_push_update(hass, bulb, {"mac": FAKE_MAC, "src": "pir", "state": False})
|
||||
await async_push_update(
|
||||
hass, bulb, {"mac": FAKE_MAC, "src": occupancy_source, "state": False}
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
@@ -47,6 +47,8 @@ INTEGRATION_DISCOVERY = {
|
||||
|
||||
async def test_form(hass: HomeAssistant) -> None:
|
||||
"""Test we get the form."""
|
||||
bulb = _mocked_wizlight(None, None, FAKE_DIMMABLE_BULB)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
@@ -54,7 +56,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||
assert result["errors"] == {}
|
||||
# Patch functions
|
||||
with (
|
||||
_patch_wizlight(),
|
||||
_patch_wizlight(device=bulb),
|
||||
patch(
|
||||
"homeassistant.components.wiz.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -76,6 +78,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
bulb.async_close.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_user_flow_enters_dns_name(hass: HomeAssistant) -> None:
|
||||
@@ -137,10 +140,10 @@ async def test_user_form_exceptions(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.wiz.wizlight.getBulbConfig",
|
||||
side_effect=side_effect,
|
||||
):
|
||||
bulb = _mocked_wizlight(None, None, FAKE_DIMMABLE_BULB)
|
||||
bulb.get_bulbtype = AsyncMock(side_effect=side_effect)
|
||||
|
||||
with _patch_wizlight(device=bulb):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
TEST_CONNECTION,
|
||||
@@ -148,6 +151,7 @@ async def test_user_form_exceptions(
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": error_base}
|
||||
bulb.async_close.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_form_updates_unique_id(hass: HomeAssistant) -> None:
|
||||
@@ -185,10 +189,10 @@ async def test_discovered_by_dhcp_connection_fails(
|
||||
hass: HomeAssistant, source, data
|
||||
) -> None:
|
||||
"""Test we abort on connection failure."""
|
||||
with patch(
|
||||
"homeassistant.components.wiz.wizlight.getBulbConfig",
|
||||
side_effect=WizLightTimeOutError,
|
||||
):
|
||||
bulb = _mocked_wizlight(None, None, FAKE_DIMMABLE_BULB)
|
||||
bulb.get_bulbtype = AsyncMock(side_effect=WizLightTimeOutError)
|
||||
|
||||
with _patch_wizlight(device=bulb):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": source}, data=data
|
||||
)
|
||||
@@ -196,6 +200,7 @@ async def test_discovered_by_dhcp_connection_fails(
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
||||
bulb.async_close.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -263,9 +268,9 @@ async def test_discovered_by_dhcp_or_integration_discovery(
|
||||
hass: HomeAssistant, source, data, bulb_type, extended_white_range, name
|
||||
) -> None:
|
||||
"""Test we can configure when discovered from dhcp or discovery."""
|
||||
with _patch_wizlight(
|
||||
device=None, extended_white_range=extended_white_range, bulb_type=bulb_type
|
||||
):
|
||||
bulb = _mocked_wizlight(None, extended_white_range, bulb_type)
|
||||
|
||||
with _patch_wizlight(device=bulb):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": source}, data=data
|
||||
)
|
||||
@@ -273,11 +278,12 @@ async def test_discovered_by_dhcp_or_integration_discovery(
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
bulb.async_close.assert_awaited_once()
|
||||
|
||||
bulb.async_close.reset_mock()
|
||||
|
||||
with (
|
||||
_patch_wizlight(
|
||||
device=None, extended_white_range=extended_white_range, bulb_type=bulb_type
|
||||
),
|
||||
_patch_wizlight(device=bulb),
|
||||
patch(
|
||||
"homeassistant.components.wiz.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -299,6 +305,7 @@ async def test_discovered_by_dhcp_or_integration_discovery(
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
bulb.async_close.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -393,8 +400,10 @@ async def test_setup_via_discovery(hass: HomeAssistant) -> None:
|
||||
assert result2["step_id"] == "pick_device"
|
||||
assert not result2["errors"]
|
||||
|
||||
bulb = _mocked_wizlight(None, None, FAKE_DIMMABLE_BULB)
|
||||
|
||||
with (
|
||||
_patch_wizlight(),
|
||||
_patch_wizlight(device=bulb),
|
||||
patch(
|
||||
"homeassistant.components.wiz.async_setup", return_value=True
|
||||
) as mock_setup,
|
||||
@@ -415,6 +424,7 @@ async def test_setup_via_discovery(hass: HomeAssistant) -> None:
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
bulb.async_close.assert_awaited_once()
|
||||
|
||||
# ignore configured devices
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
||||
@@ -1513,3 +1513,15 @@ def fibaro_fgms001_v2_8_fixture(
|
||||
node = Node(client, fibaro_fgms001_v2_8_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="fibaro_fgms001_unknown_firmware")
|
||||
def fibaro_fgms001_unknown_firmware_fixture(
|
||||
client: MagicMock, fibaro_fgms001_v2_8_state: NodeDataType
|
||||
) -> Node:
|
||||
"""Load FGMS001 node with no reported firmware version."""
|
||||
state = copy.deepcopy(fibaro_fgms001_v2_8_state)
|
||||
state.pop("firmwareVersion", None)
|
||||
node = Node(client, state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
@@ -33,7 +33,7 @@ from homeassistant.components.zwave_js.discovery_data_template import (
|
||||
DynamicCurrentTempClimateDataTemplate,
|
||||
)
|
||||
from homeassistant.components.zwave_js.helpers import get_device_id
|
||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY, ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
@@ -666,6 +666,40 @@ async def test_nabu_casa_zwa2_legacy(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platforms", [[Platform.BINARY_SENSOR, Platform.LIGHT]])
|
||||
async def test_fibaro_fgms001_unknown_firmware_setup(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
client: MagicMock,
|
||||
fibaro_fgms001_unknown_firmware: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setup completes when an FGMS001 node has no firmware version.
|
||||
|
||||
Regression test for a crash where comparing AwesomeVersion(None) to a
|
||||
schema's firmware_version_range raised AwesomeVersionCompareException
|
||||
and aborted setup of the whole config entry.
|
||||
"""
|
||||
assert integration.state is ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
identifiers={get_device_id(client.driver, fibaro_fgms001_unknown_firmware)}
|
||||
)
|
||||
assert device is not None
|
||||
|
||||
entries = er.async_entries_for_device(
|
||||
entity_registry, device.id, include_disabled_entities=True
|
||||
)
|
||||
motion_entries = [
|
||||
entry
|
||||
for entry in entries
|
||||
if entry.domain == BINARY_SENSOR_DOMAIN
|
||||
and entry.original_device_class == BinarySensorDeviceClass.MOTION
|
||||
]
|
||||
assert motion_entries == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platforms", [[Platform.BINARY_SENSOR, Platform.LIGHT]])
|
||||
async def test_fibaro_fgms001_v2_8_motion_discovery(
|
||||
hass: HomeAssistant,
|
||||
|
||||
Reference in New Issue
Block a user