2

I have a message thread for sending a buffer of messages. Each message is queued to be sent once onCharacteristicWrite is successful before the characteristic writes the next message. The characeristic is also set to WRITE_TYPE_NO_RESPONSE, so the message buffer queue is pretty fast (0-7ms approximately) between characteristic write calls.

The main issue: "jammed up" characteristic

For the most part this works great. The problem seems to arise when there's a large amount of messages (possibly happens with fewer message, but is more noticeable when sending lots of messages). What happens is writeCharacteristicwill get called, and the characteristic seems to lock up, since onCharacteristicChanged no longer reads any new data, and doesn't get to onCharacteristicWrite.

Other things I've noticed:

  1. Adding a sleep delay of 5-10ms after each characteristicWrite seems to help, but I don't understand why the Bluetooth GATT object would need a delay when onCharacteristicWrite returns successful.

  2. Sometimes I'll get a callback in onConnectionStateChange with status 8, device out of range. This doesn't always happen though.

  3. Sometimes characteristicWrite returns false; however, it can also return true before going into the "jammed up characteristic" state described above

Message Thread code:

    private boolean stopMessageThread = false;
    private boolean characteristicWriteSuccessful = true;
    private ArrayList<byte[]> messageQueue = new ArrayList<byte[]>();
    private Thread messageThread =  new Thread( new Runnable() {
        private long lastTime = 0;
        private int count = 0;
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted() && !stopMessageThread) {
                if(messageQueue.size() != 0 && characteristicWriteSuccessful) {

                    Log.i(TAG, ""+(System.currentTimeMillis()-lastTime));
                    Log.i(TAG, "Queue count: "+messageQueue.size());

                    characteristicWriteSuccessful = false;
                    byte[] message = messageQueue.remove(0);
                    customCharacteristic.setValue(message);
                    boolean status = bluetoothGatt.writeCharacteristic(customCharacteristic);

                    Log.i(TAG, "write characteristic status "+status);

                    lastTime = System.currentTimeMillis();
                    //sleep(10); // this kinda helps but can still throw the error
                }
            }
        }
    });
karamazovbros
  • 950
  • 1
  • 11
  • 40
  • 1
    First, please don't max out the cpu by having a busy loop. Second, what role does onCharacteristicChanged play here? Third, onCharacteristicWrite can't "return" false since it's a callback. Fourth, your characteristicWriteSuccessful must be volatile. – Emil Oct 11 '19 at 07:58
  • @Emil Sure, I'll change the busy loop, 2nd, onCharacteristicChanged receives the data from my device, and no longer does when I get this error. For 3rd, I meant "characteristicWrite" function, not the callback. I'll change the boolean as well. – karamazovbros Oct 11 '19 at 17:02
  • 1
    writeCharacteristic returns false when there already is another pending GATT operation. Do you execute any other requests while this write thread is running? For onCharacteristicChanged, is it another characteristic you get notifications for than the one you're writing to? – Emil Oct 11 '19 at 17:13
  • There are no other requests. It's the same, "customCharacteristic" for oncharacteristicChanged. – karamazovbros Oct 11 '19 at 17:59
  • I'd recommend to have two characteristics due to a race condition in Android https://stackoverflow.com/questions/38922639/how-could-i-achieve-maximum-thread-safety-with-a-read-write-ble-gatt-characteris. writeCharacteristic normally returns false because you have another request pending https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/bluetooth/BluetoothGatt.java#1183 – Emil Oct 12 '19 at 09:04

1 Answers1

4

Aside from busy waiting, which can block an entire CPU and quickly drain the battery, I can't see any synchronization. There are shared data structures (likely stopMessageThread, characteristicWriteSuccessful and messageQueue) and several threads accessing them. Without synchronization, race conditions will occur and the jam up could be a manifestation of it.

So I propose to go with a simpler design, in particular without a thread for sending messages:

private ArrayList<byte[]> messageQueue = new ArrayList<byte[]>();
private boolean isSending = false;

void sendMessage(byte[] message) {
    synchronized (this) {
        if (isSending) {
            messageQueue.add(message);
            return;
        }
        isSending = true;
    }
    customCharacteristic.setValue(message);
    bluetoothGatt.writeCharacteristic(customCharacteristic);
}

public void onCharacteristicWrite (BluetoothGatt gatt, 
                BluetoothGattCharacteristic characteristic, 
                int status) {
    byte[] message;
    synchronized (this) {
        if (messageQueue.size() == 0) {
            isSending = false;
            return;
        }
        message = messageQueue.remove(0);
    }
    customCharacteristic.setValue(message);
    bluetoothGatt.writeCharacteristic(customCharacteristic); 
}

The assumption in this solution is that writeCharacteristic does not block and is quick. That's a safe assumption as the method is asynchronous by design: it has a callback that will be called when the operation is complete.

So the callback onCharacteristicWrite is used to send the next message in the buffer. Therefore, the need for the thread goes away – and so goes the associated complexity.

There are still several threads involved as the callback is called from a background thread. Therefore, the access to the shared data is synchronized.

OlivierM
  • 2,820
  • 24
  • 41
Codo
  • 75,595
  • 17
  • 168
  • 206
  • 2
    Could it be a problem with WRITE_TYPE_NO_RESPONSE? – karamazovbros Oct 11 '19 at 23:57
  • 1
    Yeah, with WRITE_TYPE_DEFAULT the characteristic doesn't hang, but it takes too long in between onCharacteristicWrite callbacks to send data on time. Time is essential. @Codo – karamazovbros Oct 12 '19 at 00:02
  • For instance, with WRITE_TYPE_NO_RESPONSE putting Thread.sleep(10) after writing helps, but it's slower than desired. – karamazovbros Oct 12 '19 at 00:16
  • Do these comments refer to my code or still to your original code? If they refer to my code: what problem have you encountered? – Codo Oct 12 '19 at 09:47
  • 1
    You're code simplifies things, but I was still getting the same lock up after sending messages with WRITE_TYPE_NO_RESPONSE with your code. Sleeping helped get through all of them in this write mode, but still would occasionally lock up. Without WRITE_TYPE_NO_RESPONSE I don't get the error but the time between sending messages is a bit long. – karamazovbros Oct 12 '19 at 16:06