關於
近期都在做一些與智能硬件交互的項目,從公司的項目走向,可以明顯的感覺到越來越多的家電,醫療器械,家居用品公司開始借助手機APP來幫助他們實現自家產品的“智能化”,優化用戶體驗。相信隨着研發技術的提升和研發成本的降低,這種智能軟硬件結合的產品將會迅速普及開來。
從目前APP同硬件模塊通信的方式來看無非分為以下幾種:
- 藍牙連接模式
- WiFi 連接模式(Socket 或 HTTP server)
- DLNA 音視頻共享 (iOS端還可使用 AirPlay)
這篇隨筆是對近期項目技術點的一個簡單的回顧總結。
藍牙連接模式
藍牙基礎知識
-
iOS平台下我們可以使用 MFI(ExternalAccessory 框架) 或 BLE (CoreBluetooth 框架) 進行藍牙開發,但實際中我們基本使用 CoreBluetooth 框架,因為它功能更強大,支持藍牙4.0標准。
-
CoreBluetooth 框架的核心是 peripheral 和 central, 可以理解成外設和中心,發起連接的是 central,被連接的設備為 peripheral,它們是一組相對概念。比如,當手機去連接控制藍牙耳機時,你的手機就是 central,當手機藍牙被另一個手機連接並為其提供服務時就是 peripheral。
-
CoreBluetooth 框架分別為“中心模式”和“外設模式”提供一組API。在移動端開發中,我們通常用到的中心模式。
-
Service 和 Characteristic:藍牙設備通過GATT協議定義的數據通訊方式。一個 peripheral 可以可以提供多種 Service,一種 Service 又可以包含多個不同的 Characteristic。central 通過 peripheral 的 Characteristic 來讀寫外設的數據,和獲取通知。
藍牙的兩種工作模式
1、中心模式
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)
2、外設模式
1. 啟動一個 Peripheral 管理對象
2. 本地 Peripheral 設置服務,特性,描述,權限等等
3. Peripheral 發送廣播
4. 設置處理訂閱、取消訂閱、讀 characteristic、寫 characteristic 的委托方法
3.藍牙設備的工作狀態
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