diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index f27a8290b..751b2790f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -7,10 +7,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class); + + @Override + public boolean supports(GBDevice device) { + return getDeviceType().equals(device.getType()); + } + + @Override + public GBDevice createDevice(GBDeviceCandidate candidate) { + return new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), getDeviceType()); + } + public boolean allowFetchActivityData(GBDevice device) { return device.isInitialized() && !device.isBusy() && supportsActivityDataFetching(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java index e8161d064..c0f9d6d62 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -1,6 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices; import android.app.Activity; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.net.Uri; @@ -40,6 +41,8 @@ public interface DeviceCoordinator { */ boolean supports(GBDevice device); + GBDevice createDevice(GBDeviceCandidate candidate); + /** * Returns the kind of device type this coordinator supports. * diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java index 764d3eb53..9116784fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java @@ -76,11 +76,6 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator { return false; } - @Override - public boolean supports(GBDevice device) { - return getDeviceType().equals(device.getType()); - } - @Override public DeviceType getDeviceType() { return DeviceType.UNKNOWN; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java new file mode 100644 index 000000000..fb680b3b6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java @@ -0,0 +1,42 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.miband; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.net.Uri; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; + +public class MiBand2Coordinator extends MiBandCoordinator { + private static final Logger LOG = LoggerFactory.getLogger(MiBand2Coordinator.class); + + @Override + public DeviceType getDeviceType() { + return DeviceType.MIBAND2; + } + + @Override + public boolean supports(GBDeviceCandidate candidate) { + // and a heuristic + try { + BluetoothDevice device = candidate.getDevice(); + if (isHealthWearable(device)) { + String name = device.getName(); + return name != null && name.equalsIgnoreCase(MiBandConst.MI_BAND2_NAME); + } + } catch (Exception ex) { + LOG.error("unable to check device support", ex); + } + return false; + + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; // not supported at the moment + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java index f73f6ad09..ddaab80e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java @@ -13,8 +13,19 @@ public class MiBand2Service { public static final UUID UUID_SERVICE_MIBAND2_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE1")); public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString(String.format(BASE_UUID, "180D")); public static final UUID UUID_SERVICE_WEIGHT_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700"); - public static final UUID UUID_UNKNOQN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC1 = UUID.fromString("00000001-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC2 = UUID.fromString("00000002-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC3 = UUID.fromString("00000003-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC4 = UUID.fromString("00000004-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC5 = UUID.fromString("00000005-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC6 = UUID.fromString("00000006-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC7 = UUID.fromString("00000007-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC10 = UUID.fromString("00000010-0000-3512-2118-0009af100700"); + // set metric distance + // set 12 hour time mode // public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01")); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java index 8dca9b5f2..267d9d66f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java @@ -25,6 +25,7 @@ public final class MiBandConst { public static final String ORIGIN_PEBBLEMSG = "pebblemsg"; public static final String ORIGIN_GENERIC = "generic"; public static final String MI_GENERAL_NAME_PREFIX = "MI"; + public static final String MI_BAND2_NAME = "MI Band 2"; public static final String MI_1 = "1"; public static final String MI_1A = "1A"; public static final String MI_1S = "1S"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java index 910c80f5b..667807789 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java @@ -35,7 +35,8 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator { || macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1S)) { return true; } - if (candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND_SERVICE)) { + if (candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND_SERVICE) + && !candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE)) { return true; } // and a heuristic @@ -51,11 +52,6 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator { return false; } - @Override - public boolean supports(GBDevice device) { - return getDeviceType().equals(device.getType()); - } - @Override public DeviceType getDeviceType() { return DeviceType.MIBAND; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 9b9c6eb22..35054631e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -27,11 +27,6 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator { return name != null && name.startsWith("Pebble"); } - @Override - public boolean supports(GBDevice device) { - return getDeviceType().equals(device.getType()); - } - @Override public DeviceType getDeviceType() { return DeviceType.PEBBLE; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java index 5e1914bd7..ad36def4b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java @@ -46,10 +46,21 @@ public class BLETypeConversions { fromUint8(timestamp.get(Calendar.MINUTE)), fromUint8(timestamp.get(Calendar.SECOND)), dayOfWeekToRawBytes(timestamp), - 0 // fractions256 (not set) + 0, // fractions256 (not set) + // 0 (DST offset?) Mi2 + // k (tz) Mi2 }; } + private static int getMiBand2TimeZone(int rawOffset) { + int offsetMinutes = rawOffset / 1000 / 60; + rawOffset = offsetMinutes < 0 ? -1 : 1; + offsetMinutes = Math.abs(offsetMinutes); + int offsetHours = offsetMinutes / 60; + rawOffset *= offsetMinutes % 60 / 15 + offsetHours * 4; + return rawOffset; + } + private static byte dayOfWeekToRawBytes(Calendar cal) { int calValue = cal.get(Calendar.DAY_OF_WEEK); switch (calValue) { @@ -139,8 +150,8 @@ public class BLETypeConversions { * @return sint8 value from -48..+56 */ public static byte mapTimeZone(TimeZone timeZone) { - int utcOffsetInMinutes = (timeZone.getRawOffset() / (1000 * 60 * 60)); - return (byte) (utcOffsetInMinutes * 4); + int utcOffsetInHours = (timeZone.getRawOffset() / (1000 * 60 * 60)); + return (byte) (utcOffsetInHours * 4); } /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java index e7e56ea65..b312249fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/deviceinfo/DeviceInfoProfile.java @@ -142,9 +142,15 @@ public class DeviceInfoProfile extends Abst } private void handlePnpId(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - String pnpId = characteristic.getStringValue(0); - deviceInfo.setPnpId(pnpId); - notify(createIntent(deviceInfo)); + byte[] value = characteristic.getValue(); + if (value.length == 7) { +// int vendorSource +// +// deviceInfo.setPnpId(pnpId); + notify(createIntent(deviceInfo)); + } else { + // TODO: LOG warning + } } private Intent createIntent(DeviceInfo deviceInfo) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java index d921ee5ce..eb21aad6b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java @@ -26,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter; @@ -45,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; @@ -147,6 +149,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { // this is apparently not needed anymore, and actually causes problems when bonding is not used/does not work // so we simply not use the UUID_PAIR characteristic. // .pair(builder) + .testInit(builder) .requestDeviceInfo(builder) .requestBatteryInfo(builder); // .sendUserInfo(builder) @@ -162,6 +165,38 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { return builder; } + private MiBand2Support testInit(TransactionBuilder builder) { + builder.read(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC6)); // example read value: 0f6200e0070804072b2c20e00708040625372064 + builder.read(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC7)); // example read value: 0019000000 + setCurrentTimeWithService(builder); + builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4), new byte[] { 0x01, 0x01, (byte) 0xe0, 0x07, 0x07, 0x17, 0x15, 0x04, 0x00, 0x04 }); + builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4), new byte[] { 0x02 }); + builder.read(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC6)); // probably superfluous + builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC8), new byte[] { 0x20, 0x00, 0x00, 0x02 }); + + return this; + } + +// private MiBand2Support maybeAuth(TransactionBuilder builder) { +// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x20, 0x00}); +// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2}); +// return this; +// } + + private MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) { + GregorianCalendar now = BLETypeConversions.createCalendar(); + byte[] bytes = BLETypeConversions.calendarToRawBytes(now, true); + byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(now.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone +// byte[] tail = new byte[] { 0x2 }; // reason + byte[] all = BLETypeConversions.join(bytes, tail); + builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), all); +// byte[] localtime = BLETypeConversions.calendarToLocalTimeBytes(now); +// builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_LOCAL_TIME_INFORMATION), localtime); +// builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), new byte[] {0x2, 0x00}); +// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2}); + return this; + } + private MiBand2Support readDate(TransactionBuilder builder) { // NAVL // builder.read(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DATE_TIME)); @@ -201,6 +236,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { // TODO: tear down the notifications on quit private MiBand2Support enableNotifications(TransactionBuilder builder, boolean enable) { builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable); + builder.notify(getCharacteristic(GattService.UUID_SERVICE_CURRENT_TIME), enable); return this; } @@ -844,6 +880,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { } else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) { handleHeartrate(characteristic.getValue()); return true; +// } else if (MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0.equals(characteristicUUID)) { +// handleUnknownCharacteristic(characteristic.getValue()); +// return true; } else { LOG.info("Unhandled characteristic changed: " + characteristicUUID); logMessageContent(characteristic.getValue()); @@ -851,6 +890,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { return false; } + private void handleUnknownCharacteristic(byte[] value) { + + } + @Override public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 21addeea8..510eaa8d9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -17,6 +17,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator; @@ -113,11 +114,11 @@ public class DeviceHelper { GBDeviceCandidate candidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN); if (coordinator != null && coordinator.supports(candidate)) { - return new GBDevice(device.getAddress(), candidate.getName(), coordinator.getDeviceType()); + return coordinator.createDevice(candidate); } for (DeviceCoordinator coordinator : getAllCoordinators()) { if (coordinator.supports(candidate)) { - return new GBDevice(device.getAddress(), candidate.getName(), coordinator.getDeviceType()); + coordinator.createDevice(candidate); } } return null; @@ -162,6 +163,7 @@ public class DeviceHelper { private List createCoordinators() { List result = new ArrayList<>(2); + result.add(new MiBand2Coordinator()); // Note: MiBand2 must come before MiBand because detection is hacky, atm result.add(new MiBandCoordinator()); result.add(new PebbleCoordinator()); return result;