Add support for WMS roller shutters and blinds (#132645)

* Add support for WMS roller shutters and blinds

* Add test variants for WMS device types and their diagnostics

* Add test variants for cover movement of WMS device types

* Move device entry tests to test_init and avoid snapshot list

Suggested-by: joostlek
This commit is contained in:
Marc Hörsken 2025-04-30 20:51:10 +02:00 committed by GitHub
parent 621cf6ce58
commit c3abf5a190
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1360 additions and 69 deletions

View File

@ -32,25 +32,29 @@ async def async_setup_entry(
entities: list[WebControlProGenericEntity] = []
for dest in hub.dests.values():
if dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive):
entities.append(WebControlProAwning(config_entry.entry_id, dest)) # noqa: PERF401
entities.append(WebControlProAwning(config_entry.entry_id, dest))
elif dest.action(
WMS_WebControl_pro_API_actionDescription.RollerShutterBlindDrive
):
entities.append(WebControlProRollerShutter(config_entry.entry_id, dest))
async_add_entities(entities)
class WebControlProAwning(WebControlProGenericEntity, CoverEntity):
"""Representation of a WMS based awning."""
class WebControlProCover(WebControlProGenericEntity, CoverEntity):
"""Base representation of a WMS based cover."""
_attr_device_class = CoverDeviceClass.AWNING
_drive_action_desc: WMS_WebControl_pro_API_actionDescription
@property
def current_cover_position(self) -> int | None:
"""Return current position of cover."""
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
action = self._dest.action(self._drive_action_desc)
return 100 - action["percentage"]
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position."""
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
action = self._dest.action(self._drive_action_desc)
await action(percentage=100 - kwargs[ATTR_POSITION])
@property
@ -60,12 +64,12 @@ class WebControlProAwning(WebControlProGenericEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
action = self._dest.action(self._drive_action_desc)
await action(percentage=0)
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the cover."""
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
action = self._dest.action(self._drive_action_desc)
await action(percentage=100)
async def async_stop_cover(self, **kwargs: Any) -> None:
@ -75,3 +79,19 @@ class WebControlProAwning(WebControlProGenericEntity, CoverEntity):
WMS_WebControl_pro_API_actionType.Stop,
)
await action()
class WebControlProAwning(WebControlProCover):
"""Representation of a WMS based awning."""
_attr_device_class = CoverDeviceClass.AWNING
_drive_action_desc = WMS_WebControl_pro_API_actionDescription.AwningDrive
class WebControlProRollerShutter(WebControlProCover):
"""Representation of a WMS based roller shutter or blind."""
_attr_device_class = CoverDeviceClass.SHUTTER
_drive_action_desc = (
WMS_WebControl_pro_API_actionDescription.RollerShutterBlindDrive
)

View File

@ -55,17 +55,29 @@ def mock_hub_configuration_test() -> Generator[AsyncMock]:
"""Override WebControlPro.configuration."""
with patch(
"wmspro.webcontrol.WebControlPro._getConfiguration",
return_value=load_json_object_fixture("example_config_test.json", DOMAIN),
return_value=load_json_object_fixture("config_test.json", DOMAIN),
) as mock_hub_configuration:
yield mock_hub_configuration
@pytest.fixture
def mock_hub_configuration_prod() -> Generator[AsyncMock]:
def mock_hub_configuration_prod_awning_dimmer() -> Generator[AsyncMock]:
"""Override WebControlPro._getConfiguration."""
with patch(
"wmspro.webcontrol.WebControlPro._getConfiguration",
return_value=load_json_object_fixture("example_config_prod.json", DOMAIN),
return_value=load_json_object_fixture("config_prod_awning_dimmer.json", DOMAIN),
) as mock_hub_configuration:
yield mock_hub_configuration
@pytest.fixture
def mock_hub_configuration_prod_roller_shutter() -> Generator[AsyncMock]:
"""Override WebControlPro._getConfiguration."""
with patch(
"wmspro.webcontrol.WebControlPro._getConfiguration",
return_value=load_json_object_fixture(
"config_prod_roller_shutter.json", DOMAIN
),
) as mock_hub_configuration:
yield mock_hub_configuration
@ -75,23 +87,31 @@ def mock_hub_status_prod_awning() -> Generator[AsyncMock]:
"""Override WebControlPro._getStatus."""
with patch(
"wmspro.webcontrol.WebControlPro._getStatus",
return_value=load_json_object_fixture(
"example_status_prod_awning.json", DOMAIN
),
) as mock_dest_refresh:
yield mock_dest_refresh
return_value=load_json_object_fixture("status_prod_awning.json", DOMAIN),
) as mock_hub_status:
yield mock_hub_status
@pytest.fixture
def mock_hub_status_prod_dimmer() -> Generator[AsyncMock]:
"""Override WebControlPro._getStatus."""
with patch(
"wmspro.webcontrol.WebControlPro._getStatus",
return_value=load_json_object_fixture("status_prod_dimmer.json", DOMAIN),
) as mock_hub_status:
yield mock_hub_status
@pytest.fixture
def mock_hub_status_prod_roller_shutter() -> Generator[AsyncMock]:
"""Override WebControlPro._getStatus."""
with patch(
"wmspro.webcontrol.WebControlPro._getStatus",
return_value=load_json_object_fixture(
"example_status_prod_dimmer.json", DOMAIN
"status_prod_roller_shutter.json", DOMAIN
),
) as mock_dest_refresh:
yield mock_dest_refresh
) as mock_hub_status:
yield mock_hub_status
@pytest.fixture
@ -100,8 +120,8 @@ def mock_dest_refresh() -> Generator[AsyncMock]:
with patch(
"wmspro.destination.Destination.refresh",
return_value=True,
) as mock_dest_refresh:
yield mock_dest_refresh
) as mock_hub_status:
yield mock_hub_status
@pytest.fixture

