diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java index 7150540b6..fb362463f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java @@ -29,7 +29,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateA import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.ICommunicator; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v1.CommunicatorV1; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v2.CommunicatorV2; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitWeatherConditions; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalDefinitionsEnum; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ConfigurationMessage; @@ -144,19 +143,18 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni List weatherData = new ArrayList<>(); try { - 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", 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); - today.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(weather.currentConditionCode)); + today.setFieldByName("temperature", weather.currentTemp); + today.setFieldByName("low_temperature", weather.todayMinTemp); + today.setFieldByName("high_temperature", weather.todayMaxTemp); + today.setFieldByName("condition", weather.currentConditionCode); today.setFieldByName("wind_direction", weather.windDirection); today.setFieldByName("precipitation_probability", weather.precipProbability); today.setFieldByName("wind_speed", Math.round(weather.windSpeed)); - today.setFieldByName("temperature_feels_like", weather.feelsLikeTemp - 273.15); + today.setFieldByName("temperature_feels_like", weather.feelsLikeTemp); today.setFieldByName("relative_humidity", weather.currentHumidity); today.setFieldByName("observed_location_lat", weather.latitude); today.setFieldByName("observed_location_long", weather.longitude); @@ -169,8 +167,8 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni 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", hourly.timestamp); - weatherHourlyForecast.setFieldByName("temperature", hourly.temp - 273.15); - weatherHourlyForecast.setFieldByName("condition", FitWeatherConditions.openWeatherCodeToFitWeatherStatus(hourly.conditionCode)); + weatherHourlyForecast.setFieldByName("temperature", hourly.temp); + weatherHourlyForecast.setFieldByName("condition", hourly.conditionCode); weatherHourlyForecast.setFieldByName("wind_direction", hourly.windDirection); weatherHourlyForecast.setFieldByName("wind_speed", Math.round(hourly.windSpeed)); weatherHourlyForecast.setFieldByName("precipitation_probability", hourly.precipProbability); @@ -185,9 +183,9 @@ 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", 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)); + todayDailyForecast.setFieldByName("low_temperature", weather.todayMinTemp); + todayDailyForecast.setFieldByName("high_temperature", weather.todayMaxTemp); + todayDailyForecast.setFieldByName("condition", weather.currentConditionCode); todayDailyForecast.setFieldByName("precipitation_probability", weather.precipProbability); todayDailyForecast.setFieldByName("day_of_week", weather.timestamp); weatherData.add(todayDailyForecast); @@ -200,9 +198,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni 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", 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)); + weatherDailyForecast.setFieldByName("low_temperature", daily.minTemp); + weatherDailyForecast.setFieldByName("high_temperature", daily.maxTemp); + weatherDailyForecast.setFieldByName("condition", daily.conditionCode); weatherDailyForecast.setFieldByName("precipitation_probability", daily.precipProbability); weatherDailyForecast.setFieldByName("day_of_week", ts); weatherData.add(weatherDailyForecast); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalDefinitionsEnum.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalDefinitionsEnum.java index de490266f..550a9ed0a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalDefinitionsEnum.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalDefinitionsEnum.java @@ -6,7 +6,10 @@ 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.FieldDefinitionDayOfWeek; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTemperature; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition; public enum GlobalDefinitionsEnum { TODAY_WEATHER_CONDITIONS(MesgType.TODAY_WEATHER_CONDITIONS, new RecordDefinition( @@ -16,14 +19,14 @@ public enum GlobalDefinitionsEnum { Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"), 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"), - new FieldDefinition(2, 1, BaseType.ENUM, "condition"), + new FieldDefinitionTemperature(1, 1, BaseType.SINT8, "temperature"), + new FieldDefinitionTemperature(14, 1, BaseType.SINT8, "low_temperature"), + new FieldDefinitionTemperature(13, 1, BaseType.SINT8, "high_temperature"), + new FieldDefinitionWeatherCondition(2, 1, BaseType.ENUM, "condition"), new FieldDefinition(3, 2, BaseType.UINT16, "wind_direction"), new FieldDefinition(5, 1, BaseType.UINT8, "precipitation_probability"), new FieldDefinition(4, 2, BaseType.UINT16, "wind_speed", 298, 0), - new FieldDefinition(6, 1, BaseType.SINT8, "temperature_feels_like"), + new FieldDefinitionTemperature(6, 1, BaseType.SINT8, "temperature_feels_like"), new FieldDefinition(7, 1, BaseType.UINT8, "relative_humidity"), new FieldDefinition(10, 4, BaseType.SINT32, "observed_location_lat"), new FieldDefinition(11, 4, BaseType.SINT32, "observed_location_long"), @@ -35,8 +38,8 @@ public enum GlobalDefinitionsEnum { MesgType.HOURLY_WEATHER_FORECAST, Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"), new FieldDefinitionTimestamp(253, 4, BaseType.UINT32, "timestamp"), - new FieldDefinition(1, 1, BaseType.SINT8, "temperature"), - new FieldDefinition(2, 1, BaseType.ENUM, "condition"), + new FieldDefinitionTemperature(1, 1, BaseType.SINT8, "temperature"), + new FieldDefinitionWeatherCondition(2, 1, BaseType.ENUM, "condition"), new FieldDefinition(3, 2, BaseType.UINT16, "wind_direction"), new FieldDefinition(4, 2, BaseType.UINT16, "wind_speed", 298, 0), new FieldDefinition(5, 1, BaseType.UINT8, "precipitation_probability"), @@ -51,11 +54,11 @@ public enum GlobalDefinitionsEnum { MesgType.DAILY_WEATHER_FORECAST, Arrays.asList(new FieldDefinition(0, 1, BaseType.ENUM, "weather_report"), 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"), + new FieldDefinitionTemperature(14, 1, BaseType.SINT8, "low_temperature"), + new FieldDefinitionTemperature(13, 1, BaseType.SINT8, "high_temperature"), + new FieldDefinitionWeatherCondition(2, 1, BaseType.ENUM, "condition"), new FieldDefinition(5, 1, BaseType.UINT8, "precipitation_probability"), - new FieldDefinition(12, 1, BaseType.ENUM, "day_of_week")))), + new FieldDefinitionDayOfWeek(12, 1, BaseType.ENUM, "day_of_week")))), ; private final MesgType mesgType; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java index 68d92b650..81af3c14d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java @@ -86,6 +86,36 @@ public class RecordData { } } + 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 String toString() { StringBuilder oBuilder = new StringBuilder(); for (FieldData fieldData : @@ -102,20 +132,7 @@ public class RecordData { return oBuilder.toString(); } - - - public FieldData getFieldByNumber(int number) { - for (FieldData fieldData : - fieldDataList) { - if (number == fieldData.getNumber()) { - return fieldData; - } - } - return null; - } - - - public class FieldData { + private class FieldData { private FieldDefinition fieldDefinition; private final int position; private final int size; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java index 018b4b6dd..7877c07e3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseType.java @@ -10,7 +10,7 @@ public enum BaseType { SINT16(0x83, new BaseTypeShort(false, 0x7FFF)), UINT16(0x84, new BaseTypeShort(true, 0xFFFF)), SINT32(0x85, new BaseTypeInt(false, 0x7FFFFFFF)), - UINT32(0x86, new BaseTypeInt(true, 0xFFFFFFFF)), + UINT32(0x86, new BaseTypeInt(true, 0xFFFFFFFFL)), STRING(0x07, new BaseTypeByte(true, 0x00)), FLOAT32(0x88, new BaseTypeFloat()), FLOAT64(0x89, new BaseTypeDouble()), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java index 3419a8801..4ada1b3ce 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeByte.java @@ -29,17 +29,23 @@ public class BaseTypeByte implements BaseTypeInterface { @Override public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { - int i = (byteBuffer.get() + offset) / scale; - if (i < min || i > max) - return invalid; - return i; + int b = unsigned ? Byte.toUnsignedInt(byteBuffer.get()) : byteBuffer.get(); + if (b < min || b > max) + return null; + if (b == invalid) + return null; + return (b + offset) / scale; } @Override public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { + if (null == o) { + invalidate(byteBuffer); + return; + } int i = ((Number) o).intValue() * scale - offset; - if (!unsigned && (i < min || i > max)) { - byteBuffer.put((byte) invalid); + if (i < min || i > max) { + invalidate(byteBuffer); return; } byteBuffer.put((byte) i); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java index f2abc6a66..e517c8410 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeDouble.java @@ -9,7 +9,7 @@ public class BaseTypeDouble implements BaseTypeInterface { private final double invalid; BaseTypeDouble() { - this.min = Double.MIN_VALUE; + this.min = -Double.MAX_VALUE; this.max = Double.MAX_VALUE; this.invalid = Double.longBitsToDouble(0xFFFFFFFFFFFFFFFFL); } @@ -20,14 +20,24 @@ public class BaseTypeDouble implements BaseTypeInterface { @Override public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { - return (byteBuffer.getDouble() + offset) / scale; + double d = byteBuffer.getDouble(); + if (d < min || d > max) { + return null; + } + if (Double.isNaN(d) || d == invalid) + return null; + return (d + offset) / scale; } @Override public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { + if (null == o) { + invalidate(byteBuffer); + return; + } double d = ((Number) o).doubleValue() * scale - offset; if (d < min || d > max) { - byteBuffer.putDouble(invalid); + invalidate(byteBuffer); return; } byteBuffer.putDouble(d); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java index da9b0b1d9..41f713cd9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeFloat.java @@ -7,13 +7,11 @@ public class BaseTypeFloat implements BaseTypeInterface { private final double min; private final double max; private final double invalid; - private final boolean unsigned; BaseTypeFloat() { this.min = -Float.MAX_VALUE; this.max = Float.MAX_VALUE; this.invalid = Float.intBitsToFloat(0xFFFFFFFF); - this.unsigned = false; } public int getByteSize() { @@ -22,14 +20,24 @@ public class BaseTypeFloat implements BaseTypeInterface { @Override public Object decode(ByteBuffer byteBuffer, int scale, int offset) { - return (byteBuffer.getFloat() + offset) / scale; + float f = byteBuffer.getFloat(); + if (f < min || f > max) { + return null; + } + if (Float.isNaN(f) || f == invalid) + return null; + return (f + offset) / scale; } @Override public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { + if (null == o) { + invalidate(byteBuffer); + return; + } float f = ((Number) o).floatValue() * scale - offset; - if (!unsigned && (f < min || f > max)) { - byteBuffer.putFloat((float) invalid); + if (f < min || f > max) { + invalidate(byteBuffer); return; } byteBuffer.putFloat((float) f); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java index 0241c7d7f..a0615dab3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeInt.java @@ -3,16 +3,16 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseType import java.nio.ByteBuffer; public class BaseTypeInt implements BaseTypeInterface { - private final int min; - private final int max; - private final int invalid; + private final long min; + private final long max; + private final long invalid; private final boolean unsigned; private final int size = 4; - BaseTypeInt(boolean unsigned, int invalid) { + BaseTypeInt(boolean unsigned, long invalid) { if (unsigned) { this.min = 0; - this.max = 0xffffffff; + this.max = 0xffffffffL; } else { this.min = Integer.MIN_VALUE; this.max = Integer.MAX_VALUE; @@ -27,23 +27,23 @@ public class BaseTypeInt implements BaseTypeInterface { @Override public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { - if (unsigned) { - long i = ((byteBuffer.getInt() & 0xffffffffL) + offset) / scale; - return i; - } else { - int i = (byteBuffer.getInt() + offset) / scale; - if (i < min || i > max) - return invalid; - return i; - } - + long i = unsigned ? Integer.toUnsignedLong(byteBuffer.getInt()) : byteBuffer.getInt(); + if (i < min || i > max) + return null; + if (i == invalid) + return null; + return (int) ((i + offset) / scale); } @Override public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { + if (null == o) { + invalidate(byteBuffer); + return; + } long l = ((Number) o).longValue() * scale - offset; - if (!unsigned && (l < min || l > max)) { - byteBuffer.putInt((int) invalid); + if (l < min || l > max) { + invalidate(byteBuffer); return; } byteBuffer.putInt((int) l); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java index 1f3e54a6a..74af9461a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeLong.java @@ -1,21 +1,22 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes; +import java.math.BigInteger; import java.nio.ByteBuffer; public class BaseTypeLong implements BaseTypeInterface { private final int size = 8; - private final double min; - private final double max; - private final double invalid; + private final BigInteger min; + private final BigInteger max; + private final long invalid; private final boolean unsigned; BaseTypeLong(boolean unsigned, long invalid) { if (unsigned) { - this.min = 0; - this.max = 0xFFFFFFFFFFFFFFFFL; + this.min = BigInteger.valueOf(0); + this.max = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL); } else { - this.min = Long.MIN_VALUE; - this.max = Long.MAX_VALUE; + this.min = BigInteger.valueOf(Long.MIN_VALUE); + this.max = BigInteger.valueOf(Long.MAX_VALUE); } this.invalid = invalid; this.unsigned = unsigned; @@ -27,24 +28,26 @@ public class BaseTypeLong implements BaseTypeInterface { @Override public Object decode(ByteBuffer byteBuffer, int scale, int offset) { - if (unsigned) { - return ((byteBuffer.getLong() & 0xFFFFFFFFFFFFFFFFL + offset) / scale); - } else { - long l = (byteBuffer.getLong() + offset) / scale; - if (l < min || l > max) - return invalid; - return l; - } + BigInteger i = unsigned ? BigInteger.valueOf(byteBuffer.getLong() & 0xFFFFFFFFFFFFFFFFL) : BigInteger.valueOf(byteBuffer.getLong()); + if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0)) + return null; + if (i.compareTo(BigInteger.valueOf(invalid)) == 0) + return null; + return (i.longValue() + offset) / scale; } @Override public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { - long l = ((Number) o).longValue() * scale - offset; - if (!unsigned && (l < min || l > max)) { - byteBuffer.putLong((long) invalid); + if (null == o) { + invalidate(byteBuffer); return; } - byteBuffer.putLong(l); + BigInteger i = BigInteger.valueOf(((Number) o).longValue() * scale - offset); + if (!unsigned && (i.compareTo(min) < 0 || i.compareTo(max) > 0)) { + invalidate(byteBuffer); + return; + } + byteBuffer.putLong(i.longValue()); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java index 1bbac467f..ade9d357d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/baseTypes/BaseTypeShort.java @@ -27,23 +27,23 @@ public class BaseTypeShort implements BaseTypeInterface { @Override public Object decode(final ByteBuffer byteBuffer, int scale, int offset) { - if (unsigned) { - int s = (((byteBuffer.getShort() & 0xffff) + offset) / scale); - return s; - } else { - short s = (short) ((byteBuffer.getShort() + offset) / scale); - if (s < min || s > max) - return invalid; - return s; - } - + int s = unsigned ? Short.toUnsignedInt(byteBuffer.getShort()) : byteBuffer.getShort(); + if (s < min || s > max) + return null; + if (s == invalid) + return null; + return (s + offset) / scale; } @Override public void encode(ByteBuffer byteBuffer, Object o, int scale, int offset) { + if (null == o) { + invalidate(byteBuffer); + return; + } int i = ((Number) o).intValue() * scale - offset; - if (!unsigned && (i < min || i > max)) { - byteBuffer.putShort((short) invalid); + if (i < min || i > max) { + invalidate(byteBuffer); return; } byteBuffer.putShort((short) i); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java new file mode 100644 index 000000000..e215025e2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionTemperature.java @@ -0,0 +1,12 @@ +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; + +public class FieldDefinitionTemperature extends FieldDefinition { + + public FieldDefinitionTemperature(int localNumber, int size, BaseType baseType, String name) { + super(localNumber, size, baseType, name, 1, 273); + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitWeatherConditions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java similarity index 59% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitWeatherConditions.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java index 403bbcf52..eb3b943e0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitWeatherConditions.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherCondition.java @@ -1,29 +1,32 @@ -package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit; +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions; -public final class FitWeatherConditions { - public static final int CLEAR = 0; - public static final int PARTLY_CLOUDY = 1; - public static final int MOSTLY_CLOUDY = 2; - public static final int RAIN = 3; - public static final int SNOW = 4; - public static final int WINDY = 5; - public static final int THUNDERSTORMS = 6; - public static final int WINTRY_MIX = 7; - public static final int FOG = 8; - public static final int HAZY = 11; - public static final int HAIL = 12; - public static final int SCATTERED_SHOWERS = 13; - public static final int SCATTERED_THUNDERSTORMS = 14; - public static final int UNKNOWN_PRECIPITATION = 15; - public static final int LIGHT_RAIN = 16; - public static final int HEAVY_RAIN = 17; - public static final int LIGHT_SNOW = 18; - public static final int HEAVY_SNOW = 19; - public static final int LIGHT_RAIN_SNOW = 20; - public static final int HEAVY_RAIN_SNOW = 21; - public static final int CLOUDY = 22; +import java.nio.ByteBuffer; - public static int openWeatherCodeToFitWeatherStatus(int openWeatherCode) { +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType; + +public class FieldDefinitionWeatherCondition extends FieldDefinition { + + public FieldDefinitionWeatherCondition(int localNumber, int size, BaseType baseType, String name) { + super(localNumber, size, baseType, name, 1, 0); + } + + @Override + public Object decode(ByteBuffer byteBuffer) { + int idx = (int) baseType.decode(byteBuffer, scale, offset); + return Condition.values()[idx]; + } + + @Override + public void encode(ByteBuffer byteBuffer, Object o) { + if (o instanceof Condition) { + baseType.encode(byteBuffer, ((Condition) o).ordinal(), scale, offset); + return; + } + baseType.encode(byteBuffer, openWeatherCodeToFitWeatherStatus((int) o), scale, offset); + } + + private int openWeatherCodeToFitWeatherStatus(int openWeatherCode) { switch (openWeatherCode) { //Group 2xx: Thunderstorm case 200: //thunderstorm with light rain: //11d @@ -35,56 +38,56 @@ public final class FitWeatherConditions { case 230: //thunderstorm with light drizzle: //11d case 231: //thunderstorm with drizzle: //11d case 232: //thunderstorm with heavy drizzle: //11d - return THUNDERSTORMS; + return Condition.THUNDERSTORMS.ordinal(); case 221: //ragged thunderstorm: //11d - return SCATTERED_THUNDERSTORMS; + return Condition.SCATTERED_THUNDERSTORMS.ordinal(); //Group 3xx: Drizzle case 300: //light intensity drizzle: //09d case 310: //light intensity drizzle rain: //09d case 313: //shower rain and drizzle: //09d - return LIGHT_RAIN; + return Condition.LIGHT_RAIN.ordinal(); case 301: //drizzle: //09d case 311: //drizzle rain: //09d - return RAIN; + return Condition.RAIN.ordinal(); case 302: //heavy intensity drizzle: //09d case 312: //heavy intensity drizzle rain: //09d case 314: //heavy shower rain and drizzle: //09d - return HEAVY_RAIN; + return Condition.HEAVY_RAIN.ordinal(); case 321: //shower drizzle: //09d - return SCATTERED_SHOWERS; + return Condition.SCATTERED_SHOWERS.ordinal(); //Group 5xx: Rain case 500: //light rain: //10d case 520: //light intensity shower rain: //09d case 521: //shower rain: //09d - return LIGHT_RAIN; + return Condition.LIGHT_RAIN.ordinal(); case 501: //moderate rain: //10d case 531: //ragged shower rain: //09d - return RAIN; + return Condition.RAIN.ordinal(); case 502: //heavy intensity rain: //10d case 503: //very heavy rain: //10d case 504: //extreme rain: //10d case 522: //heavy intensity shower rain: //09d - return HEAVY_RAIN; + return Condition.HEAVY_RAIN.ordinal(); case 511: //freezing rain: //13d - return UNKNOWN_PRECIPITATION; + return Condition.UNKNOWN_PRECIPITATION.ordinal(); //Group 6xx: Snow case 600: //light snow: //[[file:13d.png]] - return LIGHT_SNOW; + return Condition.LIGHT_SNOW.ordinal(); case 601: //snow: //[[file:13d.png]] case 620: //light shower snow: //[[file:13d.png]] case 621: //shower snow: //[[file:13d.png]] - return SNOW; + return Condition.SNOW.ordinal(); case 602: //heavy snow: //[[file:13d.png]] case 622: //heavy shower snow: //[[file:13d.png]] - return HEAVY_SNOW; + return Condition.HEAVY_SNOW.ordinal(); case 611: //sleet: //[[file:13d.png]] case 612: //light shower sleet: //[[file:13d.png]] case 613: //shower sleet: //[[file:13d.png]] - return WINTRY_MIX; + return Condition.WINTRY_MIX.ordinal(); case 615: //light rain and snow: //[[file:13d.png]] - return LIGHT_RAIN_SNOW; + return Condition.LIGHT_RAIN_SNOW.ordinal(); case 616: //rain and snow: //[[file:13d.png]] - return HEAVY_RAIN_SNOW; + return Condition.HEAVY_RAIN_SNOW.ordinal(); //Group 7xx: Atmosphere case 701: //mist: //[[file:50d.png]] @@ -94,29 +97,29 @@ public final class FitWeatherConditions { case 751: //sand: //[[file:50d.png]] case 761: //dust: //[[file:50d.png]] case 762: //volcanic ash: //[[file:50d.png]] - return HAZY; + return Condition.HAZY.ordinal(); case 741: //fog: //[[file:50d.png]] - return FOG; + return Condition.FOG.ordinal(); case 771: //squalls: //[[file:50d.png]] case 781: //tornado: //[[file:50d.png]] - return WINDY; + return Condition.WINDY.ordinal(); //Group 800: Clear case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]] - return CLEAR; + return Condition.CLEAR.ordinal(); //Group 80x: Clouds case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]] case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]] - return PARTLY_CLOUDY; + return Condition.PARTLY_CLOUDY.ordinal(); case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]] - return MOSTLY_CLOUDY; + return Condition.MOSTLY_CLOUDY.ordinal(); case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]] - return CLOUDY; + return Condition.CLOUDY.ordinal(); //Group 90x: Extreme case 901: //tropical storm - return THUNDERSTORMS; + return Condition.THUNDERSTORMS.ordinal(); case 906: //hail - return HAIL; + return Condition.HAIL.ordinal(); case 903: //cold case 904: //hot case 905: //windy @@ -138,4 +141,31 @@ public final class FitWeatherConditions { throw new IllegalArgumentException("Unknown weather code " + openWeatherCode); } } + + enum Condition { + CLEAR, + PARTLY_CLOUDY, + MOSTLY_CLOUDY, + RAIN, + SNOW, + WINDY, + THUNDERSTORMS, + WINTRY_MIX, + FOG, + UNK9, + UNK10, + HAZY, + HAIL, + SCATTERED_SHOWERS, + SCATTERED_THUNDERSTORMS, + UNKNOWN_PRECIPITATION, + LIGHT_RAIN, + HEAVY_RAIN, + LIGHT_SNOW, + HEAVY_SNOW, + LIGHT_RAIN_SNOW, + HEAVY_RAIN_SNOW, + CLOUDY, + ; + } } diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java index 6983b3792..a65f316bb 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupportTest.java @@ -2,8 +2,21 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin; import org.junit.Assert; import org.junit.Test; +import org.threeten.bp.Instant; +import org.threeten.bp.ZoneId; + +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; 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.MesgType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -97,4 +110,258 @@ public class GarminSupportTest { } } + @Test + public void testBaseFields() { + + RecordDefinition recordDefinition = new RecordDefinition(new RecordHeader((byte) 6), ByteOrder.LITTLE_ENDIAN, MesgType.TODAY_WEATHER_CONDITIONS, 123); //just some random data + List fieldDefinitionList = new ArrayList<>(); + for (BaseType baseType : + BaseType.values()) { + fieldDefinitionList.add(new FieldDefinition(baseType.getIdentifier(), baseType.getSize(), baseType, baseType.name())); + + } + recordDefinition.setFieldDefinitions(fieldDefinitionList); + + RecordData test = new RecordData(recordDefinition); + + for (BaseType baseType : + BaseType.values()) { + System.out.println(baseType.getIdentifier()); + Object startVal, endVal; + + switch (baseType.name()) { + case "ENUM": + case "UINT8": + case "BASE_TYPE_BYTE": + startVal = 0; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) 0xff - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = -1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "SINT8": + startVal = (int) Byte.MIN_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) Byte.MAX_VALUE - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) Byte.MIN_VALUE - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "SINT16": + startVal = (int) Short.MIN_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) Short.MAX_VALUE - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) Short.MIN_VALUE - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT16": + startVal = 0; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) 0xffff - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = -1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "SINT32": + startVal = (int) Integer.MIN_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) Integer.MAX_VALUE - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) Integer.MIN_VALUE - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT32": + startVal = 0; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (long) 0xffffffffL - 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, (long) ((int) endVal & 0xffffffffL)); + startVal = 0xffffffff; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "FLOAT32": + startVal = 0.0f; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = -Float.MAX_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = Float.MAX_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (double) -Float.MAX_VALUE * 2; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "FLOAT64": + startVal = 0.0d; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = Double.MIN_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = Double.MAX_VALUE; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (double) -Double.MAX_VALUE * 2; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT8Z": + startVal = 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) 0xff; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = 0; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + startVal = -1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT16Z": + startVal = 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (int) 0xffff; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = -1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + startVal = 0; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT32Z": + startVal = 1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = (long) 0xffffffffL; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, (long) ((int) endVal & 0xffffffffL)); + startVal = -1; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + startVal = 0; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "SINT64": + startVal = BigInteger.valueOf(Long.MIN_VALUE); + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(((BigInteger) startVal).longValue(), endVal); + startVal = BigInteger.valueOf(Long.MAX_VALUE - 1); + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(((BigInteger) startVal).longValue(), endVal); + startVal = BigInteger.valueOf(Long.MAX_VALUE); + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT64": + startVal = 0L; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL - 1); + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(((BigInteger) startVal).longValue() & 0xFFFFFFFFFFFFFFFFL, endVal); + startVal = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL); + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "UINT64Z": + startVal = 1L; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(startVal, endVal); + startVal = BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL); + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertEquals(((BigInteger) startVal).longValue() & 0xFFFFFFFFFFFFFFFFL, endVal); + startVal = 0L; + test.setFieldByName(baseType.name(), startVal); + endVal = test.getFieldByName(baseType.name()); + Assert.assertNull(endVal); + break; + case "STRING": + //TODO + break; + default: + System.out.println(baseType.name()); + Assert.assertFalse(true); //we should not end up here, if it happen we forgot a case in the switch + } + + } + + } + + @Test + public void runningTest() { + System.out.println(Instant.ofEpochSecond(System.currentTimeMillis()).atZone(ZoneId.systemDefault()).getDayOfWeek().getValue()); + } }