mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-18 16:09:30 +01:00
Garmin protocol: various changes
- add FitFile class that deals with parsing and generating outgoing files
- consider all field definitions with number 253 as Timestamps [0]
- add support for "compressed timestamps" in fit file parsing. Those are not returned among the other normal fields but are available through a method of RecordData
- adjust the test cases
[0]48b6554d8a/fitdecode/reader.py (L719)
This commit is contained in:
parent
a5bf32b9f1
commit
f90b544dc9
@ -11,6 +11,10 @@ public class GarminByteBufferReader {
|
|||||||
this.byteBuffer = ByteBuffer.wrap(data);
|
this.byteBuffer = ByteBuffer.wrap(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int remaining() {
|
||||||
|
return byteBuffer.remaining();
|
||||||
|
}
|
||||||
|
|
||||||
public ByteBuffer asReadOnlyBuffer() {
|
public ByteBuffer asReadOnlyBuffer() {
|
||||||
return byteBuffer.asReadOnlyBuffer();
|
return byteBuffer.asReadOnlyBuffer();
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
|
|
||||||
public class FieldDefinition implements FieldInterface {
|
public class FieldDefinition implements FieldInterface {
|
||||||
@ -32,6 +33,8 @@ public class FieldDefinition implements FieldInterface {
|
|||||||
int size = garminByteBufferReader.readByte();
|
int size = garminByteBufferReader.readByte();
|
||||||
int baseTypeIdentifier = garminByteBufferReader.readByte();
|
int baseTypeIdentifier = garminByteBufferReader.readByte();
|
||||||
BaseType baseType = BaseType.fromIdentifier(baseTypeIdentifier);
|
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);
|
FieldDefinition global = globalFITMessage.getFieldDefinition(number, size);
|
||||||
if (null != global && global.getBaseType().equals(baseType)) {
|
if (null != global && global.getBaseType().equals(baseType)) {
|
||||||
|
@ -0,0 +1,214 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
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.GarminByteBufferReader;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ChecksumCalculator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
|
||||||
|
|
||||||
|
public class FitFile {
|
||||||
|
protected static final Logger LOG = LoggerFactory.getLogger(FitFile.class);
|
||||||
|
private final Header header;
|
||||||
|
private final Map<RecordDefinition, List<RecordData>> dataRecords;
|
||||||
|
private final boolean canGenerateOutput;
|
||||||
|
|
||||||
|
public FitFile(Header header, Map<RecordDefinition, List<RecordData>> dataRecords) {
|
||||||
|
this.header = header;
|
||||||
|
this.dataRecords = dataRecords;
|
||||||
|
this.canGenerateOutput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FitFile(LinkedHashMap<RecordDefinition, List<RecordData>> dataRecords) {
|
||||||
|
this.dataRecords = dataRecords;
|
||||||
|
this.header = new Header(true, 16, 21117);
|
||||||
|
this.canGenerateOutput = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readFileToByteArray(File file) {
|
||||||
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); InputStream inputStream = new FileInputStream(file)) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FitFile parseIncoming(File file) {
|
||||||
|
return parseIncoming(readFileToByteArray(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: process file in chunks??
|
||||||
|
public static FitFile parseIncoming(byte[] fileContents) {
|
||||||
|
|
||||||
|
final GarminByteBufferReader garminByteBufferReader = new GarminByteBufferReader(fileContents);
|
||||||
|
garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
final Header header = Header.parseIncomingHeader(garminByteBufferReader);
|
||||||
|
|
||||||
|
Map<RecordHeader, RecordDefinition> recordDefinitionMap = new HashMap<>(); //needed because the headers can be redefined in the file. The last header wins
|
||||||
|
Map<RecordDefinition, List<RecordData>> dataRecords = new LinkedHashMap<>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (recordHeader.isDefinition()) {
|
||||||
|
final RecordDefinition recordDefinition = RecordDefinition.parseIncoming(garminByteBufferReader, recordHeader);
|
||||||
|
if (recordDefinition != null) {
|
||||||
|
if (recordHeader.isDeveloperData())
|
||||||
|
for (RecordDefinition rd : dataRecords.keySet()) {
|
||||||
|
if (GlobalFITMessage.FIELD_DESCRIPTION.equals(rd.getGlobalFITMessage()))
|
||||||
|
recordDefinition.populateDevFields(dataRecords.get(rd));
|
||||||
|
}
|
||||||
|
recordDefinitionMap.put(recordHeader, recordDefinition);
|
||||||
|
dataRecords.put(recordDefinition, new ArrayList<>());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final RecordDefinition referenceRecordDefinition = recordDefinitionMap.get(recordHeader);
|
||||||
|
final List<RecordData> myList = dataRecords.get(referenceRecordDefinition);
|
||||||
|
if (referenceRecordDefinition != null) {
|
||||||
|
final RecordData runningData = new RecordData(referenceRecordDefinition, recordHeader);
|
||||||
|
myList.add(runningData);
|
||||||
|
Long newTimestamp = runningData.parseDataMessage(garminByteBufferReader);
|
||||||
|
if (newTimestamp != null)
|
||||||
|
referenceTimestamp = newTimestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
int fileCrc = garminByteBufferReader.readShort();
|
||||||
|
if (fileCrc != ChecksumCalculator.computeCrc(fileContents, header.getHeaderSize(), fileContents.length - header.getHeaderSize() - 2)) {
|
||||||
|
throw new IllegalArgumentException("Wrong CRC for FIT file");
|
||||||
|
}
|
||||||
|
return new FitFile(header, dataRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RecordData> getRecordsByGlobalMessage(GlobalFITMessage globalFITMessage) {
|
||||||
|
final List<RecordData> filtered = new ArrayList<>();
|
||||||
|
for (RecordDefinition rd : dataRecords.keySet()) {
|
||||||
|
if (globalFITMessage.equals(rd.getGlobalFITMessage()))
|
||||||
|
filtered.addAll(dataRecords.get(rd));
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generateOutgoingDataPayload(MessageWriter writer) {
|
||||||
|
if (!canGenerateOutput)
|
||||||
|
throw new IllegalArgumentException("Generation of previously parsed FIT file not supported.");
|
||||||
|
|
||||||
|
MessageWriter temporary = new MessageWriter();
|
||||||
|
temporary.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
for (Map.Entry<RecordDefinition, List<RecordData>> entry : dataRecords.entrySet()) {
|
||||||
|
RecordDefinition key = entry.getKey();
|
||||||
|
List<RecordData> valueList = entry.getValue();
|
||||||
|
|
||||||
|
key.generateOutgoingPayload(temporary);
|
||||||
|
for (RecordData rd :
|
||||||
|
valueList) {
|
||||||
|
rd.generateOutgoingDataPayload(temporary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.header.setDataSize(temporary.getSize());
|
||||||
|
|
||||||
|
this.header.generateOutgoingDataPayload(writer);
|
||||||
|
writer.writeBytes(temporary.getBytes());
|
||||||
|
writer.writeShort(ChecksumCalculator.computeCrc(writer.getBytes(), this.header.getHeaderSize(), writer.getBytes().length - this.header.getHeaderSize()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return dataRecords.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Header {
|
||||||
|
public static final int MAGIC = 0x5449462E;
|
||||||
|
|
||||||
|
private final int headerSize;
|
||||||
|
private final int protocolVersion;
|
||||||
|
private final int profileVersion;
|
||||||
|
private final boolean hasCRC;
|
||||||
|
private int dataSize;
|
||||||
|
|
||||||
|
public Header(boolean hasCRC, int protocolVersion, int profileVersion) {
|
||||||
|
this(hasCRC, protocolVersion, profileVersion, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header(boolean hasCRC, int protocolVersion, int profileVersion, int dataSize) {
|
||||||
|
this.hasCRC = hasCRC;
|
||||||
|
headerSize = hasCRC ? 14 : 12;
|
||||||
|
this.protocolVersion = protocolVersion;
|
||||||
|
this.profileVersion = profileVersion;
|
||||||
|
this.dataSize = dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Header parseIncomingHeader(GarminByteBufferReader garminByteBufferReader) {
|
||||||
|
int headerSize = garminByteBufferReader.readByte();
|
||||||
|
if (headerSize < 12) {
|
||||||
|
throw new IllegalArgumentException("Too short header in FIT file.");
|
||||||
|
}
|
||||||
|
boolean hasCRC = headerSize == 14;
|
||||||
|
int protocolVersion = garminByteBufferReader.readByte();
|
||||||
|
int profileVersion = garminByteBufferReader.readShort();
|
||||||
|
int dataSize = garminByteBufferReader.readInt();
|
||||||
|
int magic = garminByteBufferReader.readInt();
|
||||||
|
if (magic != MAGIC) {
|
||||||
|
throw new IllegalArgumentException("Wrong magic header in FIT file");
|
||||||
|
}
|
||||||
|
if (hasCRC) {
|
||||||
|
int incomingCrc = garminByteBufferReader.readShort();
|
||||||
|
|
||||||
|
if (incomingCrc != ChecksumCalculator.computeCrc(garminByteBufferReader.asReadOnlyBuffer(), 0, headerSize - 2)) {
|
||||||
|
throw new IllegalArgumentException("Wrong CRC for header in FIT file");
|
||||||
|
}
|
||||||
|
// LOG.info("Fit File Header didn't have CRC, no check performed.");
|
||||||
|
}
|
||||||
|
return new Header(hasCRC, protocolVersion, profileVersion, dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeaderSize() {
|
||||||
|
return headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDataSize() {
|
||||||
|
return dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSize(int dataSize) {
|
||||||
|
this.dataSize = dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generateOutgoingDataPayload(MessageWriter writer) {
|
||||||
|
writer.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
writer.writeByte(headerSize);
|
||||||
|
writer.writeByte(protocolVersion);
|
||||||
|
writer.writeShort(profileVersion);
|
||||||
|
writer.writeInt(dataSize);
|
||||||
|
writer.writeInt(MAGIC);//magic
|
||||||
|
if (hasCRC)
|
||||||
|
writer.writeShort(ChecksumCalculator.computeCrc(writer.getBytes(), 0, writer.getBytes().length));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminByteBufferReader;
|
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.service.devices.garmin.messages.MessageWriter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||||
|
|
||||||
@ -18,16 +19,16 @@ public class RecordData {
|
|||||||
|
|
||||||
private final RecordHeader recordHeader;
|
private final RecordHeader recordHeader;
|
||||||
private final GlobalFITMessage globalFITMessage;
|
private final GlobalFITMessage globalFITMessage;
|
||||||
protected ByteBuffer valueHolder;
|
|
||||||
private final List<FieldData> fieldDataList;
|
private final List<FieldData> fieldDataList;
|
||||||
|
protected ByteBuffer valueHolder;
|
||||||
|
|
||||||
public RecordData(RecordDefinition recordDefinition) {
|
public RecordData(RecordDefinition recordDefinition, RecordHeader recordHeader) {
|
||||||
if (null == recordDefinition.getFieldDefinitions())
|
if (null == recordDefinition.getFieldDefinitions())
|
||||||
throw new IllegalArgumentException("Cannot create record data without FieldDefinitions " + recordDefinition);
|
throw new IllegalArgumentException("Cannot create record data without FieldDefinitions " + recordDefinition);
|
||||||
|
|
||||||
fieldDataList = new ArrayList<>();
|
fieldDataList = new ArrayList<>();
|
||||||
|
|
||||||
this.recordHeader = recordDefinition.getRecordHeader();
|
this.recordHeader = recordHeader;
|
||||||
this.globalFITMessage = recordDefinition.getGlobalFITMessage();
|
this.globalFITMessage = recordDefinition.getGlobalFITMessage();
|
||||||
|
|
||||||
int totalSize = 0;
|
int totalSize = 0;
|
||||||
@ -38,7 +39,7 @@ public class RecordData {
|
|||||||
totalSize += fieldDef.getSize();
|
totalSize += fieldDef.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recordHeader.isDefinition() && recordDefinition.getDevFieldDefinitions() != null) {
|
if (recordDefinition.getDevFieldDefinitions() != null) {
|
||||||
for (DevFieldDefinition fieldDef :
|
for (DevFieldDefinition fieldDef :
|
||||||
recordDefinition.getDevFieldDefinitions()) {
|
recordDefinition.getDevFieldDefinitions()) {
|
||||||
FieldDefinition temp = new FieldDefinition(fieldDef.getFieldDefinitionNumber(), fieldDef.getSize(), fieldDef.getBaseType(), fieldDef.getName());
|
FieldDefinition temp = new FieldDefinition(fieldDef.getFieldDefinitionNumber(), fieldDef.getSize(), fieldDef.getBaseType(), fieldDef.getName());
|
||||||
@ -57,15 +58,23 @@ public class RecordData {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RecordData(RecordDefinition recordDefinition) {
|
||||||
|
this(recordDefinition, recordDefinition.getRecordHeader());
|
||||||
|
}
|
||||||
|
|
||||||
public GlobalFITMessage getGlobalFITMessage() {
|
public GlobalFITMessage getGlobalFITMessage() {
|
||||||
return globalFITMessage;
|
return globalFITMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
public Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
||||||
garminByteBufferReader.setByteOrder(valueHolder.order());
|
garminByteBufferReader.setByteOrder(valueHolder.order());
|
||||||
|
Long referenceTimestamp = null;
|
||||||
for (FieldData fieldData : fieldDataList) {
|
for (FieldData fieldData : fieldDataList) {
|
||||||
fieldData.parseDataMessage(garminByteBufferReader);
|
Long runningTimestamp = fieldData.parseDataMessage(garminByteBufferReader);
|
||||||
|
if (runningTimestamp != null)
|
||||||
|
referenceTimestamp = runningTimestamp;
|
||||||
}
|
}
|
||||||
|
return referenceTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateOutgoingDataPayload(MessageWriter writer) {
|
public void generateOutgoingDataPayload(MessageWriter writer) {
|
||||||
@ -133,9 +142,20 @@ public class RecordData {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder oBuilder = new StringBuilder();
|
StringBuilder oBuilder = new StringBuilder();
|
||||||
|
oBuilder.append(System.lineSeparator());
|
||||||
for (FieldData fieldData :
|
for (FieldData fieldData :
|
||||||
fieldDataList) {
|
fieldDataList) {
|
||||||
if (fieldData.getName() != null && !fieldData.getName().equals("")) {
|
if (fieldData.getName() != null && !fieldData.getName().equals("")) {
|
||||||
@ -155,7 +175,8 @@ public class RecordData {
|
|||||||
}
|
}
|
||||||
oBuilder.append(" ");
|
oBuilder.append(" ");
|
||||||
}
|
}
|
||||||
|
if (recordHeader.isCompressedTimestamp())
|
||||||
|
oBuilder.append("compressed_timestamp: " + getComputedTimestamp());
|
||||||
return oBuilder.toString();
|
return oBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,9 +222,12 @@ public class RecordData {
|
|||||||
valueHolder.position(position);
|
valueHolder.position(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
private Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
|
||||||
goToPosition();
|
goToPosition();
|
||||||
valueHolder.put(garminByteBufferReader.readBytes(size));
|
valueHolder.put(garminByteBufferReader.readBytes(size));
|
||||||
|
if (fieldDefinition instanceof FieldDefinitionTimestamp)
|
||||||
|
return (Long) decode();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encode(Object... objects) {
|
private void encode(Object... objects) {
|
||||||
|
@ -123,7 +123,7 @@ public class RecordDefinition {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return recordHeader.toString() +
|
return System.lineSeparator() + recordHeader.toString() +
|
||||||
" Global Message Number: " + globalFITMessage.name();
|
" Global Message Number: " + globalFITMessage.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class RecordHeader {
|
public class RecordHeader {
|
||||||
private final boolean definition;
|
private final boolean definition;
|
||||||
@ -10,6 +9,7 @@ public class RecordHeader {
|
|||||||
private final LocalMessage localMessage;
|
private final LocalMessage localMessage;
|
||||||
private final int rawLocalMessageType;
|
private final int rawLocalMessageType;
|
||||||
private final Integer timeOffset;
|
private final Integer timeOffset;
|
||||||
|
private long referenceTimestamp;
|
||||||
|
|
||||||
public RecordHeader(boolean definition, boolean developerData, LocalMessage localMessage, Integer timeOffset) {
|
public RecordHeader(boolean definition, boolean developerData, LocalMessage localMessage, Integer timeOffset) {
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
@ -35,6 +35,22 @@ public class RecordHeader {
|
|||||||
localMessage = LocalMessage.fromType(rawLocalMessageType);
|
localMessage = LocalMessage.fromType(rawLocalMessageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReferenceTimestamp(long referenceTimestamp) {
|
||||||
|
this.referenceTimestamp = referenceTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTimeOffset() {
|
||||||
|
return timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompressedTimestamp() {
|
||||||
|
return timeOffset != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getResultingTimestamp() {
|
||||||
|
return referenceTimestamp + timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDeveloperData() {
|
public boolean isDeveloperData() {
|
||||||
return developerData;
|
return developerData;
|
||||||
}
|
}
|
||||||
@ -81,18 +97,14 @@ public class RecordHeader {
|
|||||||
|
|
||||||
RecordHeader that = (RecordHeader) o;
|
RecordHeader that = (RecordHeader) o;
|
||||||
|
|
||||||
if (definition != that.definition) return false;
|
|
||||||
if (rawLocalMessageType != that.rawLocalMessageType) return false;
|
if (rawLocalMessageType != that.rawLocalMessageType) return false;
|
||||||
if (localMessage != that.localMessage) return false;
|
return localMessage == that.localMessage;
|
||||||
return Objects.equals(timeOffset, that.timeOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = (definition ? 1 : 0);
|
int result = (localMessage != null ? localMessage.hashCode() : 0);
|
||||||
result = 31 * result + (localMessage != null ? localMessage.hashCode() : 0);
|
|
||||||
result = 31 * result + rawLocalMessageType;
|
result = 31 * result + rawLocalMessageType;
|
||||||
result = 31 * result + (timeOffset != null ? timeOffset.hashCode() : 0);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@ public enum BaseType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static BaseType fromIdentifier(int identifier) {
|
public static BaseType fromIdentifier(int identifier) {
|
||||||
for (final BaseType status : BaseType.values()) {
|
for (final BaseType baseType : BaseType.values()) {
|
||||||
if (status.getIdentifier() == identifier) {
|
if (baseType.getIdentifier() == identifier) {
|
||||||
return status;
|
return baseType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Unknown type " + identifier);
|
throw new IllegalArgumentException("Unknown type " + identifier);
|
||||||
|
@ -173,10 +173,6 @@ public abstract class GFDIMessage {
|
|||||||
this.byteBuffer.limit(payloadSize - 2); //remove CRC
|
this.byteBuffer.limit(payloadSize - 2); //remove CRC
|
||||||
}
|
}
|
||||||
|
|
||||||
public int remaining() {
|
|
||||||
return byteBuffer.remaining();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void skip(int offset) {
|
public void skip(int offset) {
|
||||||
if (remaining() < offset) throw new IllegalStateException();
|
if (remaining() < offset) throw new IllegalStateException();
|
||||||
byteBuffer.position(byteBuffer.position() + offset);
|
byteBuffer.position(byteBuffer.position() + offset);
|
||||||
|
@ -15,6 +15,7 @@ import java.util.TimeZone;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.LocalMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.LocalMessage;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||||
@ -365,57 +366,6 @@ public class GarminSupportTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fitFileParser(byte[] fileContents) {
|
|
||||||
StringBuilder oBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
GarminByteBufferReader garminByteBufferReader = new GarminByteBufferReader(fileContents);
|
|
||||||
garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
//parseHeader
|
|
||||||
int headerSize = garminByteBufferReader.readByte(); //1
|
|
||||||
int protocolVersion = garminByteBufferReader.readByte(); //2
|
|
||||||
int profileVersion = garminByteBufferReader.readShort(); //4
|
|
||||||
int dataSize = garminByteBufferReader.readInt(); //8
|
|
||||||
int magic = garminByteBufferReader.readInt(); //12
|
|
||||||
Assert.assertEquals(0x5449462E, magic);
|
|
||||||
int headerCrc = garminByteBufferReader.readShort();
|
|
||||||
Assert.assertEquals(ChecksumCalculator.computeCrc(fileContents, 0, headerSize - 2), headerCrc);
|
|
||||||
//end of parse header
|
|
||||||
|
|
||||||
Map<RecordHeader, RecordDefinition> recordDefinitionMap = new HashMap<>(); //questo va bene qui (ultimo vince)
|
|
||||||
Map<RecordHeader, RecordData> recordDataMap = new HashMap<>();
|
|
||||||
List<RecordData> developerFieldDescriptionData = new ArrayList<>();
|
|
||||||
|
|
||||||
while (garminByteBufferReader.getPosition() < fileContents.length - 2) {
|
|
||||||
byte rawRecordHeader = (byte) garminByteBufferReader.readByte();
|
|
||||||
RecordHeader recordHeader = new RecordHeader(rawRecordHeader);
|
|
||||||
if (recordHeader.isDefinition()) {
|
|
||||||
final RecordDefinition recordDefinition = RecordDefinition.parseIncoming(garminByteBufferReader, recordHeader);
|
|
||||||
if (recordDefinition != null) {
|
|
||||||
if (recordHeader.isDeveloperData()) {
|
|
||||||
recordDefinition.populateDevFields(developerFieldDescriptionData);
|
|
||||||
}
|
|
||||||
oBuilder.append(recordDefinition.toString());
|
|
||||||
recordDefinitionMap.put(recordHeader, recordDefinition);
|
|
||||||
recordDataMap.put(new RecordHeader(recordHeader.generateOutgoingDataPayload()), new RecordData(recordDefinition));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final RecordData recordData = recordDataMap.get(recordHeader);
|
|
||||||
if (recordData != null) {
|
|
||||||
recordData.parseDataMessage(garminByteBufferReader);
|
|
||||||
if (GlobalFITMessage.FIELD_DESCRIPTION.equals(recordData.getGlobalFITMessage())) {
|
|
||||||
developerFieldDescriptionData.add(recordData);
|
|
||||||
}
|
|
||||||
oBuilder.append(recordData.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oBuilder.append(System.lineSeparator());
|
|
||||||
}
|
|
||||||
garminByteBufferReader.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
int fileCrc = garminByteBufferReader.readShort();
|
|
||||||
Assert.assertEquals(ChecksumCalculator.computeCrc(fileContents, headerSize, fileContents.length - headerSize - 2), fileCrc);
|
|
||||||
|
|
||||||
return oBuilder.toString();
|
|
||||||
}
|
|
||||||
@Test
|
@Test
|
||||||
public void TestFitFileSettings2() {
|
public void TestFitFileSettings2() {
|
||||||
|
|
||||||
@ -478,37 +428,38 @@ public class GarminSupportTest {
|
|||||||
"020b00004b00007f00090309070001000401000501000601000701000801" +
|
"020b00004b00007f00090309070001000401000501000601000701000801" +
|
||||||
"000901000a01000b45646765203531300000ffffffffffffff09ef");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/Settings2.fit
|
"000901000a01000b45646765203531300000ffffffffffffff09ef");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/Settings2.fit
|
||||||
|
|
||||||
String expectedOutput = "Local Message: raw: 0 Global Message Number: FILE_ID\n" +
|
String expectedOutput = "{\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: 0 Global Message Number: FILE_ID=[\n" +
|
||||||
"Local Message: raw: 1 Global Message Number: FILE_CREATOR\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" +
|
||||||
"software_version(UINT16/2): 340 hardware_version(UINT8/1): null \n" +
|
"Local Message: raw: 1 Global Message Number: FILE_CREATOR=[\n" +
|
||||||
"Local Message: raw: 2 Global Message Number: DEVICE_SETTINGS\n" +
|
"software_version(UINT16/2): 340 hardware_version(UINT8/1): null ], \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: 2 Global Message Number: DEVICE_SETTINGS=[\n" +
|
||||||
"Local Message: raw: 3 Global Message Number: USER_PROFILE\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" +
|
||||||
"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: 3 Global Message Number: USER_PROFILE=[\n" +
|
||||||
"Local Message: raw: 4 Global Message Number: UNK_4\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" +
|
||||||
"unknown_254(UINT16/2): 0 unknown_1(UINT16Z/2): 50008 unknown_0(UINT8/1): 1 unknown_3(UINT8Z/1): 1 \n" +
|
"Local Message: raw: 4 Global Message Number: UNK_4=[\n" +
|
||||||
"Local Message: raw: 5 Global Message Number: UNK_6\n" +
|
"unknown_254(UINT16/2): 0 unknown_1(UINT16Z/2): 50008 unknown_0(UINT8/1): 1 unknown_3(UINT8Z/1): 1 ], \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: 5 Global Message Number: UNK_6=[\n" +
|
||||||
"Local Message: type: TODAY_WEATHER_CONDITIONS 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" +
|
||||||
"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: type: TODAY_WEATHER_CONDITIONS Global Message Number: UNK_6=[\n" +
|
||||||
"Local Message: raw: 7 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" +
|
||||||
"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: 7 Global Message Number: UNK_6=[\n" +
|
||||||
"Local Message: raw: 8 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" +
|
||||||
"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: 8 Global Message Number: UNK_6=[\n" +
|
||||||
"Local Message: type: HOURLY_WEATHER_FORECAST 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" +
|
||||||
"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" +
|
"Local Message: type: HOURLY_WEATHER_FORECAST Global Message Number: UNK_6=[\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 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 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 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 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 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 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" +
|
"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" +
|
||||||
"Local Message: type: DAILY_WEATHER_FORECAST Global Message Number: UNK_6\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" +
|
||||||
"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: type: DAILY_WEATHER_FORECAST Global Message Number: UNK_6=[\n" +
|
||||||
"Local Message: raw: 11 Global Message Number: CONNECTIVITY\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" +
|
||||||
"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 \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 ]}";
|
||||||
|
|
||||||
|
FitFile fitFile = FitFile.parseIncoming(fileContents);
|
||||||
Assert.assertEquals(expectedOutput, fitFileParser(fileContents));
|
Assert.assertEquals(expectedOutput, fitFile.toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,17 +467,19 @@ public class GarminSupportTest {
|
|||||||
public void TestFitFileDevelopersField() {
|
public void TestFitFileDevelopersField() {
|
||||||
byte[] fileContents = GB.hexStringToByteArray("0e206806a20000002e464954bed040000100000401028400010002028403048c00000f042329000006a540000100cf0201100d030102000101020305080d1522375990e97962db0040000100ce05000102010102020102031107080a0700000001646f7567686e7574735f6561726e656400646f7567686e7574730060000100140403010204010205048606028401000100008c580000c738b98001008f5a00032c808e400200905c0005a9388a1003d39e");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/DeveloperData.fit
|
byte[] fileContents = GB.hexStringToByteArray("0e206806a20000002e464954bed040000100000401028400010002028403048c00000f042329000006a540000100cf0201100d030102000101020305080d1522375990e97962db0040000100ce05000102010102020102031107080a0700000001646f7567686e7574735f6561726e656400646f7567686e7574730060000100140403010204010205048606028401000100008c580000c738b98001008f5a00032c808e400200905c0005a9388a1003d39e");//https://github.com/polyvertex/fitdecode/blob/48b6554d8a3baf33f8b5b9b2fd079fcbe9ac8ce2/tests/files/DeveloperData.fit
|
||||||
|
|
||||||
String expectedOutput = "Local Message: raw: 0 Global Message Number: FILE_ID\n" +
|
String expectedOutput = "{\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: FILE_ID=[\n" +
|
||||||
"Local Message: raw: 0 Global Message Number: DEVELOPER_DATA\n" +
|
"manufacturer(UINT16/2): 15 type(ENUM/1): activity product(UINT16/2): 9001 serial_number(UINT32Z/4): 1701 ], \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: DEVELOPER_DATA=[\n" +
|
||||||
"Local Message: raw: 0 Global Message Number: FIELD_DESCRIPTION\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" +
|
||||||
"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: FIELD_DESCRIPTION=[\n" +
|
||||||
"Local Message: raw: 0 Global Message Number: RECORD\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" +
|
||||||
"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" +
|
"Local Message: raw: 0 Global Message Number: RECORD=[\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): 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): 144 unknown_4(UINT8/1): 92 unknown_5(UINT32/4): 371000 unknown_6(UINT16/2): 35344 doughnuts_earned(SINT8/1): 3 \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 ]}";
|
||||||
|
|
||||||
Assert.assertEquals(expectedOutput, fitFileParser(fileContents));
|
FitFile fitFile = FitFile.parseIncoming(fileContents);
|
||||||
|
Assert.assertEquals(expectedOutput, fitFile.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user