diff --git a/README.md b/README.md index 62bdcc0c..73b2b24c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ need to create an account and transmit any of your data to the vendor's servers. * Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round * Pebble 2 (experimental, PAIR WITHIN GADGETBRIDGE) * Mi Band, Mi Band 1A, Mi Band 1S -* Mi Band 2 (notifications, alarms and heart rate measurement only) +* Mi Band 2 * Vibratissimo (experimental) ## Features (Pebble) @@ -58,6 +58,7 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/ * Discovery and pairing * Mi Band notifications (LEDs + vibration) for +* Display live activity data (alpha) * Incoming calls * SMS received * K-9 mails received @@ -82,9 +83,10 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/ * K-9 mails received * Conversations messages * Generic Android notifications -* Synchronize the time to the Mi Band +* Synchronize the time to the Mi Band 2 * Display firmware version -* Heart rate measurement (alpha) +* Heart rate measurement on demand and during sleep +* Synchronize activity data (alpha) * Set alarms on the Mi Band 2 ## How to use (Mi Band 1+2) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java index 5cf592b7..c7debdcf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java @@ -25,9 +25,9 @@ public class MiBand2SampleProvider extends AbstractMiBandSampleProvider { // 0 = same activity kind as before // 1 = light activity walking? // 3 = definitely non-wear - // 9 = probably deep sleep, definitely some kind of sleep + // 9 = probably light sleep, definitely some kind of sleep // 10 = ignore, except for hr (if valid) - // 11 = probably light sleep + // 11 = probably deep sleep // 12 = definitely wake up // 17 = definitely not sleep related 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 b5413933..4e895849 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 @@ -500,6 +500,7 @@ class PebbleIoThread extends GBDeviceIoThread { LOG.info("syncing time"); write(mPebbleProtocol.encodeSetTime()); } + write(mPebbleProtocol.encodeEnableAppLogs(prefs.getBoolean("pebble_enable_applogs",false))); write(mPebbleProtocol.encodeReportDataLogSessions()); gbDevice.setState(GBDevice.State.INITIALIZED); 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 f6edf7fd..66eebe33 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 @@ -388,7 +388,7 @@ public class PebbleProtocol extends GBDeviceProtocol { private final HashMap mDatalogSessions = new HashMap<>(); - private static byte[] encodeSimpleMessage(short endpoint, byte command) { + private byte[] encodeSimpleMessage(short endpoint, byte command) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SIMPLEMESSAGE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_SIMPLEMESSAGE); @@ -533,7 +533,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeSetCallState("Where are you?", "Gadgetbridge", start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END); } - private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) { + private byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) { final short ACTION_LENGTH_MIN = 10; String[] parts = {title, subtitle, body}; @@ -1018,7 +1018,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array()); } - public byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) { + private byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) { short length = (short) (18 + caption.getBytes().length); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN); @@ -1039,7 +1039,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeActionResponse(UUID uuid, int iconId, String caption) { + private byte[] encodeActionResponse(UUID uuid, int iconId, String caption) { short length = (short) (29 + caption.getBytes().length); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN); @@ -1060,7 +1060,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeInstallMetadata(UUID uuid, String appName, short appVersion, short sdkVersion, int flags, int iconId) { + byte[] encodeInstallMetadata(UUID uuid, String appName, short appVersion, short sdkVersion, int flags, int iconId) { final short METADATA_LENGTH = 126; byte[] name_buf = new byte[96]; @@ -1093,7 +1093,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeGetTime() { + byte[] encodeGetTime() { return encodeSimpleMessage(ENDPOINT_TIME, TIME_GETTIME); } @@ -1314,7 +1314,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodePhoneVersion(byte os) { + private byte[] encodePhoneVersion(byte os) { return encodePhoneVersion3x(os); } @@ -1393,7 +1393,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } /* pebble specific install methods */ - public byte[] encodeUploadStart(byte type, int app_id, int size, String filename) { + byte[] encodeUploadStart(byte type, int app_id, int size, String filename) { short length; if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) { length = LENGTH_UPLOADSTART_3X; @@ -1429,7 +1429,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeUploadChunk(int token, byte[] buffer, int size) { + byte[] encodeUploadChunk(int token, byte[] buffer, int size) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCHUNK + size); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort((short) (LENGTH_UPLOADCHUNK + size)); @@ -1441,7 +1441,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeUploadCommit(int token, int crc) { + byte[] encodeUploadCommit(int token, int crc) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMMIT); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_UPLOADCOMMIT); @@ -1452,7 +1452,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeUploadComplete(int token) { + byte[] encodeUploadComplete(int token) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMPLETE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_UPLOADCOMPLETE); @@ -1462,7 +1462,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeUploadCancel(int token) { + byte[] encodeUploadCancel(int token) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCANCEL); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_UPLOADCANCEL); @@ -1483,11 +1483,11 @@ public class PebbleProtocol extends GBDeviceProtocol { } - public byte[] encodeInstallFirmwareStart() { + byte[] encodeInstallFirmwareStart() { return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARESTART); } - public byte[] encodeInstallFirmwareComplete() { + byte[] encodeInstallFirmwareComplete() { return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARECOMPLETE); } @@ -1496,7 +1496,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } - public byte[] encodeAppRefresh(int index) { + byte[] encodeAppRefresh(int index) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_REFRESHAPP); @@ -1507,7 +1507,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeDatalog(byte handle, byte reply) { + private byte[] encodeDatalog(byte handle, byte reply) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + 2); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort((short) 2); @@ -1532,7 +1532,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - private static byte[] encodePing(byte command, int cookie) { + private byte[] encodePing(byte command, int cookie) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PING); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_PING); @@ -1543,6 +1543,17 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } + byte[] encodeEnableAppLogs(boolean enable) { + final short LENGTH_APPLOGS = 1; + ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPLOGS); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort(LENGTH_APPLOGS); + buf.putShort(ENDPOINT_APPLOGS); + buf.put((byte) (enable ? 1 : 0)); + + return buf.array(); + } + private ArrayList> decodeDict(ByteBuffer buf) { ArrayList> dict = new ArrayList<>(); buf.order(ByteOrder.LITTLE_ENDIAN); @@ -1652,8 +1663,7 @@ public class PebbleProtocol extends GBDeviceProtocol { length += ((String) pair.second).getBytes().length + 1; } else if (pair.second instanceof byte[]) { length += ((byte[]) pair.second).length; - } - else { + } else { LOG.warn("unknown type: " + pair.second.getClass().toString()); } } @@ -1700,7 +1710,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - public byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) { + byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) { ArrayList> pairs = new ArrayList<>(); for (int i = 0; i < jsonArray.length(); i++) { try { @@ -1739,7 +1749,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs); } - private static byte reverseBits(byte in) { + private byte reverseBits(byte in) { byte out = 0; for (int i = 0; i < 8; i++) { byte bit = (byte) (in & 1); @@ -1803,14 +1813,10 @@ public class PebbleProtocol extends GBDeviceProtocol { byte command = buf.get(); if (command == NOTIFICATIONACTION_INVOKE) { int id; - long uuid_high = 0; - long uuid_low = 0; + UUID uuid = new UUID(0,0); if (mFwMajor >= 3) { - buf.order(ByteOrder.BIG_ENDIAN); - uuid_high = buf.getLong(); - uuid_low = buf.getLong(); - buf.order(ByteOrder.LITTLE_ENDIAN); - id = (int) (uuid_low & 0xffffffffL); + uuid = getUUID(buf); + id = (int) (uuid.getLeastSignificantBits() & 0xffffffffL); } else { id = buf.getInt(); } @@ -1880,7 +1886,7 @@ public class PebbleProtocol extends GBDeviceProtocol { if (mFwMajor >= 3 || needsAck2x) { sendBytesAck = new GBDeviceEventSendBytes(); if (mFwMajor >= 3) { - sendBytesAck.encodedBytes = encodeActionResponse(new UUID(uuid_high, uuid_low), icon_id, caption); + sendBytesAck.encodedBytes = encodeActionResponse(uuid, icon_id, caption); } else { sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, caption); } @@ -1905,6 +1911,17 @@ public class PebbleProtocol extends GBDeviceProtocol { return null; } + private void decodeAppLogs(ByteBuffer buf) { + UUID uuid = getUUID(buf); + int timestamp = buf.getInt(); + int logLevel = buf.get() & 0xff; + int messageLength = buf.get() & 0xff; + int lineNumber = buf.getShort() & 0xffff; + String fileName = getFixedString(buf, 16); + String message = getFixedString(buf, messageLength); + LOG.debug("APP_LOGS from uuid " + uuid.toString() + " in " + fileName + ":" + lineNumber + " " + message); + } + private GBDeviceEvent decodeSystemMessage(ByteBuffer buf) { buf.get(); // unknown; byte command = buf.get(); @@ -1925,9 +1942,7 @@ public class PebbleProtocol extends GBDeviceProtocol { private GBDeviceEvent[] decodeAppRunState(ByteBuffer buf) { byte command = buf.get(); - long uuid_high = buf.getLong(); - long uuid_low = buf.getLong(); - UUID uuid = new UUID(uuid_high, uuid_low); + UUID uuid = getUUID(buf); final String ENDPOINT_NAME = "APPRUNSTATE"; switch (command) { case APPRUNSTATE_START: @@ -1976,9 +1991,7 @@ public class PebbleProtocol extends GBDeviceProtocol { private GBDeviceEventAppManagement decodeAppFetch(ByteBuffer buf) { byte command = buf.get(); if (command == 0x01) { - long uuid_high = buf.getLong(); - long uuid_low = buf.getLong(); - UUID uuid = new UUID(uuid_high, uuid_low); + UUID uuid = getUUID(buf); buf.order(ByteOrder.LITTLE_ENDIAN); int app_id = buf.getInt(); GBDeviceEventAppManagement fetchRequest = new GBDeviceEventAppManagement(); @@ -2011,10 +2024,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } break; case DATALOG_OPENSESSION: - buf.order(ByteOrder.BIG_ENDIAN); - long uuid_high = buf.getLong(); - long uuid_low = buf.getLong(); - UUID uuid = new UUID(uuid_high, uuid_low); + UUID uuid = getUUID(buf); buf.order(ByteOrder.LITTLE_ENDIAN); int timestamp = buf.getInt(); int log_tag = buf.getInt(); @@ -2073,7 +2083,7 @@ public class PebbleProtocol extends GBDeviceProtocol { short length = buf.getShort(); short endpoint = buf.getShort(); GBDeviceEvent devEvts[] = null; - byte pebbleCmd = -1; + byte pebbleCmd; switch (endpoint) { case ENDPOINT_MUSICCONTROL: pebbleCmd = buf.get(); @@ -2123,14 +2133,12 @@ public class PebbleProtocol extends GBDeviceProtocol { GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); buf.getInt(); // skip - byte[] tmp = new byte[32]; - buf.get(tmp, 0, 32); - - versionCmd.fwVersion = new String(tmp).trim(); + versionCmd.fwVersion = getFixedString(buf, 32); mFwMajor = versionCmd.fwVersion.charAt(1) - 48; LOG.info("Pebble firmware major detected as " + mFwMajor); + byte[] tmp = new byte[9]; buf.get(tmp, 0, 9); int hwRev = buf.get() + 8; if (hwRev >= 0 && hwRev < hwRevisions.length) { @@ -2145,8 +2153,6 @@ public class PebbleProtocol extends GBDeviceProtocol { GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo(); int slotCount = buf.getInt(); int slotsUsed = buf.getInt(); - byte[] appName = new byte[32]; - byte[] appCreator = new byte[32]; appInfoCmd.apps = new GBDeviceApp[slotsUsed]; boolean[] slotInUse = new boolean[slotCount]; @@ -2154,8 +2160,9 @@ public class PebbleProtocol extends GBDeviceProtocol { int id = buf.getInt(); int index = buf.getInt(); slotInUse[index] = true; - buf.get(appName, 0, 32); - buf.get(appCreator, 0, 32); + String appName = getFixedString(buf, 32); + String appCreator = getFixedString(buf, 32); + int flags = buf.getInt(); GBDeviceApp.Type appType; @@ -2167,7 +2174,7 @@ public class PebbleProtocol extends GBDeviceProtocol { appType = GBDeviceApp.Type.APP_GENERIC; } Short appVersion = buf.getShort(); - appInfoCmd.apps[i] = new GBDeviceApp(tmpUUIDS.get(i), new String(appName).trim(), new String(appCreator).trim(), appVersion.toString(), appType); + appInfoCmd.apps[i] = new GBDeviceApp(tmpUUIDS.get(i), appName, appCreator, appVersion.toString(), appType); } for (int i = 0; i < slotCount; i++) { if (!slotInUse[i]) { @@ -2185,9 +2192,7 @@ public class PebbleProtocol extends GBDeviceProtocol { tmpUUIDS.clear(); slotsUsed = buf.getInt(); for (int i = 0; i < slotsUsed; i++) { - long uuid_high = buf.getLong(); - long uuid_low = buf.getLong(); - UUID uuid = new UUID(uuid_high, uuid_low); + UUID uuid = getUUID(buf); LOG.info("found uuid: " + uuid); tmpUUIDS.add(uuid); } @@ -2232,13 +2237,10 @@ public class PebbleProtocol extends GBDeviceProtocol { case ENDPOINT_LAUNCHER: pebbleCmd = buf.get(); last_id = buf.get(); - long uuid_high = buf.getLong(); - long uuid_low = buf.getLong(); + UUID uuid = getUUID(buf); switch (pebbleCmd) { case APPLICATIONMESSAGE_PUSH: - UUID uuid = new UUID(uuid_high, uuid_low); - if (endpoint == ENDPOINT_LAUNCHER) { LOG.info("got LAUNCHER PUSH from UUID " + uuid); break; @@ -2318,6 +2320,10 @@ public class PebbleProtocol extends GBDeviceProtocol { break; case ENDPOINT_APPREORDER: devEvts = new GBDeviceEvent[]{decodeAppReorder(buf)}; + break; + case ENDPOINT_APPLOGS: + decodeAppLogs(buf); + break; default: break; } @@ -2325,8 +2331,24 @@ public class PebbleProtocol extends GBDeviceProtocol { return devEvts; } - public void setForceProtocol(boolean force) { + void setForceProtocol(boolean force) { LOG.info("setting force protocol to " + force); mForceProtocol = force; } + + private String getFixedString(ByteBuffer buf, int length) { + byte[] tmp = new byte[length]; + buf.get(tmp, 0, length); + + return new String(tmp).trim(); + } + + private UUID getUUID(ByteBuffer buf) { + ByteOrder byteOrder = buf.order(); + buf.order(ByteOrder.BIG_ENDIAN); + long uuid_high = buf.getLong(); + long uuid_low = buf.getLong(); + buf.order(byteOrder); + return new UUID(uuid_high, uuid_low); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c8e5e8d..09c215ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -120,6 +120,9 @@ Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING! Always prefer BLE Use experimental Pebble LE support for all Pebbles instead of BT classic, requires paring a "Pebble LE" after non LE had been connected once + Enable Watch App Logging + Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect) + Reconnection Attempts not connected diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6dd3371b..efe3e4c7 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -341,6 +341,11 @@ android:key="pebble_force_le" android:summary="@string/pref_summary_pebble_forcele" android:title="@string/pref_title_pebble_forcele" /> +