Garmin: add support for devices not sending ConfigurationMessage

Infer capabilities from the incoming FitData message (as test/workaround, we're not sure if the field we're using is the correct one).
Use the CapabilitiesDeviceEvent instead of the hardcoded ConfigurationMessage instanceof check to call completeInitialization() in GarminSupport.
Also centralize the generation of the capabilities-related DeviceEvent in GarminCapability as they are used both in ConfigurationMessage as in FitLocalMessageHandler now.
This commit is contained in:
Daniele Gobbetti
2025-12-31 19:10:56 +01:00
parent 4ab011dd1e
commit a87ddbb66a
4 changed files with 66 additions and 16 deletions
@@ -16,11 +16,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr; package nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.CapabilitiesDeviceEvent;
public enum GarminCapability { public enum GarminCapability {
CONNECT_MOBILE_FIT_LINK, CONNECT_MOBILE_FIT_LINK,
GOLF_FIT_LINK, GOLF_FIT_LINK,
@@ -187,6 +194,22 @@ public enum GarminCapability {
return result; return result;
} }
public static Set<GarminCapability> setFromLong(final long capabilitiesMask) {
final Set<GarminCapability> result =
new HashSet<>(GarminCapability.values().length);
for (int i = 0; i < Long.SIZE; i++) {
if ((capabilitiesMask & (1L << i)) != 0) {
GarminCapability cap = FROM_ORDINAL.get(i);
if (cap != null) {
result.add(cap);
}
}
}
return result;
}
public static byte[] setToBinary(final Set<GarminCapability> capabilities) { public static byte[] setToBinary(final Set<GarminCapability> capabilities) {
final GarminCapability[] values = values(); final GarminCapability[] values = values();
final byte[] result = new byte[(values.length + 7) / 8]; final byte[] result = new byte[(values.length + 7) / 8];
@@ -213,4 +236,15 @@ public enum GarminCapability {
} }
return result.toString(); return result.toString();
} }
public static List<GBDeviceEvent> getGBDeviceEvent(final Set<GarminCapability> capabilities) {
final Set<Object> capabilitiesPref = new HashSet<>();
for (final GarminCapability capability : capabilities) {
capabilitiesPref.add(capability.name());
}
return Arrays.asList(
new CapabilitiesDeviceEvent(capabilities),
new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_GARMIN_CAPABILITIES, capabilitiesPref)
);
}
} }
@@ -6,7 +6,10 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitLocalMessageBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitLocalMessageBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFITMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData; 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.RecordDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDataMessage; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDataMessage;
@@ -44,14 +47,32 @@ public class FitLocalMessageHandler implements MessageHandler{
} }
private void parseIncomingFitDataMessage(FitDataMessage incoming) { private void parseIncomingFitDataMessage(FitDataMessage incoming) {
final List<GBDeviceEvent> deviceEventList = new ArrayList<>();
recordDataList = (incoming).applyDefinitions(recordDefinitionList); recordDataList = (incoming).applyDefinitions(recordDefinitionList);
for(RecordData d: recordDataList){ for(RecordData d: recordDataList){
LOG.info("Incoming FitDataMessage: {}", d); LOG.info("Incoming FitDataMessage: {}", d);
List<GBDeviceEvent> processed = processRecordData(d);
if(processed!=null) {
deviceEventList.addAll(processed);
}
}
LOG.info("Some incoming FitDataMessages are not processed any further, just logged.");
for (final GBDeviceEvent event : deviceEventList) {
deviceSupport.evaluateGBDeviceEvent(event);
} }
LOG.info("Incoming FitDataMessages are not processed any further, just logged.");
unregisterSelf(); unregisterSelf();
} }
private List<GBDeviceEvent> processRecordData(RecordData d) {
if (d.getRecordDefinition().getGlobalFITMessage() == GlobalFITMessage.CAPABILITIES) {
//TODO: we are not sure this is correct!
return GarminCapability.getGBDeviceEvent(
GarminCapability.setFromLong((Long) d.getFieldByName("connectivity_supported"))
);
}
return null;
}
private void unregisterSelf() { private void unregisterSelf() {
deviceSupport.unregisterHandler(this); deviceSupport.unregisterHandler(this);
} }
@@ -312,19 +312,15 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
} }
} }
final List<GBDeviceEvent> events = parsedMessage.getGBDeviceEvent();
for (final GBDeviceEvent event : events) {
evaluateGBDeviceEvent(event);
}
sendAck("send status", parsedMessage); //send status message sendAck("send status", parsedMessage); //send status message
sendOutgoingMessage("send reply", parsedMessage); //send reply if any sendOutgoingMessage("send reply", parsedMessage); //send reply if any
sendOutgoingMessage("send followup", followup); //send followup message if any sendOutgoingMessage("send followup", followup); //send followup message if any
if (parsedMessage instanceof GenericStatusMessage && ((GenericStatusMessage) parsedMessage).getGarminMessage() == GFDIMessage.GarminMessage.CONFIGURATION) { //the last forced message exchange final List<GBDeviceEvent> events = parsedMessage.getGBDeviceEvent();
completeInitialization(); for (final GBDeviceEvent event : events) {
evaluateGBDeviceEvent(event);
} }
processDownloadQueue(); processDownloadQueue();
@@ -358,6 +354,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
} }
} else if (deviceEvent instanceof CapabilitiesDeviceEvent) { } else if (deviceEvent instanceof CapabilitiesDeviceEvent) {
final Set<GarminCapability> capabilities = ((CapabilitiesDeviceEvent) deviceEvent).capabilities; final Set<GarminCapability> capabilities = ((CapabilitiesDeviceEvent) deviceEvent).capabilities;
completeInitialization();
if (capabilities.contains(GarminCapability.REALTIME_SETTINGS)) { if (capabilities.contains(GarminCapability.REALTIME_SETTINGS)) {
final String language = Locale.getDefault().getLanguage(); final String language = Locale.getDefault().getLanguage();
final String country = Locale.getDefault().getCountry(); final String country = Locale.getDefault().getCountry();
@@ -722,6 +719,11 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
} }
private void completeInitialization() { private void completeInitialization() {
if (gbDevice.getState() == GBDevice.State.INITIALIZED) {
LOG.error("completeInitialization() was called, but the device is already initialized. This should never happen! Please report this to the project.");
LOG.warn("preventing double initialization");
return;
}
sendOutgoingMessage("request supported file types", new SupportedFileTypesMessage()); sendOutgoingMessage("request supported file types", new SupportedFileTypesMessage());
sendDeviceSettings(); sendDeviceSettings();
@@ -35,14 +35,7 @@ public class ConfigurationMessage extends GFDIMessage {
@Override @Override
public List<GBDeviceEvent> getGBDeviceEvent() { public List<GBDeviceEvent> getGBDeviceEvent() {
final Set<Object> capabilitiesPref = new HashSet<>(); return GarminCapability.getGBDeviceEvent(capabilities);
for (final GarminCapability capability : capabilities) {
capabilitiesPref.add(capability.name());
}
return Arrays.asList(
new CapabilitiesDeviceEvent(capabilities),
new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_GARMIN_CAPABILITIES, capabilitiesPref)
);
} }
@Override @Override