WIP: support for different kind of LED notifications

Difficulty is: LED and "normal" notifications interfere, i.e. a normal
vibration buzz will stop an LED notification. So when we want to combine
them, we have to let them finish.
led-profile
cpfeiffer 2015-08-07 21:36:32 +02:00
parent f36caafc54
commit edcf54e55d
5 changed files with 194 additions and 43 deletions

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -137,6 +137,7 @@ public class MiBandService {
public static final byte COMMAND_SET_TIMER = 0x4;
public static final byte COMMAND_SET_LED_COLOR = 0x0e;
/*

View File

@ -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);

View File

@ -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;
}
}