Skip to content

Commit

Permalink
Merge pull request hbldh#378 from hbldh/windows-threads
Browse files Browse the repository at this point in the history
fix dotnet event callbacks
  • Loading branch information
dlech authored Dec 7, 2020
2 parents 8758ff3 + 9d8b1b9 commit d70f8a3
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 28 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Fixed
-----

* Fixed use of bare exceptions.
* Fixed #374 "BleakClientBlueZDBus.start_notify() misses initial notifications with fast Bluetooth devices"
* Fixed #374 "BleakClientBlueZDBus.start_notify() misses initial notifications with fast Bluetooth devices".
* Fix event callbacks on Windows not running in asyncio event loop thread.

Removed
~~~~~~~
Expand Down
65 changes: 42 additions & 23 deletions bleak/backends/dotnet/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
from BleakBridge import Bridge # noqa: F401

from System import Array, Object
from Windows.Devices import Enumeration
from Windows.Devices.Bluetooth.Advertisement import (
BluetoothLEAdvertisementWatcher,
BluetoothLEScanningMode,
BluetoothLEAdvertisementType,
BluetoothLEAdvertisementReceivedEventArgs,
BluetoothLEAdvertisementWatcherStoppedEventArgs,
)
from Windows.Devices.Enumeration import (
DeviceInformation,
DeviceInformationUpdate,
DeviceInformationKind,
DeviceWatcher,
)
from Windows.Foundation import TypedEventHandler

from bleak.backends.dotnet.utils import BleakDataReader
Expand Down Expand Up @@ -73,7 +78,10 @@ def _format_event_args(e):
except Exception:
return e.BluetoothAddress

def _received_handler(sender, e):
def _received_handler(
sender: BluetoothLEAdvertisementWatcher,
e: BluetoothLEAdvertisementReceivedEventArgs,
):
if sender == watcher:
logger.debug("Received {0}.".format(_format_event_args(e)))
if e.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse:
Expand All @@ -83,25 +91,30 @@ def _received_handler(sender, e):
if e.BluetoothAddress not in devices:
devices[e.BluetoothAddress] = e

def _stopped_handler(sender, e):
def _stopped_handler(
sender: BluetoothLEAdvertisementWatcher,
e: BluetoothLEAdvertisementWatcherStoppedEventArgs,
):
if sender == watcher:
logger.debug(
"{0} devices found. Watcher status: {1}.".format(
len(devices), watcher.Status
)
)

event_loop = asyncio.get_event_loop()

received_token = watcher.add_Received(
TypedEventHandler[
BluetoothLEAdvertisementWatcher,
BluetoothLEAdvertisementReceivedEventArgs,
](_received_handler)
](lambda s, e: event_loop.call_soon_threadsafe(_received_handler, s, e))
)
stopped_token = watcher.add_Stopped(
TypedEventHandler[
BluetoothLEAdvertisementWatcher,
BluetoothLEAdvertisementWatcherStoppedEventArgs,
](_stopped_handler)
](lambda s, e: event_loop.call_soon_threadsafe(_stopped_handler, s, e))
)

