123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- """Library to handle connection with Switchbot."""
- from __future__ import annotations
- import logging
- from abc import abstractmethod
- from typing import Any
- from .device import REQ_HEADER, SwitchbotDevice, update_after_operation
- # Cover keys
- COVER_COMMAND = "4501"
- # For second element of open and close arrs we should add two bytes i.e. ff00
- # First byte [ff] stands for speed (00 or ff - normal, 01 - slow) *
- # * Only for curtains 3. For other models use ff
- # Second byte [00] is a command (00 - open, 64 - close)
- POSITION_KEYS = [
- f"{REQ_HEADER}{COVER_COMMAND}0101",
- f"{REQ_HEADER}{COVER_COMMAND}05", # +speed
- ] # +actual_position
- STOP_KEYS = [f"{REQ_HEADER}{COVER_COMMAND}0001", f"{REQ_HEADER}{COVER_COMMAND}00ff"]
- COVER_EXT_SUM_KEY = f"{REQ_HEADER}460401"
- COVER_EXT_ADV_KEY = f"{REQ_HEADER}460402"
- _LOGGER = logging.getLogger(__name__)
- class SwitchbotBaseCover(SwitchbotDevice):
- """Representation of a Switchbot Cover devices for both curtains and tilt blinds."""
- def __init__(self, reverse: bool, *args: Any, **kwargs: Any) -> None:
- """Switchbot Cover device constructor."""
- super().__init__(*args, **kwargs)
- self._reverse = reverse
- self._settings: dict[str, Any] = {}
- self.ext_info_sum: dict[str, Any] = {}
- self.ext_info_adv: dict[str, Any] = {}
- self._is_opening: bool = False
- self._is_closing: bool = False
- async def _send_multiple_commands(self, keys: list[str]) -> bool:
- """Send multiple commands to device.
- Since we current have no way to tell which command the device
- needs we send both.
- """
- final_result = False
- for key in keys:
- result = await self._send_command(key)
- final_result |= self._check_command_result(result, 0, {1})
- return final_result
- @update_after_operation
- async def stop(self) -> bool:
- """Send stop command to device."""
- return await self._send_multiple_commands(STOP_KEYS)
- @update_after_operation
- async def set_position(self, position: int, speed: int = 255) -> bool:
- """Send position command (0-100) to device. Speed 255 - normal, 1 - slow"""
- position = (100 - position) if self._reverse else position
- return await self._send_multiple_commands(
- [
- f"{POSITION_KEYS[0]}{position:02X}",
- f"{POSITION_KEYS[1]}{speed:02X}{position:02X}",
- ]
- )
- @abstractmethod
- def get_position(self) -> Any:
- """Return current device position."""
- @abstractmethod
- async def get_basic_info(self) -> dict[str, Any] | None:
- """Get device basic settings."""
- @abstractmethod
- async def get_extended_info_summary(self) -> dict[str, Any] | None:
- """Get extended info for all devices in chain."""
- async def get_extended_info_adv(self) -> dict[str, Any] | None:
- """Get advance page info for device chain."""
- _data = await self._send_command(key=COVER_EXT_ADV_KEY)
- if not _data:
- _LOGGER.error("%s: Unsuccessful, no result from device", self.name)
- return None
- if _data in (b"\x07", b"\x00"):
- _LOGGER.error("%s: Unsuccessful, please try again", self.name)
- return None
- _state_of_charge = [
- "not_charging",
- "charging_by_adapter",
- "charging_by_solar",
- "fully_charged",
- "solar_not_charging",
- "charging_error",
- ]
- self.ext_info_adv["device0"] = {
- "battery": _data[1],
- "firmware": _data[2] / 10.0,
- "stateOfCharge": _state_of_charge[_data[3]],
- }
- # If grouped curtain device present.
- if _data[4]:
- self.ext_info_adv["device1"] = {
- "battery": _data[4],
- "firmware": _data[5] / 10.0,
- "stateOfCharge": _state_of_charge[_data[6]],
- }
- return self.ext_info_adv
- def get_light_level(self) -> Any:
- """Return cached light level."""
- # To get actual light level call update() first.
- return self._get_adv_value("lightLevel")
- def is_reversed(self) -> bool:
- """Return True if curtain position is opposite from SB data."""
- return self._reverse
- def is_calibrated(self) -> Any:
- """Return True curtain is calibrated."""
- # To get actual light level call update() first.
- return self._get_adv_value("calibration")
- def is_opening(self) -> bool:
- """Return True if the curtain is opening."""
- return self._is_opening
- def is_closing(self) -> bool:
- """Return True if the curtain is closing."""
- return self._is_closing
|