Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cover platform for switchbee integration #78383

Merged
merged 25 commits into from Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
36f5135
Added Platform cover for switchbee integration
jafar-atili Sep 13, 2022
8423c50
added cover to .coveragerc
jafar-atili Sep 13, 2022
1257c03
Applied code review feedback from other PR
jafar-atili Sep 13, 2022
be38b39
Addressed comments from other PRs
jafar-atili Sep 16, 2022
9507b25
rebased
jafar-atili Sep 24, 2022
c668cb0
Re-add carriage return
epenet Sep 26, 2022
f7c60c6
Update homeassistant/components/switchbee/cover.py
jafar-atili Sep 26, 2022
5ce22be
Update homeassistant/components/switchbee/cover.py
jafar-atili Sep 26, 2022
6889e28
Update homeassistant/components/switchbee/cover.py
jafar-atili Sep 26, 2022
bc843f1
Update homeassistant/components/switchbee/cover.py
jafar-atili Sep 26, 2022
df04bb7
addressed CR comments
jafar-atili Sep 26, 2022
f36d217
fixes
jafar-atili Sep 28, 2022
b60d0bf
fixes
jafar-atili Sep 28, 2022
6486131
more fixes
jafar-atili Sep 28, 2022
ad248aa
more fixes
jafar-atili Sep 28, 2022
b6e722d
separate entities for cover and somfy cover
jafar-atili Sep 28, 2022
0ee377b
fixed isort
jafar-atili Sep 28, 2022
907013a
more fixes
jafar-atili Sep 28, 2022
13f745e
Merge branch 'dev' into switchbee_cover
jafar-atili Sep 28, 2022
2978945
more fixes
jafar-atili Sep 29, 2022
fdffd29
Update homeassistant/components/switchbee/cover.py
jafar-atili Sep 29, 2022
57a356e
Update homeassistant/components/switchbee/cover.py
jafar-atili Sep 29, 2022
c7bba9b
more fixes
jafar-atili Sep 29, 2022
368c27f
more fixes
jafar-atili Sep 29, 2022
f918ec8
more
jafar-atili Sep 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Expand Up @@ -1231,6 +1231,7 @@ omit =
homeassistant/components/switchbee/__init__.py
homeassistant/components/switchbee/button.py
homeassistant/components/switchbee/coordinator.py
homeassistant/components/switchbee/cover.py
homeassistant/components/switchbee/entity.py
homeassistant/components/switchbee/light.py
homeassistant/components/switchbee/switch.py
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/switchbee/__init__.py
Expand Up @@ -13,7 +13,12 @@
from .const import DOMAIN
from .coordinator import SwitchBeeCoordinator

PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.LIGHT, Platform.SWITCH]
PLATFORMS: list[Platform] = [
Platform.BUTTON,
Platform.COVER,
Platform.LIGHT,
Platform.SWITCH,
]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/switchbee/coordinator.py
Expand Up @@ -62,6 +62,8 @@ async def _async_update_data(self) -> Mapping[int, SwitchBeeBaseDevice]:
DeviceType.TimedPowerSwitch,
DeviceType.Scenario,
DeviceType.Dimmer,
DeviceType.Shutter,
DeviceType.Somfy,
]
)
except SwitchBeeError as exp:
Expand Down
182 changes: 182 additions & 0 deletions homeassistant/components/switchbee/cover.py
@@ -0,0 +1,182 @@
"""Support for SwitchBee cover."""

from __future__ import annotations

from typing import Any, cast

from switchbee.api import SwitchBeeError, SwitchBeeTokenError
from switchbee.const import SomfyCommand
from switchbee.device import SwitchBeeShutter, SwitchBeeSomfy

