mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Lefun: Implement activity data sampling
This commit is contained in:
parent
6c32a3a99d
commit
d161415046
@ -43,7 +43,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Schema schema = new Schema(30, MAIN_PACKAGE + ".entities");
|
||||
Schema schema = new Schema(31, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -74,6 +74,9 @@ public class GBDaoGenerator {
|
||||
addWatchXPlusHealthActivitySample(schema, user, device);
|
||||
addWatchXPlusHealthActivityKindOverlay(schema, user, device);
|
||||
addTLW64ActivitySample(schema, user, device);
|
||||
addLefunActivitySample(schema, user, device);
|
||||
addLefunBiometricSample(schema,user,device);
|
||||
addLefunSleepSample(schema, user, device);
|
||||
|
||||
addHybridHRActivitySample(schema, user, device);
|
||||
addCalendarSyncState(schema, device);
|
||||
@ -404,6 +407,48 @@ public class GBDaoGenerator {
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "LefunActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty("distance").notNull();
|
||||
activitySample.addIntProperty("calories").notNull();
|
||||
addHeartRateProperties(activitySample);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addLefunBiometricSample(Schema schema, Entity user, Entity device) {
|
||||
Entity biometricSample = addEntity(schema, "LefunBiometricSample");
|
||||
biometricSample.implementsSerializable();
|
||||
|
||||
biometricSample.addIntProperty("timestamp").notNull().primaryKey();
|
||||
Property deviceId = biometricSample.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
biometricSample.addToOne(device, deviceId);
|
||||
Property userId = biometricSample.addLongProperty("userId").notNull().getProperty();
|
||||
biometricSample.addToOne(user, userId);
|
||||
|
||||
biometricSample.addIntProperty("type").notNull();
|
||||
biometricSample.addIntProperty("value1").notNull();
|
||||
biometricSample.addIntProperty("value2");
|
||||
return biometricSample;
|
||||
}
|
||||
|
||||
private static Entity addLefunSleepSample(Schema schema, Entity user, Entity device) {
|
||||
Entity sleepSample = addEntity(schema, "LefunSleepSample");
|
||||
sleepSample.implementsSerializable();
|
||||
|
||||
sleepSample.addIntProperty("timestamp").notNull().primaryKey();
|
||||
Property deviceId = sleepSample.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
sleepSample.addToOne(device, deviceId);
|
||||
Property userId = sleepSample.addLongProperty("userId").notNull().getProperty();
|
||||
sleepSample.addToOne(user, userId);
|
||||
|
||||
sleepSample.addIntProperty("type").notNull();
|
||||
return sleepSample;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass(superClass);
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
|
@ -73,4 +73,24 @@ public class LefunConstants {
|
||||
public static final int PPG_TYPE_HEART_RATE = 0;
|
||||
public static final int PPG_TYPE_BLOOD_PRESSURE = 1;
|
||||
public static final int PPG_TYPE_BLOOD_OXYGEN = 2;
|
||||
public static final int PPG_TYPE_COUNT = 3;
|
||||
|
||||
// Extended DB types
|
||||
public static final int DB_SAMPLE_TYPE_PPG = 1;
|
||||
public static final int DB_SAMPLE_TYPE_SLEEP_DAY = 2;
|
||||
public static final int DB_SAMPLE_TYPE_SLEEP_PERIODS = 3;
|
||||
|
||||
// DB activity kinds
|
||||
public static final int DB_ACTIVITY_KIND_UNKNOWN = 0;
|
||||
public static final int DB_ACTIVITY_KIND_ACTIVITY = 1;
|
||||
public static final int DB_ACTIVITY_KIND_HEART_RATE = 2;
|
||||
public static final int DB_ACTIVITY_KIND_LIGHT_SLEEP = 3;
|
||||
public static final int DB_ACTIVITY_KIND_DEEP_SLEEP = 4;
|
||||
|
||||
// Pseudo-intensity
|
||||
public static final int INTENSITY_MIN = 0;
|
||||
public static final int INTENSITY_DEEP_SLEEP = 1;
|
||||
public static final int INTENSITY_LIGHT_SLEEP = 2;
|
||||
public static final int INTENSITY_AWAKE = 3;
|
||||
public static final int INTENSITY_MAX = 4;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ public class LefunDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
return new LefunSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,101 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lefun;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class LefunSampleProvider extends AbstractSampleProvider<LefunActivitySample> {
|
||||
public LefunSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<LefunActivitySample, ?> getSampleDao() {
|
||||
return getSession().getLefunActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return LefunActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return LefunActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return LefunActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
case LefunConstants.DB_ACTIVITY_KIND_ACTIVITY:
|
||||
case LefunConstants.DB_ACTIVITY_KIND_HEART_RATE:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
case LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
case LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
default:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_ACTIVITY:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_ACTIVITY;
|
||||
case ActivityKind.TYPE_LIGHT_SLEEP:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP;
|
||||
case ActivityKind.TYPE_DEEP_SLEEP:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP;
|
||||
default:
|
||||
return LefunConstants.DB_ACTIVITY_KIND_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / (float)LefunConstants.INTENSITY_MAX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LefunActivitySample createActivitySample() {
|
||||
return new LefunActivitySample();
|
||||
}
|
||||
}
|
@ -28,11 +28,27 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import de.greenrobot.dao.query.Query;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.LefunSleepSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
@ -41,14 +57,18 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.FindDeviceRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetActivityDataRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetBatteryLevelRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFirmwareInfoRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -57,6 +77,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LefunDeviceSupport.class);
|
||||
|
||||
private final List<Request> inProgressRequests = Collections.synchronizedList(new ArrayList<Request>());
|
||||
private final Queue<Request> queuedRequests = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public LefunDeviceSupport() {
|
||||
super(LOG);
|
||||
@ -113,7 +134,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
try {
|
||||
TransactionBuilder builder = createTransactionBuilder(SetTimeRequest.class.getSimpleName());
|
||||
TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName());
|
||||
SetTimeRequest request = new SetTimeRequest(this, builder);
|
||||
request.perform();
|
||||
inProgressRequests.add(request);
|
||||
@ -186,7 +207,27 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
GetActivityDataRequest req = new GetActivityDataRequest(this);
|
||||
req.setDaysAgo(i);
|
||||
queuedRequests.add(req);
|
||||
}
|
||||
|
||||
for (int i = 0; i < LefunConstants.PPG_TYPE_COUNT; ++i) {
|
||||
GetPpgDataRequest req = new GetPpgDataRequest(this);
|
||||
req.setPpgType(i);
|
||||
queuedRequests.add(req);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
GetSleepDataRequest req = new GetSleepDataRequest(this);
|
||||
req.setDaysAgo(i);
|
||||
queuedRequests.add(req);
|
||||
}
|
||||
|
||||
runNextQueuedRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -208,7 +249,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
public void onFindDevice(boolean start) {
|
||||
if (start) {
|
||||
try {
|
||||
TransactionBuilder builder = createTransactionBuilder(FindDeviceRequest.class.getSimpleName());
|
||||
TransactionBuilder builder = performInitialized(FindDeviceRequest.class.getSimpleName());
|
||||
FindDeviceRequest request = new FindDeviceRequest(this, builder);
|
||||
request.perform();
|
||||
inProgressRequests.add(request);
|
||||
@ -296,10 +337,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
if (handleAsynchronousResponse(data))
|
||||
return true;
|
||||
|
||||
logMessageContent(data);
|
||||
LOG.error(String.format("No handler for response 0x%02x", commandId));
|
||||
return false;
|
||||
}
|
||||
|
||||
logMessageContent(data);
|
||||
LOG.error("Invalid response received");
|
||||
return false;
|
||||
}
|
||||
@ -316,4 +359,154 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
private int dateToTimestamp(byte year, byte month, byte day, byte hour, byte minute, byte second) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(
|
||||
((int) year & 0xff) + 2000,
|
||||
((int) month & 0xff) - 1,
|
||||
(int) day,
|
||||
(int) hour,
|
||||
(int) minute,
|
||||
(int) second
|
||||
);
|
||||
return (int) (calendar.getTimeInMillis() / 1000);
|
||||
}
|
||||
|
||||
private LefunActivitySample getActivitySample(DaoSession session, int timestamp) {
|
||||
LefunActivitySampleDao dao = session.getLefunActivitySampleDao();
|
||||
Long userId = DBHelper.getUser(session).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
|
||||
Query<LefunActivitySample> q = dao.queryBuilder()
|
||||
.where(LefunActivitySampleDao.Properties.Timestamp.eq(timestamp))
|
||||
.where(LefunActivitySampleDao.Properties.DeviceId.eq(deviceId))
|
||||
.where(LefunActivitySampleDao.Properties.UserId.eq(userId))
|
||||
.build();
|
||||
return q.unique();
|
||||
}
|
||||
|
||||
public void handleActivityData(GetActivityDataCommand command) {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(),
|
||||
command.getHour(), command.getMinute(), (byte) 0);
|
||||
// For the most part I'm ignoring the sample provider, because it doesn't really help
|
||||
// when I need to combine sample data instead of replacing
|
||||
LefunActivitySample sample = getActivitySample(session, timestamp);
|
||||
if (sample == null) {
|
||||
sample = new LefunActivitySample(timestamp,
|
||||
DBHelper.getDevice(getDevice(), session).getId());
|
||||
sample.setUserId(DBHelper.getUser(session).getId());
|
||||
sample.setRawKind(LefunConstants.DB_ACTIVITY_KIND_ACTIVITY);
|
||||
}
|
||||
|
||||
sample.setSteps(command.getSteps());
|
||||
sample.setDistance(command.getDistance());
|
||||
sample.setCalories(command.getCalories());
|
||||
sample.setRawIntensity(LefunConstants.INTENSITY_AWAKE);
|
||||
|
||||
session.getLefunActivitySampleDao().insertOrReplace(sample);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error handling activity data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void handlePpgData(GetPpgDataCommand command) {
|
||||
byte[] ppgData = command.getPpgData();
|
||||
int ppgData0 = ppgData[0] & 0xff;
|
||||
int ppgData1 = ppgData.length > 1 ? ppgData[1] & 0xff : 0;
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(),
|
||||
command.getHour(), command.getMinute(), command.getSecond());
|
||||
|
||||
if (command.getPpgType() == LefunConstants.PPG_TYPE_HEART_RATE) {
|
||||
LefunActivitySample sample = getActivitySample(session, timestamp);
|
||||
if (sample == null) {
|
||||
sample = new LefunActivitySample(timestamp,
|
||||
DBHelper.getDevice(getDevice(), session).getId());
|
||||
sample.setUserId(DBHelper.getUser(session).getId());
|
||||
sample.setRawKind(LefunConstants.DB_ACTIVITY_KIND_HEART_RATE);
|
||||
}
|
||||
|
||||
sample.setHeartRate(ppgData0);
|
||||
|
||||
session.getLefunActivitySampleDao().insertOrReplace(sample);
|
||||
}
|
||||
|
||||
LefunBiometricSample bioSample = new LefunBiometricSample(timestamp,
|
||||
DBHelper.getDevice(getDevice(), session).getId());
|
||||
bioSample.setUserId(DBHelper.getUser(session).getId());
|
||||
bioSample.setType(command.getPpgType());
|
||||
bioSample.setValue1(ppgData0);
|
||||
bioSample.setValue2(ppgData1);
|
||||
session.getLefunBiometricSampleDao().insertOrReplace(bioSample);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error handling PPG data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSleepData(GetSleepDataCommand command) {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
DaoSession session = handler.getDaoSession();
|
||||
int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(),
|
||||
command.getHour(), command.getMinute(), (byte) 0);
|
||||
|
||||
LefunActivitySample sample = getActivitySample(session, timestamp);
|
||||
if (sample == null) {
|
||||
sample = new LefunActivitySample(timestamp,
|
||||
DBHelper.getDevice(getDevice(), session).getId());
|
||||
sample.setUserId(DBHelper.getUser(session).getId());
|
||||
}
|
||||
|
||||
int rawKind;
|
||||
int intensity;
|
||||
switch (command.getSleepType()) {
|
||||
case GetSleepDataCommand.SLEEP_TYPE_AWAKE:
|
||||
rawKind = LefunConstants.DB_ACTIVITY_KIND_ACTIVITY;
|
||||
intensity = LefunConstants.INTENSITY_AWAKE;
|
||||
break;
|
||||
case GetSleepDataCommand.SLEEP_TYPE_LIGHT_SLEEP:
|
||||
rawKind = LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP;
|
||||
intensity = LefunConstants.INTENSITY_LIGHT_SLEEP;
|
||||
break;
|
||||
case GetSleepDataCommand.SLEEP_TYPE_DEEP_SLEEP:
|
||||
rawKind = LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP;
|
||||
intensity = LefunConstants.INTENSITY_DEEP_SLEEP;
|
||||
break;
|
||||
default:
|
||||
rawKind = LefunConstants.DB_ACTIVITY_KIND_UNKNOWN;
|
||||
intensity = LefunConstants.INTENSITY_AWAKE;
|
||||
break;
|
||||
}
|
||||
|
||||
sample.setRawKind(rawKind);
|
||||
sample.setRawIntensity(intensity);
|
||||
|
||||
session.getLefunActivitySampleDao().insertOrReplace(sample);
|
||||
|
||||
LefunSleepSample sleepSample = new LefunSleepSample(timestamp,
|
||||
DBHelper.getDevice(getDevice(), session).getId());
|
||||
sleepSample.setUserId(DBHelper.getUser(session).getId());
|
||||
sleepSample.setType(command.getSleepType());
|
||||
session.getLefunSleepSampleDao().insertOrReplace(sleepSample);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error handling sleep data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void runNextQueuedRequest() {
|
||||
Request request = queuedRequests.poll();
|
||||
if (request != null) {
|
||||
try {
|
||||
request.perform();
|
||||
if (!request.isSelfQueue())
|
||||
performConnected(request.getTransactionBuilder().getTransaction());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Failed to run next queued request", Toast.LENGTH_SHORT,
|
||||
GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class GetActivityDataRequest extends MultiFetchRequest {
|
||||
private int daysAgo;
|
||||
|
||||
public GetActivityDataRequest(LefunDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public int getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(int daysAgo) {
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetActivityDataCommand cmd = new GetActivityDataCommand();
|
||||
cmd.setDaysAgo((byte) daysAgo);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetActivityDataCommand cmd = new GetActivityDataCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (daysAgo != (cmd.getDaysAgo() & 0xff)) {
|
||||
throw new IllegalArgumentException("Mismatching days ago");
|
||||
}
|
||||
|
||||
if (totalRecords == -1) {
|
||||
totalRecords = cmd.getTotalRecords() & 0xff;
|
||||
} else if (totalRecords != (cmd.getTotalRecords() & 0xff)) {
|
||||
throw new IllegalArgumentException("Total records mismatch");
|
||||
}
|
||||
|
||||
if (totalRecords != 0) {
|
||||
int currentRecord = cmd.getCurrentRecord() & 0xff;
|
||||
if (lastRecord + 1 != currentRecord) {
|
||||
throw new IllegalArgumentException("Records received out of sequence");
|
||||
}
|
||||
lastRecord = currentRecord;
|
||||
|
||||
getSupport().handleActivityData(cmd);
|
||||
} else {
|
||||
lastRecord = totalRecords;
|
||||
}
|
||||
|
||||
if (lastRecord == totalRecords)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_ACTIVITY_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperationName() {
|
||||
return "Getting activity data";
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class GetPpgDataRequest extends MultiFetchRequest {
|
||||
private int ppgType;
|
||||
|
||||
public GetPpgDataRequest(LefunDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public int getPpgType() {
|
||||
return ppgType;
|
||||
}
|
||||
|
||||
public void setPpgType(int ppgType) {
|
||||
this.ppgType = ppgType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetPpgDataCommand cmd = new GetPpgDataCommand();
|
||||
cmd.setPpgType(ppgType);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetPpgDataCommand cmd = new GetPpgDataCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (cmd.getPpgType() != ppgType) {
|
||||
throw new IllegalArgumentException("Mismatching PPG type");
|
||||
}
|
||||
|
||||
if (totalRecords == -1) {
|
||||
totalRecords = cmd.getTotalRecords() & 0xffff;
|
||||
} else if (totalRecords != (cmd.getTotalRecords() & 0xffff)) {
|
||||
throw new IllegalArgumentException("Total records mismatch");
|
||||
}
|
||||
|
||||
if (totalRecords != 0) {
|
||||
int currentRecord = cmd.getCurrentRecord() & 0xffff;
|
||||
if (lastRecord + 1 != currentRecord) {
|
||||
throw new IllegalArgumentException("Records received out of sequence");
|
||||
}
|
||||
lastRecord = currentRecord;
|
||||
|
||||
getSupport().handlePpgData(cmd);
|
||||
} else {
|
||||
lastRecord = totalRecords;
|
||||
}
|
||||
|
||||
if (lastRecord == totalRecords)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_PPG_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperationName() {
|
||||
return "Getting PPG data";
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
|
||||
public class GetSleepDataRequest extends MultiFetchRequest {
|
||||
private int daysAgo;
|
||||
|
||||
public GetSleepDataRequest(LefunDeviceSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public int getDaysAgo() {
|
||||
return daysAgo;
|
||||
}
|
||||
|
||||
public void setDaysAgo(int daysAgo) {
|
||||
this.daysAgo = daysAgo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createRequest() {
|
||||
GetSleepDataCommand cmd = new GetSleepDataCommand();
|
||||
cmd.setDaysAgo((byte) daysAgo);
|
||||
return cmd.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(byte[] data) {
|
||||
GetSleepDataCommand cmd = new GetSleepDataCommand();
|
||||
cmd.deserialize(data);
|
||||
|
||||
if (daysAgo != (cmd.getDaysAgo() & 0xff)) {
|
||||
throw new IllegalArgumentException("Mismatching days ago");
|
||||
}
|
||||
|
||||
if (totalRecords == -1) {
|
||||
totalRecords = cmd.getTotalRecords() & 0xff;
|
||||
} else if (totalRecords != (cmd.getTotalRecords() & 0xff)) {
|
||||
throw new IllegalArgumentException("Total records mismatch");
|
||||
}
|
||||
|
||||
if (totalRecords != 0) {
|
||||
int currentRecord = cmd.getCurrentRecord() & 0xff;
|
||||
if (lastRecord + 1 != currentRecord) {
|
||||
throw new IllegalArgumentException("Records received out of sequence");
|
||||
}
|
||||
lastRecord = currentRecord;
|
||||
|
||||
getSupport().handleSleepData(cmd);
|
||||
} else {
|
||||
lastRecord = totalRecords;
|
||||
}
|
||||
|
||||
if (lastRecord == totalRecords)
|
||||
operationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommandId() {
|
||||
return LefunConstants.CMD_SLEEP_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOperationName() {
|
||||
return "Getting sleep data";
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Copyright (C) 2020 Yukai Li
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public abstract class MultiFetchRequest extends Request {
|
||||
protected MultiFetchRequest(LefunDeviceSupport support) {
|
||||
super(support, null);
|
||||
removeAfterHandling = false;
|
||||
}
|
||||
|
||||
protected int lastRecord = 0;
|
||||
protected int totalRecords = -1;
|
||||
|
||||
@Override
|
||||
protected void prePerform() throws IOException {
|
||||
super.prePerform();
|
||||
builder = performInitialized(getClass().getSimpleName());
|
||||
if (getDevice().isBusy()) {
|
||||
throw new IllegalStateException("Device is busy");
|
||||
}
|
||||
builder.add(new SetDeviceBusyAction(getDevice(), getOperationName(), getContext()));
|
||||
builder.wait(1000); // Wait a bit (after previous operation), or device sometimes won't respond
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void operationFinished() {
|
||||
if (lastRecord == totalRecords)
|
||||
removeAfterHandling = true;
|
||||
try {
|
||||
super.operationFinished();
|
||||
TransactionBuilder builder = performInitialized("Finishing operation");
|
||||
builder.setGattCallback(null);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Failed to reset callback", Toast.LENGTH_SHORT,
|
||||
GB.ERROR, e);
|
||||
}
|
||||
unsetBusy();
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
getSupport().runNextQueuedRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
if (characteristic.getUuid().equals(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY)) {
|
||||
byte[] data = characteristic.getValue();
|
||||
// Parse response
|
||||
if (data.length >= LefunConstants.CMD_HEADER_LENGTH && data[0] == LefunConstants.CMD_RESPONSE_ID) {
|
||||
try {
|
||||
handleResponse(data);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log("Failed to handle response");
|
||||
operationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
getSupport().logMessageContent(data);
|
||||
log("Invalid response received");
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.onCharacteristicChanged(gatt, characteristic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelfQueue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract String getOperationName();
|
||||
}
|
Loading…
Reference in New Issue
Block a user