Add MVP humidifier support to switchbot (#83696)

* Add MVP humidifier support to switchbot

changelog: https://github.com/Danielhiversen/pySwitchbot/compare/0.22.0...0.23.0

* Update homeassistant/components/switchbot/config_flow.py

* bump

* coveragerc

* Revert "coveragerc"

This reverts commit eb642f6543e5f37511d65570c359670e1ee7be1f.

* fix dirty branch
This commit is contained in:
J. Nick Koston 2022-12-10 08:56:57 -10:00 committed by GitHub
parent 5c79dae4c0
commit 642cefb035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 112 additions and 27 deletions

View File

@ -1245,6 +1245,7 @@ omit =
homeassistant/components/switchbot/coordinator.py homeassistant/components/switchbot/coordinator.py
homeassistant/components/switchbot/cover.py homeassistant/components/switchbot/cover.py
homeassistant/components/switchbot/entity.py homeassistant/components/switchbot/entity.py
homeassistant/components/switchbot/humidifier.py
homeassistant/components/switchbot/light.py homeassistant/components/switchbot/light.py
homeassistant/components/switchbot/sensor.py homeassistant/components/switchbot/sensor.py
homeassistant/components/switchbot/switch.py homeassistant/components/switchbot/switch.py

View File

@ -42,6 +42,7 @@ PLATFORMS_BY_TYPE = {
SupportedModels.HYGROMETER.value: [Platform.SENSOR], SupportedModels.HYGROMETER.value: [Platform.SENSOR],
SupportedModels.CONTACT.value: [Platform.BINARY_SENSOR, Platform.SENSOR], SupportedModels.CONTACT.value: [Platform.BINARY_SENSOR, Platform.SENSOR],
SupportedModels.MOTION.value: [Platform.BINARY_SENSOR, Platform.SENSOR], SupportedModels.MOTION.value: [Platform.BINARY_SENSOR, Platform.SENSOR],
SupportedModels.HUMIDIFIER.value: [Platform.HUMIDIFIER, Platform.SENSOR],
} }
CLASS_BY_DEVICE = { CLASS_BY_DEVICE = {
SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight, SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight,
@ -50,6 +51,7 @@ CLASS_BY_DEVICE = {
SupportedModels.PLUG.value: switchbot.SwitchbotPlugMini, SupportedModels.PLUG.value: switchbot.SwitchbotPlugMini,
SupportedModels.BULB.value: switchbot.SwitchbotBulb, SupportedModels.BULB.value: switchbot.SwitchbotBulb,
SupportedModels.LIGHT_STRIP.value: switchbot.SwitchbotLightStrip, SupportedModels.LIGHT_STRIP.value: switchbot.SwitchbotLightStrip,
SupportedModels.HUMIDIFIER.value: switchbot.SwitchbotHumidifier,
} }

View File

@ -66,7 +66,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
self, discovery_info: BluetoothServiceInfoBleak self, discovery_info: BluetoothServiceInfoBleak
) -> FlowResult: ) -> FlowResult:
"""Handle the bluetooth discovery step.""" """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)) await self.async_set_unique_id(format_unique_id(discovery_info.address))
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
parsed = parse_advertisement_data( parsed = parse_advertisement_data(

View File

@ -23,6 +23,7 @@ class SupportedModels(StrEnum):
CONTACT = "contact" CONTACT = "contact"
PLUG = "plug" PLUG = "plug"
MOTION = "motion" MOTION = "motion"
HUMIDIFIER = "humidifier"
CONNECTABLE_SUPPORTED_MODEL_TYPES = { CONNECTABLE_SUPPORTED_MODEL_TYPES = {
@ -32,6 +33,7 @@ CONNECTABLE_SUPPORTED_MODEL_TYPES = {
SwitchbotModel.COLOR_BULB: SupportedModels.BULB, SwitchbotModel.COLOR_BULB: SupportedModels.BULB,
SwitchbotModel.LIGHT_STRIP: SupportedModels.LIGHT_STRIP, SwitchbotModel.LIGHT_STRIP: SupportedModels.LIGHT_STRIP,
SwitchbotModel.CEILING_LIGHT: SupportedModels.CEILING_LIGHT, SwitchbotModel.CEILING_LIGHT: SupportedModels.CEILING_LIGHT,
SwitchbotModel.HUMIDIFIER: SupportedModels.HUMIDIFIER,
} }
NON_CONNECTABLE_SUPPORTED_MODEL_TYPES = { NON_CONNECTABLE_SUPPORTED_MODEL_TYPES = {

View File

@ -3,9 +3,10 @@ from __future__ import annotations
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Mapping from collections.abc import Mapping
import logging
from typing import Any from typing import Any
from switchbot import SwitchbotDevice from switchbot import Switchbot, SwitchbotDevice
from homeassistant.components.bluetooth.passive_update_coordinator import ( from homeassistant.components.bluetooth.passive_update_coordinator import (
PassiveBluetoothCoordinatorEntity, PassiveBluetoothCoordinatorEntity,
@ -13,11 +14,13 @@ from homeassistant.components.bluetooth.passive_update_coordinator import (
from homeassistant.const import ATTR_CONNECTIONS from homeassistant.const import ATTR_CONNECTIONS
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr 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 .const import MANUFACTURER
from .coordinator import SwitchbotDataUpdateCoordinator from .coordinator import SwitchbotDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
class SwitchbotEntity(PassiveBluetoothCoordinatorEntity): class SwitchbotEntity(PassiveBluetoothCoordinatorEntity):
"""Generic entity encapsulating common features of Switchbot device.""" """Generic entity encapsulating common features of Switchbot device."""
@ -61,6 +64,30 @@ class SwitchbotEntity(PassiveBluetoothCoordinatorEntity):
return {"last_run_success": self._last_run_success} 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): class SwitchbotSubscribeEntity(SwitchbotEntity):
"""Base class for Switchbot entities that use subscribe.""" """Base class for Switchbot entities that use subscribe."""

View File

@ -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()

View File

@ -2,7 +2,7 @@
"domain": "switchbot", "domain": "switchbot",
"name": "SwitchBot", "name": "SwitchBot",
"documentation": "https://www.home-assistant.io/integrations/switchbot", "documentation": "https://www.home-assistant.io/integrations/switchbot",
"requirements": ["PySwitchbot==0.22.0"], "requirements": ["PySwitchbot==0.23.1"],
"config_flow": true, "config_flow": true,
"dependencies": ["bluetooth"], "dependencies": ["bluetooth"],
"codeowners": [ "codeowners": [

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any
import switchbot import switchbot
@ -15,7 +14,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
from .const import DOMAIN from .const import DOMAIN
from .coordinator import SwitchbotDataUpdateCoordinator from .coordinator import SwitchbotDataUpdateCoordinator
from .entity import SwitchbotEntity from .entity import SwitchbotSwitchedEntity
# Initialize the logger # Initialize the logger
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -32,7 +31,7 @@ async def async_setup_entry(
async_add_entities([SwitchBotSwitch(coordinator)]) async_add_entities([SwitchBotSwitch(coordinator)])
class SwitchBotSwitch(SwitchbotEntity, SwitchEntity, RestoreEntity): class SwitchBotSwitch(SwitchbotSwitchedEntity, SwitchEntity, RestoreEntity):
"""Representation of a Switchbot switch.""" """Representation of a Switchbot switch."""
_attr_device_class = SwitchDeviceClass.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._attr_is_on = last_state.state == STATE_ON
self._last_run_success = last_state.attributes.get("last_run_success") 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 @property
def assumed_state(self) -> bool: def assumed_state(self) -> bool:
"""Return true if unable to access real state of entity.""" """Return true if unable to access real state of entity."""

View File

@ -37,7 +37,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1 PySocks==1.7.1
# homeassistant.components.switchbot # homeassistant.components.switchbot
PySwitchbot==0.22.0 PySwitchbot==0.23.1
# homeassistant.components.transport_nsw # homeassistant.components.transport_nsw
PyTransportNSW==0.1.1 PyTransportNSW==0.1.1

View File

@ -33,7 +33,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1 PySocks==1.7.1
# homeassistant.components.switchbot # homeassistant.components.switchbot
PySwitchbot==0.22.0 PySwitchbot==0.23.1
# homeassistant.components.transport_nsw # homeassistant.components.transport_nsw
PyTransportNSW==0.1.1 PyTransportNSW==0.1.1