diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/LEDColors.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/LEDColors.java new file mode 100644 index 00000000..ffd9691b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/LEDColors.java @@ -0,0 +1,27 @@ +package nodomain.freeyourgadget.gadgetbridge.miband; + +public class LEDColors { + + public static final int toInt(byte r, byte g, byte b) { + int result = ((int) r << 16); + result |= ((int) g << 8); + result |= ((int) b); + return result; + } + + public static final byte[] toBytes(int rgb) { + byte r = (byte) ((rgb >> 16) & 0x0000ff); + byte g = (byte) ((rgb >> 8) & 0x0000ff); + byte b = (byte) (rgb & 0x0000ff); + return new byte[] { r, g, b }; + } + + public static final int RED = toInt((byte) 6, (byte) 0, (byte) 0); + public static final int GREEN = toInt((byte) 0, (byte) 6, (byte) 6); + public static final int BLUE = toInt((byte) 0, (byte) 0, (byte) 6); + public static final int CYAN = toInt((byte) 0, (byte) 6, (byte) 6); + public static final int YELLOW = toInt((byte) 6, (byte) 6, (byte) 0); + public static final int MAGENTA = toInt((byte) 6, (byte) 0, (byte) 6); + public static final int OFF = toInt((byte) 0, (byte) 0, (byte) 0); + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/LEDProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/LEDProfile.java new file mode 100644 index 00000000..f01aeb2a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/LEDProfile.java @@ -0,0 +1,84 @@ +package nodomain.freeyourgadget.gadgetbridge.miband; + +import android.content.Context; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; + +public class LEDProfile { + public static final Context CONTEXT = GBApplication.getContext(); + + + public static final String ID_STACCATO = CONTEXT.getString(R.string.p_staccato); + public static final String ID_SHORT = CONTEXT.getString(R.string.p_short); + public static final String ID_MEDIUM = CONTEXT.getString(R.string.p_medium); + public static final String ID_LONG = CONTEXT.getString(R.string.p_long); + public static final String ID_WATERDROP = CONTEXT.getString(R.string.p_waterdrop); + 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 LEDProfile getProfile(String id, short repeat) { + if (ID_STACCATO.equals(id)) { + return new LEDProfile(id, new int[]{100, 0}, repeat, new int[] { LEDColors.YELLOW, 100, 0, LEDColors.RED, 100, 0 }, repeat); + } + if (ID_SHORT.equals(id)) { + return new LEDProfile(id, new int[]{200, 200}, repeat, new int[] { LEDColors.GREEN, 200, 200 }, repeat); + } + if (ID_LONG.equals(id)) { + return new LEDProfile(id, new int[]{500, 1000}, repeat, new int[] { LEDColors.MAGENTA, 500, 1000 }, repeat); + } + if (ID_WATERDROP.equals(id)) { + return new LEDProfile(id, new int[]{100, 1500}, repeat, new int[] { LEDColors.BLUE, 100, 1500 }, repeat); + } + if (ID_RING.equals(id)) { + return new LEDProfile(id, new int[]{300, 200, 600, 2000}, repeat, new int[] { LEDColors.CYAN, 300, 200, LEDColors.MAGENTA, 600, 2000 }, repeat); + } + if (ID_ALARM_CLOCK.equals(id)) { + return new LEDProfile(id, new int[]{30, 35, 30, 35, 30, 35, 30, 800}, repeat, new int[] {LEDColors.BLUE, 30, 35, LEDColors.CYAN, 30, 35, LEDColors.BLUE, 30, 35, LEDColors.CYAN, 30, 800 }, repeat); + } + // medium + return new LEDProfile(id, new int[]{300, 600}, repeat, new int[]{ LEDColors.YELLOW, 300, 600 }, repeat); + } + + private final String id; + + private final int[] vibrationOnOffSequence; + private int[] colorOnOffSequence; + private short vibrationRepeat; + private short colorRepeat; + private boolean pulsate; + + /** + * Creates a new profile instance. + * + * @param id the ID, used as preference key. + * @param vibrationOnOffSequence a sequence of alternating on and off durations, in milliseconds + * @param vibrationRepeat how often the sequence shall be repeated + */ + public LEDProfile(String id, int[] vibrationOnOffSequence, short vibrationRepeat, int[] colorOnOffSequence, short colorRepeat) { + this.id = id; + this.vibrationRepeat = vibrationRepeat; + this.vibrationOnOffSequence = vibrationOnOffSequence; + this.colorOnOffSequence = colorOnOffSequence; + this.colorRepeat = colorRepeat; + } + + public String getId() { + return id; + } + + public int[] getVibrationOnOffSequence() { + return vibrationOnOffSequence; + } + + public int[] getColorOnOffSequence() { + return colorOnOffSequence; + } + + public short getVibrationRepeat() { + return vibrationRepeat; + } + public short getColorRepeat() { + return colorRepeat; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java index e9500f36..3726c050 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandService.java @@ -137,6 +137,7 @@ public class MiBandService { public static final byte COMMAND_SET_TIMER = 0x4; + public static final byte COMMAND_SET_LED_COLOR = 0x0e; /* diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java index 5c980ef6..c6050968 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java @@ -59,7 +59,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(MiBandSupport.class); //temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 bytes) - private static final int activityDataHolderSize = 60 * 24; // 8h + private static final int activityDataHolderSize = 60; // 20min private byte[] activityDataHolder = new byte[activityDataHolderSize]; //index of the buffer above private int activityDataHolderProgress = 0; @@ -154,27 +154,52 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { * Sends a custom notification to the Mi Band. * * @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 */ - private void sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) { + private void sendCustomNotification(VibrationProfile vibrationProfile, BtLEAction extraAction, TransactionBuilder builder) { 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); + int[] vibrationOnOffSequence = vibrationProfile.getVibrationOnOffSequence(); + // first vibration, then flash LEDs + for (short i = 0; i < vibrationProfile.getVibrationRepeat(); i++) { + for (int j = 0; j < vibrationOnOffSequence.length; j++) { + if (j < vibrationOnOffSequence.length) { + int on = vibrationOnOffSequence[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 < vibrationOnOffSequence.length) { + int off = Math.max(vibrationOnOffSequence[j], 25); // wait at least 25ms + builder.wait(off); + } + } + + if (extraAction != null) { + builder.add(extraAction); + } + } + } + int[] colorOnOffSequence = vibrationProfile.getColorOnOffSequence(); + for (short i = 0; i < vibrationProfile.getColorRepeat(); i++) { + for (int j = 0; j < colorOnOffSequence.length; j++) { + int color = colorOnOffSequence[j]; + builder.write(controlPoint, getLEDFlash(color)); + + if (++j < colorOnOffSequence.length) { + int on = colorOnOffSequence[j]; + on = Math.min(500, on); // longer than 500ms is not possible + builder.wait(on); + + if (++j < colorOnOffSequence.length) { + int off = colorOnOffSequence[j]; + if (off >= 300) { // shorter periods don't seem to work + builder.write(controlPoint, getLEDFlash(LEDColors.OFF)); + builder.wait(off); + } + } } if (extraAction != null) { @@ -203,7 +228,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { builder.queue(getQueue()); } - private static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 1}; + private static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 0}; 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[] fetch = new byte[]{MiBandService.COMMAND_FETCH_DATA}; @@ -218,6 +243,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return vibrate; } + private byte[] getLEDFlash(int color) { + byte[] rgb = LEDColors.toBytes(color); + return new byte[] { MiBandService.COMMAND_SET_LED_COLOR, rgb[0], rgb[1], rgb[2], 0x1 }; + } + /** * Part of device initialization process. Do not call manually. * @@ -282,12 +312,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { int vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs); VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes); - int flashTimes = getPreferredFlashCount(notificationOrigin, prefs); - int flashColour = getPreferredFlashColour(notificationOrigin, prefs); - int originalColour = getPreferredOriginalColour(notificationOrigin, prefs); - int flashDuration = getPreferredFlashDuration(notificationOrigin, prefs); - - sendCustomNotification(profile, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder); + sendCustomNotification(profile, extraAction, builder); // sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder); } catch (IOException ex) { LOG.error("Unable to send notification to MI device", ex); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java index 7aff51c1..ad8eb252 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java @@ -7,6 +7,8 @@ import nodomain.freeyourgadget.gadgetbridge.R; public class VibrationProfile { public static final Context CONTEXT = GBApplication.getContext(); + + public static final String ID_STACCATO = CONTEXT.getString(R.string.p_staccato); public static final String ID_SHORT = CONTEXT.getString(R.string.p_short); public static final String ID_MEDIUM = CONTEXT.getString(R.string.p_medium); @@ -15,56 +17,68 @@ 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); + return new VibrationProfile(id, new int[]{100, 0}, repeat, new int[] { LEDColors.YELLOW, 100, 0, LEDColors.RED, 100, 0 }, repeat); } if (ID_SHORT.equals(id)) { - return new VibrationProfile(id, new int[]{200, 200}, repeat); + return new VibrationProfile(id, new int[]{200, 200}, repeat, new int[] { LEDColors.GREEN, 200, 200 }, repeat); } if (ID_LONG.equals(id)) { - return new VibrationProfile(id, new int[]{500, 1000}, repeat); + return new VibrationProfile(id, new int[]{500, 1000}, repeat, new int[] { LEDColors.MAGENTA, 500, 1000 }, repeat); } if (ID_WATERDROP.equals(id)) { - return new VibrationProfile(id, new int[]{100, 1500}, repeat); + return new VibrationProfile(id, new int[]{100, 1500}, repeat, new int[] { LEDColors.BLUE, 100, 1500 }, repeat); } if (ID_RING.equals(id)) { - return new VibrationProfile(id, new int[]{300, 200, 600, 2000}, repeat); + return new VibrationProfile(id, new int[]{300, 200, 600, 2000}, repeat, new int[] { LEDColors.CYAN, 300, 200, LEDColors.MAGENTA, 600, 2000 }, repeat); } if (ID_ALARM_CLOCK.equals(id)) { - return new VibrationProfile(id, new int[]{30, 35, 30, 35, 30, 35, 30, 800}, repeat); + return new VibrationProfile(id, new int[]{30, 35, 30, 35, 30, 35, 30, 800}, repeat, new int[] {LEDColors.BLUE, 30, 35, LEDColors.CYAN, 30, 35, LEDColors.BLUE, 30, 35, LEDColors.CYAN, 30, 800 }, repeat); } // medium - return new VibrationProfile(id, new int[]{300, 600}, repeat); + return new VibrationProfile(id, new int[]{300, 600}, repeat, new int[]{ LEDColors.YELLOW, 300, 600 }, repeat); } private final String id; - private int[] onOffSequence; - private short repeat; + private final int[] vibrationOnOffSequence; + private int[] colorOnOffSequence; + private short vibrationRepeat; + private short colorRepeat; + private boolean pulsate; /** * Creates a new profile instance. * * @param id the ID, used as preference key. - * @param onOffSequence a sequence of alternating on and off durations, in milliseconds - * @param repeat how ofoften the sequence shall be repeated + * @param vibrationOnOffSequence a sequence of alternating on and off durations, in milliseconds + * @param vibrationRepeat how often the sequence shall be repeated */ - public VibrationProfile(String id, int[] onOffSequence, short repeat) { + public VibrationProfile(String id, int[] vibrationOnOffSequence, short vibrationRepeat, int[] colorOnOffSequence, short colorRepeat) { this.id = id; - this.repeat = repeat; - this.onOffSequence = onOffSequence; + this.vibrationRepeat = vibrationRepeat; + this.vibrationOnOffSequence = vibrationOnOffSequence; + this.colorOnOffSequence = colorOnOffSequence; + this.colorRepeat = colorRepeat; } public String getId() { return id; } - public int[] getOnOffSequence() { - return onOffSequence; + public int[] getVibrationOnOffSequence() { + return vibrationOnOffSequence; } - public short getRepeat() { - return repeat; + public int[] getColorOnOffSequence() { + return colorOnOffSequence; + } + + public short getVibrationRepeat() { + return vibrationRepeat; + } + public short getColorRepeat() { + return colorRepeat; } }