diff --git a/homeassistant/components/switchbot_cloud/__init__.py b/homeassistant/components/switchbot_cloud/__init__.py index f14547326ba..d7812158260 100644 --- a/homeassistant/components/switchbot_cloud/__init__.py +++ b/homeassistant/components/switchbot_cloud/__init__.py @@ -16,6 +16,7 @@ from .coordinator import SwitchBotCoordinator _LOGGER = getLogger(__name__) PLATFORMS: list[Platform] = [ + Platform.BUTTON, Platform.CLIMATE, Platform.LOCK, Platform.SENSOR, @@ -28,6 +29,7 @@ PLATFORMS: list[Platform] = [ class SwitchbotDevices: """Switchbot devices data.""" + buttons: list[Device] = field(default_factory=list) climates: list[Remote] = field(default_factory=list) switches: list[Device | Remote] = field(default_factory=list) sensors: list[Device] = field(default_factory=list) @@ -136,6 +138,16 @@ async def make_device_data( ) devices_data.locks.append((device, coordinator)) + if isinstance(device, Device) and device.device_type in ["Bot"]: + coordinator = await coordinator_for_device( + hass, api, device, coordinators_by_id + ) + if coordinator.data is not None: + if coordinator.data.get("deviceMode") == "pressMode": + devices_data.buttons.append((device, coordinator)) + else: + devices_data.switches.append((device, coordinator)) + async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: """Set up SwitchBot via API from a config entry.""" diff --git a/homeassistant/components/switchbot_cloud/button.py b/homeassistant/components/switchbot_cloud/button.py new file mode 100644 index 00000000000..a6eb1a134a5 --- /dev/null +++ b/homeassistant/components/switchbot_cloud/button.py @@ -0,0 +1,41 @@ +"""Support for the Switchbot Bot as a Button.""" + +from typing import Any + +from switchbot_api import BotCommands + +from homeassistant.components.button import ButtonEntity +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 +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( + SwitchBotCloudBot(data.api, device, coordinator) + for device, coordinator in data.devices.buttons + ) + + +class SwitchBotCloudBot(SwitchBotCloudEntity, ButtonEntity): + """Representation of a SwitchBot Bot.""" + + _attr_name = None + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + + async def async_press(self, **kwargs: Any) -> None: + """Bot press command.""" + await self.send_api_command(BotCommands.PRESS) diff --git a/homeassistant/components/switchbot_cloud/switch.py b/homeassistant/components/switchbot_cloud/switch.py index 0781c91bc35..22d033625f9 100644 --- a/homeassistant/components/switchbot_cloud/switch.py +++ b/homeassistant/components/switchbot_cloud/switch.py @@ -90,4 +90,6 @@ def _async_make_entity( "Relay Switch 1", ]: return SwitchBotCloudRelaySwitchSwitch(api, device, coordinator) + if "Bot" in device.device_type: + return SwitchBotCloudSwitch(api, device, coordinator) raise NotImplementedError(f"Unsupported device type: {device.device_type}") diff --git a/tests/components/switchbot_cloud/test_button.py b/tests/components/switchbot_cloud/test_button.py new file mode 100644 index 00000000000..df5b7569100 --- /dev/null +++ b/tests/components/switchbot_cloud/test_button.py @@ -0,0 +1,71 @@ +"""Test for the switchbot_cloud bot as a button.""" + +from unittest.mock import patch + +from switchbot_api import BotCommands, Device + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.switchbot_cloud import SwitchBotAPI +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + +from . import configure_integration + + +async def test_pressmode_bot( + hass: HomeAssistant, mock_list_devices, mock_get_status +) -> None: + """Test press.""" + mock_list_devices.return_value = [ + Device( + deviceId="bot-id-1", + deviceName="bot-1", + deviceType="Bot", + hubDeviceId="test-hub-id", + ), + ] + + mock_get_status.return_value = {"deviceMode": "pressMode"} + + entry = configure_integration(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.LOADED + + entity_id = "button.bot_1" + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + with patch.object(SwitchBotAPI, "send_command") as mock_send_command: + await hass.services.async_call( + BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + mock_send_command.assert_called_once_with( + "bot-id-1", BotCommands.PRESS, "command", "default" + ) + + assert hass.states.get(entity_id).state != STATE_UNKNOWN + + +async def test_switchmode_bot_no_button_entity( + hass: HomeAssistant, mock_list_devices, mock_get_status +) -> None: + """Test a switchMode bot isn't added as a button.""" + mock_list_devices.return_value = [ + Device( + deviceId="bot-id-1", + deviceName="bot-1", + deviceType="Bot", + hubDeviceId="test-hub-id", + ), + ] + + mock_get_status.return_value = {"deviceMode": "switchMode"} + + entry = configure_integration(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.LOADED + assert not hass.states.async_entity_ids(BUTTON_DOMAIN) diff --git a/tests/components/switchbot_cloud/test_switch.py b/tests/components/switchbot_cloud/test_switch.py index d4ef2c84549..b1c6fb81b96 100644 --- a/tests/components/switchbot_cloud/test_switch.py +++ b/tests/components/switchbot_cloud/test_switch.py @@ -1,4 +1,4 @@ -"""Test for the switchbot_cloud relay switch.""" +"""Test for the switchbot_cloud relay switch & bot.""" from unittest.mock import patch @@ -54,3 +54,63 @@ async def test_relay_switch( SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert hass.states.get(entity_id).state == STATE_OFF + + +async def test_switchmode_bot( + hass: HomeAssistant, mock_list_devices, mock_get_status +) -> None: + """Test turn on and turn off.""" + mock_list_devices.return_value = [ + Device( + deviceId="bot-id-1", + deviceName="bot-1", + deviceType="Bot", + hubDeviceId="test-hub-id", + ), + ] + + mock_get_status.return_value = {"deviceMode": "switchMode", "power": "off"} + + entry = configure_integration(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.LOADED + + entity_id = "switch.bot_1" + assert hass.states.get(entity_id).state == STATE_OFF + + with patch.object(SwitchBotAPI, "send_command"): + await hass.services.async_call( + SWITCH_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert hass.states.get(entity_id).state == STATE_ON + + with patch.object(SwitchBotAPI, "send_command"): + await hass.services.async_call( + SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert hass.states.get(entity_id).state == STATE_OFF + + +async def test_pressmode_bot_no_switch_entity( + hass: HomeAssistant, mock_list_devices, mock_get_status +) -> None: + """Test a pressMode bot isn't added as a switch.""" + mock_list_devices.return_value = [ + Device( + deviceId="bot-id-1", + deviceName="bot-1", + deviceType="Bot", + hubDeviceId="test-hub-id", + ), + ] + + mock_get_status.return_value = {"deviceMode": "pressMode"} + + entry = configure_integration(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.LOADED + assert not hass.states.async_entity_ids(SWITCH_DOMAIN)