diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java index adcbee54d..2d796e108 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java @@ -12,9 +12,15 @@ import java.io.IOException; import java.io.InputStream; import java.util.Locale; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.Mi1SInfo; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +/** + * Also see Mi1SInfo. + */ public class MiBandFWHelper { private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class); private static final int MI_FW_BASE_OFFSET = 1056; @@ -117,10 +123,29 @@ public class MiBandFWHelper { return (fw[getOffsetFirmwareVersionMajor()] << 24) | (fw[getOffsetFirmwareVersionMinor()] << 16) | (fw[getOffsetFirmwareVersionRevision()] << 8) | fw[getOffsetFirmwareVersionBuild()]; } + public static String formatFirmwareVersion(int version) { + if (version == -1) + return GBApplication.getContext().getString(R.string._unknown_); + + return String.format("%d.%d.%d.%d", + version >> 24 & 255, + version >> 16 & 255, + version >> 8 & 255, + version & 255); + } + public String getHumanFirmwareVersion() { return String.format(Locale.US, "%d.%d.%d.%d", fw[getOffsetFirmwareVersionMajor()], fw[getOffsetFirmwareVersionMinor()], fw[getOffsetFirmwareVersionRevision()], fw[getOffsetFirmwareVersionBuild()]); } + public String getHumanFirmwareVersion2() { + return format(Mi1SInfo.getFirmware2VersionFrom(getFw())); + } + + public String format(int version) { + return formatFirmwareVersion(version); + } + public byte[] getFw() { return fw; } @@ -142,9 +167,13 @@ public class MiBandFWHelper { if (MiBandConst.MI_1A.equals(deviceHW)) { return getFirmwareVersionMajor() == 5; } -// if (MiBandConst.MI_1S.equals(deviceHW)) { -// return getFirmwareVersionMajor() == 4; -// } + if (true || MiBandConst.MI_1S.equals(deviceHW)) { // FIXME: REMOVE TEMPORARY HACK + return getFirmwareVersionMajor() == 4; + } return false; } + + public boolean isSingleFirmware() { + return Mi1SInfo.isSingleMiBandFirmware(getFw()); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java index 644b94d38..17b891c4a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java @@ -56,7 +56,13 @@ public class MiBandFWInstallHandler implements InstallHandler { installActivity.setInstallEnabled(false); return; } - StringBuilder builder = new StringBuilder(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion())); + StringBuilder builder = new StringBuilder(); + if (helper.isSingleFirmware()) { + builder.append(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion())); + } else { + builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2())); + } + if (helper.isFirmwareWhitelisted()) { builder.append(" ").append(mContext.getString(R.string.miband_firmware_known)); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java index d7a64de0b..5a7044974 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java @@ -1,17 +1,23 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; public class DeviceInfo extends AbstractInfo { public final String deviceId; public final int profileVersion; + /** + * Mi Band firmware version identifier + */ public final int fwVersion; public final int hwVersion; public final int feature; public final int appearance; + /** + * Heart rate firmware version identifier + */ + public final int fw2Version; private boolean isChecksumCorrect(byte[] data) { @@ -29,6 +35,15 @@ public class DeviceInfo extends AbstractInfo { hwVersion = data[6] & 255; appearance = data[5] & 255; feature = data[4] & 255; + if (data.length == 20) { + int s = 0; + for (int i = 0; i < 4; ++i) { + s |= (data[16 + i] & 255) << i * 8; + } + fw2Version = s; + } else { + fw2Version = -1; + } } else { deviceId = "crc error"; profileVersion = -1; @@ -36,6 +51,7 @@ public class DeviceInfo extends AbstractInfo { hwVersion = -1; feature = -1; appearance = -1; + fw2Version = -1; } } @@ -52,20 +68,21 @@ public class DeviceInfo extends AbstractInfo { } public String getHumanFirmwareVersion() { - if (fwVersion == -1) - return GBApplication.getContext().getString(R.string._unknown_); + return MiBandFWHelper.formatFirmwareVersion(fwVersion); + } - return String.format("%d.%d.%d.%d", - fwVersion >> 24 & 255, - fwVersion >> 16 & 255, - fwVersion >> 8 & 255, - fwVersion & 255); + public String getHumanFirmware2Version() { + return MiBandFWHelper.formatFirmwareVersion(fw2Version); } public int getFirmwareVersion() { return fwVersion; } + public int getHeartrateFirmwareVersion() { + return fw2Version; + } + @Override public String toString() { return "DeviceInfo{" + @@ -75,6 +92,7 @@ public class DeviceInfo extends AbstractInfo { ", hwVersion=" + hwVersion + ", feature=" + feature + ", appearance=" + appearance + + ", fw2Version (hr)=" + fw2Version+ '}'; } @@ -99,7 +117,8 @@ public class DeviceInfo extends AbstractInfo { return MiBandConst.MI_1A; } if (isMilli1S()) { - return MiBandConst.MI_1S; + return getHumanFirmware2Version(); +// return MiBandConst.MI_1S; } return "?"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SInfo.java new file mode 100644 index 000000000..6d98e522d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SInfo.java @@ -0,0 +1,64 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +/** + * FW1 is Mi Band firmware + * FW2 is heartrate firmware + */ +public class Mi1SInfo { + + public static int getFirmware2OffsetIn(byte[] wholeFirmwareBytes) + { + return (wholeFirmwareBytes[26] & 255) << 24 + | (wholeFirmwareBytes[27] & 255) << 16 + | (wholeFirmwareBytes[28] & 255) << 8 + | (wholeFirmwareBytes[29] & 255); + } + + public static int getFirmware2LengthIn(byte[] wholeFirmwareBytes) + { + return (wholeFirmwareBytes[30] & 255) << 24 + | (wholeFirmwareBytes[31] & 255) << 16 + | (wholeFirmwareBytes[32] & 255) << 8 + | (wholeFirmwareBytes[33] & 255); + } + + public static int getFirmware1OffsetIn(byte[] wholeFirmwareBytes) + { + return (wholeFirmwareBytes[12] & 255) << 24 + | (wholeFirmwareBytes[13] & 255) << 16 + | (wholeFirmwareBytes[14] & 255) << 8 + | (wholeFirmwareBytes[15] & 255); + } + + public static int getFirmware1LengthIn(byte[] wholeFirmwareBytes) + { + return (wholeFirmwareBytes[16] & 255) << 24 + | (wholeFirmwareBytes[17] & 255) << 16 + | (wholeFirmwareBytes[18] & 255) << 8 + | (wholeFirmwareBytes[19] & 255); + } + + public static int getFirmware1VersionFrom(byte[] wholeFirmwareBytes) + { + return (wholeFirmwareBytes[8] & 255) << 24 + | (wholeFirmwareBytes[9] & 255) << 16 + | (wholeFirmwareBytes[10] & 255) << 8 + | wholeFirmwareBytes[11] & 255; + } + + public static int getFirmware2VersionFrom(byte[] wholeFirmwareBytes) + { + return (wholeFirmwareBytes[22] & 255) << 24 + | (wholeFirmwareBytes[23] & 255) << 16 + | (wholeFirmwareBytes[24] & 255) << 8 + | wholeFirmwareBytes[25] & 255; + } + + public static boolean isSingleMiBandFirmware(byte[] wholeFirmwareBytes) { + if ((wholeFirmwareBytes[7] & 255) != 1) { + return false; + } + return false;// FIXME: hack -- should be true! + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java index 6b9b2ca19..e5e144414 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java @@ -319,7 +319,6 @@ public class FetchActivityOperation extends AbstractMiBandOperation { int numSamples = activityStruct.activityDataHolderProgress / bpm; ActivitySample[] samples = new ActivitySample[numSamples]; SampleProvider sampleProvider = new MiBandSampleProvider(); - int s = 0; for (int i = 0; i < activityStruct.activityDataHolderProgress; i += bpm) { category = activityStruct.activityDataHolder[i]; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java index ccae442fd..a351287cb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java @@ -18,6 +18,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.Mi1SInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -27,8 +28,9 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { private final Uri uri; private boolean firmwareInfoSent = false; - private byte[] newFirmware; - private boolean rebootWhenBandReady = false; +// private byte[] newFirmware; +// private boolean rebootWhenBandReady = false; + private UpdateCoordinator updateCoordinator; public UpdateFirmwareOperation(Uri uri, MiBandSupport support) { super(support); @@ -38,20 +40,25 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { @Override protected void doPerform() throws IOException { MiBandFWHelper mFwHelper = new MiBandFWHelper(uri, getContext()); - String mMac = getDevice().getAddress(); - String[] mMacOctets = mMac.split(":"); - int newFwVersion = mFwHelper.getFirmwareVersion(); - int oldFwVersion = getSupport().getDeviceInfo().getFirmwareVersion(); - int checksum = (Integer.decode("0x" + mMacOctets[4]) << 8 | Integer.decode("0x" + mMacOctets[5])) ^ CheckSums.getCRC16(mFwHelper.getFw()); +// if (getSupport().supportsHeartRate()) { + updateCoordinator = prepareFirmwareInfo1S(mFwHelper.getFw()); +// } else { +// updateCoordinator = sendFirmwareInfo(mFwHelper.getFw(), mFwHelper.getFirmwareVersion()); +// } - sendFirmwareInfo(oldFwVersion, newFwVersion, mFwHelper.getFw().length, checksum); - firmwareInfoSent = true; - newFirmware = mFwHelper.getFw(); + updateCoordinator.initNextOperation(); + updateCoordinator.initNextOperation(); // FIXME: remove, just testing mi band fw update + firmwareInfoSent = updateCoordinator.sendFwInfo(); + if (!firmwareInfoSent) { + GB.toast(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR); + done(); + } //the firmware will be sent by the notification listener if the band confirms that the metadata are ok. } private void done() { + updateCoordinator = null; operationFinished(); unsetBusy(); } @@ -83,32 +90,49 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { getSupport().logMessageContent(value); return; } + if (updateCoordinator == null) { + LOG.error("received notification when updateCoordinator is null, ignoring!"); + return; + } + switch (value[0]) { case MiBandService.NOTIFY_FW_CHECK_SUCCESS: - if (firmwareInfoSent && newFirmware != null) { - if (sendFirmwareData(newFirmware)) { - rebootWhenBandReady = true; +// if (firmwareInfoSent && newFirmware != null) { + if (firmwareInfoSent) { + GB.toast(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO); + if (updateCoordinator.sendFwData()) { +// if (sendFirmwareData(newFirmware)) { +// rebootWhenBandReady = true; // disabled for testing } else { //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do? GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); done(); } firmwareInfoSent = false; - newFirmware = null; +// newFirmware = null; } break; case MiBandService.NOTIFY_FW_CHECK_FAILED: GB.toast(getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR); firmwareInfoSent = false; - newFirmware = null; +// newFirmware = null; done(); break; case MiBandService.NOTIFY_FIRMWARE_UPDATE_SUCCESS: - if (rebootWhenBandReady) { + if (updateCoordinator.initNextOperation()) { + GB.toast(getContext(), "Heart Rate Firmware successfully updated, now updating Mi Band Firmware", Toast.LENGTH_LONG, GB.INFO); + if (!updateCoordinator.sendFwInfo()) { + GB.toast(getContext(), "Sending Mi Band Firmware failed, aborting. Do NOT reboot your Mi Band!", Toast.LENGTH_LONG, GB.INFO); + done(); + } + break; + } else if (updateCoordinator.needsReboot()) { GB.toast(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO); GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext()); getSupport().onReboot(); - rebootWhenBandReady = false; +// rebootWhenBandReady = false; + } else { + LOG.error("BUG: Successful firmware update without reboot???"); } done(); break; @@ -116,7 +140,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do? GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_write_failed), false, 0, getContext()); - rebootWhenBandReady = false; +// rebootWhenBandReady = false; done(); break; @@ -126,20 +150,103 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } } - /** - * Prepare the MiBand to receive the new firmware data. - * Some information about the new firmware version have to be pushed to the MiBand before sending - * the actual firmare. - *
- * The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it. - * - * @param currentFwVersion - * @param newFwVersion - * @param newFwSize - * @param checksum - * @see MiBandSupport#handleNotificationNotif - */ - private void sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) throws IOException { +// /** +// * Prepare the MiBand to receive the new firmware data. +// * Some information about the new firmware version have to be pushed to the MiBand before sending +// * the actual firmare. +// * +// * The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it. +// * +// * @param fwBytes +// * @param newFwVersion +// * @see MiBandSupport#handleNotificationNotif +// */ +// private byte[] sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) throws IOException { +// private UpdateCoordinator sendFirmwareInfo(byte[] fwBytes, int newFwVersion) throws IOException { +// int newFwSize = fwBytes.length; +// String mMac = getDevice().getAddress(); +// String[] mMacOctets = mMac.split(":"); +// int currentFwVersion = getSupport().getDeviceInfo().getFirmwareVersion(); +// int checksum = (Integer.decode("0x" + mMacOctets[4]) << 8 | Integer.decode("0x" + mMacOctets[5])) ^ CheckSums.getCRC16(fwBytes); +// +// byte[] fwInfo = new byte[]{ +// MiBandService.COMMAND_SEND_FIRMWARE_INFO, +// (byte) currentFwVersion, +// (byte) (currentFwVersion >> 8), +// (byte) (currentFwVersion >> 16), +// (byte) (currentFwVersion >> 24), +// (byte) newFwVersion, +// (byte) (newFwVersion >> 8), +// (byte) (newFwVersion >> 16), +// (byte) (newFwVersion >> 24), +// (byte) newFwSize, +// (byte) (newFwSize >> 8), +// (byte) checksum, +// (byte) (checksum >> 8) +//// (byte) (checksum >> 8), +//// (byte) 0 // TEST, only for Mi1S! +// }; +// return new SingleUpdateCoordinator(fwInfo, fwBytes); +// } + + private UpdateCoordinator prepareFirmwareInfo1S(byte[] wholeFirmwareBytes) { + int fw2Version = Mi1SInfo.getFirmware2VersionFrom(wholeFirmwareBytes); + int fw2Offset = Mi1SInfo.getFirmware2OffsetIn(wholeFirmwareBytes); + int fw2Length = Mi1SInfo.getFirmware2LengthIn(wholeFirmwareBytes); + + int fw1Version = Mi1SInfo.getFirmware1VersionFrom(wholeFirmwareBytes); + int fw1Offset = Mi1SInfo.getFirmware1OffsetIn(wholeFirmwareBytes); + int fw1Length = Mi1SInfo.getFirmware1LengthIn(wholeFirmwareBytes); + + String[] mMacOctets = getDevice().getAddress().split(":"); + int encodedMac = (Integer.decode("0x" + mMacOctets[4]) << 8 | Integer.decode("0x" + mMacOctets[5])); + + byte[] fw2Bytes = new byte[fw2Length]; + System.arraycopy(wholeFirmwareBytes, fw2Offset, fw2Bytes, 0, fw2Length); + int fw2Checksum = CheckSums.getCRC16(fw2Bytes) ^ encodedMac; + + byte[] fw1Bytes = new byte[fw1Length]; + System.arraycopy(wholeFirmwareBytes, fw1Offset, fw1Bytes, 0, fw1Length); + int fw1Checksum = encodedMac ^ CheckSums.getCRC16(fw1Bytes); + + // check firmware validity? + + int fw1OldVersion = getSupport().getDeviceInfo().getFirmwareVersion(); + int fw2OldVersion = getSupport().getDeviceInfo().getHeartrateFirmwareVersion(); + + boolean rebootWhenFinished = true; + if (Mi1SInfo.isSingleMiBandFirmware(wholeFirmwareBytes)) { + LOG.info("is single Mi Band firmware"); + byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 0, rebootWhenFinished /*, progress monitor */); + return new SingleUpdateCoordinator(fw1Info, fw1Bytes, rebootWhenFinished); + } else { + LOG.info("is multi Mi Band firmware, sending fw2 (hr) now"); + byte[] fw2Info = prepareFirmwareInfo(fw2Bytes, fw2OldVersion, fw2Version, fw2Checksum, 1, rebootWhenFinished /*, progress monitor */); + byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 1, rebootWhenFinished /*, progress monitor */); + return new DoubleUpdateCoordinator(fw1Info, fw1Bytes, fw2Info, fw2Bytes, rebootWhenFinished); + } + } + +// private Transaction createUpdateFirmwareTransaction() { +// +// } + + private byte[] prepareFirmwareInfo(byte[] fwBytes, int currentFwVersion, int newFwVersion, int checksum, int something, boolean reboot) { + byte[] fwInfo; + switch (something) { + case -1: + fwInfo = prepareFirmwareUpdateA(currentFwVersion, newFwVersion, fwBytes.length, checksum); + break; + case -2: + fwInfo = prepareFirmwareUpdateB(currentFwVersion, newFwVersion, fwBytes.length, checksum, 0); + break; + default: + fwInfo = prepareFirmwareUpdateB(currentFwVersion, newFwVersion, fwBytes.length, checksum, something); + } + return fwInfo; + } + + private byte[] prepareFirmwareUpdateA(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) { byte[] fwInfo = new byte[]{ MiBandService.COMMAND_SEND_FIRMWARE_INFO, (byte) currentFwVersion, @@ -154,15 +261,50 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { (byte) (newFwSize >> 8), (byte) checksum, (byte) (checksum >> 8) -// (byte) (checksum >> 8), -// (byte) 0 // TEST, only for Mi1S! }; - TransactionBuilder builder = performInitialized("send firmware info"); - builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext())); - builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), fwInfo); - builder.queue(getQueue()); +// byte[] fwInfo = new byte[]{ +// MiBandService.COMMAND_SEND_FIRMWARE_INFO +// (byte) currentFwVersion, (byte) (currentFwVersion >> 8), (byte) (currentFwVersion >> 16), (byte) (currentFwVersion >> 24), +// (byte) newFwVersion, (byte) (newFwVersion >> 8), (byte) (newFwVersion >> 16), (byte) (newFwVersion >> 24), +// (byte) newFwSize, (byte) (newFwSize >> 8), +// (byte) checksum, (byte) (checksum >> 8)})) { + return fwInfo; } + private byte[] prepareFirmwareUpdateB(int currentFwVersion, int newFwVersion, int newFwSize, int checksum, int something) { + byte[] fwInfo = new byte[]{ + MiBandService.COMMAND_SEND_FIRMWARE_INFO, + (byte) currentFwVersion, + (byte) (currentFwVersion >> 8), + (byte) (currentFwVersion >> 16), + (byte) (currentFwVersion >> 24), + (byte) newFwVersion, + (byte) (newFwVersion >> 8), + (byte) (newFwVersion >> 16), + (byte) (newFwVersion >> 24), + (byte) newFwSize, + (byte) (newFwSize >> 8), + (byte) checksum, + (byte) (checksum >> 8), + (byte) something // 0 TEST, only for Mi1S! + }; + +// // send to CONTROL POINT: +// if (!this.b(CONTROL_POINT, new byte[]{7, +// (byte) currentFwVersion, (byte) (currentFwVersion >> 8), (byte) (currentFwVersion >> 16), (byte) (currentFwVersion >> 24), +// (byte) newFwVersion, (byte) (newFwVersion >> 8), (byte) (newFwVersion >> 16), (byte) (newFwVersion >> 24), +// (byte) newFwSize, (byte) (newFwSize >> 8), +// (byte) checksum, (byte) (checksum >> 8), (byte) something})) { +// return false; +// } +// // wait for bq != -1 +// if (bq == 12) { +// return true; +// } + return fwInfo; + } + + /** * Method that uploads a firmware (fwbytes) to the MiBand. * The firmware has to be splitted into chunks of 20 bytes each, and periodically a COMMAND_SYNC comand has to be issued to the MiBand. @@ -173,18 +315,17 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { * @return whether the transfer succeeded or not. Only a BT layer exception will cause the transmission to fail. * @see MiBandSupport#handleNotificationNotif */ - private boolean sendFirmwareData(byte fwbytes[]) { + private boolean sendFirmwareData(byte[] fwbytes) { int len = fwbytes.length; final int packetLength = 20; int packets = len / packetLength; - byte fwChunk[] = new byte[packetLength]; int firmwareProgress = 0; try { TransactionBuilder builder = performInitialized("send firmware packet"); for (int i = 0; i < packets; i++) { - fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength); + byte[] fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength); builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), fwChunk); firmwareProgress += packetLength; @@ -198,8 +339,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } if (!(len % packetLength == 0)) { - byte lastChunk[] = new byte[len % packetLength]; - lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len); + byte[] lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len); builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), lastChunk); firmwareProgress += len % packetLength; } @@ -220,4 +360,134 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } return true; } + + private abstract class UpdateCoordinator { + private final boolean reboot; + + public UpdateCoordinator(boolean needsReboot) { + this.reboot = needsReboot; + } + + abstract byte[] getFirmwareInfo(); + + public abstract byte[] getFirmwareBytes(); + + public abstract boolean initNextOperation(); + + public boolean sendFwInfo() { + try { + TransactionBuilder builder = performInitialized("send firmware info"); + builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext())); + builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), getFirmwareInfo()); + builder.queue(getQueue()); + return true; + } catch (IOException e) { + LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e); + return false; + } + } + + public boolean sendFwData() { + return sendFirmwareData(getFirmwareBytes()); + } + + public boolean needsReboot() { + return false; // FIXME: renable rebooting +// return reboot; + } + } + + private class SingleUpdateCoordinator extends UpdateCoordinator { + + private final byte[] fwInfo; + private final byte[] fwData; + + public SingleUpdateCoordinator(byte[] fwInfo, byte[] fwData, boolean reboot) { + super(reboot); + this.fwInfo = fwInfo; + this.fwData = fwData; + } + + @Override + public byte[] getFirmwareInfo() { + return fwInfo; + } + + @Override + public byte[] getFirmwareBytes() { + return fwData; + } + + @Override + public boolean initNextOperation() { + return false; + } + } + + enum State { + INITIAL, + SEND_FW2, + SEND_FW1, + FINISHED, UNKNOWN + } + + private class DoubleUpdateCoordinator extends UpdateCoordinator { + + private final byte[] fw1Info; + private final byte[] fw1Data; + + private final byte[] fw21nfo; + private final byte[] fw2Data; + + private byte[] currentFwInfo; + private byte[] currentFwData; + + private State state = State.INITIAL; + + public DoubleUpdateCoordinator(byte[] fw1Info, byte[] fw1Data, byte[] fw2Info, byte[] fw2Data, boolean reboot) { + super(reboot); + this.fw1Info = fw1Info; + this.fw1Data = fw1Data; + this.fw21nfo = fw2Info; + this.fw2Data = fw2Data; + + // start with fw2 (heart rate) + currentFwInfo = fw2Info; + currentFwData = fw2Data; + } + + @Override + public byte[] getFirmwareInfo() { + return currentFwInfo; + } + + @Override + public byte[] getFirmwareBytes() { + return currentFwData; + } + + @Override + public boolean initNextOperation() { + switch (state) { + case INITIAL: + currentFwInfo = fw21nfo; + currentFwData = fw2Data; + state = State.SEND_FW2; + return true; + case SEND_FW2: + currentFwInfo = fw1Info; + currentFwData = fw1Data; + state = State.SEND_FW1; + return fw1Info != null && fw1Data != null; + case SEND_FW1: + currentFwInfo = null; + currentFwData = null; + state = State.FINISHED; + return false; // we're done + default: + state = State.UNKNOWN; + } + return false; + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc9e2a29b..22fafcfd0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,7 @@