| #include <ble/BleUUID.h> |
| #include <ble/CHIPBleServiceData.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/Darwin/UUIDHelper.h> |
| |
| #import <CoreBluetooth/CoreBluetooth.h> |
| |
| namespace { |
| struct PyObject; |
| using DeviceScannedCallback |
| = void (*)(PyObject * context, const char * address, uint16_t discriminator, uint16_t vendorId, uint16_t productId); |
| using ScanCompleteCallback = void (*)(PyObject * context); |
| } |
| |
| @interface ChipDeviceBleScanner : NSObject <CBCentralManagerDelegate> |
| |
| @property (strong, nonatomic) dispatch_queue_t workQueue; |
| @property (nonatomic, readonly, nullable) dispatch_source_t timer; |
| @property (strong, nonatomic) CBCentralManager * centralManager; |
| @property (strong, nonatomic) CBUUID * shortServiceUUID; |
| |
| @property (assign, nonatomic) PyObject * context; |
| @property (assign, nonatomic) DeviceScannedCallback scanCallback; |
| @property (assign, nonatomic) ScanCompleteCallback completeCallback; |
| |
| - (id)initWithContext:(PyObject *)context |
| scanCallback:(DeviceScannedCallback)scanCallback |
| completeCallback:(ScanCompleteCallback)completeCallback |
| timeoutMs:(uint32_t)timeout; |
| |
| - (void)stopTimeoutReached; |
| |
| @end |
| |
| @implementation ChipDeviceBleScanner |
| |
| - (id)initWithContext:(PyObject *)context |
| scanCallback:(DeviceScannedCallback)scanCallback |
| completeCallback:(ScanCompleteCallback)completeCallback |
| timeoutMs:(uint32_t)timeout |
| { |
| self = [super init]; |
| if (self) { |
| self.shortServiceUUID = [UUIDHelper GetShortestServiceUUID:&chip::Ble::CHIP_BLE_SVC_ID]; |
| |
| _workQueue = dispatch_queue_create("com.chip.python.ble.work_queue", DISPATCH_QUEUE_SERIAL); |
| _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue); |
| _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:_workQueue]; |
| _context = context; |
| _scanCallback = scanCallback; |
| _completeCallback = completeCallback; |
| |
| dispatch_source_set_event_handler(_timer, ^{ |
| [self stopTimeoutReached]; |
| }); |
| dispatch_source_set_timer( |
| _timer, dispatch_walltime(nullptr, timeout * NSEC_PER_MSEC), DISPATCH_TIME_FOREVER, 50 * NSEC_PER_MSEC); |
| dispatch_resume(_timer); |
| } |
| return self; |
| } |
| |
| - (void)centralManager:(CBCentralManager *)central |
| didDiscoverPeripheral:(CBPeripheral *)peripheral |
| advertisementData:(NSDictionary *)advertisementData |
| RSSI:(NSNumber *)RSSI |
| { |
| NSNumber * isConnectable = [advertisementData objectForKey:CBAdvertisementDataIsConnectable]; |
| |
| if (![isConnectable boolValue]) { |
| return; |
| } |
| |
| NSDictionary * servicesData = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey]; |
| for (CBUUID * serviceUUID in servicesData) { |
| if (![serviceUUID.data isEqualToData:_shortServiceUUID.data]) { |
| continue; |
| } |
| NSData * serviceData = [servicesData objectForKey:serviceUUID]; |
| |
| NSUInteger length = [serviceData length]; |
| if (length != sizeof(chip::Ble::ChipBLEDeviceIdentificationInfo)) { |
| ChipLogError(Ble, "Device has invalid advertisement data length."); |
| break; |
| } |
| |
| chip::Ble::ChipBLEDeviceIdentificationInfo data; |
| memcpy(&data, [serviceData bytes], sizeof(data)); |
| |
| _scanCallback(_context, [peripheral.identifier.UUIDString UTF8String], data.GetDeviceDiscriminator(), data.GetVendorId(), |
| data.GetProductId()); |
| |
| break; |
| } |
| } |
| |
| - (void)stopTimeoutReached |
| { |
| ChipLogProgress(Ble, "Scan timeout reached."); |
| |
| _completeCallback(_context); |
| |
| dispatch_source_cancel(_timer); |
| [self.centralManager stopScan]; |
| self.centralManager = nil; |
| } |
| |
| - (void)centralManagerDidUpdateState:(CBCentralManager *)central |
| { |
| switch (central.state) { |
| case CBManagerStatePoweredOn: |
| ChipLogProgress(Ble, "Central BLE Manager is on. Starting to scan."); |
| [central scanForPeripheralsWithServices:@[ _shortServiceUUID ] options:nil]; |
| break; |
| default: |
| ChipLogError(Ble, "CBManagerState is NOT ON. Unable to scan."); |
| break; |
| } |
| } |
| |
| - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral |
| { |
| } |
| |
| @end |
| |
| extern "C" void * pychip_ble_start_scanning( |
| PyObject * context, void * adapter, uint32_t timeout, DeviceScannedCallback scanCallback, ScanCompleteCallback completeCallback) |
| { |
| // NOTE: adapter is ignored as it does not apply to mac |
| |
| ChipDeviceBleScanner * scanner = [[ChipDeviceBleScanner alloc] initWithContext:context |
| scanCallback:scanCallback |
| completeCallback:completeCallback |
| timeoutMs:timeout]; |
| |
| return (__bridge_retained void *) (scanner); |
| } |