Add door opener button for HmIP-FDC (#179089)

This commit is contained in:
Christian Lackas
2026-08-14 14:24:52 +02:00
committed by GitHub
parent 083b156887
commit a02dad34f1
3 changed files with 183 additions and 5 deletions
@@ -12,6 +12,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .entity import HomematicipGenericEntity
from .hap import HomematicIPConfigEntry, HomematicipHAP
DOOR_OPENER_MODELS = {"HmIP-FLC", "HmIP-FDC"}
def _door_opener_authorization_channel(
device: object,
@@ -40,9 +42,9 @@ async def async_setup_entry(
if isinstance(device, WallMountedGarageDoorController)
]
entities.extend(
HomematicipFullFlushLockControllerButton(hap, device, auth_channel)
HomematicipDoorOpenerButton(hap, device, auth_channel)
for device in hap.home.devices
if getattr(device, "modelType", None) == "HmIP-FLC"
if getattr(device, "modelType", None) in DOOR_OPENER_MODELS
and (auth_channel := _door_opener_authorization_channel(device)) is not None
)
async_add_entities(entities)
@@ -62,8 +64,8 @@ class HomematicipGarageDoorControllerButton(HomematicipGenericEntity, ButtonEnti
await self._device.send_start_impulse_async()
class HomematicipFullFlushLockControllerButton(HomematicipGenericEntity, ButtonEntity):
"""Representation of the HomematicIP full flush lock controller opener."""
class HomematicipDoorOpenerButton(HomematicipGenericEntity, ButtonEntity):
"""Representation of a HomematicIP door opener (HmIP-FLC, HmIP-FDC)."""
def __init__(
self,
@@ -71,7 +73,7 @@ class HomematicipFullFlushLockControllerButton(HomematicipGenericEntity, ButtonE
device,
auth_channel: AccessAuthorizationChannel,
) -> None:
"""Initialize the full flush lock controller opener button."""
"""Initialize the door opener button."""
super().__init__(
hap, device, post="Door opener", feature_id="lock_opener_button"
)
@@ -199,6 +199,131 @@ def full_flush_lock_controller_device_data_fixture() -> dict[str, Any]:
}
@pytest.fixture(name="full_flush_door_controller_device_data")
def full_flush_door_controller_device_data_fixture() -> dict[str, Any]:
"""Return fixture data for an HmIP-FDC device."""
return {
"availableFirmwareVersion": "1.0.0",
"connectionType": "HMIP_RF",
"firmwareVersion": "1.0.0",
"firmwareVersionInteger": 65536,
"functionalChannels": {
"0": {
"configPending": False,
"deviceId": "3014F711000000000000FDC1",
"dutyCycle": False,
"functionalChannelType": "DEVICE_BASE",
"groupIndex": 0,
"groups": [],
"index": 0,
"label": "",
"lowBat": None,
"multicastRoutingEnabled": False,
"routerModuleEnabled": False,
"routerModuleSupported": False,
"rssiDeviceValue": -60,
"rssiPeerValue": -60,
"supportedOptionalFeatures": {
"IFeatureDeviceIdentify": True,
"IFeatureRssiValue": True,
"IOptionalFeatureDutyCycle": True,
},
"unreach": False,
},
"1": {
"actionParameter": "NOT_CUSTOMISABLE",
"binaryBehaviorType": "NORMALLY_CLOSE",
"channelRole": None,
"corrosionPreventionActive": False,
"deviceId": "3014F711000000000000FDC1",
"doorBellSensorEventTimestamp": None,
"eventDelay": 0,
"functionalChannelType": "MULTI_MODE_LOCK_INPUT_CHANNEL",
"glassBroken": False,
"groupIndex": 1,
"groups": [],
"index": 1,
"label": "",
"lockState": "UNLOCKED",
"multiModeInputMode": "KEY_BEHAVIOR",
"supportedOptionalFeatures": {
"IOptionalFeatureLongPressSupported": True
},
"windowState": "CLOSED",
},
"2": {
"actionParameter": "NOT_CUSTOMISABLE",
"binaryBehaviorType": "NORMALLY_CLOSE",
"channelRole": None,
"corrosionPreventionActive": False,
"deviceId": "3014F711000000000000FDC1",
"doorBellSensorEventTimestamp": None,
"eventDelay": 0,
"functionalChannelType": "MULTI_MODE_LOCK_INPUT_CHANNEL",
"glassBroken": False,
"groupIndex": 2,
"groups": [],
"index": 2,
"label": "",
"lockState": "UNLOCKED",
"multiModeInputMode": "KEY_BEHAVIOR",
"supportedOptionalFeatures": {
"IOptionalFeatureLongPressSupported": True
},
"windowState": "CLOSED",
},
"3": {
"channelRole": "DOOR_OPENER_ACTUATOR",
"deviceId": "3014F711000000000000FDC1",
"doorLockActive": False,
"functionalChannelType": "DOOR_SWITCH_CHANNEL",
"groupIndex": 3,
"groups": [],
"impulseDuration": 3.0,
"index": 3,
"internalLinkConfiguration": None,
"label": "",
"multiModeInputMode": "KEY_BEHAVIOR",
"processing": False,
"profileMode": "AUTOMATIC",
"supportedOptionalFeatures": {
"IOptionalFeatureImpulseDuration": True,
"IOptionalFeatureImpulseOutputState": True,
},
"userDesiredProfileMode": "AUTOMATIC",
},
"4": {
"authorized": True,
"channelRole": "DOOR_OPENER_ACTUATOR",
"deviceId": "3014F711000000000000FDC1",
"functionalChannelType": "ACCESS_AUTHORIZATION_CHANNEL",
"groupIndex": 3,
"groups": [],
"index": 4,
"label": "",
"supportedOptionalFeatures": {
"IFeatureAccessAuthorizationActuatorChannel": True
},
},
},
"homeId": "00000000-0000-0000-0000-000000000001",
"id": "3014F711000000000000FDC1",
"label": "Turoffner Haustuer",
"lastStatusUpdate": 1778954102356,
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
"manuallyUpdateForced": True,
"manufacturerCode": 1,
"measuredAttributes": {},
"modelId": 571,
"modelType": "HmIP-FDC",
"oem": "eQ-3",
"permanentlyReachable": True,
"serializedGlobalTradeItemNumber": "3014F711000000000000FDC1",
"type": "FULL_FLUSH_DOOR_CONTROLLER",
"updateState": "UP_TO_DATE",
}
@pytest.fixture(name="hmip_config")
def hmip_config_fixture() -> ConfigType:
"""Create a config for homematic ip cloud."""
@@ -124,3 +124,54 @@ async def test_hmip_full_flush_lock_controller_button_missing_channel(
entity_id = "button.universal_motorschloss_controller_door_opener"
assert hass.states.get(entity_id) is None
assert entity_id not in mock_hap.hmip_device_by_entity_id
async def test_hmip_full_flush_door_controller_button(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
default_mock_hap_factory: HomeFactory,
full_flush_door_controller_device_data: dict[str, Any],
) -> None:
"""Test the door opener button on an HmIP-FDC (auth channel 4, not 9)."""
entity_id = "button.turoffner_haustuer_door_opener"
entity_name = "Turoffner Haustuer Door opener"
device_model = "HmIP-FDC"
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=["Turoffner Haustuer"],
extra_devices=[full_flush_door_controller_device_data],
)
get_and_check_entity_basics(hass, mock_hap, entity_id, entity_name, device_model)
hmip_device = mock_hap.hmip_device_by_entity_id[entity_id]
auth_channel = next(
ch
for ch in hmip_device.functionalChannels
if ch.functionalChannelType.name == "ACCESS_AUTHORIZATION_CHANNEL"
and ch.channelRole == "DOOR_OPENER_ACTUATOR"
)
assert auth_channel.index == 4
with (
patch.object(
auth_channel, "async_pull_latch", new_callable=AsyncMock
) as mock_pull_latch,
patch.object(
hmip_device, "send_start_impulse_async", new_callable=AsyncMock
) as mock_send_start_impulse,
):
now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00")
freezer.move_to(now)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mock_pull_latch.assert_awaited_once_with()
mock_send_start_impulse.assert_not_awaited()
state = hass.states.get(entity_id)
assert state
assert state.state == now.isoformat()