View File

@ -0,0 +1,171 @@
{
"command": "getConfiguration",
"protocolVersion": "1.0.0",
"destinations": [
{
"id": 18894,
"animationType": 2,
"names": ["Wohnebene alle", "", "", ""],
"actions": [
{
"id": 0,
"actionType": 0,
"actionDescription": 4,
"minValue": 0,
"maxValue": 100
},
{
"id": 16,
"actionType": 6,
"actionDescription": 12
},
{
"id": 22,
"actionType": 8,
"actionDescription": 13
}
]
},
{
"id": 116682,
"animationType": 2,
"names": ["Wohnzimmer", "", "", ""],
"actions": [
{
"id": 0,
"actionType": 0,
"actionDescription": 4,
"minValue": 0,
"maxValue": 100
},
{
"id": 16,
"actionType": 6,
"actionDescription": 12
},
{
"id": 22,
"actionType": 8,
"actionDescription": 13
}
]
},
{
"id": 172555,
"animationType": 2,
"names": ["Badezimmer", "", "", ""],
"actions": [
{
"id": 0,
"actionType": 0,
"actionDescription": 4,
"minValue": 0,
"maxValue": 100
},
{
"id": 16,
"actionType": 6,
"actionDescription": 12
},
{
"id": 22,
"actionType": 8,
"actionDescription": 13
}
]
},
{
"id": 230952,
"animationType": 2,
"names": ["Sportzimmer", "", "", ""],
"actions": [
{
"id": 0,
"actionType": 0,
"actionDescription": 4,
"minValue": 0,
"maxValue": 100
},
{
"id": 16,
"actionType": 6,
"actionDescription": 12
},
{
"id": 22,
"actionType": 8,
"actionDescription": 13
}
]
},
{
"id": 284942,
"animationType": 2,
"names": ["Terrasse", "", "", ""],
"actions": [
{
"id": 0,
"actionType": 0,
"actionDescription": 4,
"minValue": 0,
"maxValue": 100
},
{
"id": 16,
"actionType": 6,
"actionDescription": 12
},
{
"id": 22,
"actionType": 8,
"actionDescription": 13
}
]
},
{
"id": 328518,
"animationType": 2,
"names": ["alle Rolll\u00e4den", "", "", ""],
"actions": [
{
"id": 0,
"actionType": 0,
"actionDescription": 4,
"minValue": 0,
"maxValue": 100
},
{
"id": 16,
"actionType": 6,
"actionDescription": 12
},
{
"id": 22,
"actionType": 8,
"actionDescription": 13
}
]
}
],
"rooms": [
{
"id": 15175,
"name": "Wohnbereich",
"destinations": [18894, 116682, 172555, 230952],
"scenes": []
},
{
"id": 92218,
"name": "Terrasse",
"destinations": [284942],
"scenes": []
},
{
"id": 193582,
"name": "Alle",
"destinations": [328518],
"scenes": []
}
],
"scenes": []
}

