diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java index 47605e416..9ef4e99bd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java @@ -317,15 +317,19 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni List 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 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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java index fff3e8638..945af6c5b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinition.java @@ -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)) { - return global; + 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, ""); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java index 220639c46..c9635d04e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java @@ -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, } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java index d8deb64d0..9da13c18c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitFile.java @@ -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> dataRecords; + private final List dataRecords; private final boolean canGenerateOutput; - public FitFile(Header header, Map> dataRecords) { + public FitFile(Header header, List dataRecords) { this.header = header; this.dataRecords = dataRecords; this.canGenerateOutput = false; } - public FitFile(LinkedHashMap> dataRecords) { + public FitFile(List 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 recordDefinitionMap = new HashMap<>(); //needed because the headers can be redefined in the file. The last header wins - Map> dataRecords = new LinkedHashMap<>(); + // needed because the headers can be redefined in the file. The last header for a local message number wins + Map recordDefinitionMap = new HashMap<>(); + List 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 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 getRecordsByGlobalMessage(GlobalFITMessage globalFITMessage) { final List 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 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> entry : dataRecords.entrySet()) { - RecordDefinition key = entry.getKey(); - List 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; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java index e6b945b90..ec12a427d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java @@ -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 KNOWNMESSAGES = new HashMap() {{ + 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 KNOWN_MESSAGES = new HashMap() {{ 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 getFieldDefinitionPrimitives() { + return fieldDefinitionPrimitives; + } + @Nullable public List 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; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java index 6c3c427d3..1e8d9309b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java @@ -36,12 +36,16 @@ public enum PredefinedLocalMessage { return null; } - public List 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 fieldDefinitions = globalFITMessage.getFieldDefinitions(globalDefinitionIds); + return new RecordDefinition( + recordHeader, + ByteOrder.BIG_ENDIAN, + globalFITMessage, + fieldDefinitions, + null + ); } public int getType() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java index 5a40a0c7b..905230180 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java @@ -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 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,21 +65,24 @@ 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); - public PredefinedLocalMessage getPredefinedLocalMessage() { - return recordHeader.getPredefinedLocalMessage(); + if (this.getClass().getName().equals(RecordData.class.getName())) { + tsb.append(globalFITMessage.name()); + } + + 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; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java index 8afb12cd3..7e5c8adc2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java @@ -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 fieldDefinitions; private List devFieldDefinitions; - public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, PredefinedLocalMessage predefinedLocalMessage, GlobalFITMessage globalFITMessage, List fieldDefinitions, List devFieldDefinitions) { + public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, GlobalFITMessage globalFITMessage, List fieldDefinitions, List 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 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 fieldDefinitions = new ArrayList<>(numFields); @@ -113,33 +103,24 @@ 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 developerFieldData) { - for (DevFieldDefinition devFieldDef : - getDevFieldDefinitions()) { - for (RecordData recordData : - developerFieldData) { - try { - if (devFieldDef.getFieldDefinitionNumber() == (int) recordData.getFieldByName("field_definition_number") && - devFieldDef.getDeveloperDataIndex() == (int) recordData.getFieldByName("developer_data_index")) { - BaseType baseType = BaseType.fromIdentifier((int) recordData.getFieldByName("fit_base_type_id")); - devFieldDef.setBaseType(baseType); - devFieldDef.setName((String) recordData.getFieldByName("field_name")); - } - } catch (Exception e) { - //ignore + 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")) { + BaseType baseType = BaseType.fromIdentifier((int) recordData.getFieldByName("fit_base_type_id")); + devFieldDef.setBaseType(baseType); + devFieldDef.setName((String) recordData.getFieldByName("field_name")); } + } catch (Exception e) { + //ignore } } - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java index e5a8b7c9d..33eb6ef6d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java @@ -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; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java new file mode 100644 index 000000000..ec83d6ea5 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/codegen/FitCodeGen.java @@ -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 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 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 ""; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java index fdf6a0bca..979e99068 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionLanguage.java @@ -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), ; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java new file mode 100644 index 000000000..caf9a614c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionSleepStage.java @@ -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; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java index eb3b943e0..e9e140d95 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java @@ -142,7 +142,7 @@ public class FieldDefinitionWeatherCondition extends FieldDefinition { } } - enum Condition { + public enum Condition { CLEAR, PARTLY_CLOUDY, MOSTLY_CLOUDY, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java new file mode 100644 index 000000000..aa60939ef --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitAlarmSettings.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java new file mode 100644 index 000000000..da48a0934 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitConnectivity.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java new file mode 100644 index 000000000..50ae99dee --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeveloperData.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java new file mode 100644 index 000000000..04cfd9777 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceInfo.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java new file mode 100644 index 000000000..3598f8876 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitDeviceSettings.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java new file mode 100644 index 000000000..00e69072e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFieldDescription.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java new file mode 100644 index 000000000..b97f9b3e8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileCreator.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java new file mode 100644 index 000000000..191e658fb --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitFileId.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java new file mode 100644 index 000000000..35170fa7a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitGoals.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java new file mode 100644 index 000000000..42a96d3d9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java @@ -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; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java new file mode 100644 index 000000000..c9a16f52f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecord.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java new file mode 100644 index 000000000..459148397 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java new file mode 100644 index 000000000..c04143b9d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStage.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java new file mode 100644 index 000000000..0cb76a090 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSleepStats.java @@ -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); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java new file mode 100644 index 000000000..cf154ee0f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitSport.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java new file mode 100644 index 000000000..24b07ff5e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitStressLevel.java @@ -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 . */ +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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java new file mode 100644 index 000000000..27cdebde9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitUserProfile.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java new file mode 100644 index 000000000..7b80b36ad --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWatchfaceSettings.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java new file mode 100644 index 000000000..aa0bd8b03 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitWeather.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java new file mode 100644 index 000000000..0b317aae6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitZonesTarget.java @@ -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); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java index 338678208..66622df8f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java @@ -24,17 +24,20 @@ public class FitDataMessage extends GFDIMessage { final List 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); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java index b54e93e09..4a48d9535 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java @@ -25,7 +25,7 @@ public class FitDefinitionMessage extends GFDIMessage { List 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)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java new file mode 100644 index 000000000..cce70c336 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBToStringBuilder.java @@ -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); + } + } + } + } +} diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java index 323e3d245..d68f23433 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java @@ -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 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());