1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-02 19:36:14 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java
Daniele Gobbetti 1a120bcd50 Garmin: Rename LocalMessage to PredefinedLocalMessage and clarify its usage
PredefinedLocalMessage are only useful for FIT messages and should not interfere with FIT files. The only impact of using the local message in fit files was in the textual output, but it was confusing.

Add an explicit constructor to RecordHeader if PredefinedLocalMessage should be taken into account, and use this only in fit messages leaving the default constructor for fit files.

Also adjusts the test case as textual output comparison needs to be fixed.
2024-04-23 16:39:59 +02:00

280 lines
10 KiB
Java

package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
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 static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType.STRING;
public class RecordData {
private final RecordHeader recordHeader;
private final GlobalFITMessage globalFITMessage;
private final List<FieldData> fieldDataList;
protected ByteBuffer valueHolder;
public RecordData(RecordDefinition recordDefinition, RecordHeader recordHeader) {
if (null == recordDefinition.getFieldDefinitions())
throw new IllegalArgumentException("Cannot create record data without FieldDefinitions " + recordDefinition);
fieldDataList = new ArrayList<>();
this.recordHeader = recordHeader;
this.globalFITMessage = recordDefinition.getGlobalFITMessage();
int totalSize = 0;
for (FieldDefinition fieldDef :
recordDefinition.getFieldDefinitions()) {
fieldDataList.add(new FieldData(fieldDef, totalSize));
totalSize += fieldDef.getSize();
}
if (recordDefinition.getDevFieldDefinitions() != null) {
for (DevFieldDefinition fieldDef :
recordDefinition.getDevFieldDefinitions()) {
FieldDefinition temp = new FieldDefinition(fieldDef.getFieldDefinitionNumber(), fieldDef.getSize(), fieldDef.getBaseType(), fieldDef.getName());
fieldDataList.add(new FieldData(temp, totalSize));
totalSize += fieldDef.getSize();
}
}
this.valueHolder = ByteBuffer.allocate(totalSize);
valueHolder.order(recordDefinition.getByteOrder());
for (FieldData fieldData :
fieldDataList) {
fieldData.invalidate();
}
}
public RecordData(RecordDefinition recordDefinition) {
this(recordDefinition, recordDefinition.getRecordHeader());
}
public GlobalFITMessage getGlobalFITMessage() {
return globalFITMessage;
}
public Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
garminByteBufferReader.setByteOrder(valueHolder.order());
Long referenceTimestamp = null;
for (FieldData fieldData : fieldDataList) {
Long runningTimestamp = fieldData.parseDataMessage(garminByteBufferReader);
if (runningTimestamp != null)
referenceTimestamp = runningTimestamp;
}
return referenceTimestamp;
}
public void generateOutgoingDataPayload(MessageWriter writer) {
writer.writeByte(recordHeader.generateOutgoingDataPayload());
writer.writeBytes(valueHolder.array());
}
public void setFieldByNumber(int number, Object... value) {
boolean found = false;
for (FieldData fieldData :
fieldDataList) {
if (fieldData.getNumber() == number) {
fieldData.encode(value);
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException("Unknown field number " + number);
}
}
public void setFieldByName(String name, Object... value) {
boolean found = false;
for (FieldData fieldData :
fieldDataList) {
if (fieldData.getName().equals(name)) {
fieldData.encode(value);
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException("Unknown field name " + name);
}
}
public Object getFieldByNumber(int number) {
for (FieldData fieldData :
fieldDataList) {
if (fieldData.getNumber() == number) {
return fieldData.decode();
}
}
throw new IllegalArgumentException("Unknown field number " + number);
}
public Object getFieldByName(String name) {
for (FieldData fieldData :
fieldDataList) {
if (fieldData.getName().equals(name)) {
return fieldData.decode();
}
}
throw new IllegalArgumentException("Unknown field name " + name);
}
public int[] getFieldsNumbers() {
int[] arr = new int[fieldDataList.size()];
int count = 0;
for (FieldData fieldData : fieldDataList) {
int number = fieldData.getNumber();
arr[count++] = number;
}
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
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();
}
public PredefinedLocalMessage getPredefinedLocalMessage() {
return recordHeader.getPredefinedLocalMessage();
}
private class FieldData {
private final FieldDefinition fieldDefinition;
private final int position;
private final int size;
private final int baseSize;
public FieldData(FieldDefinition fieldDefinition, int position) {
this.fieldDefinition = fieldDefinition;
this.position = position;
this.size = fieldDefinition.getSize();
this.baseSize = fieldDefinition.getBaseType().getSize();
}
private String getName() {
return fieldDefinition.getName();
}
private int getNumber() {
return fieldDefinition.getNumber();
}
private void invalidate() {
goToPosition();
if (STRING.equals(fieldDefinition.getBaseType())) {
for (int i = 0; i < size; i++) {
valueHolder.put((byte) 0);
}
return;
}
for (int i = 0; i < (size / baseSize); i++) {
fieldDefinition.invalidate(valueHolder);
}
}
private void goToPosition() {
valueHolder.position(position);
}
private Long parseDataMessage(GarminByteBufferReader garminByteBufferReader) {
goToPosition();
valueHolder.put(garminByteBufferReader.readBytes(size));
if (fieldDefinition instanceof FieldDefinitionTimestamp)
return (Long) decode();
return null;
}
private void encode(Object... objects) {
if (objects[0] instanceof boolean[] || objects[0] instanceof short[] || objects[0] instanceof int[] || objects[0] instanceof long[] || objects[0] instanceof float[] || objects[0] instanceof double[]) {
throw new IllegalArgumentException("Array of primitive types not supported, box them to objects");
}
goToPosition();
final int slots = size / baseSize;
int i = 0;
for (Object o : objects) {
if (i++ >= slots) {
throw new IllegalArgumentException("Number of elements in array was too big for the field");
}
if (STRING.equals(fieldDefinition.getBaseType())) {
final byte[] bytes = ((String) o).getBytes(StandardCharsets.UTF_8);
valueHolder.put(Arrays.copyOf(bytes, Math.min(this.size - 1, bytes.length)));
valueHolder.put((byte) 0);
return;
}
fieldDefinition.encode(valueHolder, o);
}
}
private Object decode() {
goToPosition();
if (STRING.equals(fieldDefinition.getBaseType())) {
final byte[] bytes = new byte[size];
valueHolder.get(bytes);
final int zero = ArrayUtils.indexOf((byte) 0, bytes);
if (zero < 0) {
return new String(bytes, StandardCharsets.UTF_8);
}
return new String(bytes, 0, zero, StandardCharsets.UTF_8);
}
if (size > baseSize) {
Object[] arr = new Object[size / baseSize];
for (int i = 0; i < arr.length; i++) {
arr[i] = fieldDefinition.decode(valueHolder);
}
return arr;
}
return fieldDefinition.decode(valueHolder);
}
public String toString() {
return "(" + fieldDefinition.getBaseType().name() + "/" + size + ")";
}
}
}