From bef59ae9c08ad55a55ca7b050448d002d624b9de Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 23 May 2016 21:13:12 +0200 Subject: [PATCH 1/5] Parse new version of Pebble health datalog message with tag "84". This message was previously treated as a further "Sleep" message type, with firmware version 3.12 further types were added that are clearly unrelated to sleep (possibly to high-intensity activities like running etc.), hence the related code is now moved to a separate class. The only decoded records are still sleep-related. Fixes #312 --- .../DatalogSessionHealthOverlayData.java | 99 +++++++++++++++++++ .../pebble/DatalogSessionHealthSleep.java | 95 ++---------------- .../devices/pebble/PebbleProtocol.java | 4 +- 3 files changed, 109 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java new file mode 100644 index 000000000..b7d425448 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java @@ -0,0 +1,99 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +class DatalogSessionHealthOverlayData extends DatalogSession { + + private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthOverlayData.class); + + public DatalogSessionHealthOverlayData(byte id, UUID uuid, int tag, byte item_type, short item_size) { + super(id, uuid, tag, item_type, item_size); + taginfo = "(health - overlay data " + tag + " )"; + } + + @Override + public boolean handleMessage(ByteBuffer datalogMessage, int length) { + LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length)); + + int initialPosition = datalogMessage.position(); + int beginOfRecordPosition; + short recordVersion; //probably + short recordType; //probably: 1=sleep, 2=deep sleep, 5=??run??ignored for now + + if (0 != (length % itemSize)) + return false;//malformed message? + + int recordCount = length / itemSize; + OverlayRecord[] overlayRecords = new OverlayRecord[recordCount]; + + for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { + beginOfRecordPosition = initialPosition + recordIdx * itemSize; + datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record + recordVersion = datalogMessage.getShort(); + if ((recordVersion != 1) && (recordVersion != 3)) + return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it + + datalogMessage.getShort();//throwaway, unknown + recordType = datalogMessage.getShort(); + + overlayRecords[recordIdx] = new OverlayRecord(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); + } + + return store(overlayRecords);//NACK if we cannot store the data yet, the watch will send the overlay records again. + } + + private boolean store(OverlayRecord[] overlayRecords) { + DBHandler dbHandler = null; + SampleProvider sampleProvider = new HealthSampleProvider(); + try { + dbHandler = GBApplication.acquireDB(); + int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); + for (OverlayRecord overlayRecord : overlayRecords) { + if (latestTimestamp < (overlayRecord.timestampStart + overlayRecord.durationSeconds)) + return false; + switch (overlayRecord.type) { + case 1: + dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); + break; + case 2: + dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider); + break; + default: + //TODO: other values refer to unknown activity types. + } + } + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } finally { + if (dbHandler != null) { + dbHandler.release(); + } + } + return true; + } + + private class OverlayRecord { + int type; //1=sleep, 2=deep sleep + int offsetUTC; //probably + int timestampStart; + int durationSeconds; + + public OverlayRecord(int type, int offsetUTC, int timestampStart, int durationSeconds) { + this.type = type; + this.offsetUTC = offsetUTC; + this.timestampStart = timestampStart; + this.durationSeconds = durationSeconds; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java index 7d06876fd..2162f9d50 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java @@ -1,7 +1,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; -import android.widget.Toast; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,71 +25,6 @@ class DatalogSessionHealthSleep extends DatalogSession { @Override public boolean handleMessage(ByteBuffer datalogMessage, int length) { LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length)); - switch (this.tag) { - case 83: - return handleMessage83(datalogMessage, length); - case 84: - return handleMessage84(datalogMessage, length); - default: - return false; - } - } - - private boolean handleMessage84(ByteBuffer datalogMessage, int length) { - int initialPosition = datalogMessage.position(); - int beginOfRecordPosition; - short recordVersion; //probably - short recordType; //probably: 1=sleep, 2=deep sleep - - if (0 != (length % itemSize)) - return false;//malformed message? - - int recordCount = length / itemSize; - SleepRecord84[] sleepRecords = new SleepRecord84[recordCount]; - - for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { - beginOfRecordPosition = initialPosition + recordIdx * itemSize; - datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record - recordVersion = datalogMessage.getShort(); - if (recordVersion != 1) - return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it - - datalogMessage.getShort();//throwaway, unknown - recordType = datalogMessage.getShort(); - - sleepRecords[recordIdx] = new SleepRecord84(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); - } - - return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. - } - - private boolean store84(SleepRecord84[] sleepRecords) { - DBHandler dbHandler = null; - SampleProvider sampleProvider = new HealthSampleProvider(); - try { - dbHandler = GBApplication.acquireDB(); - int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); - for (SleepRecord84 sleepRecord : sleepRecords) { - if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds)) - return false; - if (sleepRecord.type == 2) { - dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider); - } else { - dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); - } - - } - } catch (Exception ex) { - LOG.debug(ex.getMessage()); - } finally { - if (dbHandler != null) { - dbHandler.release(); - } - } - return true; - } - - private boolean handleMessage83(ByteBuffer datalogMessage, int length) { int initialPosition = datalogMessage.position(); int beginOfRecordPosition; short recordVersion; //probably @@ -100,7 +33,7 @@ class DatalogSessionHealthSleep extends DatalogSession { return false;//malformed message? int recordCount = length / itemSize; - SleepRecord83[] sleepRecords = new SleepRecord83[recordCount]; + SleepRecord[] sleepRecords = new SleepRecord[recordCount]; for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { beginOfRecordPosition = initialPosition + recordIdx * itemSize; @@ -109,23 +42,22 @@ class DatalogSessionHealthSleep extends DatalogSession { if (recordVersion != 1) return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it - sleepRecords[recordIdx] = new SleepRecord83(datalogMessage.getInt(), + sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); } - return store83(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. + return store(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. } - private boolean store83(SleepRecord83[] sleepRecords) { + private boolean store(SleepRecord[] sleepRecords) { DBHandler dbHandler = null; SampleProvider sampleProvider = new HealthSampleProvider(); - GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO); try { dbHandler = GBApplication.acquireDB(); int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); - for (SleepRecord83 sleepRecord : sleepRecords) { + for (SleepRecord sleepRecord : sleepRecords) { if (latestTimestamp < sleepRecord.bedTimeEnd) return false; dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); @@ -140,13 +72,13 @@ class DatalogSessionHealthSleep extends DatalogSession { return true; } - private class SleepRecord83 { + private class SleepRecord { int offsetUTC; //probably int bedTimeStart; int bedTimeEnd; int deepSleepSeconds; - public SleepRecord83(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) { + public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) { this.offsetUTC = offsetUTC; this.bedTimeStart = bedTimeStart; this.bedTimeEnd = bedTimeEnd; @@ -154,17 +86,4 @@ class DatalogSessionHealthSleep extends DatalogSession { } } - private class SleepRecord84 { - int type; //1=sleep, 2=deep sleep - int offsetUTC; //probably - int timestampStart; - int durationSeconds; - - public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) { - this.type = type; - this.offsetUTC = offsetUTC; - this.timestampStart = timestampStart; - this.durationSeconds = durationSeconds; - } - } } \ No newline at end of file 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 5d261e6a0..56863447b 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 @@ -1881,8 +1881,10 @@ public class PebbleProtocol extends GBDeviceProtocol { if (!mDatalogSessions.containsKey(id)) { if (uuid.equals(UUID_ZERO) && log_tag == 81) { mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size)); - } else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) { + } else if (uuid.equals(UUID_ZERO) && log_tag == 83) { mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size)); + } else if (uuid.equals(UUID_ZERO) && log_tag == 84) { + mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, log_tag, item_type, item_size)); } else { mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size)); } From f6979065729acc8aefe97f0792df1d2358cf3243 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 23 May 2016 22:03:21 +0200 Subject: [PATCH 2/5] update Japanese and German tranlation from transifex (thanks!) --- app/src/main/res/values-de/strings.xml | 5 +++++ app/src/main/res/values-ja/strings.xml | 3 +++ 2 files changed, 8 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 121df48e8..5baa517cd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -14,6 +14,7 @@ App Manager Löschen + Löschen und aus dem Zwischenspeicher entfernen Sperre für Benachrichtigungen @@ -44,6 +45,8 @@ Unterstützung für Anwendungen, die Benachrichtigungen per Intent an die Pebble schicken. Kann für Conversations verwendet werden. Andere Benachrichtigungen … auch wenn der Bildschirm an ist + Bitte nicht stören + Stoppe unerwünschte Nachrichten, wenn im \"Nicht Stören\"-Modus immer wenn der Bildschirm aus ist niemals @@ -202,6 +205,7 @@ Aktivitätsdaten auf dem Gerät lassen Inkompatible Firmware Diese Firmware ist nicht mit dem Gerät kompatibel + Wecker für zukünftige Ereignisse vormerken Verwende den Herzfrequenzsensor um die Schlaferkennung zu verbessern warte auf eingehende Verbindung Erneut installieren @@ -214,6 +218,7 @@ deaktivieren Authentifiziere Authentifizierung erforderlich + Konfigurieren Zzz Widget hinzufügen Gewünschte Schlafdauer in Stunden diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 864ce5412..f40138792 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,6 +14,7 @@ アプリ管理画面 削除 + キャッシュから削除 ステータス通知ブラックリスト @@ -44,6 +45,8 @@ インテント経由でPebbleに通知を送信するアプリケーションをサポートします。Conversationsに使用することができます。 一般ステータス通知対応 … スクリーンがオンのときにも + サイレント + サイレントモードに基づいて、送信される不要な通知を停止します。 いつも スクリーンがオフのとき なし From 24c51deaf9776131f5946ab5e57cc723311be1b4 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 23 May 2016 23:15:07 +0200 Subject: [PATCH 3/5] Pebble: also delete other files from cache when deleting .pbw --- .../activities/AppManagerActivity.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index de5147e99..9c0fd166d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -212,16 +212,25 @@ public class AppManagerActivity extends GBActivity { switch (item.getItemId()) { case R.id.appmanager_health_deactivate: case R.id.appmanager_app_delete_cache: - File cachedFile = null; - boolean deleteSuccess = true; + + + String baseName; try { - cachedFile = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw"); - deleteSuccess = cachedFile.delete(); + baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID(); } catch (IOException e) { LOG.warn("could not get external dir while trying to access pbw cache."); + return true; } - if (!deleteSuccess) { - LOG.warn("could not delete file from pbw cache: " + cachedFile.toString()); + + String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js"}; + + for (String suffix : suffixToDelete) { + File fileToDelete = new File(baseName + suffix); + if (!fileToDelete.delete()) { + LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString()); + } else { + LOG.info("deleted file: " + fileToDelete.toString()); + } } // fall through case R.id.appmanager_app_delete: From 884c4262cf1a3ce62df3b963bd9560b9284c1237 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 23 May 2016 23:37:08 +0200 Subject: [PATCH 4/5] update changelog, bump version (also change one German string and remove two newlines :P) --- CHANGELOG.md | 8 ++++++++ app/build.gradle | 4 ++-- .../gadgetbridge/activities/AppManagerActivity.java | 2 -- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/xml/changelog_master.xml | 8 ++++++++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf801f266..91ce3b0ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ ###Changelog +####Version 0.9.8 +* Pebble: fix more reconnnect issues +* Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health +* Pebble: option in AppManager to delete files from cache +* Pebble: enable pbw cache and watchface configuration for Firmware 2.x +* Pebble: allow enabling of Pebble Health without "untested features" being enabled +* Honour "Do Not Disturb" for phone calls and SMS + ####Version 0.9.7 * Pebble: hopefully fix some reconnect issues * Mi Band: fix live activity monitoring running forever if back button pressed diff --git a/app/build.gradle b/app/build.gradle index fffe7f3cb..c30ca034c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.9.7" - versionCode 51 + versionName "0.9.8" + versionCode 52 } buildTypes { release { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index 9c0fd166d..5f7e5af89 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -212,8 +212,6 @@ public class AppManagerActivity extends GBActivity { switch (item.getItemId()) { case R.id.appmanager_health_deactivate: case R.id.appmanager_app_delete_cache: - - String baseName; try { baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID(); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5baa517cd..1588000a9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -207,7 +207,7 @@ Diese Firmware ist nicht mit dem Gerät kompatibel Wecker für zukünftige Ereignisse vormerken Verwende den Herzfrequenzsensor um die Schlaferkennung zu verbessern - warte auf eingehende Verbindung + warte auf Verbindung Erneut installieren Über Dich Geburtsjahr diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 425e5c4a0..abe77ebae 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,13 @@ + + Pebble: fix more reconnnect issues + Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health + Pebble: option in AppManager to delete files from cache + Pebble: enable pbw cache and watchface configuration for Firmware 2.x + Pebble: allow enabling of Pebble Health without "untested features" being enabled + Honour "Do Not Disturb" for phone calls and SMS + Pebble: hopefully fix some reconnect issues Mi Band: fix live activity monitoring running forever if back button pressed From 30c37d3172ccc60e0de6212e1ba24728f5c62926 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 23 May 2016 23:46:54 +0200 Subject: [PATCH 5/5] Pebble: only remove apps from app list when they got deleted from cache also --- .../gadgetbridge/activities/AppManagerActivity.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index 5f7e5af89..f66bd7310 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -230,11 +230,10 @@ public class AppManagerActivity extends GBActivity { LOG.info("deleted file: " + fileToDelete.toString()); } } + removeAppFromList(selectedApp.getUUID()); // fall through case R.id.appmanager_app_delete: - UUID uuid = selectedApp.getUUID(); - GBApplication.deviceService().onAppDelete(uuid); - removeAppFromList(uuid); + GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); return true; case R.id.appmanager_app_reinstall: File cachePath;