Xiaomi-protobuf: Persist heart pulse samples

This commit is contained in:
José Rebelo 2024-12-01 13:34:27 +00:00
parent 95b9009318
commit 69847c19ff
3 changed files with 102 additions and 5 deletions

View File

@ -54,7 +54,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(90, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(91, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -81,6 +81,7 @@ public class GBDaoGenerator {
addHuamiSleepRespiratoryRateSample(schema, user, device);
addXiaomiActivitySample(schema, user, device);
addXiaomiSleepTimeSamples(schema, user, device);
addHeartPulseSamples(schema, user, device);
addXiaomiSleepStageSamples(schema, user, device);
addXiaomiManualSamples(schema, user, device);
addXiaomiDailySummarySamples(schema, user, device);
@ -410,6 +411,12 @@ public class GBDaoGenerator {
return sample;
}
private static Entity addHeartPulseSamples(Schema schema, Entity user, Entity device) {
Entity sample = addEntity(schema, "HeartPulseSample");
addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
return sample;
}
private static Entity addXiaomiSleepStageSamples(Schema schema, Entity user, Entity device) {
Entity sample = addEntity(schema, "XiaomiSleepStageSample");
addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);

View File

@ -0,0 +1,55 @@
/* Copyright (C) 2024 José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class HeartPulseSampleProvider extends AbstractTimeSampleProvider<HeartPulseSample> {
public HeartPulseSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<HeartPulseSample, ?> getSampleDao() {
return getSession().getHeartPulseSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return HeartPulseSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return HeartPulseSampleDao.Properties.DeviceId;
}
@Override
public HeartPulseSample createSample() {
return new HeartPulseSample();
}
}

View File

@ -30,10 +30,12 @@ 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.HeartPulseSampleProvider;
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.HeartPulseSample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
@ -140,6 +142,7 @@ public class SleepDetailsParser extends XiaomiActivityParser {
}
final List<XiaomiSleepStageSample> stages = new ArrayList<>();
final List<HeartPulseSample> heartPulseSamples = new ArrayList<>();
LOG.debug("Sleep stage packets from offset {}", Integer.toHexString(buf.position()));
// Do not crash if we face a buffer underflow, as the next parsing is not 100% fool-proof,
@ -181,7 +184,16 @@ public class SleepDetailsParser extends XiaomiActivityParser {
final ByteBuffer dataBuf = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
if (type == 16) {
if (type == 1) {
long timestampAcc = ts;
while (dataBuf.position() < dataBuf.limit()) {
final int delta = dataBuf.get() & 0xff;
final HeartPulseSample heartPulseSample = new HeartPulseSample();
heartPulseSample.setTimestamp(timestampAcc + delta);
heartPulseSamples.add(heartPulseSample);
timestampAcc += delta;
}
} else if (type == 16) {
final int data_0 = dataBuf.get() & 0xFF;
final int sleep_index = data_0 >> 4;
final int wake_count = data_0 & 0x0F;
@ -243,6 +255,8 @@ public class SleepDetailsParser extends XiaomiActivityParser {
summaries.add(sample);
}
boolean persistSuccess = !stagesParseFailed;
// save all the samples that we got
try (DBHandler handler = GBApplication.acquireDB()) {
final DaoSession session = handler.getDaoSession();
@ -271,7 +285,7 @@ public class SleepDetailsParser extends XiaomiActivityParser {
} 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;
persistSuccess = false;
}
if (!stagesParseFailed && !stages.isEmpty()) {
@ -295,11 +309,32 @@ public class SleepDetailsParser extends XiaomiActivityParser {
} 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;
persistSuccess = false;
}
}
return !stagesParseFailed;
// Save the heart pulse 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 HeartPulseSampleProvider sampleProvider = new HeartPulseSampleProvider(gbDevice, session);
for (final HeartPulseSample stageSample : heartPulseSamples) {
stageSample.setDevice(device);
stageSample.setUser(user);
}
sampleProvider.addSamples(heartPulseSamples);
} catch (final Exception e) {
GB.toast(support.getContext(), "Error saving heart pulse samples", Toast.LENGTH_LONG, GB.ERROR);
LOG.error("Error saving heart pulse samples", e);
persistSuccess = false;
}
return persistSuccess;
}
private static boolean readStagePacketHeader(final ByteBuffer buffer) {