diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java
index 1f9f46840..ec24dfa50 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTruSleepParser.java
@@ -16,10 +16,22 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class HuaweiTruSleepParser {
+ private static final Logger LOG = LoggerFactory.getLogger(HuaweiTruSleepParser.class);
+ public static final int TAG_ACC = 1;
+ public static final int TAG_PPG = 2;
public static class TruSleepStatus {
public final int startTime;
@@ -45,7 +57,307 @@ public class HuaweiTruSleepParser {
'}';
}
}
+ public static class TruSleepDataAcc {
+ public final int startTime;
+ public final short flags;
+ /*
+ Accelerometer (ACC):
+ Accelerometer data is sampled every 60 seconds, 24h a day and provides activity data on 3 axis.
+ Each axis activity is represented as byte, where 0x00 is no activity and 0xff is high activity.
+ The 3rd axis is ignored as it's always high (due to gravity??).
+ */
+ public TruSleepDataAcc(int startTime, short flags) {
+ this.startTime = startTime;
+ this.flags = flags;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ TruSleepDataAcc that = (TruSleepDataAcc) o;
+ if (startTime != that.startTime)
+ return false;
+ return flags == that.flags;
+ }
+
+ @Override
+ public String toString() {
+ return "TruSleepDataAcc{" +
+ "startTime=" + startTime +
+ ", flags=" + flags +
+ '}';
+ }
+ }
+
+ /*
+ Photoplethysmogram (PPG):
+ Every sample has a millisecond timestamp.
+ At night data is sampled every second, but at day it's sampled less often (about every 5 minutes).
+ */
+ public static class TruSleepDataPpg {
+ public final long startTime;
+ public final short flags;
+
+ TruSleepDataPpg(long startTime, short flags) {
+ this.startTime = startTime;
+ this.flags = flags;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ TruSleepDataPpg that = (TruSleepDataPpg) o;
+ if (startTime != that.startTime)
+ return false;
+ return flags == that.flags;
+ }
+
+ @Override
+ public String toString() {
+ return "TruSleepDataPpg{" +
+ "startTime=" + startTime +
+ ", flags=" + flags +
+ '}';
+ }
+ }
+ public static class TruSleepData {
+ public ArrayList dataACCs;
+ public ArrayList dataPPGs;
+
+ public TruSleepData() {
+ this.dataACCs = new ArrayList<>();
+ this.dataPPGs = new ArrayList<>();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ TruSleepData that = (TruSleepData) o;
+ if (!dataACCs.equals(that.dataACCs))
+ return false;
+ return dataPPGs.equals(that.dataPPGs);
+ }
+
+ @Override
+ public String toString() {
+ return "TruSleepData{" +
+ "dataPPGs=" + dataPPGs.toString() +
+ ", dataACCs=" + dataACCs.toString() +
+ '}';
+ }
+
+ private static final byte TAG_COMPRESSION_RAW = (byte)0xaa;
+ private static final byte TAG_COMPRESSION_COMP = (byte)0xbb;
+ private static final byte TAG_COMPRESSION_RESTART = (byte)0xff;
+
+ public void decodeAcc(byte[] data) throws IllegalArgumentException {
+ /*
+ Format:
+ - Timestamp (4 byte)
+ - Flags (3 bytes)
+ */
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ if (buffer.remaining() < 4)
+ throw new IllegalArgumentException("Timestamp is missing");
+
+ int startTime = buffer.getInt();
+ if (buffer.remaining() < 2)
+ throw new IllegalArgumentException("Flags are missing");
+
+ short flags = buffer.getShort();
+ dataACCs.add(new TruSleepDataAcc(startTime, flags));
+ }
+
+ public void decodePpg(byte[] data) throws IllegalArgumentException, IllegalStateException {
+ /*
+ Format:
+ - Timestamp (4 byte)
+ - Number of UINT16 (1 byte)
+ - Compression tag (1 byte)
+ - Compressed data
+ - Compression tag (1 byte)
+ - Compressed data
+ */
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ if (buffer.remaining() < 4)
+ throw new IllegalArgumentException("Timestamp is missing");
+
+ int startTime = buffer.getInt();
+
+ if (buffer.remaining() < 1)
+ throw new IllegalArgumentException("Count of short is missing");
+
+ byte countShort = buffer.get();
+
+ ArrayList peak = decodePeak(countShort, buffer);
+ ArrayList amp = decodeAmp(countShort, buffer);
+
+ // Sanity check
+ if (peak.size() != countShort || amp.size() != countShort)
+ throw new IllegalStateException("Decoded arrays have different length");
+
+ for (int i = 0; i < countShort; i++) {
+ dataPPGs.add(new TruSleepDataPpg((long)startTime * 1000 + peak.get(i) * 10, amp.get(i)));
+ }
+ LOG.debug("Buffer remaining {}", buffer.remaining());
+ }
+
+ private ArrayList decodePeak(byte countShort, ByteBuffer buffer) throws IllegalArgumentException {
+ if (countShort == 0)
+ throw new IllegalArgumentException("Number of short to generate is invalid");
+
+ if (buffer.remaining() < 1)
+ throw new IllegalArgumentException("Compression tag is missing");
+
+ byte tag = buffer.get();
+
+ ArrayList al = new ArrayList();
+ if (tag == TAG_COMPRESSION_RAW) {
+ if (buffer.remaining() < countShort * 2)
+ throw new IllegalArgumentException("Not enough elements in buffer");
+
+ while (countShort > 0) {
+ al.add(buffer.getShort());
+ countShort--;
+ }
+ } else if (tag == TAG_COMPRESSION_COMP) {
+ short working = buffer.getShort();
+ al.add(working);
+ countShort--;
+
+ while (countShort > 0) {
+ byte c = buffer.get();
+ if (c == TAG_COMPRESSION_RESTART) {
+ if (buffer.remaining() < 2)
+ throw new IllegalArgumentException("Not enough elements in buffer");
+ working = buffer.getShort();
+ } else {
+ working += c;
+ }
+ al.add(working);
+ countShort--;
+ }
+ } else {
+ throw new IllegalArgumentException("Compression " + String.format("%02x", tag) + " is unsupported");
+ }
+ return al;
+ }
+
+ public ArrayList decodeAmp(byte countShort, ByteBuffer buffer) throws IllegalArgumentException {
+ if (countShort == 0)
+ throw new IllegalArgumentException("Number of short to generate is invalid");
+
+ if (buffer.remaining() < 1)
+ throw new IllegalArgumentException("Compression tag is missing");
+
+ byte tag = buffer.get();
+
+ ArrayList al = new ArrayList();
+ if (tag == TAG_COMPRESSION_RAW) {
+ if (buffer.remaining() < countShort * 2)
+ throw new IllegalArgumentException("Not enough elements in buffer");
+
+ while (countShort > 0) {
+ al.add(buffer.getShort());
+ countShort--;
+ }
+ } else if (tag == TAG_COMPRESSION_COMP) {
+ if (buffer.remaining() < 2)
+ throw new IllegalArgumentException("Offset buffer is missing");
+
+ short offset = buffer.getShort();
+ short working = 0;
+ while (buffer.remaining() > 0) {
+ byte c = buffer.get();
+ if (c == TAG_COMPRESSION_RESTART) {
+ if (buffer.remaining() < 2)
+ throw new IllegalArgumentException("Raw buffer is missing");
+ working = (short)(offset + buffer.getShort());
+ } else {
+ working = (short)(offset + c);
+ }
+ al.add(working);
+ }
+ } else {
+ throw new IllegalArgumentException("Compression " + String.format("%02x", tag) + " is unsupported");
+ }
+ return al;
+ }
+
+ public void parsePpgData(byte[] data) {
+ LOG.debug("Decoding PPG: len= {}, data = {}", data.length, GB.hexdump(data));
+
+ try {
+ decodePpg(data);
+ } catch (Exception e) {
+ LOG.error("Failed to parse TrueSleep PPG data: {}", e.toString());
+ }
+ }
+ public void parseAccData(byte[] data) {
+ LOG.debug("Decoding ACC: len= {}, data = {}", data.length, GB.hexdump(data));
+
+ try {
+ decodeAcc(data);
+ } catch (Exception e) {
+ LOG.error("Failed to parse TrueSleep ACC data: {}", e.toString());
+ }
+ }
+ }
+
+ public static TruSleepData parseData(byte[] data) {
+ /*
+ Format:
+ - Zero padded
+ - ACC data interleaved with PPG data
+ - PPG data is compressed and has variable length
+ */
+ TruSleepData sleepData = new TruSleepData();
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ byte c;
+ while (buffer.remaining() > 0) {
+ c = buffer.get();
+
+ // Skip over padding
+ if (c != TAG_ACC && c != TAG_PPG)
+ continue;
+
+ // The tag identifies the data packet
+ if (c == TAG_ACC) {
+ if (buffer.remaining() < 1)
+ break;
+
+ // ACC has 1 byte length
+ byte length = buffer.get();
+ if (buffer.remaining() < length)
+ break;
+
+ byte[] accData = new byte[length];
+ buffer.get(accData);
+
+ sleepData.parseAccData(accData);
+ } else if (c == TAG_PPG) {
+ if (buffer.remaining() < 2)
+ break;
+
+ // PPG has 2 byte length
+ short length = buffer.getShort();
+ if (buffer.remaining() < length)
+ break;
+
+ byte[] ppgData = new byte[length];
+ buffer.get(ppgData);
+
+ sleepData.parsePpgData(ppgData);
+ }
+ }
+
+ return sleepData;
+ }
public static TruSleepStatus[] parseState(byte[] stateData) {
/*
Format:
@@ -63,6 +375,7 @@ public class HuaweiTruSleepParser {
while (stateData.length - buffer.position() >= 0x10) {
int startTime = buffer.getInt();
int endTime = buffer.getInt();
+
// Throw away for now because we don't know what it means, and we don't think we can implement this soon
buffer.get(); buffer.get(); buffer.get();
buffer.get(); buffer.get(); buffer.get(); buffer.get(); buffer.get();
@@ -71,4 +384,91 @@ public class HuaweiTruSleepParser {
}
return retv;
}
+
+ private static void analyzeOneTimeframe(HuaweiSupportProvider provider, TruSleepStatus status, ArrayList acc, ArrayList ppg) {
+ java.util.Date rangeStartTime = new java.util.Date((long)status.startTime*1000);
+ java.util.Date rangeEndTime = new java.util.Date((long)status.endTime*1000);
+ LOG.debug("FIXME: Analyse sleep range {} - {} not implemented", rangeStartTime, rangeEndTime);
+ //FIXME: Sleep analysis algorithm implemented here
+ for (HuaweiTruSleepParser.TruSleepDataAcc i : acc) {
+ java.util.Date startTime = new java.util.Date((long)i.startTime*1000);
+ LOG.debug("TruSleepDataAcc {} : {}", startTime, String.format("%04x", i.flags));
+ }
+ for (HuaweiTruSleepParser.TruSleepDataPpg i : ppg) {
+ java.util.Date startTime = new java.util.Date((long) i.startTime);
+ LOG.debug("TruSleepDataPpg {} : {}", startTime, String.format("%04x", i.flags));
+ }
+ }
+
+ public static void analyze(HuaweiSupportProvider provider, TruSleepStatus[] status, TruSleepData data) {
+
+ // Analyse each time range as specified by TruSleepStatus
+ for (TruSleepStatus s : status) {
+
+ ArrayList acc = new ArrayList<>();
+ ArrayList ppg = new ArrayList<>();
+
+ // Collect Accelerometer data for current time frame
+ for (TruSleepDataAcc i : data.dataACCs) {
+ if (i.startTime >= s.startTime && i.startTime <= s.endTime)
+ acc.add(i);
+ }
+
+ // Collect PPG data for current time frame
+ for (TruSleepDataPpg i : data.dataPPGs) {
+ if (i.startTime >= s.startTime && i.startTime <= s.endTime)
+ ppg.add(i);
+ }
+
+ // Analyse time frame
+ analyzeOneTimeframe(provider, s, acc, ppg);
+ }
+ }
+
+ public static class SleepFileDownloadCallback extends HuaweiFileDownloadManager.FileDownloadCallback {
+ private byte[] statusData;
+ private byte[] sleepData;
+
+ private boolean statusSynced;
+ private boolean dataSynced;
+ protected HuaweiSupportProvider provider;
+ public SleepFileDownloadCallback(HuaweiSupportProvider provider) {
+ this.dataSynced = false;
+ this.statusSynced = false;
+ this.provider = provider;
+ }
+
+ public void syncComplete(byte[] statusData, byte[] sleepData) { }
+ @Override
+ public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
+ if (e.fileRequest == null) {
+ LOG.error("Failed to download TruSleep file: {}", e.toString());
+ syncComplete(statusData, sleepData);
+ return;
+ }
+
+ if (e.fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_STATE) {
+ statusSynced = true;
+ } else if (e.fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_DATA) {
+ dataSynced = true;
+ }
+ if (statusSynced && dataSynced)
+ syncComplete(statusData, sleepData);
+ }
+
+ @Override
+ public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
+ if (fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_STATE) {
+ statusData = fileRequest.getData();
+ statusSynced = true;
+ } else if (fileRequest.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_DATA) {
+ sleepData = fileRequest.getData();
+ dataSynced = true;
+ }
+ LOG.debug("Downloaded TruSleep file {}", fileRequest.getFileType());
+ if (statusSynced && dataSynced)
+ syncComplete(statusData, sleepData);
+ }
+
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
index 00595a1ca..edbd15935 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
@@ -31,6 +31,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.ArrayList;
@@ -2379,30 +2381,37 @@ public class HuaweiSupportProvider {
public void onTestNewFunction() {
// Show to user
- gbDevice.setBusyTask("Downloading file...");
+ gbDevice.setBusyTask("Downloading files...");
gbDevice.sendDeviceUpdateIntent(getContext());
+ HuaweiTruSleepParser.SleepFileDownloadCallback callback = new HuaweiTruSleepParser.SleepFileDownloadCallback(this) {
+ @Override
+ public void syncComplete(byte[] statusData, byte[] sleepData) {
+ LOG.debug("Sync of TruSleep status and data finished");
+
+ if (statusData == null || statusData.length == 0)
+ return;
+
+ HuaweiTruSleepParser.TruSleepStatus[] results = HuaweiTruSleepParser.parseState(statusData);
+ if (results.length == 0)
+ return;
+
+ HuaweiTruSleepParser.TruSleepData data = HuaweiTruSleepParser.parseData(sleepData);
+ HuaweiTruSleepParser.analyze(this.provider, results, data);
+ }
+ };
+
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
0,
(int) (System.currentTimeMillis() / 1000),
- new HuaweiFileDownloadManager.FileDownloadCallback() {
- @Override
- public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
- // Handle file contents
- }
- }
+ callback
), true);
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepDataFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
0,
(int) (System.currentTimeMillis() / 1000),
- new HuaweiFileDownloadManager.FileDownloadCallback() {
- @Override
- public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
- // Handle file contents
- }
- }
+ callback
), true);
}
}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java
index 5aafe650b..f72442001 100644
--- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/TestHuaweiTruSleepParser.java
@@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import org.junit.Assert;
import org.junit.Test;
+import java.util.ArrayList;
+
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class TestHuaweiTruSleepParser {
@@ -36,4 +38,192 @@ public class TestHuaweiTruSleepParser {
Assert.assertArrayEquals(expected, result);
}
+ @Test
+ public void testParseDataPpg() {
+ byte[] test1 = GB.hexStringToByteArray("02B000403B0B672AAADA13C109A915E707BE130C06E3112104F80F84182F0888109900390805121004850E4317EC07C911D4037610C701580E2A02310F6B022B100E0448119B075B154F099916390A07191A0E9B00D10E2D02A7105405AAE8FDE8FDBDDA92B767943B71104EE52AB907EE06AC08BD06BE067C07DB06CC05E2079B06C206E906920639063807E0056406D905E2067807870660074D03720A8406AA0624064005CA07DF062D088706D2084B0A00");
+ byte[] test2 = GB.hexStringToByteArray("027500F61E0B6724BB0C016968685756585753545050535354565B595B5B5B5F5C5A5E5B61635D5E5B5756585656AADD401138452F7826FC1D61221F27DD2BFC1A7B248B11260A7A221E1C9128FE185025C827D41C1B206223E31B73126210EC22B620D212B232DC0A93115A322E279B116717F30E2E1500");
+ byte[] test3 = GB.hexStringToByteArray("025A00B2180B671BBB410174706A726F6D6D747B757B776E6E6D6E6F777D77777670757570AA543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00");
+
+ // TODO: Unlike PPGs with 0xBB compression the timestamps are not linear
+ // The data also looks different, hmmm....
+ ArrayList expected = new ArrayList<>();
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789362820L, (short)0xfde8));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789336970L, (short)0xfde8));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789367450L, (short)0xdabd));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789332230L, (short)0xb792));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789362540L, (short)0x9467));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789327480L, (short)0x713b));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789357790L, (short)0x4e10));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789322570L, (short)0x2ae5));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789352880L, (short)0x07b9));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789374760L, (short)0x06ee));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789332950L, (short)0x08ac));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789354320L, (short)0x06bd));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789313530L, (short)0x06be));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789333050L, (short)0x077c));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789358130L, (short)0x06db));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789322400L, (short)0x05cc));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789349170L, (short)0x07e2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789371550L, (short)0x069b));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789332280L, (short)0x06c2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789357530L, (short)0x06e9));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789321800L, (short)0x0692));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789354140L, (short)0x0639));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789316550L, (short)0x0738));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789348720L, (short)0x05e0));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789317540L, (short)0x0664));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789350890L, (short)0x05d9));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789318190L, (short)0x06e2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789353390L, (short)0x0778));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789322380L, (short)0x0687));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789356240L, (short)0x0760));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789331470L, (short)0x034d));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789366670L, (short)0x0a72));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789335830L, (short)0x0684));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789369850L, (short)0x06aa));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789338170L, (short)0x0624));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789376070L, (short)0x0540));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789348100L, (short)0x07ca));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789313550L, (short)0x06df));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789349930L, (short)0x082d));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789317570L, (short)0x0687));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789354630L, (short)0x08d2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728789325640L, (short)0x0a4b));
+
+ HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test1);
+ Assert.assertEquals(expected, result.dataPPGs);
+
+ expected = new ArrayList<>();
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782072680L, (short)0x40dd));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782073730L, (short)0x3811));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782074770L, (short)0x2f45));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782075810L, (short)0x2678));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782076680L, (short)0x1dfc));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782077540L, (short)0x2261));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782078420L, (short)0x271f));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782079290L, (short)0x2bdd));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782080120L, (short)0x1afc));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782080960L, (short)0x247b));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782081760L, (short)0x118b));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782082560L, (short)0x0a26));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782083390L, (short)0x227a));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782084220L, (short)0x1c1e));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782085060L, (short)0x2891));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782085920L, (short)0x18fe));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782086830L, (short)0x2550));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782087720L, (short)0x27c8));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782088630L, (short)0x1cd4));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782089540L, (short)0x201b));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782090450L, (short)0x2362));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782091400L, (short)0x1be3));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782092320L, (short)0x1273));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782093220L, (short)0x1062));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782094160L, (short)0x22ec));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782095070L, (short)0x20b6));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782096040L, (short)0x12d2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782097030L, (short)0x32b2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782097960L, (short)0x0adc));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782098900L, (short)0x1193));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782099810L, (short)0x325a));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782100680L, (short)0x272e));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782101540L, (short)0x119b));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782102420L, (short)0x1767));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782103280L, (short)0x0ef3));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728782104140L, (short)0x152e));
+
+ result = HuaweiTruSleepParser.parseData(test2);
+ Assert.assertEquals(expected, result.dataPPGs);
+
+ expected = new ArrayList<>();
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780469210L, (short)0x3d54));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780470370L, (short)0x520e));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780471490L, (short)0x6927));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780472550L, (short)0x4056));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780473690L, (short)0x3768));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780474800L, (short)0x3cb6));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780475890L, (short)0x3e10));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780476980L, (short)0x2cb2));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780478140L, (short)0x2969));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780479370L, (short)0x26c7));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780480540L, (short)0x2653));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780481770L, (short)0x2fea));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780482960L, (short)0x2fcc));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780484060L, (short)0x3c74));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780485160L, (short)0x38c9));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780486250L, (short)0x3902));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780487350L, (short)0x3b67));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780488460L, (short)0x3997));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780489650L, (short)0x2fea));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780490900L, (short)0x2cc9));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780492090L, (short)0x26c5));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780493280L, (short)0x2ebb));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780494460L, (short)0x2d65));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780495580L, (short)0x3364));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780496750L, (short)0x35dd));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780497920L, (short)0x3906));
+ expected.add(new HuaweiTruSleepParser.TruSleepDataPpg (1728780499040L, (short)0x2f7e));
+
+ result = HuaweiTruSleepParser.parseData(test3);
+ Assert.assertEquals(expected, result.dataPPGs);
+ }
+
+ @Test
+ public void testParseDataAcc() {
+ byte[] test = GB.hexStringToByteArray("0107F02A0B670000B6");
+
+ ArrayList expected = new ArrayList<>();
+ expected.add(new HuaweiTruSleepParser.TruSleepDataAcc (0x670b2af0, (short)0x0000));
+
+ HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test);
+ Assert.assertEquals(0, result.dataPPGs.size());
+ Assert.assertEquals(expected, result.dataACCs);
+ }
+
+ @Test
+ public void testParsePadding() {
+ byte[] test1 = GB.hexStringToByteArray("000107F02A0B670000B6");
+ byte[] test2 = GB.hexStringToByteArray("00000000000107F02A0B670000B6");
+
+ ArrayList expected = new ArrayList<>();
+ expected.add(new HuaweiTruSleepParser.TruSleepDataAcc (0x670b2af0, (short)0x0000));
+
+ HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test1);
+
+ Assert.assertEquals(0, result.dataPPGs.size());
+ Assert.assertEquals(expected, result.dataACCs);
+
+ result = HuaweiTruSleepParser.parseData(test2);
+
+ Assert.assertEquals(0, result.dataPPGs.size());
+ Assert.assertEquals(expected, result.dataACCs);
+ }
+
+ @Test
+ public void testParseFailure() {
+ ArrayList tests = new ArrayList();
+ // Length shorter than expected
+ tests.add(GB.hexStringToByteArray("0105F02A0B670000B6"));
+ // Zero length
+ tests.add(GB.hexStringToByteArray("0100F02A0B670000B6"));
+ // Length bigger than input data
+ tests.add(GB.hexStringToByteArray("0108F02A0B670000B6"));
+
+ // Length bigger than input data
+ tests.add(GB.hexStringToByteArray("027600F61E0B6724BB0C016968685756585753545050535354565B595B5B5B5F5C5A5E5B61635D5E5B5756585656AADD401138452F7826FC1D61221F27DD2BFC1A7B248B11260A7A221E1C9128FE185025C827D41C1B206223E31B73126210EC22B620D212B232DC0A93115A322E279B116717F30E2E1500"));
+ // Number of UINT16 less than provided
+ tests.add(GB.hexStringToByteArray("027500F61E0B6723BB0C016968685756585753545050535354565B595B5B5B5F5C5A5E5B61635D5E5B5756585656AADD401138452F7826FC1D61221F27DD2BFC1A7B248B11260A7A221E1C9128FE185025C827D41C1B206223E31B73126210EC22B620D212B232DC0A93115A322E279B116717F30E2E1500"));
+ // Wrong compression tag
+ tests.add(GB.hexStringToByteArray("025A00B2180B671BCC410174706A726F6D6D747B757B776E6E6D6E6F777D77777670757570AA543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00"));
+ // Wrong compression tag 2
+ tests.add(GB.hexStringToByteArray("025A00B2180B671BBB410174706A726F6D6D747B757B776E6E6D6E6F777D77777670757570DD543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00"));
+ // Wrong restart tags in compressed data
+ tests.add(GB.hexStringToByteArray("025A00B2180B671BBB410FFFFFFA726F6D6D747B757B776E6E6D6E6F777D77777670757570AA543D0E52276956406837B63C103EB22C6929C7265326EA2FCC2F743CC9380239673B9739EA2FC92CC526BB2E652D6433DD3506397E2F00"));
+
+ for (byte[] test: tests) {
+ HuaweiTruSleepParser.TruSleepData result = HuaweiTruSleepParser.parseData(test);
+
+ Assert.assertEquals("Test input data " + GB.hexdump(test), 0, result.dataPPGs.size());
+ Assert.assertEquals("Test input data " + GB.hexdump(test),0, result.dataACCs.size());
+ }
+ }
}