mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-15 14:39:26 +01:00
Garmin: Improve fit parsing
* Remove the dependency on PredefinedLocalMessage from generic fit parsing code * Standardize toString methods, omit types for known fields * Return null on unknown field number or names, instead of crashing * Map more Global FIT messages (device info, monitoring, sleep stages, sleep stats, stress level) * Prioritize "timestamp" over "253_timestamp" if specified explicitly in the global message definition * Introduce RecordData wrappers for each global message, allowing us to have proper types when getting data. If missing or unknown, the getter returns null. All classes are auto-generated by the FitCodeGen. * Persist a list of RecordData, instead of a Map from RecordDefinition * Fix parsing of compressed timestamps - keep them in computedTimestamp on each data record * Use timestamp16 if available in Monitoring records
This commit is contained in:
parent
9e1bf07cd2
commit
1b45d71cc7
@ -317,15 +317,19 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
|
||||
List<RecordData> weatherData = new ArrayList<>();
|
||||
|
||||
final RecordDefinition recordDefinitionToday = PredefinedLocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition();
|
||||
final RecordDefinition recordDefinitionHourly = PredefinedLocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition();
|
||||
final RecordDefinition recordDefinitionDaily = PredefinedLocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition();
|
||||
|
||||
List<RecordDefinition> weatherDefinitions = new ArrayList<>(3);
|
||||
weatherDefinitions.add(PredefinedLocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition());
|
||||
weatherDefinitions.add(PredefinedLocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition());
|
||||
weatherDefinitions.add(PredefinedLocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition());
|
||||
weatherDefinitions.add(recordDefinitionToday);
|
||||
weatherDefinitions.add(recordDefinitionHourly);
|
||||
weatherDefinitions.add(recordDefinitionDaily);
|
||||
|
||||
sendOutgoingMessage(new nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDefinitionMessage(weatherDefinitions));
|
||||
|
||||
try {
|
||||
RecordData today = new RecordData(PredefinedLocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition());
|
||||
RecordData today = new RecordData(recordDefinitionToday, recordDefinitionToday.getRecordHeader());
|
||||
today.setFieldByName("weather_report", 0); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
|
||||
today.setFieldByName("timestamp", weather.timestamp);
|
||||
today.setFieldByName("observed_at_time", weather.timestamp);
|
||||
@ -346,7 +350,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
for (int hour = 0; hour <= 11; hour++) {
|
||||
if (hour < weather.hourly.size()) {
|
||||
WeatherSpec.Hourly hourly = weather.hourly.get(hour);
|
||||
RecordData weatherHourlyForecast = new RecordData(PredefinedLocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition());
|
||||
RecordData weatherHourlyForecast = new RecordData(recordDefinitionHourly, recordDefinitionHourly.getRecordHeader());
|
||||
weatherHourlyForecast.setFieldByName("weather_report", 1); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
|
||||
weatherHourlyForecast.setFieldByName("timestamp", hourly.timestamp);
|
||||
weatherHourlyForecast.setFieldByName("temperature", hourly.temp);
|
||||
@ -362,7 +366,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
}
|
||||
}
|
||||
//
|
||||
RecordData todayDailyForecast = new RecordData(PredefinedLocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition());
|
||||
RecordData todayDailyForecast = new RecordData(recordDefinitionDaily, recordDefinitionDaily.getRecordHeader());
|
||||
todayDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
|
||||
todayDailyForecast.setFieldByName("timestamp", weather.timestamp);
|
||||
todayDailyForecast.setFieldByName("low_temperature", weather.todayMinTemp);
|
||||
@ -377,7 +381,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
||||
if (day < weather.forecasts.size()) {
|
||||
WeatherSpec.Daily daily = weather.forecasts.get(day);
|
||||
int ts = weather.timestamp + (day + 1) * 24 * 60 * 60; //TODO: is this needed?
|
||||
RecordData weatherDailyForecast = new RecordData(PredefinedLocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition());
|
||||
RecordData weatherDailyForecast = new RecordData(recordDefinitionDaily, recordDefinitionDaily.getRecordHeader());
|
||||
weatherDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
|
||||
weatherDailyForecast.setFieldByName("timestamp", weather.timestamp);
|
||||
weatherDailyForecast.setFieldByName("low_temperature", daily.minTemp);
|
||||
|
@ -1,5 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||
@ -8,6 +11,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefi
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||
|
||||
public class FieldDefinition implements FieldInterface {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(FieldDefinition.class);
|
||||
|
||||
protected final BaseType baseType;
|
||||
protected final int scale;
|
||||
protected final int offset;
|
||||
@ -33,13 +38,19 @@ public class FieldDefinition implements FieldInterface {
|
||||
int size = garminByteBufferReader.readByte();
|
||||
int baseTypeIdentifier = garminByteBufferReader.readByte();
|
||||
BaseType baseType = BaseType.fromIdentifier(baseTypeIdentifier);
|
||||
if (number == 253 && size == 4 && baseType.equals(BaseType.UINT32))
|
||||
return new FieldDefinitionTimestamp(number, size, baseType, "253_timestamp");
|
||||
|
||||
FieldDefinition global = globalFITMessage.getFieldDefinition(number, size);
|
||||
if (null != global && global.getBaseType().equals(baseType)) {
|
||||
if (global != null) {
|
||||
if (global.getBaseType().equals(baseType)) {
|
||||
return global;
|
||||
} else {
|
||||
LOG.warn("Global is of type {}, but message declares {}", global.getBaseType(), baseType);
|
||||
}
|
||||
}
|
||||
|
||||
if (number == 253 && size == 4 && baseType.equals(BaseType.UINT32)) {
|
||||
return new FieldDefinitionTimestamp(number, size, baseType, "253_timestamp");
|
||||
}
|
||||
|
||||
return new FieldDefinition(number, size, baseType, "");
|
||||
}
|
||||
|
||||
|
@ -8,16 +8,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefi
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTemperature;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition;
|
||||
|
||||
public class FieldDefinitionFactory {
|
||||
|
||||
public static FieldDefinition create(int localNumber, int size, BaseType baseType, String name, int scale, int offset) {
|
||||
return new FieldDefinition(localNumber, size, baseType, name, scale, offset);
|
||||
}
|
||||
|
||||
public static FieldDefinition create(int localNumber, int size, FIELD field, BaseType baseType, String name, int scale, int offset) {
|
||||
if (null == field) {
|
||||
return new FieldDefinition(localNumber, size, baseType, name, scale, offset);
|
||||
@ -43,6 +39,8 @@ public class FieldDefinitionFactory {
|
||||
return new FieldDefinitionWeatherCondition(localNumber, size, baseType, name);
|
||||
case LANGUAGE:
|
||||
return new FieldDefinitionLanguage(localNumber, size, baseType, name);
|
||||
case SLEEP_STAGE:
|
||||
return new FieldDefinitionSleepStage(localNumber, size, baseType, name);
|
||||
default:
|
||||
return new FieldDefinition(localNumber, size, baseType, name);
|
||||
}
|
||||
@ -59,5 +57,6 @@ public class FieldDefinitionFactory {
|
||||
TIMESTAMP,
|
||||
WEATHER_CONDITION,
|
||||
LANGUAGE,
|
||||
SLEEP_STAGE,
|
||||
}
|
||||
}
|
||||
|
@ -13,27 +13,27 @@ import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecordDataFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||
|
||||
public class FitFile {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(FitFile.class);
|
||||
private final Header header;
|
||||
private final Map<RecordDefinition, List<RecordData>> dataRecords;
|
||||
private final List<RecordData> dataRecords;
|
||||
private final boolean canGenerateOutput;
|
||||
|
||||
public FitFile(Header header, Map<RecordDefinition, List<RecordData>> dataRecords) {
|
||||
public FitFile(Header header, List<RecordData> dataRecords) {
|
||||
this.header = header;
|
||||
this.dataRecords = dataRecords;
|
||||
this.canGenerateOutput = false;
|
||||
}
|
||||
|
||||
public FitFile(LinkedHashMap<RecordDefinition, List<RecordData>> dataRecords) {
|
||||
public FitFile(List<RecordData> dataRecords) {
|
||||
this.dataRecords = dataRecords;
|
||||
this.header = new Header(true, 16, 21117);
|
||||
this.canGenerateOutput = true;
|
||||
@ -64,35 +64,42 @@ public class FitFile {
|
||||
|
||||
final Header header = Header.parseIncomingHeader(garminByteBufferReader);
|
||||
|
||||
Map<RecordHeader, RecordDefinition> recordDefinitionMap = new HashMap<>(); //needed because the headers can be redefined in the file. The last header wins
|
||||
Map<RecordDefinition, List<RecordData>> dataRecords = new LinkedHashMap<>();
|
||||
// needed because the headers can be redefined in the file. The last header for a local message number wins
|
||||
Map<Integer, RecordDefinition> recordDefinitionMap = new HashMap<>();
|
||||
List<RecordData> dataRecords = new ArrayList<>();
|
||||
Long referenceTimestamp = null;
|
||||
|
||||
while (garminByteBufferReader.getPosition() < header.getHeaderSize() + header.getDataSize()) {
|
||||
byte rawRecordHeader = (byte) garminByteBufferReader.readByte();
|
||||
RecordHeader recordHeader = new RecordHeader(rawRecordHeader);
|
||||
if (recordHeader.isCompressedTimestamp()) {
|
||||
referenceTimestamp += recordHeader.getTimeOffset();
|
||||
recordHeader.setReferenceTimestamp(referenceTimestamp);
|
||||
final Integer timeOffset = recordHeader.getTimeOffset();
|
||||
if (timeOffset != null) {
|
||||
if (referenceTimestamp == null) {
|
||||
throw new IllegalArgumentException("Got compressed timestamp without knowing current timestamp");
|
||||
}
|
||||
|
||||
if (timeOffset >= (referenceTimestamp & 0x1FL)) {
|
||||
referenceTimestamp = (referenceTimestamp & ~0x1FL) + timeOffset;
|
||||
} else if (timeOffset < (referenceTimestamp & 0x1FL)) {
|
||||
referenceTimestamp = (referenceTimestamp & ~0x1FL) + timeOffset + 0x20;
|
||||
}
|
||||
}
|
||||
if (recordHeader.isDefinition()) {
|
||||
final RecordDefinition recordDefinition = RecordDefinition.parseIncoming(garminByteBufferReader, recordHeader);
|
||||
if (recordDefinition != null) {
|
||||
if (recordHeader.isDeveloperData())
|
||||
for (RecordDefinition rd : dataRecords.keySet()) {
|
||||
for (RecordData rd : dataRecords) {
|
||||
if (GlobalFITMessage.FIELD_DESCRIPTION.equals(rd.getGlobalFITMessage()))
|
||||
recordDefinition.populateDevFields(dataRecords.get(rd));
|
||||
recordDefinition.populateDevFields(rd);
|
||||
}
|
||||
recordDefinitionMap.put(recordHeader, recordDefinition);
|
||||
dataRecords.put(recordDefinition, new ArrayList<>());
|
||||
recordDefinitionMap.put(recordHeader.getLocalMessageType(), recordDefinition);
|
||||
}
|
||||
} else {
|
||||
final RecordDefinition referenceRecordDefinition = recordDefinitionMap.get(recordHeader);
|
||||
final List<RecordData> myList = dataRecords.get(referenceRecordDefinition);
|
||||
final RecordDefinition referenceRecordDefinition = recordDefinitionMap.get(recordHeader.getLocalMessageType());
|
||||
if (referenceRecordDefinition != null) {
|
||||
final RecordData runningData = new RecordData(referenceRecordDefinition, recordHeader);
|
||||
myList.add(runningData);
|
||||
Long newTimestamp = runningData.parseDataMessage(garminByteBufferReader);
|
||||
final RecordData runningData = FitRecordDataFactory.create(referenceRecordDefinition, recordHeader);
|
||||
dataRecords.add(runningData);
|
||||
Long newTimestamp = runningData.parseDataMessage(garminByteBufferReader, referenceTimestamp);
|
||||
if (newTimestamp != null)
|
||||
referenceTimestamp = newTimestamp;
|
||||
}
|
||||
@ -108,28 +115,31 @@ public class FitFile {
|
||||
|
||||
public List<RecordData> getRecordsByGlobalMessage(GlobalFITMessage globalFITMessage) {
|
||||
final List<RecordData> filtered = new ArrayList<>();
|
||||
for (RecordDefinition rd : dataRecords.keySet()) {
|
||||
for (RecordData rd : dataRecords) {
|
||||
if (globalFITMessage.equals(rd.getGlobalFITMessage()))
|
||||
filtered.addAll(dataRecords.get(rd));
|
||||
filtered.add(rd);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public List<RecordData> getRecords() {
|
||||
return dataRecords;
|
||||
}
|
||||
|
||||
public void generateOutgoingDataPayload(MessageWriter writer) {
|
||||
if (!canGenerateOutput)
|
||||
throw new IllegalArgumentException("Generation of previously parsed FIT file not supported.");
|
||||
|
||||
MessageWriter temporary = new MessageWriter();
|
||||
temporary.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
for (Map.Entry<RecordDefinition, List<RecordData>> entry : dataRecords.entrySet()) {
|
||||
RecordDefinition key = entry.getKey();
|
||||
List<RecordData> valueList = entry.getValue();
|
||||
|
||||
key.generateOutgoingPayload(temporary);
|
||||
for (RecordData rd :
|
||||
valueList) {
|
||||
rd.generateOutgoingDataPayload(temporary);
|
||||
RecordDefinition prevDefinition = null;
|
||||
for (final RecordData rd : dataRecords) {
|
||||
if (!rd.getRecordDefinition().equals(prevDefinition)) {
|
||||
rd.getRecordDefinition().generateOutgoingPayload(temporary);
|
||||
prevDefinition = rd.getRecordDefinition();
|
||||
}
|
||||
|
||||
rd.generateOutgoingDataPayload(temporary);
|
||||
}
|
||||
this.header.setDataSize(temporary.getSize());
|
||||
|
||||
@ -145,7 +155,7 @@ public class FitFile {
|
||||
return dataRecords.toString();
|
||||
}
|
||||
|
||||
static class Header {
|
||||
public static class Header {
|
||||
public static final int MAGIC = 0x5449462E;
|
||||
|
||||
private final int headerSize;
|
||||
|
@ -87,10 +87,29 @@ public class GlobalFITMessage {
|
||||
new FieldDefinitionPrimitive(3, BaseType.UINT8, "heart_rate"),
|
||||
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
public static GlobalFITMessage DEVICE_INFO = new GlobalFITMessage(23, "DEVICE_INFO", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(2, BaseType.UINT16, "manufacturer"),
|
||||
new FieldDefinitionPrimitive(3, BaseType.UINT32Z, "serial_number"),
|
||||
new FieldDefinitionPrimitive(4, BaseType.UINT16, "product"),
|
||||
new FieldDefinitionPrimitive(5, BaseType.UINT16, "software_version"),
|
||||
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
public static GlobalFITMessage FILE_CREATOR = new GlobalFITMessage(49, "FILE_CREATOR", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.UINT16, "software_version"),
|
||||
new FieldDefinitionPrimitive(1, BaseType.UINT8, "hardware_version")
|
||||
));
|
||||
public static GlobalFITMessage MONITORING = new GlobalFITMessage(55, "MONITORING", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(2, BaseType.UINT32, "distance"),
|
||||
new FieldDefinitionPrimitive(3, BaseType.UINT32, "cycles"),
|
||||
new FieldDefinitionPrimitive(4, BaseType.UINT32, "active_time"),
|
||||
new FieldDefinitionPrimitive(5, BaseType.ENUM, "activity_type"),
|
||||
new FieldDefinitionPrimitive(19, BaseType.UINT16, "active_calories"),
|
||||
new FieldDefinitionPrimitive(29, BaseType.UINT16, "duration_min"),
|
||||
new FieldDefinitionPrimitive(24, BaseType.BASE_TYPE_BYTE, "current_activity_type_intensity"),
|
||||
new FieldDefinitionPrimitive(26, BaseType.UINT16, "timestamp_16"),
|
||||
new FieldDefinitionPrimitive(27, BaseType.UINT8, "heart_rate"),
|
||||
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
public static GlobalFITMessage CONNECTIVITY = new GlobalFITMessage(127, "CONNECTIVITY", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.ENUM, "bluetooth_enabled"),
|
||||
new FieldDefinitionPrimitive(3, BaseType.STRING, 20, "name"),
|
||||
@ -144,8 +163,19 @@ public class GlobalFITMessage {
|
||||
public static GlobalFITMessage ALARM_SETTINGS = new GlobalFITMessage(222, "ALARM_SETTINGS", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.UINT16, "time", FieldDefinitionFactory.FIELD.ALARM)
|
||||
));
|
||||
public static GlobalFITMessage STRESS_LEVEL = new GlobalFITMessage(227, "STRESS_LEVEL", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.SINT16, "stress_level_value"),
|
||||
new FieldDefinitionPrimitive(1, BaseType.UINT32, "stress_level_time", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
|
||||
public static Map<Integer, GlobalFITMessage> KNOWNMESSAGES = new HashMap<Integer, GlobalFITMessage>() {{
|
||||
public static GlobalFITMessage SLEEP_STAGE = new GlobalFITMessage(275, "SLEEP_STAGE", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.ENUM, "sleep_stage", FieldDefinitionFactory.FIELD.SLEEP_STAGE),
|
||||
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
public static GlobalFITMessage SLEEP_STATS = new GlobalFITMessage(346, "SLEEP_STATS", Arrays.asList(
|
||||
));
|
||||
|
||||
public static Map<Integer, GlobalFITMessage> KNOWN_MESSAGES = new HashMap<Integer, GlobalFITMessage>() {{
|
||||
put(0, FILE_ID);
|
||||
put(2, DEVICE_SETTINGS);
|
||||
put(3, USER_PROFILE);
|
||||
@ -153,13 +183,18 @@ public class GlobalFITMessage {
|
||||
put(12, SPORT);
|
||||
put(15, GOALS);
|
||||
put(20, RECORD);
|
||||
put(23, DEVICE_INFO);
|
||||
put(49, FILE_CREATOR);
|
||||
put(55, MONITORING);
|
||||
put(127, CONNECTIVITY);
|
||||
put(128, WEATHER);
|
||||
put(159, WATCHFACE_SETTINGS);
|
||||
put(206, FIELD_DESCRIPTION);
|
||||
put(207, DEVELOPER_DATA);
|
||||
put(222, ALARM_SETTINGS);
|
||||
put(227, STRESS_LEVEL);
|
||||
put(275, SLEEP_STAGE);
|
||||
put(346, SLEEP_STATS);
|
||||
}};
|
||||
private final int number;
|
||||
private final String name;
|
||||
@ -173,7 +208,7 @@ public class GlobalFITMessage {
|
||||
}
|
||||
|
||||
public static GlobalFITMessage fromNumber(final int number) {
|
||||
final GlobalFITMessage found = KNOWNMESSAGES.get(number);
|
||||
final GlobalFITMessage found = KNOWN_MESSAGES.get(number);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
@ -188,6 +223,10 @@ public class GlobalFITMessage {
|
||||
return number;
|
||||
}
|
||||
|
||||
public List<FieldDefinitionPrimitive> getFieldDefinitionPrimitives() {
|
||||
return fieldDefinitionPrimitives;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<FieldDefinition> getFieldDefinitions(int... ids) {
|
||||
if (null == fieldDefinitionPrimitives)
|
||||
@ -205,6 +244,25 @@ public class GlobalFITMessage {
|
||||
return subset;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldDefinition getFieldDefinition(String name) {
|
||||
for (FieldDefinitionPrimitive fieldDefinitionPrimitive :
|
||||
fieldDefinitionPrimitives) {
|
||||
if (fieldDefinitionPrimitive.name.equals(name)) {
|
||||
return FieldDefinitionFactory.create(
|
||||
fieldDefinitionPrimitive.number,
|
||||
fieldDefinitionPrimitive.size,
|
||||
fieldDefinitionPrimitive.type,
|
||||
fieldDefinitionPrimitive.baseType,
|
||||
fieldDefinitionPrimitive.name,
|
||||
fieldDefinitionPrimitive.scale,
|
||||
fieldDefinitionPrimitive.offset
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldDefinition getFieldDefinition(int id, int size) {
|
||||
if (null == fieldDefinitionPrimitives)
|
||||
@ -218,7 +276,7 @@ public class GlobalFITMessage {
|
||||
return null;
|
||||
}
|
||||
|
||||
static class FieldDefinitionPrimitive {
|
||||
public static class FieldDefinitionPrimitive {
|
||||
private final int number;
|
||||
private final BaseType baseType;
|
||||
private final String name;
|
||||
@ -252,5 +310,33 @@ public class GlobalFITMessage {
|
||||
public FieldDefinitionPrimitive(int number, BaseType baseType, String name, int scale, int offset) {
|
||||
this(number, baseType, baseType.getSize(), name, null, scale, offset);
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public BaseType getBaseType() {
|
||||
return baseType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public FieldDefinitionFactory.FIELD getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,16 @@ public enum PredefinedLocalMessage {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<FieldDefinition> getLocalFieldDefinitions() {
|
||||
return globalFITMessage.getFieldDefinitions(globalDefinitionIds);
|
||||
}
|
||||
|
||||
public RecordDefinition getRecordDefinition() {
|
||||
return new RecordDefinition(ByteOrder.BIG_ENDIAN, this);
|
||||
final RecordHeader recordHeader = new RecordHeader(true, false, type, null);
|
||||
final List<FieldDefinition> fieldDefinitions = globalFITMessage.getFieldDefinitions(globalDefinitionIds);
|
||||
return new RecordDefinition(
|
||||
recordHeader,
|
||||
ByteOrder.BIG_ENDIAN,
|
||||
globalFITMessage,
|
||||
fieldDefinitions,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
|
@ -6,28 +6,35 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBToStringBuilder;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType.STRING;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class RecordData {
|
||||
|
||||
private final RecordDefinition recordDefinition;
|
||||
private final RecordHeader recordHeader;
|
||||
private final GlobalFITMessage globalFITMessage;
|
||||
private final List<FieldData> fieldDataList;
|
||||
protected ByteBuffer valueHolder;
|
||||
|
||||
public RecordData(RecordDefinition recordDefinition, RecordHeader recordHeader) {
|
||||
private Long computedTimestamp = null;
|
||||
|
||||
public RecordData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
if (null == recordDefinition.getFieldDefinitions())
|
||||
throw new IllegalArgumentException("Cannot create record data without FieldDefinitions " + recordDefinition);
|
||||
|
||||
fieldDataList = new ArrayList<>();
|
||||
|
||||
this.recordDefinition = recordDefinition;
|
||||
this.recordHeader = recordHeader;
|
||||
this.globalFITMessage = recordDefinition.getGlobalFITMessage();
|
||||
|
||||
@ -58,22 +65,25 @@ public class RecordData {
|
||||
|
||||
}
|
||||
|
||||
public RecordData(RecordDefinition recordDefinition) {
|
||||
this(recordDefinition, recordDefinition.getRecordHeader());
|
||||
}
|
||||
|
||||
public GlobalFITMessage getGlobalFITMessage() {
|
||||
return globalFITMessage;
|
||||
}
|
||||
|
||||
public Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
||||
public RecordDefinition getRecordDefinition() {
|
||||
return recordDefinition;
|
||||
}
|
||||
|
||||
public Long parseDataMessage(final GarminByteBufferReader garminByteBufferReader, final Long currentTimestamp) {
|
||||
garminByteBufferReader.setByteOrder(valueHolder.order());
|
||||
computedTimestamp = currentTimestamp;
|
||||
Long referenceTimestamp = null;
|
||||
for (FieldData fieldData : fieldDataList) {
|
||||
Long runningTimestamp = fieldData.parseDataMessage(garminByteBufferReader);
|
||||
if (runningTimestamp != null)
|
||||
if (runningTimestamp != null) {
|
||||
computedTimestamp = runningTimestamp;
|
||||
referenceTimestamp = runningTimestamp;
|
||||
}
|
||||
}
|
||||
return referenceTimestamp;
|
||||
}
|
||||
|
||||
@ -119,7 +129,7 @@ public class RecordData {
|
||||
return fieldData.decode();
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown field number " + number);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getFieldByName(String name) {
|
||||
@ -129,7 +139,7 @@ public class RecordData {
|
||||
return fieldData.decode();
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown field name " + name);
|
||||
return null;
|
||||
}
|
||||
|
||||
public int[] getFieldsNumbers() {
|
||||
@ -143,45 +153,41 @@ public class RecordData {
|
||||
}
|
||||
|
||||
public Long getComputedTimestamp() {
|
||||
for (FieldData fieldData : fieldDataList) {
|
||||
if (fieldData.getNumber() == 253 || fieldData.fieldDefinition instanceof FieldDefinitionTimestamp)
|
||||
return (long) fieldData.decode();
|
||||
}
|
||||
if (recordHeader.isCompressedTimestamp())
|
||||
return (long) recordHeader.getResultingTimestamp();
|
||||
return null;
|
||||
return computedTimestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder oBuilder = new StringBuilder();
|
||||
oBuilder.append(System.lineSeparator());
|
||||
for (FieldData fieldData :
|
||||
fieldDataList) {
|
||||
if (fieldData.getName() != null && !fieldData.getName().equals("")) {
|
||||
oBuilder.append(fieldData.getName());
|
||||
} else {
|
||||
oBuilder.append("unknown_" + fieldData.getNumber());
|
||||
}
|
||||
oBuilder.append(fieldData);
|
||||
oBuilder.append(": ");
|
||||
Object o = fieldData.decode();
|
||||
if (o instanceof Object[]) {
|
||||
oBuilder.append("[");
|
||||
oBuilder.append(org.apache.commons.lang3.StringUtils.join((Object[]) o, ","));
|
||||
oBuilder.append("]");
|
||||
} else {
|
||||
oBuilder.append(o);
|
||||
}
|
||||
oBuilder.append(" ");
|
||||
}
|
||||
if (recordHeader.isCompressedTimestamp())
|
||||
oBuilder.append("compressed_timestamp: " + getComputedTimestamp());
|
||||
return oBuilder.toString();
|
||||
final GBToStringBuilder tsb = new GBToStringBuilder(this);
|
||||
|
||||
if (this.getClass().getName().equals(RecordData.class.getName())) {
|
||||
tsb.append(globalFITMessage.name());
|
||||
}
|
||||
|
||||
public PredefinedLocalMessage getPredefinedLocalMessage() {
|
||||
return recordHeader.getPredefinedLocalMessage();
|
||||
if (computedTimestamp != null) {
|
||||
tsb.append(new Date(computedTimestamp * 1000L));
|
||||
}
|
||||
|
||||
for (FieldData fieldData : fieldDataList) {
|
||||
final String fieldName;
|
||||
if (!StringUtils.isBlank(fieldData.getName())) {
|
||||
fieldName = fieldData.getName();
|
||||
} else {
|
||||
fieldName = "unknown_" + fieldData.getNumber() + fieldData;
|
||||
}
|
||||
Object o = fieldData.decode();
|
||||
final String fieldValueString;
|
||||
if (o == null) {
|
||||
fieldValueString = null;
|
||||
} else if (o instanceof Object[]) {
|
||||
fieldValueString = "[" + StringUtils.join((Object[]) o, ",") + "]";
|
||||
} else {
|
||||
fieldValueString = o.toString();
|
||||
}
|
||||
tsb.append(fieldName, fieldValueString);
|
||||
}
|
||||
return tsb.build();
|
||||
}
|
||||
|
||||
private class FieldData {
|
||||
@ -225,7 +231,7 @@ public class RecordData {
|
||||
private Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
||||
goToPosition();
|
||||
valueHolder.put(garminByteBufferReader.readBytes(size));
|
||||
if (fieldDefinition instanceof FieldDefinitionTimestamp)
|
||||
if (fieldDefinition.getNumber() == 253)
|
||||
return (Long) decode();
|
||||
return null;
|
||||
}
|
||||
|
@ -14,28 +14,18 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.Mess
|
||||
public class RecordDefinition {
|
||||
private final RecordHeader recordHeader;
|
||||
private final GlobalFITMessage globalFITMessage;
|
||||
private final PredefinedLocalMessage predefinedLocalMessage;
|
||||
private final java.nio.ByteOrder byteOrder;
|
||||
private List<FieldDefinition> fieldDefinitions;
|
||||
private List<DevFieldDefinition> devFieldDefinitions;
|
||||
|
||||
public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, PredefinedLocalMessage predefinedLocalMessage, GlobalFITMessage globalFITMessage, List<FieldDefinition> fieldDefinitions, List<DevFieldDefinition> devFieldDefinitions) {
|
||||
public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, GlobalFITMessage globalFITMessage, List<FieldDefinition> fieldDefinitions, List<DevFieldDefinition> devFieldDefinitions) {
|
||||
this.recordHeader = recordHeader;
|
||||
this.byteOrder = byteOrder;
|
||||
this.predefinedLocalMessage = predefinedLocalMessage;
|
||||
this.globalFITMessage = globalFITMessage;
|
||||
this.fieldDefinitions = fieldDefinitions;
|
||||
this.devFieldDefinitions = devFieldDefinitions;
|
||||
}
|
||||
|
||||
public RecordDefinition(ByteOrder byteOrder, PredefinedLocalMessage predefinedLocalMessage) {
|
||||
this(new RecordHeader(true, false, predefinedLocalMessage, null), byteOrder, predefinedLocalMessage, predefinedLocalMessage.getGlobalFITMessage(), predefinedLocalMessage.getLocalFieldDefinitions(), null);
|
||||
}
|
||||
|
||||
public RecordDefinition(ByteOrder byteOrder, RecordHeader recordHeader, GlobalFITMessage globalFITMessage, List<FieldDefinition> fieldDefinitions) {
|
||||
this(recordHeader, byteOrder, null, globalFITMessage, fieldDefinitions, null);
|
||||
}
|
||||
|
||||
public static RecordDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader, RecordHeader recordHeader) {
|
||||
if (!recordHeader.isDefinition())
|
||||
return null;
|
||||
@ -45,7 +35,7 @@ public class RecordDefinition {
|
||||
final int globalMesgNum = garminByteBufferReader.readShort();
|
||||
final GlobalFITMessage globalFITMessage = GlobalFITMessage.fromNumber(globalMesgNum);
|
||||
|
||||
RecordDefinition definitionMessage = new RecordDefinition(byteOrder, recordHeader, globalFITMessage, null);
|
||||
RecordDefinition definitionMessage = new RecordDefinition(recordHeader, byteOrder, globalFITMessage, null, null);
|
||||
|
||||
final int numFields = garminByteBufferReader.readByte();
|
||||
List<FieldDefinition> fieldDefinitions = new ArrayList<>(numFields);
|
||||
@ -113,21 +103,14 @@ public class RecordDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return predefinedLocalMessage != null ? predefinedLocalMessage.name() : "unknown_" + globalFITMessage;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return System.lineSeparator() + recordHeader.toString() +
|
||||
" Global Message Number: " + globalFITMessage.name();
|
||||
}
|
||||
|
||||
public void populateDevFields(List<RecordData> developerFieldData) {
|
||||
for (DevFieldDefinition devFieldDef :
|
||||
getDevFieldDefinitions()) {
|
||||
for (RecordData recordData :
|
||||
developerFieldData) {
|
||||
public void populateDevFields(RecordData recordData) {
|
||||
for (DevFieldDefinition devFieldDef : getDevFieldDefinitions()) {
|
||||
try {
|
||||
if (devFieldDef.getFieldDefinitionNumber() == (int) recordData.getFieldByName("field_definition_number") &&
|
||||
devFieldDef.getDeveloperDataIndex() == (int) recordData.getFieldByName("developer_data_index")) {
|
||||
@ -140,6 +123,4 @@ public class RecordDefinition {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,47 +7,36 @@ import androidx.annotation.Nullable;
|
||||
public class RecordHeader {
|
||||
private final boolean definition;
|
||||
private final boolean developerData;
|
||||
private final PredefinedLocalMessage predefinedLocalMessage;
|
||||
private final int rawLocalMessageType;
|
||||
private final int localMessageType;
|
||||
private final Integer timeOffset;
|
||||
private long referenceTimestamp;
|
||||
|
||||
public RecordHeader(boolean definition, boolean developerData, PredefinedLocalMessage predefinedLocalMessage, Integer timeOffset) {
|
||||
public RecordHeader(boolean definition, boolean developerData, int localMessageType, Integer timeOffset) {
|
||||
this.definition = definition;
|
||||
this.developerData = developerData;
|
||||
this.predefinedLocalMessage = predefinedLocalMessage;
|
||||
this.rawLocalMessageType = predefinedLocalMessage.getType();
|
||||
this.localMessageType = localMessageType;
|
||||
this.timeOffset = timeOffset;
|
||||
}
|
||||
|
||||
//see https://github.com/polyvertex/fitdecode/blob/master/fitdecode/reader.py#L512
|
||||
public RecordHeader(byte header) {
|
||||
this(header, false);
|
||||
}
|
||||
|
||||
public RecordHeader(byte header, boolean inferLocalMessage) {
|
||||
if ((header & 0x80) == 0x80) { //compressed timestamp TODO add support
|
||||
if ((header & 0x80) == 0x80) { //compressed timestamp
|
||||
definition = false;
|
||||
developerData = false;
|
||||
rawLocalMessageType = (header >> 5) & 0x3;
|
||||
localMessageType = (header >> 5) & 0x3;
|
||||
timeOffset = header & 0x1f;
|
||||
} else {
|
||||
definition = ((header & 0x40) == 0x40);
|
||||
developerData = ((header & 0x20) == 0x20);
|
||||
rawLocalMessageType = header & 0xf;
|
||||
localMessageType = header & 0xf;
|
||||
timeOffset = null;
|
||||
}
|
||||
if (inferLocalMessage)
|
||||
predefinedLocalMessage = PredefinedLocalMessage.fromType(rawLocalMessageType);
|
||||
else
|
||||
predefinedLocalMessage = null;
|
||||
}
|
||||
|
||||
|
||||
public void setReferenceTimestamp(long referenceTimestamp) {
|
||||
this.referenceTimestamp = referenceTimestamp;
|
||||
public int getLocalMessageType() {
|
||||
return localMessageType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTimeOffset() {
|
||||
return timeOffset;
|
||||
}
|
||||
@ -56,10 +45,6 @@ public class RecordHeader {
|
||||
return timeOffset != null;
|
||||
}
|
||||
|
||||
public Long getResultingTimestamp() {
|
||||
return referenceTimestamp + timeOffset;
|
||||
}
|
||||
|
||||
public boolean isDeveloperData() {
|
||||
return developerData;
|
||||
}
|
||||
@ -68,15 +53,12 @@ public class RecordHeader {
|
||||
return definition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PredefinedLocalMessage getPredefinedLocalMessage() {
|
||||
return predefinedLocalMessage;
|
||||
}
|
||||
|
||||
public byte generateOutgoingDefinitionPayload() {
|
||||
if (!definition && !developerData)
|
||||
return (byte) (timeOffset | (((byte) predefinedLocalMessage.getType()) << 5));
|
||||
byte base = (byte) (null == predefinedLocalMessage ? rawLocalMessageType : predefinedLocalMessage.getType());
|
||||
if (!definition && !developerData) {
|
||||
assert timeOffset != null;
|
||||
return (byte) (timeOffset | (((byte) localMessageType) << 5));
|
||||
}
|
||||
byte base = (byte) localMessageType;
|
||||
if (definition)
|
||||
base = (byte) (base | 0x40);
|
||||
if (developerData)
|
||||
@ -86,9 +68,11 @@ public class RecordHeader {
|
||||
}
|
||||
|
||||
public byte generateOutgoingDataPayload() { //TODO: unclear if correct
|
||||
if (!definition && !developerData)
|
||||
return (byte) (timeOffset | (((byte) predefinedLocalMessage.getType()) << 5));
|
||||
byte base = (byte) (null == predefinedLocalMessage ? rawLocalMessageType : predefinedLocalMessage.getType());
|
||||
if (!definition && !developerData) {
|
||||
assert timeOffset != null;
|
||||
return (byte) (timeOffset | (((byte) localMessageType) << 5));
|
||||
}
|
||||
byte base = (byte) localMessageType;
|
||||
if (developerData)
|
||||
base = (byte) (base | 0x20);
|
||||
|
||||
@ -98,24 +82,6 @@ public class RecordHeader {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Local Message: " + (null == predefinedLocalMessage ? "raw: " + rawLocalMessageType : "type: " + predefinedLocalMessage.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RecordHeader that = (RecordHeader) o;
|
||||
|
||||
if (rawLocalMessageType != that.rawLocalMessageType) return false;
|
||||
return predefinedLocalMessage == that.predefinedLocalMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (predefinedLocalMessage != null ? predefinedLocalMessage.hashCode() : 0);
|
||||
result = 31 * result + rawLocalMessageType;
|
||||
return result;
|
||||
return "Local Message: " + localMessageType;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,297 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.threeten.bp.DayOfWeek;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionFileType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
|
||||
// This class is only used to generate code, and will not be packaged in the final apk
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class FitCodeGen {
|
||||
public static void main(final String[] args) throws Exception {
|
||||
new FitCodeGen().generate();
|
||||
}
|
||||
|
||||
public void generate() throws IOException {
|
||||
final File factoryFile = new File("app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java");
|
||||
|
||||
final StringBuilder sbFactory = new StringBuilder();
|
||||
String header = getHeader(factoryFile);
|
||||
if (!header.isEmpty()) {
|
||||
sbFactory.append(header);
|
||||
sbFactory.append("\n");
|
||||
}
|
||||
|
||||
sbFactory.append("package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;\n");
|
||||
sbFactory.append("\n");
|
||||
sbFactory.append("import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;\n");
|
||||
sbFactory.append("import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;\n");
|
||||
sbFactory.append("import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;\n");
|
||||
sbFactory.append("\n");
|
||||
sbFactory.append("//\n");
|
||||
sbFactory.append("// WARNING: This class was auto-generated, please avoid modifying it directly.\n");
|
||||
sbFactory.append("// See ").append(getClass().getCanonicalName()).append("\n");
|
||||
sbFactory.append("//\n");
|
||||
sbFactory.append("public class FitRecordDataFactory {\n");
|
||||
sbFactory.append(" private FitRecordDataFactory() {\n");
|
||||
sbFactory.append(" // use create\n");
|
||||
sbFactory.append(" }\n");
|
||||
sbFactory.append("\n");
|
||||
sbFactory.append(" public static RecordData create(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {\n");
|
||||
sbFactory.append(" switch (recordDefinition.getGlobalFITMessage().getNumber()) {\n");
|
||||
|
||||
final ArrayList<GlobalFITMessage> globalFITMessages = new ArrayList<>(GlobalFITMessage.KNOWN_MESSAGES.values());
|
||||
Collections.sort(globalFITMessages, Comparator.comparingInt(GlobalFITMessage::getNumber));
|
||||
|
||||
for (final GlobalFITMessage value : globalFITMessages) {
|
||||
final String className = "Fit" + capitalize(toCamelCase(value.name()));
|
||||
sbFactory.append(" case ").append(value.getNumber()).append(":\n");
|
||||
sbFactory.append(" return new ").append(className).append("(recordDefinition, recordHeader);\n");
|
||||
|
||||
process(value);
|
||||
}
|
||||
|
||||
sbFactory.append(" }\n");
|
||||
sbFactory.append("\n");
|
||||
sbFactory.append(" return new RecordData(recordDefinition, recordHeader);\n");
|
||||
sbFactory.append(" }\n");
|
||||
sbFactory.append("}\n");
|
||||
|
||||
FileUtils.copyStringToFile(sbFactory.toString(), factoryFile, "replace");
|
||||
}
|
||||
|
||||
public void process(final GlobalFITMessage globalFITMessage) throws IOException {
|
||||
final String className = "Fit" + capitalize(toCamelCase(globalFITMessage.name()));
|
||||
final File outputFile = new File("app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/" + className + ".java");
|
||||
|
||||
final List<String> imports = new ArrayList<>();
|
||||
imports.add(Nullable.class.getCanonicalName());
|
||||
imports.add(RecordData.class.getCanonicalName());
|
||||
imports.add(RecordDefinition.class.getCanonicalName());
|
||||
imports.add(RecordHeader.class.getCanonicalName());
|
||||
//imports.add(GBToStringBuilder.class.getCanonicalName());
|
||||
|
||||
Collections.sort(imports);
|
||||
|
||||
for (final GlobalFITMessage.FieldDefinitionPrimitive primitive : globalFITMessage.getFieldDefinitionPrimitives()) {
|
||||
final Class<?> fieldType = getFieldType(primitive);
|
||||
if (!Objects.requireNonNull(fieldType.getCanonicalName()).startsWith("java.lang")) {
|
||||
imports.add(fieldType.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String header = getHeader(outputFile);
|
||||
if (!header.isEmpty()) {
|
||||
sb.append(header);
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
sb.append("package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;");
|
||||
sb.append("\n");
|
||||
|
||||
sb.append("\n");
|
||||
boolean anyImport = false;
|
||||
for (final String i : imports) {
|
||||
if (i.startsWith("androidx")) {
|
||||
sb.append("import ").append(i).append(";\n");
|
||||
anyImport = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyImport) {
|
||||
sb.append("\n");
|
||||
anyImport = false;
|
||||
}
|
||||
for (final String i : imports) {
|
||||
if (i.startsWith("nodomain.freeyourgadget")) {
|
||||
sb.append("import ").append(i).append(";\n");
|
||||
anyImport = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyImport) {
|
||||
sb.append("\n");
|
||||
anyImport = false;
|
||||
}
|
||||
for (final String i : imports) {
|
||||
if (!i.startsWith("androidx") && !i.startsWith("nodomain.freeyourgadget")) {
|
||||
sb.append("import ").append(i).append(";\n");
|
||||
anyImport = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyImport) {
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append("//\n");
|
||||
sb.append("// WARNING: This class was auto-generated, please avoid modifying it directly.\n");
|
||||
sb.append("// See ").append(getClass().getCanonicalName()).append("\n");
|
||||
sb.append("//\n");
|
||||
sb.append("public class ").append(className).append(" extends RecordData {\n");
|
||||
sb.append(" public ").append(className).append("(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {\n");
|
||||
sb.append(" super(recordDefinition, recordHeader);\n");
|
||||
sb.append("\n");
|
||||
sb.append(" final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();\n");
|
||||
sb.append(" if (globalNumber != ").append(globalFITMessage.getNumber()).append(") {\n");
|
||||
sb.append(" throw new IllegalArgumentException(\"FitFileId expects global messages of \" + ").append(globalFITMessage.getNumber()).append(" + \", got \" + globalNumber);\n");
|
||||
sb.append(" }\n");
|
||||
sb.append(" }\n");
|
||||
|
||||
for (final GlobalFITMessage.FieldDefinitionPrimitive primitive : globalFITMessage.getFieldDefinitionPrimitives()) {
|
||||
final Class<?> fieldType = getFieldType(primitive);
|
||||
final String fieldTypeName = fieldType.getSimpleName();
|
||||
sb.append("\n");
|
||||
sb.append(" @Nullable\n");
|
||||
sb.append(" public ").append(fieldTypeName).append(method(" get", primitive)).append("() {\n");
|
||||
sb.append(" return (").append(fieldTypeName).append(") getFieldByNumber(").append(primitive.getNumber()).append(");\n");
|
||||
sb.append(" }\n");
|
||||
}
|
||||
|
||||
//sb.append("\n");
|
||||
//sb.append(" @NonNull\n");
|
||||
//sb.append(" @Override\n");
|
||||
//sb.append(" public String toString() {\n");
|
||||
//sb.append(" return new GBToStringBuilder(this)\n");
|
||||
//for (final GlobalFITMessage.FieldDefinitionPrimitive primitive : globalFITMessage.getFieldDefinitionPrimitives()) {
|
||||
// sb.append(" .append(\"").append(primitive.getName()).append("\",").append(method(" get", primitive)).append("())\n");
|
||||
//}
|
||||
//sb.append(" .build();\n");
|
||||
//sb.append(" }\n");
|
||||
|
||||
if (outputFile.exists()) {
|
||||
// Keep manual changes if any
|
||||
final String fileContents = new String(Files.readAllBytes(outputFile.toPath()), StandardCharsets.UTF_8);
|
||||
final int manualChangesIndex = fileContents.indexOf("// manual changes below");
|
||||
if (manualChangesIndex > 0) {
|
||||
sb.append("\n");
|
||||
sb.append(" ");
|
||||
sb.append(fileContents.substring(manualChangesIndex));
|
||||
} else {
|
||||
sb.append("}\n");
|
||||
}
|
||||
} else {
|
||||
sb.append("}\n");
|
||||
}
|
||||
|
||||
FileUtils.copyStringToFile(sb.toString(), outputFile, "replace");
|
||||
}
|
||||
|
||||
public Class<?> getFieldType(final GlobalFITMessage.FieldDefinitionPrimitive primitive) {
|
||||
if (primitive.getType() != null) {
|
||||
switch (primitive.getType()) {
|
||||
case ALARM:
|
||||
return Calendar.class;
|
||||
case DAY_OF_WEEK:
|
||||
return DayOfWeek.class;
|
||||
case FILE_TYPE:
|
||||
return FieldDefinitionFileType.Type.class;
|
||||
case GOAL_SOURCE:
|
||||
return FieldDefinitionGoalSource.Source.class;
|
||||
case GOAL_TYPE:
|
||||
return FieldDefinitionGoalType.Type.class;
|
||||
case MEASUREMENT_SYSTEM:
|
||||
return FieldDefinitionMeasurementSystem.Type.class;
|
||||
case TEMPERATURE:
|
||||
return Integer.class;
|
||||
case TIMESTAMP:
|
||||
return Long.class;
|
||||
case WEATHER_CONDITION:
|
||||
return FieldDefinitionWeatherCondition.Condition.class;
|
||||
case LANGUAGE:
|
||||
return FieldDefinitionLanguage.Language.class;
|
||||
case SLEEP_STAGE:
|
||||
return FieldDefinitionSleepStage.SleepStage.class;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown field type " + primitive.getType());
|
||||
}
|
||||
|
||||
switch (primitive.getBaseType()) {
|
||||
case ENUM:
|
||||
case SINT8:
|
||||
case UINT8:
|
||||
case SINT16:
|
||||
case UINT16:
|
||||
case UINT8Z:
|
||||
case UINT16Z:
|
||||
case BASE_TYPE_BYTE:
|
||||
return Integer.class;
|
||||
case SINT32:
|
||||
case UINT32:
|
||||
case UINT32Z:
|
||||
case SINT64:
|
||||
case UINT64:
|
||||
case UINT64Z:
|
||||
return Long.class;
|
||||
case STRING:
|
||||
return String.class;
|
||||
case FLOAT32:
|
||||
return Float.class;
|
||||
case FLOAT64:
|
||||
return Double.class;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown base type " + primitive.getBaseType());
|
||||
}
|
||||
|
||||
public String toCamelCase(final String str) {
|
||||
final StringBuilder sb = new StringBuilder(str.toLowerCase());
|
||||
|
||||
for (int i = 0; i < sb.length(); i++) {
|
||||
if (sb.charAt(i) == '_') {
|
||||
sb.deleteCharAt(i);
|
||||
sb.replace(i, i + 1, String.valueOf(Character.toUpperCase(sb.charAt(i))));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String method(final String methodName, final GlobalFITMessage.FieldDefinitionPrimitive primitive) {
|
||||
return methodName + capitalize(toCamelCase(primitive.getName()));
|
||||
}
|
||||
|
||||
public String capitalize(final String str) {
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
public String getHeader(final File file) throws IOException {
|
||||
if (file.exists()) {
|
||||
final String fileContents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
|
||||
final int packageIndex = fileContents.indexOf("package") - 1;
|
||||
if (packageIndex > 0) {
|
||||
return fileContents.substring(0, packageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ public class FieldDefinitionLanguage extends FieldDefinition {
|
||||
baseType.encode(byteBuffer, o, scale, offset);
|
||||
}
|
||||
|
||||
private enum Language {
|
||||
public enum Language {
|
||||
english(0),
|
||||
italian(2),
|
||||
;
|
||||
|
@ -0,0 +1,57 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
||||
|
||||
public class FieldDefinitionSleepStage extends FieldDefinition {
|
||||
public FieldDefinitionSleepStage(final int localNumber, final int size, final BaseType baseType, final String name) {
|
||||
super(localNumber, size, baseType, name, 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final ByteBuffer byteBuffer) {
|
||||
final int raw = (int) baseType.decode(byteBuffer, scale, offset);
|
||||
return SleepStage.fromId(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuffer byteBuffer, final Object o) {
|
||||
if (o instanceof SleepStage) {
|
||||
baseType.encode(byteBuffer, (((SleepStage) o).getId()), scale, offset);
|
||||
return;
|
||||
}
|
||||
baseType.encode(byteBuffer, o, scale, offset);
|
||||
}
|
||||
|
||||
public enum SleepStage {
|
||||
AWAKE(1),
|
||||
LIGHT(2),
|
||||
DEEP(3),
|
||||
REM(4),
|
||||
;
|
||||
|
||||
private final int id;
|
||||
|
||||
SleepStage(final int i) {
|
||||
id = i;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static SleepStage fromId(final int id) {
|
||||
for (SleepStage stage : SleepStage.values()) {
|
||||
if (id == stage.getId()) {
|
||||
return stage;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
@ -142,7 +142,7 @@ public class FieldDefinitionWeatherCondition extends FieldDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
enum Condition {
|
||||
public enum Condition {
|
||||
CLEAR,
|
||||
PARTLY_CLOUDY,
|
||||
MOSTLY_CLOUDY,
|
||||
|
@ -0,0 +1,29 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitAlarmSettings extends RecordData {
|
||||
public FitAlarmSettings(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 222) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 222 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Calendar getTime() {
|
||||
return (Calendar) getFieldByNumber(0);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitConnectivity extends RecordData {
|
||||
public FitConnectivity(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 127) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 127 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getBluetoothEnabled() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return (String) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getLiveTrackingEnabled() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWeatherConditionsEnabled() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWeatherAlertsEnabled() {
|
||||
return (Integer) getFieldByNumber(6);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAutoActivityUploadEnabled() {
|
||||
return (Integer) getFieldByNumber(7);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getCourseDownloadEnabled() {
|
||||
return (Integer) getFieldByNumber(8);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWorkoutDownloadEnabled() {
|
||||
return (Integer) getFieldByNumber(9);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getGpsEphemerisDownloadEnabled() {
|
||||
return (Integer) getFieldByNumber(10);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitDeveloperData extends RecordData {
|
||||
public FitDeveloperData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 207) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 207 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getApplicationId() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDeveloperDataIndex() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitDeviceInfo extends RecordData {
|
||||
public FitDeviceInfo(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 23) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 23 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getManufacturer() {
|
||||
return (Integer) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getSerialNumber() {
|
||||
return (Long) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getProduct() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSoftwareVersion() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp() {
|
||||
return (Long) getFieldByNumber(253);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitDeviceSettings extends RecordData {
|
||||
public FitDeviceSettings(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 2) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 2 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getActiveTimeZone() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getUtcOffset() {
|
||||
return (Long) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimeOffset() {
|
||||
return (Long) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTimeMode() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTimeZoneOffset() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getBacklightMode() {
|
||||
return (Integer) getFieldByNumber(12);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getActivityTrackerEnabled() {
|
||||
return (Integer) getFieldByNumber(36);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getMoveAlertEnabled() {
|
||||
return (Integer) getFieldByNumber(46);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDateMode() {
|
||||
return (Integer) getFieldByNumber(47);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDisplayOrientation() {
|
||||
return (Integer) getFieldByNumber(55);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getMountingSide() {
|
||||
return (Integer) getFieldByNumber(56);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDefaultPage() {
|
||||
return (Integer) getFieldByNumber(57);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAutosyncMinSteps() {
|
||||
return (Integer) getFieldByNumber(58);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAutosyncMinTime() {
|
||||
return (Integer) getFieldByNumber(59);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getBleAutoUploadEnabled() {
|
||||
return (Integer) getFieldByNumber(86);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getAutoActivityDetect() {
|
||||
return (Long) getFieldByNumber(90);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitFieldDescription extends RecordData {
|
||||
public FitFieldDescription(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 206) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 206 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDeveloperDataIndex() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getFieldDefinitionNumber() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getFitBaseTypeId() {
|
||||
return (Integer) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFieldName() {
|
||||
return (String) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUnits() {
|
||||
return (String) getFieldByNumber(8);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitFileCreator extends RecordData {
|
||||
public FitFileCreator(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 49) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 49 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSoftwareVersion() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHardwareVersion() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionFileType.Type;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitFileId extends RecordData {
|
||||
public FitFileId(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 0) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 0 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getType() {
|
||||
return (Type) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getManufacturer() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getProduct() {
|
||||
return (Integer) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getSerialNumber() {
|
||||
return (Long) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimeCreated() {
|
||||
return (Long) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getNumber() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getManufacturerPartner() {
|
||||
return (Integer) getFieldByNumber(6);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getProductName() {
|
||||
return (String) getFieldByNumber(8);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType.Type;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource.Source;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitGoals extends RecordData {
|
||||
public FitGoals(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 15) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 15 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getType() {
|
||||
return (Type) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTargetValue() {
|
||||
return (Long) getFieldByNumber(7);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Source getSource() {
|
||||
return (Source) getFieldByNumber(11);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitMonitoring extends RecordData {
|
||||
public FitMonitoring(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 55) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 55 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getDistance() {
|
||||
return (Long) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getCycles() {
|
||||
return (Long) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getActiveTime() {
|
||||
return (Long) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getActivityType() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getActiveCalories() {
|
||||
return (Integer) getFieldByNumber(19);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDurationMin() {
|
||||
return (Integer) getFieldByNumber(29);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getCurrentActivityTypeIntensity() {
|
||||
return (Integer) getFieldByNumber(24);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTimestamp16() {
|
||||
return (Integer) getFieldByNumber(26);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHeartRate() {
|
||||
return (Integer) getFieldByNumber(27);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp() {
|
||||
return (Long) getFieldByNumber(253);
|
||||
}
|
||||
|
||||
// manual changes below
|
||||
|
||||
@Override
|
||||
public Long getComputedTimestamp() {
|
||||
final Integer timestamp16 = getTimestamp16();
|
||||
final Long computedTimestamp = super.getComputedTimestamp();
|
||||
if (timestamp16 != null && computedTimestamp != null) {
|
||||
return (computedTimestamp & ~0xFFFFL) | timestamp16;
|
||||
}
|
||||
return computedTimestamp;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitRecord extends RecordData {
|
||||
public FitRecord(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 20) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 20 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHeartRate() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp() {
|
||||
return (Long) getFieldByNumber(253);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitRecordDataFactory {
|
||||
private FitRecordDataFactory() {
|
||||
// use create
|
||||
}
|
||||
|
||||
public static RecordData create(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
switch (recordDefinition.getGlobalFITMessage().getNumber()) {
|
||||
case 0:
|
||||
return new FitFileId(recordDefinition, recordHeader);
|
||||
case 2:
|
||||
return new FitDeviceSettings(recordDefinition, recordHeader);
|
||||
case 3:
|
||||
return new FitUserProfile(recordDefinition, recordHeader);
|
||||
case 7:
|
||||
return new FitZonesTarget(recordDefinition, recordHeader);
|
||||
case 12:
|
||||
return new FitSport(recordDefinition, recordHeader);
|
||||
case 15:
|
||||
return new FitGoals(recordDefinition, recordHeader);
|
||||
case 20:
|
||||
return new FitRecord(recordDefinition, recordHeader);
|
||||
case 23:
|
||||
return new FitDeviceInfo(recordDefinition, recordHeader);
|
||||
case 49:
|
||||
return new FitFileCreator(recordDefinition, recordHeader);
|
||||
case 55:
|
||||
return new FitMonitoring(recordDefinition, recordHeader);
|
||||
case 127:
|
||||
return new FitConnectivity(recordDefinition, recordHeader);
|
||||
case 128:
|
||||
return new FitWeather(recordDefinition, recordHeader);
|
||||
case 159:
|
||||
return new FitWatchfaceSettings(recordDefinition, recordHeader);
|
||||
case 206:
|
||||
return new FitFieldDescription(recordDefinition, recordHeader);
|
||||
case 207:
|
||||
return new FitDeveloperData(recordDefinition, recordHeader);
|
||||
case 222:
|
||||
return new FitAlarmSettings(recordDefinition, recordHeader);
|
||||
case 227:
|
||||
return new FitStressLevel(recordDefinition, recordHeader);
|
||||
case 275:
|
||||
return new FitSleepStage(recordDefinition, recordHeader);
|
||||
case 346:
|
||||
return new FitSleepStats(recordDefinition, recordHeader);
|
||||
}
|
||||
|
||||
return new RecordData(recordDefinition, recordHeader);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage.SleepStage;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitSleepStage extends RecordData {
|
||||
public FitSleepStage(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 275) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 275 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SleepStage getSleepStage() {
|
||||
return (SleepStage) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp() {
|
||||
return (Long) getFieldByNumber(253);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitSleepStats extends RecordData {
|
||||
public FitSleepStats(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 346) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 346 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitSport extends RecordData {
|
||||
public FitSport(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 12) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 12 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSport() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSubSport() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return (String) getFieldByNumber(3);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitStressLevel extends RecordData {
|
||||
public FitStressLevel(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 227) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 227 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getStressLevelValue() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getStressLevelTime() {
|
||||
return (Long) getFieldByNumber(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionLanguage.Language;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionMeasurementSystem.Type;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitUserProfile extends RecordData {
|
||||
public FitUserProfile(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 3) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 3 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFriendlyName() {
|
||||
return (String) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getGender() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAge() {
|
||||
return (Integer) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHeight() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWeight() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Language getLanguage() {
|
||||
return (Language) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getElevSetting() {
|
||||
return (Type) getFieldByNumber(6);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getWeightSetting() {
|
||||
return (Type) getFieldByNumber(7);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getRestingHeartRate() {
|
||||
return (Integer) getFieldByNumber(8);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDefaultMaxBikingHeartRate() {
|
||||
return (Integer) getFieldByNumber(10);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDefaultMaxHeartRate() {
|
||||
return (Integer) getFieldByNumber(11);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHrSetting() {
|
||||
return (Integer) getFieldByNumber(12);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getSpeedSetting() {
|
||||
return (Type) getFieldByNumber(13);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getDistSetting() {
|
||||
return (Type) getFieldByNumber(14);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPowerSetting() {
|
||||
return (Integer) getFieldByNumber(16);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getActivityClass() {
|
||||
return (Integer) getFieldByNumber(17);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPositionSetting() {
|
||||
return (Integer) getFieldByNumber(18);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getTemperatureSetting() {
|
||||
return (Type) getFieldByNumber(21);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getWakeTime() {
|
||||
return (Long) getFieldByNumber(28);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getSleepTime() {
|
||||
return (Long) getFieldByNumber(29);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getHeightSetting() {
|
||||
return (Type) getFieldByNumber(30);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUserRunningStepLength() {
|
||||
return (Integer) getFieldByNumber(31);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUserWalkingStepLength() {
|
||||
return (Integer) getFieldByNumber(32);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitWatchfaceSettings extends RecordData {
|
||||
public FitWatchfaceSettings(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 159) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 159 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getMode() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getLayout() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition.Condition;
|
||||
|
||||
import org.threeten.bp.DayOfWeek;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitWeather extends RecordData {
|
||||
public FitWeather(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 128) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 128 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWeatherReport() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTemperature() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Condition getCondition() {
|
||||
return (Condition) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWindDirection() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getWindSpeed() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPrecipitationProbability() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getTemperatureFeelsLike() {
|
||||
return (Integer) getFieldByNumber(6);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getRelativeHumidity() {
|
||||
return (Integer) getFieldByNumber(7);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLocation() {
|
||||
return (String) getFieldByNumber(8);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getObservedAtTime() {
|
||||
return (Long) getFieldByNumber(9);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getObservedLocationLat() {
|
||||
return (Long) getFieldByNumber(10);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getObservedLocationLong() {
|
||||
return (Long) getFieldByNumber(11);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DayOfWeek getDayOfWeek() {
|
||||
return (DayOfWeek) getFieldByNumber(12);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHighTemperature() {
|
||||
return (Integer) getFieldByNumber(13);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getLowTemperature() {
|
||||
return (Integer) getFieldByNumber(14);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDewPoint() {
|
||||
return (Integer) getFieldByNumber(15);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Float getUvIndex() {
|
||||
return (Float) getFieldByNumber(16);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAirQuality() {
|
||||
return (Integer) getFieldByNumber(17);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp() {
|
||||
return (Long) getFieldByNumber(253);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
|
||||
//
|
||||
public class FitZonesTarget extends RecordData {
|
||||
public FitZonesTarget(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 7) {
|
||||
throw new IllegalArgumentException("FitFileId expects global messages of " + 7 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getFunctionalThresholdPower() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getMaxHeartRate() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getThresholdHeartRate() {
|
||||
return (Integer) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getHrCalcType() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPwrCalcType() {
|
||||
return (Integer) getFieldByNumber(7);
|
||||
}
|
||||
}
|
@ -24,17 +24,20 @@ public class FitDataMessage extends GFDIMessage {
|
||||
final List<RecordData> recordDataList = new ArrayList<>();
|
||||
|
||||
while (reader.remaining() > 0) {
|
||||
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte(), true);
|
||||
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
|
||||
if (recordHeader.isDefinition())
|
||||
return null;
|
||||
PredefinedLocalMessage predefinedLocalMessage = recordHeader.getPredefinedLocalMessage();
|
||||
PredefinedLocalMessage predefinedLocalMessage = PredefinedLocalMessage.fromType(recordHeader.getLocalMessageType());
|
||||
if (predefinedLocalMessage == null) {
|
||||
LOG.warn("Local message is null");
|
||||
|
||||
return null;
|
||||
}
|
||||
RecordData recordData = new RecordData(predefinedLocalMessage.getRecordDefinition());
|
||||
recordData.parseDataMessage(reader);
|
||||
RecordData recordData = new RecordData(
|
||||
predefinedLocalMessage.getRecordDefinition(),
|
||||
predefinedLocalMessage.getRecordDefinition().getRecordHeader()
|
||||
);
|
||||
recordData.parseDataMessage(reader, null);
|
||||
recordDataList.add(recordData);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ public class FitDefinitionMessage extends GFDIMessage {
|
||||
List<RecordDefinition> recordDefinitions = new ArrayList<>();
|
||||
|
||||
while (reader.remaining() > 0) {
|
||||
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte(), true);
|
||||
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
|
||||
recordDefinitions.add(RecordDefinition.parseIncoming(reader, recordHeader));
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,50 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GBToStringBuilder extends ToStringBuilder {
|
||||
public static final GBToStringStyle STYLE = new GBToStringStyle();
|
||||
|
||||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
|
||||
|
||||
public GBToStringBuilder(final Object object) {
|
||||
super(object, STYLE);
|
||||
}
|
||||
|
||||
public static class GBToStringStyle extends ToStringStyle {
|
||||
public GBToStringStyle() {
|
||||
super();
|
||||
|
||||
this.setUseShortClassName(true);
|
||||
this.setUseIdentityHashCode(false);
|
||||
|
||||
this.setContentStart("{");
|
||||
this.setContentEnd("}");
|
||||
|
||||
this.setArrayStart("[");
|
||||
this.setArrayEnd("]");
|
||||
|
||||
this.setFieldSeparator(", ");
|
||||
this.setFieldNameValueSeparator("=");
|
||||
|
||||
this.setNullText("null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail) {
|
||||
// omit nulls
|
||||
if (value != null) {
|
||||
if (value instanceof Date) {
|
||||
super.append(buffer, fieldName, SDF.format(value), fullDetail);
|
||||
} else {
|
||||
super.append(buffer, fieldName, value, fullDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -112,7 +112,7 @@ public class GarminSupportTest {
|
||||
@Test
|
||||
public void testBaseFields() {
|
||||
|
||||
RecordDefinition recordDefinition = new RecordDefinition(ByteOrder.LITTLE_ENDIAN, new RecordHeader((byte) 6), GlobalFITMessage.WEATHER, null); //just some random data
|
||||
RecordDefinition recordDefinition = new RecordDefinition(new RecordHeader((byte) 6), ByteOrder.LITTLE_ENDIAN, GlobalFITMessage.WEATHER, null, null); //just some random data
|
||||
List<FieldDefinition> fieldDefinitionList = new ArrayList<>();
|
||||
for (BaseType baseType :
|
||||
BaseType.values()) {
|
||||
@ -121,7 +121,7 @@ public class GarminSupportTest {
|
||||
}
|
||||
recordDefinition.setFieldDefinitions(fieldDefinitionList);
|
||||
|
||||
RecordData test = new RecordData(recordDefinition);
|
||||
RecordData test = new RecordData(recordDefinition, recordDefinition.getRecordHeader());
|
||||
|
||||
for (BaseType baseType :
|
||||
BaseType.values()) {
|
||||
@ -421,35 +421,24 @@ public class GarminSupportTest {
|
||||
"020b00004b00007f00090309070001000401000501000601000701000801" +
|
||||
"000901000a01000b45646765203531300000ffffffffffffff09ef");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/Settings2.fit
|
||||
|
||||
String expectedOutput = "{\n" +
|
||||
"Local Message: raw: 0 Global Message Number: FILE_ID=[\n" +
|
||||
"serial_number(UINT32Z/4): 3889965805 time_created(UINT32/4): null manufacturer(UINT16/2): 1 product(UINT16/2): 1561 number(UINT16/2): null type(ENUM/1): settings ], \n" +
|
||||
"Local Message: raw: 1 Global Message Number: FILE_CREATOR=[\n" +
|
||||
"software_version(UINT16/2): 340 hardware_version(UINT8/1): null ], \n" +
|
||||
"Local Message: raw: 2 Global Message Number: DEVICE_SETTINGS=[\n" +
|
||||
"utc_offset(UINT32/4): 0 time_offset(UINT32/4): 0 active_time_zone(UINT8/1): 0 unknown_3(ENUM/1): 0 time_mode(ENUM/1): 0 time_zone_offset(SINT8/1): 0 unknown_10(ENUM/1): 3 unknown_11(ENUM/1): 0 backlight_mode(ENUM/1): 2 unknown_13(UINT8/1): 0 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 50 unknown_16(ENUM/1): null unknown_17(ENUM/1): null unknown_18(ENUM/1): null unknown_21(ENUM/1): 1 unknown_22(ENUM/1): 0 unknown_26(ENUM/1): 254 unknown_27(ENUM/1): 0 unknown_29(ENUM/1): 0 unknown_52(ENUM/1): 0 unknown_53(ENUM/1): 1 ], \n" +
|
||||
"Local Message: raw: 3 Global Message Number: USER_PROFILE=[\n" +
|
||||
"friendly_name(STRING/8): edge510 weight(UINT16/2): 78 gender(ENUM/1): 1 age(UINT8/1): 41 height(UINT8/1): 183 language(ENUM/1): english elev_setting(ENUM/1): metric weight_setting(ENUM/1): metric resting_heart_rate(UINT8/1): 60 default_max_biking_heart_rate(UINT8/1): 185 default_max_heart_rate(UINT8/1): 185 hr_setting(ENUM/1): 1 speed_setting(ENUM/1): metric dist_setting(ENUM/1): metric power_setting(ENUM/1): 1 activity_class(ENUM/1): 168 position_setting(ENUM/1): 2 temperature_setting(ENUM/1): metric unknown_24(UINT8/1): null ], \n" +
|
||||
"Local Message: raw: 4 Global Message Number: UNK_4=[\n" +
|
||||
"unknown_254(UINT16/2): 0 unknown_1(UINT16Z/2): 50008 unknown_0(UINT8/1): 1 unknown_3(UINT8Z/1): 1 ], \n" +
|
||||
"Local Message: raw: 5 Global Message Number: UNK_6=[\n" +
|
||||
"unknown_0(STRING/4): EVO unknown_3(UINT32/4): 45719172 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 0 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 47617 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 80 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" +
|
||||
"Local Message: raw: 6 Global Message Number: UNK_6=[\n" +
|
||||
"unknown_0(STRING/5): P2SL unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 1 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 28209 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 90 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,118,190] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" +
|
||||
"Local Message: raw: 7 Global Message Number: UNK_6=[\n" +
|
||||
"unknown_0(STRING/9): LANGSTER unknown_3(UINT32/4): 1231891 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 2 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 10851 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 0 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" +
|
||||
"Local Message: raw: 8 Global Message Number: UNK_6=[\n" +
|
||||
"unknown_0(STRING/2): M unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [53,39,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 3 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 31337 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 0 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" +
|
||||
"Local Message: raw: 9 Global Message Number: UNK_6=[\n" +
|
||||
"unknown_0(STRING/7): Bike 5 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 4 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" +
|
||||
"unknown_0(STRING/7): Bike 6 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 5 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" +
|
||||
"unknown_0(STRING/7): Bike 7 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 6 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" +
|
||||
"unknown_0(STRING/7): Bike 8 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 7 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" +
|
||||
"unknown_0(STRING/7): Bike 9 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 8 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" +
|
||||
"Local Message: raw: 10 Global Message Number: UNK_6=[\n" +
|
||||
"unknown_0(STRING/8): Bike 10 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 9 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" +
|
||||
"Local Message: raw: 11 Global Message Number: CONNECTIVITY=[\n" +
|
||||
"name(STRING/9): Edge 510 bluetooth_enabled(ENUM/1): 0 live_tracking_enabled(ENUM/1): null weather_conditions_enabled(ENUM/1): null weather_alerts_enabled(ENUM/1): null auto_activity_upload_enabled(ENUM/1): null course_download_enabled(ENUM/1): null workout_download_enabled(ENUM/1): null gps_ephemeris_download_enabled(ENUM/1): null ]}";
|
||||
String expectedOutput = "[" +
|
||||
"FitFileId{serial_number=3889965805, manufacturer=1, product=1561, type=settings}, " +
|
||||
"FitFileCreator{software_version=340}, " +
|
||||
"FitDeviceSettings{utc_offset=0, time_offset=0, active_time_zone=0, unknown_3(ENUM/1)=0, time_mode=0, time_zone_offset=0, unknown_10(ENUM/1)=3, unknown_11(ENUM/1)=0, backlight_mode=2, unknown_13(UINT8/1)=0, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=50, unknown_21(ENUM/1)=1, unknown_22(ENUM/1)=0, unknown_26(ENUM/1)=254, unknown_27(ENUM/1)=0, unknown_29(ENUM/1)=0, unknown_52(ENUM/1)=0, unknown_53(ENUM/1)=1}, " +
|
||||
"FitUserProfile{friendly_name=edge510, weight=78, gender=1, age=41, height=183, language=english, elev_setting=metric, weight_setting=metric, resting_heart_rate=60, default_max_biking_heart_rate=185, default_max_heart_rate=185, hr_setting=1, speed_setting=metric, dist_setting=metric, power_setting=1, activity_class=168, position_setting=2, temperature_setting=metric}, " +
|
||||
"RecordData{UNK_4, unknown_254(UINT16/2)=0, unknown_1(UINT16Z/2)=50008, unknown_0(UINT8/1)=1, unknown_3(UINT8Z/1)=1}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/4)=EVO, unknown_3(UINT32/4)=45719172, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=0, unknown_7(UINT16Z/2)=47617, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=80, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/5)=P2SL, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=1, unknown_7(UINT16Z/2)=28209, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=90, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,118,190], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/9)=LANGSTER, unknown_3(UINT32/4)=1231891, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=2, unknown_7(UINT16Z/2)=10851, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=0, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/2)=M, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[53,39,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=3, unknown_7(UINT16Z/2)=31337, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=1, unknown_19(UINT8/1)=0, unknown_20(UINT8/1)=1, unknown_24(UINT8Z/1)=5, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/7)=Bike 5, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=4, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/7)=Bike 6, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=5, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/7)=Bike 7, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=6, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/7)=Bike 8, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=7, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/7)=Bike 9, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=8, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"RecordData{UNK_6, unknown_0(STRING/8)=Bike 10, unknown_3(UINT32/4)=0, unknown_39(UINT8Z/4)=[39,53,,], unknown_41(UINT8Z/12)=[23,21,19,18,17,16,15,14,13,12,11,], unknown_254(UINT16/2)=9, unknown_8(UINT16/2)=2096, unknown_9(UINT16/2)=0, unknown_10(UINT16/2)=95, unknown_11(UINT16/2)=500, unknown_12(UINT8/1)=1, unknown_13(UINT8/1)=1, unknown_14(UINT8/1)=0, unknown_15(UINT8/1)=0, unknown_16(UINT8/1)=0, unknown_17(UINT8/1)=0, unknown_18(UINT8/1)=0, unknown_19(UINT8/1)=254, unknown_20(UINT8/1)=0, unknown_35(UINT8/3)=[0,50,], unknown_36(ENUM/1)=4, unknown_38(UINT8Z/1)=2, unknown_40(UINT8Z/1)=11, unknown_44(ENUM/1)=0}, " +
|
||||
"FitConnectivity{name=Edge 510, bluetooth_enabled=0}" +
|
||||
"]";
|
||||
|
||||
FitFile fitFile = FitFile.parseIncoming(fileContents);
|
||||
Assert.assertEquals(expectedOutput, fitFile.toString());
|
||||
@ -460,17 +449,14 @@ public class GarminSupportTest {
|
||||
public void TestFitFileDevelopersField() {
|
||||
byte[] fileContents = GB.hexStringToByteArray("0e206806a20000002e464954bed040000100000401028400010002028403048c00000f042329000006a540000100cf0201100d030102000101020305080d1522375990e97962db0040000100ce05000102010102020102031107080a0700000001646f7567686e7574735f6561726e656400646f7567686e7574730060000100140403010204010205048606028401000100008c580000c738b98001008f5a00032c808e400200905c0005a9388a1003d39e");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/DeveloperData.fit
|
||||
|
||||
String expectedOutput = "{\n" +
|
||||
"Local Message: raw: 0 Global Message Number: FILE_ID=[\n" +
|
||||
"manufacturer(UINT16/2): 15 type(ENUM/1): activity product(UINT16/2): 9001 serial_number(UINT32Z/4): 1701 ], \n" +
|
||||
"Local Message: raw: 0 Global Message Number: DEVELOPER_DATA=[\n" +
|
||||
"application_id(BASE_TYPE_BYTE/16): [1,1,2,3,5,8,13,21,34,55,89,144,233,121,98,219] developer_data_index(UINT8/1): 0 ], \n" +
|
||||
"Local Message: raw: 0 Global Message Number: FIELD_DESCRIPTION=[\n" +
|
||||
"developer_data_index(UINT8/1): 0 field_definition_number(UINT8/1): 0 fit_base_type_id(UINT8/1): 1 field_name(STRING/17): doughnuts_earned units(STRING/10): doughnuts ], \n" +
|
||||
"Local Message: raw: 0 Global Message Number: RECORD=[\n" +
|
||||
"heart_rate(UINT8/1): 140 unknown_4(UINT8/1): 88 unknown_5(UINT32/4): 51000 unknown_6(UINT16/2): 47488 doughnuts_earned(SINT8/1): 1 , \n" +
|
||||
"heart_rate(UINT8/1): 143 unknown_4(UINT8/1): 90 unknown_5(UINT32/4): 208000 unknown_6(UINT16/2): 36416 doughnuts_earned(SINT8/1): 2 , \n" +
|
||||
"heart_rate(UINT8/1): 144 unknown_4(UINT8/1): 92 unknown_5(UINT32/4): 371000 unknown_6(UINT16/2): 35344 doughnuts_earned(SINT8/1): 3 ]}";
|
||||
String expectedOutput = "[" +
|
||||
"FitFileId{manufacturer=15, type=activity, product=9001, serial_number=1701}, " +
|
||||
"FitDeveloperData{application_id=[1,1,2,3,5,8,13,21,34,55,89,144,233,121,98,219], developer_data_index=0}, " +
|
||||
"FitFieldDescription{developer_data_index=0, field_definition_number=0, fit_base_type_id=1, field_name=doughnuts_earned, units=doughnuts}, " +
|
||||
"FitRecord{heart_rate=140, unknown_4(UINT8/1)=88, unknown_5(UINT32/4)=51000, unknown_6(UINT16/2)=47488, doughnuts_earned=1}, " +
|
||||
"FitRecord{heart_rate=143, unknown_4(UINT8/1)=90, unknown_5(UINT32/4)=208000, unknown_6(UINT16/2)=36416, doughnuts_earned=2}, " +
|
||||
"FitRecord{heart_rate=144, unknown_4(UINT8/1)=92, unknown_5(UINT32/4)=371000, unknown_6(UINT16/2)=35344, doughnuts_earned=3}" +
|
||||
"]";
|
||||
|
||||
FitFile fitFile = FitFile.parseIncoming(fileContents);
|
||||
Assert.assertEquals(expectedOutput, fitFile.toString());
|
||||
|
Loading…
Reference in New Issue
Block a user