from homeassistant.components.cover import (
ATTR_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .coordinator import SwitchBeeCoordinator
from .entity import SwitchBeeDeviceEntity


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up SwitchBee switch."""
coordinator: SwitchBeeCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
SwitchBeeCoverEntity(device, coordinator)
for device in coordinator.data.values()
if isinstance(
device,
SwitchBeeShutter,
)
)

async_add_entities(
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
SwitchBeeSomfyEntity(device, coordinator)
for device in coordinator.data.values()
if isinstance(
device,
SwitchBeeSomfy,
)
)


class SwitchBeeSomfyEntity(SwitchBeeDeviceEntity[SwitchBeeSomfy], CoverEntity):
"""Representation of a SwitchBee Somfy cover."""

_attr_device_class = CoverDeviceClass.SHUTTER

def __init__(
self,
device: SwitchBeeSomfy,
coordinator: SwitchBeeCoordinator,
) -> None:
"""Initialize the SwitchBee cover."""
super().__init__(device, coordinator)
self._attr_current_cover_position = 0
self._attr_is_closed = True
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
self._attr_supported_features = (
CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN | CoverEntityFeature.STOP
)
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved

async def _fire_somfy_command(self, command: str) -> None:
"""Async function to fire Somfy device command."""
try:
await self.coordinator.api.set_state(self._device.id, command)
except (SwitchBeeError, SwitchBeeTokenError) as exp:
raise HomeAssistantError(
f"Failed to fire {command} for {self.name}, {str(exp)}"
) from exp

async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
return await self._fire_somfy_command(SomfyCommand.UP)

async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the cover."""
return await self._fire_somfy_command(SomfyCommand.DOWN)

async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop a moving cover."""
return await self._fire_somfy_command(SomfyCommand.MY)


class SwitchBeeCoverEntity(SwitchBeeDeviceEntity[SwitchBeeShutter], CoverEntity):
"""Representation of a SwitchBee cover."""

_attr_device_class = CoverDeviceClass.SHUTTER

def __init__(
self,
device: SwitchBeeShutter,
coordinator: SwitchBeeCoordinator,
) -> None:
"""Initialize the SwitchBee cover."""
super().__init__(device, coordinator)
self._attr_current_cover_position = 0
self._attr_is_closed = True
self._attr_supported_features = (
CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.STOP
)
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._update_from_coordinator()
super()._handle_coordinator_update()

def _update_from_coordinator(self) -> None:
"""Update the entity attributes from the coordinator data."""

coordinator_device = cast(
SwitchBeeShutter, self.coordinator.data[self._device.id]
)
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved

if coordinator_device.position == -1:
self._check_if_became_offline()
return

# check if the device was offline (now online) and bring it back
self._check_if_became_online()

self._attr_current_cover_position = coordinator_device.position

if self._attr_current_cover_position == 0:
self._attr_is_closed = True
else:
self._attr_is_closed = False
super()._handle_coordinator_update()

async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
if self._attr_current_cover_position == 100:
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
return

await self.async_set_cover_position(position=100)

async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the cover."""
if self._attr_current_cover_position == 0:
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
return

await self.async_set_cover_position(position=0)

async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop a moving cover."""
# to stop the shutter, we just interrupt it with any state during operation
await self.async_set_cover_position(
position=self._attr_current_cover_position, force=True
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
)

# fetch data from the Central Unit to get the new position
await self.coordinator.async_request_refresh()

async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Async function to set position to cover."""
if (
self._attr_current_cover_position == kwargs[ATTR_POSITION]
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
and "force" not in kwargs
):
return
try:
await self.coordinator.api.set_state(self._device.id, kwargs[ATTR_POSITION])
except (SwitchBeeError, SwitchBeeTokenError) as exp:
raise HomeAssistantError(
f"Failed to set {self._attr_name} position to {str(kwargs[ATTR_POSITION])}, error: {str(exp)}"
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
) from exp
else:
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
cast(
SwitchBeeShutter, self.coordinator.data[self._device.id]
).position = kwargs[ATTR_POSITION]
self.coordinator.async_set_updated_data(self.coordinator.data)
self.async_write_ha_state()
33 changes: 17 additions & 16 deletions homeassistant/components/switchbee/entity.py
Expand Up @@ -4,7 +4,7 @@

from switchbee import SWITCHBEE_BRAND
from switchbee.api import SwitchBeeDeviceOfflineError, SwitchBeeError
from switchbee.device import SwitchBeeBaseDevice
from switchbee.device import SwitchBeeBaseDevice, DeviceType

from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand Down Expand Up @@ -46,22 +46,23 @@ def __init__(
"""Initialize the Switchbee device."""
super().__init__(device, coordinator)
self._is_online: bool = True
self._attr_device_info = DeviceInfo(
name=f"SwitchBee {device.unit_id}",
identifiers={
(
if self._device.type not in [DeviceType.Somfy]:
jafar-atili marked this conversation as resolved.
Show resolved Hide resolved
self._attr_device_info = DeviceInfo(
name=f"SwitchBee {device.unit_id}",
identifiers={
(
DOMAIN,
f"{device.unit_id}-{coordinator.mac_formatted}",
)
},
manufacturer=SWITCHBEE_BRAND,
model=coordinator.api.module_display(device.unit_id),
suggested_area=device.zone,
via_device=(
DOMAIN,
f"{device.unit_id}-{coordinator.mac_formatted}",
)
},
manufacturer=SWITCHBEE_BRAND,
model=coordinator.api.module_display(device.unit_id),
suggested_area=device.zone,
via_device=(
DOMAIN,
f"{coordinator.api.name} ({coordinator.api.mac})",
),
)
f"{coordinator.api.name} ({coordinator.api.mac})",
),
)

@property
def available(self) -> bool:
Expand Down