import logging from typing import Any from bleak.backends.device import BLEDevice from ..const import SwitchbotModel from ..const.evaporative_humidifier import ( TARGET_HUMIDITY_MODES, HumidifierMode, HumidifierWaterLevel, ) from ..models import SwitchBotAdvertisement from .device import SwitchbotEncryptedDevice _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" MODES_COMMANDS = { HumidifierMode.HIGH: "010100", HumidifierMode.MEDIUM: "010200", HumidifierMode.LOW: "010300", HumidifierMode.QUIET: "010400", HumidifierMode.TARGET_HUMIDITY: "0200", HumidifierMode.SLEEP: "0300", HumidifierMode.AUTO: "040000", } class SwitchbotEvaporativeHumidifier(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 ) def update_from_advertisement(self, advertisement: SwitchBotAdvertisement) -> None: """Update device data from advertisement.""" super().update_from_advertisement(advertisement) _LOGGER.debug( "%s: update advertisement: %s", self.name, advertisement, ) async def _get_basic_info(self) -> bytes | None: """Return basic info of device.""" _data = await self._send_command( key=COMMAND_GET_BASIC_INFO, retry=self._retry_count ) if _data in (b"\x07", b"\x00"): _LOGGER.error("Unsuccessful, please try again") return None return _data async def get_basic_info(self) -> dict[str, Any] | None: """Get device basic settings.""" if not (_data := await self._get_basic_info()): return None # Not 100% sure about this data, will verify once a firmware update is available return { "firmware": _data[2] / 10.0, } async def turn_on(self) -> bool: """Turn device on.""" result = await self._send_command(COMMAND_TURN_ON) if ok := self._check_command_result(result, 0, {1}): self._override_state({"isOn": True}) self._fire_callbacks() return ok async def turn_off(self) -> bool: """Turn device off.""" result = await self._send_command(COMMAND_TURN_OFF) if ok := self._check_command_result(result, 0, {1}): self._override_state({"isOn": False}) self._fire_callbacks() return ok async def set_mode( self, mode: HumidifierMode, target_humidity: int | None = None ) -> bool: """Set device mode.""" if mode == HumidifierMode.DRYING_FILTER: return await self.start_drying_filter() elif mode not in MODES_COMMANDS: raise ValueError("Invalid mode") command = COMMAND_SET_MODE + MODES_COMMANDS[mode] if mode in TARGET_HUMIDITY_MODES: if target_humidity is None: raise TypeError("target_humidity is required") command += f"{target_humidity:02x}" result = await self._send_command(command) if ok := self._check_command_result(result, 0, {1}): self._override_state({"mode": mode}) if mode == HumidifierMode.TARGET_HUMIDITY and target_humidity is not None: self._override_state({"target_humidity": target_humidity}) self._fire_callbacks() return ok 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 ) if ok := self._check_command_result(result, 0, {1}): self._override_state({"child_lock": enabled}) self._fire_callbacks() return ok async def start_drying_filter(self): """Start drying filter.""" result = await self._send_command(COMMAND_TURN_ON + "08") if ok := self._check_command_result(result, 0, {1}): self._override_state({"mode": HumidifierMode.DRYING_FILTER}) self._fire_callbacks() return ok async def stop_drying_filter(self): """Stop drying filter.""" result = await self._send_command(COMMAND_TURN_OFF) if ok := self._check_command_result(result, 0, {0}): self._override_state({"isOn": False, "mode": None}) self._fire_callbacks() return ok 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")