1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-01 19:06:06 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java
José Rebelo 1328ce13e1 Garmin: Improve fit parsing
* Remove the dependency on PredefinedLocalMessage from generic fit parsing code
* Standardize toString methods, omit types for known fields
* Return null on unknown field number or names, instead of crashing
* Map more Global FIT messages (device info, monitoring, sleep stages, sleep stats, stress level)
* Prioritize "timestamp" over "253_timestamp" if specified explicitly in the global message definition
* Introduce RecordData wrappers for each global message, allowing us to have proper types when getting data. If missing or unknown, the getter returns null. All classes are auto-generated by the FitCodeGen.
* Persist a list of RecordData, instead of a Map from RecordDefinition
* Fix parsing of compressed timestamps - keep them in computedTimestamp on each data record
* Use timestamp16 if available in Monitoring records
2024-05-03 20:28:12 +02:00

127 lines
5.0 KiB
Java

package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
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.messages.MessageWriter;
public class RecordDefinition {
private final RecordHeader recordHeader;
private final GlobalFITMessage globalFITMessage;
private final java.nio.ByteOrder byteOrder;
private List<FieldDefinition> fieldDefinitions;
private List<DevFieldDefinition> devFieldDefinitions;
public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, GlobalFITMessage globalFITMessage, List<FieldDefinition> fieldDefinitions, List<DevFieldDefinition> devFieldDefinitions) {
this.recordHeader = recordHeader;
this.byteOrder = byteOrder;
this.globalFITMessage = globalFITMessage;
this.fieldDefinitions = fieldDefinitions;
this.devFieldDefinitions = devFieldDefinitions;
}
public static RecordDefinition parseIncoming(GarminByteBufferReader garminByteBufferReader, RecordHeader recordHeader) {
if (!recordHeader.isDefinition())
return null;
garminByteBufferReader.readByte();//ignore
ByteOrder byteOrder = garminByteBufferReader.readByte() == 0x01 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
garminByteBufferReader.setByteOrder(byteOrder);
final int globalMesgNum = garminByteBufferReader.readShort();
final GlobalFITMessage globalFITMessage = GlobalFITMessage.fromNumber(globalMesgNum);
RecordDefinition definitionMessage = new RecordDefinition(recordHeader, byteOrder, globalFITMessage, null, null);
final int numFields = garminByteBufferReader.readByte();
List<FieldDefinition> fieldDefinitions = new ArrayList<>(numFields);
for (int i = 0; i < numFields; i++) {
fieldDefinitions.add(FieldDefinition.parseIncoming(garminByteBufferReader, globalFITMessage));
}
definitionMessage.setFieldDefinitions(fieldDefinitions);
if (recordHeader.isDeveloperData()) {
final int numDevFields = garminByteBufferReader.readByte();
List<DevFieldDefinition> devFieldDefinitions = new ArrayList<>(numDevFields);
for (int i = 0; i < numDevFields; i++) {
devFieldDefinitions.add(DevFieldDefinition.parseIncoming(garminByteBufferReader));
}
definitionMessage.setDevFieldDefinitions(devFieldDefinitions);
}
return definitionMessage;
}
public GlobalFITMessage getGlobalFITMessage() {
return globalFITMessage;
}
public ByteOrder getByteOrder() {
return byteOrder;
}
public List<DevFieldDefinition> getDevFieldDefinitions() {
return devFieldDefinitions;
}
public void setDevFieldDefinitions(List<DevFieldDefinition> devFieldDefinitions) {
this.devFieldDefinitions = devFieldDefinitions;
}
public RecordHeader getRecordHeader() {
return recordHeader;
}
@Nullable
public List<FieldDefinition> getFieldDefinitions() {
return fieldDefinitions;
}
public void setFieldDefinitions(List<FieldDefinition> fieldDefinitions) {
this.fieldDefinitions = fieldDefinitions;
}
public void generateOutgoingPayload(MessageWriter writer) {
writer.writeByte(recordHeader.generateOutgoingDefinitionPayload());
writer.writeByte(0);//ignore
writer.writeByte(byteOrder == ByteOrder.LITTLE_ENDIAN ? 0 : 1);
writer.setByteOrder(byteOrder);
writer.writeShort(globalFITMessage.getNumber());
if (fieldDefinitions != null) {
writer.writeByte(fieldDefinitions.size());
for (FieldDefinition fieldDefinition : fieldDefinitions) {
fieldDefinition.generateOutgoingPayload(writer);
}
}
}
@NonNull
public String toString() {
return System.lineSeparator() + recordHeader.toString() +
" Global Message Number: " + globalFITMessage.name();
}
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
}
}
}
}