蓝牙开发有两种模式:
- 中心者模式:苹果手机作为中心设备,连接其他设备,从其他设备接受数据或者进行传输,比如连接体重秤来获取测量结果(本次开发也是这种模式)
- 管理者模式:手机作为被连接设备,供其他设备连接
一般我们在了解蓝牙开发的过程中会接触到下面这些关键词:
- 中心设备:中心设备用来扫描并连接其他设备,并从其他设备获取数据,再我们以中心者模式进行开发的时候,苹果手机就是中心设备
- 外设:是指被中心设备发现并连接的设备,比如我们要用手机连接体重秤,那么体重秤就是外设
- 广播:外设不断发射蓝牙信号,让中心设备能够通过扫描发现外设
- 服务(Service):外部设备在与中心设备连接后会有服务,可以理解成一个功能模块,中心设备可以读取服务,筛选我们想要的服务,并从中获取出我们想要特征。(外设可以有多个服务)
- 特征(Characteristic):服务中的一个单位,一个服务可以多个特征,而特征会有一个value,一般我们向蓝牙设备写入数据、从蓝牙设备读取数据就是这个value
- UUID:区分不同服务和特征的唯一标识,使用该字端我们可以获取我们想要的服务或者特征
实际操作
- 首先引入头文件
#import <CoreBluetooth/CoreBluetooth.h>
- 遵守两个协议
CBCentralManagerDelegate,CBPeripheralDelegate
- 创建对象并实例化
//中心角色
@property (nonatomic, strong) CBCentralManager * manager;
//外设角色,我们当选中某个外设连接成功后,将外设对象赋值给该对象
@property (nonatomic, strong) CBPeripheral *peripheral;
//实例化对象并设置代理
_manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
- 实例化对象完成以后,系统会自动执行下面的方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case 0:
NSLog(@"CBCentralManagerStateUnknown");//设备类型位置
break;
case 1:
NSLog(@"CBCentralManagerStateResetting");//设备初始化中
break;
case 2:
NSLog(@"CBCentralManagerStateUnsupported");//不支持蓝牙
break;
case 3:
NSLog(@"CBCentralManagerStateUnauthorized");//设备未授权
break;
case 4:
{
NSLog(@"CBCentralManagerStatePoweredOff");//蓝牙未开启
}
break;
case 5:
{
NSLog(@"CBCentralManagerStatePoweredOn");//蓝牙已开始
//如果想自动扫描,在此处开始扫描即可
}
break;
default:
break;
}
}
下面是官方文档的说明
Invoked whenever the central manager's state has been updated. Commands should only be issued when the state is
<code>CBCentralManagerStatePoweredOn</code>.
A state below <code>CBCentralManagerStatePoweredOn</code>
implies that scanning has stopped and any connected peripherals have been disconnected.
If the state moves below<code>CBCentralManagerStatePoweredOff</code>, all <code>CBPeripheral</code> objects obtained from this central
manager become invalid and must be retrieved or discovered again.
大概翻译了一下(水平有限,仅供参考)
当central manager的状态更新时调用此方法(central manager 指我们之前创建的对象,也就是_manager,实例化对象时对象状态更新,就会调用这个方法)
当蓝牙状态为CBCentralManagerStatePoweredOn时蓝牙才可以发出命令
当蓝牙状态为CBCentralManagerStatePoweredOff时管理中心连接的对象都会无效,必须重新扫描或者重新连接
- 开启扫描的方法 ,放在自己需要的地方即可
[self.manager scanForPeripheralsWithServices:nil options:nil];
- 每扫描到一个设备都会触发以下方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
}
官方文档
This method is invoked while scanning, upon the discovery of <i>peripheral</i> by <i>central</i>. A discovered peripheral must be retained in order to use it; otherwise, it is assumed to not be of interest and will be cleaned up by the central manager. For a list of <i>advertisementData</i> keys, see {@link CBAdvertisementDataLocalNameKey} and other similar constants.
这个方法当中心设备扫描到外设的时候会调用。外设必须要保存以后才能使用,否则,这个设备就会被认为时无用的,并且会被中心设备清理掉(
这一点非常重要,对于需要连接的设备,一定要进行保存,如果发现了自己的目标设备直接去链接而自己没有保存的话,应用就会报错,在我一开始进行测试的时候并没有注意这个问题,直接与目标设备进行链接,结果应用报错。因为本人犯过错误所以格外支出,新手的话也应注意。同时我们也应该了解到开发过程中很多问题都可通过阅读官方文档解决,所以应该养成查阅文档的好习惯
)。
- 与连接状态有关的方法
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
//连接成功
NSLog(@">>>连接到名称为(%@)的设备-成功",peripheral.name);
//一般情况下我们连接到目标设备后,为了节省资源,会停止继续扫描
NSLog(@"停止扫描外设设备");
[_centerManager stopScan];
//设置的peripheral委托CBPeripheralDelegate
[_peripheral setDelegate:self];
// 一般情况下当我们连接成狗后,就开始扫描连接外设的服务
[self.peripheral discoverServices:nil];
}
/*!
* @method centralManager:didDisconnectPeripheral:error:
*
* @param central The central manager providing this information.(中心设备)
* @param peripheral The <code>CBPeripheral</code> that has disconnected.(已断开连接的设备)
* @param error If an error occurred, the cause of the failure.(如果发生了错误,这个指错误的原因)
*
* @discussion This method is invoked upon the disconnection of a peripheral that was connected by {@link connectPeripheral:options:}. If the disconnection
* was not initiated by {@link cancelPeripheralConnection}, the cause will be detailed in the <i>error</i> parameter. Once this method has been
* called, no more methods will be invoked on <i>peripheral</i>'s <code>CBPeripheralDelegate</code>.
*(这个方法会在通过 connectPeripheral:options: 这个方法连接的设备断开后执行,如果断开不是由 cancelPeripheralConnection 这个方法发起的,那么会在参数error中详细说明,同时 `CBPeripheralDelegate 的方法都不会再被执行)
*
*/
-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
//连接已经断开
NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
}
/*!
* @method centralManager:didFailToConnectPeripheral:error:
*
* @param central The central manager providing this information.(中心设备)
* @param peripheral The <code>CBPeripheral</code> that has failed to connect.(连接失败的设备)
* @param error The cause of the failure.(连接失败的原因)
*
* @discussion This method is invoked when a connection initiated by {@link connectPeripheral:options:} has failed to complete. As connection attempts do not
* timeout, the failure of a connection is atypical and usually indicative of a transient issue.
*(当由方法 connectPeripheral:options: 发起的连接失败时执行此方法,因为尝试连接时不会超时,所以连接失败是非典型的[不常见的?],一般代表是暂时的问题)
*
*/
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
//连接失败
NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
}
- 当调用
[self.peripheral discoverServices:nil]
这个方法以后,会自动调用以下方法
/*!
* @method peripheral:didDiscoverServices:
*
* @param peripheral The peripheral providing this information.(外设)
* @param error If an error occurred, the cause of the failure.(如果发生了错误,这个参数会说明错误原因)
*
* @discussion This method returns the result of a @link discoverServices: @/link call. If the service(s) were read successfully, they can be retrieved via
* <i>peripheral</i>'s @link services @/link property.
*(这个方法会返回调用的结果,如果读取成功,我们就可以检索服务下属的特征)
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@"didDiscoverServices");
if (error)
{
NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
//我们需要用的是服务下的特征,查询(每一个服务下的若干)特征
for (CBService *service in peripheral.services)
{
//好,发现了服务 接下来就是最重要的特征了
//调用该方法后,当发现了service服务中的特征后,将会自动触发didDiscoverCharacteristicsForService()方法
[peripheral discoverCharacteristics:nil forService:service];
}
}
- 获取特征值并进行后续操作(读、写等)
/*!
* @method peripheral:didDiscoverCharacteristicsForService:error:
*
* @param peripheral The peripheral providing this information.(外设)
* @param service The <code>CBService</code> object containing the characteristic(s).(包含特征的服务对象)
* @param error If an error occurred, the cause of the failure.(如果发生了错误,这个参数会说明错误原因)
*
* @discussion This method returns the result of a @link discoverCharacteristics:forService: @/link call. If the characteristic(s) were read successfully,
* they can be retrieved via <i>service</i>'s <code>characteristics</code> property.
*
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSLog(@"didDiscoverCharacteristicsForService");
//根据设备提供方提供的文档,当UUID为1001时可以向蓝牙设备发送命令:
for (CBCharacteristic *cha in service.characteristics) {
if([[cha.UUID.UUIDString lowercaseString] isEqualToString:@"1001"]){
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
-
关于蓝牙设备信息包
文档.png
以上是厂家提供的文档,按照文档给蓝牙设备发送数据
//这是最简单的办法,按照文档的需求,拼接出响应的数据然后发送给蓝牙设备
NSUInteger integer = NSCalendarUnitYear | NSCalendarUnitMonth |NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *dataCom = [currentCalendar components:integer fromDate:currentDate];
NSInteger year = [dataCom year];
NSInteger month = [dataCom month];
NSInteger day = [dataCom day];
NSInteger hour = [dataCom hour];
NSInteger minute = [dataCom minute];
NSInteger second = [dataCom second];
Byte bytes[10];
bytes[0] = 0x5A;
bytes[1] = 0x0A;
bytes[2] = 0x01;
bytes[3] = (Byte) (year);
bytes[4] = (Byte) (month);
bytes[5] = (Byte) (day);
bytes[6] = (Byte) (hour);
bytes[7] = (Byte) (minute);
bytes[8] = (Byte) (second);
Byte cmdCheck = 0;
for (int i = 0; i < 10; i++) {
cmdCheck += bytes[i];
}
bytes[9] = (Byte) (cmdCheck + 2);
NSData *data = [[NSData alloc] initWithBytes:bytes length:10];
//CBCharacteristicWriteWithoutResponse的意思是没有回调,只向设备发送数据,不从设备接受数据
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
- 下边是封装后的方法
/** 发送血压的数据包 */
+(NSData *)pressureSendDataWithCurrentDate:(prePacketStyle)style{
NSString *currentTime = [self changeDateStringDateFormatStr:@"YY MM dd HH mm ss"];
NSString *s0 = @"5A";
NSString *s1 = @"0A";
NSString *s2;
if (style == preInfoPacketStyle) {
s2 = @"00";
}else if (style == preDataPacketStyle){
s2 = @"03";
}else if (style == preStartPacketStyle){
s2 = @"01";
}else if (style == preOverPacketStyle){
s2 = @"05";
}
NSArray *array = [currentTime componentsSeparatedByString:@" "];
NSInteger sum = [self getDecimalByHex:s0] + [self getDecimalByHex:s1] + [self getDecimalByHex:s2];
for (NSString *string in array) {
sum += [string integerValue];
}
NSString *data = [self ToHex:(int)(sum+2)];
data = [data substringWithRange:NSMakeRange(data.length - 2, 2)];
NSString *sendData = [NSString stringWithFormat:@"%@%@%@",s0,s1,s2];
for (NSString *string in array) {
sendData = [NSString stringWithFormat:@"%@%@",sendData,[self ToHex:[string intValue]]];
}
sendData = [NSString stringWithFormat:@"%@%@",sendData,data];
return [self getCheckSum:sendData];
}
//将16进制转化为十进制 返回的数为NSInteger
+ (NSInteger)getDecimalByHex:(NSString *)hex
{
return strtoul([hex UTF8String], 0, 16);
}
//将十进制转化为十六进制
+ (NSString *)ToHex:(int)tmpid
{
NSString *nLetterValue;
NSString *str =@"";
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 stringWithFormat:@"%u",ttmpig];
}
str = [nLetterValue stringByAppendingString:str];
if (tmpid == 0) {
break;
}
}
//不够一个字节凑0
if(str.length == 1){
return [NSString stringWithFormat:@"0%@",str];
}else{
return str;
}
}
+ (NSData *)getCheckSum:(NSString *)byteStr {
int length = (int)byteStr.length/2;
NSData *data = [self hexToBytes:byteStr];
Byte *bytes = (unsigned char *)[data bytes];
Byte sum = 0;
for (int i = 0; i<length; i++) {
sum += bytes[i];
}
int at = sum;
//int sumT = sum;
//int at = 256 - sumT;
// printf("校验和:%d\n",at);
if (at == 256) {
at = 0;
}
NSString *str = [NSString stringWithFormat:@"%@%@",byteStr,[self ToHex:at]];
return [self hexToBytes:str];
}
+ (NSData *)hexToBytes:(NSString *)str
{
NSMutableData* data = [NSMutableData data];
int idx;
for (idx = 0; idx+2 <= str.length; idx+=2) {
NSRange range = NSMakeRange(idx, 2);
NSString* hexStr = [str substringWithRange:range];
NSScanner* scanner = [NSScanner scannerWithString:hexStr];
unsigned int intValue;
[scanner scanHexInt:&intValue];
[data appendBytes:&intValue length:1];
}
return data;
}
- 接收蓝牙传来的数据
/*!
* @method peripheral:didUpdateValueForCharacteristic:error:
*
* @param peripheral The peripheral providing this information.(外设)
* @param characteristic A <code>CBCharacteristic</code> object.
* @param error If an error occurred, the cause of the failure.(如果发生了错误,这个参数会说明错误原因)
*
* @discussion This method is invoked after a @link readValueForCharacteristic: @/link call, or upon receipt of a notification/indication.
*(这个方法在 readValueForCharacteristic: 之后调用,或者在接受到通知以后调用,也就是说从蓝牙设备传过来的数据可以在这个方法获取)
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSString * string = [self hexadecimalString:characteristic.value];
//对于接收到的数据按照文档和需求进行解析
NSLog(@"收到蓝牙发来的数据%@",string);
}
//将传入的NSData类型转换成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;
}
以上就是对本次蓝牙开发的一些心得,主要是以记录为主,其中肯定也会存在一些错误或者疏忽,如有发现欢迎指出。如果对参考借鉴的同学造成了困扰非常抱歉,或许可以通过官方文档来解决出现的问题,或者请教更高明的开发者。
再提一句,阅读官方的文档真的非常重要也非常有用!!
网友评论