diff --git a/CODEOWNERS b/CODEOWNERS index 1abdfd637ff..04906e6bf88 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1436,8 +1436,8 @@ build.json @home-assistant/supervisor /tests/components/switchbee/ @jafar-atili /homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski /tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski -/homeassistant/components/switchbot_cloud/ @SeraphicRav @laurence-presland -/tests/components/switchbot_cloud/ @SeraphicRav @laurence-presland +/homeassistant/components/switchbot_cloud/ @SeraphicRav @laurence-presland @Gigatrappeur +/tests/components/switchbot_cloud/ @SeraphicRav @laurence-presland @Gigatrappeur /homeassistant/components/switcher_kis/ @thecode /tests/components/switcher_kis/ @thecode /homeassistant/components/switchmate/ @danielhiversen @qiz-li diff --git a/homeassistant/components/switchbot_cloud/__init__.py b/homeassistant/components/switchbot_cloud/__init__.py index c79ba41018f..39a179aaa21 100644 --- a/homeassistant/components/switchbot_cloud/__init__.py +++ b/homeassistant/components/switchbot_cloud/__init__.py @@ -15,7 +15,12 @@ from .const import DOMAIN from .coordinator import SwitchBotCoordinator _LOGGER = getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH] +PLATFORMS: list[Platform] = [ + Platform.CLIMATE, + Platform.SENSOR, + Platform.SWITCH, + Platform.VACUUM, +] @dataclass @@ -25,6 +30,7 @@ class SwitchbotDevices: climates: list[Remote] = field(default_factory=list) switches: list[Device | Remote] = field(default_factory=list) sensors: list[Device] = field(default_factory=list) + vacuums: list[Device] = field(default_factory=list) @dataclass @@ -81,6 +87,16 @@ def make_device_data( devices_data.sensors.append( prepare_device(hass, api, device, coordinators_by_id) ) + if isinstance(device, Device) and device.device_type in [ + "K10+", + "K10+ Pro", + "Robot Vacuum Cleaner S1", + "Robot Vacuum Cleaner S1 Plus", + ]: + devices_data.vacuums.append( + prepare_device(hass, api, device, coordinators_by_id) + ) + return devices_data diff --git a/homeassistant/components/switchbot_cloud/climate.py b/homeassistant/components/switchbot_cloud/climate.py index e04145933ae..cd60313f37a 100644 --- a/homeassistant/components/switchbot_cloud/climate.py +++ b/homeassistant/components/switchbot_cloud/climate.py @@ -95,7 +95,7 @@ class SwitchBotCloudAirConditioner(SwitchBotCloudEntity, ClimateEntity): new_fan_speed = _SWITCHBOT_FAN_MODES.get( fan_mode or self._attr_fan_mode, _DEFAULT_SWITCHBOT_FAN_MODE ) - await self.send_command( + await self.send_api_command( AirConditionerCommands.SET_ALL, parameters=f"{new_temperature},{new_mode},{new_fan_speed},on", ) diff --git a/homeassistant/components/switchbot_cloud/const.py b/homeassistant/components/switchbot_cloud/const.py index 66c84b63047..b849194537a 100644 --- a/homeassistant/components/switchbot_cloud/const.py +++ b/homeassistant/components/switchbot_cloud/const.py @@ -10,3 +10,8 @@ DEFAULT_SCAN_INTERVAL = timedelta(seconds=600) SENSOR_KIND_TEMPERATURE = "temperature" SENSOR_KIND_HUMIDITY = "humidity" SENSOR_KIND_BATTERY = "battery" + +VACUUM_FAN_SPEED_QUIET = "quiet" +VACUUM_FAN_SPEED_STANDARD = "standard" +VACUUM_FAN_SPEED_STRONG = "strong" +VACUUM_FAN_SPEED_MAX = "max" diff --git a/homeassistant/components/switchbot_cloud/entity.py b/homeassistant/components/switchbot_cloud/entity.py index 7bb00cda945..f77adb7b192 100644 --- a/homeassistant/components/switchbot_cloud/entity.py +++ b/homeassistant/components/switchbot_cloud/entity.py @@ -35,7 +35,7 @@ class SwitchBotCloudEntity(CoordinatorEntity[SwitchBotCoordinator]): model=device.device_type, ) - async def send_command( + async def send_api_command( self, command: Commands, command_type: str = "command", diff --git a/homeassistant/components/switchbot_cloud/manifest.json b/homeassistant/components/switchbot_cloud/manifest.json index 0bafdec9f68..eb08d2183b1 100644 --- a/homeassistant/components/switchbot_cloud/manifest.json +++ b/homeassistant/components/switchbot_cloud/manifest.json @@ -1,7 +1,7 @@ { "domain": "switchbot_cloud", "name": "SwitchBot Cloud", - "codeowners": ["@SeraphicRav", "@laurence-presland"], + "codeowners": ["@SeraphicRav", "@laurence-presland", "@Gigatrappeur"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/switchbot_cloud", "integration_type": "hub", diff --git a/homeassistant/components/switchbot_cloud/switch.py b/homeassistant/components/switchbot_cloud/switch.py index fbcd4430f6e..c30e60086fa 100644 --- a/homeassistant/components/switchbot_cloud/switch.py +++ b/homeassistant/components/switchbot_cloud/switch.py @@ -36,13 +36,13 @@ class SwitchBotCloudSwitch(SwitchBotCloudEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" - await self.send_command(CommonCommands.ON) + await self.send_api_command(CommonCommands.ON) self._attr_is_on = True self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - await self.send_command(CommonCommands.OFF) + await self.send_api_command(CommonCommands.OFF) self._attr_is_on = False self.async_write_ha_state() diff --git a/homeassistant/components/switchbot_cloud/vacuum.py b/homeassistant/components/switchbot_cloud/vacuum.py new file mode 100644 index 00000000000..f9236507037 --- /dev/null +++ b/homeassistant/components/switchbot_cloud/vacuum.py @@ -0,0 +1,127 @@ +"""Support for SwitchBot vacuum.""" + +from typing import Any + +from switchbot_api import Device, Remote, SwitchBotAPI, VacuumCommands + +from homeassistant.components.vacuum import ( + STATE_CLEANING, + STATE_DOCKED, + STATE_ERROR, + STATE_IDLE, + STATE_PAUSED, + STATE_RETURNING, + StateVacuumEntity, + VacuumEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import SwitchbotCloudData +from .const import ( + DOMAIN, + VACUUM_FAN_SPEED_MAX, + VACUUM_FAN_SPEED_QUIET, + VACUUM_FAN_SPEED_STANDARD, + VACUUM_FAN_SPEED_STRONG, +) +from .coordinator import SwitchBotCoordinator +from .entity import SwitchBotCloudEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up SwitchBot Cloud entry.""" + data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id] + async_add_entities( + _async_make_entity(data.api, device, coordinator) + for device, coordinator in data.devices.vacuums + ) + + +VACUUM_SWITCHBOT_STATE_TO_HA_STATE: dict[str, str] = { + "StandBy": STATE_IDLE, + "Clearing": STATE_CLEANING, + "Paused": STATE_PAUSED, + "GotoChargeBase": STATE_RETURNING, + "Charging": STATE_DOCKED, + "ChargeDone": STATE_DOCKED, + "Dormant": STATE_IDLE, + "InTrouble": STATE_ERROR, + "InRemoteControl": STATE_CLEANING, + "InDustCollecting": STATE_DOCKED, +} + +VACUUM_FAN_SPEED_TO_SWITCHBOT_FAN_SPEED: dict[str, str] = { + VACUUM_FAN_SPEED_QUIET: "0", + VACUUM_FAN_SPEED_STANDARD: "1", + VACUUM_FAN_SPEED_STRONG: "2", + VACUUM_FAN_SPEED_MAX: "3", +} + + +# https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#robot-vacuum-cleaner-s1-plus-1 +class SwitchBotCloudVacuum(SwitchBotCloudEntity, StateVacuumEntity): + """Representation of a SwitchBot vacuum.""" + + _attr_supported_features: VacuumEntityFeature = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.START + | VacuumEntityFeature.STATE + ) + + _attr_name = None + _attr_fan_speed_list: list[str] = list( + VACUUM_FAN_SPEED_TO_SWITCHBOT_FAN_SPEED.keys() + ) + + async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: + """Set fan speed.""" + self._attr_fan_speed = fan_speed + if fan_speed in VACUUM_FAN_SPEED_TO_SWITCHBOT_FAN_SPEED: + await self.send_api_command( + VacuumCommands.POW_LEVEL, + parameters=VACUUM_FAN_SPEED_TO_SWITCHBOT_FAN_SPEED[fan_speed], + ) + self.async_write_ha_state() + + async def async_pause(self) -> None: + """Pause the cleaning task.""" + await self.send_api_command(VacuumCommands.STOP) + + async def async_return_to_base(self, **kwargs: Any) -> None: + """Set the vacuum cleaner to return to the dock.""" + await self.send_api_command(VacuumCommands.DOCK) + + async def async_start(self) -> None: + """Start or resume the cleaning task.""" + await self.send_api_command(VacuumCommands.START) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + if not self.coordinator.data: + return + + self._attr_battery_level = self.coordinator.data.get("battery") + self._attr_available = self.coordinator.data.get("onlineStatus") == "online" + + switchbot_state = str(self.coordinator.data.get("workingStatus")) + self._attr_state = VACUUM_SWITCHBOT_STATE_TO_HA_STATE.get(switchbot_state) + + self.async_write_ha_state() + + +@callback +def _async_make_entity( + api: SwitchBotAPI, device: Device | Remote, coordinator: SwitchBotCoordinator +) -> SwitchBotCloudVacuum: + """Make a SwitchBotCloudVacuum.""" + return SwitchBotCloudVacuum(api, device, coordinator)