Yawell: use persistSamples and improve timestamp logging

This commit is contained in:
Thomas Kuehne
2026-05-31 17:12:22 +00:00
parent 92619bb03f
commit 081403d83c
5 changed files with 114 additions and 123 deletions
@@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
@@ -21,6 +21,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Collections;
import java.util.List;
public interface PersistanceProvider<T> {
@@ -31,4 +32,9 @@ public interface PersistanceProvider<T> {
/// @param context optional context for displaying error messages
/// @return {@code false} if at least one sample couldn't be persisted.
boolean persistSamples(@NonNull List<T> samples, @Nullable Context context);
/// @see #persistSamples(List, Context)
default boolean persistSamples(@NonNull T sample, @Nullable Context context) {
return persistSamples(Collections.singletonList(sample), context);
}
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2024 Arjan Schrijver
/* Copyright (C) 2024-2026 Arjan Schrijver, Thomas Kuehne
This file is part of Gadgetbridge.
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.yawell.ring;
import static nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils.formatIso8601;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
@@ -53,8 +55,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSpo2Sample;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiStressSample;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiTemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
@@ -63,9 +63,13 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.yawell.ring.YawellRi
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class YawellRingPacketHandler {
public final class YawellRingPacketHandler {
private static final Logger LOG = LoggerFactory.getLogger(YawellRingPacketHandler.class);
private YawellRingPacketHandler() {
throw new UnsupportedOperationException("only static members");
}
public static void hrIntervalSettings(YawellRingDeviceSupport support, byte[] value) {
if (value[1] == YawellRingConstants.PREF_WRITE) return; // ignore empty response when writing setting
boolean enabled = value[2] == 0x01;
@@ -132,7 +136,9 @@ public class YawellRingPacketHandler {
LOG.info("Received goals preferences: {} steps, {} calories, {}m distance, {}min sport, {}min sleep", steps, calories, distance, sport, sleep);
}
public static void liveHeartRate(GBDevice device, Context context, byte[] value) {
public static void liveHeartRate(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final byte[] value) {
int errorCode = value[2];
int hrResponse = value[3] & 0xff;
switch (errorCode) {
@@ -155,14 +161,10 @@ public class YawellRingPacketHandler {
try (DBHandler db = GBApplication.acquireDB()) {
// Build sample object and save in database
ColmiHeartRateSampleProvider sampleProvider = new ColmiHeartRateSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
ColmiHeartRateSample gbSample = new ColmiHeartRateSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(Calendar.getInstance().getTimeInMillis());
gbSample.setHeartRate(hrResponse);
sampleProvider.addSample(gbSample);
sampleProvider.persistSamples(gbSample, context);
// Send local intent with sample for listeners like the heart rate dialog
Intent liveIntent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES);
liveIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
@@ -175,7 +177,10 @@ public class YawellRingPacketHandler {
}
}
public static void realtimeHeartRate(GBDevice device, Context context, YawellRingLiveActivityContext hrmContext, byte[] value) {
public static void realtimeHeartRate(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final YawellRingLiveActivityContext hrmContext,
@NonNull final byte[] value) {
int hrResponse = value[1] & 0xff;
LOG.info("Received realtime heart rate response: {} bpm", hrResponse);
@@ -192,25 +197,18 @@ public class YawellRingPacketHandler {
if (hrResponse > 0) {
// Build sample object, send intent and save in database
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
// Build heart rate sample object and save in database
ColmiHeartRateSampleProvider heartRateSampleProvider = new ColmiHeartRateSampleProvider(device, db.getDaoSession());
ColmiHeartRateSample heartRateSample = heartRateSampleProvider.createSample();
heartRateSample.setDeviceId(deviceId);
heartRateSample.setUserId(userId);
heartRateSample.setTimestamp(calendar.getTimeInMillis());
heartRateSample.setHeartRate(hrResponse);
heartRateSampleProvider.persistSamples(heartRateSample, context);
// Send local intent with sample for listeners like the live activity tab
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(GBDevice.EXTRA_DEVICE, device)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, heartRateSample);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
// Save heart rate sample to the database
heartRateSampleProvider.addSample(heartRateSample);
} catch (Exception e) {
LOG.error("Error acquiring database for recording heart rate samples", e);
}
@@ -248,7 +246,9 @@ public class YawellRingPacketHandler {
}
@NonNull
public static Runnable liveActivityPulse(GBDevice device, Context context, YawellRingLiveActivityContext liveActivityContext) {
public static Runnable liveActivityPulse(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final YawellRingLiveActivityContext liveActivityContext) {
return () -> {
Calendar calendar = Calendar.getInstance();
int sampleTimestamp = (int) (calendar.getTimeInMillis() / 1000);
@@ -286,7 +286,9 @@ public class YawellRingPacketHandler {
};
}
public static void historicalActivity(GBDevice device, Context context, byte[] value) {
public static void historicalActivity(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final byte[] value) {
if ((value[1] & 0xff) == 0xff) {
device.unsetBusyTask();
device.sendDeviceUpdateIntent(context);
@@ -309,22 +311,17 @@ public class YawellRingPacketHandler {
int calories = BLETypeConversions.toUint16(value[7], value[8]);
int steps = BLETypeConversions.toUint16(value[9], value[10]);
int distance = BLETypeConversions.toUint16(value[11], value[12]);
LOG.info("Received activity sample: {} - {} calories, {} steps, {} distance", sampleCal.getTime(), calories, steps, distance);
LOG.info("Received activity sample: {} - {} calories, {} steps, {} distance", formatIso8601(sampleCal), calories, steps, distance);
// Build sample object and save in database
try (DBHandler db = GBApplication.acquireDB()) {
ColmiActivitySampleProvider sampleProvider = new ColmiActivitySampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
ColmiActivitySample gbSample = sampleProvider.createActivitySample();
gbSample.setProvider(sampleProvider);
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setRawKind(ActivityKind.ACTIVITY.getCode());
gbSample.setTimestamp((int) (sampleCal.getTimeInMillis() / 1000));
gbSample.setCalories(calories);
gbSample.setSteps(steps);
gbSample.setDistance(distance);
sampleProvider.addGBActivitySample(gbSample);
sampleProvider.persistSamples(gbSample, context);
} catch (Exception e) {
LOG.error("Error acquiring database for recording activity samples", e);
}
@@ -338,8 +335,10 @@ public class YawellRingPacketHandler {
}
}
public static void historicalStress(GBDevice device, Context context, byte[] value) {
ArrayList<ColmiStressSample> stressSamples = new ArrayList<>();
public static void historicalStress(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final byte[] value) {
final ArrayList<ColmiStressSample> stressSamples = new ArrayList<>();
int stressPacketNr = value[1] & 0xff;
if (stressPacketNr == 0xff) {
device.unsetBusyTask();
@@ -359,30 +358,24 @@ public class YawellRingPacketHandler {
minutesInPreviousPackets += (stressPacketNr - 2) * 13 * 30; // 13 values per packet
}
for (int i = startValue; i < value.length - 1; i++) {
if (value[i] != 0x00) {
final int stress = value[i] & 0xFF;
if (stress != 0x00) {
// Determine time of day
int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 30;
sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
LOG.info("Stress level is {} at {}", value[i] & 0xff, sampleCal.getTime());
LOG.info("Stress level is {} at {}", stress, formatIso8601(sampleCal));
// Build sample object and save in database
ColmiStressSample gbSample = new ColmiStressSample();
gbSample.setTimestamp(sampleCal.getTimeInMillis());
gbSample.setStress(value[i] & 0xff);
gbSample.setStress(stress);
stressSamples.add(gbSample);
}
}
if (!stressSamples.isEmpty()) {
try (DBHandler db = GBApplication.acquireDB()) {
ColmiStressSampleProvider sampleProvider = new ColmiStressSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
for (final ColmiStressSample sample : stressSamples) {
sample.setDeviceId(deviceId);
sample.setUserId(userId);
}
LOG.info("Will persist {} stress samples", stressSamples.size());
sampleProvider.addSamples(stressSamples);
sampleProvider.persistSamples(stressSamples, context);
} catch (Exception e) {
LOG.error("Error acquiring database for recording stress samples", e);
}
@@ -394,7 +387,9 @@ public class YawellRingPacketHandler {
}
}
public static void historicalSpo2(GBDevice device, byte[] value) {
public static void historicalSpo2(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final byte[] value) {
ArrayList<ColmiSpo2Sample> spo2Samples = new ArrayList<>();
int length = BLETypeConversions.toUint16(value[2], value[3]);
int index = 6; // start of data (day nr, followed by values)
@@ -428,21 +423,16 @@ public class YawellRingPacketHandler {
if (!spo2Samples.isEmpty()) {
try (DBHandler db = GBApplication.acquireDB()) {
ColmiSpo2SampleProvider sampleProvider = new ColmiSpo2SampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
for (final ColmiSpo2Sample sample : spo2Samples) {
sample.setDeviceId(deviceId);
sample.setUserId(userId);
}
LOG.info("Will persist {} SpO2 samples", spo2Samples.size());
sampleProvider.addSamples(spo2Samples);
sampleProvider.persistSamples(spo2Samples, context);
} catch (Exception e) {
LOG.error("Error acquiring database for recording SpO2 samples", e);
}
}
}
public static void historicalSleep(GBDevice gbDevice, Context context, byte[] value) {
public static void historicalSleep(@NonNull final GBDevice gbDevice,
@NonNull final Context context,
@NonNull final byte[] value) {
int packetLength = BLETypeConversions.toUint16(value[2], value[3]);
if (packetLength < 2) {
LOG.info("Received empty sleep data packet: {}", StringUtils.bytesToHex(value));
@@ -484,7 +474,7 @@ public class YawellRingPacketHandler {
sessionEnd.set(Calendar.MINUTE, sleepEnd);
sessionEnd.set(Calendar.SECOND, 0);
sessionEnd.set(Calendar.MILLISECOND, 0);
LOG.info("Sleep session starts at {} and ends at {}", sessionStart.getTime(), sessionEnd.getTime());
LOG.info("Sleep session starts at {} and ends at {}", formatIso8601(sessionStart), formatIso8601(sessionEnd));
// Build sample object to persist
final ColmiSleepSessionSample sessionSample = new ColmiSleepSessionSample();
sessionSample.setTimestamp(sessionStart.getTimeInMillis());
@@ -499,14 +489,14 @@ public class YawellRingPacketHandler {
sample.setDuration(value[index + 1]);
sample.setStage(value[index]);
if (sleepMinutes > 0) {
LOG.info("Sleep stage type={} starts at {} and lasts for {} minutes", value[index], sleepStage.getTime(), sleepMinutes);
LOG.info("Sleep stage type={} starts at {} and lasts for {} minutes", value[index], formatIso8601(sleepStage.getTime()), sleepMinutes);
if (sleepStage.getTimeInMillis() + sleepMinutes * 60 * 1000 > sessionEnd.getTimeInMillis()) {
LOG.warn("Warning: sleep stage exceeds end of sleep session, received data may be corrupt");
}
stageSamples.add(sample);
sleepStage.add(Calendar.MINUTE, sleepMinutes);
} else {
LOG.info("Ignoring sleep stage type={} starts at {} and lasts for {} minutes", value[index], sleepStage.getTime(), sleepMinutes);
LOG.info("Ignoring sleep stage type={} starts at {} and lasts for {} minutes", value[index], formatIso8601(sleepStage.getTime()), sleepMinutes);
}
// Prepare for next sample
index += 2;
@@ -515,35 +505,18 @@ public class YawellRingPacketHandler {
try (DBHandler handler = GBApplication.acquireDB()) {
final DaoSession session = handler.getDaoSession();
final Device device = DBHelper.getDevice(gbDevice, session);
final User user = DBHelper.getUser(session);
final ColmiSleepSessionSampleProvider sampleProvider = new ColmiSleepSessionSampleProvider(gbDevice, session);
sessionSample.setDevice(device);
sessionSample.setUser(user);
LOG.debug("Will persist 1 sleep session sample from {} to {}", sessionSample.getTimestamp(), sessionSample.getWakeupTime());
sampleProvider.addSample(sessionSample);
sampleProvider.persistSamples(sessionSample, context);
} catch (final Exception e) {
GB.toast(context, "Error saving sleep session sample", Toast.LENGTH_LONG, GB.ERROR, e);
}
// Persist sleep stages
try (DBHandler handler = GBApplication.acquireDB()) {
final DaoSession session = handler.getDaoSession();
final Device device = DBHelper.getDevice(gbDevice, session);
final User user = DBHelper.getUser(session);
final ColmiSleepStageSampleProvider sampleProvider = new ColmiSleepStageSampleProvider(gbDevice, session);
for (final ColmiSleepStageSample sample : stageSamples) {
sample.setDevice(device);
sample.setUser(user);
}
LOG.debug("Will persist {} sleep stage samples", stageSamples.size());
sampleProvider.addSamples(stageSamples);
sampleProvider.persistSamples(stageSamples, context);
} catch (final Exception e) {
GB.toast(context, "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR, e);
}
@@ -551,7 +524,10 @@ public class YawellRingPacketHandler {
}
}
public static void historicalHRV(GBDevice device, Context context, byte[] value, int daysAgo) {
public static void historicalHRV(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final byte[] value,
final int daysAgo) {
LOG.info("Received HRV history sync packet: {}", StringUtils.bytesToHex(value));
int hrvPacketNr = value[1] & 0xff;
if (hrvPacketNr == 0xff) {
@@ -577,29 +553,32 @@ public class YawellRingPacketHandler {
minutesInPreviousPackets = 12 * 30; // packet 1
minutesInPreviousPackets += (hrvPacketNr - 2) * 13 * 30;
}
final List<ColmiHrvValueSample> samples = new ArrayList<>(value.length);
for (int i = startValue; i < value.length - 1; i++) {
if (value[i] != 0x00) {
final int hrv = value[i] & 0xff;
if (hrv != 0x00) {
// Determine time of day
int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 30;
sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
LOG.info("Value {} is {} ms, time of day is {}", i, value[i] & 0xff, sampleCal.getTime());
LOG.info("Value {} is {} ms, time of day is {}", i, hrv, formatIso8601(sampleCal));
// Build sample object and save in database
ColmiHrvValueSample gbSample = new ColmiHrvValueSample();
gbSample.setTimestamp(sampleCal.getTimeInMillis());
gbSample.setValue(hrv);
samples.add(gbSample);
}
}
// Save samples in database
try (DBHandler db = GBApplication.acquireDB()) {
ColmiHrvValueSampleProvider sampleProvider = new ColmiHrvValueSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
ColmiHrvValueSample gbSample = new ColmiHrvValueSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(sampleCal.getTimeInMillis());
gbSample.setValue(value[i] & 0xff);
sampleProvider.addSample(gbSample);
sampleProvider.persistSamples(samples, context);
} catch (Exception e) {
LOG.error("Error acquiring database for recording HRV samples", e);
}
}
}
if (hrvPacketNr == 4) {
device.unsetBusyTask();
device.sendDeviceUpdateIntent(context);
@@ -607,8 +586,10 @@ public class YawellRingPacketHandler {
}
}
public static void historicalTemperature(GBDevice device, byte[] value) {
ArrayList<ColmiTemperatureSample> temperatureSamples = new ArrayList<>();
public static void historicalTemperature(@NonNull final GBDevice device,
@NonNull final Context context,
@NonNull final byte[] value) {
final ArrayList<ColmiTemperatureSample> temperatureSamples = new ArrayList<>();
int length = BLETypeConversions.toUint16(value[2], value[3]);
if (length < 50) {
LOG.info("Received temperature data packet with length {} while expecting 50. Will not try to parse it.", length);
@@ -633,18 +614,20 @@ public class YawellRingPacketHandler {
float temp_30 = value[index] & 0xff;
index++;
if (temp_00 > 0) {
temp_00 = (temp_00 / 10) + 20;
LOG.info("Received temperature data from {} days ago at {}:00: {} °C", days_ago, hour, temp_00);
ColmiTemperatureSample temperatureSample = new ColmiTemperatureSample();
temperatureSample.setTimestamp(syncingDay.getTimeInMillis());
temperatureSample.setTemperature((temp_00 / 10) + 20);
temperatureSample.setTemperature(temp_00);
temperatureSamples.add(temperatureSample);
}
syncingDay.set(Calendar.MINUTE, 30);
if (temp_30 > 0) {
temp_30 = (temp_30 / 10) + 20;
LOG.info("Received temperature data from {} days ago at {}:30: {} °C", days_ago, hour, temp_30);
ColmiTemperatureSample temperatureSample = new ColmiTemperatureSample();
temperatureSample.setTimestamp(syncingDay.getTimeInMillis());
temperatureSample.setTemperature((temp_30 / 10) + 20);
temperatureSample.setTemperature(temp_30);
temperatureSamples.add(temperatureSample);
}
if (index - 6 >= length) {
@@ -655,14 +638,7 @@ public class YawellRingPacketHandler {
if (!temperatureSamples.isEmpty()) {
try (DBHandler db = GBApplication.acquireDB()) {
ColmiTemperatureSampleProvider sampleProvider = new ColmiTemperatureSampleProvider(device, db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
for (final ColmiTemperatureSample sample : temperatureSamples) {
sample.setDeviceId(deviceId);
sample.setUserId(userId);
}
LOG.info("Will persist {} temperature samples", temperatureSamples.size());
sampleProvider.addSamples(temperatureSamples);
sampleProvider.persistSamples(temperatureSamples, context);
} catch (Exception e) {
LOG.error("Error acquiring database for recording temperature samples", e);
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2024 Arjan Schrijver
/* Copyright (C) 2024-2026 Arjan Schrijver, Thomas Kuehne
This file is part of Gadgetbridge.
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.yawell.ring;
import static nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils.formatIso8601;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
@@ -30,9 +32,11 @@ import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executors;
@@ -44,7 +48,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.ColmiHeartRateSampleProvider;
@@ -229,30 +232,29 @@ public class YawellRingDeviceSupport extends AbstractBTLESingleDeviceSupport {
minutesInPreviousPackets = 9 * 5; // packet 1
minutesInPreviousPackets += (hrPacketNr - 2) * 13 * 5;
}
final List<ColmiHeartRateSample> heartRateSamples = new ArrayList<>(value.length);
for (int i = startValue; i < value.length - 1; i++) {
if (value[i] != 0x00) {
final int heartRate = value[i] & 0xFF;
if (heartRate != 0x00) {
// Determine time of day
int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 5;
sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
sampleCal.set(Calendar.SECOND, 0);
LOG.info("Value {} is {} bpm, time of day is {}", i, value[i] & 0xff, sampleCal.getTime());
// Build sample object and save in database
LOG.info("Value {} is {} bpm, time of day is {}", i, heartRate, formatIso8601(sampleCal));
ColmiHeartRateSample gbSample = new ColmiHeartRateSample();
gbSample.setTimestamp(sampleCal.getTimeInMillis());
gbSample.setHeartRate(heartRate);
heartRateSamples.add(gbSample);
}
}
try (DBHandler db = GBApplication.acquireDB()) {
ColmiHeartRateSampleProvider sampleProvider = new ColmiHeartRateSampleProvider(getDevice(), db.getDaoSession());
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
ColmiHeartRateSample gbSample = new ColmiHeartRateSample();
gbSample.setDeviceId(deviceId);
gbSample.setUserId(userId);
gbSample.setTimestamp(sampleCal.getTimeInMillis());
gbSample.setHeartRate(value[i] & 0xff);
sampleProvider.addSample(gbSample);
sampleProvider.persistSamples(heartRateSamples, getContext());
} catch (Exception e) {
LOG.error("Error acquiring database for recording heart rate samples", e);
}
}
}
if (hrPacketNr == packetsTotalNr - 1) {
getDevice().unsetBusyTask();
getDevice().sendDeviceUpdateIntent(getContext());
@@ -409,7 +411,7 @@ public class YawellRingDeviceSupport extends AbstractBTLESingleDeviceSupport {
}
switch (value[1]) {
case YawellRingConstants.BIG_DATA_TYPE_TEMPERATURE:
YawellRingPacketHandler.historicalTemperature(getDevice(), value);
YawellRingPacketHandler.historicalTemperature(getDevice(), getContext(), value);
fetchRecordedDataFinished();
break;
case YawellRingConstants.BIG_DATA_TYPE_SLEEP:
@@ -423,7 +425,7 @@ public class YawellRingDeviceSupport extends AbstractBTLESingleDeviceSupport {
fetchRecordedDataFinished();
break;
case YawellRingConstants.BIG_DATA_TYPE_SPO2:
YawellRingPacketHandler.historicalSpo2(getDevice(), value);
YawellRingPacketHandler.historicalSpo2(getDevice(), getContext(), value);
fetchHistorySleep();
break;
default:
@@ -828,7 +830,7 @@ public class YawellRingDeviceSupport extends AbstractBTLESingleDeviceSupport {
hrvHistoryRequestBB.put(0, YawellRingConstants.CMD_SYNC_HRV);
hrvHistoryRequestBB.putInt(1, daysAgo);
byte[] hrvHistoryRequest = buildPacket(hrvHistoryRequestBB.array());
LOG.info("Fetch historical HRV data request sent ({}): {}", syncingDay.getTime(), StringUtils.bytesToHex(hrvHistoryRequest));
LOG.info("Fetch historical HRV data request sent ({}): {}", formatIso8601(syncingDay), StringUtils.bytesToHex(hrvHistoryRequest));
sendWrite("hrvHistoryRequest", hrvHistoryRequest);
}
@@ -21,6 +21,7 @@ import android.content.Context;
import android.text.format.DateUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.github.pfichtner.durationformatter.DurationFormatter;
@@ -81,6 +82,11 @@ public class DateTimeUtils {
return ISO_8601_FORMAT.format(date);
}
@Nullable
public static String formatIso8601(@Nullable Calendar calendar) {
return (calendar == null) ? null : formatIso8601(calendar.getTime());
}
public static String formatIso8601UTC(Date date) {
if(GBApplication.isRunningNougatOrLater()){
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US);
@@ -339,7 +345,7 @@ public class DateTimeUtils {
return format.format(date);
}
/// format duration day - milli seconds with max 3 units shown
/// format duration day - milliseconds with max 3 units shown
public static String formatSportsDuration(long duration, TimeUnit unit) {
DurationFormatter df = DurationFormatter.Builder.SYMBOLS
.maximum(TimeUnit.HOURS)