赵工的个人空间


网络课堂部分转编程技巧部分转编程演练

 Android编程技巧

首页 > 网络课堂 > Android编程技巧 > 连接BLE设备传输数据
连接BLE设备传输数据
安卓设备搜索到附近的蓝牙低功耗设备,并显示在一个ListView中后,就可以通过点击对应项与相应设备建立连接。
1)选择蓝牙低功耗设备:
为了连接ListView中相应项的设备,需要先为名为lv的ListView加入点击事件处理:   lv.setOnItemClickListener(this);
对应的事件处理函数为:
  public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
     TextView txv = (TextView) v;
     String s = txv.getText().toString();
     String[] addr = s.split("\n");
     btDevice = btAdapter.getRemoteDevice(addr[1]);    //通过MAC地址获取蓝牙设备
     //建立GATT连接
     btGatt = btDevice.connectGatt(MainActivity.this, false, gattcallback);
  }

与BLE设备建立连接,需要使用BLE的MAC地址,在ListView中显示的每一项Item都是由name和Adress两部分组成,用"\n"分隔,因此代码中要把此字符串按"\n"分开为数组,索引1即为MAC地址。使用BluetoothAdapter的getRemoteDevice(macAddress)方法就能获取到连接对端的BluetoothDevice设备。
蓝牙低功耗使用Gatt协议,因此要使用BluetoothGatt建立连接,使用的是BluetoothDevice的connectGatt()方法,其中使用了一个建立连接的回调函数gattcallback。
当然,也可以不用在界面中列出附近BLE中的每一项,而是通过获取的BLE的name或MAC地址等信息选择连接的对端。
连接BLE设备的代码在gattcallback回调函数中:
  private BluetoothGattCallback gattcallback = new BluetoothGattCallback() {}
在Android Studio中,这个函数具有固定的结构,包括GATT连接变化响应、GATT连接发现服务、GATT读属性、GATT写属性、GATT属性变化、GATT读描述、GATT写描述等等多个方法,只需要使用其中几个方法并修改其中的代码,其他方法可以使用系统提供的默认代码。
2)蓝牙低功耗设备的连接响应:
  @Override
  public void onConnectionStateChange(BluetoothGatt gatt, int status, final int newState) {
     super.onConnectionStateChange(gatt, status, newState);
     runOnUiThread(new Runnable() {
       @Override
       public void run() {
         String status;
         switch (newState) {
           case BluetoothGatt.STATE_CONNECTED:
             sta.setText(btDevice.getAddress()+"已连接");
             btGatt.discoverServices();
             break;
           case BluetoothGatt.STATE_CONNECTING:
             sta.setText("正在连接...");
             break;
           case BluetoothGatt.STATE_DISCONNECTED:
             sta.setText("已断开");
             break;
           case BluetoothGatt.STATE_DISCONNECTING:
             sta.setText("断开中...");
             break;
         }
       }
     });
  }

代码中,定义了4种连接状态,已连接、正在连接、已断开、正在断开,使用switch结构分别进行处理,并在一个名为sta的TextView中进行提示,不过一些状态持续时间比较短,未必能在界面中看到对应显示。
这个连接状态响应函数的主体是在一个单独线程中进行的,主要是为了避免在主界面出现卡死现象。如果连接成功,就会执行BluetoothGatt的discoverServices()方法,获取对端可以提供的服务。
2)蓝牙低功耗设备的服务发现:
要建立蓝牙低功耗数据传输,需要先得到连接对端的UUID对应的Characteristic。蓝牙连接建立后,就要运行可提供服务的发现代码:
  @Override
  public void onServicesDiscovered(BluetoothGatt gatt, int status) {
     super.onServicesDiscovered(gatt, status);
     txt = "";
     if (status == btGatt.GATT_SUCCESS) {
       final List<BluetoothGattService> services = btGatt.getServices();
       runOnUiThread(new Runnable() {
         @Override
         public void run() {
           for (final BluetoothGattService bluetoothGattService : services) {
             gattSVC = bluetoothGattService;
             txt = txt + "\n" + gattSVC.getUuid().toString();  //显示UUID列表
             List<BluetoothGattCharacteristic> charc = bluetoothGattService.getCharacteristics();
             for (BluetoothGattCharacteristic charac : charc) {
               tz = charac.getUuid().toString();
               List<BluetoothGattDescriptor> dess=charac.getDescriptors();
               String str="";
               for(int i=0;i<dess.size();i++){
                 str+=dess.get(i).getPermissions();
               }
               String ret="";
               txt = txt + "\n\t" + tz+"*"+str;  //显示属性UUID列表
               rxd.setText(txt);  //显示UUID列表
               if ((tz.equals(TXDUUID)) ) {
                 gattTxd = charac;
                 btTxd.setEnabled(true);  //发送按钮使能
               }
               if ((tz.equals(RXUUID)) ) {
                 gattRxd = charac;
                 enableNotification(true, gattRxd);
               }
               rxd.setText(txt);  //显示UUID列表
             }
           }
         }
       });
     }
  }

