Compare commits

...

4 Commits

Author SHA1 Message Date
Fabio Manganiello 45664be44b
Removed deprecated `backend.bluetooth.scanner`.
Scan capabilities are now implemented on the `bluetooth` plugin itself.
2023-02-13 23:13:51 +01:00
Fabio Manganiello 471bc1fd3d
Updated dist files 2023-02-13 23:13:32 +01:00
Fabio Manganiello a3aa186ddf
- Added support for `scan_pause`/`scan_resume` on `bluetooth` integration.
- Added `BluetoothDevice` as its own entity type.
2023-02-13 23:12:25 +01:00
Fabio Manganiello 1d0be5c929
- Simplified prototype for `EntityManager.set`
- Added small documentation/annotations notes to the `Plugin` module.

- Small LINT fixes
2023-02-11 21:35:00 +01:00
25 changed files with 219 additions and 188 deletions

View File

@ -1,109 +0,0 @@
import time
from threading import Thread, RLock
from typing import Dict, Optional, List
from platypush.backend.sensor import SensorBackend
from platypush.context import get_plugin
from platypush.message.event.bluetooth import BluetoothDeviceFoundEvent, BluetoothDeviceLostEvent
class BluetoothScannerBackend(SensorBackend):
"""
This backend periodically scans for available bluetooth devices and returns events when a devices enter or exits
the range.
Triggers:
* :class:`platypush.message.event.bluetooth.BluetoothDeviceFoundEvent` when a new bluetooth device is found.
* :class:`platypush.message.event.bluetooth.BluetoothDeviceLostEvent` when a bluetooth device is lost.
Requires:
* The :class:`platypush.plugins.bluetooth.BluetoothPlugin` plugin working.
"""
def __init__(self, device_id: Optional[int] = None, scan_duration: int = 10,
track_devices: Optional[List[str]] = None, **kwargs):
"""
:param device_id: Bluetooth adapter ID to use (default configured on the ``bluetooth`` plugin if None).
:param scan_duration: How long the scan should run (default: 10 seconds).
:param track_devices: List of addresses of devices to actively track, even if they aren't discoverable.
"""
super().__init__(plugin='bluetooth', plugin_args={
'device_id': device_id,
'duration': scan_duration,
}, **kwargs)
self._last_seen_devices = {}
self._tracking_thread: Optional[Thread] = None
self._bt_lock = RLock()
self.track_devices = set(track_devices or [])
self.scan_duration = scan_duration
def _add_last_seen_device(self, dev):
addr = dev.pop('addr')
if addr not in self._last_seen_devices:
self.bus.post(BluetoothDeviceFoundEvent(address=addr, **dev))
self._last_seen_devices[addr] = {'addr': addr, **dev}
def _remove_last_seen_device(self, addr: str):
dev = self._last_seen_devices.get(addr)
if not dev:
return
self.bus.post(BluetoothDeviceLostEvent(address=addr, **dev))
del self._last_seen_devices[addr]
def _addr_tracker(self, addr):
with self._bt_lock:
name = get_plugin('bluetooth').lookup_name(addr, timeout=self.scan_duration).name
if name is None:
self._remove_last_seen_device(addr)
else:
self._add_last_seen_device({'addr': addr, 'name': name})
def _bt_tracker(self):
self.logger.info('Starting Bluetooth tracker')
while not self.should_stop():
trackers = []
for addr in self.track_devices:
tracker = Thread(target=self._addr_tracker, args=(addr,))
tracker.start()
trackers.append(tracker)
for tracker in trackers:
tracker.join(timeout=self.scan_duration)
time.sleep(self.scan_duration)
self.logger.info('Bluetooth tracker stopped')
def get_measurement(self):
with self._bt_lock:
return super().get_measurement()
def process_data( # lgtm [py/inheritance/signature-mismatch]
self, data: Dict[str, dict], new_data: Optional[Dict[str, dict]] = None, **_
):
for addr, dev in data.items():
self._add_last_seen_device(dev)
for addr, dev in self._last_seen_devices.copy().items():
if addr not in data and addr not in self.track_devices:
self._remove_last_seen_device(addr)
def run(self):
self._tracking_thread = Thread(target=self._bt_tracker)
self._tracking_thread.start()
super().run()
def on_stop(self):
super().on_stop()
if self._tracking_thread and self._tracking_thread.is_alive():
self.logger.info('Waiting for the Bluetooth tracking thread to stop')
self._tracking_thread.join(timeout=self.scan_duration)
# vim:sw=4:ts=4:et:

