蓝牙开发之ble

ble的概念以及原理的简单理解

蓝牙是一种短距离无线通信技术,而蓝牙低功耗(BLE)是在蓝牙4.0协议上修改以适用低功耗应用场景的一种蓝牙协议。
那么4.0以后的蓝牙为什么低功耗了呢?传统蓝牙是通过广播收发状态,连接建立后通过socket建立连接,交互数据
ble相对于传统蓝牙

  • 广播频段和广播时射频开启时间的减少:传统蓝牙使用16~32个频段进行广播,而BLE仅使用3个广播频段; 每次广播时的射频
    开启时间由传统蓝牙的22ms减少为0.6~1.2ms;
  • BLE设计了深度睡眠状态(Duty-Cycle)来替代传统蓝牙的空闲时间,并且在Duty-Cycle时,发送数据间隔也被增大.
  • BLE 的连接采用先进的Sniffer-Subrating模式.
  • 传统蓝牙规范规定,若某一设备正在进行广播,则它不会响应当前正在进行的设备扫描,而BLE允许正在进行广播的设备连接正
    在扫描的设备,这有效避免了重复连接。通过对连接机制的改进,BLE连接建立过程可控制在3ms内完成。
  • BLE在每个从设备和每个数据包上使用32位的存取地址,优化了传统蓝牙一对一的连接,实现一对多(目前测试的是1带6)。
  • BLE增加了GFSK调制,降低峰值功耗。
    以上参考了https://blog.csdn.net/ZQ07506149/article/details/82380509

什么是GATT?

通用属性配置文件层(Generic Attribute profile,简写 GATT),简单点就是ble应用运行的环境或者场景,官方的专业解释,个人实在懒的贴出来,而且觉得也不好理解

GATT的层次结构

GATT通常有一个或者多个“Services”组成,一个 Characteristic 可以包含若干 Descriptor和value。而 Characteristic 定义了数值和操作。Characteristic 的操作这几种权限:读、写、通知等权限。

uuid

Service、Characteristic 还有 Descriptor 都是使用 UUID 唯一标示的。一会你可以看到在实际的操作中,都是通过uuid来查找你对应的设备

ble的开发

ble也是C/S的开发模式,特别注意一点ble开发要在主线程(底层源码注释),所以当两个设备建立连接之后,它们就处于下面两种角色之一:

  • GATT服务器:为GATT客户端提供数据服务的设备。外围设备 可以创建uuid的服务
  • GATT客户端:从GATT服务器读写应用数据的设备。中央设备

android的支持版本

官方说从android4.3版本开始,就支持了ble,但是在实际测试可不是这样,国内好多机型开始支持的版本号都不一样啊,这也是一个android碎片化的体现

GATT的连接问题

GATT连接是独占的。外设只能建立一个连接,但是客户端可以连接多台设备

GATT通信

中央设备在给外设发消息的时候,有时候只能发送一次, 因为写特征值前可以设置写的类型setWriteType(),写类型有三种,如下:

  • WRITE_TYPE_DEFAULT 默认类型,需要外围设备的确认,也就是需要外围设备的回应,这样才能继续发送写。
  • WRITE_TYPE_NO_RESPONSE 设置该类型不需要外围设备的回应,可以继续写数据。加快传输速率。
  • WRITE_TYPE_SIGNED 写特征携带认证签名,具体作用不太清楚。

适配ios

在开发ble服务端的时候,遇到过一个很坑的问题,调试了很久,需要在Characteristic特征中加入以下这个BluetoothGattCharacteristic.PROPERTY_NOTIFY,否则ios接收不到,android没事,不知道ios的ble模块是怎样实现的,有知道可以留言一下

Beacon

非连接型的ble的数据收发

外围设备代码开发

初始化

 mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(), createAdvData(), mAdvertiseCallback);

广告模式的回调

  private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            super.onStartSuccess(settingsInEffect);
            Log.d(TAG, "onStartSuccess");
        }

        @Override
        public void onStartFailure(int errorCode) {
            super.onStartFailure(errorCode);
            Log.d(TAG, "onStartFailure " + errorCode);
        }
    };

设置广播数据

private AdvertiseSettings createAdvSettings() {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
    //手机的发射功率,简单说就是蓝牙走多远
    builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
    builder.setConnectable(true);
    builder.setTimeout(0);
    //ADVERTISE_MODE_LOW_POWER 在低功耗模式下执行蓝牙LE广告。这是默认和首选的广告模式,因为它消耗最少的电力。
    //ADVERTISE_MODE_BALANCED 在平衡电源模式下执行蓝牙LE广告。这是广告频率和功耗之间的平衡。
    //ADVERTISE_MODE_LOW_LATENCY 在低延迟,高功率模式下执行蓝牙LE广告。这是最高的功耗,不应该用于连续的背景广告。
    // TODO: 18-1-7 测试模式的值
    builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
    return builder.build();
}

设置响应数据

private AdvertiseData createAdvData() {
    AdvertiseData.Builder builder = new AdvertiseData.Builder();
    builder.addServiceUuid(ParcelUuid.fromString(UUID_SAMPLE_NAME_SERVICE));
    //这样即使定义service uuid跟别人的有冲突,也可以在中心过滤该magic number来找到符合自己需求的外围设备,,但目前x是以设备名+系列号
    byte mLeManufacturerData[] = {(byte) 0x4C, (byte) 0x00, (byte) 0x02, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15};
    builder.addManufacturerData(0x3103 + 1, mLeManufacturerData);
    builder.setIncludeTxPowerLevel(false);
    builder.setIncludeDeviceName(true);
    return builder.build();
}

