mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Fossil/Skagen Gen. 6 Hybrids: Add SpO2 Support
This commit is contained in:
parent
b3f29d16b3
commit
4f30648886
@ -54,7 +54,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(89, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(90, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -115,6 +115,7 @@ public class GBDaoGenerator {
|
||||
addPineTimeActivitySample(schema, user, device);
|
||||
addWithingsSteelHRActivitySample(schema, user, device);
|
||||
addHybridHRActivitySample(schema, user, device);
|
||||
addHybridHRSpo2Sample(schema, user, device);
|
||||
addVivomoveHrActivitySample(schema, user, device);
|
||||
addGarminFitFile(schema, user, device);
|
||||
addGarminActivitySample(schema, user, device);
|
||||
@ -734,6 +735,13 @@ public class GBDaoGenerator {
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addHybridHRSpo2Sample(Schema schema, Entity user, Entity device) {
|
||||
Entity spo2sample = addEntity(schema, "HybridHRSpo2Sample");
|
||||
addCommonTimeSampleProperties("AbstractSpo2Sample", spo2sample, user, device);
|
||||
spo2sample.addIntProperty("spo2").notNull().codeBeforeGetter(OVERRIDE);
|
||||
return spo2sample;
|
||||
}
|
||||
|
||||
private static Entity addCyclingSample(Schema schema, Entity user, Entity device) {
|
||||
Entity cyclingSample = addEntity(schema, "CyclingSample");
|
||||
addCommonTimeSampleProperties("AbstractTimeSample", cyclingSample, user, device);
|
||||
|
@ -0,0 +1,56 @@
|
||||
/* Copyright (C) 2024 Benjamin Swartley
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2SampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class HybridHRSpo2SampleProvider extends AbstractTimeSampleProvider<HybridHRSpo2Sample> {
|
||||
public HybridHRSpo2SampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractDao<HybridHRSpo2Sample, ?> getSampleDao() {
|
||||
return getSession().getHybridHRSpo2SampleDao();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return HybridHRSpo2SampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return HybridHRSpo2SampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HybridHRSpo2Sample createSample() {
|
||||
return new HybridHRSpo2Sample();
|
||||
}
|
||||
}
|
@ -45,11 +45,13 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||
@ -103,6 +105,11 @@ public class QHybridCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return new HybridHRActivitySampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
|
||||
return new HybridHRSpo2SampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
if (isHybridHR()) {
|
||||
@ -338,4 +345,9 @@ public class QHybridCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
public boolean supportsNavigation() {
|
||||
return isHybridHR();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSpo2(GBDevice device) {
|
||||
return device.getName().equals("Fossil Gen. 6 Hybrid");
|
||||
}
|
||||
}
|
||||
|
@ -550,7 +550,7 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||
public void handleFileData(byte[] fileData) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
ActivityFileParser parser = new ActivityFileParser();
|
||||
ArrayList<ActivityEntry> entries = parser.parseFile(fileData);
|
||||
ArrayList<ActivityEntry> entries = parser.parseFile(fileData).getKey();
|
||||
HybridHRActivitySampleProvider provider = new HybridHRActivitySampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
HybridHRActivitySample[] samples = new HybridHRActivitySample[entries.size()];
|
||||
|
@ -102,8 +102,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.CommuteActionsActivi
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilFileReader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilHRInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRActivitySampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HybridHRSpo2SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
@ -1232,19 +1236,28 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
@Override
|
||||
public void handleFileData(byte[] fileData) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||
Long userId = user.getId();
|
||||
Device device = DBHelper.getDevice(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
|
||||
Long deviceId = device.getId();
|
||||
ActivityFileParser parser = new ActivityFileParser();
|
||||
ArrayList<ActivityEntry> entries = parser.parseFile(fileData);
|
||||
Map.Entry<ArrayList<ActivityEntry>, ArrayList<HybridHRSpo2Sample>> parsedEntries = parser.parseFile(fileData);
|
||||
// Activities
|
||||
ArrayList<ActivityEntry> entries = parsedEntries.getKey();
|
||||
HybridHRActivitySampleProvider provider = new HybridHRActivitySampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
HybridHRActivitySample[] samples = new HybridHRActivitySample[entries.size()];
|
||||
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDeviceSupport().getDevice(), dbHandler.getDaoSession()).getId();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
samples[i] = entries.get(i).toDAOActivitySample(userId, deviceId);
|
||||
}
|
||||
|
||||
provider.addGBActivitySamples(samples);
|
||||
// SpO2, should be empty for an unsupported device
|
||||
ArrayList<HybridHRSpo2Sample> spo2Samples = parsedEntries.getValue();
|
||||
HybridHRSpo2SampleProvider spo2Provider = new HybridHRSpo2SampleProvider(getDeviceSupport().getDevice(), dbHandler.getDaoSession());
|
||||
for (HybridHRSpo2Sample sample : spo2Samples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
spo2Provider.addSamples(spo2Samples);
|
||||
|
||||
if (saveRawActivityFiles) {
|
||||
writeFile(String.valueOf(System.currentTimeMillis()), fileData);
|
||||
|
@ -15,10 +15,12 @@
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRSpo2Sample;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
public class ActivityFileParser {
|
||||
|
||||
@ -28,10 +30,9 @@ public class ActivityFileParser {
|
||||
int currentTimestamp = 0; // Aligns with `e2 04` from my testing
|
||||
ActivityEntry currentSample = null;
|
||||
int currentId = 1;
|
||||
int spO2 = -1; // Should actually do something with this
|
||||
|
||||
|
||||
public ArrayList<ActivityEntry> parseFile(byte[] file) {
|
||||
public Map.Entry<ArrayList<ActivityEntry>, ArrayList<HybridHRSpo2Sample>> parseFile(byte[] file) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(file);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
@ -48,6 +49,7 @@ public class ActivityFileParser {
|
||||
buffer.position(52); // Seem to be another 32 bytes after the initial 20 stop
|
||||
|
||||
ArrayList<ActivityEntry> samples = new ArrayList<>();
|
||||
ArrayList<HybridHRSpo2Sample> spo2Samples = new ArrayList<>();
|
||||
finishCurrentPacket(samples);
|
||||
|
||||
|
||||
@ -97,7 +99,12 @@ public class ActivityFileParser {
|
||||
continue; // Not sure what to do with this
|
||||
|
||||
} else if (f1 == (byte) 0xD6) {
|
||||
buffer.get(new byte[4]);
|
||||
HybridHRSpo2Sample spo2Sample = new HybridHRSpo2Sample();
|
||||
spo2Sample.setTimestamp(currentTimestamp * 1000L);
|
||||
spo2Sample.setSpo2(buffer.get() & 0xFF);
|
||||
spo2Samples.add(spo2Sample);
|
||||
|
||||
buffer.get(new byte[3]); // Likely something to do with sample statistics
|
||||
|
||||
} else if (f1 == (byte) 0xFE && f2 == (byte) 0xFE) {
|
||||
if (buffer.get(buffer.position()) == (byte) 0xFE) { buffer.get(); } // WHY?
|
||||
@ -163,9 +170,12 @@ public class ActivityFileParser {
|
||||
|
||||
break;
|
||||
|
||||
case (byte) 0xD6: // Seems to only come from intentional spot-checks, despite watch's value updating independently on occasion.
|
||||
spO2 = buffer.get() & 0xFF;
|
||||
|
||||
case (byte) 0xD6:
|
||||
buffer.get(); // Likely some statistic, notably different from 0xCE 0xD6
|
||||
HybridHRSpo2Sample spo2Sample = new HybridHRSpo2Sample();
|
||||
spo2Sample.setTimestamp(currentTimestamp * 1000L);
|
||||
spo2Sample.setSpo2(buffer.get() & 0xFF);
|
||||
spo2Samples.add(spo2Sample);
|
||||
break;
|
||||
|
||||
case (byte) 0xCB: // Very rare, may even be removed
|
||||
@ -181,7 +191,7 @@ public class ActivityFileParser {
|
||||
|
||||
|
||||
}
|
||||
return samples;
|
||||
return Map.entry(samples, spo2Samples);
|
||||
}
|
||||
|
||||
private static boolean elemValidFlags(byte value) {
|
||||
|
Loading…
Reference in New Issue
Block a user