Garmin: Initial attempt at supporting incoming FitDefinition/FitData Messages

Add the code to generate outgoing FitDefinition- and FitDataStatusMessage.
Add support for incoming FitDefinitionMessage and register handler via deviceEvent (commented for now to avoid leaking).
Refactor FitLocalMessageHandler to support both outgoing and incoming handling of FitDefinition/FitData.

Most of the changes were not tested for the lack of hardware, but at least they show no regressions.
This commit is contained in:
Daniele Gobbetti
2025-12-21 13:54:21 +00:00
committed by José Rebelo
parent a3d732f11a
commit 157c9c2237
9 changed files with 247 additions and 50 deletions
@@ -3,7 +3,12 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitLocalMessageBuilder;
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.FitDataMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.FitDefinitionMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
@@ -13,21 +18,29 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.stat
public class FitLocalMessageHandler implements MessageHandler{
private static final Logger LOG = LoggerFactory.getLogger(FitLocalMessageHandler.class);
private final GarminSupport deviceSupport;
private final FitLocalMessageBuilder localMessageBuilder;
private final List<RecordDefinition> recordDefinitionList;
private List<RecordData> recordDataList;
public FitLocalMessageHandler(GarminSupport deviceSupport, FitLocalMessageBuilder localMessageBuilder) {
this.deviceSupport = deviceSupport;
this.localMessageBuilder = localMessageBuilder;
this.recordDefinitionList = localMessageBuilder.getDefinitions();
this.recordDataList = localMessageBuilder.getRecordDataList();
}
public FitLocalMessageHandler(GarminSupport deviceSupport, List<RecordDefinition> recordDefinitionList) {
this.deviceSupport = deviceSupport;
this.recordDefinitionList = recordDefinitionList;
this.recordDataList = new ArrayList<>();
}
public FitDefinitionMessage init() {
return new FitDefinitionMessage(localMessageBuilder.getDefinitions());
return new FitDefinitionMessage(this.recordDefinitionList);
}
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());
return new FitDataMessage(this.recordDataList);
}
private void unregisterSelf() {
@@ -40,6 +53,9 @@ public class FitLocalMessageHandler implements MessageHandler{
return sendFollowUp((FitDefinitionStatusMessage) message);
else if (message instanceof FitDataStatusMessage) {
unregisterSelf();
} else if (message instanceof FitDataMessage) {
recordDataList = ((FitDataMessage) message).applyDefinitions(recordDefinitionList);
//TODO: do something
}
return null;
}
@@ -83,6 +83,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v2.CommunicatorV2;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.CapabilitiesDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.FileDownloadedDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.IncomingFitDefinitionDeviceEvent;
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;
@@ -446,6 +447,10 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
}
currentlyDownloading = null;
} else if (deviceEvent instanceof IncomingFitDefinitionDeviceEvent) {
//TODO: commented for now to avoid leaking
// final FitLocalMessageHandler fitLocalMessageHandler = new FitLocalMessageHandler(this, ((IncomingFitDefinitionDeviceEvent) deviceEvent).getRecordDefinitions());
// messageHandlers.add(fitLocalMessageHandler);
} else {
super.evaluateGBDeviceEvent(deviceEvent);
}
@@ -608,7 +613,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
protected void unregisterHandler(MessageHandler registered) {
messageHandlers.remove(registered);
LOG.warn("handlers: {}", messageHandlers.stream().count());
LOG.warn("handlers left: {}", messageHandlers.stream().count());
}
public static FitLocalMessageBuilder encodeWeather(final WeatherSpec weather) {
@@ -0,0 +1,27 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
import android.content.Context;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
public class IncomingFitDefinitionDeviceEvent extends GBDeviceEvent {
public List<RecordDefinition> getRecordDefinitions() {
return recordDefinitions;
}
private final List<RecordDefinition> recordDefinitions;
public IncomingFitDefinitionDeviceEvent(List<RecordDefinition> recordDefinitions) {
this.recordDefinitions = recordDefinitions;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -14,13 +14,12 @@ public class FitDataMessage extends GFDIMessage {
private List<RecordData> recordDataList;
private GarminByteBufferReader reader;
private final boolean generateOutgoing;
private boolean isProcessed = false;
public FitDataMessage(GarminByteBufferReader messageReader, GarminMessage garminMessage) {
this.reader = messageReader;
this.recordDataList = new ArrayList<>();
this.recordDataList = null;
this.garminMessage = garminMessage;
this.statusMessage = new FitDataStatusMessage(garminMessage, Status.ACK, FitDataStatusMessage.FitDataStatusCode.APPLIED);
this.statusMessage = new FitDataStatusMessage(garminMessage, Status.ACK, FitDataStatusMessage.FitDataStatusCode.APPLIED, true);
this.generateOutgoing = false;
}
@@ -29,7 +28,6 @@ public class FitDataMessage extends GFDIMessage {
this.recordDataList = recordDataList;
this.garminMessage = GarminMessage.FIT_DATA;
this.statusMessage = null;
this.isProcessed = true;
this.generateOutgoing = true;
}
@@ -37,13 +35,14 @@ public class FitDataMessage extends GFDIMessage {
return new FitDataMessage(new GarminByteBufferReader(reader.readBytes(reader.remaining())), garminMessage);
}
public void applyDefinitions(List<RecordDefinition> recordDefinitions) {
public List<RecordData> applyDefinitions(List<RecordDefinition> recordDefinitions) {
final List<RecordData> recordDataList = new ArrayList<>();;
Objects.requireNonNull(reader, "reader cannot be null");
try {
while (reader.remaining() > 0) {
RecordHeader recordHeader = new RecordHeader((byte) reader.readByte());
if (recordHeader.isDefinition())
return;
return null;
RecordDefinition localMessageDefinition = recordDefinitions.stream()
.filter(d -> d.getRecordHeader().getLocalMessageType() == recordHeader.getLocalMessageType())
@@ -52,7 +51,7 @@ public class FitDataMessage extends GFDIMessage {
if (localMessageDefinition == null) {
LOG.warn("Cannot find a local message definition for type: {}", recordHeader.getLocalMessageType());
return ;
return null;
}
RecordData recordData = new RecordData(
localMessageDefinition,
@@ -61,30 +60,19 @@ public class FitDataMessage extends GFDIMessage {
recordData.parseDataMessage(reader, null);
recordDataList.add(recordData);
}
this.isProcessed = true;
} catch (Exception e) {
LOG.error("Error while applying definitions", e);
throw new IllegalStateException("Failed to apply definitions to FIT data", e);
} finally {
reader = null;
reader.setPosition(0);
}
}
public List<RecordData> getRecordDataList() {
if(!isProcessed())
throw new IllegalStateException(
"Message has not been processed yet. Call applyDefinitions first."
);
return recordDataList;
}
public boolean isProcessed() {
return isProcessed;
}
@Override
protected boolean generateOutgoing() {
if(this.recordDataList == null)
return false;
final MessageWriter writer = new MessageWriter(response);
writer.writeShort(0); // packet size will be filled below
writer.writeShort(this.garminMessage.getId());
@@ -1,24 +1,36 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.IncomingFitDefinitionDeviceEvent;
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.messages.status.FitDefinitionStatusMessage;
public class FitDefinitionMessage extends GFDIMessage {
private final IncomingFitDefinitionDeviceEvent incomingFitDefinitionDeviceEvent;
private final List<RecordDefinition> recordDefinitions;
private final boolean generateOutgoing;
public FitDefinitionMessage(List<RecordDefinition> recordDefinitions, GarminMessage garminMessage) {
public FitDefinitionMessage(List<RecordDefinition> recordDefinitions, GarminMessage garminMessage) {//incoming
this.recordDefinitions = recordDefinitions;
this.garminMessage = garminMessage;
this.statusMessage = this.getStatusMessage();
this.statusMessage = new FitDefinitionStatusMessage(garminMessage, Status.ACK, FitDefinitionStatusMessage.FitDefinitionStatusCode.APPLIED, true);
this.generateOutgoing = false;
this.incomingFitDefinitionDeviceEvent = new IncomingFitDefinitionDeviceEvent(recordDefinitions);
}
public FitDefinitionMessage(List<RecordDefinition> recordDefinitions) {
public FitDefinitionMessage(List<RecordDefinition> recordDefinitions) { //outgoing
this.recordDefinitions = recordDefinitions;
this.garminMessage = GarminMessage.FIT_DEFINITION;
this.statusMessage = null;
this.generateOutgoing = true;
this.incomingFitDefinitionDeviceEvent = null;
}
public static FitDefinitionMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
@@ -36,6 +48,11 @@ public class FitDefinitionMessage extends GFDIMessage {
return recordDefinitions;
}
@Override
public List<GBDeviceEvent> getGBDeviceEvent() {
return Collections.singletonList(incomingFitDefinitionDeviceEvent);
}
@Override
protected boolean generateOutgoing() {
final MessageWriter writer = new MessageWriter(response);
@@ -44,7 +61,7 @@ public class FitDefinitionMessage extends GFDIMessage {
for (RecordDefinition recordDefinition : recordDefinitions) {
recordDefinition.generateOutgoingPayload(writer);
}
return true;
return this.generateOutgoing;
}
}
@@ -2,22 +2,19 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
public class FitDataStatusMessage extends GFDIStatusMessage {
private final Status status;
private final FitDataStatusCode fitDataStatusCode;
private final boolean sendOutgoing;
public FitDataStatusMessage(GarminMessage garminMessage, Status status, FitDataStatusCode fitDataStatusCode) {
public FitDataStatusMessage(GarminMessage garminMessage, Status status, FitDataStatusCode fitDataStatusCode, boolean sendOutgoing) {
this.garminMessage = garminMessage;
this.status = status;
this.fitDataStatusCode = fitDataStatusCode;
switch (fitDataStatusCode) {
case APPLIED:
LOG.info("FIT DATA RETURNED STATUS: {}", fitDataStatusCode.name());
break;
default:
LOG.warn("FIT DATA RETURNED STATUS: {}", fitDataStatusCode.name());
}
this.sendOutgoing = sendOutgoing;
}
public static FitDataStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
@@ -28,7 +25,25 @@ public class FitDataStatusMessage extends GFDIStatusMessage {
LOG.warn("Unknown fit data status code {}", fitDataStatusCodeByte);
return null;
}
return new FitDataStatusMessage(garminMessage, status, fitDataStatusCode);
switch (fitDataStatusCode) {
case APPLIED:
LOG.info("FIT DATA RETURNED STATUS: {}", fitDataStatusCode.name());
break;
default:
LOG.warn("FIT DATA RETURNED STATUS: {}", fitDataStatusCode.name());
}
return new FitDataStatusMessage(garminMessage, status, fitDataStatusCode, false);
}
@Override
protected boolean generateOutgoing() {
final MessageWriter writer = new MessageWriter(response);
writer.writeShort(0); // packet size will be filled below
writer.writeShort(GarminMessage.RESPONSE.getId());
writer.writeShort(garminMessage.getId());
writer.writeByte(status.ordinal());
writer.writeByte(fitDataStatusCode.ordinal());
return sendOutgoing;
}
public enum FitDataStatusCode {
@@ -2,22 +2,19 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.MessageWriter;
public class FitDefinitionStatusMessage extends GFDIStatusMessage {
private final Status status;
private final FitDefinitionStatusCode fitDefinitionStatusCode;
private final boolean sendOutgoing;
public FitDefinitionStatusMessage(GarminMessage garminMessage, Status status, FitDefinitionStatusCode fitDefinitionStatusCode) {
public FitDefinitionStatusMessage(GarminMessage garminMessage, Status status, FitDefinitionStatusCode fitDefinitionStatusCode, boolean sendOutgoing) {
this.garminMessage = garminMessage;
this.status = status;
this.fitDefinitionStatusCode = fitDefinitionStatusCode;
switch (fitDefinitionStatusCode) {
case APPLIED:
LOG.info("FIT DEFINITION RETURNED STATUS: {}", fitDefinitionStatusCode.name());
break;
default:
LOG.warn("FIT DEFINITION RETURNED STATUS: {}", fitDefinitionStatusCode.name());
}
this.sendOutgoing = sendOutgoing;
}
public static FitDefinitionStatusMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {
@@ -28,13 +25,31 @@ public class FitDefinitionStatusMessage extends GFDIStatusMessage {
LOG.warn("Unknown fit definition status code {}", fitDefinitionStatusCodeByte);
return null;
}
return new FitDefinitionStatusMessage(garminMessage, status, fitDefinitionStatusCode);
switch (fitDefinitionStatusCode) {
case APPLIED:
LOG.info("FIT DEFINITION RETURNED STATUS: {}", fitDefinitionStatusCode.name());
break;
default:
LOG.warn("FIT DEFINITION RETURNED STATUS: {}", fitDefinitionStatusCode.name());
}
return new FitDefinitionStatusMessage(garminMessage, status, fitDefinitionStatusCode, false);
}
public FitDefinitionStatusCode getFitDefinitionStatusCode() {
return fitDefinitionStatusCode;
}
@Override
protected boolean generateOutgoing() {
final MessageWriter writer = new MessageWriter(response);
writer.writeShort(0); // packet size will be filled below
writer.writeShort(GarminMessage.RESPONSE.getId());
writer.writeShort(garminMessage.getId());
writer.writeByte(status.ordinal());
writer.writeByte(fitDefinitionStatusCode.ordinal());
return sendOutgoing;
}
public enum FitDefinitionStatusCode {
APPLIED,
NOT_UNIQUE,
@@ -16,6 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.net.Uri;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -31,12 +35,24 @@ import java.lang.reflect.Parameter;
import java.math.BigInteger;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.v2.CommunicatorV2;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.IncomingFitDefinitionDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
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.RecordDefinition;
@@ -47,6 +63,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefi
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileCreator;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitLocation;
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;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -57,6 +79,89 @@ public class GarminSupportTest extends TestBase {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
@Test
public void testWeatherDataMessage() {
byte[] weatherDataMessage = GB.hexStringToByteArray("5901941306003f3672953f3672951a0e1b0200b4000a7a1a2b211bba1707fe6f1d426f6c7a616e6f2c204974616c790009013f367a1018020144037e0a360e3ecac083ff09013f36882017020144037e0a3b0e00000000ff09013f36963015020144037e0a3f0e00000000ff09013f36a44013010015037e0a430d00000000ff09013f36b25012010015037e00470d00000000ff09013f36c06012010015037e004a0d00000000ff09013f36ce7011010017037e004d0d00000000ff09013f36dc8010010017037e00500d00000000ff09013f36ea9010010017037e00510d00000000ff09013f36f8a00f000029037e00530c00000000ff09013f3706b00e000029037e00540c00000000ff09013f3714c00f000029037e00500c00000000ff0a023f3672950e1b0200030a023f3672950e1d0000040a023f3672950f1e0114050a023f36729511200114060a023f367295122100140052E3");
// byte[] weatherDataMessage = GB.hexStringToByteArray("59019413060040684550406845500A060A010095640254095200000000000000004D79204C6F636174696F6E000000000901406848000A160087012A62547F3E99999AFF0901406856100A010168025460577F3DCCCCCDFF0901406864200901000504A85E557F00000000FF09014068723009010027025458517F00000000FF09014068804009010015037E53517F00000000FF090140688E5009010161037E4D4E7F00000000FF090140689C6008010162037E334E7F00000000FF09014068AA700701015A012A1A4F7F00000000FF09014068B8800701008D0254004E7F00000000FF09014068C690070100AC037E004D7F00000000FF09014068D4A007010099012A004E7F00000000FF09014068E2B0061600B4012A004F7F00000000FF0A0240684550060A0164040A0240684550060E1064050A02406845500A121061060A02406845500A0D0361000A0240684550090F106401C8DA");
// byte[] weatherDataMessage = GB.hexStringToByteArray("590194130600405D7050405D70501007130000C20001920E3000000000000000004D79204C6F636174696F6E000000000901405D75B0100100D30245002D0040933333000901405D83C0120100B40245002A004099999A000901405D91D0120100A0048A002A0040900000000901405D9FE01301009E0649002B00406CCCCD000901405DADF013010096056A002F0040133333000901405DBC001201008703510036003FA00000000901405DCA101001003B048A003B003F000000000901405DD8200D01003B02450041003D4CCCCD000901405DE6300D16005A019200410000000000000901405DF4400C16007501BF00450000000000000901405E02500C16005A045E00450000000000000901405E10600B01007204B700490000000000000A02405D705007130000030A02405D70500814163A040A02405D705008141605050A02405D70500611035E060A02405D7050050C1640006324");
FitDataMessage fitDataMessage = (FitDataMessage) GFDIMessage.parseIncoming(weatherDataMessage);
byte[] weatherDefinitionMessage = GB.hexStringToByteArray("7b00931346000100800f000100fd04860904860101010e01010d01010201000302840501020402840601010701020a04850b0485080f0749000100800b000100fd04860101010201000302840402840501020701020f01011004881101004a0001008007000100fd04860e01010d01010201000501020c01003ae3");
FitDefinitionMessage fitDefinitionMessage = (FitDefinitionMessage) GFDIMessage.parseIncoming(weatherDefinitionMessage);
List<RecordData> recordDataList = fitDataMessage.applyDefinitions(fitDefinitionMessage.getRecordDefinitions());
List<RecordData> weatherData = new ArrayList<>();
for (RecordData rec : recordDataList) {
RecordData out = new RecordData(rec.getRecordDefinition(), rec.getRecordDefinition().getRecordHeader());
for (int fieldNumber :
rec.getFieldsNumbers()) {
Object o = rec.getFieldByNumber(fieldNumber);
out.setFieldByNumber(fieldNumber, o);
Assert.assertEquals(o, out.getFieldByNumber(fieldNumber));
}
weatherData.add(out);
System.out.println(rec.toString());
System.out.println(out.toString());
Assert.assertEquals(rec.toString(), out.toString());
}
FitDataMessage recostructed = new FitDataMessage(weatherData);
byte[] output = recostructed.getOutgoingMessage();
Assert.assertArrayEquals(weatherDataMessage, output);
}
@Test
public void incomingFitDefinition() {
GarminSupport support = createSupport();
final CobsCoDec cobsCoDec = new CobsCoDec();
cobsCoDec.receivedBytes(GB.hexStringToByteArray("00021703AA13082088236905011303021F03033F0507045BC600"));//notificationcontrol message from log
final byte[] deCobs = cobsCoDec.retrieveMessage();
GFDIMessage pippo = GFDIMessage.parseIncoming(GB.hexStringToByteArray("1700AA13002088236905011300021F00033F0007045BC6"));//notificationcontrol message from log
final byte[] incomingDefinition = GB.hexStringToByteArray("5700931340000000000603048C0404860102840202840502840001004100000C000404028400010001010005010042000001000317048C1A048C01010A4300007F00060318070101000401000501000701000A010054CE");
FitDefinitionMessage fitDefinitionMessage = (FitDefinitionMessage) GFDIMessage.parseIncoming(incomingDefinition);
FitDefinitionStatusMessage ok_applied = new FitDefinitionStatusMessage(GFDIMessage.GarminMessage.FIT_DEFINITION, GFDIMessage.Status.ACK, FitDefinitionStatusMessage.FitDefinitionStatusCode.APPLIED, true);
Assert.assertArrayEquals(ok_applied.getOutgoingMessage(), fitDefinitionMessage.getAckBytestream());
final List<GBDeviceEvent> events = fitDefinitionMessage.getGBDeviceEvent();
Assert.assertEquals(1, events.size());
Assert.assertTrue((events.getFirst() instanceof IncomingFitDefinitionDeviceEvent));
final FitLocalMessageHandler fitLocalMessageHandler = new FitLocalMessageHandler(support, ((IncomingFitDefinitionDeviceEvent) events.getFirst()).getRecordDefinitions());
final byte[] incomingData = GB.hexStringToByteArray("2E00941302CA960015070000000703466F726572756E6E65722033350000000000000000000000000101010197F3");
FitDataMessage fitDataMessage = (FitDataMessage) GFDIMessage.parseIncoming(incomingData);
fitLocalMessageHandler.handle(fitDataMessage);
FitDataStatusMessage ok_applied_data = new FitDataStatusMessage(GFDIMessage.GarminMessage.FIT_DATA, GFDIMessage.Status.ACK, FitDataStatusMessage.FitDataStatusCode.APPLIED, true);
Assert.assertArrayEquals(ok_applied_data.getOutgoingMessage(), fitDataMessage.getAckBytestream());
//RecordData{CAPABILITIES, sports=7, connectivity_supported=352360138, unknown_26(UINT32Z/4)=7}
//RecordData{CONNECTIVITY, bluetooth_le_enabled=0, name=Forerunner 35, live_tracking_enabled=1, weather_conditions_enabled=1, auto_activity_upload_enabled=1, gps_ephemeris_download_enabled=1}
//forse va in conflitto con ``private void enableWeather() {``???
// final FitLocalMessageBuilder pippo = new FitLocalMessageBuilder(((IncomingFitDefinitionDeviceEvent) events.getFirst()).getRecordDefinitions());
// pippo.getDefinitions();
}
@Test
public void testBaseFields() {
@@ -538,4 +643,14 @@ public class GarminSupportTest extends TestBase {
}
}
}
private GarminSupport createSupport() {
return new GarminSupport() {
@Override
public BluetoothGattCharacteristic getCharacteristic(final UUID uuid) {
return new BluetoothGattCharacteristic(null, 0, 0);
}
};
}
}
@@ -23,7 +23,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.weather.WeatherMapper;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitLocalMessageBuilder;
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.fit.fieldDefinitions.FieldDefinitionWeatherAqi;