diff --git a/.coveragerc b/.coveragerc index 49fd1a80c02..cec757853d7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1245,6 +1245,7 @@ omit = homeassistant/components/switchbot/coordinator.py homeassistant/components/switchbot/cover.py homeassistant/components/switchbot/entity.py + homeassistant/components/switchbot/humidifier.py homeassistant/components/switchbot/light.py homeassistant/components/switchbot/sensor.py homeassistant/components/switchbot/switch.py diff --git a/homeassistant/components/switchbot/__init__.py b/homeassistant/components/switchbot/__init__.py index 58e77dfe1bf..03e5e483a11 100644 --- a/homeassistant/components/switchbot/__init__.py +++ b/homeassistant/components/switchbot/__init__.py @@ -42,6 +42,7 @@ PLATFORMS_BY_TYPE = { SupportedModels.HYGROMETER.value: [Platform.SENSOR], SupportedModels.CONTACT.value: [Platform.BINARY_SENSOR, Platform.SENSOR], SupportedModels.MOTION.value: [Platform.BINARY_SENSOR, Platform.SENSOR], + SupportedModels.HUMIDIFIER.value: [Platform.HUMIDIFIER, Platform.SENSOR], } CLASS_BY_DEVICE = { SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight, @@ -50,6 +51,7 @@ CLASS_BY_DEVICE = { SupportedModels.PLUG.value: switchbot.SwitchbotPlugMini, SupportedModels.BULB.value: switchbot.SwitchbotBulb, SupportedModels.LIGHT_STRIP.value: switchbot.SwitchbotLightStrip, + SupportedModels.HUMIDIFIER.value: switchbot.SwitchbotHumidifier, } diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index c46a9b2d501..f34108a1038 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -66,7 +66,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" - _LOGGER.debug("Discovered bluetooth device: %s", discovery_info) + _LOGGER.debug("Discovered bluetooth device: %s", discovery_info.as_dict()) await self.async_set_unique_id(format_unique_id(discovery_info.address)) self._abort_if_unique_id_configured() parsed = parse_advertisement_data( diff --git a/homeassistant/components/switchbot/const.py b/homeassistant/components/switchbot/const.py index ecd86e1bef5..d8bcb75bf65 100644 --- a/homeassistant/components/switchbot/const.py +++ b/homeassistant/components/switchbot/const.py @@ -23,6 +23,7 @@ class SupportedModels(StrEnum): CONTACT = "contact" PLUG = "plug" MOTION = "motion" + HUMIDIFIER = "humidifier" CONNECTABLE_SUPPORTED_MODEL_TYPES = { @@ -32,6 +33,7 @@ CONNECTABLE_SUPPORTED_MODEL_TYPES = { SwitchbotModel.COLOR_BULB: SupportedModels.BULB, SwitchbotModel.LIGHT_STRIP: SupportedModels.LIGHT_STRIP, SwitchbotModel.CEILING_LIGHT: SupportedModels.CEILING_LIGHT, + SwitchbotModel.HUMIDIFIER: SupportedModels.HUMIDIFIER, } NON_CONNECTABLE_SUPPORTED_MODEL_TYPES = { diff --git a/homeassistant/components/switchbot/entity.py b/homeassistant/components/switchbot/entity.py index fcf0bdc4da2..e7b41d157cc 100644 --- a/homeassistant/components/switchbot/entity.py +++ b/homeassistant/components/switchbot/entity.py @@ -3,9 +3,10 @@ from __future__ import annotations from abc import abstractmethod from collections.abc import Mapping +import logging from typing import Any -from switchbot import SwitchbotDevice +from switchbot import Switchbot, SwitchbotDevice from homeassistant.components.bluetooth.passive_update_coordinator import ( PassiveBluetoothCoordinatorEntity, @@ -13,11 +14,13 @@ from homeassistant.components.bluetooth.passive_update_coordinator import ( from homeassistant.const import ATTR_CONNECTIONS from homeassistant.core import callback from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, ToggleEntity from .const import MANUFACTURER from .coordinator import SwitchbotDataUpdateCoordinator +_LOGGER = logging.getLogger(__name__) + class SwitchbotEntity(PassiveBluetoothCoordinatorEntity): """Generic entity encapsulating common features of Switchbot device.""" @@ -61,6 +64,30 @@ class SwitchbotEntity(PassiveBluetoothCoordinatorEntity): return {"last_run_success": self._last_run_success} +class SwitchbotSwitchedEntity(SwitchbotEntity, ToggleEntity): + """Base class for Switchbot entities that can be turned on and off.""" + + _device: Switchbot + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn device on.""" + _LOGGER.debug("Turn Switchbot device on %s", self._address) + + self._last_run_success = bool(await self._device.turn_on()) + if self._last_run_success: + self._attr_is_on = True + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn device off.""" + _LOGGER.debug("Turn Switchbot device off %s", self._address) + + self._last_run_success = bool(await self._device.turn_off()) + if self._last_run_success: + self._attr_is_on = False + self.async_write_ha_state() + + class SwitchbotSubscribeEntity(SwitchbotEntity): """Base class for Switchbot entities that use subscribe.""" diff --git a/homeassistant/components/switchbot/humidifier.py b/homeassistant/components/switchbot/humidifier.py new file mode 100644 index 00000000000..2bb71bacea1 --- /dev/null +++ b/homeassistant/components/switchbot/humidifier.py @@ -0,0 +1,72 @@ +"""Support for Switchbot humidifier.""" +from __future__ import annotations + +import logging + +import switchbot + +from homeassistant.components.humidifier import ( + MODE_AUTO, + MODE_NORMAL, + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_platform + +from .const import DOMAIN +from .coordinator import SwitchbotDataUpdateCoordinator +from .entity import SwitchbotSwitchedEntity + +PARALLEL_UPDATES = 0 +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: entity_platform.AddEntitiesCallback, +) -> None: + """Set up Switchbot based on a config entry.""" + coordinator: SwitchbotDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities([SwitchBotHumidifier(coordinator)]) + + +class SwitchBotHumidifier(SwitchbotSwitchedEntity, HumidifierEntity): + """Representation of a Switchbot humidifier.""" + + _attr_supported_features = HumidifierEntityFeature.MODES + _attr_device_class = HumidifierDeviceClass.HUMIDIFIER + _attr_available_modes = [MODE_NORMAL, MODE_AUTO] + _device: switchbot.SwitchbotHumidifier + _attr_min_humidity = 1 + + @property + def is_on(self) -> bool | None: + """Return true if device is on.""" + return self._device.is_on() + + @property + def mode(self) -> str: + """Return the humidity we try to reach.""" + return MODE_AUTO if self._device.is_auto() else MODE_NORMAL + + @property + def target_humidity(self) -> int | None: + """Return the humidity we try to reach.""" + return self._device.get_target_humidity() + + async def async_set_humidity(self, humidity: int) -> None: + """Set new target humidity.""" + self._last_run_success = bool(await self._device.set_level(humidity)) + self.async_write_ha_state() + + async def async_set_mode(self, mode: str) -> None: + """Set new target humidity.""" + if mode == MODE_AUTO: + self._last_run_success = await self._device.async_set_auto() + else: + self._last_run_success = await self._device.async_set_manual() + self.async_write_ha_state() diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 831d14d8459..8c5a488f6fe 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.22.0"], + "requirements": ["PySwitchbot==0.23.1"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index c4bbc2af1e0..67749ea0c5a 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -from typing import Any import switchbot @@ -15,7 +14,7 @@ from homeassistant.helpers.restore_state import RestoreEntity from .const import DOMAIN from .coordinator import SwitchbotDataUpdateCoordinator -from .entity import SwitchbotEntity +from .entity import SwitchbotSwitchedEntity # Initialize the logger _LOGGER = logging.getLogger(__name__) @@ -32,7 +31,7 @@ async def async_setup_entry( async_add_entities([SwitchBotSwitch(coordinator)]) -class SwitchBotSwitch(SwitchbotEntity, SwitchEntity, RestoreEntity): +class SwitchBotSwitch(SwitchbotSwitchedEntity, SwitchEntity, RestoreEntity): """Representation of a Switchbot switch.""" _attr_device_class = SwitchDeviceClass.SWITCH @@ -51,24 +50,6 @@ class SwitchBotSwitch(SwitchbotEntity, SwitchEntity, RestoreEntity): self._attr_is_on = last_state.state == STATE_ON self._last_run_success = last_state.attributes.get("last_run_success") - async def async_turn_on(self, **kwargs: Any) -> None: - """Turn device on.""" - _LOGGER.info("Turn Switchbot bot on %s", self._address) - - self._last_run_success = bool(await self._device.turn_on()) - if self._last_run_success: - self._attr_is_on = True - self.async_write_ha_state() - - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn device off.""" - _LOGGER.info("Turn Switchbot bot off %s", self._address) - - self._last_run_success = bool(await self._device.turn_off()) - if self._last_run_success: - self._attr_is_on = False - self.async_write_ha_state() - @property def assumed_state(self) -> bool: """Return true if unable to access real state of entity.""" diff --git a/requirements_all.txt b/requirements_all.txt index c0557c785ac..748e33a1563 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.22.0 +PySwitchbot==0.23.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3c0c48e9244..a99d3f2809e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.22.0 +PySwitchbot==0.23.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1