mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 09:01:55 +01:00
Colmi R0x: Add support for realtime heart rate meassurements and live activity tracking
This commit is contained in:
parent
d46a30aaf2
commit
9edbf160c7
@ -47,7 +47,7 @@ import com.github.mikephil.charting.utils.Utils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -65,6 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class LiveActivityFragment extends AbstractActivityChartFragment<ChartsData> {
|
public class LiveActivityFragment extends AbstractActivityChartFragment<ChartsData> {
|
||||||
@ -166,21 +167,33 @@ public class LiveActivityFragment extends AbstractActivityChartFragment<ChartsDa
|
|||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case DeviceService.ACTION_REALTIME_SAMPLES: {
|
case DeviceService.ACTION_REALTIME_SAMPLES: {
|
||||||
ActivitySample sample = (ActivitySample) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
|
addSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
|
||||||
addSample(sample);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private void addSample(ActivitySample sample) {
|
private void addSample(Serializable serializedSample) {
|
||||||
int heartRate = sample.getHeartRate();
|
int heartRate = 0;
|
||||||
int timestamp = tsTranslation.shorten(sample.getTimestamp());
|
int timestamp = 0;
|
||||||
|
int steps = 0;
|
||||||
|
|
||||||
|
if (serializedSample instanceof ActivitySample) {
|
||||||
|
ActivitySample activitySample = (ActivitySample) serializedSample;
|
||||||
|
heartRate = activitySample.getHeartRate();
|
||||||
|
timestamp = tsTranslation.shorten(activitySample.getTimestamp());
|
||||||
|
steps = activitySample.getSteps();
|
||||||
|
}
|
||||||
|
if (serializedSample instanceof HeartRateSample) {
|
||||||
|
HeartRateSample heartRateSample = (HeartRateSample) serializedSample;
|
||||||
|
heartRate = heartRateSample.getHeartRate();
|
||||||
|
timestamp = tsTranslation.shorten((int)(heartRateSample.getTimestamp() / 1000));
|
||||||
|
}
|
||||||
|
|
||||||
if (HeartRateUtils.getInstance().isValidHeartRateValue(heartRate)) {
|
if (HeartRateUtils.getInstance().isValidHeartRateValue(heartRate)) {
|
||||||
setCurrentHeartRate(heartRate, timestamp);
|
setCurrentHeartRate(heartRate, timestamp);
|
||||||
}
|
}
|
||||||
int steps = sample.getSteps();
|
|
||||||
if (steps > 0) {
|
if (steps > 0) {
|
||||||
addEntries(steps, timestamp);
|
addEntries(steps, timestamp);
|
||||||
}
|
}
|
||||||
|
@ -240,4 +240,9 @@ public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoord
|
|||||||
}
|
}
|
||||||
return deviceSpecificSettings;
|
return deviceSpecificSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLiveActivityFragmentPulseInterval() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.colmi;
|
||||||
|
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
public class ColmiLiveActivityContext {
|
||||||
|
private int bufferedSteps = 0;
|
||||||
|
private int bufferedCalories = 0;
|
||||||
|
private int bufferedDistance = 0;
|
||||||
|
private int lastTotalSteps = 0;
|
||||||
|
private int lastTotalCalories = 0;
|
||||||
|
private int lastTotalDistance = 0;
|
||||||
|
private int lastRealtimeHeartRateTimestamp = 0;
|
||||||
|
private boolean realtimeHrm = false;
|
||||||
|
private int realtimeHrmPacketCount = 0;
|
||||||
|
private boolean realtimeSteps = false;
|
||||||
|
private ScheduledExecutorService realtimeStepsScheduler;
|
||||||
|
|
||||||
|
public boolean isRealtimeSteps() {
|
||||||
|
return realtimeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealtimeSteps(boolean realtimeSteps) {
|
||||||
|
this.realtimeSteps = realtimeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledExecutorService getRealtimeStepsScheduler() {
|
||||||
|
return realtimeStepsScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealtimeStepsScheduler(ScheduledExecutorService realtimeStepsScheduler) {
|
||||||
|
this.realtimeStepsScheduler = realtimeStepsScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferedSteps() {
|
||||||
|
return bufferedSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBufferedSteps(int bufferedSteps) {
|
||||||
|
this.bufferedSteps = bufferedSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferedCalories() {
|
||||||
|
return bufferedCalories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBufferedCalories(int bufferedCalories) {
|
||||||
|
this.bufferedCalories = bufferedCalories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferedDistance() {
|
||||||
|
return bufferedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBufferedDistance(int bufferedDistance) {
|
||||||
|
this.bufferedDistance = bufferedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastTotalSteps() {
|
||||||
|
return lastTotalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastTotalSteps(int lastTotalSteps) {
|
||||||
|
this.lastTotalSteps = lastTotalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastTotalCalories() {
|
||||||
|
return lastTotalCalories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastTotalCalories(int lastTotalCalories) {
|
||||||
|
this.lastTotalCalories = lastTotalCalories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastTotalDistance() {
|
||||||
|
return lastTotalDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastTotalDistance(int lastTotalDistance) {
|
||||||
|
this.lastTotalDistance = lastTotalDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastRealtimeHeartRateTimestamp() {
|
||||||
|
return lastRealtimeHeartRateTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastRealtimeHeartRateTimestamp(int lastRealtimeHeartRateTimestamp) {
|
||||||
|
this.lastRealtimeHeartRateTimestamp = lastRealtimeHeartRateTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRealtimeHrm() {
|
||||||
|
return realtimeHrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealtimeHrm(boolean realtimeHrm) {
|
||||||
|
this.realtimeHrm = realtimeHrm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRealtimeHrmPacketCount() {
|
||||||
|
return realtimeHrmPacketCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealtimeHrmPacketCount(int realtimeHrmPacketCount) {
|
||||||
|
this.realtimeHrmPacketCount = realtimeHrmPacketCount;
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ public class ColmiR0xConstants {
|
|||||||
public static final byte CMD_PREFERENCES = 0x0a;
|
public static final byte CMD_PREFERENCES = 0x0a;
|
||||||
public static final byte CMD_SYNC_HEART_RATE = 0x15;
|
public static final byte CMD_SYNC_HEART_RATE = 0x15;
|
||||||
public static final byte CMD_AUTO_HR_PREF = 0x16;
|
public static final byte CMD_AUTO_HR_PREF = 0x16;
|
||||||
|
public static final byte CMD_REALTIME_HEART_RATE = 0x1e;
|
||||||
public static final byte CMD_GOALS = 0x21;
|
public static final byte CMD_GOALS = 0x21;
|
||||||
public static final byte CMD_AUTO_SPO2_PREF = 0x2c;
|
public static final byte CMD_AUTO_SPO2_PREF = 0x2c;
|
||||||
public static final byte CMD_PACKET_SIZE = 0x2f;
|
public static final byte CMD_PACKET_SIZE = 0x2f;
|
||||||
|
@ -20,6 +20,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -174,11 +175,115 @@ public class ColmiR0xPacketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void liveActivity(byte[] value) {
|
public static void realtimeHeartRate(GBDevice device, Context context, ColmiLiveActivityContext hrmContext, byte[] value) {
|
||||||
|
int hrResponse = value[1] & 0xff;
|
||||||
|
LOG.info("Received realtime heart rate response: {} bpm", hrResponse);
|
||||||
|
|
||||||
|
// Ignore realtime heart rate data if it arrives too fast
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
int sampleTimestamp = (int)(calendar.getTimeInMillis() / 1000);
|
||||||
|
if (sampleTimestamp <= hrmContext.getLastRealtimeHeartRateTimestamp()) {
|
||||||
|
LOG.info("Ignoring realtime heart rate data with same timestamp as last packet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hrmContext.setLastRealtimeHeartRateTimestamp(sampleTimestamp);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void liveActivity(GBDevice device, Context context, ColmiLiveActivityContext liveActivityContext, byte[] value) {
|
||||||
|
// Live activity will report cumulative values over the day
|
||||||
int steps = BLETypeConversions.toUint32(value[4], value[3], value[2], (byte) 0);
|
int steps = BLETypeConversions.toUint32(value[4], value[3], value[2], (byte) 0);
|
||||||
int calories = BLETypeConversions.toUint32(value[7], value[6], value[5], (byte) 0) / 10;
|
int calories = BLETypeConversions.toUint32(value[7], value[6], value[5], (byte) 0) / 10;
|
||||||
int distance = BLETypeConversions.toUint32(value[10], value[9], value[8], (byte) 0);
|
int distance = BLETypeConversions.toUint32(value[10], value[9], value[8], (byte) 0);
|
||||||
LOG.info("Received live activity notification: {} steps, {} calories, {}m distance", steps, calories, distance);
|
LOG.info("Received live activity notification: {} steps, {} calories, {}m distance", steps, calories, distance);
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate difference to last values
|
||||||
|
if (liveActivityContext.getLastTotalSteps() == 0) liveActivityContext.setLastTotalSteps(steps);
|
||||||
|
if (liveActivityContext.getLastTotalCalories() == 0) liveActivityContext.setLastTotalCalories(calories);
|
||||||
|
if (liveActivityContext.getLastTotalDistance() == 0) liveActivityContext.setLastTotalDistance(distance);
|
||||||
|
|
||||||
|
int deltaSteps = steps - liveActivityContext.getLastTotalSteps();
|
||||||
|
int deltaCalories = calories - liveActivityContext.getLastTotalCalories();
|
||||||
|
int deltaDistance = distance - liveActivityContext.getLastTotalDistance();
|
||||||
|
|
||||||
|
liveActivityContext.setLastTotalSteps(steps);
|
||||||
|
liveActivityContext.setLastTotalCalories(calories);
|
||||||
|
liveActivityContext.setLastTotalDistance(distance);
|
||||||
|
|
||||||
|
|
||||||
|
// Buffer live activity data
|
||||||
|
liveActivityContext.setBufferedSteps(liveActivityContext.getBufferedSteps() + deltaSteps);
|
||||||
|
liveActivityContext.setBufferedCalories(liveActivityContext.getBufferedCalories() + deltaCalories);
|
||||||
|
liveActivityContext.setBufferedDistance(liveActivityContext.getBufferedDistance() + deltaDistance);
|
||||||
|
|
||||||
|
LOG.info("Buffered live activity data: {} steps (+{}), {} calories (+{}), {}m distance (+{})", liveActivityContext.getBufferedSteps(), deltaSteps, liveActivityContext.getBufferedCalories(), deltaCalories, liveActivityContext.getBufferedDistance(), deltaDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Runnable liveActivityPulse(GBDevice device, Context context, ColmiLiveActivityContext liveActivityContext) {
|
||||||
|
return () -> {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
int sampleTimestamp = (int) (calendar.getTimeInMillis() / 1000);
|
||||||
|
|
||||||
|
try (DBHandler db = GBApplication.acquireDB()) {
|
||||||
|
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
|
||||||
|
Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
|
||||||
|
|
||||||
|
// Build activity sample object
|
||||||
|
ColmiActivitySampleProvider sampleProvider = new ColmiActivitySampleProvider(device, db.getDaoSession());
|
||||||
|
ColmiActivitySample activitySample = sampleProvider.createActivitySample();
|
||||||
|
activitySample.setProvider(sampleProvider);
|
||||||
|
activitySample.setDeviceId(deviceId);
|
||||||
|
activitySample.setUserId(userId);
|
||||||
|
activitySample.setRawKind(ActivityKind.ACTIVITY.getCode());
|
||||||
|
activitySample.setTimestamp(sampleTimestamp);
|
||||||
|
activitySample.setCalories(liveActivityContext.getBufferedCalories());
|
||||||
|
activitySample.setSteps(liveActivityContext.getBufferedSteps());
|
||||||
|
activitySample.setDistance(liveActivityContext.getBufferedDistance());
|
||||||
|
|
||||||
|
// 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, activitySample);
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
|
LOG.info("Sent live activity notification: {} steps, {} calories, {}m distance", liveActivityContext.getBufferedSteps(), liveActivityContext.getBufferedCalories(), liveActivityContext.getBufferedDistance());
|
||||||
|
|
||||||
|
// Reset buffered data
|
||||||
|
liveActivityContext.setBufferedSteps(0);
|
||||||
|
liveActivityContext.setBufferedCalories(0);
|
||||||
|
liveActivityContext.setBufferedDistance(0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Error acquiring database for recording activity samples", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void historicalActivity(GBDevice device, Context context, byte[] value) {
|
public static void historicalActivity(GBDevice device, Context context, byte[] value) {
|
||||||
|
@ -34,6 +34,9 @@ import java.util.Calendar;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@ -43,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiLiveActivityContext;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xConstants;
|
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xConstants;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xPacketHandler;
|
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.ColmiR0xPacketHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHeartRateSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHeartRateSampleProvider;
|
||||||
@ -78,6 +82,9 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
private int bigDataPacketSize;
|
private int bigDataPacketSize;
|
||||||
private ByteBuffer bigDataPacket;
|
private ByteBuffer bigDataPacket;
|
||||||
|
|
||||||
|
private static final int LIVE_ACTIVITY_BUFFER_INTERVAL = 2000;
|
||||||
|
private final ColmiLiveActivityContext liveActivityContext = new ColmiLiveActivityContext();
|
||||||
|
|
||||||
public ColmiR0xDeviceSupport() {
|
public ColmiR0xDeviceSupport() {
|
||||||
super(LOG);
|
super(LOG);
|
||||||
addSupportedService(ColmiR0xConstants.CHARACTERISTIC_SERVICE_V1);
|
addSupportedService(ColmiR0xConstants.CHARACTERISTIC_SERVICE_V1);
|
||||||
@ -100,6 +107,12 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
public void dispose() {
|
public void dispose() {
|
||||||
backgroundTasksHandler.removeCallbacksAndMessages(null);
|
backgroundTasksHandler.removeCallbacksAndMessages(null);
|
||||||
|
|
||||||
|
LOG.info("Stopping live activity timeout scheduler");
|
||||||
|
if(liveActivityContext.getRealtimeStepsScheduler() != null) {
|
||||||
|
liveActivityContext.getRealtimeStepsScheduler().shutdown();
|
||||||
|
liveActivityContext.setRealtimeStepsScheduler(null);
|
||||||
|
}
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +327,20 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
break;
|
break;
|
||||||
case ColmiR0xConstants.CMD_MANUAL_HEART_RATE:
|
case ColmiR0xConstants.CMD_MANUAL_HEART_RATE:
|
||||||
ColmiR0xPacketHandler.liveHeartRate(getDevice(), getContext(), value);
|
ColmiR0xPacketHandler.liveHeartRate(getDevice(), getContext(), value);
|
||||||
|
break;
|
||||||
|
case ColmiR0xConstants.CMD_REALTIME_HEART_RATE:
|
||||||
|
ColmiR0xPacketHandler.realtimeHeartRate(getDevice(), getContext(), liveActivityContext, value);
|
||||||
|
|
||||||
|
// The realtime measurement has a timeout of 60 seconds.
|
||||||
|
// Send a "continue" command every 30 packets (= every 30 seconds)
|
||||||
|
liveActivityContext.setRealtimeHrmPacketCount((liveActivityContext.getRealtimeHrmPacketCount()+1) % 30);
|
||||||
|
|
||||||
|
if(liveActivityContext.isRealtimeHrm() && liveActivityContext.getRealtimeHrmPacketCount() == 0) {
|
||||||
|
byte[] measureHeartRatePacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_REALTIME_HEART_RATE, 0x03});
|
||||||
|
LOG.info("Continue realtime HRM request sent: {}", StringUtils.bytesToHex(measureHeartRatePacket));
|
||||||
|
sendWrite("continueRealtimeHRMRequest", measureHeartRatePacket);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ColmiR0xConstants.CMD_NOTIFICATION:
|
case ColmiR0xConstants.CMD_NOTIFICATION:
|
||||||
switch (value[1]) {
|
switch (value[1]) {
|
||||||
@ -334,7 +361,7 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
evaluateGBDeviceEvent(batteryNotifEvent);
|
evaluateGBDeviceEvent(batteryNotifEvent);
|
||||||
break;
|
break;
|
||||||
case ColmiR0xConstants.NOTIFICATION_LIVE_ACTIVITY:
|
case ColmiR0xConstants.NOTIFICATION_LIVE_ACTIVITY:
|
||||||
ColmiR0xPacketHandler.liveActivity(value);
|
ColmiR0xPacketHandler.liveActivity(getDevice(), getContext(), liveActivityContext, value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.info("Received unrecognized notification: {}", StringUtils.bytesToHex(value));
|
LOG.info("Received unrecognized notification: {}", StringUtils.bytesToHex(value));
|
||||||
@ -625,6 +652,42 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendWrite("measureHRRequest", measureHeartRatePacket);
|
sendWrite("measureHRRequest", measureHeartRatePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||||
|
if (enable == liveActivityContext.isRealtimeHrm()) return;
|
||||||
|
liveActivityContext.setRealtimeHrm(enable);
|
||||||
|
liveActivityContext.setRealtimeHrmPacketCount(0);
|
||||||
|
|
||||||
|
byte enableByte;
|
||||||
|
if(enable) enableByte = 0x01;
|
||||||
|
else enableByte = 0x02;
|
||||||
|
|
||||||
|
byte[] measureHeartRatePacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_REALTIME_HEART_RATE, enableByte});
|
||||||
|
LOG.info("Enable realtime HRM request sent: {}", StringUtils.bytesToHex(measureHeartRatePacket));
|
||||||
|
sendWrite("enableRealtimeHRMRequest", measureHeartRatePacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableRealtimeSteps(boolean enable) {
|
||||||
|
if (enable == liveActivityContext.isRealtimeSteps()) return;
|
||||||
|
liveActivityContext.setRealtimeSteps(enable);
|
||||||
|
|
||||||
|
if(enable) {
|
||||||
|
liveActivityContext.setBufferedSteps(0);
|
||||||
|
liveActivityContext.setBufferedCalories(0);
|
||||||
|
liveActivityContext.setBufferedDistance(0);
|
||||||
|
|
||||||
|
LOG.info("Starting live activity timeout scheduler");
|
||||||
|
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
service.scheduleWithFixedDelay(ColmiR0xPacketHandler.liveActivityPulse(getDevice(), getContext(), liveActivityContext), 0, LIVE_ACTIVITY_BUFFER_INTERVAL, TimeUnit.MILLISECONDS);
|
||||||
|
liveActivityContext.setRealtimeStepsScheduler(service);
|
||||||
|
} else {
|
||||||
|
LOG.info("Stopping live activity timeout scheduler");
|
||||||
|
liveActivityContext.getRealtimeStepsScheduler().shutdown();
|
||||||
|
liveActivityContext.setRealtimeStepsScheduler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFetchRecordedData(int dataTypes) {
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
|
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
|
||||||
|
Loading…
Reference in New Issue
Block a user