package nodomain.freeyourgadget.gadgetbridge.miband; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import java.io.IOException; import java.util.Arrays; import java.util.Calendar; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBCommand; import nodomain.freeyourgadget.gadgetbridge.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder; 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 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); } } @Override public void onSMS(String from, String body) { performDefaultNotification("sms received"); } @Override public void onEmail(String from, String subject, String body) { performDefaultNotification("email received"); } @Override public void onGenericNotification(String title, String details) { performDefaultNotification("generic notification received"); } @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 onAppInfoReq() { // not supported } @Override public void onAppDelete(int id, int index) { // 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); } }