1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-23 18:36:50 +01:00

huawei: Decode TruSleep data in test new functionality

Download TruSleep data and extract ACC and PPG packets.
For now the data is only extracted and provided to an analysis
function that does nothing as printing the data as the TruSleep
algorithm is unknown.

With this patch it's possible to do sleep analysis using open source
algorithms, however it's not clear which one to use and how similar
the results would be. A possible use case would be Sleep as Android.

The extracted data contains of Accelerometer data (ACC) and
photoplethysmogram (PPG) data.

ACC packets:
- about every 60 seconds
- contain a timestamp
- contain the activity on one accelerometer axis
  0x00: no activity
  0xff: high activity (like walking)

PPG packets:
- contain a timestamp
- contain raw PPG samples, each with a 10msec based timestamp

This commit allows more developers and researchers to look into sleep
analysis as they gain access to the raw sleep data optained from the watch.

For issue #3860.

Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
This commit is contained in:
Patrick Rudolph 2024-10-12 12:51:40 +02:00 committed by José Rebelo
parent ce7b0db5b1
commit 42b7153162
3 changed files with 612 additions and 13 deletions

View File

@ -16,10 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
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<TruSleepDataAcc> dataACCs;
public ArrayList<TruSleepDataPpg> 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<Short> peak = decodePeak(countShort, buffer);
ArrayList<Short> 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<Short> 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<Short> al = new ArrayList<Short>();
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<Short> 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<Short> al = new ArrayList<Short>();
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<TruSleepDataAcc> acc, ArrayList<TruSleepDataPpg> 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<TruSleepDataAcc> acc = new ArrayList<>();
ArrayList<TruSleepDataPpg> 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);
}
}
}

View File

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

View File

@ -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<HuaweiTruSleepParser.TruSleepDataPpg> 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<HuaweiTruSleepParser.TruSleepDataAcc> 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<HuaweiTruSleepParser.TruSleepDataAcc> 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<byte[]> tests = new ArrayList<byte[]>();
// 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());
}
}
}