概念篇
什么是蓝牙
蓝牙(Bluetooth®):是一种无线技术标准(短距离无线通信技术),可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案。蓝牙可连接多个设备,克服了数据同步的难题。如今蓝牙由蓝牙技术联盟(Bluetooth Special Interest Group,简称SIG)管理。
蓝牙技术联盟
蓝牙技术联盟是一个以制定蓝牙规范,以推动蓝牙技术为宗旨的跨国组织。它拥有蓝牙的商标,负责认证制造厂商,授权他们使用蓝牙技术与蓝牙标志,但是它本身不负责蓝牙装置的设计、生产及贩售。
Bluetooth SIG的发起公司是Agere、爱立信、IBM、英特尔、微软、摩托罗拉、诺基亚和东芝。2006年10月13日,Bluetooth SIG(蓝牙技术联盟)宣布联想公司取代IBM在该组织中的创始成员位置,并立即生效。通过成为创始成员,联想将与其他业界领导厂商杰尔系统公司、爱立信公司、英特尔公司、微软公司、摩托罗拉公司、诺基亚公司和东芝公司一样拥有蓝牙技术联盟董事会中的一席,并积极推动蓝牙标准的发展。除了创始成员以外,Bluetooth SIG还包括200多家联盟成员公司以及约6000家应用成员企业。而企业只要使用“蓝牙(Bluetooth)”相关商标在市场上销售产品,都必须向蓝牙技术联盟交纳商标使用费和产品认证费用。
蓝牙4.x时代
蓝牙4.0是2012年最新蓝牙版本,是3.0的升级版本;较3.0版本更省电、成本低、3毫秒低延迟、超长有效连接距离、AES-128加密等;通常用在蓝牙耳机、蓝牙音箱等设备上。
蓝牙技术联盟(Bluetooth SIG)2010年7月7日宣布,正式采纳蓝牙4.0核心规范(Bluetooth Core Specification Version 4.0 ),并启动对应的认证计划。会员厂商可以提交其产品进行测试,通过后将获得蓝牙4.0标准认证。 该技术拥有极低的运行和待机功耗,使用一粒纽扣电池甚至可连续工作数年之久。
2014年12月,蓝牙技术联盟公布蓝牙4.2标准。
市场上最主要用的还是4.2(蓝牙4.2发布于2014年12月2日) 。
蓝牙5.x时代
2016年6月16日,蓝牙技术联盟在伦敦发布全新的蓝牙5.0标准。
2019年1月29日,蓝牙技术联盟(SIG)宣布蓝牙5.1正式公布。
蓝牙5.0有哪些提升?
1:4倍信号范围
2:2倍连接速度(新版本的蓝牙传输速度上限为24Mbps)
3:蓝牙广播8倍数据传输
这意味着:
蓝牙的信号传输距离能够覆盖整户公寓,甚至是整栋小型楼房,而不再是以往的一个房间。
更快的传输速度,使反应更快、性能更高的蓝牙设备成为可能。
更稳定可靠的蓝牙连接。
更好的商用蓝牙前景。大幅增强的蓝牙广播,使基于蓝牙信标的物联网应用更加强大,比如某商店向店内消费者的手机推送广告和优惠券。
蓝牙外设
BLE:bluetouch low energy,蓝牙4.0设备因为低功耗,所有也叫作BLE。苹果在iphone4s及之后的手机型号开始支持蓝牙4.0,这也是最常见的蓝牙设备。低于蓝牙4.0协议的设备需要进行MFI认证,
什么是MFi认证?
苹果MFi认证,是苹果公司(Apple Inc.)对其授权配件厂商生产的外置配件的一种标识使用许可,是apple公司 “Made for iOS”的英文缩写。
常用库
iOS中提供了4个框架用于实现蓝牙连接。
1.GameKit.framework(用法简单)
只能用于iOS设备之间的同个应用内连接,多用于游戏(eg:棋牌类),从iOS7开始过期
2.MultipeerConnectivity.framework(代替1)
只能用于iOS设备之间的连接,从iOS7开始引入,主要用于非联网状态下,通过wifi或者蓝牙进行文件共享(仅限于沙盒的文件),多用于附近无网聊天
3.ExternalAccessory.framework(MFi)
可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内很少)
4.CoreBluetooth.framework(常用 Apple推行蓝牙的核心)
可用于第三方蓝牙设备交互,必须要支持蓝牙4.0
硬件至少是4s,系统至少是iOS6 (现iPhoneX 蓝牙5.0)
蓝牙4.0以低功耗著称,一般也叫BLE(Bluetooth Low Energy)
目前应用比较多的案例:运动手环,嵌入式设备,智能家居
我们公司对接的外设比较多 心率计、踏频、码表等运动蓝牙设备
1.CoreBluetooth框架的核心其实是两个东西,peripheral和central, 能了解成外设和中心,就是你的苹果手机就是中心,外部蓝牙称为外设。
2.服务和特征(service characteristic):简而言之,外部蓝牙中它有若干个服务service(服务你能了解为蓝牙所拥有的可以力),而每个服务service下拥有若干个特征characteristic(特征你能了解为解释这个服务的属性)。
3.Descriptor(形容)使用来形容characteristic变量的属性。例如,一个descriptor能规定一个可读的形容,或者者一个characteristic变量可接受的范围,或者者一个characteristic变量特定的单位。
苹果自身有一个操作蓝牙的库CoreBluetooth.framework,这个是大多数人员进行蓝牙开发的首选框架,除此之外目前github还有一个比较流行的对原生框架进行封装的三方库BabyBluetooth,它的机制是将CoreBluetooth中众多的delegate写成了block方法,有兴趣的同学可以了解下。下面主要介绍的是原生蓝牙库的知识。
电脑、Pad、手机作为中心,心跳监听器作为外设,这种中心外设模式是最常见的。简单理解就是,发起连接的是中心设备(Central),被连接的是外围设备(Peripheral),对应传统的客户机-服务器体系结构。Central能够扫描侦听到,正在播放广告包的外设。
工具 --BabyBluetooth
BabyBluetooth是一个最简单易用的蓝牙库,基于CoreBluetooth的封装,并兼容ios和Mac osx。
BabyBluetooth的优点:
1:基于原生CoreBluetooth框架封装的轻量级的开源库,可以帮你更简单地使用CoreBluetooth API。
2:CoreBluetooth所有方法都是通过委托完成,代码冗余且顺序凌乱。BabyBluetooth使用block方法,可以重新按照功能和顺序组织代码,并提供许多方法减少蓝牙开发过程中的代码量。
3:链式方法体,代码更简洁、优雅。
4:通过channel切换区分委托调用,并方便切换
5:便利的工具方法
6:完善的文档,且项目处于活跃状态,不断的更新中
7:github上star最多的纯Bluetooch类库(非PhoneGap和SensorTag项目)
备注:具体参考 https://github.com/coolnameismy/BabyBluetooth
蓝牙连接的主要步骤
1、创建一个CBCentralManager实例来进行蓝牙管理;
2、搜索扫描外围设备;
3、连接外围设备;
4、获得外围设备的服务;
5、获得服务的特征;
6、从外围设备读取数据;
7、给外围设备发送(写入)数据。
服务与特征
外设可以包含一个或多个服务(CBService),服务是用于实现装置的功能或特征数据相关联的行为集合。 而每个服务又对应多个特征(CBCharacteristic),特征提供外设服务进一步的细节,外设,服务,特征对应的数据结构如下所示
iOS蓝牙中的进制转换,数据格式转换
https://www.cnblogs.com/W-Kr/p/5398490.html
前言
现阶段智能家居,还是比较火热门的话题。那么一部分智能家居离不开移动端。作为中间媒介,蓝牙的开发也是必要掌握的技能之一。蓝牙开发很重要的是蓝牙设备的协议掌握。如果没有掌握协议,那么你就无法解锁更多新姿势。
设备--智能锁
我们今天要介绍的设备是一个蓝牙锁。在开发蓝牙锁之前首先我们要掌握这个一个锁的基本数据。下载一个蓝牙助手,打开蓝牙助手,找到我们的智能设备--蓝牙锁。(该锁名称--LOCKSUS).点击链接进入详情页面入下图:
突然一看,其实是没有任何头绪。但是有一个是可以知道的@“LOCKSUS”。这个是锁的名字。
下面两个属性分写写着:1:写无回复和通知两个特征。
以上了解那么多就可以了,然后继续看协议。(由于协议不能随便展示,所以此处请看我的pdf文档)。
协议
Length (2 Bytes) + command code (1 Bytes) Data(N Bytes)+CheckSum(2 Bytes)
Length = sizeof(Command code + Data)
CheckSum = sum of (Command Code + Data)
例如,如下命令
Req: length(2) | REQ_BIND (1) | Key(6) | CS(2).
Command 为 REQ_BIND (1) Data为 Key(6)
REQ_BIND (1) 0xc6 Key(6):123456
备注:被请求一方,收到Request指令后,都要ack一个对应指令, 所有命令都以Req/Ack的 模式成对出现。
流程图
工具 --BabyBluetooth
BabyBluetooth按照按照说明使用。
- (void)viewDidLoad {
[super viewDidLoad];
[self setUpView];
//初始化BabyBluetooth 蓝牙库
self.babyBluetooth = [BabyBluetooth shareBabyBluetooth];
//设置蓝牙委托
[self babyDelegate];
}
第二步,在babyDelegate方法中先试下如下代理。
//过滤器
//设置查找设备的过滤器
[self.babyBluetooth setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) {
//最常用的场景是查找某一个前缀开头的设备 most common usage is discover for peripheral that name has common prefix
if ([peripheralName hasPrefix:@"LOCKSUS"] ) {
return YES;
}
return NO;
}];
该代理是过滤功能,防止中心设备搜索过多的非目标设备。设置后可以指定查找名叫@“LOCKSUS”的设备。
//设置连接的设备的过滤器
__block BOOL isFirst = YES;
[self.babyBluetooth setFilterOnConnectToPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) {
//这里的规则是:连接第一个AAA打头的设备
if(isFirst && [peripheralName hasPrefix:@"LOCKSUS"]){
isFirst = NO;
return YES;
}
return NO;
}];
该代理是过滤功能,防止中心设备连接过多的非目标设备。设置后可以指定连接名叫@“LOCKSUS”的设备。
//蓝牙网关初始化和委托方法设置
-(void)babyDelegate{
__weak typeof(&*self) weakSelf= self;
//设置扫描到设备的委托
[self.babyBluetooth setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
if ([peripheral.name isEqualToString:@"LOCKSUS"]) {
NSLog(@"搜索到了设备:%@",peripheral.name);
NSString *bleName = [advertisementData objectForKey:@"kCBAdvDataLocalName"];
weakSelf.macAddress = [NSString convertDataToHexStr:advertisementData[@"kCBAdvDataManufacturerData"]];
NSString *oriData = [TestViewController getLockOriData:advertisementData[@"kCBAdvDataManufacturerData"]];
weakSelf.peripheral = peripheral;
NSLog(@"扫描成功:%@%@%@",weakSelf.peripheral,bleName,oriData);
}
}];
//设置设备连接成功的委托
[self.babyBluetooth setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) {
NSLog(@"设备:%@--连接成功",peripheral.name);
[peripheral discoverServices:@[[CBUUID UUIDWithString:UUID_SERVICE]]];
}];
//设置发现设备的Services的委托
[self.babyBluetooth setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error) {
CBService * __nullable findService = nil;
// 遍历服务
// 启动特征扫描
for (CBService *service in peripheral.services){
NSLog(@"搜索到服务:%@",service.UUID.UUIDString);
if ([[service UUID]isEqual:[CBUUID UUIDWithString:UUID_SERVICE]]) {
findService = service;
}
}
//一下代码是 要目标服务的特征值 不写是不会触动 获取特征值的回调的。(下面一个)
if (findService){
[peripheral discoverCharacteristics:NULL forService:findService];
}
}];
//设置发现设service的Characteristics的委托
[self.babyBluetooth setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
NSLog(@"===service name:%@",service.UUID);
/*
charateristic name is :6E40FF02-B5A3-F393-E0A9-E50E24DCCA9E (可写入属性)
charateristic name is :6E40FF03-B5A3-F393-E0A9-E50E24DCCA9E (通知属性)
*/
for (CBCharacteristic *c in service.characteristics) {
NSLog(@"charateristic name is :%@",c.UUID);
//此时我们是要准备写入数据的,所以要找到相对应的特征值
if ([c.UUID isEqual:[CBUUID UUIDWithString:UUID_USER0_WRITE]]) {
[peripheral readValueForCharacteristic:c];
weakSelf.wChar = c;
[peripheral setNotifyValue:YES forCharacteristic:c];
}else{
weakSelf.ntfChar = c;
[peripheral setNotifyValue:YES forCharacteristic:weakSelf.ntfChar];
}
}
if ([weakSelf.macAddress isEqualToString:@"686f01"]) {
// 已经绑定过了
[[FBluetoothLockManage sharedSingleton] reqMatchBindDevice:peripheral wChar:weakSelf.wChar wPinCode:@"123456"];
}else{
//f00 未绑定
[[FBluetoothLockManage sharedSingleton] reqBindDevice:peripheral wChar:weakSelf.wChar wPinCode:@"123456"];
}
}];
//设置读取characteristics的委托
[self.babyBluetooth setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error) {
NSLog(@"characteristic name:%@ value is:%@",characteristics.UUID,characteristics.value);
const int8_t *rspData = (const int8_t *)[characteristics.value bytes];
if(rspData == nil){
return;
}
Byte *rspDataByte = (Byte *)[characteristics.value bytes];
NSLog(@"rspDataByte=%s",rspDataByte);
[weakSelf updateDeviceAck2AppRspData:characteristics.value];
}];
//设置发现characteristics的descriptors的委托
[self.babyBluetooth setBlockOnDiscoverDescriptorsForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error) {
NSLog(@"===characteristic name:%@",characteristic.service.UUID);
for (CBDescriptor *d in characteristic.descriptors) {
NSLog(@"CBDescriptor name is :%@",d.UUID);
}
}];
//设置读取Descriptor的委托
[self.babyBluetooth setBlockOnReadValueForDescriptors:^(CBPeripheral *peripheral, CBDescriptor *descriptor, NSError *error) {
NSLog(@"Descriptor name:%@ value is:%@",descriptor.characteristic.UUID, descriptor.value);
}];
}
-(void)clickScan{
NSLog(@"开始扫描外设");
//设置委托后直接可以使用,无需等待CBCentralManagerStatePoweredOn状态
self.babyBluetooth.scanForPeripherals().begin();
}
-(void)clickConnect{
if([_peripheral isKindOfClass:[CBPeripheral class]]){
// 开始连接外部设备
self.babyBluetooth.having(self.peripheral).connectToPeripherals().begin();
NSLog(@"开始连接....");
}else{
NSLog(@"连接对象不正确");
}
}
一整套流程走下来,控制台输出log
根据如下log 更有利于我们理解整个流程。
2019-12-07 13:40:04.608306+0800 Finance[19739:5874003] 开始扫描外设
2019-12-07 13:40:07.852921+0800 Finance[19739:5874003] str == 686f01
2019-12-07 13:40:07.853291+0800 Finance[19739:5874003] 扫描成功:<CBPeripheral: 0x28014c500, identifier = EADF26D8-D221-D04F-4257-8B483F40D108, name = LOCKSUS, state = disconnected>
2019-12-07 13:40:25.449600+0800 Finance[19739:5874003] 开始扫描外设
2019-12-07 13:40:25.456177+0800 Finance[19739:5874003] str == 686f01
2019-12-07 13:40:25.456404+0800 Finance[19739:5874003] 扫描成功:<CBPeripheral: 0x28014c500, identifier = EADF26D8-D221-D04F-4257-8B483F40D108, name = LOCKSUS, state = disconnected>
2019-12-07 13:40:25.918225+0800 Finance[19739:5874003] 开始连接....
2019-12-07 13:40:26.289438+0800 Finance[19739:5874003] 外设连接成功
2019-12-07 13:40:26.732115+0800 Finance[19739:5874003] 发现外设服务
2019-12-07 13:40:26.787383+0800 Finance[19739:5874003] 获取到特征值
2019-12-07 13:40:26.789573+0800 Finance[19739:5874003] NSData dec = {length = 6, bytes = 0x010203040506}
2019-12-07 13:40:26.790020+0800 Finance[19739:5874003] dtCrc : 81f67724
2019-12-07 13:40:26.790555+0800 Finance[19739:5874003] 茭白绑定数据 上锁密码是{length = 6, bytes = 0x2477f6810000}
2019-12-07 13:40:26.790855+0800 Finance[19739:5874003] bind cs : 02d8
2019-12-07 13:40:26.791298+0800 Finance[19739:5874003] bind Data : {length = 11, bytes = 0x0700c62477f6810000d802}
2019-12-07 13:40:26.791720+0800 Finance[19739:5874003] 茭白 数据 wr 2 bind data:{length = 11, bytes = 0x0700c62477f6810000d802}
2019-12-07 13:40:26.792888+0800 Finance[19739:5874003] NSData dec = {length = 6, bytes = 0x010203040506}
2019-12-07 13:40:26.793177+0800 Finance[19739:5874003] dtCrc : 81f67724
2019-12-07 13:40:26.793416+0800 Finance[19739:5874003] 茭白匹配数据--上锁密码是{length = 6, bytes = 0x2477f6810000}
2019-12-07 13:40:26.793592+0800 Finance[19739:5874003] bind cs : 02da
2019-12-07 13:40:26.793958+0800 Finance[19739:5874003] bind Data : {length = 11, bytes = 0x0700c82477f6810000da02}
2019-12-07 13:40:26.794274+0800 Finance[19739:5874003] 茭白 数据 wr 2 bind data:{length = 11, bytes = 0x0700c82477f6810000da02}
2019-12-07 13:40:26.795303+0800 Finance[19739:5874003] 茭白--发现数据改变了
2019-12-07 13:40:26.795687+0800 Finance[19739:5874003] 有值得改变:<CBCharacteristic: 0x281450660, UUID = 6E40FF02-B5A3-F393-E0A9-E50E24DCCA9E, properties = 0x4, value = (null), notifying = NO>
2019-12-07 13:40:26.965111+0800 Finance[19739:5874003] 茭白--发现数据改变了
2019-12-07 13:40:26.965622+0800 Finance[19739:5874003] 有值得改变:<CBCharacteristic: 0x2814507e0, UUID = 6E40FF03-B5A3-F393-E0A9-E50E24DCCA9E, properties = 0x10, value = {length = 12, bytes = 0x0200c701c8000200c900c900}, notifying = YES>
2019-12-07 13:40:26.965992+0800 Finance[19739:5874003] -[TestViewController updateDeviceAck2AppRspData:] ackData:{length = 12, bytes = 0x0200c701c8000200c900c900}
2019-12-07 13:40:26.966269+0800 Finance[19739:5874003] 命令成功后回调==199
2019-12-07 13:40:26.966461+0800 Finance[19739:5874003] 享睿--绑定成功回调
2019-12-07 13:40:54.245568+0800 Finance[19739:5874169] [] tcp_input [C3.1:3] flags=[R] seq=1483030945, ack=0, win=0 state=LAST_ACK rcv_nxt=1483030945, snd_una=2805171121
2019-12-07 13:40:57.095240+0800 Finance[19739:5874003] NSData dec = {length = 6, bytes = 0x010203040506}
2019-12-07 13:40:57.095590+0800 Finance[19739:5874003] dtCrc : 81f67724
2019-12-07 13:40:57.095845+0800 Finance[19739:5874003] 解锁密码是{length = 6, bytes = 0x2477f6810000}
2019-12-07 13:40:57.096118+0800 Finance[19739:5874003] bind cs : 02dc
2019-12-07 13:40:57.096443+0800 Finance[19739:5874003] bind Data : {length = 11, bytes = 0x0700ca2477f6810000dc02}
2019-12-07 13:40:57.096828+0800 Finance[19739:5874003] 茭白 数据 wr 2 bind data:{length = 11, bytes = 0x0700ca2477f6810000dc02}
2019-12-07 13:40:57.098047+0800 Finance[19739:5874003] 茭白--发现数据改变了
2019-12-07 13:40:57.099416+0800 Finance[19739:5874003] 有值得改变:<CBCharacteristic: 0x281450660, UUID = 6E40FF02-B5A3-F393-E0A9-E50E24DCCA9E, properties = 0x4, value = (null), notifying = NO>
2019-12-07 13:40:57.205167+0800 Finance[19739:5874003] 茭白--发现数据改变了
2019-12-07 13:40:57.205615+0800 Finance[19739:5874003] 有值得改变:<CBCharacteristic: 0x2814507e0, UUID = 6E40FF03-B5A3-F393-E0A9-E50E24DCCA9E, properties = 0x10, value = {length = 6, bytes = 0x0200cb00cb00}, notifying = YES>
2019-12-07 13:40:57.205912+0800 Finance[19739:5874003] -[TestViewController updateDeviceAck2AppRspData:] ackData:{length = 6, bytes = 0x0200cb00cb00}
2019-12-07 13:40:57.206105+0800 Finance[19739:5874003] 命令成功后回调==203
2019-12-07 13:40:57.206275+0800 Finance[19739:5874003] 享睿--解锁成功回调
2019-12-07 13:41:15.120452+0800 Finance[19739:5874003] 开始扫描外设
2019-12-07 13:41:19.278097+0800 Finance[19739:5874003] str ==
2019-12-07 13:41:19.278544+0800 Finance[19739:5874003] 扫描成功:<CBPeripheral: 0x28014c500, identifier = EADF26D8-D221-D04F-4257-8B483F40D108, name = LOCKSUS, state = disconnected>