前段時(shí)間接了一個(gè)項(xiàng)目有關(guān)藍(lán)牙的颁湖,但是自己之前沒怎么接觸過藍(lán)牙俗他,就再網(wǎng)上各種search相關(guān)的文章屎媳,但是感覺都不是很具體夺溢,現(xiàn)在貼出來自己做的項(xiàng)目藍(lán)牙模塊實(shí)現(xiàn)過程,希望和大家共同學(xué)習(xí)烛谊,其實(shí)并沒有多難风响,如果你在做藍(lán)牙這一塊的話,我建議你下載一個(gè)App---LightBlue晒来。
1.需求
主要是做一個(gè)教育類App钞诡,連接藍(lán)牙進(jìn)行數(shù)據(jù)傳輸,并且對藍(lán)牙硬件進(jìn)行數(shù)據(jù)讀取和寫入湃崩。并對藍(lán)牙進(jìn)行一對一的綁定荧降,我做的這個(gè)藍(lán)牙硬件一共分為兩部分,一部分為藍(lán)牙主體(內(nèi)置藍(lán)牙硬件攒读,以下簡稱教棒)朵诫,還有一部分就是一個(gè)IC卡片(作用相當(dāng)于一個(gè)名片,我理解起來其實(shí)就是一個(gè)擁有獨(dú)立id的一個(gè)東西)薄扁。
2.實(shí)現(xiàn)功能
App連接并綁定指定的藍(lán)牙設(shè)備教棒剪返,對教棒進(jìn)行指令發(fā)送和應(yīng)答。其實(shí)藍(lán)牙設(shè)備收發(fā)數(shù)據(jù)通過的是指令邓梅,和網(wǎng)絡(luò)請求差不多:
第一步:當(dāng)然要連接到藍(lán)牙脱盲,App開啟藍(lán)牙并尋找藍(lán)牙信號(hào),截獲到藍(lán)牙設(shè)備的廣播信號(hào)(前提是藍(lán)牙要開啟狀態(tài))日缨,判斷藍(lán)牙的名字或者別的是不是自己要連接的藍(lán)牙硬件钱反,如果是直接連接。
第二步:收發(fā)數(shù)據(jù)
---App發(fā)給藍(lán)牙一個(gè)指令(一般為十六進(jìn)制的data匣距,實(shí)現(xiàn)方式就是寫數(shù)據(jù)到藍(lán)牙)面哥,該指令代表我要獲取你的系統(tǒng)地址:"嗨,藍(lán)牙把你的地址發(fā)給我"毅待。
---藍(lán)牙收到指令后智能判斷指令的含義(這部分一般藍(lán)牙硬件廠商會(huì)給出一個(gè)藍(lán)牙協(xié)議文檔尚卫,來表明各個(gè)口令和具體代表的含義),會(huì)反饋一個(gè)信息給App:我收到你的指令了尸红, 然后藍(lán)牙會(huì)繼續(xù)返回App要的數(shù)據(jù):“噢~~~吱涉,你想要我的系統(tǒng)地址是吧刹泄,拿去***這就是我的系統(tǒng)地址,你可以用這個(gè)來作為我的唯一標(biāo)識(shí)符”怎爵。
---App實(shí)現(xiàn)相應(yīng)的代碼來接收該數(shù)據(jù)循签。收到數(shù)據(jù)后一般要給藍(lán)牙一個(gè)回復(fù),有的硬件沒做這一塊:(嗨疙咸,藍(lán)牙我收到你的數(shù)據(jù)了县匠,ps:這怎么跟TCP三次握手差不多啊)和網(wǎng)絡(luò)請求一樣收到得數(shù)據(jù)一般都是data類型撒轮,App端要進(jìn)行數(shù)據(jù)解析乞旦,并作進(jìn)一步處理:存儲(chǔ)或者上傳到服務(wù)器進(jìn)行賬號(hào)藍(lán)牙綁定等。
---大體流程就是這樣题山,我寫的比較簡單明了兰粉。
建立中心角色—掃描外設(shè)(discover)—連接外設(shè)(connect)—掃描外設(shè)中的服務(wù)和特征(discover)—與外設(shè)做數(shù)據(jù)交互(explore and interact)—斷開連接(disconnect)。
3.具體實(shí)現(xiàn)細(xì)節(jié)
(1)建立中心角色(這里可以是手機(jī)也可以是藍(lán)牙硬件,我是手機(jī)作為中心角色進(jìn)行掃描藍(lán)牙信號(hào)的)顶瞳。首先在我自己類的頭文件中要包含CoreBluetooth的頭文件玖姑,并繼承兩個(gè)協(xié)議<CBCentralManagerDelegate,CBPeripheralDelegate>,代碼如下:
創(chuàng)建一個(gè)CBCentralManager成員變量作為中心
@property(nonatomic,retain)CBCentralManager * manager;
并在viewDidLoad中實(shí)例化
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
App首先要判斷手機(jī)的藍(lán)牙是否開啟
這里是Did代理函數(shù)是自動(dòng)執(zhí)行的
//藍(lán)牙狀態(tài)改變
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSString * message;
switch (central.state) {
case 0:
message = @"初始化中慨菱,請稍后……";
break;
case 1:
message = @"設(shè)備不支持狀態(tài)焰络,過會(huì)請重試……";
break;
case 2:
message = @"設(shè)備未授權(quán)狀態(tài),過會(huì)請重試……";
break;
case 3:
message = @"設(shè)備未授權(quán)狀態(tài)符喝,過會(huì)請重試……";
break;
case 4:
message = @"尚未打開藍(lán)牙闪彼,請?jiān)谠O(shè)置中打開……";
break;
case 5:
message = @"藍(lán)牙已經(jīng)成功開啟,稍后……";
break;
default:
break;
}
if (_manager.state != CBCentralManagerStatePoweredOn ) {//如果沒有開啟提示是否開啟
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"開啟藍(lán)牙" message:nil delegate:self cancelButtonTitle:@"不開啟" otherButtonTitles:@"開啟", nil];
alertView.tag = OPENBLUETOOTH;
[alertView show];
}else{
//如果已經(jīng)手機(jī)開啟了藍(lán)牙协饲,那么便掃描藍(lán)牙硬件
[self.manager scanForPeripheralsWithServices:nil options:nil];
}
}
#pragma mark - alertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
if (alertView.tag == OPENBLUETOOTH) {
if (buttonIndex == 1) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=Bluetooth"]];
}
}else if (alertView.tag == ISBINDALERT){
[self.navigationController popViewControllerAnimated:YES];
}else if (alertView.tag == RESCANALERT){
if (buttonIndex == 1) {
[self.manager scanForPeripheralsWithServices:nil options:nil];
[self initTimerAndTimeCount];
}else{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
//手機(jī)藍(lán)牙發(fā)現(xiàn)了一個(gè)藍(lán)牙硬件peripheral//每發(fā)現(xiàn)一個(gè)藍(lán)牙設(shè)備都會(huì)調(diào)用此函數(shù)(如果想展示搜索到得藍(lán)牙可以逐一保存peripheral并展示)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"發(fā)現(xiàn)藍(lán)牙設(shè)備:%@",peripheral.name);//
if ([peripheral.name isEqual:藍(lán)牙的名字]) {
self.peripheral = peripheral;
[self.manager connectPeripheral:self.peripheral options:nil];//如果是自己要連接的藍(lán)牙硬件畏腕,那么進(jìn)行連接
}
}
//返回的藍(lán)牙服務(wù)通知通過代理實(shí)現(xiàn)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService * service in peripheral.services) {
NSLog(@"Service found with UUID :%@",service.UUID);
// if ([service.UUID isEqual:[CBUUID UUIDWithString:@"18F0"]]) {
[peripheral discoverCharacteristics:nil forService:service];
// }
}
}
//查找到該設(shè)備所對應(yīng)的服務(wù)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
//每個(gè)peripheral都有很多服務(wù)service(這個(gè)依據(jù)藍(lán)牙而定),每個(gè)服務(wù)都會(huì)有幾個(gè)特征characteristic茉稠,區(qū)分這些就是UUID
//這里可以利用開頭說的LightBlue軟件連接藍(lán)牙看看你的藍(lán)牙硬件有什么服務(wù)和每個(gè)服務(wù)所包含的特征描馅,然后根據(jù)你的協(xié)議里面看看你需要用到哪個(gè)特征的哪個(gè)服務(wù)
for (CBCharacteristic * characteristic in service.characteristics) {
// NSLog(@"查找到的服務(wù)(屬性)%@",characteristic);
//所對應(yīng)的屬性用于接收和發(fā)送數(shù)據(jù)
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];//監(jiān)聽這個(gè)服務(wù)發(fā)來的數(shù)據(jù)
[peripheral readValueForCharacteristic:characteristic];//主動(dòng)去讀取這個(gè)服務(wù)發(fā)來的數(shù)據(jù)
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF1"]]) {
_characteristic = characteristic;
//*****此處已經(jīng)連接好藍(lán)牙,可以在這里給藍(lán)牙發(fā)指令而线,也就是寫入數(shù)據(jù)
// [self sendMessageWithType:_type];//1.查詢數(shù)量
例:
NSMutableData *value = [NSMutableData data];
在這里把數(shù)據(jù)轉(zhuǎn)成data存儲(chǔ)到value里面
NSLog(@"%@",value);
[_peripheral writeValue:value forCharacteristic:_characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
//接收數(shù)據(jù)的函數(shù).處理藍(lán)牙發(fā)過來得數(shù)據(jù) 讀數(shù)據(jù)代理铭污,這里已經(jīng)收到了藍(lán)牙發(fā)來的數(shù)據(jù)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"Error discovering characteristics: %@", [error localizedDescription]);
return;
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
NSLog(@"收到藍(lán)牙發(fā)來的數(shù)據(jù)%@",characteristic.value);
NSString * string = [self hexadecimalString:characteristic.value];
//在這里解析收到的數(shù)據(jù),一般是data類型的數(shù)據(jù)吞获,這里要根據(jù)藍(lán)牙廠商提供的協(xié)議進(jìn)行解析并且配合LightBlue來查看數(shù)據(jù)結(jié)構(gòu)况凉,我當(dāng)時(shí)收到的數(shù)據(jù)是十六進(jìn)制的數(shù)據(jù)但是是data類型谚鄙,所以我先講data解析出來之后轉(zhuǎn)為十進(jìn)制來使用各拷。具體方法后面我會(huì)貼出
//還有一點(diǎn)收到數(shù)據(jù)后有的硬件是需要應(yīng)答的,如果應(yīng)答的話就是在這里再給藍(lán)牙發(fā)一個(gè)指令(寫數(shù)據(jù)):“我收到發(fā)的東西了闷营,你那邊要做什么操作可以做了”烤黍。
}
}
//*****寫數(shù)據(jù)代理知市,上面寫入數(shù)據(jù)之后就會(huì)自動(dòng)調(diào)用這個(gè)函數(shù)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"%@",characteristic.UUID);
if (error) {
NSLog(@"Error changing notification state: %@",[error localizedDescription]);
}
//其實(shí)這里貌似不用些什么(我是沒有寫只是判斷了連接狀態(tài))
}
如果要重復(fù)讀寫數(shù)據(jù),可以在每次收到數(shù)據(jù)之后發(fā)送讀取的指令速蕊,我做的項(xiàng)目就是一條一條的收數(shù)據(jù):發(fā)一個(gè)讀取指令嫂丙,返回?cái)?shù)據(jù),App做出應(yīng)答(其實(shí)就是再發(fā)一個(gè)應(yīng)答的指令)规哲,解析之后再發(fā)一條讀取指令跟啤,藍(lán)牙收到指令后刪除這個(gè)數(shù)據(jù),如此反復(fù)唉锌,直到藍(lán)牙沒有數(shù)據(jù)了隅肥。停止發(fā)送。
之前我有收到我做的項(xiàng)目還有一個(gè)IC卡袄简,藍(lán)牙可以對IC進(jìn)行讀取腥放,并生成特殊數(shù)據(jù)保存到藍(lán)牙內(nèi)存中,這個(gè)跟藍(lán)牙連接并沒有什么太大關(guān)聯(lián)绿语,我的項(xiàng)目中用到秃症,但我覺得大多數(shù)設(shè)備應(yīng)該不會(huì)用到這,就沒有詳細(xì)講吕粹。
4.我用到的相關(guān)解析函數(shù)
//將傳入的NSData類型轉(zhuǎn)換成NSString并返回
- (NSString*)hexadecimalString:(NSData *)data{
NSString* result;
const unsigned char* dataBuffer = (const unsigned char*)[data bytes];
if(!dataBuffer){
return nil;
}
NSUInteger dataLength = [data length];
NSMutableString* hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for(int i = 0; i < dataLength; i++){
[hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]];
}
result = [NSString stringWithString:hexString];
return result;
}
//將傳入的NSString類型轉(zhuǎn)換成NSData并返回
- (NSData*)dataWithHexstring:(NSString *)hexstring{
NSData* aData;
return aData = [hexstring dataUsingEncoding: NSUTF16StringEncoding];
}
// 十六進(jìn)制轉(zhuǎn)換為普通字符串的种柑。
- (NSString *)stringFromHexString:(NSString *)hexString { //
char *myBuffer = (char *)malloc((int)[hexString length] / 2 + 1);
bzero(myBuffer, [hexString length] / 2 + 1);
for (int i = 0; i < [hexString length] - 1; i += 2) {
unsigned int anInt;
NSString * hexCharStr = [hexString substringWithRange:NSMakeRange(i, 2)];
NSScanner * scanner = [[NSScanner alloc] initWithString:hexCharStr];
[scanner scanHexInt:&anInt];
myBuffer[i / 2] = (char)anInt;
}
NSString *unicodeString = [NSString stringWithCString:myBuffer encoding:4];
NSLog(@"------字符串=======%@",unicodeString);
return unicodeString;
}
//轉(zhuǎn)換成十進(jìn)制
- (NSString *)to10:(NSString *)num
{
NSString *result = [NSString stringWithFormat:@"%ld", strtoul([num UTF8String],0,16)];
return result;
}
//轉(zhuǎn)換成十六進(jìn)制
- (NSString *)to16:(int)num
{
NSString *result = [NSString stringWithFormat:@"%@",[[NSString alloc] initWithFormat:@"%1x",num]];
if ([result length] < 2) {
result = [NSString stringWithFormat:@"0%@", result];
}
return result;
}
5.總結(jié)
當(dāng)時(shí)我寫這個(gè)項(xiàng)目的時(shí)候各種查資料,Apple官方的demo也被我下載下來研究匹耕,好幾天不知道怎么搞莹规,也沒有仔細(xì)看廠商給的藍(lán)牙協(xié)議文檔,這里提醒大家泌神,一定要好好研究廠商給的藍(lán)牙協(xié)議文檔良漱,因?yàn)楹枚鄸|西都在那上面,沒有那個(gè)絕對做不出來欢际!第一次寫技術(shù)分享母市,寫的不好,歡迎指正损趋,希望可以幫到一些Developer患久。