diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 52e34c2eb..a8b381019 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -45,7 +45,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(71, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(72, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -1167,6 +1167,9 @@ public class GBDaoGenerator { workoutDataSample.addByteArrayProperty("dataErrorHex"); + workoutDataSample.addShortProperty("calories").notNull(); + workoutDataSample.addShortProperty("cyclingPower").notNull(); + return workoutDataSample; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java new file mode 100644 index 000000000..21bc21dde --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_72.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2024 Martin.JM + + 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.database.schema; + +import android.database.sqlite.SQLiteDatabase; + +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; +import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao; + +public class GadgetbridgeUpdate_72 implements DBUpdateScript { + @Override + public void upgradeSchema(final SQLiteDatabase db) { + if (!DBHelper.existsColumn(HuaweiWorkoutDataSampleDao.TABLENAME, HuaweiWorkoutDataSampleDao.Properties.Calories.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutDataSampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutDataSampleDao.Properties.Calories.columnName + "\" INTEGER NOT NULL DEFAULT -1"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutDataSampleDao.TABLENAME, HuaweiWorkoutDataSampleDao.Properties.CyclingPower.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutDataSampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutDataSampleDao.Properties.CyclingPower.columnName + "\" INTEGER NOT NULL DEFAULT -1"; + db.execSQL(statement); + } + } + + @Override + public void downgradeSchema(final SQLiteDatabase db) { + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java index 74bf0dfdf..e725eb20e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java @@ -239,12 +239,15 @@ public class Workout { public byte swolf = -1; public short strokeRate = -1; + public short calories = -1; + public short cyclingPower = -1; + public int timestamp = -1; // Calculated timestamp for this data point @Override public String toString() { return "Data{" + - "unknownData=" + unknownData + + "unknownData=" + Arrays.toString(unknownData) + ", heartRate=" + heartRate + ", speed=" + speed + ", stepRate=" + stepRate + @@ -259,13 +262,15 @@ public class Workout { ", eversionAngle=" + eversionAngle + ", swolf=" + swolf + ", strokeRate=" + strokeRate + + ", calories=" + calories + + ", cyclingPower=" + cyclingPower + ", timestamp=" + timestamp + '}'; } } // I'm not sure about the lengths, but we haven't gotten any complaints so they probably are fine - private final byte[] bitmapLengths = {1, 2, 1, 2, 2, 4, -1, 2, 2, 1, 1, 1, 1, 1, 1, 1}; + private final byte[] bitmapLengths = {1, 2, 1, 2, 2, 4, -1, 2, 2, 2, 1, 1, 1, 1, 1, 1}; private final byte[] innerBitmapLengths = {2, 2, 2, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1}; public short workoutNumber; @@ -410,6 +415,12 @@ public class Workout { } } break; + case 7: + data.calories = buf.getShort(); + break; + case 8: + data.cyclingPower = buf.getShort(); + break; default: data.unknownData = this.tlv.serialize(); // Fix alignment diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java index bb062cc4d..a23847460 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java @@ -106,6 +106,10 @@ public class ActivitySummaryEntries { public static final String MAXIMUM_OXYGEN_UPTAKE = "maximumOxygenUptake"; public static final String RECOVERY_TIME = "recoveryTime"; + public static final String CYCLING_POWER_AVERAGE = "cyclingPowerAverage"; + public static final String CYCLING_POWER_MIN = "cyclingPowerMin"; + public static final String CYCLING_POWER_MAX = "cyclingPowerMax"; + public static final String UNIT_BPM = "bpm"; public static final String UNIT_CM = "cm"; public static final String UNIT_UNIX_EPOCH_SECONDS = "unix_epoch_seconds"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index 28ce5b4ee..7722676a4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -1513,7 +1513,9 @@ public class HuaweiSupportProvider { data.eversionAngle, data.swolf, data.strokeRate, - unknown + unknown, + data.calories, + data.cyclingPower ); dao.insertOrReplace(dataSample); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java index 09d6634ed..ee552b264 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java @@ -106,7 +106,9 @@ public class HuaweiWorkoutGbParser { responseData.eversionAngle, responseData.swolf, responseData.strokeRate, - dataErrorHex + dataErrorHex, + responseData.calories, + responseData.cyclingPower ); dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().insertOrReplace(dataSample); @@ -301,6 +303,11 @@ public class HuaweiWorkoutGbParser { int heartRateCount = 0; int maxHeartRate = 0; int minHeartRate = Integer.MAX_VALUE; + int sumCalories = 0; + int minCyclingPower = Integer.MAX_VALUE; + int maxCyclingPower = 0; + int cyclingPower = 0; + int cyclingPowerCount = 0; for (HuaweiWorkoutDataSample dataSample : dataSamples) { if (dataSample.getSpeed() != -1) { speed += dataSample.getSpeed(); @@ -373,6 +380,17 @@ public class HuaweiWorkoutGbParser { if (hr < minHeartRate) minHeartRate = hr; } + if (dataSample.getCalories() != -1) + sumCalories += dataSample.getCalories(); + if (dataSample.getCyclingPower() != -1) { + int cp = dataSample.getCyclingPower(); + cyclingPower += cp; + cyclingPowerCount += 1; + if (cp > maxCyclingPower) + maxCyclingPower = cp; + if (cp < minCyclingPower) + minCyclingPower = cp; + } if (dataSample.getDataErrorHex() != null) unknownData = true; } @@ -400,6 +418,8 @@ public class HuaweiWorkoutGbParser { strokeRate = strokeRate / strokeRateCount; if (heartRateCount > 0) heartRate = heartRate / heartRateCount; + if (cyclingPowerCount > 0) + cyclingPower = cyclingPower / cyclingPowerCount; if (speedCount > 0) { JSONObject speedJson = new JSONObject(); @@ -534,6 +554,30 @@ public class HuaweiWorkoutGbParser { minHeartRateJson.put("unit", ActivitySummaryEntries.UNIT_BPM); jsonObject.put(ActivitySummaryEntries.HR_MIN, minHeartRateJson); } + + if (sumCalories > 0) { + JSONObject caloriesSumJson = new JSONObject(); + caloriesSumJson.put("value", sumCalories); + caloriesSumJson.put("unit", ActivitySummaryEntries.UNIT_KCAL); + jsonObject.put(ActivitySummaryEntries.CALORIES_BURNT, caloriesSumJson); + } + + if (cyclingPowerCount > 0) { + JSONObject cyclingPowerJson = new JSONObject(); + cyclingPowerJson.put("value", cyclingPower); + cyclingPowerJson.put("unit", ""); + jsonObject.put(ActivitySummaryEntries.CYCLING_POWER_AVERAGE, cyclingPowerJson); + + JSONObject minCyclingPowerJson = new JSONObject(); + minCyclingPowerJson.put("value", minCyclingPower); + minCyclingPowerJson.put("unit", ""); + jsonObject.put(ActivitySummaryEntries.CYCLING_POWER_MIN, minCyclingPowerJson); + + JSONObject maxCyclingPowerJson = new JSONObject(); + maxCyclingPowerJson.put("value", maxCyclingPower); + maxCyclingPowerJson.put("unit", ""); + jsonObject.put(ActivitySummaryEntries.CYCLING_POWER_MAX, maxCyclingPowerJson); + } } try (CloseableListIterator it = qbPace.build().listIterator()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9a2717a8c..fa1ebc3c8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1907,6 +1907,9 @@ Pace %d correction Pace Type %d average Unknown data encountered + Average cycling power + Min cycling power + Max cycling power m cm