123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- import logging
- from typing import Any
- from bleak.backends.device import BLEDevice
- from ..adv_parsers.humidifier import calculate_temperature_and_humidity
- from ..const import SwitchbotModel
- from ..const.evaporative_humidifier import (
- TARGET_HUMIDITY_MODES,
- HumidifierAction,
- HumidifierMode,
- HumidifierWaterLevel,
- )
- from .device import (
- SwitchbotEncryptedDevice,
- SwitchbotOperationError,
- SwitchbotSequenceDevice,
- update_after_operation,
- )
- _LOGGER = logging.getLogger(__name__)
- COMMAND_HEADER = "57"
- COMMAND_GET_CK_IV = f"{COMMAND_HEADER}0f2103"
- COMMAND_TURN_ON = f"{COMMAND_HEADER}0f430101"
- COMMAND_TURN_OFF = f"{COMMAND_HEADER}0f430100"
- COMMAND_CHILD_LOCK_ON = f"{COMMAND_HEADER}0f430501"
- COMMAND_CHILD_LOCK_OFF = f"{COMMAND_HEADER}0f430500"
- COMMAND_AUTO_DRY_ON = f"{COMMAND_HEADER}0f430a01"
- COMMAND_AUTO_DRY_OFF = f"{COMMAND_HEADER}0f430a02"
- COMMAND_SET_MODE = f"{COMMAND_HEADER}0f4302"
- COMMAND_GET_BASIC_INFO = f"{COMMAND_HEADER}000300"
- COMMAND_SET_DRYING_FILTER = f"{COMMAND_TURN_ON}08"
- MODES_COMMANDS = {
- HumidifierMode.HIGH: "010100",
- HumidifierMode.MEDIUM: "010200",
- HumidifierMode.LOW: "010300",
- HumidifierMode.QUIET: "010400",
- HumidifierMode.TARGET_HUMIDITY: "0200",
- HumidifierMode.SLEEP: "0300",
- HumidifierMode.AUTO: "040000",
- }
- DEVICE_GET_BASIC_SETTINGS_KEY = "570f4481"
- class SwitchbotEvaporativeHumidifier(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
- """Representation of a Switchbot Evaporative Humidifier"""
- def __init__(
- self,
- device: BLEDevice,
- key_id: str,
- encryption_key: str,
- interface: int = 0,
- model: SwitchbotModel = SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
- **kwargs: Any,
- ) -> None:
- self._force_next_update = False
- super().__init__(device, key_id, encryption_key, model, interface, **kwargs)
- @classmethod
- async def verify_encryption_key(
- cls,
- device: BLEDevice,
- key_id: str,
- encryption_key: str,
- model: SwitchbotModel = SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
- **kwargs: Any,
- ) -> bool:
- return await super().verify_encryption_key(
- device, key_id, encryption_key, model, **kwargs
- )
- async def get_basic_info(self) -> dict[str, Any] | None:
- """Get device basic settings."""
- if not (_data := await self._get_basic_info(DEVICE_GET_BASIC_SETTINGS_KEY)):
- return None
- _LOGGER.debug("basic info data: %s", _data.hex())
- isOn = bool(_data[1] & 0b10000000)
- mode = HumidifierMode(_data[1] & 0b00001111)
- over_humidify_protection = bool(_data[2] & 0b10000000)
- child_lock = bool(_data[2] & 0b00100000)
- tank_removed = bool(_data[2] & 0b00000100)
- tilted_alert = bool(_data[2] & 0b00000010)
- filter_missing = bool(_data[2] & 0b00000001)
- is_meter_binded = bool(_data[3] & 0b10000000)
- _temp_c, _temp_f, humidity = calculate_temperature_and_humidity(
- _data[3:6], is_meter_binded
- )
- water_level = HumidifierWaterLevel(_data[5] & 0b00000011).name.lower()
- filter_run_time = int.from_bytes(_data[6:8], byteorder="big") & 0xFFF
- target_humidity = _data[10] & 0b01111111
- return {
- "isOn": isOn,
- "mode": mode,
- "over_humidify_protection": over_humidify_protection,
- "child_lock": child_lock,
- "tank_removed": tank_removed,
- "tilted_alert": tilted_alert,
- "filter_missing": filter_missing,
- "is_meter_binded": is_meter_binded,
- "humidity": humidity,
- "temperature": _temp_c,
- "temp": {"c": _temp_c, "f": _temp_f},
- "water_level": water_level,
- "filter_run_time": filter_run_time,
- "target_humidity": target_humidity,
- }
- @update_after_operation
- async def turn_on(self) -> bool:
- """Turn device on."""
- result = await self._send_command(COMMAND_TURN_ON)
- return self._check_command_result(result, 0, {1})
- @update_after_operation
- async def turn_off(self) -> bool:
- """Turn device off."""
- result = await self._send_command(COMMAND_TURN_OFF)
- return self._check_command_result(result, 0, {1})
- @update_after_operation
- async def set_target_humidity(self, target_humidity: int) -> bool:
- """Set target humidity."""
- self._validate_water_level()
- self._validate_mode_for_target_humidity()
- command = (
- COMMAND_SET_MODE
- + MODES_COMMANDS[self.get_mode()]
- + f"{target_humidity:02x}"
- )
- result = await self._send_command(command)
- return self._check_command_result(result, 0, {1})
- @update_after_operation
- async def set_mode(self, mode: HumidifierMode) -> bool:
- """Set device mode."""
- self._validate_water_level()
- self._validate_meter_binding(mode)
- if mode == HumidifierMode.DRYING_FILTER:
- command = COMMAND_SET_DRYING_FILTER
- else:
- command = COMMAND_SET_MODE + MODES_COMMANDS[mode]
- if mode in TARGET_HUMIDITY_MODES:
- target_humidity = self.get_target_humidity()
- if target_humidity is None:
- raise SwitchbotOperationError(
- "Target humidity must be set before switching to target humidity mode or sleep mode"
- )
- command += f"{target_humidity:02x}"
- result = await self._send_command(command)
- return self._check_command_result(result, 0, {1})
- def _validate_water_level(self) -> None:
- """Validate that the water level is not empty."""
- if self.get_water_level() == HumidifierWaterLevel.EMPTY.name.lower():
- raise SwitchbotOperationError(
- "Cannot perform operation when water tank is empty"
- )
- def _validate_mode_for_target_humidity(self) -> None:
- """Validate that the current mode supports target humidity."""
- if self.get_mode() not in TARGET_HUMIDITY_MODES:
- raise SwitchbotOperationError(
- "Target humidity can only be set in target humidity mode or sleep mode"
- )
- def _validate_meter_binding(self, mode: HumidifierMode) -> None:
- """Validate that the meter is binded for specific modes."""
- if not self.is_meter_binded() and mode in [
- HumidifierMode.TARGET_HUMIDITY,
- HumidifierMode.AUTO,
- ]:
- raise SwitchbotOperationError(
- "Cannot set target humidity or auto mode when meter is not binded"
- )
- @update_after_operation
- async def set_child_lock(self, enabled: bool) -> bool:
- """Set child lock."""
- result = await self._send_command(
- COMMAND_CHILD_LOCK_ON if enabled else COMMAND_CHILD_LOCK_OFF
- )
- return self._check_command_result(result, 0, {1})
- def is_on(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("isOn")
- def get_mode(self) -> HumidifierMode | None:
- """Return state from cache."""
- return self._get_adv_value("mode")
- def is_child_lock_enabled(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("child_lock")
- def is_over_humidify_protection_enabled(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("over_humidify_protection")
- def is_tank_removed(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("tank_removed")
- def is_filter_missing(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("filter_missing")
- def is_filter_alert_on(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("filter_alert")
- def is_tilted_alert_on(self) -> bool | None:
- """Return state from cache."""
- return self._get_adv_value("tilted_alert")
- def get_water_level(self) -> HumidifierWaterLevel | None:
- """Return state from cache."""
- return self._get_adv_value("water_level")
- def get_filter_run_time(self) -> int | None:
- """Return state from cache."""
- return self._get_adv_value("filter_run_time")
- def get_target_humidity(self) -> int | None:
- """Return state from cache."""
- return self._get_adv_value("target_humidity")
- def get_humidity(self) -> int | None:
- """Return state from cache."""
- return self._get_adv_value("humidity")
- def get_temperature(self) -> float | None:
- """Return state from cache."""
- return self._get_adv_value("temperature")
- def get_action(self) -> int:
- """Return current action from cache."""
- if not self.is_on():
- return HumidifierAction.OFF
- if self.get_mode() != HumidifierMode.DRYING_FILTER:
- return HumidifierAction.HUMIDIFYING
- return HumidifierAction.DRYING
- def is_meter_binded(self) -> bool | None:
- """Return meter bind state from cache."""
- return self._get_adv_value("is_meter_binded")
|