From a5ef952e3710e1a0c9899cc33cf24d707cf80e14 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Thu, 17 Sep 2015 19:21:22 +0200 Subject: [PATCH] Pebble: Implement WIP outbound communication with PebbleKit Android Apps This improves #106 Pebblebike aka Ventoo works to some extent sometimes now ;) --- .../deviceevents/GBDeviceEventAppMessage.java | 9 ++ .../devices/pebble/PebbleIoThread.java | 49 +++++++++-- .../devices/pebble/PebbleProtocol.java | 86 +++++++++++++++++-- 3 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java new file mode 100644 index 000000000..79ee6b81f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java @@ -0,0 +1,9 @@ +package nodomain.freeyourgadget.gadgetbridge.deviceevents; + +import java.util.UUID; + +public class GBDeviceEventAppMessage extends GBDeviceEvent { + public UUID appUUID; + public int id; + public String message; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index b649621cf..9e7cd8543 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable; @@ -92,10 +93,11 @@ public class PebbleIoThread extends GBDeviceIoThread { case PEBBLEKIT_ACTION_APP_STOP: uuid = (UUID) intent.getSerializableExtra("uuid"); if (uuid != null) { - write(mPebbleProtocol.encodeAppStart(uuid, action == PEBBLEKIT_ACTION_APP_START)); + write(mPebbleProtocol.encodeAppStart(uuid, action.equals(PEBBLEKIT_ACTION_APP_START))); } break; case PEBBLEKIT_ACTION_APP_SEND: + int transaction_id = intent.getIntExtra("transaction_id", -1); uuid = (UUID) intent.getSerializableExtra("uuid"); String jsonString = intent.getStringExtra("msg_data"); LOG.info("json string: " + jsonString); @@ -103,14 +105,48 @@ public class PebbleIoThread extends GBDeviceIoThread { try { JSONArray jsonArray = new JSONArray(jsonString); write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray)); + sendAppMessageAck(transaction_id); + } catch (JSONException e) { e.printStackTrace(); } break; + case PEBBLEKIT_ACTION_APP_ACK: + // we do not get a uuid and cannot map a transaction id to it, so we ack in PebbleProtocol early + /* + uuid = (UUID) intent.getSerializableExtra("uuid"); + int transaction_id = intent.getIntExtra("transaction_id", -1); + if (transaction_id >= 0 && transaction_id <= 255) { + write(mPebbleProtocol.encodeApplicationMessageAck(uuid, (byte) transaction_id)); + } else { + LOG.warn("illegal transacktion id " + transaction_id); + } + */ + break; } } }; + private void sendAppMessageIntent(GBDeviceEventAppMessage appMessage) { + Intent intent = new Intent(); + intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE); + intent.putExtra("uuid", appMessage.appUUID); + intent.putExtra("msg_data", appMessage.message); + intent.putExtra("transaction_id", appMessage.id); + LOG.info("broadcasting to uuid " + appMessage.appUUID + " transaction id: " + appMessage.id + " JSON: " + appMessage.message); + getContext().sendBroadcast(intent); + } + + private void sendAppMessageAck(int transactionId) { + if (transactionId > 0 && transactionId <= 255) { + Intent intent = new Intent(); + intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE_ACK); + intent.putExtra("transaction_id", transactionId); + LOG.info("broadcasting ACK (transaction id " + transactionId + ")"); + getContext().sendBroadcast(intent); + } + } + public PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) { super(gbDevice, context); mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol; @@ -347,14 +383,9 @@ public class PebbleIoThread extends GBDeviceIoThread { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(PEBBLEKIT_ACTION_APP_ACK); intentFilter.addAction(PEBBLEKIT_ACTION_APP_NACK); - intentFilter.addAction(PEBBLEKIT_ACTION_APP_RECEIVE); - intentFilter.addAction(PEBBLEKIT_ACTION_APP_RECEIVE_ACK); - intentFilter.addAction(PEBBLEKIT_ACTION_APP_RECEIVE_NACK); intentFilter.addAction(PEBBLEKIT_ACTION_APP_SEND); intentFilter.addAction(PEBBLEKIT_ACTION_APP_START); intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP); - intentFilter.addAction(PEBBLEKIT_ACTION_PEBBLE_CONNECTED); - intentFilter.addAction(PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED); try { getContext().registerReceiver(mPebbleKitReceiver, intentFilter); } catch (IllegalArgumentException e) { @@ -475,7 +506,13 @@ public class PebbleIoThread extends GBDeviceIoThread { GBDeviceEventAppInfo appInfoEvent = (GBDeviceEventAppInfo) deviceEvent; setInstallSlot(appInfoEvent.freeSlot); return false; + } else if (deviceEvent instanceof GBDeviceEventAppMessage) { + if (sharedPrefs.getBoolean("pebble_force_untested", false)) { + LOG.info("Got AppMessage event"); + sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent); + } } + return false; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index 4d7c78136..10a040bcf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -20,6 +20,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; @@ -189,8 +190,8 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte TYPE_BYTEARRAY = 0; static final byte TYPE_CSTRING = 1; - static final byte TYPE_UINT32 = 2; - static final byte TYPE_INT32 = 3; + static final byte TYPE_UINT = 2; + static final byte TYPE_INT = 3; static final short LENGTH_PREFIX = 4; static final short LENGTH_SIMPLEMESSAGE = 1; @@ -1123,10 +1124,10 @@ public class PebbleProtocol extends GBDeviceProtocol { while (dictSize-- > 0) { Integer key = buf.getInt(); byte type = buf.get(); - short length = buf.getShort(); // length + short length = buf.getShort(); switch (type) { - case TYPE_INT32: - case TYPE_UINT32: + case TYPE_INT: + case TYPE_UINT: dict.add(new Pair(key, buf.getInt())); break; case TYPE_CSTRING: @@ -1145,6 +1146,72 @@ public class PebbleProtocol extends GBDeviceProtocol { return dict; } + private GBDeviceEvent[] decodeDictToJSONAppMessage(UUID uuid, ByteBuffer buf) throws JSONException { + buf.order(ByteOrder.LITTLE_ENDIAN); + byte dictSize = buf.get(); + if (dictSize == 0) { + LOG.info("dict size is 0, ignoring"); + return null; + } + JSONArray jsonArray = new JSONArray(); + while (dictSize-- > 0) { + JSONObject jsonObject = new JSONObject(); + Integer key = buf.getInt(); + byte type = buf.get(); + short length = buf.getShort(); + jsonObject.put("key", key); + jsonObject.put("length", length); + switch (type) { + case TYPE_UINT: + jsonObject.put("type", "uint"); + if (length == 1) { + jsonObject.put("value", buf.get() & 0xff); + } else if (length == 2) { + jsonObject.put("value", buf.getShort() & 0xffff); + } else { + jsonObject.put("value", buf.getInt() & 0xffffffffL); + } + break; + case TYPE_INT: + jsonObject.put("type", "int"); + if (length == 1) { + jsonObject.put("value", buf.get()); + } else if (length == 2) { + jsonObject.put("value", buf.getShort()); + } else { + jsonObject.put("value", buf.getInt()); + } + break; + case TYPE_BYTEARRAY: + case TYPE_CSTRING: + byte[] bytes = new byte[length]; + buf.get(bytes); + if (type == TYPE_BYTEARRAY) { + jsonObject.put("type", "bytes"); + jsonObject.put("value", Base64.encode(bytes, Base64.NO_WRAP)); + } else { + jsonObject.put("type", "string"); + jsonObject.put("value", Arrays.toString(bytes)); + } + break; + default: + LOG.info("unknown type in appmessage, ignoring"); + return null; + } + jsonArray.put(jsonObject); + } + + // this is a hack we send an ack to the Pebble immediately because we cannot map the transaction_id from the intent back to a uuid yet + GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); + + GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage(); + appMessage.appUUID = uuid; + appMessage.id = last_id & 0xff; + appMessage.message = jsonArray.toString(); + return new GBDeviceEvent[]{appMessage, sendBytesAck}; + } + byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList> pairs) { int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict) for (Pair pair : pairs) { @@ -1172,7 +1239,7 @@ public class PebbleProtocol extends GBDeviceProtocol { for (Pair pair : pairs) { buf.putInt(pair.first); if (pair.second instanceof Integer) { - buf.put(TYPE_INT32); + buf.put(TYPE_INT); buf.putShort((short) 4); // length of int buf.putInt((int) pair.second); } else if (pair.second instanceof String) { @@ -1599,6 +1666,13 @@ public class PebbleProtocol extends GBDeviceProtocol { } else if (GadgetbridgePblSupport.uuid.equals(uuid)) { ArrayList> dict = decodeDict(buf); devEvts = mGadgetbridgePblSupport.handleMessage(dict); + } else { + try { + devEvts = decodeDictToJSONAppMessage(uuid, buf); + } catch (JSONException e) { + e.printStackTrace(); + return null; + } } break; case APPLICATIONMESSAGE_ACK: