精华内容
下载资源
问答
  • 低功耗蓝牙BLE
    千次阅读
    2020-03-27 15:46:27

    1、树莓派蓝牙简介

    树莓派继承了蓝牙、wifi和网卡,不同树莓派的芯片类型

    型号蓝牙芯片蓝牙版本
    Raspberry Pi 3 Model A+Broadcom BCM434384.1
    Raspberry Pi 3 Model BBroadcom BCM434384.1
    Raspberry Pi Model 3B+Cypress CYW434554.2
    Raspberry Pi 4 Model BCypress CYW434555.0

    树莓派4b集成了wifi和蓝牙芯片,蓝牙支持低功耗模式。树莓派采用linux系统,并使用BlueZ Bluetooth stack,但BlueZ Bluetooth stack对ble支持不够稳定,还需要更多的测试。蓝牙5在树莓派上不支持long range模式。long range模式一般没法支持wifi和蓝牙集成的芯片。

    从Broadcom和现在的Cypress的角度来看,Pi从射频性能和功能角度来看具有相当不错的芯片组。 一些设备支持5GHz Wi-Fi频段,可减少BLE使用的2.4GHz频段上的干扰。 由于该系统是单芯片解决方案,因此还支持共存以减少干扰。Raspberry Pi的最大问题是天线相对较小。 该天线在某种程度上被GPIO连接器的引脚连接器围绕。 尽管RPI基础并未提供详细的天线辐射信息,但这两者都对性能产生了一些影响。

    由于以下几个原因,无法在树莓派上更改天线:
    •没有物理连接器,这意味着需要焊接天线或连接器
    •Raspberry Pi的认证未声明外部天线。 董事会的任何修改都会使FCC和相关认证无效。
    最终,Raspberry Pi设备的Bluetooth LE系列是不错的,但却是有限的,并且它无法满足使用外部天线和更复杂的系统所能提供的功能。 如果您希望在一两个房间以外的任何合理距离内控制设备,最好添加另一个无线电或找到另一个解决方案。
    综上所述,它可以为设备的室内控制提供良好的解决方案。 现在,让我们开始使用Raspberry Pi控制某些设备。

    树莓派安装见另外一篇文章:https://blog.csdn.net/qingfengxd1/article/details/105071360

    2、Bluez安装

    现在我们的Raspberry Pi已经准备好了,是时候使用BlueZ了。正如我们之前提到的,BlueZ是开源蓝牙堆栈。 Raspberry Pi基金会已经负责对Wi-Fi /蓝牙芯片组进行低级配置。如果这是一个自定义的硬件平台(就像我们过去使用的那样),则需要正确配置所有内容。

    Wi-Fi和蓝牙芯片组(以及组合变体)在ROM中具有固件。 ROM固件需要使用更新和修复程序进行修补。当加载了Bluetooth / Wi-Fi驱动程序并且RPI基础已经准备好了这些补丁程序和更新时,这些补丁程序和更新程序就会被加载。我们提到它是因为在某些情况下更新它很重要。在这种情况下,我们无需执行任何操作,因为默认值已足够。

    Raspbian已经带有可以下载的蓝牙程序包,但是我们将自己安装BlueZ。重要的是您可以安装并更新到最新版本,并在需要自定义功能的情况下自定义内部版本。为此,我们需要下载BlueZ发行版并安装依赖项。下载BlueZ源代码,如下所示。请注意,我们使用的是BlueZ 5.50,它是当前最新版本。

    cd ~
    wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.50.tar.xz
    tar xvf bluez-5.50.tar.xz

    上面的命令下载了BlueZ源并将其解压缩,在这种情况下,我们将使用BlueZ 5.50。 我们将在Raspberry Pi本身上编译BlueZ,因为它足够强大,并且因为建立交叉编译系统需要更多的工作且不必要。 要在RPI上进行编译,我们需要如下安装依赖项:

    sudo apt-get update
    sudo apt-get install -y libusb-dev libreadline-dev libglib2.0-dev libudev-dev libdbus-1-dev libical-dev 
    cd bluez-5.50
    .configure --enable-library
    make -j4
    sudo make install
    pi@raspberrypi:~/bluez-5.50 $ btmon -v
    5.50

    BlueZ 基本命令

    hciconfig:配置到无线电的HCI接口

    hcitool:hci工具

    gatttool:连接设备、控制GATT

    pi@raspberrypi:~/bluez-5.50/tools $ hciconfig
    hci0: Type: Primary Bus: UART
        BD Address: B8:27:EB:AF:2C:B0 ACL MTU: 1021:8 SCO MTU: 64:1
        UP RUNNING
        RX bytes:766 acl:0 sco:0 events:49 errors:0
        TX bytes:2504 acl:0 sco:0 commands:49 errors:0

    蓝牙功能是由BT适配器通过接口提供的,并且hciconfig实用程序现在告诉我们接口是hci0。 我们将使用该界面。

    要循环有时可能需要的接口,我们可以先关闭接口,然后再打开。

    如果界面无法正常运行,您可能需要使用上面的重置

    为了连接到BLE设备,我们需要扫描,其中一种方法是使用hcitool实用程序:

    pi@raspberrypi:~/bluez-5.50/tools $ sudo hcitool -i hci0 lescan 
    LE Scan ...
    4F:2A:A8:AD:7E:0B (unknown)
    4F:2A:A8:AD:7E:0B (unknown)
    DB:F8:F9:65:59:15 SCLE-01
    DB:F8:F9:65:59:15 (unknown)
    78:5D:71:CE:AF:E0 (unknown)
    7D:FC:BE:D1:39:F6 FitBit
    7D:FC:BE:D1:39:F6 (unknown)
    F5:ED:4B:A5:08:72 (unknown)
    F5:ED:4B:A5:08:72 (unknown)
    DF:F7:7B:74:4D:76 (unknown)
    E8:3D:EF:7A:30:FD SCLE-01
    E8:3D:EF:7A:30:FD (unknown)

    在上面,我们将hci0称为hcitool进行扫描,并请求进行LE扫描。 这将查找附近的所有设备并提供MAC地址。

    现在我们有了BLE设备的地址,让我们连接它们。 为此,我们将使用gatttool实用程序,因为我们还将操纵BLE设备的GATT。 hcitool基本上是所有HCI命令(经典蓝牙和BLE)的“瑞士刀”,但它本身不会操纵GATT工具。

    gatttool可以以两种模式运行:交互和非交互。 在交互模式下,控制台提供了一个界面,使您可以发出命令并与设备进行交互。 您将需要退出以返回到终端。

    执行命令时,重要的是避免让命令自行终止。 使用CTRL + C停止命令可能会导致hci接口处于未知状态。 在这种情况下,关闭接口然后再打开将有帮助

    sudo gatttool -i hci0 -b DB:F8:F9:65:59:15 -I -t random

    上面的代码是标准的,但是必须注意-t参数的使用,该参数指定设备使用的地址是随机的还是公共的。 公共地址是使用分配的IEEE MAC地址的地址。 随机是在大多数情况下BLE设备中随机分配MAC的地址。

    如果您在连接设备时遇到问题,请根据需要使用public或random连接

    读写特征

    最重要的任务之一是读取和写入特征。 使用交互式gatttool

    [DB:F8:F9:65:59:15][LE]> char-read-uuid f000aa11-0451-4000-b000-000000000000
    handle: 0x0030 value: 00 00 00

    dwad

    [DB:F8:F9:65:59:15][LE]> char-write-cmd 34 07
    [DB:F8:F9:65:59:15][LE]> char-read-hnd 30
    handle: 0x0030 value: 00 f0 02 

    从BLE设备获取通知是另一种以低功耗方式从BLE设备获取数据的机制。 当设备具有要发送的新信息(例如新数据或警报)时,可以发送通知。

    要启用通知,我们需要使用正确的标志写入CCCD(客户端配置特征描述符)

    [DB:F8:F9:65:59:15][LE]> char-write-cmd 34 07 

    PS:有定制开发、答疑需求,可以QQ联系:2472853871

     

     

     

     

    更多相关内容
  • BLE dome 分享,学者可根据自己所需,过滤所需设备。
  • 连接事件 在一个连接当中,主设备会在每个连接事件里向从设备发送数据包。一个连接事件是指主 设备和从设备之间相互发送数据包的过程。连接事件的进行始终位于一个频率,每个数据 包会在上个数据包发完之后等待 150...
  • 相对于经典蓝牙,低功耗蓝牙有传输远、功耗低、延迟低等优势。传输 距离方面,经典蓝牙只有 10-100 米,而 BLE 最远能传输 300 米;连接方式上, 经典蓝牙只能通过点对点的方式传输,而 BLE 设备能够能通过点对点、...
  • 描述了UUID值和代表的功能类型的对应关系。例如0x1800 -> Generic Access
  • 安卓低功耗蓝牙ble快速上手 最近项目中用到蓝牙ble的需求,于是把蓝牙代码整合起来,方便调用。 第一次传代码到github,不足之处,希望大家多支持支持 功能特点: 1.简洁明了,蓝牙业务与ui充分解耦 项目会一直维护...
  • 本<泰凌微ble mesh蓝牙模组天猫精灵学习之旅>系列博客学习由半颗... 4、如何在Android开发低功耗蓝牙ble控制 TB-02 模块,代码工程全部开源! 文章目录前言一、材料准备二、蓝牙模块初始化三、App开发过程4.1 搜索设
  • 低功耗蓝牙Ble_GFSK
  • 低功耗蓝牙Ble的详细使用流程

    千次阅读 2021-07-08 11:59:14
    BLE中存在两个角色,一个是中心角色(Central),一个是外围角色(Peripheral),蓝牙设备或手机都可以单独作为Central或Peripheral角色。外设角色的作用是为中心角色提供各种数据,中心角色可以扫描并接收多个...

    概述

    • 中心角色和外围角色
      在BLE中存在两个角色,一个是中心角色(Central),一个是外围角色(Peripheral),蓝牙设备或手机都可以单独作为Central或Peripheral角色。外设角色的作用是为中心角色提供各种数据,中心角色可以扫描并接收多个外设角色数据( 外围角色中的设备进行广播,中心角色的设备扫描寻找广播),数据以服务(Service)和特征(Characteristic)的形式呈现。其中Ble中心角色的API在Android 4.3得到支持,而外围角色的API在Android 5.0才得到支持
    • 协议、服务、特征、描述符
      一份协议(BluetoothGatt)由一个或多个服务(BluetoothGattService)构成,一个服务由零个或多个特征(BluetoothGattCharacteristic)构成,一个特征可以包含零个或多个描述符(BluetoothGattDescriptor)。每一个服务、特征、描述符都有一个UUID作为唯一识别符,识别符有通用的,也可以自定义,也可以随机生成,固定格式00000000-0000-0000-0000-000000000000(8-4-4-4-12),一般来说自定义的UUID只有前8位有变化,后面的基本是固定的0000-1000-8000-00805f9b34fb,所以一个自定义的UUID一般看起来就像这样 “0000????-0000-1000-8000-00805f9b34fb” 通配符就表示4个16进制数。每一个特征都有其属性和权限(Read | Write | Notify | Indicate),特征根据属性可读可写。在每个Ble蓝牙设备中,都会有两个默认的服务如下:

    //Generic Access(Generic Attribute Profile 通用属性规范GATT)
    service:00001801-0000-1000-8000-00805f9b34fb
    characteristic:00002a05-0000-1000-8000-00805f9b34fb
    
    //Generic Attribute (Generic Access Profile 通用接入规范GAP)
    service:00001800-0000-1000-8000-00805f9b34fb
    characteristic:00002a00-0000-1000-8000-00805f9b34fb
    characteristic:00002a01-0000-1000-8000-00805f9b34fb
    characteristic:00002aa6-0000-1000-8000-00805f9b34fb
    
    • 适配器,扫描器:
      每一台支持蓝牙的手机中都会有一个蓝牙适配器,由蓝牙管理器管理着,从其中获得蓝牙适配器。适配器中自带扫描器,使用扫描器可以扫描周边的蓝牙设备。

    Ble中常用类

    • BluetoothDeivce:蓝牙设备,代表一个具体的蓝牙外设
    • BluetoothManager:蓝牙管理器,主要用于获取蓝牙适配器和管理所有和蓝牙相关的东西
    • BluetoothAdapter:蓝牙适配器,每一台支持蓝牙功能的手机都有一个蓝牙适配器,一般来说,只有一个,可以通过BluetoothManager获取
    • BluetoothLeScanner:蓝牙适配器里面的扫描器,用于扫描BLE外设,可以通过BluetoothAdapter获取
    • BluetoothGatt:通用属性协议,定义了BLE通讯的基本规则,就是通过把数据包装成服务和特征的约定过程,可以通过建立连接获取
    • BluetoothGattService:服务,描述了一个BLE设备的一项基本功能,由零或多个特征组构成,可以通过BluetoothGatt获取并自定义(现有的约定服务可以在bluetooth.org上找到)
    • BluetoothGattCallback:作为中央的回调类,用于回调GATT通信的各种状态和结果
    • BluetoothGattServerCallback:作为周边的回调类,用于回调GATT通信的各种状态和结果
    • BluetoothGattCharacteristic:特征,里面包含了一组或多组数据,是GATT通信中的最小数据单元。
    • BluetoothGattDescriptor:特征描述符,对特征的额外描述,包括但不仅限于特征的单位,属性等。
    • BluetoothProfile:一个通用的规范,按照这个规范来收发数据

    使用流程

    中心设备:判断蓝牙是否可用->打开蓝牙->开始扫描->获取被扫描到的设备->连接设备->发现服务->获取到指定特征->写入特征值
    外围设备:判断蓝牙是否可用->打开蓝牙->创建广播数据->发送广播->添加服务至广播->根据监听获取写入的数据
    下图是中心设备的使用流程图 来源

    权限声明

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    //获取扫描结果权限,Android6.0以上需要动态权限(这两个权限是在经典蓝牙的扫描中需要声明的)
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    //或者
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     //当android:required为true的时候,app只能强制运行在支持BLE功能的设备商,为false的时候,可以运行在所有设备上,但某些方法需要手动检测,否则可能存在隐性BUG 
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
    

    Ble可用性判断

    不是任何设备都支持BLE,最开始要确定设备是否支持,还要确定蓝牙已经打开。

    • Android系统从4.3以后才支持,这是先决条件

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
        ... ...
    }
    
    • 判断蓝牙是否可用

    //小于等于API17时直接使用BluetoothAdapter.getDefaultAdapter()来获取Adapter
    private BluetoothAdapter getAdapter(){
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
                mBluetoothAdapter = mBluetoothManager.getAdapter();
            } else {
                mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            }
        return mBluetoothAdapter;
    }
    if(mBluetoothAdapter == null){
        //蓝牙不可用
    }
    
    //Ble不可用
    private boolean checkIfSupportBle(){
        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }
    

    蓝牙开启

    //开启蓝牙功能需要一小段时间,所以不能执行开启蓝牙立即执行其他操作,这时蓝牙实际还没有开启,回出现异常,所以后续操作应该在蓝牙状态广播中处理
    private void enableBluetooth(){
        if (mBluetoothAdapter == null && !mBluetoothAdapter.isEnabled()) {
            //使用系统弹框来启动蓝牙,REQUEST_ENABLE_BT为自定义的开启蓝牙请求码
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    
            //或者静默打开
            bluetoothAdapter.enable();
        }
    }
    
    //静默关闭
    bluetoothAdapter.disable();
    

    蓝牙开关广播 更多蓝牙相关广播

    //广播的Action
    BluetoothAdapter.ACTION_STATE_CHANGED
    
    //四种状态值    
    BluetoothAdapter.STATE_ON  //已开启
    BluetoothAdapter.STATE_OFF //已关闭
    BluetoothAdapter.STATE_TURNING_ON //正在开启
    BluetoothAdapter.STATE_TURNING_OFF //正在关闭
    
    //当前状态值的获取
    int curState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON); 
    

    扫描具体使用流程

    • 经典通用方式

    //此过程大概持续10秒,当扫描到蓝牙设备后,会发出广播,只要在需要的地方注册接收广播,就可以获得扫描结果。这种方法可以扫描出所有蓝牙设备,包括BLE,但貌似不同手机有不同体验。
    private void startDiscover(){ 
        mBluetoothAdapter.startDiscover();
    } 
    
    //注册此广播,监听BluetoothDevice.ACTION_FOUND,以接收系统消息取得扫描结果 
    private class DeviceReceiver extends BroadcastReceiver { 
      @Override 
      public void onReceive(Context context, Intent intent) { 
        String action = intent.getAction(); 
        if(BluetoothDevice.ACTION_FOUND.equals(action)){ 
          //这个就是所获得的蓝牙设备。
          BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 
          mDevices.add(device ); 
        } 
      } 
    }
    
    • Ble扫描方式

    //Ble蓝牙扫描方式在5.0之后发生了变更
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
        BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
        //过滤器设置
        ArrayList<ScanFilter> filters = new ArrayList<>();
        ScanFilter.Builder filterBuilder = new ScanFilter.Builder();
        filters.add(filterBuilder);
    
        //扫描结果回调设置
        ScanCallback scanCallback = new ScanCallback(){        
            public void onScanResult(int callbackType, ScanResult result){
                //scanResult.getDevice()取得device
            }
        };
    
        //扫描参数设置
        ScanSettings.Builder settingsBuilder = new ScanSettings.Builder();
        //这种扫描方式占用资源比较高,建议当应用处于前台时使用该模式
        settingsBuilder.setScanMode(SCAN_MODE_LOW_LATENCY);
        // 开启扫描,第三项参数见下面,该操作为异步操作的,但是会消耗大量资源,一般扫描时长为12秒,建议找到需要的设备后,执行取消扫描
        bluetoothLeScanner.startScan(scanCallback, filter, settingsBuilder.build());
    }else{
        //扫描结果回调设置  
        BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback(){
            public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord){}
        };
        //该方法已被声明为过时方法 
        adapter.startLeScan(leScanCallback)
    }
    
    • 停止扫描

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
       bluetoothLeScanner.stopScan(mScanCallback);
    }else{
        bluetoothAdapter.stopLeScan(mLeScanCallback);
    }
    

    设备信息获取

    • 根据蓝牙地址获取蓝牙设备

    BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
    
    • 已绑定设备获取

    private void getBoundDevices(){
        Set<BluetoothDevice> boundDevices = mBluetoothAdapter.getBondedDevices();
        for(BluetoothDevice device : boundDevices){
            //对device进行其他操作,比如连接等。
        }
    }
    
    • 设备详细信息获取

    private void showDetailOfDevice(){
        //获得设备名称,多个设备可以有同一个名称。
        String deviceName = bluetoothDevice.getName();
        //获取设备物理地址,一个设备只能有一个物理地址,每个设备都有每个设备的物理地址,无法相同。
        String deviceMacAddress =bluetoothDevice.getAddress();
        //绑定设备
       bluetoothDevice.createBond();
    }
    

    连接

    • 建立连接

    //扫描回调的BluetoothDevice用于建立GATT连接,参数2是控制是否自动链接,为true的时候,当设备进入中心范围,会进行自动连接,为false时是主动连接,一般推荐使用主动连接,因为这样连接速度更快。
    BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback);
    
    //如果已经连接后,但是又断开了连接,需要重新连接时
    bluetoothGatt.connect();
    
    • 连接状态的获取

    //连接状态改变会在BluetoothGattCallback的onConnectionStateChange方法中回调,同时也可以随时主动去查询
    int state = bluetoothManager.getConnectionState(bluetoothDevice, BluetoothGatt.GATT);
    
    • 发现服务

    //在建立连接成功后进行发现服务,onServicesDiscovered为发现服务的回调
    bluetoothGatt.discoverServer();
    
    • 特征改变

    //该方法一般是在发现服务后,进行设置的,设置该方法的目的是让硬件在特征数据改变的时候,发送数据给app,app则通过onCharacteristicChanged方法回调给用户,从参数中可获取到回调回来的数据
    bluetoothGatt.setCharacteristicNotification(characteristic, true);
    
    • 连接数据回调(在非UI线程

    //mBluetoothGattCallback 为所有蓝牙数据回调的处理者,也是整个蓝牙操作当中最为核心的一部分。它里面有很多方法,但并非所有都需要在开发当中用到,这里列出来只是作为部分解析,需要哪个方法,就重写哪个方法,不需要的,直接去掉
    private BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            //当设备与中心连接状态发生改变时,下面是已连接的判断
            if (status == BluetoothGatt.GATT_SUCCESS && newState== BluetoothProfile.STATE_CONNECTED){
                ...
            }
        }
    
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            //当发现设备服务时,会回调到此处,下面是遍历所有发现的服务
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //gatt.getServices()可以获得外设的所有服务
                for (BluetoothGattService service : gatt.getServices()) {
                    //每发现一个服务,我们再次遍历服务当中所包含的特征,service.getCharacteristics()可以获得当前服务所包含的所有特征
                    for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                        //通常可以把所发现的特征放进一个列表当中以便后续操作,如果你想知道每个特征都包含哪些描述符,很简单,再用一个循环去遍历每一个特征的getDescriptor()方法。
                        mCharacteristics.add(characteristic);
                        //UUID获取,或者根据UUID判断是否是自己需要的服务/特征
                        Log.i("", characteristic.getUuid().toString());//打印特征的UUID。
                    }
                }
            }
        }
    
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            //读取特征后回调到此处。
        }
    
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            //写入特征后回调到此处,status == BluetoothGatt.GATT_SUCCESS代表写入成功
        }
    
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            //当特征(值)发生变法时回调到此处。
        }
    
        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            //读取描述符后回调到此处。
        }
    
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            //写入描述符后回调到此处
        }
    
        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            //暂时没有用过。
        }
    
        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            //Rssi表示设备与中心的信号强度,发生变化时回调到此处。
        }
    
        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            //暂时没有用过。
        }
    };
    

    数据传输

    • 写入特征值,会回调onCharacteristicWrite

    BluetoothGattService service = bluetoothGatt.getService(SERVICE_UUID);
    BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);
    characteristic.setValue(writeString); 
    //设置回复方式
    bluetoothGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
    bluetoothGatt.writeCharacteristic(characteristic);
    
    • 写入描述符

    bluetoothGattDescriptor.setValue(writeContent.getBytes());
    bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);
    
    • 读取特征值

    bluetoothGatt.readCharacteristic(characteristic);
    

    外围设备实现(被连接者/服务端 )

    • 广播的设定

    private AdvertiseSettings buildAdvertiseSettings() {
        AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder()
                //设置广播模式:低功耗、平衡、低延时,广播间隔时间依次越来越短
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
                //设置是否可以连接,一般不可连接广播应用在iBeacon设备上
                .setConnectable(true)
                //设置广播的最长时间,最大时长为LIMITED_ADVERTISING_MAX_MILLIS(180秒)
                .setTimeout(10*1000)
                //设置广播的信号强度 ADVERTISE_TX_POWER_ULTRA_LOW, ADVERTISE_TX_POWER_LOW,
                //ADVERTISE_TX_POWER_MEDIUM, ADVERTISE_TX_POWER_HIGH 信号强度依次增强
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
    
        return builder.build();
    }
    
    • 对外广播的数据(数据限制 31 Bytes)

    private AdvertiseData buildAdvertiseData() {
        AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
                //添加厂家信息,第一个参数为厂家ID(不足两个字节会自动补0,例如这里为0x34,实际数据则为34,00)
                //一般情况下无需设置,否则会出现无法被其他设备扫描到的情况
                .addManufacturerData(0x34, new byte[]{0x56})
                //添加服务进行广播,即对外广播本设备拥有的服务
                .addServiceData(...);
                //是否广播信号强度
                .setIncludeTxPowerLevel(true)
                //是否广播设备名称
                .setIncludeDeviceName(true);
    
        return dataBuilder.build();
    }
    
    • 对外广播(即允许被扫描到)

    private void advertise() {
     BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        BluetoothAdapter mAdapter = bluetoothManager.getAdapter(); 
        AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
            @Override
            public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                //广播成功,建议在这里开启服务
            }
            @Override
            public void onStartFailure(int errorCode) {
                 //广播失败
            }
        };
        BluetoothLeAdvertiser mAdvertiser = mAdapter.getBluetoothLeAdvertiser();
        mAdvertiser.startAdvertising(buildAdvertiseSettings(), buildAdvertiseData(), mAdvertiseCallback);
    }
    
    • 开启服务

    //声明需要广播的服务的UUID和特征的UUID,注意不要占用蓝牙设备默认的UUID
    UUID UUID_SERVICE = UUID.fromString("00001354-0000-1000-8000-00805f9b34fb");
    UUID UUID_CHARACTERISTIC = UUID.fromString("00001355-0000-1000-8000-00805f9b34fb");
    UUID UUID_DESCRIPTOR = UUID.fromString("00001356-0000-1000-8000-00805f9b34fb");
    
    //外围设备状态、数据回调,详情见后面
    BluetoothGattServerCallback serverCallback = new BluetoothGattServerCallback() {...};
    //GATT协议服务
    BluetoothGattServer server = bluetoothManager.openGattServer(this, serverCallback);
    
    //创建一个服务
    BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
            BluetoothGattService.SERVICE_TYPE_PRIMARY);
    
    //创建一个特征,首先此characteristic属性满足BluetoothGattCharacteristic.PROPERTY_WRITY或BluetoothGattCharacteristic.PROPERTY_WRITY_NO_RESPONSE,
    //如果其property都不包含这两个,写特征值writeCharacteristic()函数直接返回false,什么都不做处理。其次此characteristic权限应满足
    //BluetoothGattCharacteristic.PERMISSION_WRITE,否则onCharacteristicWrite()回调收到GATT_WRITE_NOT_PERMITTED回应
    //如果需要既能读,又能写,则可以参考如下写法
    BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(Constants.UUID_CHARACTERISTIC,
            BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
            BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
    
    //创建一个描述符
    BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,
            BluetoothGattDescriptor.PERMISSION_READ);
    
    //将描述符添加到特征中,一个特征可以包含0至多个描述符
    characteristic.addDescriptor(descriptor);
    //将特征添加到服务中,一个服务可以包含1到多个特征
    service.addCharacteristic(characteristic);
    server.addService(service);
    
    • BluetoothGattServerCallback

    public abstract class BluetoothGattServerCallback {
        public BluetoothGattServerCallback() {
        }
    
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            //连接状态被改变
        }
    
        public void onServiceAdded(int status, BluetoothGattService service) {
            //添加服务
        }
    
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
            //被读取特征
        }
    
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            //被写入数据,其中device是写入的设备,value是写入的值,responseNeeded指是否需要恢复,如果需要恢复则调用gattServer.sendResponse()方法回复
        }
    
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
            //被读取描述符
        }
    
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            //被写入描述符
        }
    
        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
        }
    
        @Override
        public void onNotificationSent(BluetoothDevice device, int status) {
        }
    
        @Override
        public void onMtuChanged(BluetoothDevice device, int mtu) {
        }
    
        @Override
        public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
        }
    
        @Override
        public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
        }
    }
    
    • 广播数据格式
      广播数据(或者扫描应答数据)由一个一个的AD Structure组成,对于未满31bytes的其它数据,则填充为0;每个AD Structure由两部分组成:1byte的长度信息(Data的长度),和剩余的Data信息;
      Data信息又由两部分组成:AD Type(长度不定)指示该AD Structure的类型,以及具体的AD Data。

       

       

      例如:

    02 01 06 03 03 aa fe 17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00
    

    02 01 06是一个AD Structure:Data的长度是02;Data是01 06;AD Type是01(Flags);AD Data是06,表明支持General Discoverable Mode、不支持BR/EDR。
    03 03 aa fe是一个AD Structure:Data的长度是03;Data是03 aa fe;AD Type是03(16 bits的Service UUID);AD Data是aa fe,是Eddystone profile的Service UUID。AD Type查询

    注意

    • 外围设备广播的数据最多31 byte
    • 写特征一次最多写入20 byte
    • 单次扫描时间不宜过长(建议10~15秒)
    • BluetoothGattCallback的回调是在非UI线程
    • 每个设备所能使用的Gatt连接是有限个数的,所以应该断开连接时也关闭GattbluetoothGatt.close(),然后重新连接
    • 外围设备蓝牙关闭后,中心设备的BluetoothGattCallback的onConnectionStateChange会回调状态码status = 19newState = 0(断开连接)
    • 外围设备蓝牙关闭后,中心设备再进行连接,BluetoothGattCallback的onConnectionStateChange会回调状态码133,并且当外围设备蓝牙开启后再调用中心设备的bluetoothGatt.connect()不能重新连接,仍然是133错误,所以建议针对133错误进行关闭gatt并释放资源

    源码地址



    作者:丶麦芽
    链接:https://www.jianshu.com/p/3d4bfdc72384
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • Win10 平台C#与低功耗蓝牙BLE设备通信案例

    千次阅读 热门讨论 2020-12-09 19:27:29
    一开始以为是普通的蓝牙设备呢,收到客户寄来的测试设备,才发现是低功耗BLE蓝牙设备。 PS:当时我研发用的台式机是没有蓝牙设备的,客户给寄了个USB的蓝牙适配器,插上后,系统自动安装了驱动,但是在设备发现页面...

    前几天接了个单,客户要在win10电脑上做个工具软件,跟蓝牙锁设备相互通信。一开始以为是普通的蓝牙设备呢,收到客户寄来的测试设备,才发现是低功耗BLE蓝牙设备。

    PS:当时我研发用的台式机是没有蓝牙设备的,客户给寄了个USB的蓝牙适配器,插上后,系统自动安装了驱动,但是在设备发现页面,一直无法发现附近的设备。后来联系到厂家,装了他们的专用驱动就可以了,有遇到类似情况的小伙伴,及得找蓝牙设备厂家要驱动试下。

    下面进入正题,PC上与BLE通信的案例真不多,一开始打算用JavaFX做的呢,后来发现Java在PC上根本没有现成库可以支持与BLE设备通信,果断放弃,改用C#。

    一番查找后,发现也没能找到整套通信案例。就在快要放弃的时候,发现一位博主大神的文章,终于找到了解决问题的方向,感谢互联网的无私分享精神。

    传送门:https://blog.csdn.net/code_long/article/details/105636398?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.control

    在实际研发中,发现上面这位博主的代码有些地方还是有点问题的,本文将一一化解,另外博主最后提到的发现设备慢的问题,本文也一并解决了,目前发现附近设备的时间缩短至3秒左右。

    1、有朋友留言说BluetoothAdapter类无法识别问题:

          本人当时也遇到这问题了,后来发现是文中提到的windows.winmd这个依赖库的问题,本人一开始是从网上下的这个库,里面其他类包括函数都没问题,就没有BluetoothAdapter这个类。后来发现在自己电脑里就能找到这个库,根本不用去网上下载。

          文件地址:C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17763.0,我的电脑是在这个文件夹下,由于Windows版本不同大家路径可能稍有差别,但注意必须是win10系统,win7系统应该不支持BLE通信,win8的就没试过了。

    2、

    /// <summary>
    /// 获取蓝牙服务
    /// </summary>
    public async void FindService()
    {
       var GattServices = this.CurrentDevice.GattServices;
       foreach (GattDeviceService ser in GattServices)
       {
           this.GattDeviceServiceAdded(ser);
       }
    }
    

    在这段代码中,我这边运行的时候是获取不到服务列表的,所以改成如下模式:

    this.CurrentDevice.GetGattServicesAsync().Completed = async (asyncInfo, asyncStatus) =>
                {
                    if (asyncStatus == AsyncStatus.Completed)
                    {
                        var services = asyncInfo.GetResults().Services;
                        this.MessAgeChanged(MsgType.NotifyTxt, "GattServices size="+ services.Count);
                        foreach (GattDeviceService ser in services)
                        {
                            FindCharacteristic(ser);
                        }
                        this.MessAgeChanged(MsgType.NotifyTxt, "获取特码收集完毕" );
                        
                    }
                };

    在获取特征码的时候,也是同样问题,这里就不赘述了,文末会送上整套代码

    3、附近设备发现慢的问题(大概需要30秒左右才能发现附近设备),经过改良后,现在只需3秒左右即可发现附近设备,核心代码如下:

            /// <summary>
            /// 搜索蓝牙设备
            /// </summary>
            public void StartBleDeviceWatcher()
            {
                watcher = new BluetoothLEAdvertisementWatcher();
    
                watcher.ScanningMode = BluetoothLEScanningMode.Active;
    
                // only activate the watcher when we're recieving values >= -80
                watcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;
    
                // stop watching if the value drops below -90 (user walked away)
                watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;
    
                // register callback for when we see an advertisements
                watcher.Received += OnAdvertisementReceived;
    
                // wait 5 seconds to make sure the device is really out of range
                watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
                watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
    
                // starting watching for advertisements
                watcher.Start();
                string msg = "自动发现设备中..";
    
                this.MessAgeChanged(MsgType.NotifyTxt, msg);
            }
            private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
            {
             BluetoothLEDevice.FromBluetoothAddressAsync(eventArgs.BluetoothAddress).Completed = async (asyncInfo, asyncStatus) =>
                    {
                        if (asyncStatus == AsyncStatus.Completed)
                        {
                            if (asyncInfo.GetResults() == null)
                            {
                                this.MessAgeChanged(MsgType.NotifyTxt, "没有得到结果集");
                            }
                            else
                            {
                                BluetoothLEDevice currentDevice = asyncInfo.GetResults();
    
                                Boolean contain = false;
                                foreach (BluetoothLEDevice device in DeviceList)//过滤重复的设备
                                {
                                    if (device.DeviceId == currentDevice.DeviceId)
                                    {
                                        contain = true;
                                    }
                                }
                                if (!contain)
                                {
                                    byte[] _Bytes1 = BitConverter.GetBytes(currentDevice.BluetoothAddress);
                                    Array.Reverse(_Bytes1);
    
                                    this.DeviceList.Add(currentDevice);
                                    this.MessAgeChanged(MsgType.NotifyTxt, "发现设备:"+ currentDevice.Name+"  address:"+ BitConverter.ToString(_Bytes1, 2, 6).Replace('-', ':').ToLower());
                                    this.DeviceWatcherChanged(MsgType.BleDevice, currentDevice);
                                }
                            }
    
                        }
                    };
            }

    软件运行界面截图如下:

    整套代码下载链接:https://download.csdn.net/download/shengfakun1234/13617970

    展开全文
  • 导读:本方案基于低功耗蓝牙方案内置蓝牙4.0芯片,可与支持蓝牙4.0的手机连接,手机通过相应软件可对台灯进行灯光开关和调光控制。  手机遥控调光台灯简介  此调光台灯支持传统的触摸调光控制和手机调光控制两种...
  • 低功耗蓝牙BLE的例子

    2017-03-01 15:16:08
    这是一个基于官方例子改的Ble例子,上面加了我一些注释,希望可以帮到有需要的同学
  • 低功耗蓝牙BLE学习记录.doc
  • 文章目录背景术语Managed FloodingModelsScenes架构Node Features中继节点代理节点友元节点和低功耗节点示例The Provisioning ProcessStep 1: BeaconingStep 2: InvitationStep 3: Public Key ExchangeStep 4: ...

    背景

    我们先了解下传统的拓扑网络:

    • 最常见最广泛使用的星型结构是以中央节点作为核心,其他节点都连接至中央节点上,这种结构的成本较高、可靠性较低,但是其延迟小、结构简单便于管理。

    • 总线型结构则是各个网络设备都挂接在一条总线上,没有明显的中心,优点是结构简单、可扩展性好,但是缺点同样明显,比如维护困难、分支结构故障查找困难。

    • 为了在使用中更安全、更高效,目前绝大部分运行中的商用局域网都采用多种网络拓扑模式组合的方式,尽可能发挥局域网的性能并避免阻碍的产生。
      在这里插入图片描述

    目前典型的局域网布置都采用星型结构或者多层星型结构,网络通过主路由器接入,再分配至各个分路由器,最后连接至不同的主机和设备上。这样的布线实现起来比较简单,并且所需的线缆数量也比较少。这样的布置方式和布置思想横跨了有线和无线时代,比如在家庭中,用户会从电信、联通等网络服务商处接入网络,再通过无线路由器转出多路信号或者无线信号供家中的多个有线、无线设备使用,这也是一个典型的星形结构。

    随着互联网的大爆发和无线网络的发展,之前的星型网络和总线型网络的问题数据中心暴露的愈发突出,比如数据中心的安全性和可靠性、总线型布置维护难度等。

    自2010年 BLE标准颁布以来,其在IOT领域大放异彩,可物有所长,必有其短,在其初级阶段缺乏的一件事是支持多对多拓扑(网状网络),即其中多个BLE设备可以相互手法消息并将消息中继到网络中的其他设备。于是,在2017年7月Bluetooth SIG发布了新的支持方案——Bluetooth mesh standard,而其具有的网状网络结构在处理上诉瓶颈时也更加契合时代发展。

    蓝牙mesh网络的目标是增加 BLE 网络的范围的同时,增加更多利用 BLE 技术的工业应用。在蓝牙mesh发布之前,BLE只支持两种拓扑:

    • 一对一:当两个 BLE 设备相互连接时。
    • 一对多:当 BLE 设备处于广播状态时,例如在 Beacons 中。

    借助蓝牙mesh网络,为 BLE 网络引入了一种新的拓扑结构:设备现在可以在多对多拓扑中运行。

    在这里插入图片描述
    Mesh网络有两个主要好处:

    • 扩展范围:由于节点可以通过它们之间的节点将消息中继到远处的节点,这
      允许网络扩展其范围并扩大设备的覆盖范围。
    • Self-healing 自愈能力:自愈是指不存在单点故障的事实。如果一个节点从网状网络掉线,其他节点仍然可以参与并向另一个节点发送消息。然而,这仅适用于蓝牙网状网络,因为它具有不同的网络中的节点类型,其中一些其他节点可能依赖。我们会本章稍后将介绍不同类型的节点。

    以下是有关蓝牙网状网络的一些重要说明:

    • 蓝牙网状网络支持所有 BLE 版本(即使是原始版本 4.0)并且不需要更改硬件。
      但是,它确实需要安装设备的软件更新字段才能支持该标准。
    • 蓝牙网状网络是独立于 BLE 的标准,并且有自己的规范文档,可在此链接中找到。
    • 蓝牙网状网络 1.0 版不支持 任何蓝牙 5 功能,例如广播扩展和编码的 PHY。
      这在未来的版本中很可能会改变。

    术语

    属于蓝牙网状网络一部分的设备称为 Node / 节点。 不属于网络的设备称为未配置设备。未配置的设备无法发送或接收网格消息;但是,它会向 Provisioners 宣传其存在。

    Provisioner 将在其经过身份验证后邀请未配置的设备进入网状网络,将未配置的设备转换为节点。 一旦未配置的设备被配置,它就会加入网络并成为一个节点。

    一个节点可能包含多个可以独立控制的部分。 例如,一个灯具可能包含多个可以独立打开/关闭的灯泡。 单个节点的这些不同部分称为 Elements / 元素。
    在这里插入图片描述
    Messages / 消息 在蓝牙网状网络中,网络内的所有通信都是面向消息的,节点发送消息以控制或中继信息,消息是调用节点操作的机制。如果一个节点需要报告它的状态,它也会通过消息发送它。给定的消息类型表示对一个状态或多个状态值集合的操作。

    蓝牙mesh中有三种类型的消息,每一种都由一个唯一的操作码(操作码)定义:

    • GET 消息:从一个或多个节点请求状态的消息。
    • SET 消息:更改给定状态值的消息。
    • 状态消息:状态消息用于不同的场景:
      • 响应 GET 消息时发送,包含状态值。
      • 响应已确认的 SET 消息时发送
      • 独立于任何消息发送以报告元素的状态。一个示例是由在发送此消息的元素上运行的计时器触发的消息。某些消息需要原始消息的接收者发送确认消息。

    确认消息有两个目的:

    • 确认收到消息。
    • 返回与接收到的消息相关的数据。

    在发送方未收到对消息的响应或收到意外响应的情况下,发送方可以重新发送该消息。一个节点收到的多个确认消息不影响行为(就好像消息收到一次一样) 。

    蓝牙网状网络中的消息必须发送到地址或从地址发送。 Address / 地址分为三种类型:

    1. Unicast Address / 单播地址:唯一标识在provisioning / 配置过程中分配的单个节点的地址。
    2. Group Address / 组地址:用于标识一组节点的地址。组地址通常反映节点的物理分组,例如特定房间内的所有节点。组地址可以是:
      • 由蓝牙 SIG 定义,也称为 SIG 固定组地址。这些包括全代理、全朋友、全中继和全节点组地址。
      • 动态组地址,由用户通过配置应用程序定义。
    3. Virtual Address / 虚拟地址:可以分配给一个或多个元素的地址,跨越一个或多个节点。它充当label / 标签并采用 128 位 UUID 的形式,任何元素都可以与之关联。虚拟地址很可能在制造时预先配置。

    在蓝牙网状网络中交换消息的方式是通过发布订阅模式。 来自维基百科关于发布订阅模式的页面:

    在软件架构中,发布-订阅是一种消息传递模式,其中消息的发送者(称为发布者)不会将消息编程为直接发送给特定的接收者(称为订阅者),而是将已发布的消息分类,即使不知道哪些订阅者(如果有)。

    类似地,订阅者表示对一个或多个类感兴趣并且只接收感兴趣的消息,而不知道有哪些发布者。

    Publishing / 发布是发送消息的行为。Subscribing / 订阅是一种配置,用于允许将选择的消息发送到特定地址进行处理。通常,消息被寻址到组地址或虚拟地址。

    这是一个家庭中网状网络的例子,它由 6 个电灯开关和 9 个灯泡组成。网络利用发布-订阅方法允许节点相互发送消息。

    在这里插入图片描述
    节点可以订阅多个地址,例如本示例中的 light #3,它订阅了厨房和餐厅组地址。此外,多个节点可能会发布到同一地址:例如本示例中的开关 #5 和 #6。这两个开关控制位于花园中的同一组灯。使用组地址或虚拟地址的好处是添加或删除节点不需要重新配置节点。

    Managed Flooding

    许多网状网络使用路由机制(routing mechanisms)在网络上中继消息,另一个极端是在不考虑这些消息到达其预期目的地所需采取的最佳路由的情况下,用正在中继的消息淹没网络(flood the network)。蓝牙网状网络使用的技术是这两种技术的折衷方案。

    这种技术被称为管理泛洪(Managed Flooding)。本规范使用多种方法来限制托管洪水网状网络中消息的无限制中继。使用的两种主要方法是网络消息缓存方法和生存时间方法。

    泛洪管理依赖于向发送方节点范围内的所有节点广播消息,并添加了一些优化:

    • 消息分配了TTL :TTL 代表生存时间,这限制了消息在mesh网络中可以跨越多个节点的越点数。零值表示消息尚未中继且不应中继。这意味着节点可以向其直接无线电范围内的其他节点发送消息,并指示接收节点不应中继该消息。

      如果发送的消息的 TTL ≥ 2,则每次中继时,TTL 值都会递减。TTL 值为 1 意味着该消息可能至少已中继一次,但不应再次中继。
    • Message Cache / 消息缓存:所有节点都需要消息缓存,并要求立即丢弃已存在于缓存中的接收消息。
    • Heartbeat / 心跳消息定期发送:心跳消息用于向其他节点指示发送方在网络中处于活动状态。每个节点定期发送Heartbeat消息,通知其他节点它是活跃的。并且它还可以帮助接收节点计算向心跳源节点发送消息所需的希望数。
    • Friendship / 友谊:友谊是指两个节点之间的关系。这两种节点类型是:
      • 低功率节点或 LPN( low-power node),可以节省功率并且不能一直接收网格消息。该节点大部分时间都在关闭无线电。
      • 一个实时供电的节点称为朋友节点(friend node),它可以作为 LPN 的代理。友元节点为LPN缓存消息以节省电量,使LPN可以大部分时间保持睡眠,只是偶尔醒来。当 LPN 醒来时,它会轮询朋友节点以读取缓存的消息并将它需要发送的任何消息发送到网状网络。

    Models

    Models / 模型定义给定 element / 元素的部分或全部功能。模型分为三类:

    1. Server model:是包含模型的元素可以发送或接收的状态、状态转换、状态绑定和消息的集合。
    2. Client model:没有定义任何状态; 相反,它只定义了诸如发送到服务器模型的 GET、SET 和 STATUS 消息之类的消息。
    3. Control model:包含服务器和客户端模型,允许与其他服务器和客户端模型进行通信。

    Scenes

    Scenes / 场景是存储的状态集合,由 16 位数字标识,该数字在网状网络中是唯一的。场景允许触发一个动作来设置不同节点的多个状态。它们可以按需触发或在指定时间触发。

    例如,可以将场景配置为将房间的温度设置为 72 度,将客厅的灯设置为某个亮度级别,并关闭百叶窗。

    架构

    蓝牙MESH基于低功耗蓝牙,并与该协议共享最低层。蓝牙MESH物理层与现有的低功耗蓝牙设备兼容,因为MESH消息包含在BLE广播包的有效负载中。 但是蓝牙MESH指定了一个全新的主机层,尽管一些概念是共享的(例如Host层),但蓝牙MESH与低功耗Host 层是不兼容的。
    在这里插入图片描述
    但蓝牙mesh网络依然建立在 BLE 之上,它专门利用了 BLE 设备的广播状态。蓝牙mesh网络中的设备不像传统的 BLE 设备那样相互连接。相反,它们使用广播和扫描状态来相互中继消息。在可以成为网状网络一部分的特殊设备中,有一个例外(我们将在“Node Type”一节中介绍)

    在这里插入图片描述

    1. Bluetooth Low Energy layer:蓝牙低功耗层

      正如我们之前提到的,蓝牙网状网络构建在 BLE 之上,因此它需要在设备上运行完整的 BLE 协议栈。它利用在mesh网络内的设备之间广播和扫描状态发送和接收消息,它还支持称为代理节点 / proxy node的特殊设备的连接状态和 GATT。
    2. Bearer layer:承载层

      承载层定义了网络消息(Protocol Data Units or PDUs)如何在节点之间传输。 定义了两种承载,广播承载和 GATT 承载。 未来可能会定义额外的承载。
      1. 广播承载:该承载利用BLE的广播和扫描状态设备。
      2. GATT 承载:该承载利用 BLE 设备的连接状态。它允许非网状支持设备通过 GATT 操作与网状网络交互。这是通过称为代理节点的特殊节点完成的。
    3. Network layer:网络层

      网络层定义了传输消息如何寻址到一个或多个 elements / 元素。它定义了允许承载层传输传输 PDU 的网络消息格式。网络层决定是中继/转发消息,接受它们以供进一步处理,还是拒绝他们。它还定义了如何加密和验证网络消息。
    4. Lower transport layer:下层运输层

      较低的传输层定义了如何将Upper transport layer消息分段并重新组合成多个较低的传输 PDU供 layer below(bearer layer),以将较大的 Upper transport layer消息传递给其他节点。它还定义了单个控制消息来管理分段和重组。
    5. Upper transport layer:上层传输层

      上层传输层对应用程序数据进行加密、解密和验证,旨在提供访问消息的机密性。它还定义了如何使用传输控制消息来管理节点之间的上层传输层,包括何时被 Friend feature使用。
    6. Access layer:接入层

      接入层定义了higher layer应用程序如何使用upper transport layer。它定义了应用程序数据的格式; 它定义和控制在上层传输层执行的应用程序数据加密和解密; 并检查传入的应用程序数据是否已在正确的网络和应用程序密钥的上下文中接收,然后再将其转发到the higher layer。
    7. Foundation Models layer:基础模型层

      基础模型层定义了配置和管理网状网络所需的状态、消息和模型。
    8. Models layer:模型层

      模型层定义了用于标准化典型用户场景操作的模型,并在蓝牙mesh网络模型规范 或其他更高层规范中定义。 更高层模型规范的示例包括照明和传感器模型,包括行为、消息、状态和状态绑定。

    Node Features

    所有类型的节点都可以发送和接收网格消息。但是,可选功能赋予特定节点特殊功能。以下是启用了可选功能的不同类型的节点:

    • Relay feature : 中继特性,通过广播载体接收和重传网状消息的能力,以支持更大的网络。
    • Proxy feature :代理特性,在GATT 和 广播载体之间接收和重新传输网状消息的能力。没有 Mesh 协议栈的设备可以使用代理节点的 GATT 承载与 BLE mesh 中的设备进行交互。
    • Friend feature :友元节点,通过在低功耗​​节点轮询时存储和转发发往低功耗节点的消息,与低功耗节点一起工作。
    • Low power feature :低功耗节点,大部分时间都处于低功耗状态并且射频处于关闭状态,仅在与支持 Friend 功能的节点结合使用时,才能在网状网络内以显着降低的接收器占空比运行。

    节点可能不支持、部分或全部这些可选功能,这些可选功能可以随时启用或禁用。
    例如,单个节点可能同时具有中继节点、代理节点和朋友节点的特征。

    中继节点

    中继节点是支持中继功能的节点。 这意味着它可以重新传输由其他节点广播的消息。 这可以扩展这些消息的范围,并允许它们在原始传输节点的范围之外遍历整个网络。

    代理节点

    为了允许来自不支持网状网络的 BLE 设备与网状网络进行通信,可以使用一种称为代理节点的特殊类型的节点。代理节点充当中介,并利用 GATT 操作允许网状网络之外的其他节点与网络接口和交互。

    在这种情况下使用的协议称为代理协议,旨在与启用连接的设备(使用 GATT)一起使用。该协议建立在 GATT 之上,允许设备从代理节点公开的 GATT 特征中读取和写入代理协议 PDU。

    代理节点执行代理协议 PDU 和网状 PDU 之间的转换。例如,这允许未实现蓝牙网状网络协议的智能手机通过代理设备通过 GATT 操作与网状网络交互。
    在这里插入图片描述

    友元节点和低功耗节点

    朋友节点和低功耗节点(LPN)彼此密切相关。事实上,为了让低功耗节点加入蓝牙mesh网络,它需要与另一个节点建立友谊关系,称为友元节点。

    以下是这两种类型的节点如何协同工作:

    • 低功率节点通常具有有限的电源(例如电池),因此它们需要通过尽可能经常关闭无线电来节省能源。
    • 低功率节点可能更关心发送消息而不是接收消息。以由纽扣电池供电的温度传感器为例:每当读数高于或低于设定限制时,它可能需要每分钟发送一次温度读数。如果用户决定更改阈值限制,则会向温度传感器发送消息。为了让传感器不会错过此阈值配置消息,它需要一直处于开启状态,这意味着它会消耗大量电量。
    • 为了解决上一点提到的问题,引入了友元节点的概念。友元节点让低功耗节点保持更长时间的睡眠状态。
    • 友元节点通过缓存发往低功耗节点的消息使这成为可能。低功耗节点在其控制下唤醒并轮询友元节点以获取这些缓存消息。当它轮询消息时,它还向其友元节点发送它需要中继到网络的任何消息。
    • 友元节点和低功耗节点之间的关系称为“友谊”。
    • 友谊是允许功率受限节点参与网状网络同时保持其功耗优化的关键。
      在这里插入图片描述

    示例

    支持上述各种功能的节点可以形成mesh网络。 mesh网络的图示如下图 2.8 所示。
    在这里插入图片描述
    图 2.8 显示了三个中继节点:Q、R 和 S。

    支持好友功能的三个节点是N、O、P,但是N没有任何好友关系;因此只有 O 和 P 是有缘节点。

    有五个低功耗节点:I、J、K、L 和 M。节点 I、J 和 K 有 P 作为他们的朋友,而 L 和 M 有 O 作为他们的朋友。

    节点 T 仅使用 GATT 承载连接到网状网络;因此 S 必须将所有消息转发到 T 或从 T 转发所有消息。

    例如,如果一条消息要从 T 发送到 L,那么 T 将使用 GATT 承载将消息发送到节点 S。节点 S 将使用广播承载重传此消息。节点 H、R、N 和 O 在节点 S 的无线电范围内;因此他们将收到此消息。


    作为节点 L 的朋友的节点 O 将存储该消息,如果该消息是分段消息,则节点 O 将在较低的传输层以确认进行响应。稍后,L 将轮询节点 O 以检查新消息,以便 O 将 T 最初发送的消息转发给 L。

    BLE Mesh Networking 在智能家居中是如何工作的?

    让我们假设一个有走廊和 3 个房间的智能家居。这将帮助我们了解 BLE 网格如何在不处于该设备的 BLE 范围内的情况下向任何节点发送消息。每个房间至少有 1 个灯泡。客厅和卧室也有恒温器,用于控制温度。

    在这里插入图片描述
    智能手机应用程序在单一网状网络中扮演供应商、添加灯泡和恒温器的角色,通过交换消息实现它们之间的通信。

    与温控器T2通信,最直接的方式是通过blub B1-B5传递消息,到达温控器T2。但是,这条最短路径可能会被家中的墙壁或其他金属器具挡住。在这种情况下,中继节点 B2-B3-B4-B5 可以帮助智能手机应用程序到达恒温器 T2。

    智能手机应用程序用于与不支持 BLE Mesh 但支持 BLE 的恒温器 T2 通信。因此,智能手机应用程序必须找到一个代理节点,该节点可以作为与网状设备通信的中介。Bulb B1 是这里的代理节点,它接收来自智能手机应用程序的消息并将消息发布到整个网状网络。

    走廊里的灯泡B3只是一个中继节点,在网络中传递消息。灯泡 B4 和 B5 作为恒温器 T1 和 T2 的朋友节点,恒温器被抽象为低功耗节点。

    这是一个简单的示例,用于演示 BLE 网格在智能家居中的作用。但是,BLE 网格使智能家居能够提供高级家庭自动化功能,如组、场景、日程管理等。


    The Provisioning Process

    配置过程是蓝牙网状网络中最重要的概念之一。它用于将设备添加到网状网络。

    添加到网络的设备称为节点,用于向网络添加节点的设备称为供应器(通常是平板电脑、智能手机或 PC)。这个过程包括五个步骤:

    在这里插入图片描述

    Step 1: Beaconing

    步骤 1 涉及所谓的 beaconing / 信标,其中未配置的设备通过在广播数据包中发送网格信标广播来宣布其可用性。

    这是蓝牙网状网络标准中引入的一种新型广播数据类型。触发此过程的常见方法是通过未配置设备上定义的按下按钮的顺序。

    Step 2: Invitation

    当供应商通过发送的信标发现 unprovisioned device / 未供应的设备时,它会向该未供应的设备发送 邀请 / Invitation。这使用了蓝牙网状网络中引入的一种新型 PDU,称为provisioning invite PDU / 供应邀请 PDU。

    然后未供应设备在供应能力 PDU 中使用有关其能力的信息进行响应,其中包括: - - 设备支持的元素数量。

    • 支持的安全算法集。
    • 其公钥的可用性使用带外 (OOB) 技术。
    • 此设备向用户输出值的能力。
    • 此设备允许用户输入值的能力。
      在这里插入图片描述

    Step 3: Public Key Exchange

    蓝牙网状网络中的安全性涉及对称和非对称密钥的组合使用,例如椭圆曲线 Diffie-Hellman (ECDH) 算法。在 ECDH 中,公钥在供应商和要供应的设备之间交换。

    这是直接通过 BLE 或通过带外 (OOB) 通道完成的。

    在这里插入图片描述

    Step 4: Authentication

    下一步是对未配置的设备进行身份验证。这通常需要用户通过与供应商和未供应设备进行交互来执行操作。身份验证方法取决于所使用的两个设备的功能。

    在一种称为输出 OOB 的情况下,未配置的设备可以以某种形式向用户输出随机的一位或多位数字,例如多次闪烁 LED。然后通过某种输入方法将该号码输入到供应设备中。

    其他情况包括输入 OOB,其中数字由供应商生成并输入未供应的设备、静态 OOB 或根本没有 OOB。

    无论使用何种认证方法,认证还包括确认值生成步骤和确认检查步骤。

    Step 5: Provision Data Distribution

    身份验证完成后,每个设备使用其私钥和从其他设备发送给它的公钥派生会话密钥。

    然后,会话密钥用于保护连接以交换额外的供应数据,包括网络密钥 (NetKey)、设备密钥、称为 IV 索引的安全参数以及分配给供应设备的单播地址 供应商。

    在此步骤之后,未配置的设备将成为节点。

    Security in Bluetooth mesh

    关于蓝牙网状网络安全性的第一个重要说明是它是强制性的。这与 BLE 相比是不同的,BLE 是可选的,由开发人员决定是否包含它。以下是蓝牙网状网络安全的一些基础知识:

    • 所有网格消息都经过加密和身份验证
    • 网络安全、应用安全和设备安全都是独立处理的。
    • 在mesh网络的生命周期内可以更改安全密钥

    由于网络、应用程序和设备级别之间的安全性分离,因此存在三种类型的安全密钥(每种都针对特定问题):

    • 网络密钥(NetKey)
      拥有此共享密钥使设备成为网络的一部分(也称为节点)。从 NetKey 派生出两个密钥:网络加密密钥和隐私密钥。拥有 NetKey 允许节点解密和验证网络层,允许消息中继,但不能解密应用程序数据。

    • 应用密钥(AppKey)
      这是在mesh网络中的节点子集之间共享的密钥,通常是那些参与通用应用程序的节点。例如,照明系统 AppKey 将在电灯开关和灯泡之间共享,但不与恒温器或运动传感器共享。

      AppKey 用于在应用程序级别对消息进行解密和验证,但它仅在一个网状网络内有效,而不是跨多个网络。

    • 设备密钥 (DevKey)
      这是一个特定于设备的密钥,在配置过程中使用,用于保护未配置设备和配置者之间的通信。


    节点移除

    mesh网络的一个主要问题是通过丢弃或移除曾经是网络一部分的设备来访问网络。这可以通过对存储在设备中的密钥进行物理访问来实现(通常称为 trash can attack / 垃圾桶攻击)。

    为了防止这种攻击,蓝牙网状网络定义了一个删除节点的程序,在该程序中设备被添加到黑名单并刷新密钥。此过程将新的网络密钥、应用程序密钥和其他相关数据分发给除黑名单中的节点之外的所有节点。

    隐私

    另一个问题是 Privacy / 隐私。蓝牙mesh网络中解决隐私问题的方法是使用用于混淆消息头的隐私密钥。隐私密钥源自网络密钥 (NetKey)。

    例如,可以混淆源地址以防止通过其地址跟踪设备。


    重放攻击

    我们要讨论的最后一个安全主题是重放攻击。重放攻击是指恶意设备存储一条或多条消息并稍后重放。

    蓝牙网状网络通过以下方式提供针对重放攻击的保护:

    • 利用序列号 (SEQ)
      元素每次发布消息时都会增加 SEQ 值。一个节点从一个元素接收到一个消息,如果其中包含一个小于或等于最后一个有效消息中的 SEQ 值的消息,将丢弃它(因为它很可能与重放攻击有关)。
    • 使用递增的 IV 索引
      这是一个附加值,在收到消息时也会得到验证。

    对标ZigBee

    • 路由机制:对于较小节点数量的网络,两者的性能相似。但当节点处于移动状态时,Zigbee的路由机制可能会受影响,因为ZigBee的动态路由维护,将交换大量路由协议消息以更新路由表,这可能会导致网络拥塞。

      但由于Zigbee节点对较大数据包的延迟较小,因此它可以封装较高层的帧头和命令,同时提供良好的性能。它可以与单播和广播结合使用以提高可靠性。当在链路上发送大于12个数据包的大小时,由于在网络层执行分包和重组,蓝牙Mesh的延迟会更高一些。因此,与Zigbee相比,在传输大量需要分包和重组的消息命令当中,蓝牙Mesh的时延会更高一些,而Zigbee表现会更好一些。
    • 数据速率:BLE 4.2标准速率为1Mbps,而BLE 5.0提高了2倍,通讯速度最高为2Mbps。Zigbee基于802.15.4,该协议将数据速率限制为最高250Kbps。
    • 范围:节点到节点范围的计算取决于多个因素,例如发射功率,工作频率,接收器灵敏度,天线等。Zigbee(在2.4GHz)通常比BLE 4.0传输距离更远,两者都不加PA(功率放大器)的情况下,Zigbee基本能做到100米。而最新发布的蓝牙5 标准的覆盖范围是蓝牙4.2 的4 倍。也就是300米,所以基于BLE 5.0的蓝牙Mesh网络将拥有更远的传输距离。
    • 功耗:Zigbee和BLE的功耗相当。此外,Zigbee中采用的路由机制使其电源效率略高。但是随着蓝牙5的速率提升,发送相同大小的数据包蓝牙Mesh将会更快,发射机和接收机的工作时间也将进一步缩短。
    • 复杂程度:Zigbee具有较小的协议栈,但其路由管理起来更为复杂。相比之下,基于BLE的蓝牙Mesh总体上来说更复杂,但是路由很简单。
    • 其他:BLE支持信标,这意味着基于蓝牙Mesh的应用除了照明以外,还可以用于资产跟踪,室内定位与导航。

    总结:

    凭借其更简单的路由机制,蓝牙Mesh更适用于移动节点,因为Zigbee路由协议会为维护高度移动节点的路由表带来更多网络开销。因此,在选择技术实现之前,必须考虑使用场景。

    较低的硬件成本,流行的OS实现中的稳定协议栈以及智能手机控制,这是蓝牙Mesh相对Zigbee的主要优势。由于Zigbee较早进入该市场领域,因此它目前在家庭网络,工业物联网(IIoT)系统中拥有更大的生态系统。但是,作为现有BLE堆栈之上基于软件的解决方案,蓝牙Mesh可以在不久的将来迅速赶上。


    参考文献

    1. Intro to Bluetooth Low Energy v1.1.pdf
    2. 什么是Mesh网络结构? - CSDN - zpznba
    3. Extend the Power of IoT Solutions with BLE Mesh Network
    展开全文
  • 安卓蓝牙 Bluetooth 低功耗蓝牙demo BLE
  • 低功耗蓝牙BLE芯片,FR8016HA蓝牙开发板PDF原理图使用说明+altium pcb图文件,硬件参考设计,PCB采用2层板设计,大小为80*63mm,可做为你的学习设计参考。
  • 低功耗蓝牙BLE的日志文件,可以分析BLE的内容。对BLE的规格有更好的理解。
  • 连接事件 在一个连接当中,主设备会在每个连接事件里向从设备发送数据包。一个连接事件是指主设备和从设备之间相互发送数据包的过程。连接事件的进行始终位于一个频率,每个数据包会在上个数据包发完之后等待 150μs...
  • 利用低功耗蓝牙BLE实现计步测温功能,计步通过mpu6050陀螺仪实现,利用GY906传感器实现红外测温
  • 低功耗蓝牙ble应用库

    2018-10-19 10:25:07
    整理的一套很实用的android低功耗蓝牙ble应用库,包含周边代码与中心代码。
  • 经典蓝牙和低功耗蓝牙BLE的区别 蓝牙大致被认为是1.0 2.0 3.0 4.0版本,不过现在已经不再用版本号区分蓝牙了,蓝牙1.0~3.0都是经典蓝牙,在塞班系统就已经开始使用了。而蓝牙4.0开始就是包括蓝牙BLE了。蓝牙4.0是...
  • 这是使用QT编写,在Android手机上使用的BLE低功耗蓝牙调试助手。用于调试低功耗蓝牙的数据通信。 本软件是一款BLE蓝牙串口调试助手,支持常规的发送和接收调试,BLE是低功耗类型蓝牙,在智能家居、物联网领域使用较...
  • 低功耗蓝牙BLE传统广播总结

    千次阅读 2020-03-26 16:24:46
    低功耗蓝牙BLE传统广播总结 低功耗蓝牙:Bluetooth Low Energy简称BLE,相较于传统蓝牙BT具有低功耗、低成本、小体积等优势,BLE和BT都是工作在全世界公开通用的2.4GHz无线频段上,但他们是完全不同的两种技术,只是...
  • 第一章 协议组成图 1.1、协议由 HOST 层和 CONTROLLER 层组成 1.1.1、CONTROLLER 组成 1.1.2、HOST 组成 第二章 控制器 2.1、PHY 物理层 2.2、链路层描述 2.8、连接状态 2.9、空中接口包 第三章 主机 ...
  • 1、问题描述最近进行Android ble的开发,遇到最大的问题就是在往characteristic中写入数据的时候,会有一个成功率抖动的问题,常会出现第一次写入失败,必须再写一次才成功的情况。每次往characteristic中写入数据的...
  • 低功耗蓝牙BLE扫描

    2021-01-11 17:35:57
    BLE扫描:Scan,扫描周围环境中的低功耗蓝牙设备,常见于第三方APP搜索低功耗蓝牙(手环、手表、体脂秤等应用中) 简单的BLE扫描其实也是分为多种扫描方式,按照协议主要分为如下两种: Passive Scanning — 被动...
  • 但是从连接开始就要了解一些低功耗蓝牙的知识,这些是在Android以外的,现在介绍最基础的使用。 1.连接 public void connect(Context context, BluetoothDevice device) mBluetoothGatt = device.connectGatt...
  • Android 低功耗蓝牙BLE连接通信

    千次阅读 2020-03-29 23:21:22
    蓝牙(Bluetooth®):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为...
  • BLE低功耗蓝牙快速入门,适合刚接触蓝牙没多久的小白以及想了解蓝牙低功耗技术的人。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,416
精华内容 3,366
关键字:

低功耗蓝牙BLE