Android: отправка данных>20 байт по BLE
Я могу отправить данные размером до 20 байтов, подключившись к внешнему устройству BLE. Как отправить данные размером более 20 байтов. Я прочитал, что мы должны либо фрагментировать данные, либо разбить характеристику на требуемые части. Если я предполагаю, что мои данные имеют размер 32 байта, не могли бы вы сказать, какие изменения мне нужно внести в мой код, чтобы это работало? Ниже приведены необходимые фрагменты из моего кода:
public boolean send(byte[] data) {
if (mBluetoothGatt == null || mBluetoothGattService == null) {
Log.w(TAG, "BluetoothGatt not initialized");
return false;
}
BluetoothGattCharacteristic characteristic =
mBluetoothGattService.getCharacteristic(UUID_SEND);
if (characteristic == null) {
Log.w(TAG, "Send characteristic not found");
return false;
}
characteristic.setValue(data);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
return mBluetoothGatt.writeCharacteristic(characteristic);
}
Это код, который я использовал для отправки данных. Функция "send" используется в следующем событии onclick.
sendValueButton = (Button) findViewById(R.id.sendValue);
sendValueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = dataEdit.getText().toString();
yableeService.send(text.getBytes());
}
});
Когда String text
больше 20 байтов, тогда принимаются только первые 20 байтов. Как это исправить?
Чтобы проверить отправку нескольких характеристик, я попробовал это:
sendValueButton = (Button) findViewById(R.id.sendValue);
sendValueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = "Test1";
yableeService.send(text.getBytes());
text = "Test2";
yableeService.send(text.getBytes());
text = "Test3";
yableeService.send(text.getBytes());
}
});
Но я получил только "Test3", т.е. последнюю характеристику. Какую ошибку я совершил? Я новичок в BLE, поэтому, пожалуйста, игнорируйте любую наивность
Редактировать:
После принятия ответа для всех, кто просматривает это позже.
Есть два способа сделать это.
1. Разделите ваши данные и напишите в цикле, как выбранный ответ.
2. Разделите ваши данные и напишите, используя обратный вызов, т.е. onCharacterisitcWrite()
, Это избавит вас от ошибок, если они были во время записи.
Но самое важное между записью использовать Thread.sleep(200)
если вы только пишете и не ждете ответа от прошивки. Это гарантирует, что все ваши данные достигнут. Без sleep
Я всегда получал последний пакет. Если вы заметили принятый ответ, он также использовал sleep
между.
9 ответов
BLE позволяет передавать максимум до 20 байт.
Если вы хотите отправить больше 20 байтов, вы должны определить массив байтов [], включающий количество пакетов, которое вы хотите.
Пример работал нормально, если вы хотите отправить < 160 символов (160 байт).
p / s: Позвольте редактированию следовать, как вы хотите. Не последовал за мной точно.
На самом деле, когда мы используем BLE, сторона мобильного устройства и прошивка должны установить ключ (например, 0x03 ...) для определения шлюза соединения между обеими сторонами.
Идея заключается в следующем:
Когда мы все еще продолжаем передавать пакеты, не последний. Ворота
byte[1] = 0x01
,Если мы отправим последний, Ворота
byte[1] = 0x00
,
Обработка данных (20 байтов):
1 - Byte 1
- определить Gate ID
Пример ID шлюза сообщения byte[0] = 0x03
,
2 - Byte 2
- определить recognization
: Последний пакет 0x00
или продолжить отправку пакетов 0x01
,
3 - Byte 3
(Должно быть 18 байт после минуса Byte 1
& Byte 2
) - Присоедините содержание сообщения здесь.
Пожалуйста, поймите мою логику, прежде чем читать код ниже.
Ниже приведен пример отправки сообщения со многими пакетами, каждый пакет: байт [20].
private void sendMessage(BluetoothGattCharacteristic characteristic, String CHARACTERS){
byte[] initial_packet = new byte[3];
/**
* Indicate byte
*/
initial_packet[0] = BLE.INITIAL_MESSAGE_PACKET;
if (Long.valueOf(
String.valueOf(CHARACTERS.length() + initial_packet.length))
> BLE.DEFAULT_BYTES_VIA_BLE) {
sendingContinuePacket(characteristic, initial_packet, CHARACTERS);
} else {
sendingLastPacket(characteristic, initial_packet, CHARACTERS);
}
}
private void sendingContinuePacket(BluetoothGattCharacteristic characteristic,
byte[] initial_packet, String CHARACTERS){
/**
* TODO If data length > Default data can sent via BLE : 20 bytes
*/
// Check the data length is large how many times with Default Data (BLE)
int times = Byte.valueOf(String.valueOf(
CHARACTERS.length() / BLE.DEFAULT_BYTES_IN_CONTINUE_PACKET));
Log.i(TAG, "CHARACTERS.length() " + CHARACTERS.length());
Log.i(TAG, "times " + times);
// TODO
// 100 : Success
// 101 : Error
byte[] sending_continue_hex = new byte[BLE.DEFAULT_BYTES_IN_CONTINUE_PACKET];
for (int time = 0; time <= times; time++) {
/**
* Wait second before sending continue packet
*/
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time == times) {
Log.i(TAG, "LAST PACKET ");
/**
* If can not have enough characters to send continue packet,
* This is the last packet will be sent to the band
*/
/**
* Packet length byte :
*/
/**
* Length of last packet
*/
int character_length = CHARACTERS.length()
- BLE.DEFAULT_BYTES_IN_CONTINUE_PACKET*times;
initial_packet[1] = Byte.valueOf(String.valueOf(character_length
+ BLE.INITIAL_MESSAGE_PACKET_LENGTH));
initial_packet[2] = BLE.SENDING_LAST_PACKET;
Log.i(TAG, "character_length " + character_length);
/**
* Message
*/
// Hex file
byte[] sending_last_hex = new byte[character_length];
// Hex file : Get next bytes
for (int i = 0; i < sending_last_hex.length; i++) {
sending_last_hex[i] =
CHARACTERS.getBytes()[sending_continue_hex.length*time + i];
}
// Merge byte[]
byte[] last_packet =
new byte[character_length + BLE.INITIAL_MESSAGE_PACKET_LENGTH];
System.arraycopy(initial_packet, 0, last_packet,
0, initial_packet.length);
System.arraycopy(sending_last_hex, 0, last_packet,
initial_packet.length, sending_last_hex.length);
// Set value for characteristic
characteristic.setValue(last_packet);
} else {
Log.i(TAG, "CONTINUE PACKET ");
/**
* If have enough characters to send continue packet,
* This is the continue packet will be sent to the band
*/
/**
* Packet length byte
*/
int character_length = sending_continue_hex.length;
/**
* TODO Default Length : 20 Bytes
*/
initial_packet[1] = Byte.valueOf(String.valueOf(
character_length + BLE.INITIAL_MESSAGE_PACKET_LENGTH));
/**
* If sent data length > 20 bytes (Default : BLE allow send 20 bytes one time)
* -> set 01 : continue sending next packet
* else or if after sent until data length < 20 bytes
* -> set 00 : last packet
*/
initial_packet[2] = BLE.SENDING_CONTINUE_PACKET;
/**
* Message
*/
// Hex file : Get first 17 bytes
for (int i = 0; i < sending_continue_hex.length; i++) {
Log.i(TAG, "Send stt : "
+ (sending_continue_hex.length*time + i));
// Get next bytes
sending_continue_hex[i] =
CHARACTERS.getBytes()[sending_continue_hex.length*time + i];
}
// Merge byte[]
byte[] sending_continue_packet =
new byte[character_length + BLE.INITIAL_MESSAGE_PACKET_LENGTH];
System.arraycopy(initial_packet, 0, sending_continue_packet,
0, initial_packet.length);
System.arraycopy(sending_continue_hex, 0, sending_continue_packet,
initial_packet.length, sending_continue_hex.length);
// Set value for characteristic
characteristic.setValue(sending_continue_packet);
}
// Write characteristic via BLE
mBluetoothGatt.writeCharacteristic(characteristic);
}
}
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic,
String data) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return false;
}
if (ActivityBLEController.IS_FIRST_TIME) {
/**
* In the first time,
* should send the Title
*/
byte[] merge_title = sendTitle(data);
// Set value for characteristic
characteristic.setValue(merge_title);
// Write characteristic via BLE
mBluetoothGatt.writeCharacteristic(characteristic);
// Reset
ActivityBLEController.IS_FIRST_TIME = false;
return true;
} else {
/**
* In the second time,
* should send the Message
*/
if (data.length() <= BLE.LIMIT_CHARACTERS) {
sendMessage(characteristic, data);
// Reset
ActivityBLEController.IS_FIRST_TIME = true;
return true;
} else {
// Typed character
typed_character = data.length();
return false;
}
}
}
На Lollipop вы можете отправить до 512 байт. Вам нужно использовать BluetoothGatt.requestMtu()
со значением 512. Также, как упомянул @Devunwired, вам нужно дождаться завершения любой предыдущей операции, прежде чем вызывать это.
Здесь много заблуждений.
BLE способен отправлять намного больше 20 байтов, и это легко сделать в Android.
Что вам нужно изменить, так это MTU канала связи, для которого по умолчанию установлено значение 23 (для установки значения можно использовать только 20 из них).
Android обеспечивает механизм фрагментации, если данный пакет для отправки больше, чем текущий MTU канала (это является целью параметра смещения в onCharacteristicRead(...)
API).
Таким образом, вы можете увеличить MTU, как запрос от центра, используя: requestMtu(...)
API. Последний вызовет обратный вызов onMtuChanged
на периферийной стороне, которая сообщит ему о новом MTU. После выполнения этого действия вы можете отправлять большие пакеты без использования механизма фрагментации Android.
Альтернативой является создание собственного механизма фрагментации, а не отправка пакетов, размер которых превышает MTU. Или положитесь на механизм Android и работайте с ним, используя параметр offset.
Вам необходимо запросить обновление MTU. Это максимальная единица передачи. Как и сейчас, BLE принимает до 512 байтов в одном пакете. Однако без запроса этого обновления MTU ваше устройство не будет отправлять пакет длиной более 23 байтов (в настоящее время).
Использование вашего объекта BluetoothGatt вызывает requestMtu()
Вот ссылка на страницу разработчика
BluetoothGattCallback получит событие onMtuChanged(), как показано ниже. После успешного обновления MTU вы можете отправить данные одним пакетом. Вот ссылка на эту страницу разработчика.
Обычно я вызываю requestMtu() после подключения к характеристике, в которую я хочу написать. Удачи.
Вы правы, что спецификация BLE не позволяет операциям записи превышать 20 байтов. Если вы не можете разделить свою полезную нагрузку по нескольким характеристикам (что, по логике, будет проще поддерживать), то ваш механизм разделения будет другим подходом.
Однако следует понимать, что стек BLE ненавидит, когда вы пытаетесь поставить в очередь несколько операций. Каждое чтение / запись является асинхронным, результат которого поступает через onCharacteristicRead()
или же onCharacteristicWrite()
обратный звонок на BluetoothGattCallback
пример. Код, который вы написали, пытается отправить три характерные операции записи друг на друга, не дожидаясь обратного вызова между ними. Ваш код должен следовать по пути, например:
send(Test1)
-> Wait for onCharacteristicWrite()
-> send(Test2)
-> Wait for onCharacteristicWrite()
-> send(Test3)
-> Wait for onCharacteristicWrite()
Done!
Вы можете фактически запустить запись BLE Long, если устройство на другом конце поддерживает это.
Вы можете сделать это, установив тип записи BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
В этом случае вы можете отправить более 20 байтов.
Еще одно решение с очередью, которое позволяет неограниченное количество сообщений любого размера (управляйте протоколом самостоятельно, устанавливая разделители сообщений). Без сна, без дополнительных задержек:
private volatile boolean isWriting;
private Queue<String> sendQueue; //To be inited with sendQueue = new ConcurrentLinkedQueue<String>();
public int send(String data) {
while (data.length()>20) {
sendQueue.add(data.substring(0,20));
data=data.substring(20);
}
sendQueue.add(data);
if (!isWriting) _send();
return ST_OK; //0
}
private boolean _send() {
if (sendQueue.isEmpty()) {
Log.d("TAG", "_send(): EMPTY QUEUE");
return false;
}
Log.d(TAG, "_send(): Sending: "+sendQueue.peek());
tx.setValue(sendQueue.poll().getBytes(Charset.forName("UTF-8")));
isWriting = true; // Set the write in progress flag
mGatt.writeCharacteristic(tx);
return true;
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("TAG","onCharacteristicWrite(): Successful");
}
isWriting = false;
_send();
}
Если вы хотите отправлять большие наборы данных через BLE, то лучше всего использовать две характеристики: одну для отправки большого объема ваших данных, а другую для отправки последнего сегмента. Таким образом, вам не нужно устанавливать ответ на WRITE_NO_RESPONSE и использовать обратный вызов для отправки следующего сегмента до тех пор, пока вы не доберетесь до последнего сегмента, после чего вы запишете это во второй признак, который сообщит устройству что вы закончили запись данных и что он может объединить все данные вместе, чтобы сформировать один большой пакет данных.
У меня есть очень похожий ответ на Huy Tower, но я нашел время, чтобы написать более приятный код для этого. Здесь не используется произвольная задержка, вместо этого используется обратный вызов onCharacteristicWrite. Я бы сослался на его объяснение как на понимание того, как это работает.
Во-первых, объявляются две переменные в области видимости класса:
private byte[][] byteMessageToWrite;
private int currentMessageProgress = 0;
Затем объявляются три функции следующим образом:
// Send a message
public void sendMessage(String strMessage) {
if (characteristic == null || strMessage == null || strMessage.isEmpty()) {
// Do nothing if there is no device or message to send.
return;
}
//Note that byte arrays after the first are written in the onCharacteristicWrite Callback
byteMessageToWrite = getMessageByteArray(strMessage);
writeBytes(byteMessageToWrite[0]);
currentMessageProgress = 1;
}
//writes an array of bytes
//note the max BLE message limit
private void writeBytes(byte[] bytesToWrite) {
characteristic.setValue(bytesToWrite);
}
// Note: BLE Only allows 20 bytes to be written to a characteristic at once. Have to write
// multiple times if sending larger data. This function breaks a string up to do that.
// The first byte is reserved as a key
// Note, the second byte in every 20byte section is either a 1 or a 2. A 2 indicates that it is
// The last message in the set
private byte[][] getMessageByteArray(String message) {
byte[] initBytes = message.getBytes(Charset.forName("UTF-8"));
int currentIndex = 0;
int msgLength = initBytes.length;
int numMessages = (int) (Math.ceil((double) (Math.ceil((double) msgLength) / (double) (BLE_BYTE_LIMIT-2))));
byte[][] retMessage = new byte[numMessages][20];
for (int i = 0; i < numMessages; i++) {
//set key
retMessage[i][0] = 0x03;
//set second byte (indicator of termination)
if (i == numMessages - 1) {//final message, set byte 1 to 2
retMessage[i][1] = 0x02;
} else {//not final message
retMessage[i][1] = 0x01;
}
//set other bytes
for (int ii = 2; ii < BLE_BYTE_LIMIT; ii++) {// fill in the data
int index = (i * (BLE_BYTE_LIMIT - 2)) + ii - 2;
if(index>=msgLength){
// Have reached the end of the message, don't fill any other bytes
return retMessage;
}
retMessage[i][ii] = initBytes[index];
}
}
return retMessage;
}
Наконец, в функции OnCharacteristicWrite у меня есть следующее:
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (currentMessageProgress < byteMessageToWrite.length) {
//there are more bytes to write
writeBytes(byteMessageToWrite[currentMessageProgress]);
currentMessageProgress++;
}
}
Я также отмечу, что вместо использования 0x00 и 0x01, как предлагал Хай, я решил использовать 0x01 и 0x02. Причина этого в том, что я обнаружил, что мое устройство Bluetooth не может успешно читать какие-либо пакеты с 0x00. Я не могу этого объяснить.
Это пример реализации с использованием метода chunk, но без использования Thread.sleep я обнаружил, что для моего приложения лучше и эффективнее отправлять более 20-битные данные.
Пакеты будут отправлены послеonCharacteristicWrite()
срабатывает. я только что узнал, что этот метод будет запущен автоматически после периферийного устройства (BluetoothGattServer)
отправляет sendResponse()
метод.
во-первых, мы должны преобразовать пакетные данные в кусок с помощью этой функции:
public void sendData(byte [] data){
int chunksize = 20; //20 byte chunk
packetSize = (int) Math.ceil( data.length / (double)chunksize); //make this variable public so we can access it on the other function
//this is use as header, so peripheral device know ho much packet will be received.
characteristicData.setValue(packetSize.toString().getBytes());
mGatt.writeCharacteristic(characteristicData);
mGatt.executeReliableWrite();
packets = new byte[packetSize][chunksize];
packetInteration =0;
Integer start = 0;
for(int i = 0; i < packets.length; i++) {
int end = start+chunksize;
if(end>data.length){end = data.length;}
packets[i] = Arrays.copyOfRange(data,start, end);
start += chunksize;
}
после того, как наши данные готовы, я поместил свою итерацию в эту функцию:
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if(packetInteration<packetSize){
characteristicData.setValue(packets[packetInteration]);
mGatt.writeCharacteristic(characteristicData);
packetInteration++;
}
}