[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 {
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;
}

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 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

View File

@ -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";

View File

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

View File

@ -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<HuaweiWorkoutPaceSample> it = qbPace.build().listIterator()) {

View File

@ -1907,6 +1907,9 @@
<string name="fmtPaceCorrection">Pace %d correction</string>
<string name="fmtPaceTypeAverage">Pace Type %d average</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-->
<string name="meters">m</string>
<string name="cm">cm</string>