2015-04-14 01:24:03 +02:00
package nodomain.freeyourgadget.gadgetbridge.miband ;
2015-05-01 01:49:43 +02:00
import android.bluetooth.BluetoothGatt ;
import android.bluetooth.BluetoothGattCharacteristic ;
2015-05-17 22:57:37 +02:00
import android.content.SharedPreferences ;
import android.preference.PreferenceManager ;
2015-05-01 01:49:43 +02:00
2015-05-18 20:56:19 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2015-04-19 02:37:29 +02:00
import java.io.IOException ;
2015-05-12 20:32:34 +02:00
import java.util.Arrays ;
2015-04-23 14:11:57 +02:00
import java.util.Calendar ;
2015-05-24 00:11:14 +02:00
import java.util.GregorianCalendar ;
2015-04-26 00:53:48 +02:00
import java.util.UUID ;
2015-04-19 02:37:29 +02:00
2015-05-24 00:11:14 +02:00
import java.text.DateFormat ;
2015-06-01 10:15:19 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBActivitySample ;
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
2015-04-14 01:24:03 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBCommand ;
2015-04-19 15:11:50 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State ;
2015-04-19 02:37:29 +02:00
import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport ;
import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder ;
2015-04-14 01:24:03 +02:00
2015-05-18 20:56:19 +02:00
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 ;
2015-05-17 22:57:37 +02:00
2015-04-14 01:24:03 +02:00
public class MiBandSupport extends AbstractBTLEDeviceSupport {
2015-05-12 06:28:11 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( MiBandSupport . class ) ;
2015-06-01 09:42:44 +02:00
private static final Logger ACTIVITYLOG = LoggerFactory . getLogger ( " activity " ) ;
//temporary buffer, size is 60 because we want to store complete minutes (1 minute = 3 bytes)
private static final int activityDataHolderSize = 60 ;
private byte [ ] activityDataHolder = new byte [ activityDataHolderSize ] ;
//index of the buffer above
private int activityDataHolderProgress = 0 ;
//number of bytes we will get in a single data transfer, used as counter
private int activityDataRemainingBytes = 0 ;
//same as above, but remains untouched for the ack message
private int activityDataUntilNextHeader = 0 ;
//timestamp of the single data transfer, incremented to store each minute's data
private GregorianCalendar activityDataTimestampProgress = null ;
//same as above, but remains untouched for the ack message
private GregorianCalendar activityDataTimestampToAck = null ;
2015-05-24 19:09:14 +02:00
2015-04-19 02:37:29 +02:00
public MiBandSupport ( ) {
addSupportedService ( MiBandService . UUID_SERVICE_MIBAND_SERVICE ) ;
2015-04-14 02:03:14 +02:00
}
2015-04-14 01:24:03 +02:00
@Override
2015-04-19 02:37:29 +02:00
protected TransactionBuilder initializeDevice ( TransactionBuilder builder ) {
2015-05-28 00:26:41 +02:00
builder . add ( new SetDeviceStateAction ( getDevice ( ) , State . INITIALIZING , getContext ( ) ) ) ;
2015-05-25 23:14:02 +02:00
pair ( builder )
. sendUserInfo ( builder )
. enableNotifications ( builder , true )
. setCurrentTime ( builder )
2015-05-28 00:26:41 +02:00
. requestBatteryInfo ( builder )
. setInitialized ( builder ) ;
2015-05-24 00:11:14 +02:00
2015-04-19 02:37:29 +02:00
return builder ;
2015-04-14 01:24:03 +02:00
}
2015-05-28 00:26:41 +02:00
/ * *
* Last action of initialization sequence . Sets the device to initialized .
* It is only invoked if all other actions were successfully run , so the device
* must be initialized , then .
* @param builder
* /
private void setInitialized ( TransactionBuilder builder ) {
builder . add ( new SetDeviceStateAction ( getDevice ( ) , State . INITIALIZED , getContext ( ) ) ) ;
}
2015-05-24 00:11:14 +02:00
// 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 )
2015-05-24 14:39:36 +02:00
. notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_ACTIVITY_DATA ) , enable )
2015-05-24 00:11:14 +02:00
. notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_BATTERY ) , enable )
. notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_SENSOR_DATA ) , enable ) ;
return this ;
}
2015-04-14 01:24:03 +02:00
@Override
2015-04-19 02:37:29 +02:00
public boolean useAutoConnect ( ) {
return true ;
}
2015-04-19 11:28:03 +02:00
2015-05-05 00:48:02 +02:00
@Override
public void pair ( ) {
for ( int i = 0 ; i < 5 ; i + + ) {
if ( connect ( ) ) {
return ;
}
}
}
2015-04-19 02:37:29 +02:00
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 ) ;
}
2015-04-14 01:24:03 +02:00
2015-04-19 02:37:29 +02:00
private void sendDefaultNotification ( TransactionBuilder builder ) {
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) ;
2015-05-12 06:28:11 +02:00
LOG . info ( " Sending notification to MiBand: " + characteristic ) ;
2015-04-19 02:37:29 +02:00
builder . write ( characteristic , getDefaultNotification ( ) ) . queue ( getQueue ( ) ) ;
}
2015-05-17 22:57:37 +02:00
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 ( ) ) ;
}
2015-05-21 18:19:07 +02:00
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 } ;
2015-05-24 00:11:14 +02:00
private static final byte [ ] fetch = new byte [ ] { 6 } ;
2015-05-17 22:57:37 +02:00
2015-04-19 02:37:29 +02:00
private byte [ ] getNotification ( long vibrateDuration , int vibrateTimes , int flashTimes , int flashColour , int originalColour , long flashDuration ) {
2015-05-21 18:19:07 +02:00
byte [ ] vibrate = new byte [ ] { MiBandService . COMMAND_SEND_NOTIFICATION , ( byte ) 1 } ;
2015-04-19 02:37:29 +02:00
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 .
2015-04-19 11:28:03 +02:00
*
2015-04-19 02:37:29 +02:00
* @param builder
* @return
* /
private MiBandSupport sendUserInfo ( TransactionBuilder builder ) {
2015-05-12 06:28:11 +02:00
LOG . debug ( " Writing User Info! " ) ;
2015-04-19 02:37:29 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_USER_INFO ) ;
2015-05-07 23:51:03 +02:00
builder . write ( characteristic , MiBandCoordinator . getAnyUserInfo ( getDevice ( ) . getAddress ( ) ) . getData ( ) ) ;
2015-04-19 02:37:29 +02:00
return this ;
}
2015-05-05 23:25:54 +02:00
private MiBandSupport requestBatteryInfo ( TransactionBuilder builder ) {
2015-05-12 06:28:11 +02:00
LOG . debug ( " Requesting Battery Info! " ) ;
2015-05-05 23:25:54 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_BATTERY ) ;
builder . read ( characteristic ) ;
return this ;
}
2015-04-19 02:37:29 +02:00
/ * *
* Part of device initialization process . Do not call manually .
2015-04-19 11:28:03 +02:00
*
2015-05-07 23:51:03 +02:00
* @param transaction
2015-04-19 02:37:29 +02:00
* @return
* /
private MiBandSupport pair ( TransactionBuilder transaction ) {
2015-05-12 06:28:11 +02:00
LOG . info ( " Attempting to pair MI device... " ) ;
2015-04-19 02:37:29 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_PAIR ) ;
if ( characteristic ! = null ) {
transaction . write ( characteristic , new byte [ ] { 2 } ) ;
} else {
2015-05-12 06:28:11 +02:00
LOG . info ( " Unable to pair MI device -- characteristic not available " ) ;
2015-04-19 02:37:29 +02:00
}
return this ;
}
private void performDefaultNotification ( String task ) {
try {
TransactionBuilder builder = performInitialized ( task ) ;
sendDefaultNotification ( builder ) ;
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to send notification to MI device " , ex ) ;
2015-04-19 02:37:29 +02:00
}
2015-04-14 01:24:03 +02:00
}
2015-05-17 22:57:37 +02:00
// 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 ) ;
}
2015-04-14 01:24:03 +02:00
@Override
2015-04-19 02:37:29 +02:00
public void onSMS ( String from , String body ) {
2015-05-17 22:57:37 +02:00
// performCustomNotification("sms received", 500, 3, 2000, 0, 0, 0, 0);
performPreferredNotification ( " sms received " , ORIGIN_SMS ) ;
2015-04-19 02:37:29 +02:00
}
2015-04-14 01:24:03 +02:00
2015-04-19 02:37:29 +02:00
@Override
public void onEmail ( String from , String subject , String body ) {
2015-05-17 22:57:37 +02:00
performPreferredNotification ( " email received " , ORIGIN_K9MAIL ) ;
2015-04-14 01:24:03 +02:00
}
2015-05-13 21:55:22 +02:00
@Override
public void onGenericNotification ( String title , String details ) {
2015-05-17 22:57:37 +02:00
performPreferredNotification ( " generic notification received " , ORIGIN_GENERIC ) ;
2015-05-13 21:55:22 +02:00
}
2015-04-14 01:24:03 +02:00
@Override
public void onSetTime ( long ts ) {
2015-04-27 21:43:57 +02:00
try {
TransactionBuilder builder = performInitialized ( " Set date and time " ) ;
setCurrentTime ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to set time on MI device " , ex ) ;
2015-04-27 21:43:57 +02:00
}
}
/ * *
* Sets the current time to the Mi device using the given builder .
2015-05-01 01:49:43 +02:00
*
2015-04-27 21:43:57 +02:00
* @param builder
* /
private MiBandSupport setCurrentTime ( TransactionBuilder builder ) {
2015-06-01 09:42:44 +02:00
Calendar now = GregorianCalendar . getInstance ( ) ;
2015-04-26 00:53:48 +02:00
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
} ;
2015-04-27 21:43:57 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_DATE_TIME ) ;
if ( characteristic ! = null ) {
builder . write ( characteristic , time ) ;
} else {
2015-05-12 06:28:11 +02:00
LOG . info ( " Unable to set time -- characteristic not available " ) ;
2015-04-23 14:11:57 +02:00
}
2015-04-27 21:43:57 +02:00
return this ;
2015-04-14 01:24:03 +02:00
}
@Override
public void onSetCallState ( String number , String name , GBCommand command ) {
2015-04-19 15:15:53 +02:00
if ( GBCommand . CALL_INCOMING . equals ( command ) ) {
performDefaultNotification ( " incoming call " ) ;
}
2015-04-14 01:24:03 +02:00
}
@Override
public void onSetMusicInfo ( String artist , String album , String track ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
@Override
public void onFirmwareVersionReq ( ) {
2015-04-19 22:20:47 +02:00
try {
2015-04-19 22:31:09 +02:00
TransactionBuilder builder = performInitialized ( " Get MI Band device info " ) ;
2015-04-19 22:20:47 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_DEVICE_INFO ) ;
builder . read ( characteristic ) . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to read device info from MI " , ex ) ;
2015-04-19 22:20:47 +02:00
}
2015-04-14 01:24:03 +02:00
}
2015-04-19 22:31:09 +02:00
@Override
public void onBatteryInfoReq ( ) {
try {
TransactionBuilder builder = performInitialized ( " Get MI Band battery info " ) ;
2015-05-05 23:25:54 +02:00
requestBatteryInfo ( builder ) ;
builder . queue ( getQueue ( ) ) ;
2015-04-19 22:31:09 +02:00
} catch ( IOException ex ) {
2015-05-12 06:28:11 +02:00
LOG . error ( " Unable to read battery info from MI " , ex ) ;
2015-04-19 22:31:09 +02:00
}
}
2015-05-17 22:57:37 +02:00
@Override
public void onReboot ( ) {
try {
2015-05-24 15:04:48 +02:00
TransactionBuilder builder = performInitialized ( " fetch activity data " ) ;
2015-05-24 00:11:14 +02:00
builder . write ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT ) , fetch ) ;
2015-05-17 22:57:37 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2015-05-24 15:04:48 +02:00
LOG . error ( " Unable to fetch MI activity data " , ex ) ;
2015-05-17 22:57:37 +02:00
}
2015-05-24 15:04:48 +02:00
}
2015-05-17 22:57:37 +02:00
2015-04-14 01:24:03 +02:00
@Override
public void onAppInfoReq ( ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
2015-05-18 22:20:01 +02:00
@Override
public void onAppStart ( UUID uuid ) {
// not supported
}
2015-04-14 01:24:03 +02:00
@Override
2015-05-18 20:56:19 +02:00
public void onAppDelete ( UUID uuid ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
@Override
public void onPhoneVersion ( byte os ) {
2015-05-07 23:51:03 +02:00
// not supported
2015-04-14 01:24:03 +02:00
}
2015-04-19 02:37:29 +02:00
2015-05-24 00:11:14 +02:00
@Override
public void onCharacteristicChanged ( BluetoothGatt gatt ,
2015-05-24 15:04:48 +02:00
BluetoothGattCharacteristic characteristic ) {
2015-05-24 00:11:14 +02:00
super . onCharacteristicChanged ( gatt , characteristic ) ;
UUID characteristicUUID = characteristic . getUuid ( ) ;
if ( MiBandService . UUID_CHARACTERISTIC_ACTIVITY_DATA . equals ( characteristicUUID ) ) {
2015-05-24 19:09:14 +02:00
handleActivityNotif ( characteristic . getValue ( ) ) ;
2015-05-24 00:11:14 +02:00
}
}
2015-05-24 14:39:36 +02:00
2015-04-19 22:20:47 +02:00
@Override
public void onCharacteristicRead ( BluetoothGatt gatt ,
2015-04-20 11:58:59 +02:00
BluetoothGattCharacteristic characteristic , int status ) {
2015-04-19 22:20:47 +02:00
super . onCharacteristicRead ( gatt , characteristic , status ) ;
2015-04-19 22:31:09 +02:00
UUID characteristicUUID = characteristic . getUuid ( ) ;
if ( MiBandService . UUID_CHARACTERISTIC_DEVICE_INFO . equals ( characteristicUUID ) ) {
2015-04-19 22:20:47 +02:00
handleDeviceInfo ( characteristic . getValue ( ) , status ) ;
2015-04-19 22:31:09 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_BATTERY . equals ( characteristicUUID ) ) {
handleBatteryInfo ( characteristic . getValue ( ) , status ) ;
2015-04-19 22:20:47 +02:00
}
}
2015-04-19 02:37:29 +02:00
@Override
public void onCharacteristicWrite ( BluetoothGatt gatt ,
2015-04-19 11:28:03 +02:00
BluetoothGattCharacteristic characteristic , int status ) {
2015-04-19 02:37:29 +02:00
UUID characteristicUUID = characteristic . getUuid ( ) ;
if ( MiBandService . UUID_CHARACTERISTIC_PAIR . equals ( characteristicUUID ) ) {
handlePairResult ( characteristic . getValue ( ) , status ) ;
2015-04-19 15:11:50 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_USER_INFO . equals ( characteristicUUID ) ) {
handleUserInfoResult ( characteristic . getValue ( ) , status ) ;
2015-05-24 00:11:14 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_CONTROL_POINT . equals ( characteristicUUID ) ) {
handleControlPointResult ( characteristic . getValue ( ) , status ) ;
2015-04-19 02:37:29 +02:00
}
}
2015-04-19 22:31:09 +02:00
private void handleDeviceInfo ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
DeviceInfo info = new DeviceInfo ( value ) ;
getDevice ( ) . setFirmwareVersion ( info . getFirmwareVersion ( ) ) ;
getDevice ( ) . sendDeviceUpdateIntent ( getContext ( ) ) ;
}
}
2015-05-24 19:09:14 +02:00
private void handleActivityNotif ( byte [ ] value ) {
2015-06-01 09:42:44 +02:00
LOG . info ( " handleActivityNotif GOT " + value . length + " BYTES. " ) ;
if ( value . length = = 11 ) {
// byte 0 is the data type: 1 means that each minute is represented by a triplet of bytes
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 ] ) ;
// counter of all data held by the band
int totalDataToRead = ( value [ 7 ] & 0xff ) | ( ( value [ 8 ] & 0xff ) < < 8 ) ;
totalDataToRead * = ( dataType = = 1 ) ? 3 : 1 ;
// counter of this data block
int dataUntilNextHeader = ( value [ 9 ] & 0xff ) | ( ( value [ 10 ] & 0xff ) < < 8 ) ;
dataUntilNextHeader * = ( dataType = = 1 ) ? 3 : 1 ;
// there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1),
// these chunks are usually 20 bytes long and grouped in blocks
// after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed
// as we just did
LOG . info ( " total data to read: " + totalDataToRead + " len: " + ( totalDataToRead / 3 ) + " minute(s) " ) ;
LOG . info ( " data to read until next header: " + dataUntilNextHeader + " len: " + ( dataUntilNextHeader / 3 ) + " minute(s) " ) ;
LOG . info ( " TIMESTAMP: " + DateFormat . getDateTimeInstance ( ) . format ( timestamp . getTime ( ) ) . toString ( ) + " magic byte: " + dataUntilNextHeader ) ;
this . activityDataRemainingBytes = this . activityDataUntilNextHeader = dataUntilNextHeader ;
this . activityDataTimestampProgress = this . activityDataTimestampToAck = timestamp ;
} else {
bufferActivityData ( value ) ;
}
if ( this . activityDataRemainingBytes = = 0 ) {
sendAckDataTransfer ( this . activityDataTimestampToAck , this . activityDataUntilNextHeader ) ;
flushActivityDataHolder ( ) ;
2015-05-24 00:11:14 +02:00
}
}
2015-06-01 09:42:44 +02:00
private void bufferActivityData ( byte [ ] value ) {
if ( this . activityDataRemainingBytes > = value . length ) {
//I don't like this clause, but until we figure out why we get different data sometimes this should work
if ( value . length = = 20 | | value . length = = this . activityDataRemainingBytes ) {
System . arraycopy ( value , 0 , this . activityDataHolder , this . activityDataHolderProgress , value . length ) ;
this . activityDataHolderProgress + = value . length ;
this . activityDataRemainingBytes - = value . length ;
if ( this . activityDataHolderSize = = this . activityDataHolderProgress ) {
flushActivityDataHolder ( ) ;
}
} else {
// the lenght of the chunk is not what we expect. We need to make sense of this data
LOG . warn ( " GOT UNEXPECTED ACTIVITY DATA WITH LENGTH: " + value . length + " , EXPECTED LENGTH: " + this . activityDataRemainingBytes ) ;
for ( byte b : value ) {
LOG . warn ( " DATA: " + String . format ( " 0x%8x " , b ) ) ;
}
2015-05-24 15:04:48 +02:00
}
2015-06-01 09:42:44 +02:00
}
}
private void flushActivityDataHolder ( ) {
GregorianCalendar timestamp = this . activityDataTimestampProgress ;
for ( int i = 0 ; i < this . activityDataHolder . length ; i + = 3 ) {
2015-06-01 10:15:19 +02:00
GBApplication . getActivityDatabaseHandler ( ) . addGBActivitySample (
( int ) timestamp . getTimeInMillis ( ) / 1000 ,
GBActivitySample . PROVIDER_MIBAND ,
this . activityDataHolder [ i + 1 ] ,
this . activityDataHolder [ i + 2 ] ,
this . activityDataHolder [ i ] ) ;
2015-06-01 09:42:44 +02:00
ACTIVITYLOG . info (
" timestamp: " + DateFormat . getDateTimeInstance ( ) . format ( timestamp . getTime ( ) ) . toString ( ) +
" category: " + this . activityDataHolder [ i ] +
" intensity: " + this . activityDataHolder [ i + 1 ] +
" steps: " + this . activityDataHolder [ i + 2 ]
) ;
timestamp . add ( Calendar . MINUTE , 1 ) ;
}
this . activityDataHolderProgress = 0 ;
this . activityDataTimestampProgress = timestamp ;
}
private void handleControlPointResult ( byte [ ] value , int status ) {
if ( status ! = BluetoothGatt . GATT_SUCCESS ) {
LOG . warn ( " Could not write to the control point. " ) ;
}
LOG . info ( " handleControlPoint got status: " + status ) ;
for ( byte b : value ) {
LOG . info ( " handleControlPoint GOT DATA: " + String . format ( " 0x%8x " , b ) ) ;
2015-05-24 00:11:14 +02:00
}
}
2015-06-01 09:42:44 +02:00
private void sendAckDataTransfer ( Calendar time , int bytesTransferred ) {
2015-05-24 00:11:14 +02:00
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 ) ,
2015-06-01 09:42:44 +02:00
( byte ) ( bytesTransferred & 0xff ) ,
( byte ) ( 0xff & ( bytesTransferred > > 8 ) )
2015-05-24 00:11:14 +02:00
} ;
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 ) ;
}
}
2015-04-19 22:31:09 +02:00
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 ( ) ) ;
}
}
2015-04-19 15:11:50 +02:00
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 ( ) ) ;
}
2015-04-19 02:37:29 +02:00
private void handlePairResult ( byte [ ] pairResult , int status ) {
if ( status ! = BluetoothGatt . GATT_SUCCESS ) {
2015-05-12 06:28:11 +02:00
LOG . info ( " Pairing MI device failed: " + status ) ;
2015-04-19 02:37:29 +02:00
return ;
}
2015-05-12 20:32:34 +02:00
String value = null ;
2015-04-19 02:37:29 +02:00
if ( pairResult ! = null ) {
if ( pairResult . length = = 1 ) {
try {
2015-05-12 20:32:34 +02:00
if ( pairResult [ 0 ] = = 2 ) {
2015-05-12 06:28:11 +02:00
LOG . info ( " Successfully paired MI device " ) ;
2015-04-19 02:37:29 +02:00
return ;
}
} catch ( Exception ex ) {
2015-05-12 06:28:11 +02:00
LOG . warn ( " Error identifying pairing result " , ex ) ;
2015-04-19 02:37:29 +02:00
return ;
}
}
2015-05-12 20:32:34 +02:00
value = Arrays . toString ( pairResult ) ;
2015-04-19 02:37:29 +02:00
}
2015-05-12 06:28:11 +02:00
LOG . info ( " MI Band pairing result: " + value ) ;
2015-04-19 02:37:29 +02:00
}
2015-05-24 00:11:14 +02:00
@Override
protected TransactionBuilder createTransactionBuilder ( String taskName ) {
return new MiBandTransactionBuilder ( taskName ) ;
}
2015-06-01 09:42:44 +02:00
}