mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin: Better Local Message Handling
Use a temporary MessageHandler to send Definitions and Data in sequence instead of in parallel. Add functionality to populate FitLocalMessageBuilder record by record instead of with a List, also add helper to get an unused localMessageType. Don't use hardcoded localMessageTypes for weather message as they are not required.
This commit is contained in:
committed by
José Rebelo
parent
fe6def63ee
commit
63e0ecba63
+46
@@ -0,0 +1,46 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitLocalMessageBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDataMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDefinitionMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.FitDataStatusMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.FitDefinitionStatusMessage;
|
||||
|
||||
public class FitLocalMessageHandler implements MessageHandler{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FitLocalMessageHandler.class);
|
||||
private final GarminSupport deviceSupport;
|
||||
private final FitLocalMessageBuilder localMessageBuilder;
|
||||
|
||||
public FitLocalMessageHandler(GarminSupport deviceSupport, FitLocalMessageBuilder localMessageBuilder) {
|
||||
this.deviceSupport = deviceSupport;
|
||||
this.localMessageBuilder = localMessageBuilder;
|
||||
}
|
||||
|
||||
public FitDefinitionMessage init() {
|
||||
return new FitDefinitionMessage(localMessageBuilder.getDefinitions());
|
||||
}
|
||||
|
||||
private FitDataMessage sendFollowUp(FitDefinitionStatusMessage fitDefinitionStatusMessage) {
|
||||
if (fitDefinitionStatusMessage.getFitDefinitionStatusCode() != FitDefinitionStatusMessage.FitDefinitionStatusCode.APPLIED)
|
||||
LOG.warn("FitDefinition was not applied, will send FitData anyway.");
|
||||
return new FitDataMessage(localMessageBuilder.getRecordDataList());
|
||||
}
|
||||
|
||||
private void unregisterSelf() {
|
||||
deviceSupport.unregisterHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFDIMessage handle(GFDIMessage message) {
|
||||
if (message instanceof FitDefinitionStatusMessage)
|
||||
return sendFollowUp((FitDefinitionStatusMessage) message);
|
||||
else if (message instanceof FitDataStatusMessage) {
|
||||
unregisterSelf();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+21
-11
@@ -33,7 +33,6 @@ import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -599,14 +598,22 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
return;
|
||||
}
|
||||
|
||||
final FitLocalMessageBuilder weatherLocalMessage = new FitLocalMessageBuilder(encodeWeather(weather));
|
||||
final FitLocalMessageBuilder weatherLocalMessage = encodeWeather(weather);
|
||||
|
||||
sendOutgoingMessage("send " + weatherLocalMessage.getDefinitions().size() + " weather definitions", new FitDefinitionMessage(weatherLocalMessage.getDefinitions()));
|
||||
sendOutgoingMessage("send weather data", new FitDataMessage(weatherLocalMessage.getData()));
|
||||
final FitLocalMessageHandler weatherHandler = new FitLocalMessageHandler(this, weatherLocalMessage);
|
||||
messageHandlers.add(weatherHandler);
|
||||
|
||||
sendOutgoingMessage("send " + weatherLocalMessage.getDefinitions().size() + " weather definitions", weatherHandler.init());
|
||||
}
|
||||
|
||||
public static List<RecordData> encodeWeather(final WeatherSpec weather) {
|
||||
List<RecordData> weatherData = new ArrayList<>();
|
||||
protected void unregisterHandler(MessageHandler registered) {
|
||||
messageHandlers.remove(registered);
|
||||
LOG.warn("handlers: {}", messageHandlers.stream().count());
|
||||
}
|
||||
|
||||
public static FitLocalMessageBuilder encodeWeather(final WeatherSpec weather) {
|
||||
|
||||
final FitLocalMessageBuilder weatherLocalMessage = new FitLocalMessageBuilder();
|
||||
|
||||
final FitWeather.Builder today = new FitWeather.Builder();
|
||||
today.setWeatherReport(FieldDefinitionWeatherReport.Type.current);
|
||||
@@ -628,8 +635,9 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
today.setAirQuality(FieldDefinitionWeatherAqi.aqiAbsoluteValueToEnum(weather.getAirQuality().getAqi()));
|
||||
}
|
||||
today.setLocation(weather.getLocation());
|
||||
weatherData.add(today.build(6));
|
||||
weatherLocalMessage.addRecordData(today.build(weatherLocalMessage.getNextAvailableLocalMessageType()));
|
||||
|
||||
final int hourlyMessageType = weatherLocalMessage.getNextAvailableLocalMessageType();
|
||||
for (int hour = 0; hour <= 11; hour++) {
|
||||
if (hour < weather.getHourly().size()) {
|
||||
WeatherSpec.Hourly hourly = weather.getHourly().get(hour);
|
||||
@@ -647,10 +655,12 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
// weatherHourlyForecast.setDewPoint(0); // TODO: add once Hourly contains this information
|
||||
weatherHourlyForecast.setUvIndex(hourly.getUvIndex());
|
||||
// weatherHourlyForecast.setAirQuality(0); // TODO: add once Hourly contains this information
|
||||
weatherData.add(weatherHourlyForecast.build(9));
|
||||
weatherLocalMessage.addRecordData(weatherHourlyForecast.build(hourlyMessageType));
|
||||
}
|
||||
}
|
||||
//
|
||||
final int dailyMessageType = weatherLocalMessage.getNextAvailableLocalMessageType();
|
||||
|
||||
final FitWeather.Builder todayDailyForecast = new FitWeather.Builder();
|
||||
todayDailyForecast.setWeatherReport(FieldDefinitionWeatherReport.Type.daily_forecast);
|
||||
todayDailyForecast.setTimestamp((long) weather.getTimestamp());
|
||||
@@ -663,7 +673,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
if (null != weather.getAirQuality()) {
|
||||
todayDailyForecast.setAirQuality(FieldDefinitionWeatherAqi.aqiAbsoluteValueToEnum(weather.getAirQuality().getAqi()));
|
||||
}
|
||||
weatherData.add(todayDailyForecast.build(10));
|
||||
weatherLocalMessage.addRecordData(todayDailyForecast.build(dailyMessageType));
|
||||
|
||||
|
||||
for (int day = 0; day < 4; day++) {
|
||||
@@ -681,11 +691,11 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
if (null != daily.getAirQuality()) {
|
||||
weatherDailyForecast.setAirQuality(FieldDefinitionWeatherAqi.aqiAbsoluteValueToEnum(daily.getAirQuality().getAqi()));
|
||||
}
|
||||
weatherData.add(weatherDailyForecast.build(10));
|
||||
weatherLocalMessage.addRecordData(weatherDailyForecast.build(dailyMessageType));
|
||||
}
|
||||
}
|
||||
|
||||
return weatherData;
|
||||
return weatherLocalMessage;
|
||||
}
|
||||
|
||||
private void completeInitialization() {
|
||||
|
||||
+37
-4
@@ -14,20 +14,45 @@ public class FitLocalMessageBuilder {
|
||||
private final List<RecordData> recordData = new ArrayList<>();
|
||||
private final Map<Integer, RecordDefinition> definitionsByLocalType = new LinkedHashMap<>();
|
||||
|
||||
public FitLocalMessageBuilder() {
|
||||
}
|
||||
|
||||
public FitLocalMessageBuilder addRecordData(RecordData data) {
|
||||
Objects.requireNonNull(data, "RecordData cannot be null");
|
||||
Objects.requireNonNull(data.getRecordDefinition(), "RecordDefinition cannot be null");
|
||||
|
||||
processRecordData(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FitLocalMessageBuilder(List<RecordData> recordDataList) {
|
||||
Objects.requireNonNull(recordDataList, "recordDataList cannot be null");
|
||||
|
||||
for (RecordData data : recordDataList) {
|
||||
Objects.requireNonNull(data.getRecordDefinition(), "recordDefinition cannot be null");
|
||||
|
||||
processRecordData(data);
|
||||
addRecordData(data);
|
||||
}
|
||||
}
|
||||
|
||||
public int getNextAvailableLocalMessageType() {
|
||||
for (int i = 0; i < MAX_DEFINITIONS; i++) {
|
||||
if (!definitionsByLocalType.containsKey(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("No available localMessageType: all %d slots are occupied", MAX_DEFINITIONS)
|
||||
);
|
||||
}
|
||||
|
||||
private void processRecordData(RecordData data) {
|
||||
final RecordDefinition definition = data.getRecordDefinition();
|
||||
final int localMessageType = definition.getRecordHeader().getLocalMessageType();
|
||||
|
||||
validateLocalMessageType(localMessageType);
|
||||
|
||||
if (!definitionsByLocalType.containsKey(localMessageType)) {
|
||||
addNewDefinition(data, definition, localMessageType);
|
||||
} else {
|
||||
@@ -35,6 +60,15 @@ public class FitLocalMessageBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLocalMessageType(int localMessageType) {
|
||||
if (localMessageType < 0 || localMessageType >= MAX_DEFINITIONS) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("localMessageType must be between 0 and %d, got: %d",
|
||||
MAX_DEFINITIONS - 1, localMessageType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewDefinition(RecordData data, RecordDefinition definition, int localMessageType) {
|
||||
if (definitionsByLocalType.size() >= MAX_DEFINITIONS) {
|
||||
throw new IllegalStateException(
|
||||
@@ -77,8 +111,7 @@ public class FitLocalMessageBuilder {
|
||||
return new ArrayList<>(definitionsByLocalType.values());
|
||||
}
|
||||
|
||||
public List<RecordData> getData() {
|
||||
public List<RecordData> getRecordDataList() {
|
||||
return new ArrayList<>(recordData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -31,6 +31,10 @@ public class FitDefinitionStatusMessage extends GFDIStatusMessage {
|
||||
return new FitDefinitionStatusMessage(garminMessage, status, fitDefinitionStatusCode);
|
||||
}
|
||||
|
||||
public FitDefinitionStatusCode getFitDefinitionStatusCode() {
|
||||
return fitDefinitionStatusCode;
|
||||
}
|
||||
|
||||
public enum FitDefinitionStatusCode {
|
||||
APPLIED,
|
||||
NOT_UNIQUE,
|
||||
|
||||
+2
-3
@@ -292,7 +292,8 @@ public class FitWeatherTest {
|
||||
Assert.assertEquals("FitWeather{weather_report=2, condition=CLEAR, precipitation_probability=53, day_of_week=TUESDAY, high_temperature=301, low_temperature=286, timestamp=1764364324}", weatherData.get(17).toString());
|
||||
*/
|
||||
|
||||
List<RecordData> weatherData = GarminSupport.encodeWeather(weather);
|
||||
FitLocalMessageBuilder weatherLocalMessage = GarminSupport.encodeWeather(weather);
|
||||
List<RecordData> weatherData = weatherLocalMessage.getRecordDataList();
|
||||
|
||||
// Get all distinct record definitions
|
||||
Set<Integer> seenDefinitions = new HashSet<>();
|
||||
@@ -305,9 +306,7 @@ public class FitWeatherTest {
|
||||
}
|
||||
}
|
||||
|
||||
FitLocalMessageBuilder weatherLocalMessage = new FitLocalMessageBuilder(weatherData);
|
||||
|
||||
FitLocalMessageBuilder test = new FitLocalMessageBuilder(null);
|
||||
for (RecordDefinition weatherDefinition : weatherDefinitions) {
|
||||
MessageWriter writer = new MessageWriter();
|
||||
weatherDefinition.generateOutgoingPayload(writer);
|
||||
|
||||
Reference in New Issue
Block a user