我有一个蓝牙服务器,它使用bleno并将可用的Wifi网络列表返回给客户端。readCharacteristic的代码基本上如下所示:
class ReadCharacteristic extends bleno.Characteristic {
constructor(uuid, name, action) {
super({
uuid: uuid,
properties: ["read"],
value: null,
descriptors: [
new bleno.Descriptor({
uuid: "2901",
value: name
})
]
});
this.actionFunction = action;
}
onReadRequest(offset, callback) {
console.log("Offset: " + offset);
if(offset === 0) {
const result = this.actionFunction();
result.then(value => {
this.actionFunctionResult = value;
const data = new Buffer.from(value).slice(0,bleno.mtu);
console.log("onReadRequest: " + data.toString('utf-8'));
callback(this.RESULT_SUCCESS, data);
}, err => {
console.log("onReadRequest error: " + err);
callback(this.RESULT_UNLIKELY_ERROR);
}).catch( err => {
console.log("onReadRequest error: " + err);
callback(this.RESULT_UNLIKELY_ERROR);
});
}
else {
let data = new Buffer.from(this.actionFunctionResult);
if(offset > data.length) {
callback(this.RESULT_INVALID_OFFSET, null);
}
data = data.slice(offset+1, offset+bleno.mtu);
console.log(data.toString('utf-8'));
callback(this.RESULT_SUCCESS, data);
}
}
}(我试过data = data.slice(offset+1, offset+bleno.mtu);,像这样的data = data.slice(offset+1);)
客户端是一个Android应用程序,可以读取这一特性。
用于阅读的Android部分如下所示:
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.requestMtu(256);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
mFancyShowCaseView.show();
gatt.close();
scanForBluetoothDevices();
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "Can't set mtu to: " + mtu);
} else {
Log.i(TAG, "Connected to GATT server. MTU: " + mtu);
Log.i(TAG, "Attempting to start service discovery:" +
mWifiProvisioningService.discoverServices());
}
}
@Override
// New services discovered
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "ACTION_GATT_SERVICES_DISCOVERED");
BluetoothGattService wifiProvisioningService = gatt.getService(WIFI_PROVISIONING_SERVICE_UUID);
BluetoothGattCharacteristic currentConnectedWifiCharacteristic = wifiProvisioningService.getCharacteristic(WIFI_ID_UUID);
BluetoothGattCharacteristic availableWifiCharacteristic = wifiProvisioningService.getCharacteristic(WIFI_SCAN_UUID);
// Only read the first characteristic and add the 2nd one to a list as we have to wait
// for the read return before we read the 2nd one.
if (!gatt.readCharacteristic(currentConnectedWifiCharacteristic)) {
Log.e(TAG, "Error while reading current connected wifi name.");
}
readCharacteristics.add(availableWifiCharacteristic);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
UUID characteristicUUID = characteristic.getUuid();
if (WIFI_ID_UUID.equals(characteristicUUID)) {
Log.d(TAG, "HEUREKA we found the current wifi name: " + new String(characteristic.getValue()));
final String currentWifiName = new String(characteristic.getValue());
runOnUiThread(new Runnable() {
@Override
public void run() {
((TextView) findViewById(R.id.currentWifiTxt)).setText(currentWifiName);
findViewById(R.id.currentWifiTxtProgress).setVisibility(View.GONE);
}
});
} else if (WIFI_SCAN_UUID.equals(characteristicUUID)) {
Log.d(TAG, "HEUREKA we found the wifi list: " + new String(characteristic.getValue()));
List<String> wifiListArrayList = new ArrayList<>();
try {
JSONObject wifiListRoot = new JSONObject(characteristic.getStringValue(0));
JSONArray wifiListJson = wifiListRoot.getJSONArray("list");
for (int i = 0; i < wifiListJson.length(); i++) {
wifiListArrayList.add(wifiListJson.get(i).toString());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
return;
}
final String[] wifiList = new String[wifiListArrayList.size()];
wifiListArrayList.toArray(wifiList);
runOnUiThread(new Runnable() {
@Override
public void run() {
((ListView) findViewById(R.id.availableWifiList)).setAdapter(new ArrayAdapter<String>(mContext, R.layout.wifi_name_list_item, wifiList));
findViewById(R.id.currentWifiTxtProgress).setVisibility(View.GONE);
}
});
} else {
Log.i(TAG, "Unexpected Gatt vale: " + new String(characteristic.getValue()));
}
if (readCharacteristics.size() > 0) {
BluetoothGattCharacteristic readCharacteristic = readCharacteristics.get(0);
if (!gatt.readCharacteristic(readCharacteristic)) {
Log.e(TAG, "Error while writing descriptor for connected wifi");
}
readCharacteristics.remove(readCharacteristic);
}
}
}MTU被调整为256字节。在读取列表时,我在服务器上反映了这一点。调用本身工作正常并返回列表,但是如果列表包含更多的 600 字节,那么在Android上只有600个字节可用。我确信JS服务器会发送所有数据,但出于某种原因,Android客户端只接收或缓存600个字节,这似乎不正确。
我找到了这篇文章:Android BLE - Peripheral | onCharacteristicRead return wrong value or part of it (but repeated)
这个:Android BLE - How is large characteristic value read in chunks (using an offset)?
但都没能解决我的问题。我知道,在开始下一次读取之前,我需要等待一次读取返回,并且在继续读取数据之前,我需要等待MTU被写入。据我所知,这反映在你上面看到的来源中。我有点迷路了。
任何想法都有很强的说服力。
非常感谢
发布于 2018-10-25 19:34:15
对于任何一个看到这篇文章的人来说,也想知道为什么Android似乎只返回600个字节,而GATT的长期特性就像这个问题所问的一样,这都取决于Bluedroid (Android的蓝牙堆栈)是如何实现GATT客户端的,以及它是如何超出规范的。在我的例子中,我使用了一个基于ESP32 32的IoT设备作为关贸总协定的服务器和安卓(SDK 24)的关贸总协定客户端。
根据规范(蓝牙核心4.2;第3卷,F部分: 3.2.9),特征值的最大大小(从ATT属性值继承)为512字节。但是,由于某些原因,Bluedroid并不试图强制执行此要求,而是决定了最大大小为600;如果深入到Bluedroid源并找到设置为600 ( GATT_MAX_ATTR_LEN )的宏GATT_MAX_ATTR_LEN,就可以看到这一点。因为在我的例子(和您的例子中)中,我实现了read请求响应代码,所以我也没有看到对读取特性执行512字节的限制。
现在,重要的是要认识到Bluedroid读取特征的方式,以及它与MTU大小、读取的最大大小(应该是512,但是Bluedroid是600 )以及如何处理比最大大小更长的数据的关系。MTU大小是您可以使用的ATT级别上最大的数据包大小。因此,对于每次对BluetoothGatt.readCharacteristic的调用,您可能会向服务器发送一个或多个读取请求,这取决于Bluedroid是否认为特征大小超过了MTU大小。在较低的级别上,Bluedroid将首先发送ATT请求(0x0a),如果数据包长度为MTU字节,则它将跟踪ATT请求(0x0c),偏移量设置为MTU大小。它将继续发送ATT请求,直到ATT响应长度小于MTU字节,或者直到达到最大特征大小(即,600表示Bluedroid)。需要注意的是,如果MTU大小不是600字节以上数据的完美倍数,则剩余的字节将被丢弃(因为Bluedroid实际上从未期望读取600个字节,因为它认为GATT Server将对特征大小强制执行512字节限制)。因此,如果您的数据超过了600个字节限制(或512个安全限制),您应该会多次调用BluetoothGatt.readCharacteristic。下面是一个在Android端读取大量数据的简单示例(对不起,我没有使用bleno,因此无法给您修复该端的代码),它首先将数据的长度作为一个无符号32位整数发送,然后在数据大于600个字节时通过重复调用BluetoothGatt.readCharacteristic读取数据:
private int readLength;
private StringBuilder packet; // In my case, Im building a string out of the data
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.requestMtu(201); // NOTE: If you are going to read a long piece of data, its best to make this value a factor of 600 + 1, like 51, 61, 101, 151, etc due to the risk of data loss if the last packet contains more than 600 bytes of cumulative data
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
gatt.discoverServices();
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
// Kick off a read
BluetoothGattCharacteristic characteristic = gatt.getService(UUID.fromString(SERVICE_UUID)).getCharacteristic(UUID.fromString(CHAR_UUID));
readLength = 0;
gatt.readCharacteristic(characteristic);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (readLength == 0) {
readLength = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0);
packet = new StringBuilder();
gatt.readCharacteristic(characteristic);
} else {
byte[] data = charactertic.getValue();
packet.append(new String(data));
readLength -= data.length;
if (readLength == 0) {
// Got all data this time; you can now process the data however you want
} else {
gatt.readCharacteristic(characteristic);
}
}
}https://stackoverflow.com/questions/48741196
复制相似问题