diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index b51fde55ca..ed32fff4b5 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -58,7 +58,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(116, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(117, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -443,6 +443,9 @@ public class GBDaoGenerator { addHeartRateProperties(activitySample); activitySample.addIntProperty(SAMPLE_STRESS); activitySample.addIntProperty(SAMPLE_SPO2); + activitySample.addIntProperty("distanceCm").notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty("activeCalories").notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty("energy").notNull(); return activitySample; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_117.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_117.java new file mode 100644 index 0000000000..1617a865b8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_117.java @@ -0,0 +1,35 @@ +package nodomain.freeyourgadget.gadgetbridge.database.schema; + +import android.database.sqlite.SQLiteDatabase; + +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; +import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySampleDao; + +public class GadgetbridgeUpdate_117 implements DBUpdateScript { + @Override + public void upgradeSchema(final SQLiteDatabase db) { + if (!DBHelper.existsColumn(XiaomiActivitySampleDao.TABLENAME, XiaomiActivitySampleDao.Properties.ActiveCalories.columnName, db)) { + final String statement = "ALTER TABLE " + XiaomiActivitySampleDao.TABLENAME + " ADD COLUMN \"" + + XiaomiActivitySampleDao.Properties.ActiveCalories.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + + if (!DBHelper.existsColumn(XiaomiActivitySampleDao.TABLENAME, XiaomiActivitySampleDao.Properties.DistanceCm.columnName, db)) { + final String statement = "ALTER TABLE " + XiaomiActivitySampleDao.TABLENAME + " ADD COLUMN \"" + + XiaomiActivitySampleDao.Properties.DistanceCm.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + + if (!DBHelper.existsColumn(XiaomiActivitySampleDao.TABLENAME, XiaomiActivitySampleDao.Properties.Energy.columnName, db)) { + final String statement = "ALTER TABLE " + XiaomiActivitySampleDao.TABLENAME + " ADD COLUMN \"" + + XiaomiActivitySampleDao.Properties.Energy.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + } + + @Override + public void downgradeSchema(SQLiteDatabase database) { + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiBodyEnergySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiBodyEnergySampleProvider.java new file mode 100644 index 0000000000..20ada54be0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiBodyEnergySampleProvider.java @@ -0,0 +1,61 @@ +/* Copyright (C) 2025 José Rebelo + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; + +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleToTimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample; + +public class XiaomiBodyEnergySampleProvider extends AbstractSampleToTimeSampleProvider { + public XiaomiBodyEnergySampleProvider(final GBDevice device, final DaoSession session) { + super(new XiaomiSampleProvider(device, session), device, session); + } + + @Override + protected BodyEnergySample convertSample(final XiaomiActivitySample sample) { + if (sample.getEnergy() <= 0) { + return null; + } + + return new XiaomiBodyEnergySample( + sample.getTimestamp() * 1000L, + sample.getEnergy() + ); + } + + protected static class XiaomiBodyEnergySample implements BodyEnergySample { + private final long timestamp; + private final int energy; + + public XiaomiBodyEnergySample(final long timestamp, final int energy) { + this.timestamp = timestamp; + this.energy = energy; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public int getEnergy() { + return energy; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java index a26f224b79..80fb569a14 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java @@ -63,6 +63,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSampleDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; +import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample; import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; @@ -94,7 +95,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { @NonNull @Override - public Class getDeviceSupportClass(final GBDevice device) { + public Class getDeviceSupportClass(@NonNull final GBDevice device) { return XiaomiSupport.class; } @@ -132,12 +133,12 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public SampleProvider getSampleProvider(final GBDevice device, DaoSession session) { + public SampleProvider getSampleProvider(@NonNull final GBDevice device, DaoSession session) { return new XiaomiSampleProvider(device, session); } @Override - public TimeSampleProvider getStressSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getStressSampleProvider(@NonNull final GBDevice device, final DaoSession session) { return new XiaomiStressSampleProvider(device, session); } @@ -151,17 +152,22 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public TimeSampleProvider getTemperatureSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getTemperatureSampleProvider(@NonNull final GBDevice device, final DaoSession session) { return new XiaomiTemperatureSampleProvider(device, session); } @Override - public TimeSampleProvider getSpo2SampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getSpo2SampleProvider(@NonNull final GBDevice device, final DaoSession session) { return new XiaomiSpo2SampleProvider(device, session); } @Override - public TimeSampleProvider getHeartRateMaxSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getBodyEnergySampleProvider(@NonNull final GBDevice device, final DaoSession session) { + return new XiaomiBodyEnergySampleProvider(device, session); + } + + @Override + public TimeSampleProvider getHeartRateMaxSampleProvider(@NonNull final GBDevice device, final DaoSession session) { // TODO XiaomiHeartRateMaxSampleProvider return super.getHeartRateMaxSampleProvider(device, session); } @@ -172,25 +178,25 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public TimeSampleProvider getHeartRateManualSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getHeartRateManualSampleProvider(@NonNull final GBDevice device, final DaoSession session) { // TODO XiaomiHeartRateManualSampleProvider return super.getHeartRateManualSampleProvider(device, session); } @Override - public TimeSampleProvider getPaiSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getPaiSampleProvider(@NonNull final GBDevice device, final DaoSession session) { return new XiaomiPaiSampleProvider(device, session); } @Override - public TimeSampleProvider getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getRespiratoryRateSampleProvider(@NonNull final GBDevice device, final DaoSession session) { // TODO XiaomiSleepRespiratoryRateSampleProvider return super.getRespiratoryRateSampleProvider(device, session); } @Nullable @Override - public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) { + public ActivitySummaryParser getActivitySummaryParser(@NonNull final GBDevice device, final Context context) { return new WorkoutSummaryParser(); } @@ -200,62 +206,57 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public int getAlarmSlotCount(final GBDevice device) { + public int getAlarmSlotCount(@NonNull final GBDevice device) { return getPrefs(device).getInt(XiaomiPreferences.PREF_ALARM_SLOTS, 0); } @Override - public boolean supportsSmartWakeup(final GBDevice device, int position) { + public boolean supportsSmartWakeup(@NonNull final GBDevice device, int position) { return true; } @Override - public boolean supportsAppsManagement(final GBDevice device) { + public boolean supportsAppsManagement(@NonNull final GBDevice device) { return true; } @Override - public boolean supportsCachedAppManagement(GBDevice device) { + public boolean supportsCachedAppManagement(@NonNull final GBDevice device) { return false; } @Override - public boolean supportsInstalledAppManagement(GBDevice device) { + public boolean supportsInstalledAppManagement(@NonNull final GBDevice device) { return false; } @Override - public boolean supportsWatchfaceManagement(GBDevice device) { + public boolean supportsWatchfaceManagement(@NonNull final GBDevice device) { return supportsAppsManagement(device); } @Override - public Class getAppsManagementActivity(final GBDevice device) { + public Class getAppsManagementActivity(@NonNull final GBDevice device) { return AppManagerActivity.class; } @Override - public boolean supportsAppListFetching(final GBDevice device) { + public boolean supportsAppListFetching(@NonNull final GBDevice device) { return true; } - @Override - public boolean supportsAppReordering(final GBDevice device) { - return false; - } - @Override public int getBondingStyle() { return BONDING_STYLE_REQUIRE_KEY; } @Override - public boolean supportsCalendarEvents(final GBDevice device) { + public boolean supportsCalendarEvents(@NonNull final GBDevice device) { return true; } @Override - public boolean supportsActivityDataFetching(final GBDevice device) { + public boolean supportsActivityDataFetching(@NonNull final GBDevice device) { return true; } @@ -265,7 +266,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public boolean supportsActivityTracks(final GBDevice device) { + public boolean supportsActivityTracks(@NonNull final GBDevice device) { return true; } @@ -275,10 +276,15 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public boolean supportsSpo2(GBDevice device) { + public boolean supportsSpo2(@NonNull GBDevice device) { return true; } + @Override + public boolean supportsBodyEnergy(@NonNull GBDevice device) { + return false; // FIXME is should, but untested + } + @Override public boolean supportsHeartRateStats(@NonNull GBDevice device) { // TODO it does, and they're persisted - see DailySummaryParser @@ -286,7 +292,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public boolean supportsHeartRateRestingMeasurement(GBDevice device) { + public boolean supportsHeartRateRestingMeasurement(@NonNull final GBDevice device) { return true; } @@ -324,12 +330,12 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public int getReminderSlotCount(final GBDevice device) { + public int getReminderSlotCount(@NonNull final GBDevice device) { return getPrefs(device).getInt(XiaomiPreferences.PREF_REMINDER_SLOTS, 0); } @Override - public int getCannedRepliesSlotCount(final GBDevice device) { + public int getCannedRepliesSlotCount(@NonNull final GBDevice device) { return getPrefs(device).getInt(XiaomiPreferences.PREF_CANNED_MESSAGES_MAX, 0); } @@ -353,12 +359,12 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public boolean supportsHeartRateMeasurement(final GBDevice device) { + public boolean supportsHeartRateMeasurement(@NonNull final GBDevice device) { return true; } @Override - public boolean supportsManualHeartRateMeasurement(final GBDevice device) { + public boolean supportsManualHeartRateMeasurement(@NonNull final GBDevice device) { return true; } @@ -378,7 +384,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public boolean supportsWeather(final GBDevice device) { + public boolean supportsWeather(@NonNull final GBDevice device) { return true; } @@ -412,7 +418,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) { + public DeviceSpecificSettings getDeviceSpecificSettings(@NonNull final GBDevice device) { final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings(); if (supports(device, FEAT_WEAR_MODE)) { @@ -536,12 +542,12 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) { + public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(@NonNull final GBDevice device) { return new XiaomiSettingsCustomizer(); } @Override - public String[] getSupportedLanguageSettings(final GBDevice device) { + public String[] getSupportedLanguageSettings(@NonNull final GBDevice device) { return new String[]{ "auto", "ar_SA", @@ -591,20 +597,20 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public boolean supportsWidgets(final GBDevice device) { + public boolean supportsWidgets(@NonNull final GBDevice device) { return getPrefs(device).getBoolean(XiaomiPreferences.FEAT_WIDGETS, false); } @Override - public WidgetManager getWidgetManager(final GBDevice device) { + public WidgetManager getWidgetManager(@NonNull final GBDevice device) { return new XiaomiWidgetManager(device); } - protected static Prefs getPrefs(final GBDevice device) { + protected static Prefs getPrefs(@NonNull final GBDevice device) { return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); } - public boolean supports(final GBDevice device, final String feature) { + public boolean supports(@NonNull final GBDevice device, final String feature) { return getPrefs(device).getBoolean(feature, false); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiBand9ActiveCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiBand9ActiveCoordinator.java index 46480d7f35..99519d8830 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiBand9ActiveCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiBand9ActiveCoordinator.java @@ -54,7 +54,7 @@ public class MiBand9ActiveCoordinator extends XiaomiCoordinator { } @Override - public boolean supportsManualHeartRateMeasurement(final GBDevice device) { + public boolean supportsManualHeartRateMeasurement(@NonNull final GBDevice device) { return false; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiWatchLiteCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiWatchLiteCoordinator.java index 9ad02a188d..8386fee433 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiWatchLiteCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/MiWatchLiteCoordinator.java @@ -46,7 +46,7 @@ public class MiWatchLiteCoordinator extends XiaomiCoordinator { } @Override - public boolean supportsCalendarEvents(final GBDevice device) { + public boolean supportsCalendarEvents(@NonNull final GBDevice device) { return false; } @@ -56,7 +56,7 @@ public class MiWatchLiteCoordinator extends XiaomiCoordinator { } @Override - public boolean supportsSpo2(GBDevice device) { + public boolean supportsSpo2(@NonNull GBDevice device) { return false; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/RedmiSmartBand3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/RedmiSmartBand3Coordinator.java index 8be04f8596..7bd5e2c08a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/RedmiSmartBand3Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/watches/RedmiSmartBand3Coordinator.java @@ -54,7 +54,7 @@ public class RedmiSmartBand3Coordinator extends XiaomiCoordinator { } @Override - public boolean supportsManualHeartRateMeasurement(final GBDevice device) { + public boolean supportsManualHeartRateMeasurement(@NonNull final GBDevice device) { // #4809 does not seem to be supported return false; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java index 147b82b39d..606012bcbb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/DailyDetailsParser.java @@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser; @@ -89,6 +90,9 @@ public class DailyDetailsParser extends XiaomiActivityParser { final XiaomiActivitySample sample = new XiaomiActivitySample(); sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000)); + sample.setActiveCalories(ActivitySample.NOT_MEASURED); + sample.setDistanceCm(ActivitySample.NOT_MEASURED); + sample.setEnergy(ActivitySample.NOT_MEASURED); int includeExtraEntry = 0; if (complexParser.nextGroup(16)) { @@ -105,7 +109,7 @@ public class DailyDetailsParser extends XiaomiActivityParser { if (complexParser.nextGroup(8)) { // TODO activity type? if (complexParser.hasSecond()) { - final int calories = complexParser.get(2, 6); + sample.setActiveCalories(complexParser.get(2, 6)); } } @@ -114,7 +118,9 @@ public class DailyDetailsParser extends XiaomiActivityParser { } if (complexParser.nextGroup(16)) { - // TODO distance + if (complexParser.hasFirst()) { + sample.setDistanceCm(complexParser.get(0, 16) * 100); + } } if (complexParser.nextGroup(8)) { @@ -127,6 +133,7 @@ public class DailyDetailsParser extends XiaomiActivityParser { if (complexParser.nextGroup(8)) { if (complexParser.hasFirst()) { // energy, 8 bits + sample.setEnergy(complexParser.get(0, 8)); } }