mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 09:01:55 +01:00
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.
This commit is contained in:
parent
f36caafc54
commit
edcf54e55d
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -137,6 +137,7 @@ public class MiBandService {
|
||||
|
||||
public static final byte COMMAND_SET_TIMER = 0x4;
|
||||
|
||||
public static final byte COMMAND_SET_LED_COLOR = 0x0e;
|
||||
/*
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user