From c44258310e4e381d81244b93fcc0c055e612522a Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Tue, 23 Apr 2024 16:39:59 +0200 Subject: [PATCH] Garmin: Rename LocalMessage to PredefinedLocalMessage and clarify its usage PredefinedLocalMessage are only useful for FIT messages and should not interfere with FIT files. The only impact of using the local message in fit files was in the textual output, but it was confusing. Add an explicit constructor to RecordHeader if PredefinedLocalMessage should be taken into account, and use this only in fit messages leaving the default constructor for fit files. Also adjusts the test case as textual output comparison needs to be fixed. --- .../service/devices/garmin/GarminSupport.java | 16 ++++----- ...ssage.java => PredefinedLocalMessage.java} | 12 +++---- .../devices/garmin/fit/RecordData.java | 4 +-- .../devices/garmin/fit/RecordDefinition.java | 16 ++++----- .../devices/garmin/fit/RecordHeader.java | 36 +++++++++++-------- .../garmin/messages/FitDataMessage.java | 10 +++--- .../garmin/messages/FitDefinitionMessage.java | 2 +- .../devices/garmin/GarminSupportTest.java | 6 ++-- 8 files changed, 53 insertions(+), 49 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/{LocalMessage.java => PredefinedLocalMessage.java} (75%) 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 a648f943c..5856ff325 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 @@ -45,7 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents. import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.LocalMessage; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage; 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.messages.ConfigurationMessage; @@ -276,14 +276,14 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni List weatherData = new ArrayList<>(); List weatherDefinitions = new ArrayList<>(3); - weatherDefinitions.add(LocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition()); - weatherDefinitions.add(LocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition()); - weatherDefinitions.add(LocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition()); + weatherDefinitions.add(PredefinedLocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition()); + weatherDefinitions.add(PredefinedLocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition()); + weatherDefinitions.add(PredefinedLocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition()); sendOutgoingMessage(new nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDefinitionMessage(weatherDefinitions)); try { - RecordData today = new RecordData(LocalMessage.TODAY_WEATHER_CONDITIONS.getRecordDefinition()); + RecordData today = new RecordData(PredefinedLocalMessage.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); @@ -304,7 +304,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni for (int hour = 0; hour <= 11; hour++) { if (hour < weather.hourly.size()) { WeatherSpec.Hourly hourly = weather.hourly.get(hour); - RecordData weatherHourlyForecast = new RecordData(LocalMessage.HOURLY_WEATHER_FORECAST.getRecordDefinition()); + RecordData weatherHourlyForecast = new RecordData(PredefinedLocalMessage.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); @@ -320,7 +320,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni } } // - RecordData todayDailyForecast = new RecordData(LocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition()); + RecordData todayDailyForecast = new RecordData(PredefinedLocalMessage.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); @@ -335,7 +335,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni if (day < weather.forecasts.size()) { WeatherSpec.Daily daily = weather.forecasts.get(day); int ts = weather.timestamp + (day + 1) * 24 * 60 * 60; //TODO: is this needed? - RecordData weatherDailyForecast = new RecordData(LocalMessage.DAILY_WEATHER_FORECAST.getRecordDefinition()); + RecordData weatherDailyForecast = new RecordData(PredefinedLocalMessage.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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/LocalMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java similarity index 75% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/LocalMessage.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java index 7948a563f..6c3c427d3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/LocalMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java @@ -5,7 +5,7 @@ import androidx.annotation.Nullable; import java.nio.ByteOrder; import java.util.List; -public enum LocalMessage { +public enum PredefinedLocalMessage { TODAY_WEATHER_CONDITIONS(6, GlobalFITMessage.WEATHER, new int[]{0, 253, 9, 1, 14, 13, 2, 3, 5, 4, 6, 7, 10, 11, 8} ), @@ -20,17 +20,17 @@ public enum LocalMessage { private final GlobalFITMessage globalFITMessage; private final int[] globalDefinitionIds; - LocalMessage(int type, GlobalFITMessage globalFITMessage, int[] globalDefinitionIds) { + PredefinedLocalMessage(int type, GlobalFITMessage globalFITMessage, int[] globalDefinitionIds) { this.type = type; this.globalFITMessage = globalFITMessage; this.globalDefinitionIds = globalDefinitionIds; } @Nullable - public static LocalMessage fromType(int type) { - for (final LocalMessage localMessage : LocalMessage.values()) { - if (localMessage.getType() == type) { - return localMessage; + public static PredefinedLocalMessage fromType(int type) { + for (final PredefinedLocalMessage predefinedLocalMessage : PredefinedLocalMessage.values()) { + if (predefinedLocalMessage.getType() == type) { + return predefinedLocalMessage; } } return null; 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 f20785f64..5a40a0c7b 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 @@ -180,8 +180,8 @@ public class RecordData { return oBuilder.toString(); } - public LocalMessage getLocalMessage() { - return recordHeader.getLocalMessage(); + public PredefinedLocalMessage getPredefinedLocalMessage() { + return recordHeader.getPredefinedLocalMessage(); } private class FieldData { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java index 795ca8d23..8afb12cd3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordDefinition.java @@ -14,26 +14,22 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.Mess public class RecordDefinition { private final RecordHeader recordHeader; private final GlobalFITMessage globalFITMessage; - private final LocalMessage localMessage; + private final PredefinedLocalMessage predefinedLocalMessage; private final java.nio.ByteOrder byteOrder; private List fieldDefinitions; private List devFieldDefinitions; - public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, LocalMessage localMessage, GlobalFITMessage globalFITMessage, List fieldDefinitions, List devFieldDefinitions) { + public RecordDefinition(RecordHeader recordHeader, ByteOrder byteOrder, PredefinedLocalMessage predefinedLocalMessage, GlobalFITMessage globalFITMessage, List fieldDefinitions, List devFieldDefinitions) { this.recordHeader = recordHeader; this.byteOrder = byteOrder; - this.localMessage = localMessage; + this.predefinedLocalMessage = predefinedLocalMessage; this.globalFITMessage = globalFITMessage; this.fieldDefinitions = fieldDefinitions; this.devFieldDefinitions = devFieldDefinitions; } - public RecordDefinition(ByteOrder byteOrder, LocalMessage localMessage, List fieldDefinitions) { - this(new RecordHeader(true, false, localMessage, null), byteOrder, localMessage, localMessage.getGlobalFITMessage(), fieldDefinitions, null); - } - - public RecordDefinition(ByteOrder byteOrder, LocalMessage localMessage) { - this(new RecordHeader(true, false, localMessage, null), byteOrder, localMessage, localMessage.getGlobalFITMessage(), localMessage.getLocalFieldDefinitions(), null); + public RecordDefinition(ByteOrder byteOrder, PredefinedLocalMessage predefinedLocalMessage) { + this(new RecordHeader(true, false, predefinedLocalMessage, null), byteOrder, predefinedLocalMessage, predefinedLocalMessage.getGlobalFITMessage(), predefinedLocalMessage.getLocalFieldDefinitions(), null); } public RecordDefinition(ByteOrder byteOrder, RecordHeader recordHeader, GlobalFITMessage globalFITMessage, List fieldDefinitions) { @@ -118,7 +114,7 @@ public class RecordDefinition { } public String getName() { - return localMessage != null ? localMessage.name() : "unknown_" + globalFITMessage; + return predefinedLocalMessage != null ? predefinedLocalMessage.name() : "unknown_" + globalFITMessage; } @NonNull diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java index 56fab488f..e5a8b7c9d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordHeader.java @@ -7,21 +7,25 @@ import androidx.annotation.Nullable; public class RecordHeader { private final boolean definition; private final boolean developerData; - private final LocalMessage localMessage; + private final PredefinedLocalMessage predefinedLocalMessage; private final int rawLocalMessageType; private final Integer timeOffset; private long referenceTimestamp; - public RecordHeader(boolean definition, boolean developerData, LocalMessage localMessage, Integer timeOffset) { + public RecordHeader(boolean definition, boolean developerData, PredefinedLocalMessage predefinedLocalMessage, Integer timeOffset) { this.definition = definition; this.developerData = developerData; - this.localMessage = localMessage; - this.rawLocalMessageType = localMessage.getType(); + this.predefinedLocalMessage = predefinedLocalMessage; + this.rawLocalMessageType = predefinedLocalMessage.getType(); this.timeOffset = timeOffset; } //see https://github.com/polyvertex/fitdecode/blob/master/fitdecode/reader.py#L512 public RecordHeader(byte header) { + this(header, false); + } + + public RecordHeader(byte header, boolean inferLocalMessage) { if ((header & 0x80) == 0x80) { //compressed timestamp TODO add support definition = false; developerData = false; @@ -33,9 +37,13 @@ public class RecordHeader { rawLocalMessageType = header & 0xf; timeOffset = null; } - localMessage = LocalMessage.fromType(rawLocalMessageType); + if (inferLocalMessage) + predefinedLocalMessage = PredefinedLocalMessage.fromType(rawLocalMessageType); + else + predefinedLocalMessage = null; } + public void setReferenceTimestamp(long referenceTimestamp) { this.referenceTimestamp = referenceTimestamp; } @@ -61,14 +69,14 @@ public class RecordHeader { } @Nullable - public LocalMessage getLocalMessage() { - return localMessage; + public PredefinedLocalMessage getPredefinedLocalMessage() { + return predefinedLocalMessage; } public byte generateOutgoingDefinitionPayload() { if (!definition && !developerData) - return (byte) (timeOffset | (((byte) localMessage.getType()) << 5)); - byte base = (byte) (null == localMessage ? rawLocalMessageType : localMessage.getType()); + return (byte) (timeOffset | (((byte) predefinedLocalMessage.getType()) << 5)); + byte base = (byte) (null == predefinedLocalMessage ? rawLocalMessageType : predefinedLocalMessage.getType()); if (definition) base = (byte) (base | 0x40); if (developerData) @@ -79,8 +87,8 @@ public class RecordHeader { public byte generateOutgoingDataPayload() { //TODO: unclear if correct if (!definition && !developerData) - return (byte) (timeOffset | (((byte) localMessage.getType()) << 5)); - byte base = (byte) (null == localMessage ? rawLocalMessageType : localMessage.getType()); + return (byte) (timeOffset | (((byte) predefinedLocalMessage.getType()) << 5)); + byte base = (byte) (null == predefinedLocalMessage ? rawLocalMessageType : predefinedLocalMessage.getType()); if (developerData) base = (byte) (base | 0x20); @@ -90,7 +98,7 @@ public class RecordHeader { @NonNull @Override public String toString() { - return "Local Message: " + (null == localMessage ? "raw: " + rawLocalMessageType : "type: " + localMessage.name()); + return "Local Message: " + (null == predefinedLocalMessage ? "raw: " + rawLocalMessageType : "type: " + predefinedLocalMessage.name()); } @Override @@ -101,12 +109,12 @@ public class RecordHeader { RecordHeader that = (RecordHeader) o; if (rawLocalMessageType != that.rawLocalMessageType) return false; - return localMessage == that.localMessage; + return predefinedLocalMessage == that.predefinedLocalMessage; } @Override public int hashCode() { - int result = (localMessage != null ? localMessage.hashCode() : 0); + int result = (predefinedLocalMessage != null ? predefinedLocalMessage.hashCode() : 0); result = 31 * result + rawLocalMessageType; return result; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java index c4217c826..338678208 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDataMessage.java @@ -3,7 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages; import java.util.ArrayList; import java.util.List; -import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.LocalMessage; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader; @@ -24,16 +24,16 @@ public class FitDataMessage extends GFDIMessage { final List recordDataList = new ArrayList<>(); while (reader.remaining() > 0) { - RecordHeader recordHeader = new RecordHeader((byte) reader.readByte()); + RecordHeader recordHeader = new RecordHeader((byte) reader.readByte(), true); if (recordHeader.isDefinition()) return null; - LocalMessage localMessage = recordHeader.getLocalMessage(); - if (localMessage == null) { + PredefinedLocalMessage predefinedLocalMessage = recordHeader.getPredefinedLocalMessage(); + if (predefinedLocalMessage == null) { LOG.warn("Local message is null"); return null; } - RecordData recordData = new RecordData(localMessage.getRecordDefinition()); + RecordData recordData = new RecordData(predefinedLocalMessage.getRecordDefinition()); recordData.parseDataMessage(reader); recordDataList.add(recordData); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java index 4a48d9535..b54e93e09 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/messages/FitDefinitionMessage.java @@ -25,7 +25,7 @@ public class FitDefinitionMessage extends GFDIMessage { List recordDefinitions = new ArrayList<>(); while (reader.remaining() > 0) { - RecordHeader recordHeader = new RecordHeader((byte) reader.readByte()); + RecordHeader recordHeader = new RecordHeader((byte) reader.readByte(), true); recordDefinitions.add(RecordDefinition.parseIncoming(reader, recordHeader)); } 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 9fe5344ab..323e3d245 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 @@ -434,19 +434,19 @@ public class GarminSupportTest { "unknown_254(UINT16/2): 0 unknown_1(UINT16Z/2): 50008 unknown_0(UINT8/1): 1 unknown_3(UINT8Z/1): 1 ], \n" + "Local Message: raw: 5 Global Message Number: UNK_6=[\n" + "unknown_0(STRING/4): EVO unknown_3(UINT32/4): 45719172 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 0 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 47617 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 80 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" + - "Local Message: type: TODAY_WEATHER_CONDITIONS Global Message Number: UNK_6=[\n" + + "Local Message: raw: 6 Global Message Number: UNK_6=[\n" + "unknown_0(STRING/5): P2SL unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 1 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 28209 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 90 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,118,190] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" + "Local Message: raw: 7 Global Message Number: UNK_6=[\n" + "unknown_0(STRING/9): LANGSTER unknown_3(UINT32/4): 1231891 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 2 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 10851 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 0 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" + "Local Message: raw: 8 Global Message Number: UNK_6=[\n" + "unknown_0(STRING/2): M unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [53,39,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 3 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): 31337 unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 1 unknown_19(UINT8/1): 0 unknown_20(UINT8/1): 1 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): 5 unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" + - "Local Message: type: HOURLY_WEATHER_FORECAST Global Message Number: UNK_6=[\n" + + "Local Message: raw: 9 Global Message Number: UNK_6=[\n" + "unknown_0(STRING/7): Bike 5 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 4 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" + "unknown_0(STRING/7): Bike 6 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 5 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" + "unknown_0(STRING/7): Bike 7 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 6 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" + "unknown_0(STRING/7): Bike 8 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 7 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 , \n" + "unknown_0(STRING/7): Bike 9 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 8 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" + - "Local Message: type: DAILY_WEATHER_FORECAST Global Message Number: UNK_6=[\n" + + "Local Message: raw: 10 Global Message Number: UNK_6=[\n" + "unknown_0(STRING/8): Bike 10 unknown_3(UINT32/4): 0 unknown_39(UINT8Z/4): [39,53,,] unknown_41(UINT8Z/12): [23,21,19,18,17,16,15,14,13,12,11,] unknown_254(UINT16/2): 9 unknown_4(UINT16Z/2): null unknown_5(UINT16Z/2): null unknown_6(UINT16Z/2): null unknown_7(UINT16Z/2): null unknown_8(UINT16/2): 2096 unknown_9(UINT16/2): 0 unknown_10(UINT16/2): 95 unknown_11(UINT16/2): 500 unknown_42(UINT16Z/2): null unknown_1(ENUM/1): null unknown_2(ENUM/1): null unknown_12(UINT8/1): 1 unknown_13(UINT8/1): 1 unknown_14(UINT8/1): 0 unknown_15(UINT8/1): 0 unknown_16(UINT8/1): 0 unknown_17(UINT8/1): 0 unknown_18(UINT8/1): 0 unknown_19(UINT8/1): 254 unknown_20(UINT8/1): 0 unknown_21(UINT8Z/1): null unknown_22(UINT8Z/1): null unknown_23(UINT8Z/1): null unknown_24(UINT8Z/1): null unknown_35(UINT8/3): [0,50,] unknown_36(ENUM/1): 4 unknown_37(UINT8/1): null unknown_38(UINT8Z/1): 2 unknown_40(UINT8Z/1): 11 unknown_43(UINT8Z/1): null unknown_44(ENUM/1): 0 ], \n" + "Local Message: raw: 11 Global Message Number: CONNECTIVITY=[\n" + "name(STRING/9): Edge 510 bluetooth_enabled(ENUM/1): 0 live_tracking_enabled(ENUM/1): null weather_conditions_enabled(ENUM/1): null weather_alerts_enabled(ENUM/1): null auto_activity_upload_enabled(ENUM/1): null course_download_enabled(ENUM/1): null workout_download_enabled(ENUM/1): null gps_ephemeris_download_enabled(ENUM/1): null ]}";