View File

@ -0,0 +1,22 @@
{
"command": "getStatus",
"protocolVersion": "1.0.0",
"details": [
{
"destinationId": 18894,
"data": {
"drivingCause": 0,
"heartbeatError": false,
"blocking": false,
"productData": [
{
"actionId": 0,
"value": {
"percentage": 100
}
}
]
}
}
]
}

View File

@ -1,5 +1,5 @@
# serializer version: 1
# name: test_diagnostics
# name: test_diagnostics[mock_hub_configuration_prod_awning_dimmer]
dict({
'config': dict({
'command': 'getConfiguration',
@ -242,3 +242,540 @@
}),
})
# ---
# name: test_diagnostics[mock_hub_configuration_prod_roller_shutter]
dict({
'config': dict({
'command': 'getConfiguration',
'destinations': list([
dict({
'actions': list([
dict({
'actionDescription': 4,
'actionType': 0,
'id': 0,
'maxValue': 100,
'minValue': 0,
}),
dict({
'actionDescription': 12,
'actionType': 6,
'id': 16,
}),
dict({
'actionDescription': 13,
'actionType': 8,
'id': 22,
}),
]),
'animationType': 2,
'id': 18894,
'names': list([
'Wohnebene alle',
'',
'',
'',
]),
}),
dict({
'actions': list([
dict({
'actionDescription': 4,
'actionType': 0,
'id': 0,
'maxValue': 100,
'minValue': 0,
}),
dict({
'actionDescription': 12,
'actionType': 6,
'id': 16,
}),
dict({
'actionDescription': 13,
'actionType': 8,
'id': 22,
}),
]),
'animationType': 2,
'id': 116682,
'names': list([
'Wohnzimmer',
'',
'',
'',
]),
}),
dict({
'actions': list([
dict({
'actionDescription': 4,
'actionType': 0,
'id': 0,
'maxValue': 100,
'minValue': 0,
}),
dict({
'actionDescription': 12,
'actionType': 6,
'id': 16,
}),
dict({
'actionDescription': 13,
'actionType': 8,
'id': 22,
}),
]),
'animationType': 2,
'id': 172555,
'names': list([
'Badezimmer',
'',
'',
'',
]),
}),
dict({
'actions': list([
dict({
'actionDescription': 4,
'actionType': 0,
'id': 0,
'maxValue': 100,
'minValue': 0,
}),
dict({
'actionDescription': 12,
'actionType': 6,
'id': 16,
}),
dict({
'actionDescription': 13,
'actionType': 8,
'id': 22,
}),
]),
'animationType': 2,
'id': 230952,
'names': list([
'Sportzimmer',
'',
'',
'',
]),
}),
dict({
'actions': list([
dict({
'actionDescription': 4,
'actionType': 0,
'id': 0,
'maxValue': 100,
'minValue': 0,
}),
dict({
'actionDescription': 12,
'actionType': 6,
'id': 16,
}),
dict({
'actionDescription': 13,
'actionType': 8,
'id': 22,
}),
]),
'animationType': 2,
'id': 284942,
'names': list([
'Terrasse',
'',
'',
'',
]),
}),
dict({
'actions': list([
dict({
'actionDescription': 4,
'actionType': 0,
'id': 0,
'maxValue': 100,
'minValue': 0,
}),
dict({
'actionDescription': 12,
'actionType': 6,
'id': 16,
}),
dict({
'actionDescription': 13,
'actionType': 8,
'id': 22,
}),
]),
'animationType': 2,
'id': 328518,
'names': list([
'alle Rollläden',
'',
'',
'',
]),
}),
]),
'protocolVersion': '1.0.0',
'rooms': list([
dict({
'destinations': list([
18894,
116682,
172555,
230952,
]),
'id': 15175,
'name': 'Wohnbereich',
'scenes': list([
]),
}),
dict({
'destinations': list([
284942,
]),
'id': 92218,
'name': 'Terrasse',
'scenes': list([
]),
}),
dict({
'destinations': list([
328518,
]),
'id': 193582,
'name': 'Alle',
'scenes': list([
]),
}),
]),
'scenes': list([
]),
}),
'dests': dict({
'116682': dict({
'actions': dict({
'0': dict({
'actionDescription': 'RollerShutterBlindDrive',
'actionType': 'Percentage',
'attrs': dict({
'maxValue': 100,
'minValue': 0,
}),
'id': 0,
'params': dict({
}),
}),
'16': dict({
'actionDescription': 'ManualCommand',
'actionType': 'Stop',
'attrs': dict({
}),
'id': 16,
'params': dict({
}),
}),
'22': dict({
'actionDescription': 'Identify',
'actionType': 'Identify',
'attrs': dict({
}),
'id': 22,
'params': dict({
}),
}),
}),
'animationType': 'RollerShutterBlind',
'available': True,
'blocking': None,
'drivingCause': 'Unknown',
'heartbeatError': None,
'id': 116682,
'name': 'Wohnzimmer',
'room': dict({
'15175': 'Wohnbereich',
}),
'status': dict({
}),
'unknownProducts': dict({
}),
}),
'172555': dict({
'actions': dict({
'0': dict({
'actionDescription': 'RollerShutterBlindDrive',
'actionType': 'Percentage',
'attrs': dict({
'maxValue': 100,
'minValue': 0,
}),
'id': 0,
'params': dict({
}),
}),
'16': dict({
'actionDescription': 'ManualCommand',
'actionType': 'Stop',
'attrs': dict({
}),
'id': 16,
'params': dict({
}),
}),
'22': dict({
'actionDescription': 'Identify',
'actionType': 'Identify',
'attrs': dict({
}),
'id': 22,
'params': dict({
}),
}),
}),
'animationType': 'RollerShutterBlind',
'available': True,
'blocking': None,
'drivingCause': 'Unknown',
'heartbeatError': None,
'id': 172555,
'name': 'Badezimmer',
'room': dict({
'15175': 'Wohnbereich',
}),
'status': dict({
}),
'unknownProducts': dict({
}),
}),
'18894': dict({
'actions': dict({
'0': dict({
'actionDescription': 'RollerShutterBlindDrive',
'actionType': 'Percentage',
'attrs': dict({
'maxValue': 100,
'minValue': 0,
}),
'id': 0,
'params': dict({
}),
}),
'16': dict({
'actionDescription': 'ManualCommand',
'actionType': 'Stop',
'attrs': dict({
}),
'id': 16,
'params': dict({
}),
}),
'22': dict({
'actionDescription': 'Identify',
'actionType': 'Identify',
'attrs': dict({
}),
'id': 22,
'params': dict({
}),
}),
}),
'animationType': 'RollerShutterBlind',
'available': True,
'blocking': None,
'drivingCause': 'Unknown',
'heartbeatError': None,
'id': 18894,
'name': 'Wohnebene alle',
'room': dict({
'15175': 'Wohnbereich',
}),
'status': dict({
}),
'unknownProducts': dict({
}),
}),
'230952': dict({
'actions': dict({
'0': dict({
'actionDescription': 'RollerShutterBlindDrive',
'actionType': 'Percentage',
'attrs': dict({
'maxValue': 100,
'minValue': 0,
}),
'id': 0,
'params': dict({
}),
}),
'16': dict({
'actionDescription': 'ManualCommand',
'actionType': 'Stop',
'attrs': dict({
}),
'id': 16,
'params': dict({
}),
}),
'22': dict({
'actionDescription': 'Identify',
'actionType': 'Identify',
'attrs': dict({
}),
'id': 22,
'params': dict({
}),
}),
}),
'animationType': 'RollerShutterBlind',
'available': True,
'blocking': None,
'drivingCause': 'Unknown',
'heartbeatError': None,
'id': 230952,
'name': 'Sportzimmer',
'room': dict({
'15175': 'Wohnbereich',
}),
'status': dict({
}),
'unknownProducts': dict({
}),
}),
'284942': dict({
'actions': dict({
'0': dict({
'actionDescription': 'RollerShutterBlindDrive',
'actionType': 'Percentage',
'attrs': dict({
'maxValue': 100,
'minValue': 0,
}),
'id': 0,
'params': dict({
}),
}),
'16': dict({
'actionDescription': 'ManualCommand',
'actionType': 'Stop',
'attrs': dict({
}),
'id': 16,
'params': dict({
}),
}),
'22': dict({
'actionDescription': 'Identify',
'actionType': 'Identify',
'attrs': dict({
}),
'id': 22,
'params': dict({
}),
}),
}),
'animationType': 'RollerShutterBlind',
'available': True,
'blocking': None,
'drivingCause': 'Unknown',
'heartbeatError': None,
'id': 284942,
'name': 'Terrasse',
'room': dict({
'92218': 'Terrasse',
}),
'status': dict({
}),
'unknownProducts': dict({
}),
}),
'328518': dict({
'actions': dict({
'0': dict({
'actionDescription': 'RollerShutterBlindDrive',
'actionType': 'Percentage',
'attrs': dict({
'maxValue': 100,
'minValue': 0,
}),
'id': 0,
'params': dict({
}),
}),
'16': dict({
'actionDescription': 'ManualCommand',
'actionType': 'Stop',
'attrs': dict({
}),
'id': 16,
'params': dict({
}),
}),
'22': dict({
'actionDescription': 'Identify',
'actionType': 'Identify',
'attrs': dict({
}),
'id': 22,
'params': dict({
}),
}),
}),
'animationType': 'RollerShutterBlind',
'available': True,
'blocking': None,
'drivingCause': 'Unknown',
'heartbeatError': None,
'id': 328518,
'name': 'alle Rollläden',
'room': dict({
'193582': 'Alle',
}),
'status': dict({
}),
'unknownProducts': dict({
}),
}),
}),
'host': 'webcontrol',
'rooms': dict({
'15175': dict({
'destinations': dict({
'116682': 'Wohnzimmer',
'172555': 'Badezimmer',
'18894': 'Wohnebene alle',
'230952': 'Sportzimmer',
}),
'id': 15175,
'name': 'Wohnbereich',
'scenes': dict({
}),
}),
'193582': dict({
'destinations': dict({
'328518': 'alle Rollläden',
}),
'id': 193582,
'name': 'Alle',
'scenes': dict({
}),
}),
'92218': dict({
'destinations': dict({
'284942': 'Terrasse',
}),
'id': 92218,
'name': 'Terrasse',
'scenes': dict({
}),
}),
}),
'scenes': dict({
}),
})
# ---

