• [iOS 基于CoreBluetooth的蓝牙4.0通讯]


    一、首先大致介绍下蓝牙4.0的模式,中心和周边:

    一般情况下,iPhone作为中心,接收来自周边传感器(比如手环等)采集的数据。

    二、那整一个数据通讯的协议是怎样的呢?

    为什么要一层层搞这么复杂呢?据我的理解是这样的:

      (1)蓝牙2.0的通讯非常简单,只有数据接收和发送,这样产生的问题就是:假如我有2个传感器的数据,但传输通道就一个,就发送时必须自己切割字符串等。

          但4.0根据不同的功能,有点像传输分了很多“通道”,比如传感器1传输温度,服务的UUID是FFF0,然后特征字节发送的UUID为FFF1;传感器2传输距离,服务的UUID也是FFF0,但是特征字节发送的UUID为FFF2,这样就可以各取所需了,而不是蓝牙2.0那样一股脑儿收进来再切割。

      (2)蓝牙4.0的每个“通道”都可以定义为发送或者接收字节,所以可以把发送和接收区分开。

      补充:一般情况下,服务(Service)的UUID根据功能来区分(假如有FFF0和FFFE0两种服务),比如FFF0的服务ID里的特征字节UUID通道用来作为传感器通讯,FFE0里放蓝牙设备的信息,名称啊电池啊等等。。

    三、代码:

    1.BLEInfo.h(存放周边蓝牙设备的信息)

    #import <Foundation/Foundation.h>
    #import <CoreBluetooth/CoreBluetooth.h>
    @interface BLEInfo : NSObject
    
    @property (nonatomic, strong) CBPeripheral *discoveredPeripheral;
    @property (nonatomic, strong) NSNumber *rssi;
    
    @end

      BLEInfo.m

    #import "BLEInfo.h"
    
    @implementation BLEInfo
    
    @end

    2.RootTableViewController.h(显示周边蓝牙信息,主要用scan函数就行了)

    #import <UIKit/UIKit.h>
    #import <CoreBluetooth/CoreBluetooth.h>
    #import "BLEInfo.h"
    #import "DetailViewController.h"
    @interface RootTableViewController : UITableViewController<CBCentralManagerDelegate>
    
    @property (nonatomic, strong) CBCentralManager *centralMgr;
    @property (nonatomic, strong) NSMutableArray *arrayBLE;
    
    @end

      RootTableViewController.m

    #import "RootTableViewController.h"
    
    @interface RootTableViewController ()
    
    @end
    
    @implementation RootTableViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.navigationItem.title=@"蓝牙搜索";
        self.centralMgr = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
        self.arrayBLE = [[NSMutableArray alloc] init];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    //蓝牙状态delegate
    - (void)centralManagerDidUpdateState:(CBCentralManager *)central
    {
        switch (central.state)
        {
            case CBCentralManagerStatePoweredOn:
                [self.centralMgr scanForPeripheralsWithServices:nil options:nil];
                NSLog(@"start scan Peripherals");
    
                break;
                
            default:
                NSLog(@"Central Manager did change state");
                break;
        }
    }
    
    //发现设备delegate
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
    {
        BLEInfo *discoveredBLEInfo = [[BLEInfo alloc] init];
        discoveredBLEInfo.discoveredPeripheral = peripheral;
        discoveredBLEInfo.rssi = RSSI;
        
        // update tableview
        [self saveBLE:discoveredBLEInfo];
       
    }
    
    //保存设备信息
    - (BOOL)saveBLE:(BLEInfo *)discoveredBLEInfo
    {
        for (BLEInfo *info in self.arrayBLE)
        {
            if ([info.discoveredPeripheral.identifier.UUIDString isEqualToString:discoveredBLEInfo.discoveredPeripheral.identifier.UUIDString])
            {
                return NO;
            }
        }
        
        NSLog(@"
    Discover New Devices!
    ");
        NSLog(@"BLEInfo
     UUID:%@
     RSSI:%@
    
    ",discoveredBLEInfo.discoveredPeripheral.identifier.UUIDString,discoveredBLEInfo.rssi);
        
        [self.arrayBLE addObject:discoveredBLEInfo];
        [self.tableView reloadData];
        return YES;
    }
    
    
    #pragma mark - Table view data source
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        // Return the number of sections.
        return 1;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        // Return the number of rows in the section.
        return _arrayBLE.count;
    }
    
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];
        
        // Step 2: If there are no cells to reuse, create a new one
        if(cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
        // Add a detail view accessory
        BLEInfo *thisBLEInfo=[self.arrayBLE objectAtIndex:indexPath.row];
        cell.textLabel.text=[NSString stringWithFormat:@"%@ %@",thisBLEInfo.discoveredPeripheral.name,thisBLEInfo.rssi];
        cell.detailTextLabel.text=[NSString stringWithFormat:@"UUID:%@",thisBLEInfo.discoveredPeripheral.identifier.UUIDString];
        // Step 3: Set the cell text
        
        // Step 4: Return the cell
        return cell;
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        
        BLEInfo *thisBLEInfo=[self.arrayBLE objectAtIndex:indexPath.row];
        DetailViewController* dtvc=[self.storyboard instantiateViewControllerWithIdentifier:@"DetailViewController"];
        dtvc.centralMgr=self.centralMgr;
        dtvc.discoveredPeripheral=thisBLEInfo.discoveredPeripheral;
        
        [self.navigationController pushViewController:dtvc animated:YES];
        
    }
    @end

    3.DetailViewController.h(连接具体的蓝牙,发现其内部信息)

    #import <UIKit/UIKit.h>
    #import <CoreBluetooth/CoreBluetooth.h>
    
    @interface DetailViewController : UIViewController<
    CBPeripheralManagerDelegate,
    CBCentralManagerDelegate,
    CBPeripheralDelegate
    >
    
    @property (nonatomic, strong) CBCentralManager *centralMgr;
    @property (nonatomic, strong) CBPeripheral *discoveredPeripheral;
    @property (strong, nonatomic) CBCharacteristic* writeCharacteristic;
    
    @property int current_humitidy;
    @property int current_temperature;
    
    - (IBAction)led1control:(id)sender;
    
    @end

    DetailViewController.m

    #import "DetailViewController.h"
    
    @interface DetailViewController ()
    
    @end
    
    @implementation DetailViewController
    
    #define SECTION_NAME @"Serviceinfo"
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        [_centralMgr setDelegate:self];
        if (_discoveredPeripheral)
        {
            NSLog(@"connectPeripheral");
            [_centralMgr connectPeripheral:_discoveredPeripheral options:nil];
        }
    }
    
    //界面退出
    -(void)viewWillDisappear:(BOOL)animated{
        [self.centralMgr cancelPeripheralConnection:_discoveredPeripheral];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    /*
    //========================================================================================
    //0.假设蓝牙关闭、掉线什么的,重新搜索
    - (void)centralManagerDidUpdateState:(CBCentralManager *)central
    {
        switch (central.state)
        {
            case CBCentralManagerStatePoweredOn:
                //[self.centralMgr scanForPeripheralsWithServices:nil options:nil];
                NSLog(@"start scan Peripherals");
                
                break;
                
            default:
                NSLog(@"Central Manager did change state");
                break;
        }
    }
    
    //1.搜索后重连
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
    {
        //[_centralMgr connectPeripheral:_discoveredPeripheral options:nil];
    }
    //========================================================================================
    */
    
    //2.连接的Delegate 连接若成功则搜索服务
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    {
        NSLog(@"didFailToConnectPeripheral : %@", error.localizedDescription);
    }
    
    
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
    {
        
        [_discoveredPeripheral setDelegate:self];
        
        //查找服务
        [_discoveredPeripheral discoverServices:nil];
    }
    
    //========================================================================================
    //3.搜索服务的Delegate 若发现服务,然后搜索其内的特征服务
    
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    {
        if (error)
        {
            NSLog(@"didDiscoverServices : %@", [error localizedDescription]);
            //        [self cleanup];
            return;
        }
        
        for (CBService *s in peripheral.services)
        {
            NSLog(@"
    >>>服务UUID found with UUID : %@ des:%@", s.UUID,s.UUID.description);
            //查找特征字节
            [s.peripheral discoverCharacteristics:nil forService:s];
        }
    }
    //========================================================================================
    //4.搜索特征的Delegate 若发现特征,则看看这个“通道是发送的还是接收”,接收就read,发送就把这个writeCharacteristic记录下
    //注意:不是所有的特性值都是可读的(readable)。通过访问 CBCharacteristicPropertyRead 可以知道特性值是否可读。如果一个特性的值不可读,使用 peripheral:didUpdateValueForCharacteristic:error:就会返回一个错误。
    //Subscribing to a Characteristic’s Value(订制一个特性值) 尽管通过 readValueForCharacteristic:方法能够得到特性值,但是对于一个变化的特性值就不是很 有效了。大多数的特性值是变化的,比如一个心率监测应用,如果需要得到特性值,就需要 通过预定的方法获得。当预定了一个特性值,当值改变时,就会收到设备发出的通知。
    
    /*特征值的属性:c.properties
     typedef NS_OPTIONS(NSInteger, CBCharacteristicProperties) {
     // 标识这个characteristic的属性是广播
     CBCharacteristicPropertyBroadcast= 0x01,
     // 标识这个characteristic的属性是读
     CBCharacteristicPropertyRead= 0x02,
     // 标识这个characteristic的属性是写-没有响应
     CBCharacteristicPropertyWriteWithoutResponse= 0x04,
     // 标识这个characteristic的属性是写
     CBCharacteristicPropertyWrite= 0x08,
     // 标识这个characteristic的属性是通知
     CBCharacteristicPropertyNotify= 0x10,
     // 标识这个characteristic的属性是声明
     CBCharacteristicPropertyIndicate= 0x20,
     // 标识这个characteristic的属性是通过验证的
     CBCharacteristicPropertyAuthenticatedSignedWrites= 0x40,
     // 标识这个characteristic的属性是拓展
     CBCharacteristicPropertyExtendedProperties= 0x80,
     // 标识这个characteristic的属性是需要加密的通知
     CBCharacteristicPropertyNotifyEncryptionRequiredNS_ENUM_AVAILABLE(NA, 6_0)= 0x100,
     // 标识这个characteristic的属性是需要加密的申明
     CBCharacteristicPropertyIndicateEncryptionRequiredNS_ENUM_AVAILABLE(NA, 6_0)= 0x200
     };
     */
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
    {
        if (error)
        {
            NSLog(@"didDiscoverCharacteristicsForService error : %@", [error localizedDescription]);
            return;
        }
        
        for (CBCharacteristic *c in service.characteristics)
        {
            NSLog(@"
    >>>	特征UUID FOUND(in 服务UUID:%@): %@ (data:%@)",service.UUID.description,c.UUID,c.UUID.data);
            
            /*
            根据特征不同属性去读取或者写
            if (c.properties==CBCharacteristicPropertyRead) {
            }
            if (c.properties==CBCharacteristicPropertyWrite) {
            }
            if (c.properties==CBCharacteristicPropertyNotify) {
            }
            */
            
            //假如你和硬件商量好了,某个UUID时写,某个读的,那就不用判断啦
            /*
            if([c.UUID isEqual:[CBUUID UUIDWithString:@"FFF1"]]){
                self.writeCharacteristic = c;
            }
            if([c.UUID isEqual:[CBUUID UUIDWithString:@"FFF6"]]){
                [peripheral readValueForCharacteristic:c];
            }*/
            if (c.properties==CBCharacteristicPropertyRead) {
                [peripheral readValueForCharacteristic:c];
            }
        }
    }
    //========================================================================================
    
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    {
        if (error)
        {
            NSLog(@"didUpdateValueForCharacteristic error : %@", error.localizedDescription);
            return;
        }
        
        NSLog(@"
    FindtheValueis (UUID:%@):%@ ",characteristic.UUID,characteristic.value);
    
        /*
        if([characteristic.UUID.description isEqualToString:@"FFF6"]){
            //我这里采用的是16进制数据,如<34001a00>,3400代表湿度十进制52.0,1a00代表温度26.0
            //当然你也可以有自己定义传输字符的意义
            NSData *datavalue=characteristic.value;
            NSData *shidudata=[datavalue subdataWithRange:NSMakeRange(0, 1)];
            NSData *wendudata=[datavalue subdataWithRange:NSMakeRange(2, 1)];
            NSLog(@"
    Find---theValueis:%@   %@-%@",characteristic.value,shidudata,wendudata);
            int i=0,j=0;
            [shidudata getBytes: &i length: sizeof(i)];
            [wendudata getBytes: &j length: sizeof(j)];
    
            self.current_humitidy=i;
            self.current_temperature=j;
            
            NSLog(@"
    室内温度为:%d℃,室内湿度为:%d%%",_current_temperature,_current_humitidy);
        }
        */
    }
    
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
        if (error) {
            NSLog(@"Error changing notification state: %@", [error localizedDescription]);
        }
    }
    
    //向peripheral中写入数据后的回调函数
    - (void)peripheral:(CBPeripheral*)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        NSLog(@"write value success : %@", characteristic);
    }
    
    
    //自定义的写入数据的函数
    - (void)writeToPeripheral:(NSString *)string{
        if(!_writeCharacteristic){
            NSLog(@"writeCharacteristic is nil!");
            return;
        }
        
        NSData* value = [self stringToByte:string];
        NSLog(@"Witedata: %@",value);
        
        
        [_discoveredPeripheral writeValue:value forCharacteristic:_writeCharacteristic type:CBCharacteristicWriteWithResponse];
    }
    
    //====================================================================================================
    //一些转换函数,本例中只用到stringToByte
    -(NSData*)stringToByte:(NSString*)string
    {
        NSString *hexString=[[string uppercaseString] stringByReplacingOccurrencesOfString:@" " withString:@""];
        if ([hexString length]%2!=0) {
            return nil;
        }
        Byte tempbyt[1]={0};
        NSMutableData* bytes=[NSMutableData data];
        for(int i=0;i<[hexString length];i++)
        {
            unichar hex_char1 = [hexString characterAtIndex:i]; ////两位16进制数中的第一位(高位*16)
            int int_ch1;
            if(hex_char1 >= '0' && hex_char1 <='9')
                int_ch1 = (hex_char1-48)*16;   //// 0 的Ascll - 48
            else if(hex_char1 >= 'A' && hex_char1 <='F')
                int_ch1 = (hex_char1-55)*16; //// A 的Ascll - 65
            else
                return nil;
            i++;
            
            unichar hex_char2 = [hexString characterAtIndex:i]; ///两位16进制数中的第二位(低位)
            int int_ch2;
            if(hex_char2 >= '0' && hex_char2 <='9')
                int_ch2 = (hex_char2-48); //// 0 的Ascll - 48
            else if(hex_char2 >= 'A' && hex_char2 <='F')
                int_ch2 = hex_char2-55; //// A 的Ascll - 65
            else
                return nil;
            
            tempbyt[0] = int_ch1+int_ch2;  ///将转化后的数放入Byte数组里
            [bytes appendBytes:tempbyt length:1];
        }
        return bytes;
    }
    
    //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;
    }
    //====================================================================================================
    
    
    //假设有个swt1,开一下发送一个开关信号,控制LED灯
    - (IBAction)led1control:(id)sender {
        UISwitch *swt1= (UISwitch*)sender;
        if (swt1.on) {
            [self writeToPeripheral:@"11"];
        }else{
            [self writeToPeripheral:@"10"];
    
        }
    }
    
    @end

    完整代码地址:https://github.com/rayshen/ShenBLETest

    四、示例:基于蓝牙4.0的温湿度采集控制器

    这是我自己做的湿度和温度的收集器和控制器。

    iPhone收集来自单片机(蓝牙CC2541芯片)的温度和湿度信息,并且可以控制LED灯和继电器。

    继电器就是一个开关,外围可以连接其他带电源和电器的大电路。

    欢迎大家和我交流,我的微博是:weibo.com/rayshen1012

  • 相关阅读:
    ORACLE--Connect By、Level、Start With的使用(Hierarchical query-层次查询)
    小工具-ANT
    测试类。。。重写篇
    java代码----求最大值,平均值。。。
    java代码,输入n多个数,求其平均值,虽有重复,但是第二次,我就乱写了
    java代码求输入数的平均值~~~~
    java代码求输入的三个数的最大值
    java代码switch语句求分数等级
    java代码求分数等级的输出~~~
    java代码 求和1+1/2+1/3+1/4+1/5+1/6+.......+1/n 的值~~~~
  • 原文地址:https://www.cnblogs.com/rayshen/p/4536160.html
Copyright © 2020-2023  润新知