Garmin protocol: refactoring and fixes of BaseTypes

The boundaries are enforced on the stored value when decoding, before applying the adjustments for scale and offset.
Also add some tests for the BaseTypes
Introduce new FieldDefinition for Temperature and WeatherCondition (removing the static class)
Add accessors for field data in the containing RecordData, thus keeping the FieldData private
This commit is contained in:
Daniele Gobbetti 2024-03-26 07:07:27 +01:00 committed by José Rebelo
parent 1db2cc0354
commit 520a510dd9
13 changed files with 506 additions and 152 deletions

View File

@ -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<RecordData> 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);

View File

@ -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;

View File

@ -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;

View File

@ -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()),

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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,
;
}
}

View File

@ -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<FieldDefinition> 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());
}
}