Garmin: Add support for V1

This commit is contained in:
José Rebelo
2024-12-05 18:35:00 +00:00
parent ce126b805e
commit a6de977d50
4 changed files with 71 additions and 17 deletions
@@ -143,20 +143,26 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
protected TransactionBuilder initializeDevice(final TransactionBuilder builder) { protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
if (getSupportedServices().contains(CommunicatorV2.UUID_SERVICE_GARMIN_ML_GFDI)) {
communicator = new CommunicatorV2(this);
} else if (getSupportedServices().contains(CommunicatorV1.UUID_SERVICE_GARMIN_GFDI)) {
communicator = new CommunicatorV1(this);
} else {
LOG.warn("Failed to find a known Garmin service");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
return builder;
}
if (getDevicePrefs().getBoolean(PREF_ALLOW_HIGH_MTU, true)) { if (getDevicePrefs().getBoolean(PREF_ALLOW_HIGH_MTU, true)) {
builder.requestMtu(515); builder.requestMtu(515);
} }
final CommunicatorV2 communicatorV2 = new CommunicatorV2(this);
if (communicatorV2.initializeDevice(builder)) {
communicator = communicatorV2;
} else {
// V2 did not manage to initialize, attempt V1
final CommunicatorV1 communicatorV1 = new CommunicatorV1(this);
if (!communicatorV1.initializeDevice(builder)) {
// Neither V1 nor V2 worked, not a Garmin device?
LOG.warn("Failed to find a known Garmin service");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.NOT_CONNECTED, getContext()));
return builder;
}
communicator = communicatorV1;
}
communicator.initializeDevice(builder); communicator.initializeDevice(builder);
return builder; return builder;
@@ -10,7 +10,7 @@ public interface ICommunicator {
void onMtuChanged(final int mtu); void onMtuChanged(final int mtu);
void initializeDevice(TransactionBuilder builder); boolean initializeDevice(TransactionBuilder builder);
boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic); boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
@@ -6,40 +6,88 @@ import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.CobsCoDec;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.ICommunicator; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.communicator.ICommunicator;
public class CommunicatorV1 implements ICommunicator { public class CommunicatorV1 implements ICommunicator {
private static final Logger LOG = LoggerFactory.getLogger(CommunicatorV1.class); private static final Logger LOG = LoggerFactory.getLogger(CommunicatorV1.class);
public static final UUID UUID_SERVICE_GARMIN_GFDI = UUID.fromString("6A4E2401-667B-11E3-949A-0800200C9A66"); public static final UUID UUID_SERVICE_GARMIN_GFDI = UUID.fromString("6A4E2401-667B-11E3-949A-0800200C9A66");
public static final UUID UUID_CHARACTERISTIC_GARMIN_GFDI_SEND = UUID.fromString("6A4E4C80-667B-11E3-949A-0800200C9A66");
public static final UUID UUID_CHARACTERISTIC_GARMIN_GFDI_RECEIVE = UUID.fromString("6A4ECD28-667B-11E3-949A-0800200C9A66");
private BluetoothGattCharacteristic characteristicSend;
private BluetoothGattCharacteristic characteristicReceive;
private final GarminSupport mSupport; private final GarminSupport mSupport;
public final CobsCoDec cobsCoDec;
public int maxWriteSize = 20;
public CommunicatorV1(final GarminSupport garminSupport) { public CommunicatorV1(final GarminSupport garminSupport) {
this.mSupport = garminSupport; this.mSupport = garminSupport;
this.cobsCoDec = new CobsCoDec();
} }
@Override @Override
public void onMtuChanged(final int mtu) { public void onMtuChanged(final int mtu) {
maxWriteSize = mtu - 3;
} }
@Override @Override
public void initializeDevice(final TransactionBuilder builder) { public boolean initializeDevice(final TransactionBuilder builder) {
LOG.error("Initialization is not implemented for V1"); characteristicSend = mSupport.getCharacteristic(UUID_CHARACTERISTIC_GARMIN_GFDI_SEND);
characteristicReceive = mSupport.getCharacteristic(UUID_CHARACTERISTIC_GARMIN_GFDI_RECEIVE);
if (characteristicSend == null || characteristicReceive == null) {
LOG.warn("Failed to find V1 GFDI characteristics");
return false;
}
builder.notify(characteristicReceive, true);
LOG.debug("Initializing as Garmin V1");
return true;
} }
@Override @Override
public void sendMessage(final String taskName, final byte[] message) { public void sendMessage(final String taskName, final byte[] message) {
if (null == message)
return;
final byte[] payload = cobsCoDec.encode(message);
final TransactionBuilder builder = new TransactionBuilder(taskName);
int remainingBytes = payload.length;
if (remainingBytes > maxWriteSize - 1) {
int position = 0;
while (remainingBytes > 0) {
final byte[] fragment = Arrays.copyOfRange(payload, position, position + Math.min(remainingBytes, maxWriteSize - 1));
builder.write(characteristicSend, fragment);
position += fragment.length;
remainingBytes -= fragment.length;
}
} else {
builder.write(characteristicSend, payload);
}
builder.queue(this.mSupport.getQueue());
} }
@Override @Override
public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { public boolean onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
if (characteristic.getUuid().equals(characteristicReceive.getUuid())) {
this.cobsCoDec.receivedBytes(characteristic.getValue());
this.mSupport.onMessage(this.cobsCoDec.retrieveMessage());
return true;
}
return false; return false;
} }
@@ -77,7 +77,7 @@ public class CommunicatorV2 implements ICommunicator {
} }
@Override @Override
public void initializeDevice(final TransactionBuilder builder) { public boolean initializeDevice(final TransactionBuilder builder) {
// Iterate through the known ML characteristics until we find a known pair // Iterate through the known ML characteristics until we find a known pair
// send characteristic = read characteristic + 0x10 (eg. 2810 / 2820) // send characteristic = read characteristic + 0x10 (eg. 2810 / 2820)
for (int i = 0x2810; i <= 0x2814; i++) { for (int i = 0x2810; i <= 0x2814; i++) {
@@ -90,13 +90,13 @@ public class CommunicatorV2 implements ICommunicator {
builder.notify(characteristicReceive, true); builder.notify(characteristicReceive, true);
builder.write(characteristicSend, closeAllServices()); builder.write(characteristicSend, closeAllServices());
return; return true;
} }
} }
LOG.warn("Failed to find any known ML characteristics"); LOG.warn("Failed to find any known ML characteristics");
builder.add(new SetDeviceStateAction(mSupport.getDevice(), GBDevice.State.NOT_CONNECTED, mSupport.getContext())); return false;
} }
@Override @Override