近期都在作一些與智能硬件交互的項目,從公司的項目走向,能夠明顯的感受到愈來愈多的家電,醫療器械,家居用品公司開始藉助手機APP來幫助他們實現自家產品的「智能化」,優化用戶體驗。相信隨着研發技術的提高和研發成本的下降,這種智能軟硬件結合的產品將會迅速普及開來。git
從目前APP同硬件模塊通訊的方式來看無非分爲如下幾種:github
這篇隨筆是對近期項目技術點的一個簡單的回顧總結。框架
iOS平臺下咱們可使用 MFI(ExternalAccessory 框架) 或 BLE (CoreBluetooth 框架) 進行藍牙開發,但實際中咱們基本使用 CoreBluetooth 框架,由於它功能更強大,支持藍牙4.0標準。測試
CoreBluetooth 框架的核心是 peripheral 和 central, 能夠理解成外設和中心,發起鏈接的是 central,被鏈接的設備爲 peripheral,它們是一組相對概念。好比,當手機去鏈接控制藍牙耳機時,你的手機就是 central,當手機藍牙被另外一個手機鏈接併爲其提供服務時就是 peripheral。優化
CoreBluetooth 框架分別爲「中心模式」和「外設模式」提供一組API。在移動端開發中,咱們一般用到的中心模式。ui
Service 和 Characteristic:藍牙設備經過GATT協議定義的數據通信方式。一個 peripheral 能夠能夠提供多種 Service,一種 Service 又能夠包含多個不一樣的 Characteristic。central 經過 peripheral 的 Characteristic 來讀寫外設的數據,和獲取通知。
spa
1. 創建中心 2. 掃描外設(discover) 3. 鏈接外設(connect) 4. 掃描外設中的服務和特徵(discover) - 4.1 獲取外設的 services - 4.2 獲取外設的 Characteristics,獲取Characteristics的值,獲 Characteristics的 Descriptor 和 Descriptor 的值 5. 與外設作數據交互(explore and interact) 6. 訂閱 Characteristic 的通知 7. 斷開鏈接(disconnect)
1. 啓動一個 Peripheral 管理對象 2. 本地 Peripheral 設置服務,特性,描述,權限等等 3. Peripheral 發送廣播 4. 設置處理訂閱、取消訂閱、讀 characteristic、寫 characteristic 的委託方法
1. 準備(standby) 2. 廣播(advertising) 3. 監聽掃描(Scanning 4. 發起鏈接(Initiating) 5. 已鏈接(Connected)
#import "ViewController.h" #import <CoreBluetooth/CoreBluetooth.h> @interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate> { CBCentralManager *_centralManager; //central 管理器 (中心模式) NSMutableArray *_peripherals; //peripheral 外設集 } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. _peripherals = [NSMutableArray array]; //初始化並設置委託和線程隊列 _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:nil]; } #pragma mark - CBCentralManagerDelegate // _centralManager 初始化狀態 - (void)centralManagerDidUpdateState:(CBCentralManager *)central{ NSLog(@"%s -- %ld", __func__ ,(long)central.state); // typedef NS_ENUM(NSInteger, CBCentralManagerState) { // CBCentralManagerStateUnknown = 0, // CBCentralManagerStateResetting, // CBCentralManagerStateUnsupported, // CBCentralManagerStateUnauthorized, // CBCentralManagerStatePoweredOff, // CBCentralManagerStatePoweredOn, // }; //手機藍牙狀態判斷 switch (central.state) { case CBCentralManagerStateUnknown: NSLog(@"central state >> CBCentralManagerStateUnknown"); break; case CBCentralManagerStateResetting: NSLog(@"central state >> CBCentralManagerStateResetting"); break; case CBCentralManagerStateUnsupported: NSLog(@"central state >> CBCentralManagerStateUnsupported"); break; case CBCentralManagerStateUnauthorized: NSLog(@"central state >> CBCentralManagerStateUnauthorized"); break; case CBCentralManagerStatePoweredOff: NSLog(@"central state >> CBCentralManagerStatePoweredOff"); break; case CBCentralManagerStatePoweredOn: { NSLog(@"central state >> CBCentralManagerStatePoweredOn"); //開始掃描 peripheral //if <i>serviceUUIDs</i> is <i>nil</i> all discovered peripherals will be returned. [_centralManager scanForPeripheralsWithServices:nil options:nil]; break; } default: break; } } // 發現藍牙外設 peripheral - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{ // NSLog(@"%s -- %@ RRSI:%ld", __func__ ,peripheral.name,(long)RSSI.integerValue); //鏈接全部 「Bioland」 名前綴的 peripheral if ([peripheral.name hasPrefix:@"Bioland-BGM"]) { //一個主設備最多能連7個外設,但外設最多隻能給一個主設備鏈接,鏈接成功,失敗,斷開會進入各自的委託 //找到的設備必須持有它,不然CBCentralManager中也不會保存peripheral,那麼CBPeripheralDelegate中的方法也不會被調用! [_peripherals addObject:peripheral]; [_centralManager connectPeripheral:peripheral options:nil]; } } // 中心設備(central) 鏈接到 外設(peripheral) - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@"鏈接到 外設(peripheral) -- %@" ,peripheral.name); //設置的peripheral委託CBPeripheralDelegate [peripheral setDelegate:self]; //掃描外設Services,成功後會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ [peripheral discoverServices:nil]; } // 外設鏈接失敗 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@"%s -- %@", __func__ ,peripheral.name); } // peripheral 斷開鏈接 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@"%s -- %@", __func__ ,peripheral.name); } #pragma mark - CBPeripheralDelegate - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ NSLog(@"掃描到服務:%@",peripheral.services); if (error) { NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]); return; } for (CBService *service in peripheral.services) { NSLog(@"%@",service.UUID); if ([service.UUID.UUIDString isEqualToString:@"1000"]) { //掃描每一個service的Characteristics,掃描到後會進入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error [peripheral discoverCharacteristics:nil forService:service]; } } } - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ if (error) { NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]); return; } for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID); } //獲取Characteristic的值,讀到數據會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error for (CBCharacteristic *characteristic in service.characteristics){ { [peripheral readValueForCharacteristic:characteristic]; } } // //搜索Characteristic的Descriptors,讀到數據會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error // for (CBCharacteristic *characteristic in service.characteristics){ // [peripheral discoverDescriptorsForCharacteristic:characteristic]; // } } //獲取的charateristic的值 -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ //打印出characteristic的UUID和值 //!注意,value的類型是NSData,具體開發時,會根據外設協議制定的方式去解析數據 NSLog(@"characteristic uuid:%@ value:%@",characteristic.UUID.UUIDString,characteristic.value); if ([characteristic.UUID.UUIDString isEqualToString:@"1001"]) { //Bioland-BGM 05應答包 測試數據 char byte[11]; memset(byte, 0, 11); byte[0] = 0x5a; byte[1] = 0x0b; byte[2] = 0x05; byte[3] = 0x0e; byte[4] = 0x0b; byte[5] = 0x08; byte[6] = 0x0c; byte[7] = 0x12; byte[8] = 0xa9; byte[9] = 0x00; byte[10] = 0x00; [self writeCharacteristic:peripheral characteristic:characteristic value:[NSData dataWithBytes:byte length:11]]; } if ([characteristic.UUID.UUIDString isEqualToString:@"1002"]) { [self notifyPeripheral:peripheral characteristic:characteristic]; } // if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"00001002-0000-1000-8000-00805f9b34fb"]]) { // [self notifyPeripheral:peripheral characteristic:characteristic]; // } } ////搜索到Characteristic的Descriptors //-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ // // //打印出Characteristic和他的Descriptors // NSLog(@"characteristic uuid:%@",characteristic.UUID); // for (CBDescriptor *d in characteristic.descriptors) { // NSLog(@"Descriptor uuid:%@",d.UUID); // } // //} ////獲取到Descriptors的值 //-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{ // //打印出DescriptorsUUID 和value // //這個descriptor都是對於characteristic的描述,通常都是字符串,因此這裏咱們轉換成字符串去解析 // NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value); //} //寫數據 -(void)writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:(NSData *)value{ //打印出 characteristic 的權限,能夠看到有不少種,這是一個NS_OPTIONS,就是能夠同時用於好幾個值,常見的有read,write,notify,indicate,知知道這幾個基本就夠用了,前連個是讀寫權限,後兩個都是通知,兩種不一樣的通知方式。 /* typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) { CBCharacteristicPropertyBroadcast = 0x01, CBCharacteristicPropertyRead = 0x02, CBCharacteristicPropertyWriteWithoutResponse = 0x04, CBCharacteristicPropertyWrite = 0x08, CBCharacteristicPropertyNotify = 0x10, CBCharacteristicPropertyIndicate = 0x20, CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40, CBCharacteristicPropertyExtendedProperties = 0x80, CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100, CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200 }; */ NSLog(@"write %lu", (unsigned long)characteristic.properties); //只有 characteristic.properties 有write的權限才能夠寫 if(characteristic.properties & CBCharacteristicPropertyWrite){ /* 最好一個type參數能夠爲CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區別是是否會有反饋 */ [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; }else{ NSLog(@"該字段不可寫!"); } } //設置通知 -(void)notifyPeripheral:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic{ NSLog(@"Notify %lu", (unsigned long)characteristic.properties); //設置通知,數據通知會進入:didUpdateValueForCharacteristic方法 [peripheral setNotifyValue:YES forCharacteristic:characteristic]; } //取消通知 -(void)cancelNotifyPeripheral:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic{ [peripheral setNotifyValue:NO forCharacteristic:characteristic]; } //中止掃描並斷開鏈接 -(void)disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)peripheral{ //中止掃描 [centralManager stopScan]; //斷開鏈接 [centralManager cancelPeripheralConnection:peripheral]; }
以上代碼基於 《CoreBluetooth》 實現藍牙簡單交互,全部操做均在其代理方法中實現,過程十分繁瑣,這裏推薦一個藍牙開源庫:BabyBluetooth線程