[Python] Delete BLE scanner handle in an explicit way (#32574)
* Normalize BT MAC letter case passed to DiscoverAsync
* Remove obsolete docstrings
* [Python] Delete BLE scanner handle in an explicit way
* Update callback names
* Get rid of obsolete comments
* Unref manager client on glib thread
diff --git a/src/controller/python/chip/ble/LinuxImpl.cpp b/src/controller/python/chip/ble/LinuxImpl.cpp
index 6218d9c..852e1f7 100644
--- a/src/controller/python/chip/ble/LinuxImpl.cpp
+++ b/src/controller/python/chip/ble/LinuxImpl.cpp
@@ -111,11 +111,9 @@
{
mCompleteCallback(mContext);
}
-
- delete this;
}
- virtual void OnScanError(CHIP_ERROR error) override
+ void OnScanError(CHIP_ERROR error) override
{
if (mErrorCallback)
{
@@ -133,10 +131,10 @@
} // namespace
-extern "C" void * pychip_ble_start_scanning(PyObject * context, void * adapter, uint32_t timeoutMs,
- ScannerDelegateImpl::DeviceScannedCallback scanCallback,
- ScannerDelegateImpl::ScanCompleteCallback completeCallback,
- ScannerDelegateImpl::ScanErrorCallback errorCallback)
+extern "C" void * pychip_ble_scanner_start(PyObject * context, void * adapter, uint32_t timeoutMs,
+ ScannerDelegateImpl::DeviceScannedCallback scanCallback,
+ ScannerDelegateImpl::ScanCompleteCallback completeCallback,
+ ScannerDelegateImpl::ScanErrorCallback errorCallback)
{
std::unique_ptr<ScannerDelegateImpl> delegate =
std::make_unique<ScannerDelegateImpl>(context, scanCallback, completeCallback, errorCallback);
@@ -151,3 +149,9 @@
return delegate.release();
}
+
+extern "C" void pychip_ble_scanner_delete(void * scanner)
+{
+ chip::DeviceLayer::StackLock lock;
+ delete static_cast<ScannerDelegateImpl *>(scanner);
+}
diff --git a/src/controller/python/chip/ble/darwin/Scanning.mm b/src/controller/python/chip/ble/darwin/Scanning.mm
index caf18c6..d3fb928 100644
--- a/src/controller/python/chip/ble/darwin/Scanning.mm
+++ b/src/controller/python/chip/ble/darwin/Scanning.mm
@@ -131,7 +131,7 @@
@end
-extern "C" void * pychip_ble_start_scanning(PyObject * context, void * adapter, uint32_t timeout,
+extern "C" void * pychip_ble_scanner_start(PyObject * context, void * adapter, uint32_t timeout,
DeviceScannedCallback scanCallback, ScanCompleteCallback completeCallback, ScanErrorCallback errorCallback)
{
// NOTE: adapter is ignored as it does not apply to mac
@@ -144,3 +144,8 @@
return (__bridge_retained void *) (scanner);
}
+
+extern "C" void pychip_ble_scanner_delete(void * scanner)
+{
+ CFRelease((CFTypeRef) scanner);
+}
diff --git a/src/controller/python/chip/ble/library_handle.py b/src/controller/python/chip/ble/library_handle.py
index 2dbb52f..00f2a77 100644
--- a/src/controller/python/chip/ble/library_handle.py
+++ b/src/controller/python/chip/ble/library_handle.py
@@ -57,8 +57,10 @@
setter.Set('pychip_ble_adapter_list_get_raw_adapter',
VoidPointer, [VoidPointer])
- setter.Set('pychip_ble_start_scanning', VoidPointer, [
- py_object, VoidPointer, c_uint32, DeviceScannedCallback, ScanDoneCallback, ScanErrorCallback
+ setter.Set('pychip_ble_scanner_start', VoidPointer, [
+ py_object, VoidPointer, c_uint32, DeviceScannedCallback,
+ ScanDoneCallback, ScanErrorCallback,
])
+ setter.Set('pychip_ble_scanner_delete', None, [VoidPointer])
return handle
diff --git a/src/controller/python/chip/ble/scan_devices.py b/src/controller/python/chip/ble/scan_devices.py
index 5e9ee24..86cc0a3 100644
--- a/src/controller/python/chip/ble/scan_devices.py
+++ b/src/controller/python/chip/ble/scan_devices.py
@@ -17,6 +17,7 @@
import ctypes
from dataclasses import dataclass
from queue import Queue
+from threading import Thread
from typing import Generator
from chip.ble.library_handle import _GetBleLibraryHandle
@@ -26,36 +27,68 @@
@DeviceScannedCallback
def ScanFoundCallback(closure, address: str, discriminator: int, vendor: int,
product: int):
- closure.DeviceFound(address, discriminator, vendor, product)
+ closure.OnDeviceScanned(address, discriminator, vendor, product)
@ScanDoneCallback
def ScanDoneCallback(closure):
- closure.ScanCompleted()
+ closure.OnScanComplete()
@ScanErrorCallback
def ScanErrorCallback(closure, errorCode: int):
- closure.ScanErrorCallback(errorCode)
+ closure.OnScanError(errorCode)
-def DiscoverAsync(timeoutMs: int, scanCallback, doneCallback, errorCallback, adapter=None):
- """Initiate a BLE discovery of devices with the given timeout.
+@dataclass
+class DeviceInfo:
+ address: str
+ discriminator: int
+ vendor: int
+ product: int
- NOTE: devices are not guaranteed to be unique. New entries are returned
+
+class _DeviceInfoReceiver:
+ """Uses a queue to notify of objects received asynchronously
+ from a BLE scan.
+
+ Internal queue gets filled on DeviceFound and ends with None when
+ ScanCompleted.
+ """
+
+ def __init__(self):
+ self.queue = Queue()
+
+ def OnDeviceScanned(self, address, discriminator, vendor, product):
+ self.queue.put(DeviceInfo(address, discriminator, vendor, product))
+
+ def OnScanComplete(self):
+ self.queue.put(None)
+
+ def OnScanError(self, errorCode):
+ # TODO need to determine what we do with this error. Most of the time this
+ # error is just a timeout introduced in PR #24873, right before we get a
+ # ScanCompleted.
+ pass
+
+
+def DiscoverSync(timeoutMs: int, adapter=None) -> Generator[DeviceInfo, None, None]:
+ """Discover BLE devices over the specified period of time.
+
+ NOTE: Devices are not guaranteed to be unique. New entries are returned
as soon as the underlying BLE manager detects changes.
Args:
timeoutMs: scan will complete after this time
- scanCallback: callback when a device is found
- doneCallback: callback when the scan is complete
- errorCallback: callback when error occurred during scan
adapter: what adapter to choose. Either an AdapterInfo object or
a string with the adapter address. If None, the first
adapter on the system is used.
"""
- if adapter and not isinstance(adapter, str):
- adapter = adapter.address
+ if adapter:
+ if isinstance(adapter, str):
+ adapter = adapter.upper()
+ else:
+ adapter = adapter.address
handle = _GetBleLibraryHandle()
@@ -69,87 +102,49 @@
nativeList).decode('utf8')):
continue
- class ScannerClosure:
-
- def DeviceFound(self, *args):
- scanCallback(*args)
-
- def ScanCompleted(self, *args):
- doneCallback(*args)
- ctypes.pythonapi.Py_DecRef(ctypes.py_object(self))
-
- def ScanErrorCallback(self, *args):
- errorCallback(*args)
-
- closure = ScannerClosure()
- ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
-
- scanner = handle.pychip_ble_start_scanning(
- ctypes.py_object(closure),
- handle.pychip_ble_adapter_list_get_raw_adapter(
- nativeList), timeoutMs,
- ScanFoundCallback, ScanDoneCallback, ScanErrorCallback)
+ receiver = _DeviceInfoReceiver()
+ scanner = handle.pychip_ble_scanner_start(
+ ctypes.py_object(receiver),
+ handle.pychip_ble_adapter_list_get_raw_adapter(nativeList),
+ timeoutMs, ScanFoundCallback, ScanDoneCallback, ScanErrorCallback)
if scanner == 0:
- raise Exception('Failed to initiate scan')
+ raise Exception('Failed to start BLE scan')
+
+ while True:
+ data = receiver.queue.get()
+ if not data:
+ break
+ yield data
+
+ handle.pychip_ble_scanner_delete(scanner)
break
finally:
handle.pychip_ble_adapter_list_delete(nativeList)
-@dataclass
-class DeviceInfo:
- address: str
- discriminator: int
- vendor: int
- product: int
+def DiscoverAsync(timeoutMs: int, scanCallback, doneCallback, errorCallback, adapter=None):
+ """Discover BLE devices over the specified period of time without blocking.
-
-class _DeviceInfoReceiver:
- """Uses a queue to notify of objects received asynchronously
- from a ble scan.
-
- Internal queue gets filled on DeviceFound and ends with None when
- ScanCompleted.
- """
-
- def __init__(self):
- self.queue = Queue()
-
- def DeviceFound(self, address, discriminator, vendor, product):
- self.queue.put(DeviceInfo(address, discriminator, vendor, product))
-
- def ScanCompleted(self):
- self.queue.put(None)
-
- def ScanError(self, errorCode):
- # TODO need to determine what we do with this error. Most of the time this
- # error is just a timeout introduced in PR #24873, right before we get a
- # ScanCompleted.
- pass
-
-
-def DiscoverSync(timeoutMs: int, adapter=None) -> Generator[DeviceInfo, None, None]:
- """Discover BLE devices over the specified period of time.
-
- NOTE: devices are not guaranteed to be unique. New entries are returned
+ NOTE: Devices are not guaranteed to be unique. The scanCallback is called
as soon as the underlying BLE manager detects changes.
Args:
timeoutMs: scan will complete after this time
scanCallback: callback when a device is found
doneCallback: callback when the scan is complete
+ errorCallback: callback when error occurred during scan
adapter: what adapter to choose. Either an AdapterInfo object or
a string with the adapter address. If None, the first
adapter on the system is used.
"""
- receiver = _DeviceInfoReceiver()
- DiscoverAsync(timeoutMs, receiver.DeviceFound,
- receiver.ScanCompleted, receiver.ScanError, adapter)
+ def _DiscoverAsync(timeoutMs, scanCallback, doneCallback, errorCallback, adapter):
+ for device in DiscoverSync(timeoutMs, adapter):
+ scanCallback(device.address, device.discriminator, device.vendor, device.product)
+ doneCallback()
- while True:
- data = receiver.queue.get()
- if not data:
- break
- yield data
+ t = Thread(target=_DiscoverAsync,
+ args=(timeoutMs, scanCallback, doneCallback, errorCallback, adapter),
+ daemon=True)
+ t.start()
diff --git a/src/platform/Linux/bluez/AdapterIterator.cpp b/src/platform/Linux/bluez/AdapterIterator.cpp
index ced1724..0455359 100644
--- a/src/platform/Linux/bluez/AdapterIterator.cpp
+++ b/src/platform/Linux/bluez/AdapterIterator.cpp
@@ -47,6 +47,18 @@
return CHIP_NO_ERROR;
}
+CHIP_ERROR AdapterIterator::Shutdown()
+{
+ // Release resources on the glib thread to synchronize with potential signal handlers
+ // attached to the manager client object that may run on the glib thread.
+ return PlatformMgrImpl().GLibMatterContextInvokeSync(
+ +[](AdapterIterator * self) {
+ self->mManager.reset();
+ return CHIP_NO_ERROR;
+ },
+ this);
+}
+
bool AdapterIterator::Advance()
{
for (; mIterator != BluezObjectList::end(); ++mIterator)
diff --git a/src/platform/Linux/bluez/AdapterIterator.h b/src/platform/Linux/bluez/AdapterIterator.h
index 38af64f..c998df6 100644
--- a/src/platform/Linux/bluez/AdapterIterator.h
+++ b/src/platform/Linux/bluez/AdapterIterator.h
@@ -48,6 +48,9 @@
class AdapterIterator
{
public:
+ AdapterIterator() = default;
+ ~AdapterIterator() { Shutdown(); }
+
/// Moves to the next DBUS interface.
///
/// MUST be called before any of the 'current value' methods are
@@ -66,6 +69,8 @@
private:
/// Sets up the DBUS manager and loads the list
CHIP_ERROR Initialize();
+ /// Destroys the DBUS manager
+ CHIP_ERROR Shutdown();
/// Loads the next value in the list.
///
diff --git a/src/platform/Linux/bluez/ChipDeviceScanner.cpp b/src/platform/Linux/bluez/ChipDeviceScanner.cpp
index 5523bf8..ce52b84 100644
--- a/src/platform/Linux/bluez/ChipDeviceScanner.cpp
+++ b/src/platform/Linux/bluez/ChipDeviceScanner.cpp
@@ -112,16 +112,12 @@
VerifyOrReturnError(mTimerState == ScannerTimerState::TIMER_CANCELED, CHIP_ERROR_INCORRECT_STATE);
mCancellable.reset(g_cancellable_new());
- if (PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStartScan, this) != CHIP_NO_ERROR)
+ CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStartScan, this);
+ if (err != CHIP_NO_ERROR)
{
- ChipLogError(Ble, "Failed to schedule BLE scan start.");
-
- ChipDeviceScannerDelegate * delegate = this->mDelegate;
- // callback is explicitly allowed to delete the scanner (hence no more
- // references to 'self' here)
- delegate->OnScanComplete();
-
- return CHIP_ERROR_INTERNAL;
+ ChipLogError(Ble, "Failed to initiate BLE scan start: %" CHIP_ERROR_FORMAT, err.Format());
+ mDelegate->OnScanComplete();
+ return err;
}
// Here need to set the Bluetooth scanning status immediately.
@@ -129,14 +125,14 @@
// calling StopScan will be effective.
mScannerState = ChipDeviceScannerState::SCANNER_SCANNING;
- CHIP_ERROR err = chip::DeviceLayer::SystemLayer().StartTimer(timeout, TimerExpiredCallback, static_cast<void *>(this));
-
+ err = chip::DeviceLayer::SystemLayer().StartTimer(timeout, TimerExpiredCallback, static_cast<void *>(this));
if (err != CHIP_NO_ERROR)
{
- ChipLogError(Ble, "Failed to schedule scan timeout.");
+ ChipLogError(Ble, "Failed to schedule scan timeout: %" CHIP_ERROR_FORMAT, err.Format());
StopScan();
return err;
}
+
mTimerState = ScannerTimerState::TIMER_STARTED;
ChipLogDetail(Ble, "ChipDeviceScanner has started scanning!");
@@ -157,9 +153,10 @@
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mScannerState == ChipDeviceScannerState::SCANNER_SCANNING, CHIP_NO_ERROR);
- if (PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStopScan, this) != CHIP_NO_ERROR)
+ CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStopScan, this);
+ if (err != CHIP_NO_ERROR)
{
- ChipLogError(Ble, "Failed to schedule BLE scan stop.");
+ ChipLogError(Ble, "Failed to initiate BLE scan stop: %" CHIP_ERROR_FORMAT, err.Format());
return CHIP_ERROR_INTERNAL;
}
@@ -176,10 +173,7 @@
// Reset timer status
mTimerState = ScannerTimerState::TIMER_CANCELED;
- ChipDeviceScannerDelegate * delegate = this->mDelegate;
- // callback is explicitly allowed to delete the scanner (hence no more
- // references to 'self' here)
- delegate->OnScanComplete();
+ mDelegate->OnScanComplete();
return CHIP_NO_ERROR;
}