Skip to content
This repository has been archived by the owner on Mar 29, 2023. It is now read-only.

Commit

Permalink
fix race condition in core bluetooth startup
Browse files Browse the repository at this point in the history
core bluetooth has to wait until centralManagerDidUpdateState_ is called
and the state is CBManagerStatePoweredOn before any other bluetooth
functions can be used.

Since __init__ can't be async, this means we have to move the check
to the other async entry points of discovery and scanner.
  • Loading branch information
dlech committed Jun 19, 2020
1 parent 7d4d903 commit 0b37282
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 12 deletions.
21 changes: 14 additions & 7 deletions bleak/backends/corebluetooth/CentralManagerDelegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def init(self):
self.connected_peripheral = None
self._connection_state = CMDConnectionState.DISCONNECTED

self.ready = False
self.powered_on_event = asyncio.Event()
self.devices = {}

self.disconnected_callback = None
Expand All @@ -84,15 +84,19 @@ def compliant(self):
CBCentralManagerDelegate
)

@property
def enabled(self):
"""Check if the bluetooth device is on and running"""
return self.central_manager.state() == CBManagerStatePoweredOn

@property
def isConnected(self) -> bool:
return self._connection_state == CMDConnectionState.CONNECTED

async def waitForPowerOn_(self, timeout: float):
"""
Waits for state to be CBManagerStatePoweredOn. This must be done before
attempting to do anything else.
Throws asyncio.TimeoutError if power on is not detected before timeout.
"""
await asyncio.wait_for(self.powered_on_event.wait(), timeout)

async def scanForPeripherals_(self, scan_options) -> List[CBPeripheral]:
"""
Scan for peripheral devices
Expand Down Expand Up @@ -160,7 +164,10 @@ def centralManagerDidUpdateState_(self, centralManager):
elif centralManager.state() == CBManagerStatePoweredOn:
logger.debug("Bluetooth powered on")

self.ready = True
if centralManager.state() == CBManagerStatePoweredOn:
self.powered_on_event.set()
else:
self.powered_on_event.clear()

def centralManager_didDiscoverPeripheral_advertisementData_RSSI_(
self,
Expand Down
4 changes: 3 additions & 1 deletion bleak/backends/corebluetooth/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ async def discover(timeout: float = 5.0, **kwargs) -> List[BLEDevice]:
timeout (float): duration of scanning period
"""
if not _manager.enabled:
try:
await _manager.waitForPowerOn_(0.1)
except asyncio.TimeoutError:
raise BleakError("Bluetooth device is turned off")

scan_options = {"timeout": timeout}
Expand Down
9 changes: 5 additions & 4 deletions bleak/backends/corebluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ class BleakScannerCoreBluetooth(BaseBleakScanner):
def __init__(self, **kwargs):
super(BleakScannerCoreBluetooth, self).__init__(**kwargs)
self._manager = cbapp.central_manager_delegate

if not self._manager.enabled:
raise BleakError("Bluetooth device is turned off")

self._timeout = kwargs.get("timeout", 5.0)

async def start(self):
try:
await self._manager.waitForPowerOn_(0.1)
except asyncio.TimeoutError:
raise BleakError("Bluetooth device is turned off")

# TODO: Evaluate if newer macOS than 10.11 has stopScan.
if hasattr(self._manager, "stopScan_"):
await self._manager.scanForPeripherals_()
Expand Down

0 comments on commit 0b37282

Please sign in to comment.