I'm sending a good bit of data to my Pebble, but some of it keeps getting dropped. I realized that part of this was due to the buffer size not being large enough for the PebbleDictionary
that I was sending it, so I broke it up into multiple small ones instead. However, this introduces the issue with getting an APP_MSG_BUSY
error.
This may be happening because I'm not waiting for an ack/nack from the pebble, but instead just sending data back to back to back. I thus tried adding ack/nack handlers as well as a queue, but I was unable to get that to work due to my sendMessage()
function blocking the main UI thread while waiting for the ack/nack handlers.
Thus, my question is what the best way of handling this particular instance of APP_MSG_BUSY
is. I don't want any of the data I send to get dropped, so that means either waiting for acknowledgements before sending the next piece of data, or resending after getting a nack. If possible, I'd like to avoid threading, but I haven't been able to come up with a reasonable solution that doesn't involve threading.
EDIT: To the best of my knowledge, there are no bugs in the pebble code. It will ask for data with the correct key, and it will acknowledge (automatically) any messages sent by the android app.
If you would like, I've posted my code below:
Current code (relevant part of android app):
public class MainActivity extends ActionBarActivity {
private PebbleDataReceiver mReceiver;
private PebbleAckReceiver ackReceiver;
private PebbleNackReceiver nackReceiver;
ConcurrentLinkedQueue<BoolDictionary> queue = new ConcurrentLinkedQueue<BoolDictionary>();
final UUID PEBBLE_APP_UUID = UUID.fromString("2ef1h2ba-1a59-41f7-87da-797beca4d395");
final static int CONTACTS_NEEDED = 0x0;
final static int CONTACTS_SIZE = 0x1;
final static int NEW_MESSAGE = 0x2;
final static int NEW_CONVERSATION = 0x3;
final static int RECORD_NEW_MESSAGE = 0x4;
Thread sendMessages = new Thread(){
public void run(){
while (true){
PebbleKit.sendDataToPebbleWithTransactionId(getApplicationContext(), PEBBLE_APP_UUID, queue.element().getDict(), queue.element().getTransId());
try {
queue.element().wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
if (queue.element().isAkced()){
break;
}
}
queue.remove();
if (queue.size() > 0){
run();
}
else if (queue.size() == 0){
queue.element().wait();
}
}
};
public BoolDictionary createBoolDictionary(int key, int data){
PebbleDictionary dict = new PebbleDictionary();
dict.addInt32(key, data);
return new BoolDictionary(dict);
}
public BoolDictionary createBoolDictionary(int key, String data){
PebbleDictionary dict = new PebbleDictionary();
dict.addString(key, data);
return new BoolDictionary(dict);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sendMessages.start();
ackReceiver = new PebbleAckReceiver(PEBBLE_APP_UUID) {
@Override
public void receiveAck(Context context, int transactionId) {
if (queue.element().getTransId() == transactionId){
queue.element().setAkced(true);
queue.element().notifyAll();
}
}
};
nackReceiver = new PebbleNackReceiver(PEBBLE_APP_UUID){
@Override
public void receiveNack(Context context, int transactionId) {
if (queue.element().getTransId() == transactionId){
queue.element().setAkced(false);
queue.element().notifyAll();
}
}
};
mReceiver = new PebbleDataReceiver(PEBBLE_APP_UUID) {
@Override
public void receiveData(Context context, int transactionId, PebbleDictionary data) {
PebbleKit.sendAckToPebble(context, transactionId);
if (data.contains(CONTACTS_NEEDED)){
//test data
queue.add(createBoolDictionary(0x5, "Entry 1"));
queue.add(createBoolDictionary(0x6, "Entry 2"));
queue.add(createBoolDictionary(0x7, "Entry 3"));
queue.add(createBoolDictionary(0x8, "Entry 4"));
queue.add(createBoolDictionary(0x9, "Entry 5"));
queue.element().notifyAll();
}
}
};
PebbleKit.registerReceivedDataHandler(this, mReceiver);
PebbleKit.registerReceivedAckHandler(this, ackReceiver);
PebbleKit.registerReceivedNackHandler(this, nackReceiver);
}
@Override
protected void onPause(){
super.onPause();
unregisterReceiver(mReceiver);
unregisterReceiver(ackReceiver);
unregisterReceiver(nackReceiver);
}
}
BoolDictionary:
public class BoolDictionary extends PebbleDictionary{
private PebbleDictionary dict;
private boolean akced = false;
private int transId;
BoolDictionary(PebbleDictionary data){
this.setDict(data);
setTransId(new Random().nextInt(Integer.MAX_VALUE));
}
[insert getters and setters here]
}
Which generates the following errors:
07-01 10:43:06.096: E/AndroidRuntime(21941): FATAL EXCEPTION: Thread-5310
07-01 10:43:06.096: E/AndroidRuntime(21941): Process: com.example.firstapp, PID: 21941
07-01 10:43:06.096: E/AndroidRuntime(21941): java.util.NoSuchElementException
07-01 10:43:06.096: E/AndroidRuntime(21941): at java.util.AbstractQueue.element(AbstractQueue.java:107)
07-01 10:43:06.096: E/AndroidRuntime(21941): at com.example.firstapp.MainActivity$1.run(MainActivity.java:366)
The error APP_MSG_BUSY
is returned when the Pebble receives your new packet but there is already a message in the bluetooth buffer that your app has not had time to read yet.
To understand this, you need to remember that memory is tight on Pebble and so the system can not buffer incoming messages because this would take a lot of memory very quickly. Instead it rejects the message.
The best strategy to avoid this problem is to wait for the ACK/NACK from Pebble before you send another message. It seems you have done that already but another tip is to make sure you group multiple key in one message, instead of sending one message for each key (this maximizes the usage of the incoming buffer but of course the limit is the size of that buffer).