You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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列表。

现提出疑问:

  1. 若要通过蓝牙与自定义设备的Web服务器通信,是否需要选用特定的Bluetooth SoC(例如可配置自定义UUID以实现HTTP连接的BluetoothGattService的型号)?
  2. 若需使用此类支持自定义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请求的步骤

  1. 自定义设备端配置

    • 在设备的蓝牙固件中,创建一个自定义的GATT Service(使用你指定的UUID),并添加两个Characteristic:
      • 一个用于发送HTTP请求(设置为可写属性,PROPERTY_WRITE)
      • 一个用于接收HTTP响应(设置为可读+通知属性,PROPERTY_READ | PROPERTY_NOTIFY)
    • 固件要监听这个可写Characteristic的写入事件,当收到Android端发来的HTTP请求数据(将HTTP请求的完整内容转成字节数组),就转发给内置Web服务器。
    • 待Web服务器返回响应后,把响应内容写入通知型Characteristic,主动推送给Android端。
  2. 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响应内容。
  3. 注意事项

    • BLE的单包数据长度有限(一般MTU默认是23字节),如果HTTP请求/响应内容较长,需要实现分片传输,在两端处理数据的拼接。
    • 确保自定义设备的蓝牙固件正确处理GATT的读写和通知逻辑,避免数据丢失。

内容的提问来源于stack exchange,提问作者Luigino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 22:47:33