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 @@ FW/App installer You are about to install firmware %s instead of the one currently on your Mi Band. + You are about to install firmwares %1$s and %2$s instead of the ones currently on your Mi Band. This firmware has been tested and is known to be compatible with Gadgetbridge. "This firmware is untested and may not be compatible with Gadgetbridge.\n\nYou are NOT encouraged to flash it to your Mi Band!" If you still want to proceed and things continue to work properly afterwards, please tell the Gadgetbridge developers to whitelist firmware version: %s