Add cover platform to Tailwind integration (#106042)

This commit is contained in:
Franck Nijhof 2023-12-19 12:46:21 +01:00 committed by GitHub
parent a4ccd6e13b
commit 48740e6b05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 312 additions and 1 deletions

View file

@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import TailwindDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.NUMBER]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.COVER, Platform.BUTTON, Platform.NUMBER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View file

@ -0,0 +1,88 @@
"""Cover entity platform for Tailwind."""
from __future__ import annotations
from typing import Any
from gotailwind import TailwindDoorOperationCommand, TailwindDoorState
from homeassistant.components.cover import (
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import TailwindDataUpdateCoordinator
from .entity import TailwindDoorEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Tailwind cover based on a config entry."""
coordinator: TailwindDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
TailwindDoorCoverEntity(coordinator, door_id)
for door_id in coordinator.data.doors
)
class TailwindDoorCoverEntity(TailwindDoorEntity, CoverEntity):
"""Representation of a Tailwind door binary sensor entity."""
_attr_device_class = CoverDeviceClass.GARAGE
_attr_is_closing = False
_attr_is_opening = False
_attr_name = None
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
def __init__(
self,
coordinator: TailwindDataUpdateCoordinator,
door_id: str,
) -> None:
"""Initiate Tailwind button entity."""
super().__init__(coordinator, door_id)
self._attr_unique_id = f"{coordinator.data.device_id}-{door_id}"
@property
def is_closed(self) -> bool:
"""Return if the cover is closed or not."""
return (
self.coordinator.data.doors[self.door_id].state == TailwindDoorState.CLOSED
)
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the garage door.
The Tailwind operating command will await the confirmation of the
door being opened before returning.
"""
self._attr_is_opening = True
self.async_write_ha_state()
await self.coordinator.tailwind.operate(
door=self.coordinator.data.doors[self.door_id],
operation=TailwindDoorOperationCommand.OPEN,
)
self._attr_is_opening = False
await self.coordinator.async_request_refresh()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the garage door.
The Tailwind operating command will await the confirmation of the
door being closed before returning.
"""
self._attr_is_closing = True
self.async_write_ha_state()
await self.coordinator.tailwind.operate(
door=self.coordinator.data.doors[self.door_id],
operation=TailwindDoorOperationCommand.CLOSE,
)
self._attr_is_closing = False
await self.coordinator.async_request_refresh()

View file

@ -0,0 +1,147 @@
# serializer version: 1
# name: test_cover_entities[cover.door_1]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'garage',
'friendly_name': 'Door 1',
'supported_features': <CoverEntityFeature: 3>,
}),
'context': <ANY>,
'entity_id': 'cover.door_1',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'open',
})
# ---
# name: test_cover_entities[cover.door_1].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.door_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <CoverDeviceClass.GARAGE: 'garage'>,
'original_icon': None,
'original_name': None,
'platform': 'tailwind',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 3>,
'translation_key': None,
'unique_id': '_3c_e9_e_6d_21_84_-door1',
'unit_of_measurement': None,
})
# ---
# name: test_cover_entities[cover.door_1].2
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'tailwind',
'_3c_e9_e_6d_21_84_-door1',
),
}),
'is_new': False,
'manufacturer': 'Tailwind',
'model': 'iQ3',
'name': 'Door 1',
'name_by_user': None,
'serial_number': None,
'suggested_area': None,
'sw_version': '10.10',
'via_device_id': None,
})
# ---
# name: test_cover_entities[cover.door_2]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'garage',
'friendly_name': 'Door 2',
'supported_features': <CoverEntityFeature: 3>,
}),
'context': <ANY>,
'entity_id': 'cover.door_2',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'open',
})
# ---
# name: test_cover_entities[cover.door_2].1
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.door_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <CoverDeviceClass.GARAGE: 'garage'>,
'original_icon': None,
'original_name': None,
'platform': 'tailwind',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 3>,
'translation_key': None,
'unique_id': '_3c_e9_e_6d_21_84_-door2',
'unit_of_measurement': None,
})
# ---
# name: test_cover_entities[cover.door_2].2
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'tailwind',
'_3c_e9_e_6d_21_84_-door2',
),
}),
'is_new': False,
'manufacturer': 'Tailwind',
'model': 'iQ3',
'name': 'Door 2',
'name_by_user': None,
'serial_number': None,
'suggested_area': None,
'sw_version': '10.10',
'via_device_id': None,
})
# ---

View file

@ -0,0 +1,76 @@
"""Tests for cover entities provided by the Tailwind integration."""
from unittest.mock import ANY, MagicMock
from gotailwind import TailwindDoorOperationCommand
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.cover import (
DOMAIN as COVER_DOMAIN,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
pytestmark = pytest.mark.usefixtures("init_integration")
@pytest.mark.parametrize(
"entity_id",
[
"cover.door_1",
"cover.door_2",
],
)
async def test_cover_entities(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
entity_id: str,
) -> None:
"""Test cover entities provided by the Tailwind integration."""
assert (state := hass.states.get(entity_id))
assert state == snapshot
assert (entity_entry := entity_registry.async_get(state.entity_id))
assert entity_entry == snapshot
assert entity_entry.device_id
assert (device_entry := device_registry.async_get(entity_entry.device_id))
assert device_entry == snapshot
async def test_cover_operations(
hass: HomeAssistant,
mock_tailwind: MagicMock,
) -> None:
"""Test operating the doors."""
assert len(mock_tailwind.operate.mock_calls) == 0
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
mock_tailwind.operate.assert_called_with(
door=ANY, operation=TailwindDoorOperationCommand.OPEN
)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{
ATTR_ENTITY_ID: "cover.door_1",
},
blocking=True,
)
mock_tailwind.operate.assert_called_with(
door=ANY, operation=TailwindDoorOperationCommand.CLOSE
)