mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Xiaomi: Implement sleep stage parsing
This allows sleep stage detection to work by parsing some of the data sent in SleepDetails. It's still missing parsing the summary contained inside SleepDetails. and decoding the large amount of other mostly unknown data.
This commit is contained in:
parent
1e1f0014c7
commit
e06b2e1f95
@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -23,13 +24,18 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepStageSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepTimeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
@ -60,6 +66,68 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
sample.setWakeupTime(wakeupTime * 1000L);
|
||||
sample.setIsAwake(isAwake == 1);
|
||||
|
||||
// SleepAssistItemInfo 2x
|
||||
// - 0: Heart rate samples
|
||||
// - 1: Sp02 samples
|
||||
for (int i = 0; i < 2; i++) {
|
||||
final int unit = buf.getShort(); // Time unit (i.e sample rate)
|
||||
final int count = buf.getShort();
|
||||
final int firstRecordTime = buf.getInt();
|
||||
|
||||
// Skip count samples - each sample is a u8
|
||||
// timestamp of each sample is firstRecordTime + (unit * index)
|
||||
buf.position(buf.position() + count);
|
||||
}
|
||||
|
||||
final List<XiaomiSleepStageSample> stages = new ArrayList<>();
|
||||
|
||||
|
||||
while (buf.remaining() >= 17 && buf.getInt() == 0xFFFCFAFB) {
|
||||
final int headerLen = buf.get() & 0xFF; // this seems to always be 17
|
||||
|
||||
// This timestamp is kind of weird, is seems to sometimes be in seconds
|
||||
// and other times in nanoseconds. Message types 16 and 17 are in seconds
|
||||
final long ts = buf.getLong();
|
||||
final int unk = buf.get() & 0xFF;
|
||||
final int type = buf.get() & 0xFF;
|
||||
|
||||
final int dataLen = ((buf.get() & 0xFF) << 8) | (buf.get() & 0xFF);
|
||||
|
||||
final byte[] data = new byte[dataLen];
|
||||
buf.get(data);
|
||||
|
||||
final ByteBuffer dataBuf = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
// Known types:
|
||||
// - acc_unk = 0,
|
||||
// - ppg_unk = 1,
|
||||
// - fall_asleep = 2,
|
||||
// - wake_up = 3,
|
||||
// - switch_ts_unk1 = 12,
|
||||
// - switch_ts_unk2 = 13,
|
||||
// - Summary = 16,
|
||||
// - Stages = 17
|
||||
|
||||
if (type == 17) { // Stages
|
||||
long currentTime = ts * 1000;
|
||||
for (int i = 0; i < dataLen / 2; i++) {
|
||||
// when the change to the phase occurs
|
||||
final int val = dataBuf.getShort() & 0xFFFF;
|
||||
|
||||
final int stage = val >> 12;
|
||||
final int offsetMinutes = val & 0xFFF;
|
||||
|
||||
final XiaomiSleepStageSample stageSample = new XiaomiSleepStageSample();
|
||||
stageSample.setTimestamp(currentTime);
|
||||
stageSample.setStage(decodeStage(stage));
|
||||
stages.add(stageSample);
|
||||
|
||||
currentTime += offsetMinutes * 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// save all the samples that we got
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
@ -83,12 +151,50 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
}
|
||||
|
||||
sampleProvider.addSample(sample);
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving sleep sample", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the sleep stage samples
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
final XiaomiSleepStageSampleProvider sampleProvider = new XiaomiSleepStageSampleProvider(gbDevice, session);
|
||||
|
||||
for (final XiaomiSleepStageSample stageSample : stages) {
|
||||
stageSample.setDevice(device);
|
||||
stageSample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(stages);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving sleep stage samples", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static private int decodeStage(int rawStage) {
|
||||
switch (rawStage) {
|
||||
case 0:
|
||||
return 5; // AWAKE
|
||||
case 1:
|
||||
return 3; // LIGHT_SLEEP
|
||||
case 2:
|
||||
return 2; // DEEP_SLEEP
|
||||
case 3:
|
||||
return 4; // REM_SLEEP
|
||||
case 4:
|
||||
return 0; // NOT_SLEEP
|
||||
default:
|
||||
return 1; // N/A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user