mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 12:56:49 +01:00
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
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
@ -26,19 +25,28 @@ import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
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.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
||||
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.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
|
||||
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.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
|
||||
@ -109,6 +117,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||
private RealtimeSamplesSupport realtimeSamplesSupport;
|
||||
|
||||
public MiBand2Support() {
|
||||
super(LOG);
|
||||
@ -124,7 +133,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
|
||||
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||
addSupportedProfile(deviceInfoProfile);
|
||||
heartRateProfile = new HeartRateProfile<MiBand2Support>(this);
|
||||
heartRateProfile = new HeartRateProfile<>(this);
|
||||
addSupportedProfile(heartRateProfile);
|
||||
|
||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||
@ -173,12 +182,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
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) {
|
||||
byte[] bytes = BLETypeConversions.shortCalendarToRawBytes(calendar, true);
|
||||
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() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("HeartRateTest");
|
||||
heartRateProfile.requestHeartRateMeasurement(builder);
|
||||
// profile.resetEnergyExpended(builder);
|
||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
||||
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());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to read HearRate with MI2", ex);
|
||||
@ -691,18 +692,19 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
// try {
|
||||
// TransactionBuilder builder = performInitialized("EnableRealtimeHeartRateMeasurement");
|
||||
// if (enable) {
|
||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
|
||||
// } else {
|
||||
// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
||||
// }
|
||||
// builder.queue(getQueue());
|
||||
// } catch (IOException ex) {
|
||||
// LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
|
||||
// }
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Enable realtime heart rateM measurement");
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
enableRealtimeSamplesTimer(enable);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -745,6 +747,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
// performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications")
|
||||
// .write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), enable ? getLowLatency() : getHighLatency())
|
||||
// .write(controlPoint, enable ? startRealTimeStepsNotifications : stopRealTimeStepsNotifications).queue(getQueue());
|
||||
// enableRealtimeSamplesTimer(enable);
|
||||
// } catch (IOException 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) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS && value != null) {
|
||||
LOG.info("Got heartrate:");
|
||||
if (value.length == 2 && value[0] == 6) {
|
||||
if (value.length == 2 && value[0] == 0) {
|
||||
int hrValue = (value[1] & 0xff);
|
||||
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) {
|
||||
if (value.length == 2 && value[0] == 6) {
|
||||
if (value.length == 2 && value[0] == 0) {
|
||||
int hrValue = (value[1] & 0xff);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("heart rate: " + hrValue);
|
||||
}
|
||||
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
|
||||
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
|
||||
realtimeSamplesSupport.setHeartrateBpm(hrValue);
|
||||
if (!realtimeSamplesSupport.isRunning()) {
|
||||
// single shot measurement, manually invoke storage and result publishing
|
||||
realtimeSamplesSupport.triggerCurrentSample();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -949,11 +954,76 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
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)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, steps)
|
||||
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps())
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user