iOS – BLE开发笔记

CB Means CoreBluetooth
Refercene To:About Core Bluetooth
The Core Bluetooth framework provides the classes needed for your iOS and Mac apps to communicate with devices that are equipped with Bluetooth low energy wireless technology.
意思是说CoreBluetooth库提供了必要的类来满足iOS和Mac的App与低耗能蓝牙设备进行数据交互。

Central and Peripheral Devices

使用BLE的时候,我们需要区分好什么是中心设备?什么是外围设备?以及两者在蓝牙技术中扮演的角色意义。

如图1-1所示,我们一般使用的手机、电脑、IPad是中心设备,他们需要蓝牙设备传过来的数据;而蓝牙设备、便携式设备等是外围设备,他们可以发送数据给中心设备。
Centrals Discover and Connect to Peripherals That Are Advertising(中心设备发现和连接外围设备是通过广播的方式进行)外围设备会广播数据信息。
我们来看看外围设备的数据组成
中心设备(Central)如何和外围设备(Peripherals)进行数据交互
当中心设备与外围设备连接成功之后,外围设备的数据通过服务和特征传过来,同样的中心设备可以修改和设置特征的值。

Local Centrals and Remote Peripherals

在iOS中,中心设备通过CBCentralManager对象来表示。主要作用:发现和连接远程外围设备。而外围设备使用CBPeripheral对象来表示。如下图1-4所示
而远程外围设备的数据格式主要通过CBServiceCBCharacteristic两个对象来表示,如下图1-5所示
当然除了有【本地中心设备 – 远程外围设备】的模式还有一种就是【本地外围设备 – 远程中心设备】,此时需要CBPeripheralManager和CBCentral对象。当然目前不需要。上述模式如下图所示

