iOS 开发蓝牙4.0的框架是CoreBluetooth
在CoreBluetooth中有两个主要的部分,Central(中心)和Peripheral(周边)。
CBPeripheralManager作为周边设备是服务器,CBCentralManager作为中心设备是客户端。所有可用的iOS 设备可以作为周边 也可以作为中央,但不可以同时既是周边也是中央。
一般手机是客户端(中心设备),设备(魔法棒)---->硬件设备,因为是手机去连接手环这个服务器的。周边(Peripheral)是生成或者保存了数据的设备,中心是使用这些数据的设备。
可以认为周边是一个广播数据的设备,他广播到外部世界说他这儿有数据,并且也说明了能提供的服务。另一边,中央开始扫描附近有没有服务,如果中央发现了想要的服务,然后中央就会请求连接周边,一旦连接成功,两个设备之间就开始交换数据了。
除了中央和周边,还要考虑两者交换的数据结构,这些数据在服务中被结构化,每个服务由不同的特征组成,特征是包含一个单一逻辑值的属性类型。
BLE-----------buletouch low energy , 蓝牙4.0设备耗电低
peripheral--外设 central 中心 发起连接的是central 被连接的设备是peripheral
service and characteristics -- 服务和特征 每个设备都会提供服务和特征,类似于服务端的API,但是机构不同。每个外设会有很多服务,每个服务中包含很多字段,这些字段的权限一般分为 读read 和写write ,通知notify几种,就是我们连接设备后具体需要操作的内容。
Description 每个characteristics可以对应一个或多个Descrition用户描述characteristics的信息或属性
蓝牙一般有两种不同的业务场景,一种叫做中心模式,就是以你的app作为中心,连接其他的外设的场景;另一种是外设模式,使用手机作为外设识别其他中心设备操作的场景。
蓝牙中心模式流程
1 建立中心角色
2 扫描外设
3 连接外设
4扫描外设中的服务和特征
4.1 获取外设的services
4.2 获取外设的Characteristics,获取characteristics的值,获取characteristics的Descriptor和Descriptor的值
5 与外设做数据交互
6 订阅characteristics的通知
7 断开连接(disconnect)
蓝牙外设模式流程
1 启动一个Peripheral管理对象
2 本地Peraipheral设置服务、特性、描述、权限等等
3 Peripheral发送广告
4 设置处理订阅、取消订阅、读Characteristic 写characteristics的委托方法
蓝牙设备的状态
CBCentralManagerStateUnknown ------ 初始化中
CBCentralManagerStateResetting ------ 设备不支持状态
CBCentralManagerStateUnsupported --------设备未授权状态
CBCentralManagerStateUnauthorized ---------设备未授权状态
CBCentralManagerStatePoweredOff -------- 尚未打开蓝牙
CBCentralManagerStatePoweredOn ----------蓝牙已经成功开启
蓝牙设备中的五种工作状态
准备 广播 监听扫描 发起连接 已连接
蓝牙外设模式
//
// HB_BindStickViewController.m
// HomeBridge
//
// Created by Jason on 15/9/9.
// Copyright (c) 2015年 ___HB___. All rights reserved.
//
#import "HB_BindStickViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import "Header.h"
#define OPENBLUETOOTH 100
#define BINDSUCCESS 101
#define ALREADYBIND 102
@interface HB_BindStickViewController ()<UITableViewDataSource,UITableViewDelegate,CBCentralManagerDelegate,CBPeripheralDelegate,UIAlertViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIView *bindView;
@property (nonatomic,retain) NSMutableArray * dataArray;
@property(nonatomic,retain)CBCentralManager * manager;
@property(nonatomic,retain)CBPeripheral * peripheral;
@property(nonatomic,retain)CBCharacteristic * characteristic;
@property(nonatomic,assign)NSInteger type;
@property (nonatomic,copy) NSString * teacherID;
@property (nonatomic,assign) BOOL isShowAlert;
//@property (nonatomic,copy) NSMutableString * macID;
@end
@implementation HB_BindStickViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (!_dataArray) {
_dataArray = [[NSMutableArray alloc]initWithCapacity:1];
}
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)getUserInfoWithID:(NSString *)user_id{
// HB_UserInfoModel * userinfo = [HB_UserInfoModel defaultManager];
//判断是否相同_macID
[HB_UserService customsGetPropWithUserId:[NSNumber numberWithInteger:[user_id integerValue]] tn:@"customs" field:@"rftoken" withBlock:^(NSDictionary *result, NSError *error) {
if (result) {
NSString * str = [result valueForKey:@"Content"];
NSString * _macID = [[NSUserDefaults standardUserDefaults]stringForKey:@"macID"];
if ([str isEqual:_macID]) {
[self showAL:user_id];
}else{
[self Bindbluetooth];
}
}
}];
}
- (void)showAL:(NSString *)user_id{
NSString * val = [NSString stringWithFormat:@"%@",user_id];
[HB_UserService customsGetPropWithUserId:[NSNumber numberWithInteger:[user_id integerValue]] tn:@"customs" field:@"phone" withBlock:^(NSDictionary *result, NSError *error) {
if (result) {
NSString * phone = [result valueForKey:@"Content"];
[HB_UserService getUserInfo:@"id" Val:val More:@1 withBlock:^(NSDictionary *result1, NSError *error1) {
if (result1) {
NSArray * item = [result1 valueForKey:@"Content"];
NSString * name;
if (item.count > 0 ) {
name = [[item objectAtIndex:0] valueForKey:@"name_zh"];
}
NSString * string = [NSString stringWithFormat:@"此设备已绑定到其他教师,如需使用请联系此教师解锁此设备\n%@联系电话:%@",name,phone];
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"教棒已被使用" message:string delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
alertView.tag = ALREADYBIND;
if (!_isShowAlert) {
_isShowAlert = YES;
[alertView show];
}
}
}];
}
}];
}
- (IBAction)ScanBluetoothBtnClick:(UIButton *)sender {
if (_manager.state != CBCentralManagerStatePoweredOn) {
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"开启蓝牙" message:nil delegate:self cancelButtonTitle:@"不开启" otherButtonTitles:@"开启", nil];
alertView.tag = OPENBLUETOOTH;
[alertView show];
}else{
[self.manager scanForPeripheralsWithServices:nil options:nil];
}
}
//蓝牙状态改变
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSString * message;
switch (central.state) {
case 0:
message = @"初始化中,请稍后……";
break;
case 1:
message = @"设备不支持状态,过会请重试……";
break;
case 2:
message = @"设备未授权状态,过会请重试……";
break;
case 3:
message = @"设备未授权状态,过会请重试……";
break;
case 4:
message = @"尚未打开蓝牙,请在设置中打开……";
break;
case 5:
message = @"蓝牙已经成功开启,稍后……";
break;
default:
break;
}
}
//扫描到设备
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"发现蓝牙设备:%@",peripheral.name);
if (_dataArray.count == 0) {
[_dataArray addObject:peripheral];
}else{
for (NSInteger i = 0 ; i < _dataArray.count; i++) {
CBPeripheral * ph = _dataArray[i];
if (i == _dataArray.count - 1 && ph != peripheral) {
[_dataArray addObject:peripheral];
}
}
}
[_tableView reloadData];
if ([peripheral.name isEqual:BLUETOOTHNAME]) {
self.peripheral = peripheral;
}
}
//连接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
peripheral.delegate = self;
[central stopScan];
[peripheral discoverServices:nil];
}
//返回的蓝牙服务通知通过代理实现
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService * service in peripheral.services) {
if (error) {
NSLog(@"Error changing notification state: %@",[error localizedDescription]);
return;
}
NSLog(@"Service found with UUID :%@",service.UUID);
NSLog(@"%@",service.UUID.UUIDString);
// if ([service.UUID isEqual:[CBUUID UUIDWithString:@"FFE0"]]) {
[peripheral discoverCharacteristics:nil forService:service];
// }
}
}
//查找到该设备所对应的服务
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error) {
NSLog(@"Error changing notification state: %@",[error localizedDescription]);
return;
}
NSLog(@"%@",service.UUID.UUIDString);
for (CBCharacteristic * characteristic in service.characteristics) {
NSLog(@"查找到的服务(属性)%@",characteristic.UUID);
NSString * str1 = characteristic.UUID.UUIDString;
NSLog(@"哈哈哈哈哈哈哈哈%@",str1);
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]) {
[peripheral readValueForCharacteristic:characteristic];
}
//所对应的属性用于接收和发送数据
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
[peripheral readValueForCharacteristic:characteristic];
}
// NSLog(@"%@",string);
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF1"]]) {
_characteristic = characteristic;
// [self Bindbluetooth];
[self LookForID];
}
}
}
//接收数据的函数.处理蓝牙发过来得数据 读数据代理
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"Error changing notification state: %@",[error localizedDescription]);
return;
}
NSLog(@"---------%@",[characteristic UUID]);
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
NSLog(@"收到蓝牙发来的数据%@",characteristic.value);
NSString * string = [self hexadecimalString:characteristic.value];
if ([string isEqual:@"00"]) {
[self LookForID];
return;
}
if ([string isEqual:@"0000ff00ff00"]) {
if (_type == 2) {
[self contect];
}
}
NSLog(@"%@",string);
[self getUserIDWithString:string];
}else if([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]){
NSLog(@"收到蓝牙发来的数据%@",characteristic.value);
NSString * string = [self hexadecimalString:characteristic.value];
NSLog(@"##############################%@",string);
NSMutableString * _macID = [[NSMutableString alloc]init];
for (NSInteger i = string.length - 1 ; i >= 0 ; i-= 2) {
NSString * sr = [string substringWithRange:NSMakeRange(i - 1, 2)];
if (i== 1) {
// sr = [NSString stringWithFormat:@"%d",[self to10:sr].integerValue - 16];
sr = [self to16:[self to10:sr].integerValue - 16];
}
[_macID appendString:sr];
if (i % 2 != 0 && i > 1) {
[_macID appendString:@":"];
}
}
[[NSUserDefaults standardUserDefaults]setValue:_macID forKey:@"macID"];
}
}
//写数据代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
sleep(3);
NSLog(@"%@",characteristic.UUID);
if (error) {
NSLog(@"Error changing notification state: %@",[error localizedDescription]);
[HB_Utils showItoast:@"绑定失败"];
}
}
- (void)contect{
NSString * _macID = [[NSUserDefaults standardUserDefaults]stringForKey:@"macID"];
[HB_UserService customsEditWithUserId:[HB_UserInfoModel defaultManager].user_id tn:@"customs" field:@"rftoken" val:_macID withBlock:^(NSDictionary *result, NSError *error) {
if (result) {
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"魔法棒绑定成功,可以鼓励孩子了!" message:nil delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
alertView.tag = BINDSUCCESS;
if (!_isShowAlert) {
_isShowAlert = YES;
[alertView show];
}
return;
}
}];
}
#pragma mark - NSData and NSString
//将传入的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;
}
//将传入的NSString类型转换成NSData并返回
- (NSData*)dataWithHexstring:(NSString *)hexstring{
NSData* aData;
return aData = [hexstring dataUsingEncoding: NSUTF16StringEncoding];
}
// 十六进制转换为普通字符串的。
- (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;
}
- (NSString *)to10:(NSString *)num
{
NSString *result = [NSString stringWithFormat:@"%ld", strtoul([num UTF8String],0,16)];
return result;
}
- (NSString *)to16:(int)num
{
NSString *result = [NSString stringWithFormat:@"%@",[[NSString alloc] initWithFormat:@"%1x",num]];
if ([result length] < 2) {
result = [NSString stringWithFormat:@"0%@", result];
}
return result;
}
- (void)LookForID{
_type = 1;
if(_characteristic == nil){
NSLog(@"_characteristic 为空");
return;
}
u_int8_t packet[7] = {0x00, 0x00, 0xFF, 0x03, 0x01, 0x04, 0x00};
NSMutableData *value = [NSMutableData data];
for (NSInteger i = 0; i < 7; i ++) {
NSData * data ;
data = [NSData dataWithBytes:&packet[i] length:1];
NSLog(@"%02lx",(unsigned long)packet[i]);
[value appendData:data];
}
NSLog(@"十六进制:%@",value);
sleep(3);
[_peripheral writeValue:value forCharacteristic:_characteristic type:CBCharacteristicWriteWithResponse];
NSLog(@"已经向外设%@的特征值%@写入数据",_peripheral.name,_characteristic.description);
}
- (void)Bindbluetooth{
_type = 2;
if(_characteristic == nil){
NSLog(@"_characteristic 为空");
return;
}
u_int8_t packet[8] = {0x00, 0x00, 0xFF, 0x07, 0x02, 0x00, 0x00, 0x00};
// u_int16_t str = 539;
// NSString * m = [self to16:540];
NSInteger userid = [[HB_UserInfoModel defaultManager].user_id integerValue];
// NSLog(@"----%@",[self stringFromHexString:s]);
NSData * idData = [NSData dataWithBytes:&userid length:4];
const unsigned char* dataBuffer = (const unsigned char*)[idData bytes];
NSMutableData * Data5= [NSMutableData data];
NSInteger length = 0;
for (NSInteger i = idData.length - 1; i >= 0; i --) {
[Data5 appendData:[NSData dataWithBytes:&dataBuffer[i] length:1]];
length += [[self to10:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]integerValue];
}
NSLog(@"%@",Data5);
NSInteger checkNum = length + 9;
NSData * checkNumData = [NSData dataWithBytes:&checkNum length:4];
const unsigned char* dataBuffer2 = (const unsigned char*)[checkNumData bytes];
NSMutableData * Data6= [NSMutableData data];
for (NSInteger i = 0; i < 1; i ++) {
[Data6 appendData:[NSData dataWithBytes:&dataBuffer2[i] length:1]];
}
NSLog(@"%@",Data6);
NSMutableData *value = [NSMutableData data];
for (NSInteger i = 0; i < 8; i ++) {
NSData * data ;
if (i == 5) {
data = Data5;
}else if(i == 6){
data = Data6;
}else{
data = [NSData dataWithBytes:&packet[i] length:1];
}
NSLog(@"%02lx",(unsigned long)packet[i]);
[value appendData:data];
}
NSLog(@"十六进制:%@",value);
sleep(3);
[_peripheral writeValue:value forCharacteristic:_characteristic type:CBCharacteristicWriteWithResponse];
NSLog(@"已经向外设%@的特征值%@写入数据",_peripheral.name,_characteristic.description);
}
#pragma mark -tableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
CBPeripheral * peripheral = _dataArray[indexPath.row];
//NSLog(@"%@",peripheral.RSSI);
cell.textLabel.text = peripheral.name;
if ([peripheral.name isEqual:BLUETOOTHNAME]) {
cell.detailTextLabel.text = @"魔法棒";
}else{
cell.detailTextLabel.text = nil;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
CBPeripheral * peripheral = _dataArray[indexPath.row];
if ([peripheral.name isEqual:BLUETOOTHNAME]) {
self.peripheral = peripheral;
[self.manager connectPeripheral:self.peripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
}else{
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"选择的设备有误" message:nil delegate:self cancelButtonTitle:@"重新选择" otherButtonTitles: nil];
[alertView show];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return 0.01;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return 0.01;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//应答回复
- (void)answerBack{
if(_characteristic == nil){
NSLog(@"_characteristic 为空");
return;
}
u_int8_t packet[6] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
NSMutableData *value = [NSMutableData data];
for (NSInteger i = 0; i < 6; i ++) {
NSData * data = [NSData dataWithBytes:&packet[i] length:1];
[value appendData:data];
}
NSLog(@"十六进制:%@",value);
sleep(3);
[_peripheral writeValue:value forCharacteristic:_characteristic type:CBCharacteristicWriteWithResponse];
NSLog(@"已经向外设%@的特征值%@回复数据",_peripheral.name,_characteristic.description);
}
#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 == BINDSUCCESS){
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:ISBINGING];
if ([self.delegate respondsToSelector:@selector(updateUIWithBindSuccess:)]) {
[self.delegate updateUIWithBindSuccess:YES];
}
[self.navigationController popViewControllerAnimated:YES];
}else if (alertView.tag == ALREADYBIND){
[self.navigationController popViewControllerAnimated:YES];
}
}
- (void)getUserIDWithString:(NSString *)string{
if (string.length > 15 && _type == 1) {
_teacherID = [self to10:[string substringWithRange:NSMakeRange(8, 8)]];
if (_teacherID.integerValue == 0) {
[self Bindbluetooth];
}else{
[self getUserInfoWithID:_teacherID];
}
[self answerBack];
}
}
@end