import enum from typing import Collection from uuid import UUID from bleak.backends.device import BLEDevice from typing_extensions import override from platypush.context import get_or_create_event_loop from platypush.entities import EnumSwitchEntityManager from platypush.entities.switches import EnumSwitch from platypush.plugins import action from platypush.plugins.bluetooth.ble import BluetoothBlePlugin, UUIDType class Command(enum.Enum): """ Supported commands. """ PRESS = b'\x57\x01\x00' ON = b'\x57\x01\x01' OFF = b'\x57\x01\x02' # pylint: disable=too-many-ancestors class SwitchbotBluetoothPlugin(BluetoothBlePlugin, EnumSwitchEntityManager): """ Plugin to interact with a Switchbot (https://www.switch-bot.com/) device and programmatically control switches over a Bluetooth interface. Note that this plugin currently only supports Switchbot "bot" devices (mechanical switch pressers). For support for other devices, you may want the :class:`platypush.plugins.switchbot.SwitchbotPlugin` integration (which requires a Switchbot hub). Requires: * **bleak** (``pip install bleak``) """ # Map of service names -> UUID prefixes exposed by SwitchBot devices _uuid_prefixes = { 'tx': '002', 'rx': '003', 'service': 'd00', } # Static list of Bluetooth service UUIDs commonly exposed by SwitchBot # devices. _uuids = { service: UUID(f'cba20{prefix}-224d-11e6-9fb8-0002a5d5c51b') for service, prefix in _uuid_prefixes.items() } def __init__(self, *args, **kwargs): super().__init__(*args, service_uuids=self._uuids.values(), **kwargs) async def _run( self, device: str, command: Command, service_uuid: UUIDType = _uuids['tx'], ): await self._write(device, command.value, service_uuid) @action def press(self, device: str): """ Send a press button command to a device :param device: Device name or address """ loop = get_or_create_event_loop() return loop.run_until_complete(self._run(device, Command.PRESS)) @action def toggle(self, device, **_): return self.press(device) @action def on(self, device: str, **_): """ Send a press-on button command to a device :param device: Device name or address """ loop = get_or_create_event_loop() return loop.run_until_complete(self._run(device, Command.ON)) @action def off(self, device: str, **_): """ Send a press-off button command to a device :param device: Device name or address """ loop = get_or_create_event_loop() return loop.run_until_complete(self._run(device, Command.OFF)) @override @action def set_value(self, device: str, *_, data: str, **__): """ Entity-compatible ``set_value`` method to send a command to a device. :param device: Device name or address :param data: Command to send. Possible values are: - ``on``: Press the button and remain in the pressed state. - ``off``: Release a previously pressed button. - ``press``: Press and release the button. """ if data == 'on': self.on(device) if data == 'off': self.off(device) if data == 'press': self.press(device) self.logger.warning('Unknown command for SwitchBot "%s": "%s"', device, data) @override def transform_entities( self, entities: Collection[BLEDevice] ) -> Collection[EnumSwitch]: devices = super().transform_entities(entities) return [ EnumSwitch( id=dev.id, name=dev.name, value=None, values=['on', 'off', 'press'], is_write_only=True, ) for dev in devices ] # vim:sw=4:ts=4:et: