Android - BLE 连接参数和在 SQLite 数据库中存储 BLE 传感器数据
Android - BLE connection parameter and Storing BLE sensor data in SQLite Database
我正在开发一个 Android 应用程序,它以大约每秒 8000 字节的速率从 BLE 传感器接收数据。
我的应用程序中的连接逻辑基于 Google 的 BluetoothLeGatt 示例。有用。我没有更改任何内容,也没有明确设置任何连接参数,例如连接事件之间的间隔(我认为 Android 4.4 API 不支持)。
我正在测试两个 Android phones,都使用 Android 版本 4.4.2 并使用 TI BLE 嗅探器来监控 BLE 流量。
一个 phone 协商一个 7.5 毫秒的间隔,并在每个连接事件中交换三个 20 字节的数据包。另一个 phone 在连接事件之间协商 48.75 毫秒,每个事件交换 19 个 20 字节的数据包(每秒有效数据传输速率大致相同)。
我的问题是我试图记录来自 BLE 服务 activity 的数据,因为它进入 SQLite 数据库。日志记录适用于间隔为 7.5 毫秒的 phone。然而,
该应用程序以 48.75 毫秒的间隔锁定 phone。 (一般来说,phone 的连接不太稳定)。我认为那是因为它正在处理 19 个数据包,这些数据包是相互叠加的。
我的问题:
1. 无论如何我可以让两个 phones(以及任何未来的设备)都使用 7.5ms 间隔,因为这似乎效果更好?有没有办法控制 参数?
- 有没有比直接从 BLE 服务记录数据更好的方法 activity? These SQLite Android Developer pages 建议使用 ASync 任务,但这似乎不合适,因为数据不会进入 UI 线程。
我的代码片段:
这是我的连接代码directly from the BluetoothLeGatt sample
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param address The device address of the destination device.
*
* @return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}`
我的日志记录代码在 broadcastUpdate 函数中:
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
StringBuilder stringBuilder = new StringBuilder();
StringBuilder descStringBuilder = new StringBuilder();
byte[] newData = characteristic.getValue();
String dataString;
if (newData != null && newData.length > 0) {
if (UUID_SENSOR_FFF4.equals(characteristic.getUuid())) {
totalDataBytes += newData.length;
//
estimatedTime = System.currentTimeMillis();
Date timeDiff = new Date(estimatedTime - startTime - 19 * 3600000);
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss.SSS");
descStringBuilder.append("CHAR_FFF4\n");
descStringBuilder.append("Total Data: " + totalDataBytes + " Bytes\n");
descStringBuilder.append("Elapsed Time: " + timeFormat.format(timeDiff) + "\n");
for (int i = 0; i < newData.length; i++){
byte[] tempArray = { newData[i+1], newData[i] };
ByteBuffer wrapper = ByteBuffer.wrap(tempArray);
short tempShort = wrapper.getShort();
i++;
stringBuilder.append( tempShort );
stringBuilder.append( ", ");
}
dataString = stringBuilder.toString();
values.put(NbmcContract.NmbcDeviceData.COLUMN_TIMESTAMP, estimatedTime );
values.put(NbmcContract.NmbcDeviceData.COLUMN_DATA_STRING, dataString);
long newRowId = db.insert(NbmcContract.NmbcDeviceData.TABLE_NAME, null, values);
descStringBuilder.append("Row ID: " + newRowId + "\n");
} else {
descStringBuilder.append(getCharacteristicString(characteristic) + "\nDATA: ");
// We expect these characteristics to return ASCII strings
if ( DEVICE_NAME_CHAR.equals(characteristic.getUuid()) ||
MODEL_NUM_CHAR.equals(characteristic.getUuid()) ||
SERIAL_NUM_CHAR.equals(characteristic.getUuid()) ||
FIRMWARE_REV_CHAR.equals(characteristic.getUuid()) ||
HARDWARE_REV_CHAR.equals(characteristic.getUuid()) ||
FIRMWARE_REV_CHAR.equals(characteristic.getUuid()) ||
SOFTWARE_REV_CHAR.equals(characteristic.getUuid()) ||
MANUF_NAME_STRING_CHAR.equals(characteristic.getUuid()))
{
for (byte byteChar : newData) {
stringBuilder.append(String.format("%c", byteChar));
}
}
else {
for (byte byteChar : newData) {
stringBuilder.append(String.format("%02X", byteChar));
}
}
dataString = stringBuilder.toString();
}
String descString = descStringBuilder.toString();
intent.putExtra("DESC_STRING", descString);
UUID uuid = characteristic.getUuid();
String uuidString = uuid.toString();
intent.putExtra("CHAR_UUID", uuidString);
intent.putExtra("EXTRA_DATA", dataString);
}
sendBroadcast(intent);
}
1) API lvl21+ 你可以试试 bluetoothGatt.requestConnectionPriority(int connectionPriority)
但这就是你所能做的。
2)
Android 上的 BTLE 堆栈不是最好的,我建议在不进行任何处理的情况下获取数据,并在收到所有数据后进行处理。 请注意 gatt 回调发生在一个随机线程上,该线程不是您的 ui 线程。两个调用可以来自两个不同的线程,如果一个写入数据库而另一个写入并开始写入,那么你会变得一团糟。
我的建议:
在每个接收事件中,您 复制 byte[]
数组(因为 Android 可能会为将来的数据重用给定的数组)并将其发送到主线程 Handler
。在主线程上,你收集列表或集合中的数组,一旦收集到一定数量,你就启动一个新线程,给它列表,让它将数据输入数据库,同时主线程为新的创建一个新列表数据.
我正在开发一个 Android 应用程序,它以大约每秒 8000 字节的速率从 BLE 传感器接收数据。
我的应用程序中的连接逻辑基于 Google 的 BluetoothLeGatt 示例。有用。我没有更改任何内容,也没有明确设置任何连接参数,例如连接事件之间的间隔(我认为 Android 4.4 API 不支持)。
我正在测试两个 Android phones,都使用 Android 版本 4.4.2 并使用 TI BLE 嗅探器来监控 BLE 流量。
一个 phone 协商一个 7.5 毫秒的间隔,并在每个连接事件中交换三个 20 字节的数据包。另一个 phone 在连接事件之间协商 48.75 毫秒,每个事件交换 19 个 20 字节的数据包(每秒有效数据传输速率大致相同)。
我的问题是我试图记录来自 BLE 服务 activity 的数据,因为它进入 SQLite 数据库。日志记录适用于间隔为 7.5 毫秒的 phone。然而, 该应用程序以 48.75 毫秒的间隔锁定 phone。 (一般来说,phone 的连接不太稳定)。我认为那是因为它正在处理 19 个数据包,这些数据包是相互叠加的。
我的问题:
1. 无论如何我可以让两个 phones(以及任何未来的设备)都使用 7.5ms 间隔,因为这似乎效果更好?有没有办法控制
- 有没有比直接从 BLE 服务记录数据更好的方法 activity? These SQLite Android Developer pages 建议使用 ASync 任务,但这似乎不合适,因为数据不会进入 UI 线程。
我的代码片段: 这是我的连接代码directly from the BluetoothLeGatt sample
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param address The device address of the destination device.
*
* @return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}`
我的日志记录代码在 broadcastUpdate 函数中:
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
StringBuilder stringBuilder = new StringBuilder();
StringBuilder descStringBuilder = new StringBuilder();
byte[] newData = characteristic.getValue();
String dataString;
if (newData != null && newData.length > 0) {
if (UUID_SENSOR_FFF4.equals(characteristic.getUuid())) {
totalDataBytes += newData.length;
//
estimatedTime = System.currentTimeMillis();
Date timeDiff = new Date(estimatedTime - startTime - 19 * 3600000);
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss.SSS");
descStringBuilder.append("CHAR_FFF4\n");
descStringBuilder.append("Total Data: " + totalDataBytes + " Bytes\n");
descStringBuilder.append("Elapsed Time: " + timeFormat.format(timeDiff) + "\n");
for (int i = 0; i < newData.length; i++){
byte[] tempArray = { newData[i+1], newData[i] };
ByteBuffer wrapper = ByteBuffer.wrap(tempArray);
short tempShort = wrapper.getShort();
i++;
stringBuilder.append( tempShort );
stringBuilder.append( ", ");
}
dataString = stringBuilder.toString();
values.put(NbmcContract.NmbcDeviceData.COLUMN_TIMESTAMP, estimatedTime );
values.put(NbmcContract.NmbcDeviceData.COLUMN_DATA_STRING, dataString);
long newRowId = db.insert(NbmcContract.NmbcDeviceData.TABLE_NAME, null, values);
descStringBuilder.append("Row ID: " + newRowId + "\n");
} else {
descStringBuilder.append(getCharacteristicString(characteristic) + "\nDATA: ");
// We expect these characteristics to return ASCII strings
if ( DEVICE_NAME_CHAR.equals(characteristic.getUuid()) ||
MODEL_NUM_CHAR.equals(characteristic.getUuid()) ||
SERIAL_NUM_CHAR.equals(characteristic.getUuid()) ||
FIRMWARE_REV_CHAR.equals(characteristic.getUuid()) ||
HARDWARE_REV_CHAR.equals(characteristic.getUuid()) ||
FIRMWARE_REV_CHAR.equals(characteristic.getUuid()) ||
SOFTWARE_REV_CHAR.equals(characteristic.getUuid()) ||
MANUF_NAME_STRING_CHAR.equals(characteristic.getUuid()))
{
for (byte byteChar : newData) {
stringBuilder.append(String.format("%c", byteChar));
}
}
else {
for (byte byteChar : newData) {
stringBuilder.append(String.format("%02X", byteChar));
}
}
dataString = stringBuilder.toString();
}
String descString = descStringBuilder.toString();
intent.putExtra("DESC_STRING", descString);
UUID uuid = characteristic.getUuid();
String uuidString = uuid.toString();
intent.putExtra("CHAR_UUID", uuidString);
intent.putExtra("EXTRA_DATA", dataString);
}
sendBroadcast(intent);
}
1) API lvl21+ 你可以试试 bluetoothGatt.requestConnectionPriority(int connectionPriority)
但这就是你所能做的。
2) Android 上的 BTLE 堆栈不是最好的,我建议在不进行任何处理的情况下获取数据,并在收到所有数据后进行处理。 请注意 gatt 回调发生在一个随机线程上,该线程不是您的 ui 线程。两个调用可以来自两个不同的线程,如果一个写入数据库而另一个写入并开始写入,那么你会变得一团糟。
我的建议:
在每个接收事件中,您 复制 byte[]
数组(因为 Android 可能会为将来的数据重用给定的数组)并将其发送到主线程 Handler
。在主线程上,你收集列表或集合中的数组,一旦收集到一定数量,你就启动一个新线程,给它列表,让它将数据输入数据库,同时主线程为新的创建一个新列表数据.