代码中,如果状态status == btGatt.GATT_SUCCESS,即GATT协议建立成功,就使用BluetoothGatt的getServices()方法获取对端可以提供的服务BluetoothGattService,并存入BluetoothGattService类型的列表services中,后面的代码也是在一个单独的线程中运行,避免界面出现卡死现象。
然后在获取到的服务列表中遍历,使用getUuid()方法获取服务的UUID信息;再使用getCharacteristics()方法获取每个服务的特性BluetoothGattCharacteristic列表,继而遍历所有特性,得到每个Characteristic的UUID;再使用getDescriptors()方法获取每个特性的描述BluetoothGattDescriptor列表,遍历描述列表,使用getPermissions()方法获取授权信息,最后加以显示。这样就可以在界面中的TextView中显示出来对端所有的服务和特性的UUID,并有授权信息,当然也可以加入其他代码以显示出更多信息。
后面一部分代码是通过预先设置的UUID字符串进行判断,如果与TXDUUID相同,就将此BluetoothGattCharacteristic设为BLE的GATT发送Characteristic,如果与RXUUID相同,就设为BLE的GATT的接收Characteristic并使用enableNotification()方法使能其Notification功能。对特定的蓝牙低功耗模块硬件及软件版本,有固定的发送/接收数据的UUID,通过上述方法获取到后,就可以对应进行收发数据了。
3)蓝牙低功耗Notification及功能设置:
对蓝牙低功耗设备,发送数据使用write()方法,而获取数据则使用的是Notification。也就是在建立双方连接后,对端如果有数据就会以Notification方式传送过来,而Android系统则是通过onCharacteristicChanged事件处理获取,代码为:
  @Override
  public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
     super.onCharacteristicChanged(gatt, characteristic);
     final byte[] values = characteristic.getValue();
     runOnUiThread(() -> {
       len = values.length;
       String str= "";
       for(byte b:values){
         str.append(String.format("%02x",new Integer(b&0xff)));
       }
       rxd.setText(str.toString());*/
     });
  }

在Android系统中,蓝牙低功耗数据收发都使用字节数组byte[]格式,从对端传来的数据使用接收characteristic的getValue()方法,获得的就是字节数组byte[]。
后续处理也在一个单独的线程中进行,如果是普通字符数据,可以使用new String(values)直接将字节数组转换为一个对应的字符串,然后在TextView中显示出来;但如果要以十六进制值的方式显示,就要按字节逐个转换,并连接为一个十六进制字符串,再赋值给一个TextView的文本以便显示。
上述代码也是需要在BluetoothGatt连接的Callback函数中定义,是回调的一部分。
不过,连接对端的Notification默认情况下并未使能,在连接过程中获取到接收Characteristic后,需要使用enableNotification()方法使其有效,具体代码为:
  private String enableNotification(boolean enable, BluetoothGattCharacteristic characteristic) {
     boolean res = btGatt.setCharacteristicNotification(characteristic, true);
     if(res){
       List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
       if (null != descriptors && descriptors.size() > 0) {
         for (BluetoothGattDescriptor descriptor : descriptors) {
           descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
           boolean res1 = btGatt.writeDescriptor(descriptor);
           if (res1) {
             return "Cfg Succ";
           } else {
             return "wDescri fail";
           }
         }
       }else{
         return "getDescri fail";
       }
     }else{
         return "setCharNotif fail";
     }
     return "0";
  }

代码中,先直接使用BluetoothGatt的setCharacteristicNotification()方法,把对应的characteristic的Notification特性设为true;根据返回值进行判断,如果为true,使用getDescriptors()方法获取此characteristic的描述列表,如果获取到就进行遍历,对其中每一个descriptor都使用setValue()方法设置值为ENABLE_NOTIFICATION_VALUE。然后使用writeDescriptor()方法,如果返回值为true表示配置成功。
上述代码在BluetoothGatt连接的Callback函数之外,其中有多个分支,目的是配置不成功时检查是哪一步出现了问题,因此赋值了不同的返回值字符串,方便调试,调试完成运行正常后可以不再显示返回的字符串。
通过实测,连接的蓝牙低功耗模块启动时,具有Notification功能的Characteristic,有个16bit的可配置值,初始为0000,如果使用enableNotification()方法配置成功,低位就会变为1,这样就使能了Notification。不同的模块可能也会有一些差别,但通过此指令使能Notification功能是一致的。
4)蓝牙低功耗发送数据:
Android设备向蓝牙低功耗设备发送数据也是使用字节数组格式,代码为:
  if(btGatt!=null) {
     strSend = txd.getText().toString();
     byte[] hex=strSend.getBytes();
     gattTxd.setValue(hex);  //向蓝牙服务中的写属性写入待发送数据
     boolean res=btGatt.writeCharacteristic(gattTxd);  //发送数据
  }

代码中,先从名为txd的TextView中获取到值并转换为字符串,直接使用String的getBytes()方法得到对应的字节数组,然后使用setValue()方法把此字节数组传递给对应的BluetoothGattCharacteristic,最后使用BluetoothGatt的writeCharacteristic()方法将数据发送出去。
蓝牙低功耗一次发送的数据一般为20字节左右,较大的数据包需要截为数段分别发送。为了避免发送数据操作影响界面的响应,可以放在单独的线程中。也可以根据发送的内容不同,使用不同的编码转换方法。
Copyright@dwenzhao.cn All Rights Reserved   备案号:粤ICP备15026949号
联系邮箱:dwenzhao@163.com  QQ:1608288659