core/homeassistant/components/switchbot/cover.py

190 lines
7.1 KiB
Python
Raw Normal View History

"""Support for SwitchBot curtains."""
from __future__ import annotations
import logging
from typing import Any
import switchbot
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_CURRENT_TILT_POSITION,
ATTR_POSITION,
ATTR_TILT_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
Update switchbot to be local push (#75645) * Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-07-24 16:38:45 +00:00
from .const import DOMAIN
from .coordinator import SwitchbotDataUpdateCoordinator
from .entity import SwitchbotEntity
# Initialize the logger
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Switchbot curtain based on a config entry."""
Update switchbot to be local push (#75645) * Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-07-24 16:38:45 +00:00
coordinator: SwitchbotDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
if isinstance(coordinator.device, switchbot.SwitchbotBlindTilt):
async_add_entities([SwitchBotBlindTiltEntity(coordinator)])
else:
async_add_entities([SwitchBotCurtainEntity(coordinator)])
class SwitchBotCurtainEntity(SwitchbotEntity, CoverEntity, RestoreEntity):
"""Representation of a Switchbot."""
_device: switchbot.SwitchbotCurtain
_attr_device_class = CoverDeviceClass.CURTAIN
_attr_supported_features = (
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.STOP
| CoverEntityFeature.SET_POSITION
)
_attr_translation_key = "cover"
_attr_name = None
def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None:
"""Initialize the Switchbot."""
super().__init__(coordinator)
self._attr_is_closed = None
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added."""
await super().async_added_to_hass()
last_state = await self.async_get_last_state()
if not last_state or ATTR_CURRENT_POSITION not in last_state.attributes:
return
self._attr_current_cover_position = last_state.attributes.get(
ATTR_CURRENT_POSITION
)
self._last_run_success = last_state.attributes.get("last_run_success")
if self._attr_current_cover_position is not None:
self._attr_is_closed = self._attr_current_cover_position <= 20
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the curtain."""
Update switchbot to be local push (#75645) * Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-07-24 16:38:45 +00:00
_LOGGER.debug("Switchbot to open curtain %s", self._address)
self._last_run_success = bool(await self._device.open())
self.async_write_ha_state()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the curtain."""
Update switchbot to be local push (#75645) * Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-07-24 16:38:45 +00:00
_LOGGER.debug("Switchbot to close the curtain %s", self._address)
self._last_run_success = bool(await self._device.close())
self.async_write_ha_state()
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the moving of this device."""
Update switchbot to be local push (#75645) * Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-07-24 16:38:45 +00:00
_LOGGER.debug("Switchbot to stop %s", self._address)
self._last_run_success = bool(await self._device.stop())
self.async_write_ha_state()
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover shutter to a specific position."""
position = kwargs.get(ATTR_POSITION)
Update switchbot to be local push (#75645) * Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-07-24 16:38:45 +00:00
_LOGGER.debug("Switchbot to move at %d %s", position, self._address)
self._last_run_success = bool(await self._device.set_position(position))
self.async_write_ha_state()
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_current_cover_position = self.parsed_data["position"]
self._attr_is_closed = self.parsed_data["position"] <= 20
self._attr_is_opening = self.parsed_data["inMotion"]
self.async_write_ha_state()
class SwitchBotBlindTiltEntity(SwitchbotEntity, CoverEntity, RestoreEntity):
"""Representation of a Switchbot."""
_device: switchbot.SwitchbotBlindTilt
_attr_device_class = CoverDeviceClass.BLIND
_attr_supported_features = (
CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.STOP_TILT
| CoverEntityFeature.SET_TILT_POSITION
)
_attr_translation_key = "cover"
CLOSED_UP_THRESHOLD = 80
CLOSED_DOWN_THRESHOLD = 20
def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None:
"""Initialize the Switchbot."""
super().__init__(coordinator)
self._attr_is_closed = None
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added."""
await super().async_added_to_hass()
last_state = await self.async_get_last_state()
if not last_state or ATTR_CURRENT_TILT_POSITION not in last_state.attributes:
return
self._attr_current_cover_tilt_position = last_state.attributes.get(
ATTR_CURRENT_TILT_POSITION
)
self._last_run_success = last_state.attributes.get("last_run_success")
if (_tilt := self._attr_current_cover_position) is not None:
self._attr_is_closed = (_tilt < self.CLOSED_DOWN_THRESHOLD) or (
_tilt > self.CLOSED_UP_THRESHOLD
)
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the tilt."""
_LOGGER.debug("Switchbot to open blind tilt %s", self._address)
self._last_run_success = bool(await self._device.open())
self.async_write_ha_state()
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close the tilt."""
_LOGGER.debug("Switchbot to close the blind tilt %s", self._address)
self._last_run_success = bool(await self._device.close())
self.async_write_ha_state()
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
"""Stop the moving of this device."""
_LOGGER.debug("Switchbot to stop %s", self._address)
self._last_run_success = bool(await self._device.stop())
self.async_write_ha_state()
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position."""
position = kwargs.get(ATTR_TILT_POSITION)
_LOGGER.debug("Switchbot to move at %d %s", position, self._address)
self._last_run_success = bool(await self._device.set_position(position))
self.async_write_ha_state()
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
_tilt = self.parsed_data["tilt"]
self._attr_current_cover_tilt_position = _tilt
self._attr_is_closed = (_tilt < self.CLOSED_DOWN_THRESHOLD) or (
_tilt > self.CLOSED_UP_THRESHOLD
)
self._attr_is_opening = self.parsed_data["motionDirection"]["opening"]
self._attr_is_closing = self.parsed_data["motionDirection"]["closing"]
self.async_write_ha_state()