初始化GATT服务

//初始化GATT服务
BluetoothGattService nameService = new BluetoothGattService(UUID.fromString(UUID_SAMPLE_NAME_SERVICE),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
//增加读写特征
mBleGattCharacteristicWrite = new BluetoothGattCharacteristic(
UUID.fromString(UUID_SAMPLE_NAME_CHARACTERISTIC),
BluetoothGattCharacteristic.PROPERTY_READ
| BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ
| BluetoothGattCharacteristic.PERMISSION_WRITE);
nameService.addCharacteristic(mBleGattCharacteristicWrite);
mGattServer.addService(nameService);

GATT服务的回调

mGattServer = mBluetoothManager.openGattServer(mContext, new BluetoothGattServerCallback() {
            @Override
            public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
                super.onConnectionStateChange(device, status, newState);
                {
                    switch (newState) {
                        case BluetoothProfile.STATE_DISCONNECTED:
                            Log.d(TAG, device.getName() + " 断开");
                            sendHandlerMsg(device.getName() + " 断开");
                            break;
                        case BluetoothProfile.STATE_CONNECTED:
                            Log.d(TAG, device.getName() + " 连接");
                            sendHandlerMsg(device.getName() + " 连接");
                            break;
                        default:
                            break;
                    }
                }
            }

            @Override
            public void onServiceAdded(int status, BluetoothGattService service) {
                super.onServiceAdded(status, service);
                Log.d(TAG, "onServiceAdded");
            }

            /**A remote client has requested to read a local characteristic.*/
            @Override
            public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
                Log.d(TAG, "read");
            }

            /**
             * 接收消息的方法 - 接收具体的字节
             */
            @Override
            public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
                characteristic.setValue(mReceiveSuccess.getBytes());
                //触发发消息给客户端
                mGattServer.notifyCharacteristicChanged(device,characteristic,false);
                String msg = new String(value);
                Log.d(TAG, "write " + msg);
                sendHandlerMsg(msg);
                //.处理响应内容
                //onResponseToClient(requestBytes, device, requestId, characteristic);
            }

            /**
             *  特征被读取。当回复响应成功后,客户端会读取然后触发本方法
             *
             */
            @Override
            public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
                super.onDescriptorReadRequest(device, requestId, offset, descriptor);
                Log.d(TAG, "DescriptorRead");
                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
            }

            /**
             * 2.描述被写入时
             */
            @Override
            public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
                Log.d(TAG, "DescriptorWrite");
                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
            }

            @Override
            public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
                super.onExecuteWrite(device, requestId, execute);
                Log.d(TAG, "onExecuteWrite");
            }

            @Override
            public void onNotificationSent(BluetoothDevice device, int status) {
                super.onNotificationSent(device, status);
                Log.d(TAG, "onNotificationSent");
            }
        });

客户端代码 - 中央设备

初始化-扫描-连接-连接GATT服务-搜索服务-通信

检查蓝牙打开,ble可用,得到BluetoothAdapter对象

扫描

该方法专门针对ble的扫描设备,不是传统扫描,无法扫描非ble外设,api要求21以上

  mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
  mBluetoothLeScanner.startScan(mScanCallback);
  
  private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            if (null != result) {
                BluetoothDevice device = result.getDevice();
            
            }

        }
    };

连接

public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
    return false;
}
//根据地址连接远程设备
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
        return false;
}
//连接已开启的GATT服务
mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
mBluetoothDeviceAddress = address;

GATT服务的回调,成功连接后开始搜索服务 mBluetoothGatt.discoverServices();

private  BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        /**
         * 连接状态的改变
         */
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            //成功连接后开始搜索服务
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                discoverServices();
                setConnectionStatus(STATE_CONNECTED);
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onConnectionStateChange(gatt, mConnectionState);
                }
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                setConnectionStatus(STATE_DISCONNECTED);
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onConnectionStateChange(gatt, mConnectionState);
                }
            }
        }

       /**
         * 发现服务
         */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            Log.d(TAG,"onServicesDiscovered");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onServicesDiscovered(gatt);
                }
            } else {
                excuteError(status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onCharacteristicRead(gatt, characteristic);
                }
            } else {
                excuteError(status);
            }
        }

        
         /**
         * 接收外设的消息
         * @param gatt           BluetoothProfile的对象
         * @param characteristic 特征
         */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            if (null != mIBleGattCallback) {
                mIBleGattCallback.onCharacteristicChanged(gatt, characteristic);
                onReceiveMsg(characteristic);
            }
        }
    };

发数据

获取可用的特征
可以通过协定好的uuid获取,也可以遍历获取可写的服务

//判断特征可写
public boolean ifCharacteristicWritable(BluetoothGattCharacteristic characteristic){
    return ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 ||
            (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0);
}
//直接获取相应uuid的服务
UUID uuid = UUID.fromString(BleStatusConstant.UUID_SAMPLE_NAME_SERVICE);
BluetoothGattService service = gatt.getService(uuid);
UUID uuid2 = UUID.fromString(BleStatusConstant.UUID_SAMPLE_NAME_CHARACTERISTIC);
mBleCharacteristic = service.getCharacteristic(uuid2);

写入属性

/**
 * 写入属性
 *
 * @param characteristic 特征
 * @return true写入成功 false写入失败
 */
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        return false;
    }
    return mBluetoothGatt.writeCharacteristic(characteristic);
}

结束别忘了释放,如果有扫描也需要停止扫描

if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容