1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-04 09:17:29 +01:00

Storage and improved way of realtime data (hr, steps so far)

This commit is contained in:
cpfeiffer 2016-09-04 00:02:58 +02:00
parent 5a2ddaaec0
commit 125c0092cb
6 changed files with 202 additions and 19 deletions

View File

@ -162,6 +162,7 @@ public class GBDaoGenerator {
private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) { private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "MiBandActivitySample"); Entity activitySample = addEntity(schema, "MiBandActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);

View File

@ -40,6 +40,9 @@ public abstract class AbstractActivitySample implements ActivitySample {
public void setSteps(int steps) { public void setSteps(int steps) {
} }
/**
* Unix timestamp of the sample, i.e. the number of seconds since 1970-01-01 00:00:00 UTC.
*/
public abstract void setTimestamp(int timestamp); public abstract void setTimestamp(int timestamp);
public abstract void setUserId(long userId); public abstract void setUserId(long userId);

View File

@ -37,6 +37,11 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration"; String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration";
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps"; String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
/**
* Use EXTRA_REALTIME_SAMPLE instead
*/
@Deprecated
String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps"; String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement"; String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support"; String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
@ -77,8 +82,17 @@ public interface DeviceService extends EventHandler {
String EXTRA_ALARMS = "alarms"; String EXTRA_ALARMS = "alarms";
String EXTRA_PERFORM_PAIR = "perform_pair"; String EXTRA_PERFORM_PAIR = "perform_pair";
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps"; String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
/**
* Use EXTRA_REALTIME_SAMPLE instead
*/
@Deprecated
String EXTRA_REALTIME_STEPS = "realtime_steps"; String EXTRA_REALTIME_STEPS = "realtime_steps";
String EXTRA_REALTIME_SAMPLE = "realtime_sample";
String EXTRA_TIMESTAMP = "timestamp"; String EXTRA_TIMESTAMP = "timestamp";
/**
* Use EXTRA_REALTIME_SAMPLE instead
*/
@Deprecated
String EXTRA_HEART_RATE_VALUE = "hr_value"; String EXTRA_HEART_RATE_VALUE = "hr_value";
String EXTRA_CALENDAREVENT_ID = "calendarevent_id"; String EXTRA_CALENDAREVENT_ID = "calendarevent_id";
String EXTRA_CALENDAREVENT_TYPE = "calendarevent_type"; String EXTRA_CALENDAREVENT_TYPE = "calendarevent_type";

View File

@ -21,16 +21,26 @@ 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.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.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
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.MiBandActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
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;
@ -93,6 +103,7 @@ public class MiBandSupport 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 MiBandSupport() { public MiBandSupport() {
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
@ -663,6 +674,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
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());
enableRealtimeSamplesTimer(enable);
} catch (IOException ex) { } catch (IOException ex) {
LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex); LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
} }
@ -713,6 +725,7 @@ public class MiBandSupport 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);
} }
@ -909,10 +922,12 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
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();
}
} }
} }
@ -921,10 +936,75 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("realtime steps: " + steps); LOG.debug("realtime steps: " + steps);
} }
Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS) getRealtimeSamplesSupport().setSteps(steps);
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, steps) }
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); 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);
MiBandSampleProvider provider = new MiBandSampleProvider(gbDevice, session);
MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
sample.setHeartRate(getHeartrateBpm());
sample.setSteps(getSteps());
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
sample.setRawKind(MiBandSampleProvider.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, 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;
} }
/** /**

View File

@ -0,0 +1,86 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import java.util.Timer;
import java.util.TimerTask;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
/**
* Basic support for aggregating different sources of realtime data that comes in in a mostly
* fixed interval. The aggregated data will be stored together.
*
* start() and stop() may be called multiple times, but the first stop() call will really
* stop the timer.
* manner.
*
* Subclasses must implement #doCurrentSample() and should override #resetCurrentValues()
* (but call super!).
*/
public abstract class RealtimeSamplesSupport {
private final long delay;
private final long period;
protected int steps;
protected int heartrateBpm;
// subclasses may add more
private Timer realtimeStorageTimer;
public RealtimeSamplesSupport(long delay, long period) {
this.delay = delay;
this.period = period;
}
public synchronized void start() {
if (isRunning()) {
return; // already running
}
realtimeStorageTimer = new Timer("Mi Band Realtime Storage Timer");
realtimeStorageTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
triggerCurrentSample();
}
}, delay, period);
}
public synchronized void stop() {
if (realtimeStorageTimer != null) {
realtimeStorageTimer.cancel();
realtimeStorageTimer.purge();
realtimeStorageTimer = null;
}
}
public synchronized boolean isRunning() {
return realtimeStorageTimer != null;
}
public void setSteps(int stepsPerMinute) {
this.steps = stepsPerMinute;
}
public int getSteps() {
return steps;
}
public void setHeartrateBpm(int hrBpm) {
this.heartrateBpm = hrBpm;
}
public int getHeartrateBpm() {
return heartrateBpm;
}
public void triggerCurrentSample() {
doCurrentSample();
resetCurrentValues();
}
protected void resetCurrentValues() {
steps = ActivitySample.NOT_MEASURED;
heartrateBpm = ActivitySample.NOT_MEASURED;
}
protected abstract void doCurrentSample();
}

View File

@ -22,7 +22,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
@ -307,8 +309,8 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
try (DBHandler dbHandler = GBApplication.acquireDB()){ try (DBHandler dbHandler = GBApplication.acquireDB()){
MiBandSampleProvider provider = new MiBandSampleProvider(getDevice(), dbHandler.getDaoSession()); MiBandSampleProvider provider = new MiBandSampleProvider(getDevice(), dbHandler.getDaoSession());
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); User user = DBHelper.getUser(dbHandler.getDaoSession());
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
int minutes = 0; int minutes = 0;
try { try {
int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000); int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000);
@ -327,15 +329,12 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
LOG.debug("heartrate received: " + (heartrate & 0xff)); LOG.debug("heartrate received: " + (heartrate & 0xff));
} }
samples[minutes] = new MiBandActivitySample( MiBandActivitySample sample = getSupport().createActivitySample(device, user, timestampInSeconds, provider);
timestampInSeconds, sample.setRawIntensity(intensity & 0xff);
deviceId, sample.setSteps(steps & 0xff);
userId, sample.setRawKind(category & 0xff);
intensity & 0xff, sample.setHeartRate(heartrate & 0xff);
steps & 0xff, samples[minutes] = sample;
category & 0xff,
heartrate & 0xff);
samples[minutes].setProvider(provider);
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("sample: " + samples[minutes]); LOG.debug("sample: " + samples[minutes]);