View File

@ -0,0 +1,397 @@
# serializer version: 1
# name: test_cover_device[mock_hub_configuration_prod_awning_dimmer-mock_hub_status_prod_awning][device-19239]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'19239',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'Room',
'model_id': None,
'name': 'Terrasse',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '19239',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_awning_dimmer-mock_hub_status_prod_awning][device-58717]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'58717',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'Awning',
'model_id': None,
'name': 'Markise',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '58717',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_awning_dimmer-mock_hub_status_prod_awning][device-97358]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'97358',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'Dimmer',
'model_id': None,
'name': 'Licht',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '97358',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_awning_dimmer-mock_hub_status_prod_dimmer][device-19239]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'19239',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'Room',
'model_id': None,
'name': 'Terrasse',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '19239',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_awning_dimmer-mock_hub_status_prod_dimmer][device-58717]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'58717',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'Awning',
'model_id': None,
'name': 'Markise',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '58717',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_awning_dimmer-mock_hub_status_prod_dimmer][device-97358]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'97358',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'Dimmer',
'model_id': None,
'name': 'Licht',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '97358',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_roller_shutter-mock_hub_status_prod_roller_shutter][device-116682]
DeviceRegistryEntrySnapshot({
'area_id': 'wohnbereich',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'116682',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'RollerShutterBlind',
'model_id': None,
'name': 'Wohnzimmer',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '116682',
'suggested_area': 'Wohnbereich',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_roller_shutter-mock_hub_status_prod_roller_shutter][device-172555]
DeviceRegistryEntrySnapshot({
'area_id': 'wohnbereich',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'172555',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'RollerShutterBlind',
'model_id': None,
'name': 'Badezimmer',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '172555',
'suggested_area': 'Wohnbereich',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_roller_shutter-mock_hub_status_prod_roller_shutter][device-18894]
DeviceRegistryEntrySnapshot({
'area_id': 'wohnbereich',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'18894',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'RollerShutterBlind',
'model_id': None,
'name': 'Wohnebene alle',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '18894',
'suggested_area': 'Wohnbereich',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_roller_shutter-mock_hub_status_prod_roller_shutter][device-230952]
DeviceRegistryEntrySnapshot({
'area_id': 'wohnbereich',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'230952',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'RollerShutterBlind',
'model_id': None,
'name': 'Sportzimmer',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '230952',
'suggested_area': 'Wohnbereich',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_roller_shutter-mock_hub_status_prod_roller_shutter][device-284942]
DeviceRegistryEntrySnapshot({
'area_id': 'terrasse',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'284942',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'RollerShutterBlind',
'model_id': None,
'name': 'Terrasse',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '284942',
'suggested_area': 'Terrasse',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_cover_device[mock_hub_configuration_prod_roller_shutter-mock_hub_status_prod_roller_shutter][device-328518]
DeviceRegistryEntrySnapshot({
'area_id': 'alle',
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'http://webcontrol/control',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'wmspro',
'328518',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'WAREMA Renkhoff SE',
'model': 'RollerShutterBlind',
'model_id': None,
'name': 'alle Rollläden',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': '328518',
'suggested_area': 'Alle',
'sw_version': None,
'via_device_id': <ANY>,
})
# ---

View File

@ -367,13 +367,15 @@ async def test_config_flow_multiple_entries(
mock_hub_ping: AsyncMock,
mock_dest_refresh: AsyncMock,
mock_hub_configuration_test: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
) -> None:
"""Test we allow creation of different config entries."""
await setup_config_entry(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
mock_hub_configuration_prod.return_value = mock_hub_configuration_test.return_value
mock_hub_configuration_prod_awning_dimmer.return_value = (
mock_hub_configuration_test.return_value
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}

View File

@ -3,6 +3,7 @@
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.wmspro.const import DOMAIN
@ -29,7 +30,7 @@ async def test_cover_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
mock_hub_status_prod_awning: AsyncMock,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
@ -37,7 +38,7 @@ async def test_cover_device(
"""Test that a cover device is created correctly."""
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_configuration_prod_awning_dimmer.mock_calls) == 1
assert len(mock_hub_status_prod_awning.mock_calls) == 2
device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "58717")})
@ -49,7 +50,7 @@ async def test_cover_update(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
mock_hub_status_prod_awning: AsyncMock,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
@ -57,7 +58,7 @@ async def test_cover_update(
"""Test that a cover entity is created and updated correctly."""
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_configuration_prod_awning_dimmer.mock_calls) == 1
assert len(mock_hub_status_prod_awning.mock_calls) == 2
entity = hass.states.get("cover.markise")
@ -72,21 +73,41 @@ async def test_cover_update(
assert len(mock_hub_status_prod_awning.mock_calls) >= 3
@pytest.mark.parametrize(
("mock_hub_configuration", "mock_hub_status", "entity_name"),
[
(
"mock_hub_configuration_prod_awning_dimmer",
"mock_hub_status_prod_awning",
"cover.markise",
),
(
"mock_hub_configuration_prod_roller_shutter",
"mock_hub_status_prod_roller_shutter",
"cover.wohnebene_alle",
),
],
)
async def test_cover_open_and_close(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_status_prod_awning: AsyncMock,
mock_hub_configuration: AsyncMock,
mock_hub_status: AsyncMock,
mock_action_call: AsyncMock,
request: pytest.FixtureRequest,
entity_name: str,
) -> None:
"""Test that a cover entity is opened and closed correctly."""
mock_hub_configuration = request.getfixturevalue(mock_hub_configuration)
mock_hub_status = request.getfixturevalue(mock_hub_status)
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_status_prod_awning.mock_calls) >= 1
assert len(mock_hub_configuration.mock_calls) == 1
assert len(mock_hub_status.mock_calls) >= 1
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_CLOSED
assert entity.attributes["current_position"] == 0
@ -95,7 +116,7 @@ async def test_cover_open_and_close(
"wmspro.destination.Destination.refresh",
return_value=True,
):
before = len(mock_hub_status_prod_awning.mock_calls)
before = len(mock_hub_status.mock_calls)
await hass.services.async_call(
Platform.COVER,
@ -104,17 +125,17 @@ async def test_cover_open_and_close(
blocking=True,
)
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_OPEN
assert entity.attributes["current_position"] == 100
assert len(mock_hub_status_prod_awning.mock_calls) == before
assert len(mock_hub_status.mock_calls) == before
with patch(
"wmspro.destination.Destination.refresh",
return_value=True,
):
before = len(mock_hub_status_prod_awning.mock_calls)
before = len(mock_hub_status.mock_calls)
await hass.services.async_call(
Platform.COVER,
@ -123,28 +144,48 @@ async def test_cover_open_and_close(
blocking=True,
)
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_CLOSED
assert entity.attributes["current_position"] == 0
assert len(mock_hub_status_prod_awning.mock_calls) == before
assert len(mock_hub_status.mock_calls) == before
@pytest.mark.parametrize(
("mock_hub_configuration", "mock_hub_status", "entity_name"),
[
(
"mock_hub_configuration_prod_awning_dimmer",
"mock_hub_status_prod_awning",
"cover.markise",
),
(
"mock_hub_configuration_prod_roller_shutter",
"mock_hub_status_prod_roller_shutter",
"cover.wohnebene_alle",
),
],
)
async def test_cover_open_to_pos(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_status_prod_awning: AsyncMock,
mock_hub_configuration: AsyncMock,
mock_hub_status: AsyncMock,
mock_action_call: AsyncMock,
request: pytest.FixtureRequest,
entity_name: str,
) -> None:
"""Test that a cover entity is opened to correct position."""
mock_hub_configuration = request.getfixturevalue(mock_hub_configuration)
mock_hub_status = request.getfixturevalue(mock_hub_status)
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_status_prod_awning.mock_calls) >= 1
assert len(mock_hub_configuration.mock_calls) == 1
assert len(mock_hub_status.mock_calls) >= 1
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_CLOSED
assert entity.attributes["current_position"] == 0
@ -153,7 +194,7 @@ async def test_cover_open_to_pos(
"wmspro.destination.Destination.refresh",
return_value=True,
):
before = len(mock_hub_status_prod_awning.mock_calls)
before = len(mock_hub_status.mock_calls)
await hass.services.async_call(
Platform.COVER,
@ -162,28 +203,48 @@ async def test_cover_open_to_pos(
blocking=True,
)
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_OPEN
assert entity.attributes["current_position"] == 50
assert len(mock_hub_status_prod_awning.mock_calls) == before
assert len(mock_hub_status.mock_calls) == before
@pytest.mark.parametrize(
("mock_hub_configuration", "mock_hub_status", "entity_name"),
[
(
"mock_hub_configuration_prod_awning_dimmer",
"mock_hub_status_prod_awning",
"cover.markise",
),
(
"mock_hub_configuration_prod_roller_shutter",
"mock_hub_status_prod_roller_shutter",
"cover.wohnebene_alle",
),
],
)
async def test_cover_open_and_stop(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_status_prod_awning: AsyncMock,
mock_hub_configuration: AsyncMock,
mock_hub_status: AsyncMock,
mock_action_call: AsyncMock,
request: pytest.FixtureRequest,
entity_name: str,
) -> None:
"""Test that a cover entity is opened and stopped correctly."""
mock_hub_configuration = request.getfixturevalue(mock_hub_configuration)
mock_hub_status = request.getfixturevalue(mock_hub_status)
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_status_prod_awning.mock_calls) >= 1
assert len(mock_hub_configuration.mock_calls) == 1
assert len(mock_hub_status.mock_calls) >= 1
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_CLOSED
assert entity.attributes["current_position"] == 0
@ -192,7 +253,7 @@ async def test_cover_open_and_stop(
"wmspro.destination.Destination.refresh",
return_value=True,
):
before = len(mock_hub_status_prod_awning.mock_calls)
before = len(mock_hub_status.mock_calls)
await hass.services.async_call(
Platform.COVER,
@ -201,17 +262,17 @@ async def test_cover_open_and_stop(
blocking=True,
)
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_OPEN
assert entity.attributes["current_position"] == 80
assert len(mock_hub_status_prod_awning.mock_calls) == before
assert len(mock_hub_status.mock_calls) == before
with patch(
"wmspro.destination.Destination.refresh",
return_value=True,
):
before = len(mock_hub_status_prod_awning.mock_calls)
before = len(mock_hub_status.mock_calls)
await hass.services.async_call(
Platform.COVER,
@ -220,8 +281,8 @@ async def test_cover_open_and_stop(
blocking=True,
)
entity = hass.states.get("cover.markise")
entity = hass.states.get(entity_name)
assert entity is not None
assert entity.state == STATE_OPEN
assert entity.attributes["current_position"] == 80
assert len(mock_hub_status_prod_awning.mock_calls) == before
assert len(mock_hub_status.mock_calls) == before

View File

@ -2,6 +2,7 @@
from unittest.mock import AsyncMock
import pytest
from syrupy import SnapshotAssertion
from homeassistant.core import HomeAssistant
@ -13,20 +14,30 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
@pytest.mark.parametrize(
("mock_hub_configuration"),
[
("mock_hub_configuration_prod_awning_dimmer"),
("mock_hub_configuration_prod_roller_shutter"),
],
)
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration: AsyncMock,
mock_dest_refresh: AsyncMock,
snapshot: SnapshotAssertion,
request: pytest.FixtureRequest,
) -> None:
"""Test that a config entry can be loaded with DeviceConfig."""
mock_hub_configuration = request.getfixturevalue(mock_hub_configuration)
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_dest_refresh.mock_calls) == 2
assert len(mock_hub_configuration.mock_calls) == 1
assert len(mock_dest_refresh.mock_calls) > 0
result = await get_diagnostics_for_config_entry(
hass, hass_client, mock_config_entry

View File

@ -3,9 +3,13 @@
from unittest.mock import AsyncMock
import aiohttp
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.wmspro.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from . import setup_config_entry
@ -36,3 +40,49 @@ async def test_config_entry_device_config_refresh_failed(
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_refresh.mock_calls) == 1
@pytest.mark.parametrize(
("mock_hub_configuration", "mock_hub_status"),
[
("mock_hub_configuration_prod_awning_dimmer", "mock_hub_status_prod_awning"),
("mock_hub_configuration_prod_awning_dimmer", "mock_hub_status_prod_dimmer"),
(
"mock_hub_configuration_prod_roller_shutter",
"mock_hub_status_prod_roller_shutter",
),
],
)
async def test_cover_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration: AsyncMock,
mock_hub_status: AsyncMock,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
request: pytest.FixtureRequest,
) -> None:
"""Test that the device is created correctly."""
mock_hub_configuration = request.getfixturevalue(mock_hub_configuration)
mock_hub_status = request.getfixturevalue(mock_hub_status)
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration.mock_calls) == 1
assert len(mock_hub_status.mock_calls) > 0
device_entries = device_registry.devices.get_devices_for_config_entry_id(
mock_config_entry.entry_id
)
assert len(device_entries) > 1
device_entries = list(
filter(
lambda e: e.identifiers != {(DOMAIN, mock_config_entry.entry_id)},
device_entries,
)
)
assert len(device_entries) > 0
for device_entry in device_entries:
assert device_entry == snapshot(name=f"device-{device_entry.serial_number}")

View File

@ -28,7 +28,7 @@ async def test_light_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
mock_hub_status_prod_dimmer: AsyncMock,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
@ -36,7 +36,7 @@ async def test_light_device(
"""Test that a light device is created correctly."""
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_configuration_prod_awning_dimmer.mock_calls) == 1
assert len(mock_hub_status_prod_dimmer.mock_calls) == 2
device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "97358")})
@ -48,7 +48,7 @@ async def test_light_update(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
mock_hub_status_prod_dimmer: AsyncMock,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
@ -56,7 +56,7 @@ async def test_light_update(
"""Test that a light entity is created and updated correctly."""
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_configuration_prod_awning_dimmer.mock_calls) == 1
assert len(mock_hub_status_prod_dimmer.mock_calls) == 2
entity = hass.states.get("light.licht")
@ -75,14 +75,14 @@ async def test_light_turn_on_and_off(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
mock_hub_status_prod_dimmer: AsyncMock,
mock_action_call: AsyncMock,
) -> None:
"""Test that a light entity is turned on and off correctly."""
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_configuration_prod_awning_dimmer.mock_calls) == 1
assert len(mock_hub_status_prod_dimmer.mock_calls) >= 1
entity = hass.states.get("light.licht")
@ -133,14 +133,14 @@ async def test_light_dimm_on_and_off(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hub_ping: AsyncMock,
mock_hub_configuration_prod: AsyncMock,
mock_hub_configuration_prod_awning_dimmer: AsyncMock,
mock_hub_status_prod_dimmer: AsyncMock,
mock_action_call: AsyncMock,
) -> None:
"""Test that a light entity is dimmed on and off correctly."""
assert await setup_config_entry(hass, mock_config_entry)
assert len(mock_hub_ping.mock_calls) == 1
assert len(mock_hub_configuration_prod.mock_calls) == 1
assert len(mock_hub_configuration_prod_awning_dimmer.mock_calls) == 1
assert len(mock_hub_status_prod_dimmer.mock_calls) >= 1
entity = hass.states.get("light.licht")