Support for continuous hr readings (live activity) #323
This commit is contained in:
parent
49acde118d
commit
09ff95eb34
|
@ -44,12 +44,6 @@ public class HeartRateProfile<T extends AbstractBTLEDeviceSupport> extends Abstr
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: I didn't find anything in the spec to request heart rate readings, so probably this
|
|
||||||
// should be done in a device specific way.
|
|
||||||
public void requestHeartRateMeasurement(TransactionBuilder builder) {
|
|
||||||
writeToControlPoint(new byte[] { 0x15, 0x02, 0x01}, builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||||
if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
|
if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
|
||||||
|
@ -61,7 +55,7 @@ public class HeartRateProfile<T extends AbstractBTLEDeviceSupport> extends Abstr
|
||||||
format = BluetoothGattCharacteristic.FORMAT_UINT8;
|
format = BluetoothGattCharacteristic.FORMAT_UINT8;
|
||||||
}
|
}
|
||||||
final int heartRate = characteristic.getIntValue(format, 1);
|
final int heartRate = characteristic.getIntValue(format, 1);
|
||||||
GB.toast(getContext(), "Heart rate: " + heartRate, Toast.LENGTH_LONG, GB.INFO);
|
LOG.info("Heart rate: " + heartRate, Toast.LENGTH_LONG, GB.INFO);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
@ -26,19 +25,28 @@ import java.util.UUID;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
|
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.SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
|
||||||
|
@ -109,6 +117,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||||
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||||
|
private RealtimeSamplesSupport realtimeSamplesSupport;
|
||||||
|
|
||||||
public MiBand2Support() {
|
public MiBand2Support() {
|
||||||
super(LOG);
|
super(LOG);
|
||||||
|
@ -124,7 +133,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||||
addSupportedProfile(deviceInfoProfile);
|
addSupportedProfile(deviceInfoProfile);
|
||||||
heartRateProfile = new HeartRateProfile<MiBand2Support>(this);
|
heartRateProfile = new HeartRateProfile<>(this);
|
||||||
addSupportedProfile(heartRateProfile);
|
addSupportedProfile(heartRateProfile);
|
||||||
|
|
||||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||||
|
@ -173,12 +182,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private MiBand2Support maybeAuth(TransactionBuilder builder) {
|
|
||||||
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x20, 0x00});
|
|
||||||
// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2});
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public byte[] getTimeBytes(Calendar calendar) {
|
public byte[] getTimeBytes(Calendar calendar) {
|
||||||
byte[] bytes = BLETypeConversions.shortCalendarToRawBytes(calendar, true);
|
byte[] bytes = BLETypeConversions.shortCalendarToRawBytes(calendar, true);
|
||||||
byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
|
byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
|
||||||
|
@ -678,11 +681,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
public void onHeartRateTest() {
|
public void onHeartRateTest() {
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("HeartRateTest");
|
TransactionBuilder builder = performInitialized("HeartRateTest");
|
||||||
heartRateProfile.requestHeartRateMeasurement(builder);
|
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
||||||
// profile.resetEnergyExpended(builder);
|
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
||||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
|
||||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
|
||||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
|
|
||||||
builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("Unable to read HearRate with MI2", ex);
|
LOG.error("Unable to read HearRate with MI2", ex);
|
||||||
|
@ -691,18 +692,19 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||||
// try {
|
try {
|
||||||
// TransactionBuilder builder = performInitialized("EnableRealtimeHeartRateMeasurement");
|
TransactionBuilder builder = performInitialized("Enable realtime heart rateM measurement");
|
||||||
// if (enable) {
|
if (enable) {
|
||||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
||||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
|
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
|
||||||
// } else {
|
} else {
|
||||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
||||||
// }
|
}
|
||||||
// builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
// } catch (IOException ex) {
|
enableRealtimeSamplesTimer(enable);
|
||||||
// LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
|
} catch (IOException ex) {
|
||||||
// }
|
LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -745,6 +747,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
// performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications")
|
// performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications")
|
||||||
// .write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), enable ? getLowLatency() : getHighLatency())
|
// .write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), enable ? getLowLatency() : getHighLatency())
|
||||||
// .write(controlPoint, enable ? startRealTimeStepsNotifications : stopRealTimeStepsNotifications).queue(getQueue());
|
// .write(controlPoint, enable ? startRealTimeStepsNotifications : stopRealTimeStepsNotifications).queue(getQueue());
|
||||||
|
// enableRealtimeSamplesTimer(enable);
|
||||||
// } catch (IOException e) {
|
// } catch (IOException e) {
|
||||||
// LOG.error("Unable to change realtime steps notification to: " + enable, e);
|
// LOG.error("Unable to change realtime steps notification to: " + enable, e);
|
||||||
// }
|
// }
|
||||||
|
@ -922,7 +925,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
public void logHeartrate(byte[] value, int status) {
|
public void logHeartrate(byte[] value, int status) {
|
||||||
if (status == BluetoothGatt.GATT_SUCCESS && value != null) {
|
if (status == BluetoothGatt.GATT_SUCCESS && value != null) {
|
||||||
LOG.info("Got heartrate:");
|
LOG.info("Got heartrate:");
|
||||||
if (value.length == 2 && value[0] == 6) {
|
if (value.length == 2 && value[0] == 0) {
|
||||||
int hrValue = (value[1] & 0xff);
|
int hrValue = (value[1] & 0xff);
|
||||||
GB.toast(getContext(), "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
|
GB.toast(getContext(), "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
|
||||||
}
|
}
|
||||||
|
@ -932,15 +935,17 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleHeartrate(byte[] value) {
|
private void handleHeartrate(byte[] value) {
|
||||||
if (value.length == 2 && value[0] == 6) {
|
if (value.length == 2 && value[0] == 0) {
|
||||||
int hrValue = (value[1] & 0xff);
|
int hrValue = (value[1] & 0xff);
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("heart rate: " + hrValue);
|
LOG.debug("heart rate: " + hrValue);
|
||||||
}
|
}
|
||||||
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
|
RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
|
||||||
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue)
|
realtimeSamplesSupport.setHeartrateBpm(hrValue);
|
||||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
if (!realtimeSamplesSupport.isRunning()) {
|
||||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
// single shot measurement, manually invoke storage and result publishing
|
||||||
|
realtimeSamplesSupport.triggerCurrentSample();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -949,11 +954,76 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("realtime steps: " + steps);
|
LOG.debug("realtime steps: " + steps);
|
||||||
}
|
}
|
||||||
|
getRealtimeSamplesSupport().setSteps(steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableRealtimeSamplesTimer(boolean enable) {
|
||||||
|
if (enable) {
|
||||||
|
getRealtimeSamplesSupport().start();
|
||||||
|
} else {
|
||||||
|
if (realtimeSamplesSupport != null) {
|
||||||
|
realtimeSamplesSupport.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MiBandActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
|
||||||
|
MiBandActivitySample sample = new MiBandActivitySample();
|
||||||
|
sample.setDevice(device);
|
||||||
|
sample.setUser(user);
|
||||||
|
sample.setTimestamp(timestampInSeconds);
|
||||||
|
sample.setProvider(provider);
|
||||||
|
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RealtimeSamplesSupport getRealtimeSamplesSupport() {
|
||||||
|
if (realtimeSamplesSupport == null) {
|
||||||
|
realtimeSamplesSupport = new RealtimeSamplesSupport(1000, 1000) {
|
||||||
|
@Override
|
||||||
|
public void doCurrentSample() {
|
||||||
|
|
||||||
|
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
|
DaoSession session = handler.getDaoSession();
|
||||||
|
|
||||||
|
Device device = DBHelper.getDevice(getDevice(), session);
|
||||||
|
User user = DBHelper.getUser(session);
|
||||||
|
int ts = (int) (System.currentTimeMillis() / 1000);
|
||||||
|
MiBand2SampleProvider provider = new MiBand2SampleProvider(gbDevice, session);
|
||||||
|
MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
|
||||||
|
sample.setHeartRate(getHeartrateBpm());
|
||||||
|
sample.setSteps(getSteps());
|
||||||
|
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
|
||||||
|
sample.setRawKind(MiBand2SampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
|
||||||
|
|
||||||
|
// TODO: remove this once fully ported to REALTIME_SAMPLES
|
||||||
|
if (sample.getSteps() != ActivitySample.NOT_MEASURED) {
|
||||||
Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS)
|
Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS)
|
||||||
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, steps)
|
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps())
|
||||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
if (sample.getHeartRate() != ActivitySample.NOT_MEASURED) {
|
||||||
|
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
|
||||||
|
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, sample.getHeartRate())
|
||||||
|
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||||
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
|
||||||
|
// .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
||||||
|
// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||||
|
|
||||||
|
LOG.debug("Storing realtime sample: " + sample);
|
||||||
|
provider.addGBActivitySample(sample);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Unable to acquire db for saving realtime samples", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return realtimeSamplesSupport;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
|
* React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
|
||||||
|
|
Loading…
Reference in New Issue