1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-09 07:46:37 +02:00

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 4313fc833a
commit e323e7fbde
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()); RecordData today = new RecordData(GlobalDefinitionsEnum.TODAY_WEATHER_CONDITIONS.getRecordDefinition());
today.setFieldByName("weather_report", 0); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast today.setFieldByName("weather_report", 0); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast
today.setFieldByName("timestamp", GarminTimeUtils.unixTimeToGarminTimestamp(weather.timestamp)); today.setFieldByName("timestamp", weather.timestamp);
today.setFieldByName("observed_at_time", GarminTimeUtils.unixTimeToGarminTimestamp(weather.timestamp)); today.setFieldByName("observed_at_time", weather.timestamp);
today.setFieldByName("temperature", weather.currentTemp - 273.15); today.setFieldByName("temperature", weather.currentTemp - 273.15);
today.setFieldByName("low_temperature", weather.todayMinTemp - 273.15); today.setFieldByName("low_temperature", weather.todayMinTemp - 273.15);
today.setFieldByName("high_temperature", weather.todayMaxTemp - 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); WeatherSpec.Hourly hourly = weather.hourly.get(hour);
RecordData weatherHourlyForecast = new RecordData(GlobalDefinitionsEnum.HOURLY_WEATHER_FORECAST.getRecordDefinition()); RecordData weatherHourlyForecast = new RecordData(GlobalDefinitionsEnum.HOURLY_WEATHER_FORECAST.getRecordDefinition());
weatherHourlyForecast.setFieldByName("weather_report", 1); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast 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("temperature", hourly.temp - 273.15);
weatherHourlyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(hourly.conditionCode)); weatherHourlyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(hourly.conditionCode));
weatherHourlyForecast.setFieldByName("wind_direction", hourly.windDirection); 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()); RecordData todayDailyForecast = new RecordData(GlobalDefinitionsEnum.DAILY_WEATHER_FORECAST.getRecordDefinition());
todayDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast 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("low_temperature", weather.todayMinTemp - 273.15);
todayDailyForecast.setFieldByName("high_temperature", weather.todayMaxTemp - 273.15); todayDailyForecast.setFieldByName("high_temperature", weather.todayMaxTemp - 273.15);
todayDailyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(weather.currentConditionCode)); 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? int ts = weather.timestamp + (day + 1) * 24 * 60 * 60; //TODO: is this needed?
RecordData weatherDailyForecast = new RecordData(GlobalDefinitionsEnum.DAILY_WEATHER_FORECAST.getRecordDefinition()); RecordData weatherDailyForecast = new RecordData(GlobalDefinitionsEnum.DAILY_WEATHER_FORECAST.getRecordDefinition());
weatherDailyForecast.setFieldByName("weather_report", 2); // 0 = current, 1 = hourly_forecast, 2 = daily_forecast 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("low_temperature", daily.minTemp - 273.15);
weatherDailyForecast.setFieldByName("high_temperature", daily.maxTemp - 273.15); weatherDailyForecast.setFieldByName("high_temperature", daily.maxTemp - 273.15);
weatherDailyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(daily.conditionCode)); weatherDailyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(daily.conditionCode));

View File

@ -5,7 +5,7 @@ import org.threeten.bp.ZoneId;
public class GarminTimeUtils { 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) { public static int unixTimeToGarminTimestamp(int unixTime) {
return unixTime - GARMIN_TIME_EPOCH; return unixTime - GARMIN_TIME_EPOCH;

View File

@ -1,16 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit; 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.fit.baseTypes.BaseType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageReader;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
public class FieldDefinition { public class FieldDefinition implements FieldInterface {
private final int localNumber; private final int localNumber;
private final int size; private final int size;
private final BaseType baseType; protected final BaseType baseType;
private final String name; private final String name;
private final int scale; protected final int scale;
private final int offset; protected final int offset;
public FieldDefinition(int localNumber, int size, BaseType baseType, String name, int scale, int offset) { public FieldDefinition(int localNumber, int size, BaseType baseType, String name, int scale, int offset) {
this.localNumber = localNumber; this.localNumber = localNumber;
@ -70,4 +72,18 @@ public class FieldDefinition {
writer.writeByte(baseType.getIdentifier()); 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 java.util.Arrays;
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;
public enum GlobalDefinitionsEnum { public enum GlobalDefinitionsEnum {
TODAY_WEATHER_CONDITIONS(MesgType.TODAY_WEATHER_CONDITIONS, new RecordDefinition( TODAY_WEATHER_CONDITIONS(MesgType.TODAY_WEATHER_CONDITIONS, new RecordDefinition(
@ -13,8 +14,8 @@ public enum GlobalDefinitionsEnum {
ByteOrder.BIG_ENDIAN, ByteOrder.BIG_ENDIAN,
MesgType.TODAY_WEATHER_CONDITIONS, MesgType.TODAY_WEATHER_CONDITIONS,
Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"), 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(9, 4, BaseType.UINT32, "observed_at_time"), new FieldDefinitionTimestamp(9, 4, BaseType.UINT32, "observed_at_time"),
new FieldDefinition(1, 1, BaseType.SINT8, "temperature"), new FieldDefinition(1, 1, BaseType.SINT8, "temperature"),
new FieldDefinition(14, 1, BaseType.SINT8, "low_temperature"), new FieldDefinition(14, 1, BaseType.SINT8, "low_temperature"),
new FieldDefinition(13, 1, BaseType.SINT8, "high_temperature"), new FieldDefinition(13, 1, BaseType.SINT8, "high_temperature"),
@ -33,7 +34,7 @@ public enum GlobalDefinitionsEnum {
ByteOrder.BIG_ENDIAN, ByteOrder.BIG_ENDIAN,
MesgType.HOURLY_WEATHER_FORECAST, MesgType.HOURLY_WEATHER_FORECAST,
Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"), 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(1, 1, BaseType.SINT8, "temperature"),
new FieldDefinition(2, 1, BaseType.ENUM, "condition"), new FieldDefinition(2, 1, BaseType.ENUM, "condition"),
new FieldDefinition(3, 2, BaseType.UINT16, "wind_direction"), new FieldDefinition(3, 2, BaseType.UINT16, "wind_direction"),
@ -49,7 +50,7 @@ public enum GlobalDefinitionsEnum {
ByteOrder.BIG_ENDIAN, ByteOrder.BIG_ENDIAN,
MesgType.DAILY_WEATHER_FORECAST, MesgType.DAILY_WEATHER_FORECAST,
Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"), 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(14, 1, BaseType.SINT8, "low_temperature"),
new FieldDefinition(13, 1, BaseType.SINT8, "high_temperature"), new FieldDefinition(13, 1, BaseType.SINT8, "high_temperature"),
new FieldDefinition(2, 1, BaseType.ENUM, "condition"), new FieldDefinition(2, 1, BaseType.ENUM, "condition"),

View File

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