Garmin protocol: move field encode/decode interface to the FieldDefinition

This allows for semantic subclassing the FieldDefinition.
A FieldDefinitionTimestamp subclass is introduced as example
This commit is contained in:
Daniele Gobbetti 2024-03-24 16:32:51 +01:00
parent 719a104811
commit 40d064cf7f
7 changed files with 98 additions and 41 deletions

View File

@ -147,8 +147,8 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
RecordData today = new RecordData(GlobalDefinitionsEnum.TODAY_WEATHER_CONDITIONS.getRecordDefinition());
today.setFieldByName("weather_report", 0); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
today.setFieldByName("timestamp", GarminTimeUtils.unixTimeToGarminTimestamp(weather.timestamp));
today.setFieldByName("observed_at_time", GarminTimeUtils.unixTimeToGarminTimestamp(weather.timestamp));
today.setFieldByName("timestamp", weather.timestamp);
today.setFieldByName("observed_at_time", weather.timestamp);
today.setFieldByName("temperature", weather.currentTemp - 273.15);
today.setFieldByName("low_temperature", weather.todayMinTemp - 273.15);
today.setFieldByName("high_temperature", weather.todayMaxTemp - 273.15);
@ -168,7 +168,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
WeatherSpec.Hourly hourly = weather.hourly.get(hour);
RecordData weatherHourlyForecast = new RecordData(GlobalDefinitionsEnum.HOURLY_WEATHER_FORECAST.getRecordDefinition());
weatherHourlyForecast.setFieldByName("weather_report", 1); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
weatherHourlyForecast.setFieldByName("timestamp", GarminTimeUtils.unixTimeToGarminTimestamp(hourly.timestamp));
weatherHourlyForecast.setFieldByName("timestamp", hourly.timestamp);
weatherHourlyForecast.setFieldByName("temperature", hourly.temp - 273.15);
weatherHourlyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(hourly.conditionCode));
weatherHourlyForecast.setFieldByName("wind_direction", hourly.windDirection);
@ -184,7 +184,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
//
RecordData todayDailyForecast = new RecordData(GlobalDefinitionsEnum.DAILY_WEATHER_FORECAST.getRecordDefinition());
todayDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
todayDailyForecast.setFieldByName("timestamp", GarminTimeUtils.unixTimeToGarminTimestamp(weather.timestamp));
todayDailyForecast.setFieldByName("timestamp", weather.timestamp);
todayDailyForecast.setFieldByName("low_temperature", weather.todayMinTemp - 273.15);
todayDailyForecast.setFieldByName("high_temperature", weather.todayMaxTemp - 273.15);
todayDailyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(weather.currentConditionCode));
@ -199,7 +199,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
int ts = weather.timestamp + (day + 1) * 24 * 60 * 60; //TODO: is this needed?
RecordData weatherDailyForecast = new RecordData(GlobalDefinitionsEnum.DAILY_WEATHER_FORECAST.getRecordDefinition());
weatherDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
weatherDailyForecast.setFieldByName("timestamp", GarminTimeUtils.unixTimeToGarminTimestamp(weather.timestamp));
weatherDailyForecast.setFieldByName("timestamp", weather.timestamp);
weatherDailyForecast.setFieldByName("low_temperature", daily.minTemp - 273.15);
weatherDailyForecast.setFieldByName("high_temperature", daily.maxTemp - 273.15);
weatherDailyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(daily.conditionCode));

View File

