1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-19 23:27:48 +01:00

[Huawei] Add support for workout calories and cycling power

This commit is contained in:
Martin.JM 2024-04-30 21:08:23 +02:00
parent 013ffe5559
commit 1c2c1f710e
7 changed files with 115 additions and 5 deletions

View File

@ -45,7 +45,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception { 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 userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@ -1167,6 +1167,9 @@ public class GBDaoGenerator {
workoutDataSample.addByteArrayProperty("dataErrorHex"); workoutDataSample.addByteArrayProperty("dataErrorHex");
workoutDataSample.addShortProperty("calories").notNull();
workoutDataSample.addShortProperty("cyclingPower").notNull();
return workoutDataSample; return workoutDataSample;
} }

View File

@ -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 <https://www.gnu.org/licenses/>. */
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) {
}
}

View File

@ -239,12 +239,15 @@ public class Workout {
public byte swolf = -1; public byte swolf = -1;
public short strokeRate = -1; public short strokeRate = -1;
public short calories = -1;
public short cyclingPower = -1;
public int timestamp = -1; // Calculated timestamp for this data point public int timestamp = -1; // Calculated timestamp for this data point
@Override @Override
public String toString() { public String toString() {
return "Data{" + return "Data{" +
"unknownData=" + unknownData + "unknownData=" + Arrays.toString(unknownData) +
", heartRate=" + heartRate + ", heartRate=" + heartRate +
", speed=" + speed + ", speed=" + speed +
", stepRate=" + stepRate + ", stepRate=" + stepRate +
@ -259,13 +262,15 @@ public class Workout {
", eversionAngle=" + eversionAngle + ", eversionAngle=" + eversionAngle +
", swolf=" + swolf + ", swolf=" + swolf +
", strokeRate=" + strokeRate + ", strokeRate=" + strokeRate +
", calories=" + calories +
", cyclingPower=" + cyclingPower +
", timestamp=" + timestamp + ", timestamp=" + timestamp +
'}'; '}';
} }
} }
// I'm not sure about the lengths, but we haven't gotten any complaints so they probably are fine // 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}; private final byte[] innerBitmapLengths = {2, 2, 2, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1};
public short workoutNumber; public short workoutNumber;
@ -410,6 +415,12 @@ public class Workout {
} }
} }
break; break;
case 7:
data.calories = buf.getShort();
break;
case 8:
data.cyclingPower = buf.getShort();
break;
default: default:
data.unknownData = this.tlv.serialize(); data.unknownData = this.tlv.serialize();
// Fix alignment // Fix alignment

View File

@ -106,6 +106,10 @@ public class ActivitySummaryEntries {
public static final String MAXIMUM_OXYGEN_UPTAKE = "maximumOxygenUptake"; public static final String MAXIMUM_OXYGEN_UPTAKE = "maximumOxygenUptake";
public static final String RECOVERY_TIME = "recoveryTime"; 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_BPM = "bpm";
public static final String UNIT_CM = "cm"; public static final String UNIT_CM = "cm";
public static final String UNIT_UNIX_EPOCH_SECONDS = "unix_epoch_seconds"; public static final String UNIT_UNIX_EPOCH_SECONDS = "unix_epoch_seconds";

View File

@ -1513,7 +1513,9 @@ public class HuaweiSupportProvider {
data.eversionAngle, data.eversionAngle,
data.swolf, data.swolf,
data.strokeRate, data.strokeRate,
unknown unknown,
data.calories,
data.cyclingPower
); );
dao.insertOrReplace(dataSample); dao.insertOrReplace(dataSample);
} }

View File

@ -106,7 +106,9 @@ public class HuaweiWorkoutGbParser {
responseData.eversionAngle, responseData.eversionAngle,
responseData.swolf, responseData.swolf,
responseData.strokeRate, responseData.strokeRate,
dataErrorHex dataErrorHex,
responseData.calories,
responseData.cyclingPower
); );
dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().insertOrReplace(dataSample); dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().insertOrReplace(dataSample);
@ -301,6 +303,11 @@ public class HuaweiWorkoutGbParser {
int heartRateCount = 0; int heartRateCount = 0;
int maxHeartRate = 0; int maxHeartRate = 0;
int minHeartRate = Integer.MAX_VALUE; 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) { for (HuaweiWorkoutDataSample dataSample : dataSamples) {
if (dataSample.getSpeed() != -1) { if (dataSample.getSpeed() != -1) {
speed += dataSample.getSpeed(); speed += dataSample.getSpeed();
@ -373,6 +380,17 @@ public class HuaweiWorkoutGbParser {
if (hr < minHeartRate) if (hr < minHeartRate)
minHeartRate = hr; 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) if (dataSample.getDataErrorHex() != null)
unknownData = true; unknownData = true;
} }
@ -400,6 +418,8 @@ public class HuaweiWorkoutGbParser {
strokeRate = strokeRate / strokeRateCount; strokeRate = strokeRate / strokeRateCount;
if (heartRateCount > 0) if (heartRateCount > 0)
heartRate = heartRate / heartRateCount; heartRate = heartRate / heartRateCount;
if (cyclingPowerCount > 0)
cyclingPower = cyclingPower / cyclingPowerCount;
if (speedCount > 0) { if (speedCount > 0) {
JSONObject speedJson = new JSONObject(); JSONObject speedJson = new JSONObject();
@ -534,6 +554,30 @@ public class HuaweiWorkoutGbParser {
minHeartRateJson.put("unit", ActivitySummaryEntries.UNIT_BPM); minHeartRateJson.put("unit", ActivitySummaryEntries.UNIT_BPM);
jsonObject.put(ActivitySummaryEntries.HR_MIN, minHeartRateJson); 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<HuaweiWorkoutPaceSample> it = qbPace.build().listIterator()) { try (CloseableListIterator<HuaweiWorkoutPaceSample> it = qbPace.build().listIterator()) {

View File

@ -1907,6 +1907,9 @@
<string name="fmtPaceCorrection">Pace %d correction</string> <string name="fmtPaceCorrection">Pace %d correction</string>
<string name="fmtPaceTypeAverage">Pace Type %d average</string> <string name="fmtPaceTypeAverage">Pace Type %d average</string>
<string name="unknownDataEncountered">Unknown data encountered</string> <string name="unknownDataEncountered">Unknown data encountered</string>
<string name="cyclingPowerAverage">Average cycling power</string>
<string name="cyclingPowerMin">Min cycling power</string>
<string name="cyclingPowerMax">Max cycling power</string>
<!-- activity summary units--> <!-- activity summary units-->
<string name="meters">m</string> <string name="meters">m</string>
<string name="cm">cm</string> <string name="cm">cm</string>