Android: Sending data >20 bytes by BLE

JavaAndroidBluetooth Lowenergy

Java Problem Overview


I am able to send data upto 20 bytes by connecting to an external BLE device. How do I send data greater than 20 bytes. I have read that we have to either fragment the data or split characteristic to required parts. If I assume my data is 32 bytes, could you tell me changes I need to make in my code to get this working? Following are the required snippets from my code:

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);
}

This is the code I used for sending the data. The "send" function is used in the following onclick event.

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());
        }
    });

When the String text is greater than 20 bytes then only the first 20 bytes are received. How to rectify this?

To test sending multiple characteristics I tried this:

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());
    }
});

But I only received "Test3" i.e. the last characteristic. What mistake did I commit? I am new to BLE so please ignore any naiveness

Edit:

After accepting answer for anyone who views this later.

There are two ways to accomplish this.

  1. Split up your data and write in a loop as the selected answer does.
  2. Split up your data and write using callback i.e. onCharacterisitcWrite(). This will save you from errors if there were any during writing.

But most important between the writes use a Thread.sleep(200) if you are only writing and not waiting for a response from the firmware. This will ensure that all of your data reaches. Without the sleep I was always getting the last packet. If you notice the accepted answer he has also used sleep in between.

Java Solutions


Solution 1 - Java

BLE allows you transfer maximum of 20 bytes.

If you want to send more than 20 bytes, you should define array byte[] to contain how many packets you want.

Following example worked fine if you want to send less than 160 characters (160 bytes).

p/s : Let edit following as you want. Do not follow me exactly.

Actually, when we are using BLE, mobile side and firmware side need to set up the Key (ex. 0x03 ...) to define the connection gate among both sides.

The idea is :

  • When we continue to transfer packets, not is the last one. The gate is byte[1] = 0x01.

  • If we send the last one, The gate is byte[1] = 0x00.

The data contruction (20 bytes):

1 - Byte 1 - Define the Gate ID : ex. Message gate ID byte[0] = 0x03.

2 - Byte 2 - Define the recognization : Is the last packet 0x00 or continue sending packets 0x01.

3 - Byte 3 (Should be 18 bytes after eliminating Byte 1 & Byte 2) - Attach the message content in here.

please understand my logic before reading the code below.

Below is an example of sending a Message with many packets, each packet is an array of size 20 bytes.

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 you do not have enough characters to send continue packet,
				 * This is the last packet that 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 you have enough characters to send continue packet,
				 * This is the continue packet that 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;
			}
		}
	}

Solution 2 - Java

On Lollipop you can send up to 512 bytes. You need to use BluetoothGatt.requestMtu() with a value of 512. Also, as @Devunwired mentioned you need to wait until any previous operation is complete before calling this.

Solution 3 - Java

There are a lot of misleads here.

BLE is capable of sending much more than 20 bytes, and it can be done easily in android.

What you need to change is the link MTU that is set to 23 by default(only 20 of them can be used to set a value). Android provides fragmentation mechanism if the given packet to send is larger than the current link MTU(this is the purpose of the offset parameter in the onCharacteristicRead(...) API).

So you can make the MTU bigger, as a request from the central using: requestMtu(...) API. The latter will cause a callback call onMtuChanged at the peripheral side which will inform him of the new MTU. After this action is done, you can send bigger packets without issuing the Android fragmentation mechanism.

The alternatives are to build yourself your own fragmetation mechanism and not to send packets that are bigger then the MTU. Or rely on the Android mechanism and work with it using the 'offset' parameter.

Solution 4 - Java

You are correct that the BLE specification doesn't allow write operations to exceed 20 bytes. If you can't subdivide your payload over multiple characteristics (which is logically going to be easier to maintain), then your chunking mechanism is the other approach.

However, realize that the BLE stack hates when you try to queue up multiple operations. Each read/write is asynchronous, which the result coming via the onCharacteristicRead() or onCharacteristicWrite() callback on the BluetoothGattCallback instance. The code you've written attempts to send three characteristic write operations on top of each other, without waiting for the callback in between. Your code will need to follow a path more like:

send(Test1)
  -> Wait for onCharacteristicWrite()
  -> send(Test2)
    -> Wait for onCharacteristicWrite()
    -> send(Test3)
      -> Wait for onCharacteristicWrite()
Done!

Solution 5 - Java

You need to request a MTU update. This is the maximum transmission unit. As it is now, BLE accepts up to 512 bytes in a single packet. However, without requesting this MTU update, your device will not send a packet over 23 bytes (currently).


Using your BluetoothGatt object call requestMtu()

Here is a link to the developer's page

enter image description here


BluetoothGattCallback will receive the onMtuChanged() event as shown below. Upon a successful MTU update, you can send the data as one packet. Here is a link to this developer page.

enter image description here


I typically call requestMtu() after connecting to the characteristic that I wish to write to. Good Luck.

Solution 6 - Java

You can actually trigger a BLE Long write if the device on the other end supports it.

You can do this by setting the write type to be BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT

In this case you can send more than 20 bytes.

Solution 7 - Java

If you want to send large sets of data over BLE, then your best bet is to use two characteristics, one to send the bulk of your data and the other to send the last segment. This way you don't need to set the response to WRITE_NO_RESPONSE and use the callback to send the next segment all the way until you get to the last segment, at which point you will write that to the second characteristic which will let the device know that you are done writing the data and that it can concatenate all the data together to form one large data packet.

Solution 8 - Java

Another solution with Queue and that allow unlimited messages of any size (manage the protocol by yourself to put message delimiters). No Sleep, no additional delays:

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();
}

Solution 9 - Java

I have a very similar answer to Huy Tower, but I have taken the time to write some nicer code to do it. This doesn't use an arbitrary delay, it instead uses the onCharacteristicWrite callback.I would reference his explanation as an understandign of how this works.

Firstly, two class scoped variables are declared:

private byte[][] byteMessageToWrite;
private int currentMessageProgress = 0;

Then three functions are declared as follows:

// 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;
    }

Finally, in the OnCharacteristicWrite function, I have the following:

      @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++;
        }
    }

I will also note that instead of using 0x00 and 0x01 as Huy suggested, I have chosen to use 0x01 and 0x02. The reason for this is that I found that my bluetooth device would not successfully read any packets with a 0x00. I cannot explain this.

Solution 10 - Java

This is the example of implementation using chunk method, but without using Thread.sleep , i found it is better and efficient for my application to send more than 20 bit data.

The packets will be send afteronCharacteristicWrite() triggered. i just found out this method will be triggered automatically after peripheral device (BluetoothGattServer) sends a sendResponse() method.

firstly we have to transform the packet data into chunk with this function:

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;
    }

after our data ready, so i put my iteration on this function:

@Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        if(packetInteration<packetSize){
        characteristicData.setValue(packets[packetInteration]);
        mGatt.writeCharacteristic(characteristicData);
            packetInteration++;
        }
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAnkit AggarwalView Question on Stackoverflow
Solution 1 - JavaHuy TowerView Answer on Stackoverflow
Solution 2 - JavaThomasWView Answer on Stackoverflow
Solution 3 - JavaSielarView Answer on Stackoverflow
Solution 4 - JavadevunwiredView Answer on Stackoverflow
Solution 5 - Javauser5803705View Answer on Stackoverflow
Solution 6 - JavaZac SiegelView Answer on Stackoverflow
Solution 7 - JavaZombView Answer on Stackoverflow
Solution 8 - JavaMartinLorenView Answer on Stackoverflow
Solution 9 - Javauser1814893View Answer on Stackoverflow
Solution 10 - JavaDoniView Answer on Stackoverflow