最近人有點墮落,好久沒有更新東西了,一直負責維護開發公司的藍牙項目,借此整理一下關于藍牙的相關資料信息。
最開始,做藍牙的時候感覺這個技術還挺low的,距離受限制傳輸效率也受限制,直到最近有個科技展展示了一些用藍牙技術開發的智能硬件,才發現在這個物聯網被炒的熱火朝天的硬件市場,藍牙還是能占有一席之地的~
想要實現藍牙連接首先第一步就是導入頭文件
1.導入頭文件#import <CoreBluetooth/CoreBluetooth.h>
2.設置中心及外設的屬性
@property(nonatomic,strong)CBCentralManager*cbCentralMgr;//管理中心(發起連接)
@property(nonatomic,strong)CBPeripheral*myPeripheral;//外部設備(被動連接)
3.繼承代理方法<CBCentralManagerDelegate,CBPeripheralDelegate>
4.創建中心設備的實例并設置代理
self.cbCentralMgr= [[CBCentralManageralloc]initWithDelegate:selfqueue:nil];
5.到了這一步開始就要進入真正的干貨,中心管理設置delegate后會自動調用本機藍牙狀態的方法也就是
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//這個方法
這里: central.state 有多種狀態
其中我們最主要使用的是兩種CBManagerStatePoweredOff,CBManagerStatePoweredOn, //關閉狀態,
藍牙開啟
具體區分可以考慮下面使用模式
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state)
{
case CBCentralManagerStatePoweredOff:
NSLog(@"Bluetooth is currently powered off.");
// 這里就是藍牙未開啟,我們可以在這個位置給用戶個提示什么的
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@"CBCentralManagerStatePoweredOn");
// 這個地方就是藍牙已開啟,我們可以使用[self.cbCentralMgr scanForPeripheralsWithServices:nil options:nil]; 這個方法進行掃描周邊設備設備
}
break;
default:
break;
}
}
6.一旦掃描到設備就會自動調用代理方法
//發現藍牙設備,可能發現不止一個藍牙設備,所以該方法可能被調用多次
- (void)centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber*)RSSI{}
在這里,符合我們條件的可以進行藍牙連接操作(手動調用)
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
7.在掃描到一個設備時,我們可以進行外部設備和中心的連接
[self.cbCentralMgr connectPeripheral:peripheral options:[NSDictionary dictionaryWithObject:[NSNumbernumberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
在連接的時候同時可以關閉中心的掃描
[self.cbCentralMgrstopScan];
8.在中心和外部設備的連接過程中,可能會調用以下幾個方法:
當連接成功時調用當連接上某個藍牙之后,CBCentralManager會通知代理處理
- (void)centralManager:(CBCentralManager*)central didConnectPeripheral:(CBPeripheral*)peripheral{}
當連接失敗時調用 當連接藍牙失敗的時候會調用
- (void)centralManager:(CBCentralManager*)central didFailToConnectPeripheral:(CBPeripheral*)peripheral error:(NSError*)error{}
當連接斷開時調用 當中心主動與外設斷開或外設主動和中心斷開成功時調用
- (void)centralManager:(CBCentralManager*)central didDisconnectPeripheral:(CBPeripheral*)peripheral error:(nullableNSError*)error{}
(注意:當我們連接成功后,要為外設設置代理 peripheral.delegate=self;)
到目前為止,上面所有的代理(我說的是代理方法,代理里面調用的方法要自己寫)方法,都是cbCentralMgr(管理中心的代理方法),滿足條件就會調用.當連接成功之后,我們設置好設備代理,下面開始就是設備代理的方法
(管理中心 的代理方法都是以centralManager 開頭,而外設的代理方法都是以peripheral,還是比較好區分)
9.查詢所有服務時會調用外設的方法
//返回的藍牙服務通知通過代理實現
- (void)peripheral:(CBPeripheral*)peripheral didDiscoverServices:(NSError*)error
{
for (CBService* service in peripheral.services){
// 但是服務并不是我們的目標,也沒有實際意義。我們需要用的是服務下的特征,查詢(每一個服務下的若干)特征(下面那段知道服務和特征的小伙伴可以直接跳過)
// 什么是服務和特征:每個藍牙4.0的設備都是通過服務和特征來展示自己的,一個設備必然包含一個或多個服務,每個服務下面又包含若干個特征。特征是與外界交互的最小單位。
[peripheral discoverCharacteristics:nil forService:service];
}
}
10.在這個方法里會進行掃描所有特征值,進而調用外設查詢特征值的代理方法
//返回的藍牙特征值通知通過代理實現(不是所有的特征我們都需要,具體的要根據需求做區分,要跟硬件或者廠家溝通)
- (void)peripheral:(CBPeripheral*)peripheral didDiscoverCharacteristicsForService:(CBService*)service error:(NSError*)error{
}
在上面的方法中可以獲取到已經與中心連接的外設內有哪些特征值,并且可以通過打開通知來讀取特征值內數據的變化
[peripheral setNotifyValue:YES forCharacteristic:characterstic];
11.特征值內數據變化時會調用外設的代理方法
- (void) peripheral:(CBPeripheral*)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic*)characteristic error:(NSError*)error{}
12.在該方法里面讀取的特征值內容是NSData類型,我們也可以轉換為字節數組進行判斷
NSData*data = characteristic.value;
//data轉byte數組
Byte*testByte = (Byte*)[databytes];
for(inti=0;i<[datalength]; i++){
if((testByte[1] ==2)&&(testByte[0] ==2)) {
//做簡單的演示判斷
}
}
// 如果我們想給藍牙設備發送指令就要執行下面的方法(手動執行)
[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
------------------------- 分割線 干貨來了 -------------------------
OK 到了這里基本就夠用了,我們開發基本就是連接藍牙接受發送命令,上面的內容已經被無數的人寫爛了,如果就這樣我都不帶寫的!我最主要想寫的是藍牙的交互!
首先我們要明確的是藍牙發送過來的基本是以廣播包的形式發送過來的(廣播不理解的自行百度),以我們現在的項目為例,當藍牙發送過來的廣播包包含的內容如下
是不是有些看不懂了,廣播包中的數據為byte數組 A1 表示的就是接收數據(我們公司定義的是這個也可以是其他的),后面的圖上都有解釋,因為我們收到藍牙發送的時候是NSdata類型的數據,所以我們要把它轉化成byte類型,這樣我們才可以進一步操作了
Byte *testByte = (Byte *)[data bytes]; // 可以使用這個方法轉換
以這個圖片匯總的內容為例:
testByte[2] 表示的就是控制器狀態1
textByte[3] 表示的就是控制器狀態2
.......
到了這一步還有一點點的坑需要解決就是這下面的bit 0 bit 1 ....... 這些東西是啥? 這個就是控制器狀體藍牙發送過來的byte類型中的這幾個元素是以16進制發送過來的,當我們把它轉換成二進制就是下面的這個樣子
這回能看懂了嗎十六進制轉換成二進制就是這個樣子,二進制一共八位與之相對應的就是圖片中的狀態
例如: 圖一中 二進制位為11011110 對照圖二
二進制第一位為 1 表示電機缺項 (如果為0 表示 不缺項)
二進制第二位為 1 暫無狀態表示
二進制第三位為 0 表示不再巡航狀態
......
一個控制器狀態要傳兩個byte,因為一個byte能存儲的最大為 255 也就是16進制的 FF 超過這個就要丟失數據了所以我們要傳兩個byte 控制器狀態,如果你們公司的硬件給力其他的一些數據給你發過來的基本就可以直接展示不需要轉化(我們的硬件就挺給力的,忘了那些不需要轉換了)
下面我們開始發送數據
如圖其他的都還好,如驅動方式這個直接發送0 1 2 都行沒什么需要注意的,但是這個控制器控制設定需要我們注意一下,因為這個也是個二進制需要我們轉換一下在發送
這里我手動發送一個指令出去
// 176 十六進制 B1 (這個是發送的指令)
// 0 第二位沒確定意義
// 255 0xff 就是 255
// 1 代表確定方式設定
// 0 設置值 (當超過255 的時候這一位就起作用了)
// 1 設置值 (1 代表電驅動)
Byte byteData[6] = {176,0,255,1,0,1};
// 這個byte的意思就是發送指令給藍牙 驅動方式為電驅動
// 176 十六進制 B1 (這個是發送的指令)
// 0 第二位沒確定意義
// 255 0xff 就是 255
// 5 代表輪徑 (就是圖中的0x05)
// 255 設置值 (當超過255 的時候這一位就起作用了)
// 100 設置值 (1 代表電驅動)
當輪徑為355 的時候,一個字節不夠用,所以前面一位備用這個時候正好使用,255 后面一位則為剩下的值 100 合起來拼接之后就是 355 (硬件那邊會處理)
//Byte byteData[6] = {176,0,255,5,255,100}; // 說的就是這里的 255 和100 最后兩位
NSData * data = [NSData dataWithBytes:byteData length:6];
[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
---------------------------------- 更新----------------------------------
再給大家來點干貨進制轉換,藍牙發送過來的數據基本以十六進制為主,而我們要給藍牙發送的命令也需要轉換成16進制,有些展示內容還是二進制的,所以進制轉換必不可少.這里給大家整理了一下常用的進制轉換,這些都是經過實戰考驗的,項目代碼就不能給大家分享了,不過到這里差不多藍牙開發需要的大多數東西都已經OK了,剩下的可能就是大家公司具體要求不同的那些細節!(這里的代碼可以直接拷貝使用)
/**二進制轉十進制*/
+ (NSString *)toDecimalSystemWithBinarySystem:(NSString *)binary
{
int ll = 0 ;
int temp = 0 ;
for (int i = 0; i < binary.length; i ++)
{
temp = [[binary substringWithRange:NSMakeRange(i, 1)] intValue];
temp = temp * powf(2, binary.length - i - 1);
ll += temp;
}
NSString * result = [NSString stringWithFormat:@"%d",ll];
return result;
}
/**十進制轉二進制*/
+ (NSString *)toBinarySystemWithDecimalSystem:(NSInteger)decimal
{
NSInteger num = decimal;//[decimal intValue];
NSInteger remainder = 0; //余數
NSInteger divisor = 0; //除數
NSString * prepare = @"";
while (true)
{
remainder = num%2;
divisor = num/2;
num = divisor;
prepare = [prepare stringByAppendingFormat:@"%ld",remainder];
if (divisor == 0)
{
break;
}
}
NSString * result = @"";
for (NSInteger i = prepare.length - 1; i >= 0; i --)
{
result = [result stringByAppendingFormat:@"%@",
[prepare substringWithRange:NSMakeRange(i , 1)]];
}
return result;
}
//將十進制轉化為十六進制
+ (NSString *)ToHex:(int)tmpid
{
NSString *nLetterValue;
NSString *str =@"";
long long int ttmpig;
for (int i = 0; i<9; i++) {
ttmpig=tmpid%16;
tmpid=tmpid/16;
switch (ttmpig)
{
case 10:
nLetterValue =@"a";break;
case 11:
nLetterValue =@"b";break;
case 12:
nLetterValue =@"c";break;
case 13:
nLetterValue =@"d";break;
case 14:
nLetterValue =@"e";break;
case 15:
nLetterValue =@"f";break;
default:nLetterValue=[[NSString alloc]initWithFormat:@"%lli",ttmpig];
}
str = [nLetterValue stringByAppendingString:str];
if (tmpid == 0) {
break;
}
}
return str;
}
// 十六轉二
+ (NSString *)getBinaryByhex:(NSString *)hex
{
NSMutableDictionary *hexDic = [[NSMutableDictionary alloc] init];
hexDic = [[NSMutableDictionary alloc] initWithCapacity:16];
[hexDic setObject:@"0000" forKey:@"0"];
[hexDic setObject:@"0001" forKey:@"1"];
[hexDic setObject:@"0010" forKey:@"2"];
[hexDic setObject:@"0011" forKey:@"3"];
[hexDic setObject:@"0100" forKey:@"4"];
[hexDic setObject:@"0101" forKey:@"5"];
[hexDic setObject:@"0110" forKey:@"6"];
[hexDic setObject:@"0111" forKey:@"7"];
[hexDic setObject:@"1000" forKey:@"8"];
[hexDic setObject:@"1001" forKey:@"9"];
[hexDic setObject:@"1010" forKey:@"A"];
[hexDic setObject:@"1011" forKey:@"B"];
[hexDic setObject:@"1100" forKey:@"C"];
[hexDic setObject:@"1101" forKey:@"D"];
[hexDic setObject:@"1110" forKey:@"E"];
[hexDic setObject:@"1111" forKey:@"F"];
[hexDic setObject:@"1010" forKey:@"a"];
[hexDic setObject:@"1011" forKey:@"b"];
[hexDic setObject:@"1100" forKey:@"c"];
[hexDic setObject:@"1101" forKey:@"d"];
[hexDic setObject:@"1110" forKey:@"e"];
[hexDic setObject:@"1111" forKey:@"f"];
NSMutableString *binaryString=[[NSMutableString alloc] init];
for (int i=0; i<[hex length]; i++) {
NSRange rage;
rage.length = 1;
rage.location = i;
NSString *key = [hex substringWithRange:rage];
//NSLog(@"%@",[NSString stringWithFormat:@"%@",[hexDic objectForKey:key]]);
binaryString = [NSString stringWithFormat:@"%@%@",binaryString,[NSString stringWithFormat:@"%@",[hexDic objectForKey:key]]];
}
// NSLog(@"轉化后的二進制為:%@",binaryString);
return binaryString;
}
-------------------------------- 在更新一輪 --------------------------------
正常來說一般情況下藍牙產品通常不怎么需要我們手動傳輸信息什么的,我們app通常做好接受工作就可以,但是我們經常要做一些設置操作這就需要我們主動向外發送信息!比如如下兩種情況
第一種還好,關鍵是第二種很多剛剛開發藍牙的朋友都絕對會碰到這樣的問題如果說沒有好的思路這個地方絕對困擾你很久(當時想了半天才想明白怎么搞)當然我的做法不一定是最優的方法暫時就是為大家提供個思路,如果說大家有什么其他好的方法希望能夠跟我說一下謝謝
先說說問題是什么樣的 :
比如說我開啟第一個開關則要發送 : 這條數據二進制 00000001 轉換成為 十 進制 為 1 Byte byteData[6] = {176,0,255,3,0,1};
開啟第二個開關則要發送 00000010 十進制 為2 發送 Byte byteData[6] = {176,0,255,3,0,2};
........
這個是單個開關開啟,如果多個開關同時開啟怎么辦?
比如說前前三個開關同時開啟怎么辦,這個要發送00000111 , 轉化為十進制則為 7 Byte byteData[6] = {176,0,255,3,0,7};
其實到這為止看著都沒什么問題怎么發送指令我們之前都已經說好了,但是這個開關確實有點困難,跟上邊不一樣不好弄,這八位位數 一共有 256 種組合方式,我們都要把所有的開關都算成一個整體的二進制!初始階段我們給他一個默認的值.
1 : 定義一個 開關數組
@property (nonatomic,strong)NSMutableArray *setSwitchArray;
2 : 懶加載
-(NSMutableArray *)setSwitchArray{
// 有朋友可能會問了這是什么東西,暫時階段我們數據存儲的是本地
NSString *Path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) lastObject] stringByAppendingString:[NSString stringWithFormat:@"/setSwitchArray.plist"]];
_setSwitchArray = [NSMutableArray arrayWithContentsOfFile:Path];
if (!_setSwitchArray) {
_setSwitchArray = [NSMutableArray arrayWithArray:@[@0,@0,@0,@0,@0,@0,@0,@0]];
[_setSwitchArray writeToFile:Path atomically:YES];
}
return _setSwitchArray;
}
好了到這里發送和接收就基本OK了,這里面的內容基本的藍牙功能實現基本可以滿足了,可能還有有一些其他的問題,我還會后續的慢慢更新,有喜歡的朋友可以給我點個贊,有什么不嚴謹錯誤的地方也歡迎大家指正!
注:上方分割線之前的關于藍牙代理的相關方法選自我的同事<a href = "http://www.lxweimin.com/p/1461ebb498ab">[愛吃蘋果的兔子 ]