Java Android:通过 BLE 发送 >20 字节的数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24135682/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 10:24:10  来源:igfitidea点击:

Android: Sending data >20 bytes by BLE

javaandroidbluetooth-lowenergy

提问by Ankit Aggarwal

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:

通过连接到外部 BLE 设备,我能够发送最多 20 个字节的数据。如何发送大于 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);
}

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

这是我用于发送数据的代码。"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());
        }
    });

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

String text大于 20 个字节时,则仅接收前 20 个字节。如何纠正这个问题?

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

但我只收到“Test3”即最后一个特征。我犯了什么错误?我是 BLE 的新手,所以请忽略任何幼稚

Edit:

编辑:

After accepting answerfor anyone who views this later.

在接受稍后查看此内容的任何人的答案后。

There are twoways 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.

两种方法可以实现这一点。1. 拆分您的数据并按照所选答案循环书写。2.拆分您的数据并使用回调即写入onCharacterisitcWrite()。如果在编写过程中有任何错误,这将使您免于出错。

But most importantbetween 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 sleepI was always getting the last packet. If you notice the accepted answer he has also used sleepin between.

最重要的是,Thread.sleep(200)如果您只是在写入而不是等待固件的响应,则在写入之间使用 a 。这将确保您的所有数据都能到达。没有sleep我总是得到最后一个数据包。如果您注意到已接受的答案,他也在sleep两者之间使用过。

采纳答案by Huy Tower

BLE allows you transfer maximum of 20 bytes.

BLE 允许您最多传输 20 个字节。

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

如果你想发送超过 20 个字节,你应该定义数组byte[]来包含你想要的数据包数量。

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

如果您想发送少于 160 个字符(160 字节),以下示例工作正常。

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

p/s :根据需要编辑以下内容。不要完全跟着我。

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.

实际上,当我们使用BLE时,移动端和固件端需要设置Key(例如0x03...)来定义双方之间的连接门。

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.

  • 当我们继续传输数据包时,不是最后一个。大门是byte[1] = 0x01

  • 如果我们发送最后一个,门是byte[1] = 0x00

The data contruction (20 bytes):

数据结构(20字节):

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

1 - Byte 1- 定义Gate ID:例如。消息门 ID byte[0] = 0x03

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

2 -Byte 2定义recognization:是最后一个数据包0x00还是继续发送数据包0x01

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

3 - Byte 3(去掉Byte 1&后应该是 18 个字节Byte 2) - 在这里附上消息内容。

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.

下面是一个发送带有多个数据包的 Message 的示例,每个数据包是一个大小为 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 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;
            }
        }
    }

回答by Zomb

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.

如果您想通过 BLE 发送大量数据,那么最好的办法是使用两个特征,一个发送大量数据,另一个发送最后一段。这样你就不需要将响应设置为 WRITE_NO_RESPONSE 并使用回调一直发送下一个片段,直到你到达最后一个片段,此时你将把它写入第二个特征,让设备知道您已完成写入数据,并且可以将所有数据连接在一起以形成一个大数据包。

回答by Devunwired

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.

BLE 规范不允许写操作超过 20 个字节,这是正确的。如果您无法将有效负载细分为多个特征(这在逻辑上更易于维护),那么您的分块机制是另一种方法。

However, realize that the BLE stack hateswhen you try to queue up multiple operations. Each read/write is asynchronous, which the result coming via the onCharacteristicRead()or onCharacteristicWrite()callback on the BluetoothGattCallbackinstance. 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:

但是,当您尝试将多个操作排队时,请意识到 BLE 堆栈讨厌。每个读/写都是异步的,其结果通过实例上的onCharacteristicRead()oronCharacteristicWrite()回调BluetoothGattCallback。您编写的代码尝试在彼此之上发送三个特征写入操作,而无需等待其间的回调。您的代码将需要遵循更类似的路径:

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

回答by Zac Siegel

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

如果另一端的设备支持,您实际上可以触发 BLE 长写入。

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

您可以通过将写入类型设置为BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT来做到这一点

In this case you can send more than 20 bytes.

在这种情况下,您可以发送超过 20 个字节。

回答by ThomasW

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.

在 Lollipop 上,您最多可以发送 512 个字节。您需要使用 BluetoothGatt.requestMtu()512 的值。此外,正如@Devunwired 提到的,您需要等到任何先前的操作完成后再调用它。

回答by Sielar

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.

BLE 能够发送超过 20 个字节,并且可以在 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).

您需要更改的是默认设置为23的链接MTU(其中只有20个可用于设置值)。如果要发送的给定数据包大于当前链路 MTU(这是onCharacteristicRead(...)API 中偏移参数的目的),Android 提供分片机制。

So you can make the MTU bigger, as a request from the central using: requestMtu(...)API. The latter will cause a callback call onMtuChangedat 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.

因此,您可以使用requestMtu(...)API作为来自中央的请求使 MTU 更大。后者将onMtuChanged在外围侧引起回叫,通知他新的 MTU。完成此操作后,您可以发送更大的数据包,而无需发出 Android 分片机制。

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.

替代方案是自己构建自己的分段机制,而不是发送比 MTU 大的数据包。或者依靠 Android 机制并使用 'offset' 参数使用它。

回答by Doni

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.

这是使用 chunk 方法实现的示例,但不使用Thread.sleep,我发现我的应用程序发送超过 20 位的数据更好更高效。

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.

数据包将在onCharacteristicWrite()触发后发送。我刚刚发现这个方法会在外围设备(BluetoothGattServer)发送一个sendResponse()方法后自动触发。

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

回答by Doni

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).

您需要请求 MTU 更新。这是最大传输单位。与现在一样,BLE 在单个数据包中最多接受 512 个字节。但是,如果不请求此 MTU 更新,您的设备将不会发送超过 23 个字节的数据包(当前)。



Using your BluetoothGatt object call requestMtu()

使用您的 BluetoothGatt 对象调用requestMtu()

Here is a linkto the developer's page

这是开发者页面的链接

enter image description here

在此处输入图片说明



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

BluetoothGattCallback将接收onMtuChanged()事件,如下所示。MTU 更新成功后,您可以将数据作为一个数据包发送。这是此开发人员页面的链接

enter image description here

在此处输入图片说明



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

我通常在连接到我希望写入的特征后调用 requestMtu() 。祝你好运。

回答by MartinLoren

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:

Queue 的另一种解决方案,它允许任意大小的无限消息(自己管理协议以放置消息分隔符)。没有睡眠,没有额外的延迟:

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