watcher.ScanningMode = BluetoothLEScanningMode.Active
Expand Down Expand Up @@ -175,10 +188,10 @@ async def discover_by_enumeration(timeout: float = 5.0, **kwargs) -> List[BLEDev
aqs_all_bluetooth_le_devices = (
'(System.Devices.Aep.ProtocolId:="' '{bb7bb05e-5972-42b5-94fc-76eaa7084d49}")'
)
watcher = Enumeration.DeviceInformation.CreateWatcher(
watcher = DeviceInformation.CreateWatcher(
aqs_all_bluetooth_le_devices,
requested_properties,
Enumeration.DeviceInformationKind.AssociationEndpoint,
DeviceInformationKind.AssociationEndpoint,
)

devices = {}
Expand All @@ -191,67 +204,73 @@ def _format_device_info(d):
except Exception:
return d.Id

def _added_handler(sender, dinfo):
def _added_handler(sender: DeviceWatcher, dinfo: DeviceInformation):
if sender == watcher:

logger.debug("Added {0}.".format(_format_device_info(dinfo)))
if dinfo.Id not in devices:
devices[dinfo.Id] = dinfo

def _updated_handler(sender, dinfo_update):
def _updated_handler(sender: DeviceWatcher, dinfo_update: DeviceInformationUpdate):
if sender == watcher:
if dinfo_update.Id in devices:
logger.debug(
"Updated {0}.".format(_format_device_info(devices[dinfo_update.Id]))
)
devices[dinfo_update.Id].Update(dinfo_update)

def _removed_handler(sender, dinfo_update):
def _removed_handler(sender: DeviceWatcher, dinfo_update: DeviceInformationUpdate):
if sender == watcher:
logger.debug(
"Removed {0}.".format(_format_device_info(devices[dinfo_update.Id]))
)
if dinfo_update.Id in devices:
devices.pop(dinfo_update.Id)

def _enumeration_completed_handler(sender, obj):
def _enumeration_completed_handler(sender: DeviceWatcher, _: Object):
if sender == watcher:
logger.debug(
"{0} devices found. Enumeration completed. Watching for updates...".format(
len(devices)
)
)

def _stopped_handler(sender, obj):
def _stopped_handler(sender: DeviceWatcher, _: Object):
if sender == watcher:
logger.debug(
"{0} devices found. Watcher status: {1}.".format(
len(devices), watcher.Status
)
)

event_loop = asyncio.get_event_loop()

added_token = watcher.add_Added(
TypedEventHandler[Enumeration.DeviceWatcher, Enumeration.DeviceInformation](
_added_handler
TypedEventHandler[DeviceWatcher, DeviceInformation](
lambda s, e: event_loop.call_soon_threadsafe(_added_handler, s, e)
)
)
updated_token = watcher.add_Updated(
TypedEventHandler[
Enumeration.DeviceWatcher, Enumeration.DeviceInformationUpdate
](_updated_handler)
TypedEventHandler[DeviceWatcher, DeviceInformationUpdate](
lambda s, e: event_loop.call_soon_threadsafe(_updated_handler, s, e)
)
)
removed_token = watcher.add_Removed(
TypedEventHandler[
Enumeration.DeviceWatcher, Enumeration.DeviceInformationUpdate
](_removed_handler)
TypedEventHandler[DeviceWatcher, DeviceInformationUpdate](
lambda s, e: event_loop.call_soon_threadsafe(_removed_handler, s, e)
)
)
enumeration_completed_token = watcher.add_EnumerationCompleted(
TypedEventHandler[Enumeration.DeviceWatcher, Object](
_enumeration_completed_handler
TypedEventHandler[DeviceWatcher, Object](
lambda s, e: event_loop.call_soon_threadsafe(
_enumeration_completed_handler, s, e
)
)
)
stopped_token = watcher.add_Stopped(
TypedEventHandler[Enumeration.DeviceWatcher, Object](_stopped_handler)
TypedEventHandler[DeviceWatcher, Object](
lambda s, e: event_loop.call_soon_threadsafe(_stopped_handler, s, e)
)
)

# Watcher works outside of the Python process.
Expand Down
22 changes: 18 additions & 4 deletions bleak/backends/dotnet/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ def __init__(self, **kwargs):
self._signal_strength_filter = kwargs.get("SignalStrengthFilter", None)
self._advertisement_filter = kwargs.get("AdvertisementFilter", None)

def _received_handler(self, sender, event_args):
def _received_handler(
self,
sender: BluetoothLEAdvertisementWatcher,
event_args: BluetoothLEAdvertisementReceivedEventArgs,
):
if sender == self.watcher:
logger.debug("Received {0}.".format(_format_event_args(event_args)))
if (
Expand All @@ -91,7 +95,11 @@ def _received_handler(self, sender, event_args):
if self._callback is not None:
self._callback(sender, event_args)

def _stopped_handler(self, sender, event_args):
def _stopped_handler(
self,
sender: BluetoothLEAdvertisementWatcher,
event_args: BluetoothLEAdvertisementWatcherStoppedEventArgs,
):
if sender == self.watcher:
logger.debug(
"{0} devices found. Watcher status: {1}.".format(
Expand All @@ -103,17 +111,23 @@ async def start(self):
self.watcher = BluetoothLEAdvertisementWatcher()
self.watcher.ScanningMode = self._scanning_mode

event_loop = asyncio.get_event_loop()

self._received_token = self.watcher.add_Received(
TypedEventHandler[
BluetoothLEAdvertisementWatcher,
BluetoothLEAdvertisementReceivedEventArgs,
](self._received_handler)
](
lambda s, e: event_loop.call_soon_threadsafe(
self._received_handler, s, e
)
)
)
self._stopped_token = self.watcher.add_Stopped(
TypedEventHandler[
BluetoothLEAdvertisementWatcher,
BluetoothLEAdvertisementWatcherStoppedEventArgs,
](self._stopped_handler)
](lambda s, e: event_loop.call_soon_threadsafe(self._stopped_handler, s, e))
)

if self._signal_strength_filter is not None:
Expand Down

0 comments on commit d70f8a3

Please sign in to comment.