mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Mi Band 10: Fix sleep parsing
This commit is contained in:
+4
@@ -129,4 +129,8 @@ public abstract class XiaomiActivityParser {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean validData(final byte[] header, final int i) {
|
||||
return (header[i / 8] & (1 << (7 - (i % 8)))) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
+56
-27
@@ -50,15 +50,26 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
// Seems to come both as DetailType.DETAILS (version 2) and DetailType.SUMMARY (version 4)
|
||||
if (fileId.getVersion() > 5) {
|
||||
LOG.warn("Unknown sleep details version {}", fileId.getVersion());
|
||||
return false;
|
||||
// Seems to come both as DetailType.DETAILS (version 2) and DetailType.SUMMARY (version 4, 5)
|
||||
final int version = fileId.getVersion();
|
||||
final int headerSize;
|
||||
switch (version) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
headerSize = 1;
|
||||
break;
|
||||
case 5:
|
||||
headerSize = 2;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown sleep details version {}", fileId.getVersion());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stores number of fields which are only present in certain versions of the message
|
||||
// this is required for correct header offset calculation
|
||||
int versionDependentFields = 0;
|
||||
// Current offset in the header, which only advances if we process a field available in the version
|
||||
int headerIdx = 0;
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.get(new byte[7]); // skip fileId bytes
|
||||
@@ -67,18 +78,24 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
|
||||
}
|
||||
|
||||
final byte header = buf.get();
|
||||
if (fileId.getVersion() > 4) {
|
||||
buf.get();
|
||||
}
|
||||
final byte[] header = new byte[headerSize];
|
||||
buf.get(header);
|
||||
|
||||
final int isAwake = buf.get() & 0xff; // 0/1 - more correctly this would be !isSleepFinish
|
||||
headerIdx++;
|
||||
|
||||
final int bedTime = buf.getInt();
|
||||
headerIdx++;
|
||||
|
||||
final int wakeupTime = buf.getInt();
|
||||
headerIdx++;
|
||||
|
||||
int sleepQuality = -1;
|
||||
if (fileId.getVersion() >= 4) {
|
||||
versionDependentFields += 1;
|
||||
sleepQuality = buf.get() & 0xff;
|
||||
if (validData(header, headerIdx)) {
|
||||
sleepQuality = buf.get() & 0xff;
|
||||
}
|
||||
headerIdx++;
|
||||
}
|
||||
|
||||
// note on RR-intervals (msg type 1) on Xiaomi band 9, FW 2.3.93, HW M2345B1:
|
||||
@@ -103,8 +120,15 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
sample.setWakeupTime(wakeupTime * 1000L);
|
||||
sample.setIsAwake(isAwake == 1);
|
||||
|
||||
if (fileId.getVersion() >= 5) {
|
||||
buf.get(new byte[9]); // ?
|
||||
final int bedTime2 = buf.getInt(); // around ~30 min before bedTime, is previous one fall asleep time?
|
||||
final int wakeupTime2 = buf.getInt(); // == wakeupTime
|
||||
headerIdx += 5;
|
||||
}
|
||||
|
||||
// Heart rate samples
|
||||
if ((header & (1 << (5 - versionDependentFields))) != 0) {
|
||||
if (validData(header, headerIdx)) {
|
||||
LOG.debug("Heart rate samples from offset {}", Integer.toHexString(buf.position()));
|
||||
final int unit = buf.getShort(); // Time unit (i.e sample rate)
|
||||
final int count = buf.getShort();
|
||||
@@ -120,9 +144,10 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
buf.position(buf.position() + count);
|
||||
}
|
||||
}
|
||||
headerIdx++;
|
||||
|
||||
// SpO2 samples
|
||||
if ((header & (1 << (4 - versionDependentFields))) != 0) {
|
||||
if (validData(header, headerIdx)) {
|
||||
LOG.debug("SpO₂ samples from offset {}", Integer.toHexString(buf.position()));
|
||||
final int unit = buf.getShort(); // Time unit (i.e sample rate)
|
||||
final int count = buf.getShort();
|
||||
@@ -138,23 +163,27 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
buf.position(buf.position() + count);
|
||||
}
|
||||
}
|
||||
headerIdx++;
|
||||
|
||||
// snore samples
|
||||
if (fileId.getVersion() >= 3 && (header & (1 << (3 - versionDependentFields))) != 0) {
|
||||
LOG.debug("Snore level samples from offset {}", Integer.toHexString(buf.position()));
|
||||
final int unit = buf.getShort(); // Time unit (i.e sample rate)
|
||||
final int count = buf.getShort();
|
||||
if (fileId.getVersion() >= 3) {
|
||||
if (validData(header, headerIdx)) {
|
||||
LOG.debug("Snore level samples from offset {}", Integer.toHexString(buf.position()));
|
||||
final int unit = buf.getShort(); // Time unit (i.e sample rate)
|
||||
final int count = buf.getShort();
|
||||
|
||||
if (count > 0) {
|
||||
// If version is less than 2 firstRecordTime is bedTime
|
||||
if (fileId.getVersion() >= 2) {
|
||||
final int firstRecordTime = buf.getInt();
|
||||
if (count > 0) {
|
||||
// If version is less than 2 firstRecordTime is bedTime
|
||||
if (fileId.getVersion() >= 2) {
|
||||
final int firstRecordTime = buf.getInt();
|
||||
}
|
||||
|
||||
// Skip count samples - each sample is a float
|
||||
// timestamp of each sample is firstRecordTime + (unit * index)
|
||||
buf.position(buf.position() + count * 4);
|
||||
}
|
||||
|
||||
// Skip count samples - each sample is a float
|
||||
// timestamp of each sample is firstRecordTime + (unit * index)
|
||||
buf.position(buf.position() + count * 4);
|
||||
}
|
||||
headerIdx++;
|
||||
}
|
||||
|
||||
final List<XiaomiSleepStageSample> stages = new ArrayList<>();
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ public class XiaomiSimpleActivityParser {
|
||||
|
||||
// Each bit in the header marks whether the data is valid or not, in order of the fields
|
||||
final boolean validData = (header[i / 8] & (1 << (7 - (i % 8)))) != 0;
|
||||
// FIXME: We can't use the header before identifying the correct field lenggths for unknown fields
|
||||
// FIXME: We can't use the header before identifying the correct field lengths for unknown fields
|
||||
// or parsing gets out of sync with the header and we will potentially ignore valid data
|
||||
//if (!validData) {
|
||||
// LOG.debug("Ignoring non-valid data {}", i);
|
||||
@@ -152,7 +152,7 @@ public class XiaomiSimpleActivityParser {
|
||||
|
||||
public static class Builder {
|
||||
private int headerSize;
|
||||
private List<XiaomiSimpleDataEntry> dataEntries = new ArrayList<>();
|
||||
private final List<XiaomiSimpleDataEntry> dataEntries = new ArrayList<>();
|
||||
|
||||
public Builder setHeaderSize(final int headerSize) {
|
||||
this.headerSize = headerSize;
|
||||
|
||||
Reference in New Issue
Block a user