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:
parent
502c005a0e
commit
a5ef952e37
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue