Pebble: Implement WIP outbound communication with PebbleKit Android Apps

This improves #106

Pebblebike aka Ventoo works to some extent sometimes now ;)
This commit is contained in:
Andreas Shimokawa 2015-09-17 19:21:22 +02:00
parent 502c005a0e
commit a5ef952e37
3 changed files with 132 additions and 12 deletions

View File

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

View File

@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable;
@ -92,10 +93,11 @@ public class PebbleIoThread extends GBDeviceIoThread {
case PEBBLEKIT_ACTION_APP_STOP: case PEBBLEKIT_ACTION_APP_STOP:
uuid = (UUID) intent.getSerializableExtra("uuid"); uuid = (UUID) intent.getSerializableExtra("uuid");
if (uuid != null) { if (uuid != null) {
write(mPebbleProtocol.encodeAppStart(uuid, action == PEBBLEKIT_ACTION_APP_START)); write(mPebbleProtocol.encodeAppStart(uuid, action.equals(PEBBLEKIT_ACTION_APP_START)));
} }
break; break;
case PEBBLEKIT_ACTION_APP_SEND: case PEBBLEKIT_ACTION_APP_SEND:
int transaction_id = intent.getIntExtra("transaction_id", -1);
uuid = (UUID) intent.getSerializableExtra("uuid"); uuid = (UUID) intent.getSerializableExtra("uuid");
String jsonString = intent.getStringExtra("msg_data"); String jsonString = intent.getStringExtra("msg_data");
LOG.info("json string: " + jsonString); LOG.info("json string: " + jsonString);
@ -103,14 +105,48 @@ public class PebbleIoThread extends GBDeviceIoThread {
try { try {
JSONArray jsonArray = new JSONArray(jsonString); JSONArray jsonArray = new JSONArray(jsonString);
write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray)); write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray));
sendAppMessageAck(transaction_id);
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }
break; 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) { public PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) {
super(gbDevice, context); super(gbDevice, context);
mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol; mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol;
@ -347,14 +383,9 @@ public class PebbleIoThread extends GBDeviceIoThread {
IntentFilter intentFilter = new IntentFilter(); IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PEBBLEKIT_ACTION_APP_ACK); intentFilter.addAction(PEBBLEKIT_ACTION_APP_ACK);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_NACK); 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_SEND);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_START); intentFilter.addAction(PEBBLEKIT_ACTION_APP_START);
intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP); intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP);
intentFilter.addAction(PEBBLEKIT_ACTION_PEBBLE_CONNECTED);
intentFilter.addAction(PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED);
try { try {
getContext().registerReceiver(mPebbleKitReceiver, intentFilter); getContext().registerReceiver(mPebbleKitReceiver, intentFilter);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -475,7 +506,13 @@ public class PebbleIoThread extends GBDeviceIoThread {
GBDeviceEventAppInfo appInfoEvent = (GBDeviceEventAppInfo) deviceEvent; GBDeviceEventAppInfo appInfoEvent = (GBDeviceEventAppInfo) deviceEvent;
setInstallSlot(appInfoEvent.freeSlot); setInstallSlot(appInfoEvent.freeSlot);
return false; return false;
} else if (deviceEvent instanceof GBDeviceEventAppMessage) {
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
LOG.info("Got AppMessage event");
sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent);
} }
}
return false; return false;
} }

View File

@ -20,6 +20,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; 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_BYTEARRAY = 0;
static final byte TYPE_CSTRING = 1; static final byte TYPE_CSTRING = 1;
static final byte TYPE_UINT32 = 2; static final byte TYPE_UINT = 2;
static final byte TYPE_INT32 = 3; static final byte TYPE_INT = 3;
static final short LENGTH_PREFIX = 4; static final short LENGTH_PREFIX = 4;
static final short LENGTH_SIMPLEMESSAGE = 1; static final short LENGTH_SIMPLEMESSAGE = 1;
@ -1123,10 +1124,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
while (dictSize-- > 0) { while (dictSize-- > 0) {
Integer key = buf.getInt(); Integer key = buf.getInt();
byte type = buf.get(); byte type = buf.get();
short length = buf.getShort(); // length short length = buf.getShort();
switch (type) { switch (type) {
case TYPE_INT32: case TYPE_INT:
case TYPE_UINT32: case TYPE_UINT:
dict.add(new Pair<Integer, Object>(key, buf.getInt())); dict.add(new Pair<Integer, Object>(key, buf.getInt()));
break; break;
case TYPE_CSTRING: case TYPE_CSTRING:
@ -1145,6 +1146,72 @@ public class PebbleProtocol extends GBDeviceProtocol {
return dict; 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<Pair<Integer, Object>> pairs) { byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList<Pair<Integer, Object>> pairs) {
int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict) int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict)
for (Pair<Integer, Object> pair : pairs) { for (Pair<Integer, Object> pair : pairs) {
@ -1172,7 +1239,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
for (Pair<Integer, Object> pair : pairs) { for (Pair<Integer, Object> pair : pairs) {
buf.putInt(pair.first); buf.putInt(pair.first);
if (pair.second instanceof Integer) { if (pair.second instanceof Integer) {
buf.put(TYPE_INT32); buf.put(TYPE_INT);
buf.putShort((short) 4); // length of int buf.putShort((short) 4); // length of int
buf.putInt((int) pair.second); buf.putInt((int) pair.second);
} else if (pair.second instanceof String) { } else if (pair.second instanceof String) {
@ -1599,6 +1666,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
} else if (GadgetbridgePblSupport.uuid.equals(uuid)) { } else if (GadgetbridgePblSupport.uuid.equals(uuid)) {
ArrayList<Pair<Integer, Object>> dict = decodeDict(buf); ArrayList<Pair<Integer, Object>> dict = decodeDict(buf);
devEvts = mGadgetbridgePblSupport.handleMessage(dict); devEvts = mGadgetbridgePblSupport.handleMessage(dict);
} else {
try {
devEvts = decodeDictToJSONAppMessage(uuid, buf);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
} }
break; break;
case APPLICATIONMESSAGE_ACK: case APPLICATIONMESSAGE_ACK: