mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add support for Window Covering CC (#93314)
* Refactor zwave_js.cover and improve test coverage * Remove extra fixtures * cleanup old stuff * Get coverage to 100 * Add support for Window Covering CC * fix bug * typo * remove redundant stuff * slight change to improve readability * Add device class * Update to match specs
This commit is contained in:
parent
6a8d18ab35
commit
90bf5429ca
@ -3,6 +3,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from zwave_js_server.const.command_class.window_covering import (
|
||||||
|
WindowCoveringPropertyKey,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.const import APPLICATION_NAME, __version__ as HA_VERSION
|
from homeassistant.const import APPLICATION_NAME, __version__ as HA_VERSION
|
||||||
|
|
||||||
USER_AGENT = {APPLICATION_NAME: HA_VERSION}
|
USER_AGENT = {APPLICATION_NAME: HA_VERSION}
|
||||||
@ -131,3 +135,35 @@ ENTITY_DESC_KEY_TOTAL_INCREASING = "total_increasing"
|
|||||||
API_KEY_FIRMWARE_UPDATE_SERVICE = (
|
API_KEY_FIRMWARE_UPDATE_SERVICE = (
|
||||||
"2e39d98fc56386389fbb35e5a98fa1b44b9fdd8f971460303587cff408430d4cfcde6134"
|
"2e39d98fc56386389fbb35e5a98fa1b44b9fdd8f971460303587cff408430d4cfcde6134"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Platform constants
|
||||||
|
# cover
|
||||||
|
COVER_POSITION_PROPERTY_KEYS: set[str | int | None] = {
|
||||||
|
WindowCoveringPropertyKey.INBOUND_BOTTOM,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_BOTTOM_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_LEFT,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_LEFT_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_LEFT_RIGHT,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_LEFT_RIGHT_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_RIGHT,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_RIGHT_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_TOP,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_TOP_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_TOP_BOTTOM,
|
||||||
|
WindowCoveringPropertyKey.INBOUND_TOP_BOTTOM_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_BOTTOM,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_BOTTOM_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_LEFT,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_LEFT_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_RIGHT,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_RIGHT_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_TOP,
|
||||||
|
WindowCoveringPropertyKey.OUTBOUND_TOP_NO_POSITION,
|
||||||
|
}
|
||||||
|
|
||||||
|
COVER_TILT_PROPERTY_KEYS: set[str | int | None] = {
|
||||||
|
WindowCoveringPropertyKey.HORIZONTAL_SLATS_ANGLE,
|
||||||
|
WindowCoveringPropertyKey.HORIZONTAL_SLATS_ANGLE_NO_POSITION,
|
||||||
|
WindowCoveringPropertyKey.VERTICAL_SLATS_ANGLE,
|
||||||
|
WindowCoveringPropertyKey.VERTICAL_SLATS_ANGLE_NO_POSITION,
|
||||||
|
}
|
||||||
|
@ -4,13 +4,23 @@ from __future__ import annotations
|
|||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from zwave_js_server.client import Client as ZwaveClient
|
from zwave_js_server.client import Client as ZwaveClient
|
||||||
from zwave_js_server.const import TARGET_STATE_PROPERTY, TARGET_VALUE_PROPERTY
|
from zwave_js_server.const import (
|
||||||
|
CURRENT_VALUE_PROPERTY,
|
||||||
|
TARGET_STATE_PROPERTY,
|
||||||
|
TARGET_VALUE_PROPERTY,
|
||||||
|
)
|
||||||
from zwave_js_server.const.command_class.barrier_operator import BarrierState
|
from zwave_js_server.const.command_class.barrier_operator import BarrierState
|
||||||
from zwave_js_server.const.command_class.multilevel_switch import (
|
from zwave_js_server.const.command_class.multilevel_switch import (
|
||||||
COVER_ON_PROPERTY,
|
COVER_ON_PROPERTY,
|
||||||
COVER_OPEN_PROPERTY,
|
COVER_OPEN_PROPERTY,
|
||||||
COVER_UP_PROPERTY,
|
COVER_UP_PROPERTY,
|
||||||
)
|
)
|
||||||
|
from zwave_js_server.const.command_class.window_covering import (
|
||||||
|
NO_POSITION_PROPERTY_KEYS,
|
||||||
|
NO_POSITION_SUFFIX,
|
||||||
|
WINDOW_COVERING_OPEN_PROPERTY,
|
||||||
|
SlatStates,
|
||||||
|
)
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
from zwave_js_server.model.value import Value as ZwaveValue
|
from zwave_js_server.model.value import Value as ZwaveValue
|
||||||
|
|
||||||
@ -27,7 +37,12 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DATA_CLIENT, DOMAIN
|
from .const import (
|
||||||
|
COVER_POSITION_PROPERTY_KEYS,
|
||||||
|
COVER_TILT_PROPERTY_KEYS,
|
||||||
|
DATA_CLIENT,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
from .discovery import ZwaveDiscoveryInfo
|
||||||
from .discovery_data_template import CoverTiltDataTemplate
|
from .discovery_data_template import CoverTiltDataTemplate
|
||||||
from .entity import ZWaveBaseEntity
|
from .entity import ZWaveBaseEntity
|
||||||
@ -49,7 +64,9 @@ async def async_setup_entry(
|
|||||||
driver = client.driver
|
driver = client.driver
|
||||||
assert driver is not None # Driver is ready before platforms are loaded.
|
assert driver is not None # Driver is ready before platforms are loaded.
|
||||||
entities: list[ZWaveBaseEntity] = []
|
entities: list[ZWaveBaseEntity] = []
|
||||||
if info.platform_hint == "motorized_barrier":
|
if info.platform_hint == "window_covering":
|
||||||
|
entities.append(ZWaveWindowCovering(config_entry, driver, info))
|
||||||
|
elif info.platform_hint == "motorized_barrier":
|
||||||
entities.append(ZwaveMotorizedBarrier(config_entry, driver, info))
|
entities.append(ZwaveMotorizedBarrier(config_entry, driver, info))
|
||||||
elif info.platform_hint and info.platform_hint.endswith("tilt"):
|
elif info.platform_hint and info.platform_hint.endswith("tilt"):
|
||||||
entities.append(ZWaveTiltCover(config_entry, driver, info))
|
entities.append(ZWaveTiltCover(config_entry, driver, info))
|
||||||
@ -130,7 +147,7 @@ class CoverPositionMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
"""Return true if cover is closed."""
|
"""Return true if cover is closed."""
|
||||||
if not (value := self._current_position_value) or value.value is None:
|
if not (value := self._current_position_value) or value.value is None:
|
||||||
return None
|
return None
|
||||||
return bool(value.value == 0)
|
return bool(value.value == self._fully_closed_position)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_cover_position(self) -> int | None:
|
def current_cover_position(self) -> int | None:
|
||||||
@ -183,6 +200,7 @@ class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
self,
|
self,
|
||||||
current_value: ZwaveValue,
|
current_value: ZwaveValue,
|
||||||
target_value: ZwaveValue | None = None,
|
target_value: ZwaveValue | None = None,
|
||||||
|
stop_value: ZwaveValue | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set values for tilt."""
|
"""Set values for tilt."""
|
||||||
self._attr_supported_features = (
|
self._attr_supported_features = (
|
||||||
@ -196,6 +214,10 @@ class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
TARGET_VALUE_PROPERTY, value_property_key=current_value.property_key
|
TARGET_VALUE_PROPERTY, value_property_key=current_value.property_key
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if stop_value:
|
||||||
|
self._stop_tilt_value = stop_value
|
||||||
|
self._attr_supported_features |= CoverEntityFeature.STOP_TILT
|
||||||
|
|
||||||
def percent_to_zwave_tilt(self, value: int) -> int:
|
def percent_to_zwave_tilt(self, value: int) -> int:
|
||||||
"""Convert position in 0-100 scale to closed_value-open_value scale."""
|
"""Convert position in 0-100 scale to closed_value-open_value scale."""
|
||||||
return (
|
return (
|
||||||
@ -256,6 +278,12 @@ class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
|||||||
self._target_tilt_value, self._fully_closed_tilt
|
self._target_tilt_value, self._fully_closed_tilt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
|
"""Stop the cover tilt."""
|
||||||
|
assert self._stop_tilt_value
|
||||||
|
# Stop the tilt, will stop regardless of the actual direction of travel.
|
||||||
|
await self.info.node.async_set_value(self._stop_tilt_value, False)
|
||||||
|
|
||||||
|
|
||||||
class ZWaveMultilevelSwitchCover(CoverPositionMixin):
|
class ZWaveMultilevelSwitchCover(CoverPositionMixin):
|
||||||
"""Representation of a Z-Wave Cover that uses Multilevel Switch CC for position."""
|
"""Representation of a Z-Wave Cover that uses Multilevel Switch CC for position."""
|
||||||
@ -304,6 +332,79 @@ class ZWaveTiltCover(ZWaveMultilevelSwitchCover, CoverTiltMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ZWaveWindowCovering(CoverPositionMixin, CoverTiltMixin):
|
||||||
|
"""Representation of a Z-Wave Window Covering cover device."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(config_entry, driver, info)
|
||||||
|
pos_value: ZwaveValue | None = None
|
||||||
|
tilt_value: ZwaveValue | None = None
|
||||||
|
|
||||||
|
# If primary value is for position, we have to search for a tilt value
|
||||||
|
if info.primary_value.property_key in COVER_POSITION_PROPERTY_KEYS:
|
||||||
|
pos_value = info.primary_value
|
||||||
|
tilt_value = next(
|
||||||
|
(
|
||||||
|
value
|
||||||
|
for property_key in COVER_TILT_PROPERTY_KEYS
|
||||||
|
if (
|
||||||
|
value := self.get_zwave_value(
|
||||||
|
CURRENT_VALUE_PROPERTY, value_property_key=property_key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
# If primary value is for tilt, there is no position value
|
||||||
|
else:
|
||||||
|
tilt_value = info.primary_value
|
||||||
|
|
||||||
|
# Set position and tilt values if they exist. If the corresponding value is of
|
||||||
|
# the type No Position, we remove the corresponding set position feature.
|
||||||
|
for set_values_func, value, set_position_feature in (
|
||||||
|
(self._set_position_values, pos_value, CoverEntityFeature.SET_POSITION),
|
||||||
|
(self._set_tilt_values, tilt_value, CoverEntityFeature.SET_TILT_POSITION),
|
||||||
|
):
|
||||||
|
if value:
|
||||||
|
set_values_func(
|
||||||
|
value,
|
||||||
|
stop_value=self.get_zwave_value(
|
||||||
|
WINDOW_COVERING_OPEN_PROPERTY,
|
||||||
|
value_property_key=value.property_key,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if value.property_key in NO_POSITION_PROPERTY_KEYS:
|
||||||
|
assert self._attr_supported_features
|
||||||
|
self._attr_supported_features ^= set_position_feature
|
||||||
|
|
||||||
|
additional_info: list[str] = []
|
||||||
|
for value in (self._current_position_value, self._current_tilt_value):
|
||||||
|
if value and value.property_key_name:
|
||||||
|
additional_info.append(
|
||||||
|
value.property_key_name.removesuffix(f" {NO_POSITION_SUFFIX}")
|
||||||
|
)
|
||||||
|
self._attr_name = self.generate_name(additional_info=additional_info)
|
||||||
|
self._attr_device_class = CoverDeviceClass.WINDOW
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _fully_open_tilt(self) -> int:
|
||||||
|
"""Return position to open cover tilt."""
|
||||||
|
return SlatStates.OPEN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _fully_closed_tilt(self) -> int:
|
||||||
|
"""Return position to close cover tilt."""
|
||||||
|
return SlatStates.CLOSED_1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _tilt_range(self) -> int:
|
||||||
|
"""Return range of valid tilt positions."""
|
||||||
|
return abs(SlatStates.CLOSED_2 - SlatStates.CLOSED_1)
|
||||||
|
|
||||||
|
|
||||||
class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity):
|
class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity):
|
||||||
"""Representation of a Z-Wave motorized barrier device."""
|
"""Representation of a Z-Wave motorized barrier device."""
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.const import EntityCategory, Platform
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntry
|
from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
|
|
||||||
from .const import LOGGER
|
from .const import COVER_POSITION_PROPERTY_KEYS, COVER_TILT_PROPERTY_KEYS, LOGGER
|
||||||
from .discovery_data_template import (
|
from .discovery_data_template import (
|
||||||
BaseDiscoverySchemaDataTemplate,
|
BaseDiscoverySchemaDataTemplate,
|
||||||
ConfigurableFanValueMappingDataTemplate,
|
ConfigurableFanValueMappingDataTemplate,
|
||||||
@ -259,6 +259,18 @@ SIREN_TONE_SCHEMA = ZWaveValueDiscoverySchema(
|
|||||||
type={ValueType.NUMBER},
|
type={ValueType.NUMBER},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
WINDOW_COVERING_COVER_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||||
|
command_class={CommandClass.WINDOW_COVERING},
|
||||||
|
property={CURRENT_VALUE_PROPERTY},
|
||||||
|
property_key=COVER_POSITION_PROPERTY_KEYS,
|
||||||
|
)
|
||||||
|
|
||||||
|
WINDOW_COVERING_SLAT_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||||
|
command_class={CommandClass.WINDOW_COVERING},
|
||||||
|
property={CURRENT_VALUE_PROPERTY},
|
||||||
|
property_key=COVER_TILT_PROPERTY_KEYS,
|
||||||
|
)
|
||||||
|
|
||||||
# For device class mapping see:
|
# For device class mapping see:
|
||||||
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json
|
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json
|
||||||
DISCOVERY_SCHEMAS = [
|
DISCOVERY_SCHEMAS = [
|
||||||
@ -858,6 +870,17 @@ DISCOVERY_SCHEMAS = [
|
|||||||
),
|
),
|
||||||
# cover
|
# cover
|
||||||
# window coverings
|
# window coverings
|
||||||
|
ZWaveDiscoverySchema(
|
||||||
|
platform=Platform.COVER,
|
||||||
|
hint="window_covering",
|
||||||
|
primary_value=WINDOW_COVERING_COVER_CURRENT_VALUE_SCHEMA,
|
||||||
|
),
|
||||||
|
ZWaveDiscoverySchema(
|
||||||
|
platform=Platform.COVER,
|
||||||
|
hint="window_covering",
|
||||||
|
primary_value=WINDOW_COVERING_SLAT_CURRENT_VALUE_SCHEMA,
|
||||||
|
absent_values=[WINDOW_COVERING_COVER_CURRENT_VALUE_SCHEMA],
|
||||||
|
),
|
||||||
ZWaveDiscoverySchema(
|
ZWaveDiscoverySchema(
|
||||||
platform=Platform.COVER,
|
platform=Platform.COVER,
|
||||||
hint="multilevel_switch",
|
hint="multilevel_switch",
|
||||||
@ -869,6 +892,10 @@ DISCOVERY_SCHEMAS = [
|
|||||||
"Multiposition Motor",
|
"Multiposition Motor",
|
||||||
},
|
},
|
||||||
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||||
|
absent_values=[
|
||||||
|
WINDOW_COVERING_COVER_CURRENT_VALUE_SCHEMA,
|
||||||
|
WINDOW_COVERING_SLAT_CURRENT_VALUE_SCHEMA,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
# cover
|
# cover
|
||||||
# motorized barriers
|
# motorized barriers
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Generic Z-Wave Entity Class."""
|
"""Generic Z-Wave Entity Class."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from zwave_js_server.const import NodeStatus
|
from zwave_js_server.const import NodeStatus
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
|
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
|
||||||
@ -139,7 +141,7 @@ class ZWaveBaseEntity(Entity):
|
|||||||
self,
|
self,
|
||||||
include_value_name: bool = False,
|
include_value_name: bool = False,
|
||||||
alternate_value_name: str | None = None,
|
alternate_value_name: str | None = None,
|
||||||
additional_info: list[str | None] | None = None,
|
additional_info: Sequence[str | None] | None = None,
|
||||||
name_prefix: str | None = None,
|
name_prefix: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Generate entity name."""
|
"""Generate entity name."""
|
||||||
|
@ -445,6 +445,12 @@ def iblinds_v2_state_fixture():
|
|||||||
return json.loads(load_fixture("zwave_js/cover_iblinds_v2_state.json"))
|
return json.loads(load_fixture("zwave_js/cover_iblinds_v2_state.json"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="iblinds_v3_state", scope="session")
|
||||||
|
def iblinds_v3_state_fixture():
|
||||||
|
"""Load the iBlinds v3 node state fixture data."""
|
||||||
|
return json.loads(load_fixture("zwave_js/cover_iblinds_v3_state.json"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="qubino_shutter_state", scope="session")
|
@pytest.fixture(name="qubino_shutter_state", scope="session")
|
||||||
def qubino_shutter_state_fixture():
|
def qubino_shutter_state_fixture():
|
||||||
"""Load the Qubino Shutter node state fixture data."""
|
"""Load the Qubino Shutter node state fixture data."""
|
||||||
@ -953,6 +959,14 @@ def iblinds_v2_cover_fixture(client, iblinds_v2_state):
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="iblinds_v3")
|
||||||
|
def iblinds_v3_cover_fixture(client, iblinds_v3_state):
|
||||||
|
"""Mock an iBlinds v3 window cover node."""
|
||||||
|
node = Node(client, copy.deepcopy(iblinds_v3_state))
|
||||||
|
client.driver.controller.nodes[node.node_id] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="qubino_shutter")
|
@pytest.fixture(name="qubino_shutter")
|
||||||
def qubino_shutter_cover_fixture(client, qubino_shutter_state):
|
def qubino_shutter_cover_fixture(client, qubino_shutter_state):
|
||||||
"""Mock a Qubino flush shutter node."""
|
"""Mock a Qubino flush shutter node."""
|
||||||
|
1236
tests/components/zwave_js/fixtures/cover_iblinds_v3_state.json
Normal file
1236
tests/components/zwave_js/fixtures/cover_iblinds_v3_state.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -20,12 +20,15 @@ from homeassistant.components.cover import (
|
|||||||
SERVICE_SET_COVER_POSITION,
|
SERVICE_SET_COVER_POSITION,
|
||||||
SERVICE_SET_COVER_TILT_POSITION,
|
SERVICE_SET_COVER_TILT_POSITION,
|
||||||
SERVICE_STOP_COVER,
|
SERVICE_STOP_COVER,
|
||||||
|
SERVICE_STOP_COVER_TILT,
|
||||||
CoverDeviceClass,
|
CoverDeviceClass,
|
||||||
|
CoverEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
|
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_SUPPORTED_FEATURES,
|
||||||
STATE_CLOSED,
|
STATE_CLOSED,
|
||||||
STATE_CLOSING,
|
STATE_CLOSING,
|
||||||
STATE_OPEN,
|
STATE_OPEN,
|
||||||
@ -688,3 +691,107 @@ async def test_fibaro_fgr222_shutter_cover_no_tilt(
|
|||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNKNOWN
|
||||||
assert ATTR_CURRENT_POSITION not in state.attributes
|
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||||
assert ATTR_CURRENT_TILT_POSITION not in state.attributes
|
assert ATTR_CURRENT_TILT_POSITION not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
|
async def test_iblinds_v3_cover(
|
||||||
|
hass: HomeAssistant, client, iblinds_v3, integration
|
||||||
|
) -> None:
|
||||||
|
"""Test iBlinds v3 cover which uses Window Covering CC."""
|
||||||
|
entity_id = "cover.window_blind_controller_horizontal_slats_angle"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
# This device has no state because there is no position value
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_FEATURES] == (
|
||||||
|
CoverEntityFeature.CLOSE_TILT
|
||||||
|
| CoverEntityFeature.OPEN_TILT
|
||||||
|
| CoverEntityFeature.SET_TILT_POSITION
|
||||||
|
| CoverEntityFeature.STOP_TILT
|
||||||
|
)
|
||||||
|
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||||
|
assert ATTR_CURRENT_TILT_POSITION in state.attributes
|
||||||
|
assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 0
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_CLOSE_COVER_TILT,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
|
args = client.async_send_command.call_args[0][0]
|
||||||
|
assert args["command"] == "node.set_value"
|
||||||
|
assert args["nodeId"] == 12
|
||||||
|
assert args["valueId"] == {
|
||||||
|
"endpoint": 0,
|
||||||
|
"commandClass": 106,
|
||||||
|
"property": "targetValue",
|
||||||
|
"propertyKey": 23,
|
||||||
|
}
|
||||||
|
assert args["value"] == 0
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_OPEN_COVER_TILT,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
|
args = client.async_send_command.call_args[0][0]
|
||||||
|
assert args["command"] == "node.set_value"
|
||||||
|
assert args["nodeId"] == 12
|
||||||
|
assert args["valueId"] == {
|
||||||
|
"endpoint": 0,
|
||||||
|
"commandClass": 106,
|
||||||
|
"property": "targetValue",
|
||||||
|
"propertyKey": 23,
|
||||||
|
}
|
||||||
|
assert args["value"] == 50
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_COVER_TILT_POSITION,
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_TILT_POSITION: 12},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
|
args = client.async_send_command.call_args[0][0]
|
||||||
|
assert args["command"] == "node.set_value"
|
||||||
|
assert args["nodeId"] == 12
|
||||||
|
assert args["valueId"] == {
|
||||||
|
"endpoint": 0,
|
||||||
|
"commandClass": 106,
|
||||||
|
"property": "targetValue",
|
||||||
|
"propertyKey": 23,
|
||||||
|
}
|
||||||
|
assert args["value"] == 12
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_STOP_COVER_TILT,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
|
args = client.async_send_command.call_args[0][0]
|
||||||
|
assert args["command"] == "node.set_value"
|
||||||
|
assert args["nodeId"] == 12
|
||||||
|
assert args["valueId"] == {
|
||||||
|
"endpoint": 0,
|
||||||
|
"commandClass": 106,
|
||||||
|
"property": "open",
|
||||||
|
"propertyKey": 23,
|
||||||
|
}
|
||||||
|
assert args["value"] is False
|
||||||
|
|
||||||
|
client.async_send_command.reset_mock()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user