diff --git a/app/src/main/assets/logback.xml b/app/src/main/assets/logback.xml
index f08c0b4c0..1fca7b466 100644
--- a/app/src/main/assets/logback.xml
+++ b/app/src/main/assets/logback.xml
@@ -28,8 +28,32 @@
+
+ ${GB_LOGFILES_DIR}/activity.log
+
+ true
+
+
+ ${GB_LOGFILES_DIR}/gadgetbridge-%d{yyyy-MM-dd}.log.zip
+ 10
+
+
+ 5MB
+
+
+
+ %d{HH:mm:ss.SSS} %msg%n
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
index 9874de777..56533f122 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
@@ -39,15 +39,13 @@ public class GBApplication extends Application {
}
private void setupLogging() {
- if (isFileLoggingEnabled()) {
- File dir = getExternalFilesDir(null);
- if (dir != null && !dir.exists()) {
- dir.mkdirs();
- }
- // used by assets/logback.xml since the location cannot be statically determined
- System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
- } else {
- System.setProperty("GB_LOGFILES_DIR", "/dev/null"); // just to please logback configuration, not used at all
+ File dir = getExternalFilesDir(null);
+ if (dir != null && !dir.exists()) {
+ dir.mkdirs();
+ }
+ // used by assets/logback.xml since the location cannot be statically determined
+ System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
+ if (!isFileLoggingEnabled()) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.detachAppender("FILE");
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
index 8847225e2..4b678d9ca 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
@@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.miband;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
@@ -16,7 +15,6 @@ import java.util.GregorianCalendar;
import java.util.UUID;
import java.text.DateFormat;
-import java.nio.ByteBuffer;
import nodomain.freeyourgadget.gadgetbridge.GBCommand;
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State;
@@ -45,8 +43,21 @@ import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotific
public class MiBandSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(MiBandSupport.class);
+ private static final Logger ACTIVITYLOG = LoggerFactory.getLogger("activity");
- private ByteBuffer activityDataHolder = null;
+ //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;
public MiBandSupport() {
@@ -284,7 +295,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
* @param builder
*/
private MiBandSupport setCurrentTime(TransactionBuilder builder) {
- Calendar now = Calendar.getInstance();
+ Calendar now = GregorianCalendar.getInstance();
byte[] time = new byte[]{
(byte) (now.get(Calendar.YEAR) - 2000),
(byte) now.get(Calendar.MONTH),
@@ -419,94 +430,97 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
private void handleActivityNotif(byte[] value) {
- LOG.info("NOTIF GOT " + value.length + " BYTES.");
- if (this.activityDataHolder == null && 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;
+ 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]);
- // no idea about the following two bytes
- int i1 = value[9] & 0xff ;
- int j1 = value[10] & 0xff;
+ // counter of all data held by the band
+ int totalDataToRead = (value[7] & 0xff) | ((value[8] & 0xff) << 8);
+ totalDataToRead *= (dataType == 1) ? 3 : 1;
- // but combined they tell us how to proceed:
- int k1 = i1 | (j1 << 8);
- if (dataType == 1) {
- k1 *= 3;
- }
- int dataUntilNextHeader = k1;
- // there is a total of totalDataToRead that will come in (3 bytes per minute),
- // however, 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("RAW DATA: i="+ i +" j="+ j +" k="+ k +" i1="+ i1 +" j1="+ j1 +" k1="+ k1 +"");
- LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()).toString() + " magic byte: " + dataUntilNextHeader);
- if (createActivityDataHolder(dataUntilNextHeader)) {
- sendAckDataTransfer(timestamp, dataUntilNextHeader);
- }
- } else {
- for (byte b: value){
- this.activityDataHolder.put(b);
- }
- LOG.info("Buffer remaining bytes: " + this.activityDataHolder.remaining());
- if (this.activityDataHolder.remaining() == 0) {
- consumeActivityDataHolder();
- }
- }
- }
+ // counter of this data block
+ int dataUntilNextHeader = (value[9] & 0xff) | ((value[10] & 0xff) << 8);
+ dataUntilNextHeader *= (dataType ==1) ? 3 : 1;
- private boolean createActivityDataHolder(int size) {
- if(this.activityDataHolder == null) {
- this.activityDataHolder = ByteBuffer.allocate(size);
- return true;
- }
- return false;
- }
+ // 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
- private void consumeActivityDataHolder() {
- int currentSize = this.activityDataHolder.capacity();
- this.activityDataHolder.rewind();
- /*
- intensity = byte1;
- steps = byte2;
- category = byte0;
- */
- for ( int i = 0 ; i < currentSize; i+=3 ) {
- LOG.info("index: "+i/3+" category:"+this.activityDataHolder.get()+" intensity:"+this.activityDataHolder.get()+" steps:"+this.activityDataHolder.get());
+ 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);
}
- this.activityDataHolder = null;
+ if (this.activityDataRemainingBytes == 0) {
+ sendAckDataTransfer(this.activityDataTimestampToAck, this.activityDataUntilNextHeader);
+ flushActivityDataHolder();
+ }
+ }
+
+ 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));
+ }
+ }
+ }
+ }
+
+ private void flushActivityDataHolder() {
+ GregorianCalendar timestamp = this.activityDataTimestampProgress;
+ for (int i=0; i> 8))
+ (byte) (bytesTransferred & 0xff),
+ (byte) (0xff & (bytesTransferred >> 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);
@@ -577,4 +590,4 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
protected TransactionBuilder createTransactionBuilder(String taskName) {
return new MiBandTransactionBuilder(taskName);
}
-}
+}
\ No newline at end of file