以及【本地外围设备 – 远程中心设备】的数据表示方式如下图所示
在BLE中,中心设备有一组需要完成的任务,主要有:
1 discovering and connecting to available peripherals (发现和连接外围设备)
2 exploring and interacting with the data that peripherals have to offer (完成和外围设备的数据交互)
再BLE中,外围设备同样有一组不同于中心设备需要完成的任务
1 publishing and advertising services (分发或者广播服务)
2 responding to read, write, and subscription requests from connected centrals (回复中心设备的各类请求)
下面我们说一下如何使用Core Bluetooth Framework完成最普通的BLE的中心设备功能

  • Start up a central manager object.
  • Discover and connect to peripheral devices that are advertising.
  • Explore the data on a peripheral device after you’ve connected to it.
  • Send read and write requests to a characteristic value of a peripheral’s service.
  • Subscribe to a characteristic’s value to be notified when it is updated.
  • 在程序中,我们如何完成这几步呢?

    蓝牙BLE – IOS开发

    IOS制作类似Android的Toast功能

  • Toast.h
  • #import 
    #import 
    @interface TopToast : NSObject
    // API
    + (void)showToptoastWithText:(NSString *)text;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color alpha:(float)alpha;
    @end
    

    @interface、@protocal和@implementation
    在Java中interface是接口、class是类.
    例如如下

    interface BaseInterface {
        void print();
    };
    
    class BaseObject {
        
    };
    

    两者可以通过implement继承

    class BaseObject implement BaseInterface {
        void print() {
            system.out.println("HelloWorld");
        }
    };
    

    那么在OC中是什么情况呢?
    1 @protocal相当于Java的interface
    2 @interface和@implementation两者结合相当于Java中的class
    OC中类包括interface和implementation两个部分
    成员变量和成员方法的声明放置interface部分中
    成员变量和成员方法的实现放置implementation部分中

  • Toast.m
  • #import "TopToast.h"
    #define ToastDispalyDuration 2.0f
    #define ToastBackgroundColor [UIColor purpleColor]
    #define ToastHeight 64
    //
    @interface TopToast ()
    @property (nonatomic, strong) UIButton *contentView;
    @property (nonatomic, assign) CGFloat duration;
    @property (nonatomic, assign) CGFloat height;
    @end
    // 实现
    @implementation TopToast
    //多次点击缓存,逐个显示
    static NSMutableArray *_toastArr;
    - (void)dealloc{
    }
    
    - (id)initWithText:(NSString *)text{
        if (self = [super init]) {
            _duration = ToastDispalyDuration;
            _height = ToastHeight;
            if (_toastArr.count == 0) {
                _toastArr = [[NSMutableArray alloc]init];
            }
            
            _contentView = [[UIButton alloc] initWithFrame:CGRectMake(0, -_height, [UIScreen mainScreen].bounds.size.width, _height)];
            _contentView.backgroundColor = ToastBackgroundColor;
            [_contentView addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(hide)]];
    
            UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0,[UIScreen mainScreen].bounds.size.width, _height)];
            textLabel.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, _height);
            textLabel.backgroundColor = [UIColor clearColor];
            textLabel.textColor = [UIColor whiteColor];
            textLabel.textAlignment = NSTextAlignmentCenter;
            textLabel.font = [UIFont boldSystemFontOfSize:15];
            textLabel.text = text;
            textLabel.numberOfLines = 0;
            [_contentView addSubview:textLabel];
     
        }
        return self;
    }
    //设置停留时间
    - (void)setDuration:(CGFloat)duration{
        _duration = duration;
    }
    //设置背景色
    - (void)setBackgroundColor:(UIColor *)color{
        _contentView.backgroundColor = color;
    }
    //设置高度
    - (void)setHeigh:(float)height{
        _height = height;
    }
    //设置透明度
    - (void)setBackgroundAlpha:(float)alpha{
        _contentView.alpha = alpha;
    }
    //出现动画
    -(void)showAnimationWithToast:(TopToast *)toast{
        [UIView beginAnimations:@"show" context:NULL];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
        [UIView setAnimationDuration:0.3];
        toast.contentView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, toast.height);
        [UIView commitAnimations];
    }
    //影藏动画
    -(void)hideAnimationWithToast:(TopToast *)toast{
        [UIView animateWithDuration:0.3 animations:^{
            toast.contentView.frame = CGRectMake(0, -toast.height, [UIScreen mainScreen].bounds.size.width, toast.height);
        } completion:^(BOOL finished) {
            [toast.contentView removeFromSuperview];
            //影藏后检查是否有toast缓存
            if (_toastArr.count > 0) {
                [_toastArr removeObjectAtIndex:0];
                if (_toastArr.count > 0) {
                    [self showWithToast:_toastArr[0]];
                }
            }
        }];
    }
    //触摸隐藏
    - (void)hide{
        [UIView animateWithDuration:0.3 animations:^{
            _contentView.frame = CGRectMake(0, -_height, [UIScreen mainScreen].bounds.size.width, _height);
        }completion:^(BOOL finished) {
            [_contentView removeFromSuperview];
        }];
    }
    
    - (void)showWithToast:(TopToast *)toast{
        [[UIApplication sharedApplication].keyWindow addSubview:toast.contentView];
        [self showAnimationWithToast:toast];
        
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_duration * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self hideAnimationWithToast:toast];
        });
    }
    
    + (void)showToptoastWithText:(NSString *)text{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:ToastDispalyDuration];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:duration];
        [toast setHeigh:height];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:duration];
        [toast setHeigh:height];
        [toast setBackgroundColor:color];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color alpha:(float)alpha{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:duration];
        [toast setHeigh:height];
        [toast setBackgroundColor:color];
        [toast setBackgroundAlpha:alpha];
        
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
        
    }
    @end
    

    蓝牙BLE4.0开发流程

    1 建立中心角色 (硬件盒子)

    #import 
    CBCentralManager *manager;
    manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    

    2 扫描外设 (外设指的是你的IPhone和IPad)

    [manager scanForPeripheralsWithServices:nil options:options];
    

    3 连接外设
    指的是手机设备和硬件连接过程

    - (void)centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber*)RSSI {
        if ([peripheral.name isEqualToString:BLE_SERVICE_NAME]) {
            [self connect:peripheral];
        }
    }
    - (BOOL)connect:(CBPeripheral*) peripheral {
        self.manager.delegate = self;
        [self.manager connectPeripheral:peripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
    }
    

    4 扫描外设中的服务和特征

    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
        [peripheral setDelegate:self];
        [peripheral discoverServices:nil];
    }
    
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
        if (error) {
            NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
            if ([self.delegate respondsToSelector:@selector(DidNotifyFailConnectService:withPeripheral:error:)])
                [self.delegate DidNotifyFailConnectService:nil withPeripheral:nil error:nil];
            return; 
        }
        for (CBService *service in peripheral.services) {
            // 发现服务
            if ([service.UUID isEqual:[CBUUID UUIDWithString:UUIDSTR_ISSC_PROPRIETARY_SERVICE]]) {
                NSLog(@"Service found with UUID: %@", service.UUID);    // 查找特征
                [peripheral discoverCharacteristics:nil forService:service];
                break; 
            }
        }
    }
    
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
        if (error) {
            NSLog(@"Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
            [self error];
            return;
        }
        NSLog(@"服务:%@",service.UUID);
        for (CBCharacteristic *characteristic in service.characteristics) {
            //发现特征
            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"xxxxxxx"]]) {
                NSLog(@"监听:%@",characteristic); //监听特征
                [self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }
        }
    }
    

    5 与外设做数据交互,包括读和写

  • 读操作
  • - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
        if (error) {
            NSLog(@"Error updating value for characteristic %@ error: %@", characteristic.UUID, [error localizedDescription]);
            self.error_b = BluetoothError_System;
            [self error];
            return;
        }
        [self decodeData:characteristic.value];
    }
    
  • 写操作
  • NSData *d2 = [[PBABluetoothDecode sharedManager] HexStringToNSData:@"0x02"];
    [self.peripheral writeValue:d2 forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
    

    蓝牙2.0 Demo

    Manifest文件综述

    // 权限说明
    uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
    uses-permission android:name="android.permission.BLUETOOTH"
    uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
    uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" 
    uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
    

    存在扫描设备和操作蓝牙设置操作时,需要BLUETOOTH_ADMIN权限和BLUETOOTH权限.
    MOUNT_UNMOUNT_FILESYSTEMS允许允许挂载和反挂载文件系统可移动存储.
    WRITE_EXTERNAL_STORAGE使得SD卡获得写的权限.

    蓝牙存在的问题

    蓝牙一直在等待搜索,但是蓝牙列表却不弹出来。
    android 6.0之后要用蓝牙还需要添加一个模糊定位的权限android.permission.ACCESS_COARSE_LOCATION(GPS是精确定位)。
    在android 6.0(targetSdkVersion小于23)之前,安装新的app时系统会提示应用将要获取某某权限,如果同意安装,系统会默认为应用授予所申请的所有权限,而不同意的话,就不能安装应用;所以如果你以前的APP设置的targetSdkVersion低于23,在运行时是不会崩溃的。而在android 6.0(targetSdkVersion>=23)之后,我们会直接安装,不过当app需要获取不恰当权限的时候我们再决定是确定还是拒绝。

    android 6.0权限分类

  • Normal Permissions
  • Normal Permissions一般不涉及用户隐私,是不需要用户授权的.

    ACCESS_LOCATION_EXTRA_COMMANDS
    ACCESS_NETWORK_STATE
    ACCESS_NOTIFICATION_POLICY
    ACCESS_WIFI_STATE
    BLUETOOTH
    BLUETOOTH_ADMIN
    BROADCAST_STICKY
    CHANGE_NETWORK_STATE
    CHANGE_WIFI_MULTICAST_STATE
    CHANGE_WIFI_STATE
    DISABLE_KEYGUARD
    EXPAND_STATUS_BAR
    GET_PACKAGE_SIZE
    INSTALL_SHORTCUT
    INTERNET
    KILL_BACKGROUND_PROCESSES
    MODIFY_AUDIO_SETTINGS
    NFC
    READ_SYNC_SETTINGS
    READ_SYNC_STATS
    RECEIVE_BOOT_COMPLETED
    REORDER_TASKS
    REQUEST_INSTALL_PACKAGES
    SET_ALARM
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    UNINSTALL_SHORTCUT
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS
    
  • Dangerous Permissions
  • Dangerous Permissions涉及到用户隐私,在使用时需要用户实时授权才行

    group:android.permission-group.CONTACTS
      permission:android.permission.WRITE_CONTACTS
      permission:android.permission.GET_ACCOUNTS
      permission:android.permission.READ_CONTACTS
    group:android.permission-group.PHONE
      permission:android.permission.READ_CALL_LOG
      permission:android.permission.READ_PHONE_STATE
      permission:android.permission.CALL_PHONE
      permission:android.permission.WRITE_CALL_LOG
      permission:android.permission.USE_SIP
      permission:android.permission.PROCESS_OUTGOING_CALLS
      permission:com.android.voicemail.permission.ADD_VOICEMAIL
    group:android.permission-group.CALENDAR
      permission:android.permission.READ_CALENDAR
      permission:android.permission.WRITE_CALENDAR
    group:android.permission-group.CAMERA
      permission:android.permission.CAMERA
    group:android.permission-group.SENSORS
      permission:android.permission.BODY_SENSORS
    group:android.permission-group.LOCATION
      permission:android.permission.ACCESS_FINE_LOCATION
      permission:android.permission.ACCESS_COARSE_LOCATION
    group:android.permission-group.STORAGE
      permission:android.permission.READ_EXTERNAL_STORAGE
      permission:android.permission.WRITE_EXTERNAL_STORAGE
    group:android.permission-group.MICROPHONE
      permission:android.permission.RECORD_AUDIO
    group:android.permission-group.SMS
      permission:android.permission.READ_SMS
      permission:android.permission.RECEIVE_WAP_PUSH
      permission:android.permission.RECEIVE_MMS
      permission:android.permission.RECEIVE_SMS
      permission:android.permission.SEND_SMS
      permission:android.permission.READ_CELL_BROADCASTS
    

    android部分知识学习

    蓝牙适配器
    主要功能包括:
    1 开关蓝牙设备
    2 扫描蓝牙设备
    3 设置/获取蓝牙状态信息
    蓝牙状态值
    蓝牙Name
    蓝牙Mac地址
    BluetoothAdapter.getDefaultAdapter();
    BluetoothAdapter STATE
    STATE_OFF 蓝牙已经关闭
    STATE_ON 蓝牙已经打开
    STATE_TURNING_OFF 蓝牙处于关闭过程中
    STATE_TURNING_ON 蓝牙处于打开过程中
    BluetoothAdapter SCAN_MOD
    SCAN_MODE_CONNECTABLE 表明该蓝牙可以扫描其他蓝牙设备
    SCAN_MODE_CONNECTABLE_DISCOVERABLE 该蓝牙设备同时可以扫码其他蓝牙设备,并且可以被其他蓝牙设备扫描到
    SCAN_MODE_NONE 该蓝牙不能扫描以及被扫描

  • 获取蓝牙适配器
  • 
    BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
    
  • 打开蓝牙
  • 
    _bluetooth.enable();
    

    requestWindowFeature
    DEFAULT_FEATURES 系统默认状态,一般不需要指定
    FEATURE_CONTEXT_MENU 启用ContextMenu,默认该项已启用,一般无需指定
    FEATURE_CUSTOM_TITLE 自定义标题。当需要自定义标题时必须指定。如:标题是一个按钮时
    FEATURE_INDETERMINATE_PROGRESS 不确定的进度
    FEATURE_LEFT_ICON 标题栏左侧的图标
    FEATURE_NO_TITLE 无标题
    FEATURE_OPTIONS_PANEL 启用“选项面板”功能,默认已启用
    FEATURE_PROGRESS 进度指示器功能
    FEATURE_RIGHT_ICON 标题栏右侧的图标

  • 浏览搜索
  • 
    setProgressBarIndeterminateVisibility(true);
    setTitle("搜索中...");
    if (mBtAdapter.isDiscovering()) {
        mBtAdapter.cancelDiscovery();
    }
    mBtAdapter.startDiscovery();
    
  • getRemoteDevice
  • 给定Mac地址创建一个BluetoothDevice类的实力(代表远程蓝牙实力),并连接

    _device = _bluetooth.getRemoteDevice(address);
    // createRfcommSocketToServiceRecord创建一个BluetoothSocket实例
    _socket = _device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
    _socket.connect();
    

    剩余的通信均依靠_socket来完成,附录核心代码

    public void onSendButtonClicked(View v){
        int i=0;
        int n=0;
        try{
            OutputStream os = _socket.getOutputStream();
            byte[] bos = edit0.getText().toString().getBytes();
            for(i=0;i < bos.length;i++){
                if(bos[i]==0x0a)n++;
            }
            byte[] bos_new = new byte[bos.length+n];
            n=0;
            for(i=0;i < bos.length;i++){
                if(bos[i]==0x0a){
                    bos_new[n]=0x0d;
                    n++;
                    bos_new[n]=0x0a;
                }else{
                    bos_new[n]=bos[i];
                }
                n++;
            }
            
            os.write(bos_new);  
        }catch(IOException e){          
        }   
    }
    
    Thread ReadThread=new Thread(){     
        public void run(){
            int num = 0;
            byte[] buffer = new byte[1024];
            byte[] buffer_new = new byte[1024];
            int i = 0;
            int n = 0;
            bRun = true;
            while(true){
                try{
                    while(is.available()==0){
                        while(bRun == false){}
                    }
                    while(true){
                        num = is.read(buffer);
                        n=0;
                        
                        String s0 = new String(buffer,0,num);
                        fmsg+=s0;
                        for(i=0;i < num;i++){
                            if((buffer[i] == 0x0d)&&(buffer[i+1]==0x0a)){
                                buffer_new[n] = 0x0a;
                                i++;
                            }else{
                                buffer_new[n] = buffer[i];
                            }
                            n++;
                        }
                        String s = new String(buffer_new,0,n);
                        smsg+=s;
                        if(is.available()==0)break;  //
                    }
                        handler.sendMessage(handler.obtainMessage());                       
                    }catch(IOException e){
                    }
            }
        }
    };
    

    Android 4.0 BLE开发

    android 4.3(API 18)为BLE核心功能提供了平台支持和API,App可以用这些API来发现设备查询服务读写特征。对比传统蓝牙,BLE的主要特点是低功耗
    关键的术语和概念

  • GATT
  • Generic Attribute Profile GATT配置文件是一个通用规范,用于在BLE连路上链路发送和接收”Attribute”数据块,目前所有BLE应用都基于GATT。蓝牙SIG规定了许多低功耗设备和配置文件。配置文件是设备如何特定的应用程序中工作的规格说明。一个设备可以实现多个配置文件。

  • ATT
  • Attribute Protocol GATT在ATT协议基础上建立起来,被称为GATT/ATT。ATT在BLE设备上进行了优化,使用了尽可能少的字节。每一个属性通过一个唯一的统一标识符(UUID)来标记。每个String类型UUID使用的128 bits标准格式。属性通过ATT格式化称为characteristicsservices

  • Characteristic
  • 每个Characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以认为是一个类。

  • Descriptor
  • Descriptor用来描述characteristic变量的属性。

  • Service
  • Service是characteristic的集合。


    参考:http://blog.csdn.net/ohyeahhhh/article/details/52175596

    BLE核心概念

    1 Profile 一种规范,一种通信协议,profile存放在从机中,SIG规定了一些协议包括:心率计、防丢器等。

    2 Service 一种服务,在BLE从机中存在多个服务,包括电量信息服务器,系统信息服务,每个Service中包含多个Characteristic,每个具体的Characteristic的值才是BLE通信的主体。比如当前的电量是80%,会通过电量的Characteristic特征值存在从机的Profile里面,这样,主机就可以通过这个Characteristic值活的从机的80%电量值。

    3 Characteristic BLE主从机通信均是通过Characteristic实现的,可以理解成为一个标签,通过标签可以得到想要的内容。

    4 UUID 唯一识别码,上面的Service和Characteristic均需要通过一个UUID来识别。UUID为128,但是在BLE中,UUID通常用16位,也就是双字节来代替,16位与128位可以相互转化。

    综上所述:

    每个从机由一个或者多个profile构成,每个profile由一个或者多个Service构成,每个Service由多个Characteristic构成,主机和从机通信均是由Characteristic实现。

    有关GATT Profile内容
    现在通用的BLE一般都是建立在GATT(Generic Attribute Profile)协议上。GATT是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范。每个数据段被称为Attribute。

    GAP
    GAP用来控制设备的连接和广播。GAP是你的设备被其它设备可见,并决定了你的设备是否被其它设备可见,以及与其它设备如何交互。
    GAP给设备定义了若干角色,主要包括Peripheral(外围设备)和Central(中心设备)。
    外围设备:一般是非常小或者简单的低功耗的设备,用来提供数据,并连接到一个相对强大的中心设备。
    中心设备:中心设备相对比较强大,用来连接其它外围设备,例如手机等。
    广播数据
    外围设备由两种向外的广播数据:Advertising Data Payload(广播数据)和Scan Response Data Payload(扫描回复),每种数据最长可以包含31byte。这里广播数据是必须的。因为外设必须不停的向外广播,让中心设备知道它的存在。扫描回复是可选的。

    GATT
    GATT(Generic Attribute Profile)普通属性协议,它定义两个BLE设备通过Service和Characterisitic来进行通信,使用ATT(Attribute Protocol)协议,ATT协议把Service、Characterisitic数据保存在查找表中,使用16bitID座位每一项的索引(UUID),一旦两个设备建立连接,GATT开始起作用。同时需要注意的是GATT连接是独占的,也就是说一个BLE外设同时只被一个中心设备连接,一旦外设被连接,它马上就会停止广播,这样它就对其它设备不可见了。当设备断开,它又开始广播。一个中心设备可以连接多个外设。
    GATT结构如下