Android Java应用蓝牙连接自定义设备内置Web服务器的技术咨询
问题背景
我正在用Android Java应用对接一台内置Web服务器的自定义设备,目前已成功实现两种连接方式:
- WiFi
- Hotspot
我通过HTTP对象请求与该Web服务器通信,获取相关参数。当前目标是新增蓝牙连接方式,实现直接连接自定义设备并与Web服务器通信。
作为蓝牙领域的完全新手,为理解蓝牙连接原理,我完成了如下实现:
private void printGattTable() { if (services.isEmpty()) { Log.d("printGattTable", "No service and characteristic available, call discoverServices() first?"); } services.forEach(service -> { characteristics = service.getCharacteristics(); characteristics.listIterator(); characteristics.forEach( characteristic -> { List<BluetoothGattDescriptor> descriptor = characteristic.getDescriptors(); descriptor.forEach(singleDescriptor -> { Log.d("printGattTable", "singleDescriptor found: " + singleDescriptor.toString() ); }); }); }); } private void scanBondedBLEDevices() { localIPs_BT.clear(); localHostnames_BT.clear(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (!(mBluetoothAdapter == null) && mBluetoothAdapter.isEnabled()) { if (ActivityCompat.checkSelfPermission(bt_context, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { System.out.println("BLUETOOTH_CONNECT not granted?!?!?"); return; } Set<BluetoothDevice> bluetoothSet = mBluetoothAdapter.getBondedDevices(); if (bluetoothSet.size() > 0) { for (BluetoothDevice device : bluetoothSet) { localIPs_BT.add(device.getAddress()); localHostnames_BT.add(device.getName()); ParcelUuid[] deviceUuids = device.getUuids(); localUUIDs.add(deviceUuids); System.out.println("All paired devices checked?...."); } } if (!localIPs_BT.isEmpty() && localIPs_BT.size() > 0) { uuidIndex = 0; try { localIPs_BT.forEach((currentIpAddress) -> { if (mBluetoothAdapter.isEnabled()) { BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(currentIpAddress); BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); Log.d("onConnectionStateChange_BT", "onConnectionStateChange status: " + status + " for MACAddress: " + currentIpAddress); if ( status == BluetoothGatt.GATT_SUCCESS ) { if (ActivityCompat.checkSelfPermission(bt_context, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { return; } if (newState == BluetoothProfile.STATE_CONNECTED) { Log.d("BluetoothGattCallback", "Successfully connected to $deviceAddress"); gatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d("BluetoothGattCallback", "Successfully disconnected from $deviceAddress"); gatt.close(); } } else if (newState == BluetoothGatt.STATE_DISCONNECTED) { Log.d("testBTconnection", "Disconnected from GATT server" + " for MACAddress: " + currentIpAddress); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); Log.e("onServicesDiscovered BT", "onServicesDiscovered status: " + status + " for MACAddress: " + currentIpAddress); if (status == BluetoothGatt.GATT_SUCCESS) { services = gatt.getServices(); printGattTable(); } else { Log.e("testBTconnection", "Service discovery failed with status: " + status + " for MACAddress: " + currentIpAddress); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { } } }; mBluetoothGatt = device.connectGatt(bt_context, false, gattCallback, BluetoothDevice.TRANSPORT_LE); } }); } catch(Exception e) { e.printStackTrace(); } } } }
同时在Manifest中添加了以下权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" android:required="true" />
通过该实现,我连接Garmin Forerunner设备进行测试时,调试可查看设备的services、characteristics列表,以及与各功能关联的UUID列表。
现提出疑问:
- 若要通过蓝牙与自定义设备的Web服务器通信,是否需要选用特定的Bluetooth SoC(例如可配置自定义UUID以实现HTTP连接的BluetoothGattService的型号)?
- 若需使用此类支持自定义UUID的Bluetooth SoC,如何通过BluetoothGattService发送HTTP object requests?
解答
关于Bluetooth SoC的选择
不需要限定特定型号的Bluetooth SoC,只要是支持**BLE(低功耗蓝牙)**的SoC都可以实现需求。几乎所有主流蓝牙SoC(比如Nordic nRF系列、TI CC系列、ESP32蓝牙模块等)都支持自定义GATT Service和Characteristic的UUID。
核心是你的自定义设备端需要实现一个GATT服务,这个服务要能接收蓝牙传来的HTTP请求数据,转发给内置的Web服务器,再把Web服务器的响应通过蓝牙Characteristic回传给Android端。
通过BluetoothGattService发送HTTP请求的步骤
自定义设备端配置
- 在设备的蓝牙固件中,创建一个自定义的GATT Service(使用你指定的UUID),并添加两个Characteristic:
- 一个用于发送HTTP请求(设置为可写属性,
PROPERTY_WRITE) - 一个用于接收HTTP响应(设置为可读+通知属性,
PROPERTY_READ | PROPERTY_NOTIFY)
- 一个用于发送HTTP请求(设置为可写属性,
- 固件要监听这个可写Characteristic的写入事件,当收到Android端发来的HTTP请求数据(将HTTP请求的完整内容转成字节数组),就转发给内置Web服务器。
- 待Web服务器返回响应后,把响应内容写入通知型Characteristic,主动推送给Android端。
- 在设备的蓝牙固件中,创建一个自定义的GATT Service(使用你指定的UUID),并添加两个Characteristic:
Android端实现
- 连接到自定义设备后,通过
gatt.getService(CUSTOM_SERVICE_UUID)获取你定义的GATT服务,再分别获取请求和响应对应的Characteristic。 - 发送HTTP请求:将HTTP请求(比如
GET /api/params HTTP/1.1\r\nHost: localhost\r\n\r\n)转成字节数组,调用characteristic.setValue(byteArray),再用gatt.writeCharacteristic(requestCharacteristic)发送。 - 接收HTTP响应:给响应Characteristic设置通知(调用
gatt.setCharacteristicNotification(responseCharacteristic, true)),并在BluetoothGattCallback的onCharacteristicChanged方法中处理收到的响应字节数组,转成字符串后解析HTTP响应内容。
- 连接到自定义设备后,通过
注意事项
- BLE的单包数据长度有限(一般MTU默认是23字节),如果HTTP请求/响应内容较长,需要实现分片传输,在两端处理数据的拼接。
- 确保自定义设备的蓝牙固件正确处理GATT的读写和通知逻辑,避免数据丢失。
内容的提问来源于stack exchange,提问作者Luigino
相关产品推荐
相关产品推荐

