mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Colmi V76: Fix workout parsing
This commit is contained in:
+16
-8
@@ -48,20 +48,21 @@ public class MoyoungWorkoutSummaryParser implements ActivitySummaryParser {
|
||||
|
||||
final ActivitySummaryData summaryData = new ActivitySummaryData();
|
||||
|
||||
int protocolVersion = 0;
|
||||
final int protocolVersion;
|
||||
if (rawSummaryData.length % 24 == 0) {
|
||||
protocolVersion = 1;
|
||||
} else if (rawSummaryData.length % 26 == 0) {
|
||||
protocolVersion = 2;
|
||||
}
|
||||
if (protocolVersion == 0) {
|
||||
LOG.error("Invalid raw data");
|
||||
} else if (rawSummaryData.length % 30 == 0) {
|
||||
protocolVersion = 3;
|
||||
} else {
|
||||
LOG.error("Unknown protocol version");
|
||||
return summary;
|
||||
}
|
||||
|
||||
byte num = 0;
|
||||
byte avgHR = 0;
|
||||
if (protocolVersion == 2) {
|
||||
if (protocolVersion == 2 || protocolVersion == 3) {
|
||||
buffer.get(); // skip packet subtype
|
||||
num = buffer.get();
|
||||
}
|
||||
@@ -70,8 +71,10 @@ public class MoyoungWorkoutSummaryParser implements ActivitySummaryParser {
|
||||
int validTime = buffer.getShort();
|
||||
if (protocolVersion == 1) {
|
||||
num = buffer.get(); // == i
|
||||
} else {
|
||||
} else if (protocolVersion == 2) {
|
||||
avgHR = buffer.get();
|
||||
} else {
|
||||
buffer.get(); // unknown (always 0x00)
|
||||
}
|
||||
byte type = buffer.get();
|
||||
int steps = buffer.getInt();
|
||||
@@ -79,15 +82,20 @@ public class MoyoungWorkoutSummaryParser implements ActivitySummaryParser {
|
||||
int calories;
|
||||
if (protocolVersion == 1) {
|
||||
calories = buffer.getShort();
|
||||
} else {
|
||||
} else if (protocolVersion == 2) {
|
||||
calories = buffer.getInt();
|
||||
} else {
|
||||
calories = buffer.getShort();
|
||||
avgHR = buffer.get();
|
||||
buffer.get(); // 0?
|
||||
// todo last 4 bytes?
|
||||
}
|
||||
|
||||
summaryData.add(ActivitySummaryEntries.ACTIVE_SECONDS, validTime, ActivitySummaryEntries.UNIT_SECONDS);
|
||||
summaryData.add(ActivitySummaryEntries.STEPS, steps, ActivitySummaryEntries.UNIT_STEPS);
|
||||
summaryData.add(ActivitySummaryEntries.DISTANCE_METERS, distance, ActivitySummaryEntries.UNIT_METERS);
|
||||
summaryData.add(ActivitySummaryEntries.CALORIES_BURNT, calories, ActivitySummaryEntries.UNIT_KCAL);
|
||||
if (protocolVersion == 2) {
|
||||
if (avgHR > 0) {
|
||||
summaryData.add(ActivitySummaryEntries.HR_AVG, avgHR, ActivitySummaryEntries.UNIT_BPM);
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -116,6 +116,15 @@ public class MoyoungActivitySampleProvider extends AbstractSampleProvider<Moyoun
|
||||
return MoyoungConstants.WORKOUT_TYPES_TO_ACTIVITY_KIND.getOrDefault((byte) rawType, ActivityKind.ACTIVITY);
|
||||
}
|
||||
|
||||
// FIXME we should actually persist the raw kinds...
|
||||
public ActivityKind normalizeTypeV3(int rawType) {
|
||||
return switch (rawType) {
|
||||
case 13 -> ActivityKind.WALKING; // gps on foot?
|
||||
case 14 -> ActivityKind.WALKING;
|
||||
default -> ActivityKind.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(ActivityKind activityKind) {
|
||||
if (activityKind == ActivityKind.NOT_MEASURED)
|
||||
|
||||
+26
-12
@@ -1339,26 +1339,33 @@ public class MoyoungDeviceSupport extends AbstractBTLESingleDeviceSupport {
|
||||
}
|
||||
|
||||
public void handleTrainingData(byte[] data) {
|
||||
int protocolVersion = 0;
|
||||
int trainingBytesLength = 24;
|
||||
final int protocolVersion;
|
||||
final int trainingBytesLength;
|
||||
if (data.length % 24 == 0) {
|
||||
protocolVersion = 1;
|
||||
trainingBytesLength = 24;
|
||||
} else if (data.length % 26 == 0) {
|
||||
protocolVersion = 2;
|
||||
trainingBytesLength = 26;
|
||||
}
|
||||
if (protocolVersion == 0) {
|
||||
LOG.error("Invalid training data received");
|
||||
} else if (data.length % 30 == 0) {
|
||||
protocolVersion = 3;
|
||||
trainingBytesLength = 30;
|
||||
} else {
|
||||
LOG.error("Invalid training data received of length {}", data.length);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.length / trainingBytesLength; i++) {
|
||||
if (protocolVersion == 1 && ArrayUtils.isAllZeros(data, trainingBytesLength * i, trainingBytesLength)) {
|
||||
LOG.info("Skipping empty workout details packet");
|
||||
LOG.info("Skipping empty workout details packet v1");
|
||||
continue;
|
||||
}
|
||||
if (protocolVersion == 2 && ArrayUtils.isAllZeros(data, 2, trainingBytesLength - 2)) {
|
||||
LOG.info("Skipping empty workout details packet");
|
||||
LOG.info("Skipping empty workout details packet v2");
|
||||
continue;
|
||||
}
|
||||
if (protocolVersion == 3 && ArrayUtils.isAllZeros(data, trainingBytesLength * i + 2, 24)) {
|
||||
LOG.info("Skipping empty workout details packet v3");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1366,7 +1373,7 @@ public class MoyoungDeviceSupport extends AbstractBTLESingleDeviceSupport {
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
byte num = 0;
|
||||
byte avgHR = 0;
|
||||
if (protocolVersion == 2) {
|
||||
if (protocolVersion == 2 || protocolVersion == 3) {
|
||||
buffer.get(); // skip packet subtype
|
||||
num = buffer.get();
|
||||
}
|
||||
@@ -1375,8 +1382,10 @@ public class MoyoungDeviceSupport extends AbstractBTLESingleDeviceSupport {
|
||||
int validTime = buffer.getShort();
|
||||
if (protocolVersion == 1) {
|
||||
num = buffer.get(); // == i
|
||||
} else {
|
||||
} else if (protocolVersion == 2) {
|
||||
avgHR = buffer.get();
|
||||
} else {
|
||||
buffer.get(); // unknown (always 0x00)
|
||||
}
|
||||
byte type = buffer.get();
|
||||
int steps = buffer.getInt();
|
||||
@@ -1384,8 +1393,13 @@ public class MoyoungDeviceSupport extends AbstractBTLESingleDeviceSupport {
|
||||
int calories;
|
||||
if (protocolVersion == 1) {
|
||||
calories = buffer.getShort();
|
||||
} else {
|
||||
} else if (protocolVersion == 2) {
|
||||
calories = buffer.getInt();
|
||||
} else {
|
||||
calories = buffer.getShort();
|
||||
avgHR = buffer.get();
|
||||
buffer.get(); // 0?
|
||||
// todo last 4 bytes?
|
||||
}
|
||||
LOG.info("Training data: start={} end={} totalTimeWithoutPause={} num={} type={} steps={} avgHR={} distance={} calories={}", startTime, endTime, validTime, num, type, steps, avgHR, distance, calories);
|
||||
|
||||
@@ -1415,8 +1429,8 @@ public class MoyoungDeviceSupport extends AbstractBTLESingleDeviceSupport {
|
||||
summary.setDevice(device);
|
||||
summary.setUser(user);
|
||||
|
||||
ActivityKind gbType = provider.normalizeType(type);
|
||||
summary.setName(gbType.name());
|
||||
ActivityKind gbType = protocolVersion < 3 ? provider.normalizeType(type) : provider.normalizeTypeV3(type);
|
||||
summary.setName(gbType.getLabel(getContext()));
|
||||
summary.setActivityKind(gbType.getCode());
|
||||
|
||||
summary.setStartTime(startTime);
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.moyoung;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class MoyoungWorkoutSummaryParserTest extends TestBase {
|
||||
@Test
|
||||
public void testParseWorkout1() {
|
||||
// #6053 - 00:36:34, 2.3km, 163kcal, 15'41'', 2767 steps, 76 spm, 89bpm
|
||||
final BaseActivitySummary summary = new BaseActivitySummary();
|
||||
summary.setRawSummaryData(GB.hexStringToByteArray("030D257C126AB984126A92080022CF0A00001A090000A3005900A9C1C440"));
|
||||
|
||||
new MoyoungWorkoutSummaryParser(null).parseBinaryData(summary, false);
|
||||
|
||||
final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summary.getSummaryData());
|
||||
assertNotNull(summaryData);
|
||||
|
||||
assertEquals((double) (36 * 60 + 34), summaryData.getNumber(ActivitySummaryEntries.ACTIVE_SECONDS, -1));
|
||||
assertEquals(2330d, summaryData.getNumber(ActivitySummaryEntries.DISTANCE_METERS, -1));
|
||||
assertEquals(163d, summaryData.getNumber(ActivitySummaryEntries.CALORIES_BURNT, -1));
|
||||
assertEquals(2767d, summaryData.getNumber(ActivitySummaryEntries.STEPS, -1));
|
||||
assertEquals(89d, summaryData.getNumber(ActivitySummaryEntries.HR_AVG, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseWorkout2() {
|
||||
// #6053 - 00:32:01, 1.7km, 122kcal, 18'20'', 2011 steps, 62 spm, 93bpm
|
||||
final BaseActivitySummary summary = new BaseActivitySummary();
|
||||
summary.setRawSummaryData(GB.hexStringToByteArray("030E9BAF136A1CB7136A8107001EDB070000D20600007A005D003DB8C740"));
|
||||
|
||||
new MoyoungWorkoutSummaryParser(null).parseBinaryData(summary, false);
|
||||
|
||||
final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summary.getSummaryData());
|
||||
assertNotNull(summaryData);
|
||||
|
||||
assertEquals((double) (32 * 60 + 1), summaryData.getNumber(ActivitySummaryEntries.ACTIVE_SECONDS, -1));
|
||||
assertEquals(1746d, summaryData.getNumber(ActivitySummaryEntries.DISTANCE_METERS, -1));
|
||||
assertEquals(122d, summaryData.getNumber(ActivitySummaryEntries.CALORIES_BURNT, -1));
|
||||
assertEquals(2011d, summaryData.getNumber(ActivitySummaryEntries.STEPS, -1));
|
||||
assertEquals(93d, summaryData.getNumber(ActivitySummaryEntries.HR_AVG, -1));
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.moyoung;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class MoyoungDeviceSupportTest extends TestBase {
|
||||
@Test
|
||||
public void testWorkoutColumnV76() {
|
||||
final MoyoungDeviceSupport support = new MoyoungDeviceSupport();
|
||||
// #6053
|
||||
final byte[][] packets = new byte[][] {
|
||||
GB.hexStringToByteArray("FEEA2023B2030000000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030100000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030200000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030300000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030400000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030500000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030600000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030700000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030800000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030900000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030A00000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030B00000000000000000000000000000000000000000000000000000000"),
|
||||
GB.hexStringToByteArray("FEEA2023B2030C00000000000000000000000000000000000000000000000052B8CB41"),
|
||||
|
||||
// 00:36:34
|
||||
// 2.3km
|
||||
// 163kcal
|
||||
// 15'41"
|
||||
// 2767 steps
|
||||
// 76 steps/min
|
||||
// 80bpm
|
||||
GB.hexStringToByteArray("FEEA2023B2030D257C126AB984126A92080022CF0A00001A090000A3005900A9C1C440"),
|
||||
|
||||
// 00:32:01
|
||||
// 1.7km
|
||||
// 122kcal
|
||||
// 18'20"
|
||||
// GPS trace
|
||||
// 2011 steps
|
||||
// 62 steps/min
|
||||
// 93bpm
|
||||
GB.hexStringToByteArray("FEEA2023B2030E9BAF136A1CB7136A8107001EDB070000D20600007A005D003DB8C740")
|
||||
};
|
||||
MoyoungPacketIn packetIn = new MoyoungPacketIn();
|
||||
for (byte[] value : packets) {
|
||||
if (packetIn.putFragment(value)) {
|
||||
Pair<Byte, byte[]> packet = MoyoungPacketIn.parsePacket(packetIn.getPacket());
|
||||
packetIn = new MoyoungPacketIn();
|
||||
|
||||
if (packet != null) {
|
||||
byte packetType = packet.first;
|
||||
byte[] payload = packet.second;
|
||||
support.handleTrainingData(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user