mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-26 02:25:50 +01:00
WIP for fetching activity data
This probably affects #44 and #45 Thanks go to Daniele Gobbetti <daniele@gobbetti.name>!
This commit is contained in:
parent
6ea9537d38
commit
f004b7b11c
@ -53,6 +53,10 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
}
|
||||
}
|
||||
|
||||
protected TransactionBuilder createTransactionBuilder(String taskName) {
|
||||
return new TransactionBuilder(taskName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send commands like this to the device:
|
||||
* <p>
|
||||
@ -71,10 +75,10 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
}
|
||||
if (!isInitialized()) {
|
||||
// first, add a transaction that performs device initialization
|
||||
TransactionBuilder builder = new TransactionBuilder("Initialize device");
|
||||
TransactionBuilder builder = createTransactionBuilder("Initialize device");
|
||||
initializeDevice(builder).queue(getQueue());
|
||||
}
|
||||
return new TransactionBuilder(taskName);
|
||||
return createTransactionBuilder(taskName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +160,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
@Override
|
||||
public void onServicesDiscovered(BluetoothGatt gatt) {
|
||||
gattServicesDiscovered(getQueue().getSupportedGattServices());
|
||||
initializeDevice(new TransactionBuilder("Initializing device")).queue(getQueue());
|
||||
initializeDevice(createTransactionBuilder("Initializing device")).queue(getQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,37 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.btle;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.miband.MiBandService;
|
||||
|
||||
/**
|
||||
* Enables or disables notifications for a given GATT characteristic.
|
||||
* The result will be made available asynchronously through the
|
||||
* {@link BluetoothGattCallback}.
|
||||
*/
|
||||
public class NotifyAction extends BtLEAction {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TransactionBuilder.class);
|
||||
protected final boolean enableFlag;
|
||||
|
||||
public NotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) {
|
||||
super(characteristic);
|
||||
enableFlag = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(BluetoothGatt gatt) {
|
||||
return gatt.setCharacteristicNotification(getCharacteristic(), enableFlag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expectsResult() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.miband.MiBandNotifyAction;
|
||||
|
||||
public class TransactionBuilder {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TransactionBuilder.class);
|
||||
|
||||
@ -32,6 +34,19 @@ public class TransactionBuilder {
|
||||
return add(action);
|
||||
}
|
||||
|
||||
public TransactionBuilder notify(BluetoothGattCharacteristic characteristic, boolean enable) {
|
||||
if (characteristic == null) {
|
||||
LOG.warn("Unable to notify characteristic: null");
|
||||
return this;
|
||||
}
|
||||
NotifyAction action = createNotifyAction(characteristic, enable);
|
||||
return add(action);
|
||||
}
|
||||
|
||||
protected NotifyAction createNotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) {
|
||||
return new NotifyAction(characteristic, enable);
|
||||
}
|
||||
|
||||
public TransactionBuilder wait(int millis) {
|
||||
WaitAction action = new WaitAction(millis);
|
||||
return add(action);
|
||||
|
@ -0,0 +1,55 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.miband;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.BtLEAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.NotifyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.miband.MiBandService;
|
||||
|
||||
/**
|
||||
* Enables or disables notifications for a given GATT characteristic.
|
||||
* The result will be made available asynchronously through the
|
||||
* {@link BluetoothGattCallback}.
|
||||
*
|
||||
* This class is Mi Band specific.
|
||||
*/
|
||||
public class MiBandNotifyAction extends NotifyAction {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TransactionBuilder.class);
|
||||
|
||||
public MiBandNotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) {
|
||||
super(characteristic, enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(BluetoothGatt gatt) {
|
||||
boolean result = super.run(gatt);
|
||||
if (result) {
|
||||
BluetoothGattDescriptor sleepDescriptor = getCharacteristic().getDescriptor(MiBandService.UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION);
|
||||
if (sleepDescriptor != null) {
|
||||
int properties = getCharacteristic().getProperties();
|
||||
if ((properties & 0x10) > 0) {
|
||||
LOG.info("properties & 0x10 > 0");
|
||||
sleepDescriptor.setValue(enableFlag ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
|
||||
result = gatt.writeDescriptor(sleepDescriptor);
|
||||
} else if ((properties & 0x20) > 0){
|
||||
LOG.info("properties & 0x20 > 0");
|
||||
sleepDescriptor.setValue(enableFlag ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
|
||||
result = gatt.writeDescriptor(sleepDescriptor);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("sleep descriptor null");
|
||||
}
|
||||
} else {
|
||||
LOG.error("Unable to enable notification for " + getCharacteristic().getUuid());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import java.util.UUID;
|
||||
|
||||
public class MiBandService {
|
||||
|
||||
|
||||
public static final String MAC_ADDRESS_FILTER = "88:0F:10";
|
||||
|
||||
public static final String BASE_UUID = "0000%s-0000-1000-8000-00805f9b34fb";
|
||||
@ -42,6 +43,10 @@ public class MiBandService {
|
||||
|
||||
public static final UUID UUID_CHARACTERISTIC_PAIR = UUID.fromString(String.format(BASE_UUID, "FF0F"));
|
||||
|
||||
public static final UUID UUID_DESCRIPTOR_CHARACTERISTIC_USER_CONFIGURATION = UUID.fromString(String.format(BASE_UUID, "2901"));
|
||||
|
||||
public static final UUID UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION = UUID.fromString(String.format(BASE_UUID, "2902"));
|
||||
|
||||
public static final byte ALIAS_LEN = 0xa;
|
||||
|
||||
public static final byte NOTIFY_AUTHENTICATION_FAILED = 0x6;
|
||||
@ -126,9 +131,9 @@ public class MiBandService {
|
||||
|
||||
public static final byte COMMAND_STOP_MOTOR_VIBRATE = 0x13;
|
||||
|
||||
public static final byte COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xa;
|
||||
/*
|
||||
|
||||
public static final COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xat;
|
||||
|
||||
public static final byte COMMAND_FACTORY_RESET = 0x9t;
|
||||
|
||||
|
@ -11,8 +11,11 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State;
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport;
|
||||
@ -47,10 +50,22 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
pair(builder).sendUserInfo(builder).setCurrentTime(builder).requestBatteryInfo(builder);
|
||||
pair(builder).sendUserInfo(builder).setCurrentTime(builder).requestBatteryInfo(builder).enableNotifications(builder, true);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
// TODO: tear down the notifications on quit
|
||||
private MiBandSupport enableNotifications(TransactionBuilder builder, boolean enable) {
|
||||
builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable)
|
||||
.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
|
||||
.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA),enable)
|
||||
.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_BATTERY), enable)
|
||||
.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
@ -101,6 +116,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
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[] fetch = new byte[]{6};
|
||||
|
||||
private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) {
|
||||
byte[] vibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, (byte) 1};
|
||||
@ -308,13 +324,20 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
@Override
|
||||
public void onReboot() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Reboot");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), reboot);
|
||||
TransactionBuilder builder = performInitialized("send command");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), fetch);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to reboot MI", ex);
|
||||
LOG.error("Unable to fetch MI", ex);
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// TransactionBuilder builder = performInitialized("Read Activity data");
|
||||
// BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA);
|
||||
// builder.read(characteristic).queue(getQueue());
|
||||
// } catch (IOException ex) {
|
||||
// LOG.error("Unable to read activity data from MI", ex);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppInfoReq() {
|
||||
@ -336,6 +359,17 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
LOG.info("Notification characteristic changed: " + characteristic.getUuid());
|
||||
super.onCharacteristicChanged(gatt, characteristic);
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA.equals(characteristicUUID)) {
|
||||
handleActivityData(characteristic.getValue(), BluetoothGatt.GATT_SUCCESS);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
@ -346,6 +380,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
handleDeviceInfo(characteristic.getValue(), status);
|
||||
} else if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
|
||||
handleBatteryInfo(characteristic.getValue(), status);
|
||||
} else if (MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA.equals(characteristicUUID)) {
|
||||
handleActivityData(characteristic.getValue(), status);
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,6 +393,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
handlePairResult(characteristic.getValue(), status);
|
||||
} else if (MiBandService.UUID_CHARACTERISTIC_USER_INFO.equals(characteristicUUID)) {
|
||||
handleUserInfoResult(characteristic.getValue(), status);
|
||||
} else if (MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT.equals(characteristicUUID)) {
|
||||
handleControlPointResult(characteristic.getValue(), status);
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,6 +406,89 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleActivityData(byte[] value, int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
for (byte b: value){
|
||||
LOG.info("2GOT DATA:" + String.format("0x%6x", b));
|
||||
}
|
||||
|
||||
if ( value.length == 11 ) {
|
||||
//I know what to do:
|
||||
// byte 0 is the data type
|
||||
int dataType = value[0];
|
||||
// byte 1 to 6 represent a timestamp
|
||||
GregorianCalendar timestamp = new GregorianCalendar(value[1]+2000,
|
||||
value[2],
|
||||
value[3],
|
||||
value[4],
|
||||
value[5],
|
||||
value[6]);
|
||||
// no idea about the following two bytes
|
||||
int i = value[7] & 0xff;
|
||||
int j = value[8] & 0xff;
|
||||
// but combined they tell us how to proceed:
|
||||
int k = i | (j << 8);
|
||||
if (dataType == 1) {
|
||||
k *= 3;
|
||||
}
|
||||
int totalDataToRead = k;
|
||||
|
||||
// no idea about the following two bytes
|
||||
int l = value[9] & 0xff ;
|
||||
int i1 = value[10] & 0xff;
|
||||
|
||||
int j1 = l | (i1 << 8);
|
||||
|
||||
int k1;
|
||||
if (dataType == 1)
|
||||
{
|
||||
k1 = j1 * 3;
|
||||
} else
|
||||
{
|
||||
k1 = j1;
|
||||
}
|
||||
LOG.info("totaldatatoread: "+ totalDataToRead +" len: " + (k1 / 3) + " minute(s)");
|
||||
|
||||
LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()).toString() + " magic byte: " + j1);
|
||||
//sendAckDataTransfer(timestamp, j1);
|
||||
sendAckDataTransfer(timestamp, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleControlPointResult(byte[] value, int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
for (byte b: value){
|
||||
LOG.info("3GOT DATA:" + String.format("0x%20x", b));
|
||||
}
|
||||
handleActivityData(value, status);
|
||||
} else {
|
||||
LOG.info("BOOOOOOOOO");
|
||||
}
|
||||
|
||||
}
|
||||
private void sendAckDataTransfer(Calendar time, int unknown_code) {
|
||||
byte[] ack = new byte[]{
|
||||
MiBandService.COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE,
|
||||
(byte) (time.get(Calendar.YEAR) - 2000),
|
||||
(byte) time.get(Calendar.MONTH),
|
||||
(byte) time.get(Calendar.DATE),
|
||||
(byte) time.get(Calendar.HOUR_OF_DAY),
|
||||
(byte) time.get(Calendar.MINUTE),
|
||||
(byte) time.get(Calendar.SECOND),
|
||||
(byte) (unknown_code & 0xff),
|
||||
(byte) (0xff & (unknown_code >> 8))
|
||||
};
|
||||
LOG.info("WRITING: " + DateFormat.getDateTimeInstance().format(time.getTime()).toString());
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("send acknowledge");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), ack);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send ack to MI", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBatteryInfo(byte[] value, int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
BatteryInfo info = new BatteryInfo(value);
|
||||
@ -412,4 +533,9 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
LOG.info("MI Band pairing result: " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder createTransactionBuilder(String taskName) {
|
||||
return new MiBandTransactionBuilder(taskName);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,415 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.miband;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State;
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_DURATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PAUSE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_COLOUR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_COUNT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_DURATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.FLASH_ORIGINAL_COLOUR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_GENERIC;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_K9MAIL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_SMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_COUNT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_DURATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_PAUSE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotificationPrefIntValue;
|
||||
|
||||
public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandSupport.class);
|
||||
|
||||
public MiBandSupport() {
|
||||
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
pair(builder).sendUserInfo(builder).setCurrentTime(builder).requestBatteryInfo(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (connect()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
|
||||
LOG.info("Sending notification to MiBand: " + characteristic);
|
||||
builder.write(characteristic, getDefaultNotification()).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[]{8, 1};
|
||||
private static final byte[] stopVibrate = new byte[]{19};
|
||||
private static final byte[] reboot = new byte[]{12};
|
||||
|
||||
private byte[] getNotification(long vibrateDuration, int vibrateTimes, int flashTimes, int flashColour, int originalColour, long flashDuration) {
|
||||
byte[] vibrate = new byte[]{(byte) 8, (byte) 1};
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of device initialization process. Do not call manually.
|
||||
*
|
||||
* @param builder
|
||||
* @return
|
||||
*/
|
||||
private MiBandSupport sendUserInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Writing User Info!");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_USER_INFO);
|
||||
builder.write(characteristic, MiBandCoordinator.getAnyUserInfo(getDevice().getAddress()).getData());
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBandSupport requestBatteryInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Battery Info!");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_BATTERY);
|
||||
builder.read(characteristic);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of device initialization process. Do not call manually.
|
||||
*
|
||||
* @param transaction
|
||||
* @return
|
||||
*/
|
||||
private MiBandSupport pair(TransactionBuilder transaction) {
|
||||
LOG.info("Attempting to pair MI device...");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_PAIR);
|
||||
if (characteristic != null) {
|
||||
transaction.write(characteristic, new byte[]{2});
|
||||
} else {
|
||||
LOG.info("Unable to pair MI device -- characteristic not available");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void performDefaultNotification(String task) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized(task);
|
||||
sendDefaultNotification(builder);
|
||||
} 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) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized(task);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
int vibrateDuration = getPreferredVibrateDuration(notificationOrigin, prefs);
|
||||
int vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs);
|
||||
int vibratePause = getPreferredVibratePause(notificationOrigin, prefs);
|
||||
|
||||
int flashTimes = getPreferredFlashCount(notificationOrigin, prefs);
|
||||
int flashColour = getPreferredFlashColour(notificationOrigin, prefs);
|
||||
int originalColour = getPreferredOriginalColour(notificationOrigin, prefs);
|
||||
int flashDuration = getPreferredFlashDuration(notificationOrigin, prefs);
|
||||
|
||||
sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send notification to MI device", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int getPreferredFlashDuration(String notificationOrigin, SharedPreferences prefs) {
|
||||
return getNotificationPrefIntValue(FLASH_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_DURATION);
|
||||
}
|
||||
|
||||
private int getPreferredOriginalColour(String notificationOrigin, SharedPreferences prefs) {
|
||||
return getNotificationPrefIntValue(FLASH_ORIGINAL_COLOUR, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR);
|
||||
}
|
||||
|
||||
private int getPreferredFlashColour(String notificationOrigin, SharedPreferences prefs) {
|
||||
return getNotificationPrefIntValue(FLASH_COLOUR, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_COLOUR);
|
||||
}
|
||||
|
||||
private int getPreferredFlashCount(String notificationOrigin, SharedPreferences prefs) {
|
||||
return getNotificationPrefIntValue(FLASH_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_COUNT);
|
||||
}
|
||||
|
||||
private int getPreferredVibratePause(String notificationOrigin, SharedPreferences prefs) {
|
||||
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 int getPreferredVibrateDuration(String notificationOrigin, SharedPreferences prefs) {
|
||||
return getNotificationPrefIntValue(VIBRATION_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSMS(String from, String body) {
|
||||
// performCustomNotification("sms received", 500, 3, 2000, 0, 0, 0, 0);
|
||||
performPreferredNotification("sms received", ORIGIN_SMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmail(String from, String subject, String body) {
|
||||
performPreferredNotification("email received", ORIGIN_K9MAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenericNotification(String title, String details) {
|
||||
performPreferredNotification("generic notification received", ORIGIN_GENERIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime(long ts) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Set date and time");
|
||||
setCurrentTime(builder);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to set time on MI device", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current time to the Mi device using the given builder.
|
||||
*
|
||||
* @param builder
|
||||
*/
|
||||
private MiBandSupport setCurrentTime(TransactionBuilder builder) {
|
||||
Calendar now = Calendar.getInstance();
|
||||
byte[] time = new byte[]{
|
||||
(byte) (now.get(Calendar.YEAR) - 2000),
|
||||
(byte) now.get(Calendar.MONTH),
|
||||
(byte) now.get(Calendar.DATE),
|
||||
(byte) now.get(Calendar.HOUR_OF_DAY),
|
||||
(byte) now.get(Calendar.MINUTE),
|
||||
(byte) now.get(Calendar.SECOND),
|
||||
(byte) 0x0f,
|
||||
(byte) 0x0f,
|
||||
(byte) 0x0f,
|
||||
(byte) 0x0f,
|
||||
(byte) 0x0f,
|
||||
(byte) 0x0f
|
||||
};
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DATE_TIME);
|
||||
if (characteristic != null) {
|
||||
builder.write(characteristic, time);
|
||||
} else {
|
||||
LOG.info("Unable to set time -- characteristic not available");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(String number, String name, GBCommand command) {
|
||||
if (GBCommand.CALL_INCOMING.equals(command)) {
|
||||
performDefaultNotification("incoming call");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(String artist, String album, String track) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirmwareVersionReq() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Get MI Band device info");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DEVICE_INFO);
|
||||
builder.read(characteristic).queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to read device info from MI", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatteryInfoReq() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Get MI Band battery info");
|
||||
requestBatteryInfo(builder);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to read battery info from MI", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Reboot");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), reboot);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to reboot MI", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppInfoReq() {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppStart(UUID uuid) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppDelete(UUID uuid) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhoneVersion(byte os) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
super.onCharacteristicRead(gatt, characteristic, status);
|
||||
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (MiBandService.UUID_CHARACTERISTIC_DEVICE_INFO.equals(characteristicUUID)) {
|
||||
handleDeviceInfo(characteristic.getValue(), status);
|
||||
} else if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
|
||||
handleBatteryInfo(characteristic.getValue(), status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (MiBandService.UUID_CHARACTERISTIC_PAIR.equals(characteristicUUID)) {
|
||||
handlePairResult(characteristic.getValue(), status);
|
||||
} else if (MiBandService.UUID_CHARACTERISTIC_USER_INFO.equals(characteristicUUID)) {
|
||||
handleUserInfoResult(characteristic.getValue(), status);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(byte[] value, int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
DeviceInfo info = new DeviceInfo(value);
|
||||
getDevice().setFirmwareVersion(info.getFirmwareVersion());
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBatteryInfo(byte[] value, int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
BatteryInfo info = new BatteryInfo(value);
|
||||
getDevice().setBatteryLevel((short) info.getLevelInPercent());
|
||||
getDevice().setBatteryState(info.getStatus());
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUserInfoResult(byte[] value, int status) {
|
||||
// successfully transfered user info means we're initialized
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
setConnectionState(State.INITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private void setConnectionState(State newState) {
|
||||
getDevice().setState(newState);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
private void handlePairResult(byte[] pairResult, int status) {
|
||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||
LOG.info("Pairing MI device failed: " + status);
|
||||
return;
|
||||
}
|
||||
|
||||
String value = null;
|
||||
if (pairResult != null) {
|
||||
if (pairResult.length == 1) {
|
||||
try {
|
||||
if (pairResult[0] == 2) {
|
||||
LOG.info("Successfully paired MI device");
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("Error identifying pairing result", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
value = Arrays.toString(pairResult);
|
||||
}
|
||||
LOG.info("MI Band pairing result: " + value);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.miband;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.NotifyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder;
|
||||
|
||||
public class MiBandTransactionBuilder extends TransactionBuilder {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandTransactionBuilder.class);
|
||||
|
||||
public MiBandTransactionBuilder(String taskName) {
|
||||
super(taskName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NotifyAction createNotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) {
|
||||
return new MiBandNotifyAction(characteristic, enable);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user