From c350f04fa9180f09b9b8a9e70d6ce2e1eff96b08 Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Thu, 22 Oct 2015 00:53:27 +0200 Subject: [PATCH] Make "Locate device" work with newer firmware and MI1A. (#136) Separate the different notification logic into distinct strategy classes. --- .../devices/miband/MiBandFWHelper.java | 7 +- .../devices/miband/VibrationProfile.java | 2 +- .../service/devices/miband/MiBandSupport.java | 138 ++++-------------- .../devices/miband/NotificationStrategy.java | 26 ++++ .../miband/V1NotificationStrategy.java | 102 +++++++++++++ .../miband/V2NotificationStrategy.java | 52 +++++++ 6 files changed, 214 insertions(+), 113 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java index c48a5bd76..c2d9cd0c4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java @@ -26,6 +26,11 @@ public class MiBandFWHelper { private final int firmwareVersionMinor = 1058; private final int firmwareVersionMajor = 1059; + /** + * Provides a different notification API which is also used on Mi1A devices. + */ + public static final int FW_16779790 = 16779790; + private final int[] whitelistedFirmwareVersion = { 16779534, // 1.0.9.14 tested by developer 16779547, //1.0.9.27 tested by developer @@ -34,7 +39,7 @@ public class MiBandFWHelper { 16779779, //1.0.10.3 reported on the wiki 16779782, //1.0.10.6 reported on the wikiew 16779787, //1.0.10.11 tested by developer - //16779790, //1.0.10.14 reported on the wiki (vibration does not work currently) + //FW_16779790, //1.0.10.14 reported on the wiki (vibration does not work currently) }; public MiBandFWHelper(Uri uri, Context context) throws IOException { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java index 406a89e94..136f3515e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java @@ -15,7 +15,7 @@ public class VibrationProfile { public static final String ID_RING = CONTEXT.getString(R.string.p_ring); public static final String ID_ALARM_CLOCK = CONTEXT.getString(R.string.p_alarm_clock); - public static VibrationProfile getProfile(String id, byte repeat) { + public static VibrationProfile getProfile(String id, short repeat) { if (ID_STACCATO.equals(id)) { return new VibrationProfile(id, new int[]{100, 0}, repeat); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index a0fa6c827..00ec89a98 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -9,6 +9,7 @@ import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.widget.Toast; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -144,30 +145,17 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return mDeviceInfo; } - private byte[] getDefaultNotification() { - final int vibrateTimes = 1; - final long vibrateDuration = 250l; - final int flashTimes = 1; - final int flashColour = 0xFFFFFFFF; - final int originalColour = 0xFFFFFFFF; - final long flashDuration = 250l; - - return getNotification(vibrateDuration, vibrateTimes, flashTimes, flashColour, originalColour, flashDuration); - } - - private void sendDefaultNotification(TransactionBuilder builder, short repeat, BtLEAction extraAction) { - BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); - LOG.info("Sending notification to MiBand: " + characteristic + " (" + repeat + " times)"); - byte[] defaultNotification = getDefaultNotification(); + private MiBandSupport sendDefaultNotification(TransactionBuilder builder, short repeat, BtLEAction extraAction) { + LOG.info("Sending notification to MiBand: (" + repeat + " times)"); + NotificationStrategy strategy = getNotificationStrategy(); for (short i = 0; i < repeat; i++) { - builder.write(characteristic, defaultNotification); - builder.add(extraAction); + strategy.sendDefaultNotification(builder, extraAction); } - builder.queue(getQueue()); + return this; } /** - * Sends a custom notification to the Mi Band. + * Adds a custom notification to the given transaction builder * * @param vibrationProfile specifies how and how often the Band shall vibrate. * @param flashTimes @@ -177,89 +165,24 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { * @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example. * @param builder */ - private void sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) { + private MiBandSupport sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) { + getNotificationStrategy().sendCustomNotification(vibrationProfile, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder); + LOG.info("Sending notification to MiBand"); + return this; + } - if(mDeviceInfo.getFirmwareVersion() >= 16779790) { - //use the new alert characteristic - BluetoothGattCharacteristic alert = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL); - for (short i = 0; i < vibrationProfile.getRepeat(); i++) { - int[] onOffSequence = vibrationProfile.getOnOffSequence(); - for (int j = 0; j < onOffSequence.length; j++) { - int on = onOffSequence[j]; - on = Math.min(500, on); // longer than 500ms is not possible - builder.write(alert, new byte[]{GattCharacteristic.MILD_ALERT}); //MILD_ALERT lights up GREEN leds, HIGH_ALERT lights up RED leds - builder.wait(on); - builder.write(alert, new byte[]{GattCharacteristic.NO_ALERT}); - - if (++j < onOffSequence.length) { - int off = Math.max(onOffSequence[j], 25); // wait at least 25ms - builder.wait(off); - } - - if (extraAction != null) { - builder.add(extraAction); - } - } - } - LOG.info("Sending notification to MiBand: " + alert); + private NotificationStrategy getNotificationStrategy() { + if (mDeviceInfo.getFirmwareVersion() < MiBandFWHelper.FW_16779790) { + return new V1NotificationStrategy(this); } else { - BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); - for (short i = 0; i < vibrationProfile.getRepeat(); i++) { - int[] onOffSequence = vibrationProfile.getOnOffSequence(); - for (int j = 0; j < onOffSequence.length; j++) { - int on = onOffSequence[j]; - on = Math.min(500, on); // longer than 500ms is not possible - builder.write(controlPoint, startVibrate); - builder.wait(on); - builder.write(controlPoint, stopVibrate); - - if (++j < onOffSequence.length) { - int off = Math.max(onOffSequence[j], 25); // wait at least 25ms - builder.wait(off); - } - - if (extraAction != null) { - builder.add(extraAction); - } - } - } - LOG.info("Sending notification to MiBand: " + controlPoint); + //use the new alert characteristic + return new V2NotificationStrategy(this); } - - builder.queue(getQueue()); } - private void sendCustomNotification(int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration, TransactionBuilder builder) { - BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); - int vDuration = Math.min(500, vibrateDuration); // longer than 500ms is not possible - for (int i = 0; i < vibrateTimes; i++) { - builder.write(controlPoint, startVibrate); - builder.wait(vDuration); - builder.write(controlPoint, stopVibrate); - if (pause > 0) { - builder.wait(pause); - } - } - - LOG.info("Sending notification to MiBand: " + controlPoint); - builder.queue(getQueue()); - } - - private static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 1}; - private static final byte[] stopVibrate = new byte[]{MiBandService.COMMAND_STOP_MOTOR_VIBRATE}; - private static final byte[] reboot = new byte[]{MiBandService.COMMAND_REBOOT}; - private static final byte[] startRealTimeStepsNotifications = new byte[]{MiBandService.COMMAND_SET_REALTIME_STEPS_NOTIFICATION, 1}; - private static final byte[] stopRealTimeStepsNotifications = new byte[]{MiBandService.COMMAND_SET_REALTIME_STEPS_NOTIFICATION, 0}; - - private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) { - byte[] vibrate = startVibrate; - byte r = 6; - byte g = 0; - byte b = 6; - boolean display = true; - // byte[] flashColor = new byte[]{ 14, r, g, b, display ? (byte) 1 : (byte) 0 }; - return vibrate; - } + static final byte[] reboot = new byte[]{MiBandService.COMMAND_REBOOT}; + static final byte[] startRealTimeStepsNotifications = new byte[]{MiBandService.COMMAND_SET_REALTIME_STEPS_NOTIFICATION, 1}; + static final byte[] stopRealTimeStepsNotifications = new byte[]{MiBandService.COMMAND_SET_REALTIME_STEPS_NOTIFICATION, 0}; /** * Part of device initialization process. Do not call manually. @@ -370,27 +293,19 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { try { TransactionBuilder builder = performInitialized(task); sendDefaultNotification(builder, repeat, extraAction); + builder.queue(getQueue()); } catch (IOException ex) { LOG.error("Unable to send notification to MI device", ex); } } -// private void performCustomNotification(String task, int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration) { -// try { -// TransactionBuilder builder = performInitialized(task); -// sendCustomNotification(vibrateDuration, vibrateTimes, pause, flashTimes, flashColour, originalColour, flashDuration, builder); -// } catch (IOException ex) { -// LOG.error("Unable to send notification to MI device", ex); -// } -// } - private void performPreferredNotification(String task, String notificationOrigin, BtLEAction extraAction) { try { TransactionBuilder builder = performInitialized(task); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); int vibrateDuration = getPreferredVibrateDuration(notificationOrigin, prefs); int vibratePause = getPreferredVibratePause(notificationOrigin, prefs); - int vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs); + short vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs); VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes); int flashTimes = getPreferredFlashCount(notificationOrigin, prefs); @@ -400,6 +315,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { sendCustomNotification(profile, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder); // sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder); + builder.queue(getQueue()); } catch (IOException ex) { LOG.error("Unable to send notification to MI device", ex); } @@ -425,17 +341,17 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return getNotificationPrefIntValue(VIBRATION_PAUSE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PAUSE); } - private int getPreferredVibrateCount(String notificationOrigin, SharedPreferences prefs) { - return getNotificationPrefIntValue(VIBRATION_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_COUNT); + private short getPreferredVibrateCount(String notificationOrigin, SharedPreferences prefs) { + return (short) Math.min(Short.MAX_VALUE, getNotificationPrefIntValue(VIBRATION_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_COUNT)); } private int getPreferredVibrateDuration(String notificationOrigin, SharedPreferences prefs) { return getNotificationPrefIntValue(VIBRATION_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_DURATION); } - private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, SharedPreferences prefs, int repeat) { + private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, SharedPreferences prefs, short repeat) { String profileId = getNotificationPrefStringValue(VIBRATION_PROFILE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PROFILE); - return VibrationProfile.getProfile(profileId, (byte) (repeat & 0xfff)); + return VibrationProfile.getProfile(profileId, repeat); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java new file mode 100644 index 000000000..34e538fb8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/NotificationStrategy.java @@ -0,0 +1,26 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.bluetooth.BluetoothGattCharacteristic; + +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; + +public interface NotificationStrategy { + public void sendDefaultNotification(TransactionBuilder builder, BtLEAction extraAction); + + /** + * Adds a custom notification to the given transaction builder + * + * @param vibrationProfile specifies how and how often the Band shall vibrate. + * @param flashTimes + * @param flashColour + * @param originalColour + * @param flashDuration + * @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example. + * @param builder + */ + public void sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java new file mode 100644 index 000000000..132ec182b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V1NotificationStrategy.java @@ -0,0 +1,102 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.bluetooth.BluetoothGattCharacteristic; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; + +public class V1NotificationStrategy implements NotificationStrategy { + private static final Logger LOG = LoggerFactory.getLogger(V1NotificationStrategy.class); + + static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 1}; + static final byte[] stopVibrate = new byte[]{MiBandService.COMMAND_STOP_MOTOR_VIBRATE}; + + private final MiBandSupport support; + + public V1NotificationStrategy(MiBandSupport support) { + this.support = support; + } + + @Override + public void sendDefaultNotification(TransactionBuilder builder, BtLEAction extraAction) { + BluetoothGattCharacteristic characteristic = support.getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); + builder.write(characteristic, getDefaultNotification()); + builder.add(extraAction); + } + + private byte[] getDefaultNotification() { + final int vibrateTimes = 1; + final long vibrateDuration = 250l; + final int flashTimes = 1; + final int flashColour = 0xFFFFFFFF; + final int originalColour = 0xFFFFFFFF; + final long flashDuration = 250l; + + return getNotification(vibrateDuration, vibrateTimes, flashTimes, flashColour, originalColour, flashDuration); + } + + private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) { + byte[] vibrate = startVibrate; + byte r = 6; + byte g = 0; + byte b = 6; + boolean display = true; + // byte[] flashColor = new byte[]{ 14, r, g, b, display ? (byte) 1 : (byte) 0 }; + return vibrate; + } + + /** + * Adds a custom notification to the given transaction builder + * + * @param vibrationProfile specifies how and how often the Band shall vibrate. + * @param flashTimes + * @param flashColour + * @param originalColour + * @param flashDuration + * @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example. + * @param builder + */ + public void sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) { + BluetoothGattCharacteristic controlPoint = support.getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); + for (short i = 0; i < vibrationProfile.getRepeat(); i++) { + int[] onOffSequence = vibrationProfile.getOnOffSequence(); + for (int j = 0; j < onOffSequence.length; j++) { + int on = onOffSequence[j]; + on = Math.min(500, on); // longer than 500ms is not possible + builder.write(controlPoint, startVibrate); + builder.wait(on); + builder.write(controlPoint, stopVibrate); + + if (++j < onOffSequence.length) { + int off = Math.max(onOffSequence[j], 25); // wait at least 25ms + builder.wait(off); + } + + if (extraAction != null) { + builder.add(extraAction); + } + } + } + } + +// private void sendCustomNotification(int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration, TransactionBuilder builder) { +// BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); +// int vDuration = Math.min(500, vibrateDuration); // longer than 500ms is not possible +// for (int i = 0; i < vibrateTimes; i++) { +// builder.write(controlPoint, startVibrate); +// builder.wait(vDuration); +// builder.write(controlPoint, stopVibrate); +// if (pause > 0) { +// builder.wait(pause); +// } +// } +// +// LOG.info("Sending notification to MiBand: " + controlPoint); +// builder.queue(getQueue()); +// } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java new file mode 100644 index 000000000..32ed1275f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/V2NotificationStrategy.java @@ -0,0 +1,52 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.bluetooth.BluetoothGattCharacteristic; + +import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; + +public class V2NotificationStrategy implements NotificationStrategy { + private final MiBandSupport support; + + public V2NotificationStrategy(MiBandSupport support) { + this.support = support; + } + + @Override + public void sendDefaultNotification(TransactionBuilder builder, BtLEAction extraAction) { + VibrationProfile profile = VibrationProfile.getProfile(VibrationProfile.ID_MEDIUM, (short) 3); + sendCustomNotification(profile, extraAction, builder); + } + + private void sendCustomNotification(VibrationProfile vibrationProfile, BtLEAction extraAction, TransactionBuilder builder) { + //use the new alert characteristic + BluetoothGattCharacteristic alert = support.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL); + for (short i = 0; i < vibrationProfile.getRepeat(); i++) { + int[] onOffSequence = vibrationProfile.getOnOffSequence(); + for (int j = 0; j < onOffSequence.length; j++) { + int on = onOffSequence[j]; + on = Math.min(500, on); // longer than 500ms is not possible + builder.write(alert, new byte[]{GattCharacteristic.MILD_ALERT}); //MILD_ALERT lights up GREEN leds, HIGH_ALERT lights up RED leds + builder.wait(on); + builder.write(alert, new byte[]{GattCharacteristic.NO_ALERT}); + + if (++j < onOffSequence.length) { + int off = Math.max(onOffSequence[j], 25); // wait at least 25ms + builder.wait(off); + } + + if (extraAction != null) { + builder.add(extraAction); + } + } + } + } + + @Override + public void sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) { + // all other parameters are unfortunately not supported anymore ;-( + sendCustomNotification(vibrationProfile, extraAction, builder); + } +}