View File

@ -1,10 +0,0 @@
manifest:
events:
platypush.message.event.bluetooth.BluetoothDeviceFoundEvent: when a new bluetooth
device is found.
platypush.message.event.bluetooth.BluetoothDeviceLostEvent: when a bluetooth device
is lost.
install:
pip: []
package: platypush.backend.bluetooth.scanner
type: backend

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" type="module" src="/static/js/chunk-vendors.95bedba1.js"></script><script defer="defer" type="module" src="/static/js/app.9397ac28.js"></script><link href="/static/css/chunk-vendors.0fcd36f0.css" rel="stylesheet"><link href="/static/css/app.d7cb662c.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.79dede0c.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.22d80610.js" nomodule></script></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" type="module" src="/static/js/chunk-vendors.95bedba1.js"></script><script defer="defer" type="module" src="/static/js/app.aa9a5927.js"></script><link href="/static/css/chunk-vendors.0fcd36f0.css" rel="stylesheet"><link href="/static/css/app.d7cb662c.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.79dede0c.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.6a27238d.js" nomodule></script></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,6 +31,14 @@
}
},
"bluetooth_device": {
"name": "Device",
"name_plural": "Devices",
"icon": {
"class": "fab fa-bluetooth-b"
}
},
"device": {
"name": "Device",
"name_plural": "Devices",

View File

@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Any, Optional
from typing import Any
from typing_extensions import override
from . import EntityManager
@ -11,15 +11,13 @@ class WriteableEntityManager(EntityManager, ABC):
"""
@abstractmethod
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
def set(self, entity: str, value: Any, **kwargs):
"""
Set the value of an entity.
:param entity: The entity to set the value for. It's usually the ID of
the entity provided by the plugin.
:param value: The value to set the entity to.
:param attribute: The name of the attribute to set for the entity, if
required by the integration.
"""
raise NotImplementedError()
@ -45,7 +43,7 @@ class SwitchEntityManager(WriteableEntityManager, ABC):
raise NotImplementedError()
@override
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
def set(self, entity: str, value: Any, **kwargs):
method = self.on if value else self.off
return method(entity, **kwargs)

View File

@ -0,0 +1,24 @@
from sqlalchemy import Column, Integer, Boolean, ForeignKey
from platypush.common.db import Base
from .devices import Device
if 'bluetooth_device' not in Base.metadata:
class BluetoothDevice(Device):
"""
Entity that represents a Bluetooth device.
"""
__tablename__ = 'bluetooth_device'
id = Column(
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
)
connected = Column(Boolean, default=False)
__mapper_args__ = {
'polymorphic_identity': __tablename__,
}

View File

@ -5,7 +5,31 @@ from platypush.message.event import Event
class BluetoothEvent(Event):
"""
Base class for Bluetooth Low-Energy device events.
Base class for Bluetooth events.
"""
class BluetoothScanPausedEvent(BluetoothEvent):
"""
Event triggered when the Bluetooth scan is paused.
"""
def __init__(self, *args, duration: Optional[float] = None, **kwargs):
super().__init__(*args, duration=duration, **kwargs)
class BluetoothScanResumedEvent(BluetoothEvent):
"""
Event triggered when the Bluetooth scan is resumed.
"""
def __init__(self, *args, duration: Optional[float] = None, **kwargs):
super().__init__(*args, duration=duration, **kwargs)
class BluetoothDeviceEvent(BluetoothEvent):
"""
Base class for Bluetooth device events.
"""
def __init__(
@ -17,7 +41,7 @@ class BluetoothEvent(Event):
trusted: bool,
blocked: bool,
name: Optional[str] = None,
service_uuids: Optional[Collection[str]] = None,
characteristics: Optional[Collection[str]] = None,
**kwargs
):
"""
@ -27,7 +51,8 @@ class BluetoothEvent(Event):
:param trusted: Whether the device is trusted.
:param blocked: Whether the device is blocked.
:param name: The name of the device.
:param service_uuids: The service UUIDs of the device.
:param characteristics: The UUIDs of the characteristics exposed by the
device.
"""
super().__init__(
*args,
@ -37,66 +62,66 @@ class BluetoothEvent(Event):
paired=paired,
blocked=blocked,
trusted=trusted,
service_uuids=service_uuids or [],
characteristics=characteristics or [],
**kwargs
)
class BluetoothDeviceFoundEvent(BluetoothEvent):
class BluetoothDeviceFoundEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is discovered during a scan.
"""
class BluetoothDeviceLostEvent(BluetoothEvent):
class BluetoothDeviceLostEvent(BluetoothDeviceEvent):
"""
Event triggered when a previously discovered Bluetooth device is lost.
"""
class BluetoothDeviceConnectedEvent(BluetoothEvent):
class BluetoothDeviceConnectedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is connected.
"""
class BluetoothDeviceDisconnectedEvent(BluetoothEvent):
class BluetoothDeviceDisconnectedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is disconnected.
"""
class BluetoothDevicePairedEvent(BluetoothEvent):
class BluetoothDevicePairedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is paired.
"""
class BluetoothDeviceUnpairedEvent(BluetoothEvent):
class BluetoothDeviceUnpairedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is unpaired.
"""
class BluetoothDeviceBlockedEvent(BluetoothEvent):
class BluetoothDeviceBlockedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is blocked.
"""
class BluetoothDeviceUnblockedEvent(BluetoothEvent):
class BluetoothDeviceUnblockedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is unblocked.
"""
class BluetoothDeviceTrustedEvent(BluetoothEvent):
class BluetoothDeviceTrustedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is trusted.
"""
class BluetoothDeviceUntrustedEvent(BluetoothEvent):
class BluetoothDeviceUntrustedEvent(BluetoothDeviceEvent):
"""
Event triggered when a Bluetooth device is untrusted.
"""

View File

@ -5,6 +5,7 @@ import threading
from abc import ABC, abstractmethod
from functools import wraps
from typing import Any, Callable, Optional
from typing_extensions import override
from platypush.bus import Bus
from platypush.common import ExtensionWithManifest
@ -97,21 +98,33 @@ class RunnablePlugin(Plugin):
self._thread: Optional[threading.Thread] = None
def main(self):
"""
Implementation of the main loop of the plugin.
"""
raise NotImplementedError()
def should_stop(self):
def should_stop(self) -> bool:
return self._should_stop.is_set()
def wait_stop(self, timeout=None):
"""
Wait until a stop event is received.
"""
return self._should_stop.wait(timeout=timeout)
def start(self):
"""
Start the plugin.
"""
self._thread = threading.Thread(
target=self._runner, name=self.__class__.__name__
)
self._thread.start()
def stop(self):
"""
Stop the plugin.
"""
self._should_stop.set()
if self._thread and self._thread.is_alive():
self.logger.info('Waiting for the plugin to stop')
@ -129,6 +142,9 @@ class RunnablePlugin(Plugin):
self.logger.info('%s stopped', self.__class__.__name__)
def _runner(self):
"""
Implementation of the runner thread.
"""
self.logger.info('Starting %s', self.__class__.__name__)
while not self.should_stop():
@ -151,7 +167,7 @@ class AsyncRunnablePlugin(RunnablePlugin, ABC):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._loop: Optional[asyncio.AbstractEventLoop] = None
self._loop: Optional[asyncio.AbstractEventLoop] = asyncio.new_event_loop()
self._task: Optional[asyncio.Task] = None
@property
@ -185,7 +201,9 @@ class AsyncRunnablePlugin(RunnablePlugin, ABC):
raise e
def _run_listener(self):
self._loop = asyncio.new_event_loop()
"""
Initialize an event loop and run the listener as a task.
"""
asyncio.set_event_loop(self._loop)
self._task = self._loop.create_task(self._listen())
@ -198,6 +216,7 @@ class AsyncRunnablePlugin(RunnablePlugin, ABC):
self._task.cancel()
@override
def main(self):
if self.should_stop():
self.logger.info('The plugin is already scheduled to stop')
@ -214,6 +233,7 @@ class AsyncRunnablePlugin(RunnablePlugin, ABC):
else:
self.wait_stop()
@override
def stop(self):
if self._loop and self._loop.is_running():
self._loop.call_soon_threadsafe(self._loop.stop)

View File

@ -1,6 +1,7 @@
import base64
from asyncio import Event, ensure_future
from contextlib import asynccontextmanager
from threading import RLock
from threading import RLock, Timer
from typing import AsyncGenerator, Collection, List, Optional, Dict, Type, Union
from uuid import UUID
@ -10,7 +11,7 @@ from typing_extensions import override
from platypush.context import get_bus, get_or_create_event_loop
from platypush.entities import Entity, EntityManager
from platypush.entities.devices import Device
from platypush.entities.bluetooth import BluetoothDevice
from platypush.message.event.bluetooth.ble import (
BluetoothDeviceBlockedEvent,
BluetoothDeviceConnectedEvent,
@ -22,7 +23,9 @@ from platypush.message.event.bluetooth.ble import (
BluetoothDeviceUnblockedEvent,
BluetoothDeviceUnpairedEvent,
BluetoothDeviceUntrustedEvent,
BluetoothEvent,
BluetoothDeviceEvent,
BluetoothScanPausedEvent,
BluetoothScanResumedEvent,
)
from platypush.plugins import AsyncRunnablePlugin, action
@ -52,7 +55,8 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
interface: Optional[str] = None,
connect_timeout: float = _default_connect_timeout,
device_names: Optional[Dict[str, str]] = None,
service_uuids: Optional[Collection[UUIDType]] = None,
characteristics: Optional[Collection[UUIDType]] = None,
scan_paused_on_start: bool = False,
**kwargs,
):
"""
@ -60,7 +64,8 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
on Linux). Default: first available interface.
:param connect_timeout: Timeout in seconds for the connection to a
Bluetooth device. Default: 5 seconds.
:param service_uuids: List of service UUIDs to discover. Default: all.
:param characteristics: List of service/characteristic UUIDs to
discover. Default: all.
:param device_names: Bluetooth address -> device name mapping. If not
specified, the device's advertised name will be used, or its
Bluetooth address. Example:
@ -73,13 +78,19 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
"00:11:22:33:44:57": "Button"
}
:param scan_paused_on_start: If ``True``, the plugin will not the
scanning thread until :meth:`.scan_resume` is called (default:
``False``).
"""
super().__init__(**kwargs)
self._interface = interface
self._connect_timeout = connect_timeout
self._service_uuids = service_uuids
self._characteristics = characteristics
self._scan_lock = RLock()
self._scan_enabled = Event()
self._scan_controller_timer: Optional[Timer] = None
self._connections: Dict[str, BleakClient] = {}
self._devices: Dict[str, BLEDevice] = {}
self._device_name_by_addr = device_names or {}
@ -87,6 +98,9 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
name: addr for addr, name in self._device_name_by_addr.items()
}
if not scan_paused_on_start:
self._scan_enabled.set()
async def _get_device(self, device: str) -> BLEDevice:
"""
Utility method to get a device by name or address.
@ -113,7 +127,7 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
)
def _post_event(
self, event_type: Type[BluetoothEvent], device: BLEDevice, **kwargs
self, event_type: Type[BluetoothDeviceEvent], device: BLEDevice, **kwargs
):
props = device.details.get('props', {})
get_bus().post(
@ -124,13 +138,13 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
paired=props.get('Paired', False),
blocked=props.get('Blocked', False),
trusted=props.get('Trusted', False),
service_uuids=device.metadata.get('uuids', []),
characteristics=device.metadata.get('uuids', []),
**kwargs,
)
)
def _on_device_event(self, device: BLEDevice, _):
event_types: List[Type[BluetoothEvent]] = []
event_types: List[Type[BluetoothDeviceEvent]] = []
existing_device = self._devices.get(device.address)
if existing_device:
@ -168,6 +182,9 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
event_types.append(BluetoothDeviceFoundEvent)
self._devices[device.address] = device
if device.name:
self._device_name_by_addr[device.address] = device.name
self._device_addr_by_name[device.name] = device.address
if event_types:
for event_type in event_types:
@ -217,7 +234,7 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
async def _scan(
self,
duration: Optional[float] = None,
service_uuids: Optional[Collection[UUIDType]] = None,
characteristics: Optional[Collection[UUIDType]] = None,
publish_entities: bool = False,
) -> Collection[Entity]:
with self._scan_lock:
@ -226,7 +243,7 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
adapter=self._interface,
timeout=timeout,
service_uuids=list(
map(str, service_uuids or self._service_uuids or [])
map(str, characteristics or self._characteristics or [])
),
detection_callback=self._on_device_event,
)
@ -234,29 +251,75 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
# TODO Infer type from device.metadata['manufacturer_data']
self._devices.update({dev.address: dev for dev in devices})
if publish_entities:
entities = self.publish_entities(devices)
else:
entities = self.transform_entities(devices)
return (
self.publish_entities(devices)
if publish_entities
else self.transform_entities(devices)
)
return entities
async def _scan_state_set(self, state: bool, duration: Optional[float] = None):
def timer_callback():
if state:
self.scan_pause()
else:
self.scan_resume()
self._scan_controller_timer = None
with self._scan_lock:
if not state and self._scan_enabled.is_set():
get_bus().post(BluetoothScanPausedEvent(duration=duration))
elif state and not self._scan_enabled.is_set():
get_bus().post(BluetoothScanResumedEvent(duration=duration))
if state:
self._scan_enabled.set()
else:
self._scan_enabled.clear()
if duration and not self._scan_controller_timer:
self._scan_controller_timer = Timer(duration, timer_callback)
self._scan_controller_timer.start()
@action
def scan_pause(self, duration: Optional[float] = None):
"""
Pause the scanning thread.
:param duration: For how long the scanning thread should be paused
(default: null = indefinitely).
"""
if self._loop:
ensure_future(self._scan_state_set(False, duration), loop=self._loop)
@action
def scan_resume(self, duration: Optional[float] = None):
"""
Resume the scanning thread, if inactive.
:param duration: For how long the scanning thread should be running
(default: null = indefinitely).
"""
if self._loop:
ensure_future(self._scan_state_set(True, duration), loop=self._loop)
@action
def scan(
self,
duration: Optional[float] = None,
service_uuids: Optional[Collection[UUIDType]] = None,
characteristics: Optional[Collection[UUIDType]] = None,
):
"""
Scan for Bluetooth devices nearby.
Scan for Bluetooth devices nearby and return the results as a list of
entities.
:param duration: Scan duration in seconds (default: same as the plugin's
`poll_interval` configuration parameter)
:param service_uuids: List of service UUIDs to discover. Default: all.
:param characteristics: List of characteristic UUIDs to discover. Default: all.
"""
loop = get_or_create_event_loop()
loop.run_until_complete(
self._scan(duration, service_uuids, publish_entities=True)
return loop.run_until_complete(
self._scan(duration, characteristics, publish_entities=True)
)
@action
@ -319,9 +382,11 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
return self.scan().output
@override
def transform_entities(self, entities: Collection[BLEDevice]) -> Collection[Device]:
def transform_entities(
self, entities: Collection[BLEDevice]
) -> Collection[BluetoothDevice]:
return [
Device(
BluetoothDevice(
id=dev.address,
name=self._get_device_name(dev),
)
@ -333,7 +398,9 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
device_addresses = set()
while True:
await self._scan_enabled.wait()
entities = await self._scan()
new_device_addresses = {e.id for e in entities}
missing_device_addresses = device_addresses - new_device_addresses
missing_devices = [
@ -348,5 +415,12 @@ class BluetoothBlePlugin(AsyncRunnablePlugin, EntityManager):
device_addresses = new_device_addresses
@override
def stop(self):
if self._scan_controller_timer:
self._scan_controller_timer.cancel()
super().stop()
# vim:sw=4:ts=4:et:

View File

@ -126,7 +126,7 @@ class MqttPlugin(Plugin):
if version == 'tlsv1.2':
return ssl.PROTOCOL_TLSv1_2
assert 'Unrecognized TLS version: {}'.format(version)
assert f'Unrecognized TLS version: {version}'
def _mqtt_args(self, **kwargs):
return {

View File

@ -356,7 +356,7 @@ class SmartthingsPlugin(
}
missing_devs = {dev for dev in devices if dev not in found_devs}
return list(found_devs.values()), list(missing_devs) # type: ignore
return list(found_devs.values()), list(missing_devs)
def _get_devices(self, *devices: str) -> List[DeviceEntity]:
devs, missing_devs = self._get_existing_and_missing_devices(*devices)
@ -633,7 +633,7 @@ class SmartthingsPlugin(
self._entities_by_id.update({e.id: e for e in compatible_entities})
return super().transform_entities(compatible_entities) # type: ignore
return super().transform_entities(compatible_entities)
async def _get_device_status(
self, api, device_id: str, publish_entities: bool
@ -642,7 +642,7 @@ class SmartthingsPlugin(
assert device, f'No such device: {device_id}'
await device.status.refresh()
if publish_entities:
self.publish_entities([device]) # type: ignore
self.publish_entities([device])
self._devices_by_id[device_id] = device
self._devices_by_name[device.label] = device
@ -863,7 +863,6 @@ class SmartthingsPlugin(
@action
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
super().set(entity, value, attribute, **kwargs)
return self.set_value(entity, property=attribute, value=value, **kwargs)
@action
@ -994,6 +993,7 @@ class SmartthingsPlugin(
self.logger.exception(e)
self.logger.error('Could not refresh the status: %s', e)
self.wait_stop(3 * (self.poll_interval or 5))
return None
while not self.should_stop():
updated_devices = {}
@ -1010,7 +1010,7 @@ class SmartthingsPlugin(
if self._has_status_changed(devices.get(device_id, {}), new_status)
}
self.publish_entities(updated_devices.values()) # type: ignore
self.publish_entities(updated_devices.values())
devices.update(new_devices)
self.wait_stop(self.poll_interval)
refresh_status_safe()

View File

@ -34,7 +34,7 @@ class DeviceMapper:
entity_type: Type[Entity],
capability: str,
attribute: str,
value_type: Union[Type, str],
value_type: Union[Type, Enum, str],
set_command: Optional[Union[str, Callable[[Any], str]]] = None,
get_value: Optional[Callable[[DeviceEntity], Any]] = None,
set_value_args: Optional[Callable[..., Any]] = None,
@ -46,7 +46,7 @@ class DeviceMapper:
self.attribute = attribute
self.value_type = value_type
self.get_value = get_value if get_value else self._default_get_value
self.values = []
self.values: List[str] = []
self.entity_args = kwargs
if isinstance(value_type, Enum):

View File

@ -54,7 +54,7 @@ class SwitchbotBluetoothPlugin(BluetoothBlePlugin, EnumSwitchEntityManager):
}
def __init__(self, *args, **kwargs):
super().__init__(*args, service_uuids=self._uuids.values(), **kwargs)
super().__init__(*args, characteristics=self._uuids.values(), **kwargs)
async def _run(
self,
@ -122,7 +122,7 @@ class SwitchbotBluetoothPlugin(BluetoothBlePlugin, EnumSwitchEntityManager):
self.logger.warning('Unknown command for SwitchBot "%s": "%s"', device, value)
@override
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
def set(self, entity: str, value: Any, **kwargs):
return self.set_value(entity, value, **kwargs)
@override

View File

@ -348,7 +348,7 @@ class ZwaveBasePlugin(
raise NotImplementedError
@action
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
def set(self, entity: str, value: Any, **kwargs):
return self.set_value(
value_id=entity, id_on_network=entity, data=value, **kwargs
)