@ -5,7 +5,7 @@ import org.threeten.bp.ZoneId;
public class GarminTimeUtils {
private static final int GARMIN_TIME_EPOCH = 631065600;
public static final int GARMIN_TIME_EPOCH = 631065600;
public static int unixTimeToGarminTimestamp(int unixTime) {
return unixTime - GARMIN_TIME_EPOCH;

View File

@ -1,16 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import java.nio.ByteBuffer;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
public class FieldDefinition {
public class FieldDefinition implements FieldInterface {
private final int localNumber;
private final int size;
private final BaseType baseType;
protected final BaseType baseType;
private final String name;
private final int scale;
private final int offset;
protected final int scale;
protected final int offset;
public FieldDefinition(int localNumber, int size, BaseType baseType, String name, int scale, int offset) {
this.localNumber = localNumber;
@ -70,4 +72,18 @@ public class FieldDefinition {
writer.writeByte(baseType.getIdentifier());
}
@Override
public Object decode(ByteBuffer byteBuffer) {
return baseType.decode(byteBuffer, scale, offset);
}
@Override
public void encode(ByteBuffer byteBuffer, Object o) {
baseType.encode(byteBuffer, o, scale, offset);
}
@Override
public void invalidate(ByteBuffer byteBuffer) {
baseType.invalidate(byteBuffer);
}
}

View File

@ -0,0 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
import java.nio.ByteBuffer;
public interface FieldInterface {
Object decode(ByteBuffer byteBuffer);
void encode(ByteBuffer byteBuffer, Object o);
void invalidate(ByteBuffer byteBuffer);
}

View File

@ -6,6 +6,7 @@ import java.nio.ByteOrder;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp;
public enum GlobalDefinitionsEnum {
TODAY_WEATHER_CONDITIONS(MesgType.TODAY_WEATHER_CONDITIONS, new RecordDefinition(
@ -13,8 +14,8 @@ public enum GlobalDefinitionsEnum {
ByteOrder.BIG_ENDIAN,
MesgType.TODAY_WEATHER_CONDITIONS,
Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"),
new FieldDefinition(253, 4, BaseType.UINT32, "timestamp"),
new FieldDefinition(9, 4, BaseType.UINT32, "observed_at_time"),
new FieldDefinitionTimestamp(253, 4, BaseType.UINT32, "timestamp"),
new FieldDefinitionTimestamp(9, 4, BaseType.UINT32, "observed_at_time"),
new FieldDefinition(1, 1, BaseType.SINT8, "temperature"),
new FieldDefinition(14, 1, BaseType.SINT8, "low_temperature"),
new FieldDefinition(13, 1, BaseType.SINT8, "high_temperature"),
@ -33,7 +34,7 @@ public enum GlobalDefinitionsEnum {
ByteOrder.BIG_ENDIAN,
MesgType.HOURLY_WEATHER_FORECAST,
Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"),
new FieldDefinition(253, 4, BaseType.UINT32, "timestamp"),
new FieldDefinitionTimestamp(253, 4, BaseType.UINT32, "timestamp"),
new FieldDefinition(1, 1, BaseType.SINT8, "temperature"),
new FieldDefinition(2, 1, BaseType.ENUM, "condition"),
new FieldDefinition(3, 2, BaseType.UINT16, "wind_direction"),
@ -49,7 +50,7 @@ public enum GlobalDefinitionsEnum {
ByteOrder.BIG_ENDIAN,
MesgType.DAILY_WEATHER_FORECAST,
Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"),
new FieldDefinition(253, 4, BaseType.UINT32, "timestamp"),
new FieldDefinitionTimestamp(253, 4, BaseType.UINT32, "timestamp"),
new FieldDefinition(14, 1, BaseType.SINT8, "low_temperature"),
new FieldDefinition(13, 1, BaseType.SINT8, "high_temperature"),
new FieldDefinition(2, 1, BaseType.ENUM, "condition"),

View File

@ -29,7 +29,7 @@ public class RecordData {
for (FieldDefinition fieldDef :
recordDefinition.getFieldDefinitions()) {
fieldDataList.add(new FieldData(fieldDef.getBaseType(), totalSize, fieldDef.getSize(), fieldDef.getName(), fieldDef.getLocalNumber(), fieldDef.getScale(), fieldDef.getOffset()));
fieldDataList.add(new FieldData(fieldDef, totalSize));
totalSize += fieldDef.getSize();
}
@ -90,10 +90,10 @@ public class RecordData {
StringBuilder oBuilder = new StringBuilder();
for (FieldData fieldData :
fieldDataList) {
if (fieldData.getName() != null) {
if (fieldData.getName() != null && !fieldData.getName().equals("")) {
oBuilder.append(fieldData.getName());
} else {
oBuilder.append(fieldData.getNumber());
oBuilder.append("unknown_" + fieldData.getNumber());
}
oBuilder.append(": ");
oBuilder.append(fieldData.decode());
@ -104,45 +104,46 @@ public class RecordData {
}
private class FieldData {
private final BaseType baseType;
public FieldData getFieldByNumber(int number) {
for (FieldData fieldData :
fieldDataList) {
if (number == fieldData.getNumber()) {
return fieldData;
}
}
return null;
}
public class FieldData {
private FieldDefinition fieldDefinition;
private final int position;
private final int size;
private final String name;
private final int scale;
private final int offset;
private final int number;
public FieldData(BaseType baseType, int position, int size, String name, int number, int scale, int offset) {
this.baseType = baseType;
public FieldData(FieldDefinition fieldDefinition, int position) {
this.fieldDefinition = fieldDefinition;
this.position = position;
this.size = size;
this.name = name;
this.number = number;
this.scale = scale;
this.offset = offset;
this.size = fieldDefinition.getSize();
}
public BaseType getBaseType() {
return baseType;
}
public String getName() {
return name;
return fieldDefinition.getName();
}
public int getNumber() {
return number;
return fieldDefinition.getLocalNumber();
}
public void invalidate() {
goToPosition();
if (STRING.equals(getBaseType())) {
if (STRING.equals(fieldDefinition.getBaseType())) {
for (int i = 0; i < size; i++) {
valueHolder.put((byte) 0);
}
return;
}
baseType.invalidate(valueHolder);
fieldDefinition.invalidate(valueHolder);
}
private void goToPosition() {
@ -159,18 +160,18 @@ public class RecordData {
throw new IllegalArgumentException("Array of values not supported yet"); //TODO: handle arrays
Object o = objects[0];
goToPosition();
if (STRING.equals(getBaseType())) {
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;
}
getBaseType().encode(valueHolder, o, scale, offset);
fieldDefinition.encode(valueHolder, o);
}
public Object decode() {
goToPosition();
if (STRING.equals(getBaseType())) {
if (STRING.equals(fieldDefinition.getBaseType())) {
final byte[] bytes = new byte[size];
valueHolder.get(bytes);
final int zero = ArrayUtils.indexOf((byte) 0, bytes);
@ -180,7 +181,8 @@ public class RecordData {
return new String(bytes, 0, zero, StandardCharsets.UTF_8);
}
//TODO: handle arrays
return getBaseType().decode(valueHolder, scale, offset);
return fieldDefinition.decode(valueHolder);
}
}
}

View File

@ -0,0 +1,26 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminTimeUtils.GARMIN_TIME_EPOCH;
public class FieldDefinitionTimestamp extends FieldDefinition {
public FieldDefinitionTimestamp(int localNumber, int size, BaseType baseType, String name) {
super(localNumber, size, baseType, name, 1, GARMIN_TIME_EPOCH);
}
// @Override
// public Object decode(ByteBuffer byteBuffer) {
// return new Timestamp((long) baseType.decode(byteBuffer, scale, offset) * 1000L);
// }
//
// @Override
// public void encode(ByteBuffer byteBuffer, Object o) {
// if(o instanceof Timestamp) {
// baseType.encode(byteBuffer, (int) (((Timestamp) o).getTime() / 1000L), scale, offset);
// return;
// }
// baseType.encode(byteBuffer, o, scale, offset);
// }
}