Enhanced support for firmware detection, recognition and upgrade #234

Also supports double firmware upgrade for Mi1S.
- so far, only hr firmware upgrade is tested for 1S
- adds junit testcases for firmware recognition and handling
here
cpfeiffer 2016-03-20 01:05:23 +01:00
parent 6d8d6d5bc8
commit 4f956000c5
14 changed files with 573 additions and 257 deletions

View File

@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -10,27 +11,23 @@ import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
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.service.devices.miband.AbstractMiFirmwareInfo;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
/**
* Also see Mi1SInfo.
* Also see Mi1SFirmwareInfo.
*/
public class MiBandFWHelper {
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
private static final int MI_FW_BASE_OFFSET = 1056;
private static final int MI1S_FW_BASE_OFFSET = 1092;
private final Uri uri;
private final ContentResolver cr;
private byte[] fw;
private int baseOffset = -1;
private final @NonNull AbstractMiFirmwareInfo firmwareInfo;
private final @NonNull byte[] fw;
/**
* Provides a different notification API which is also used on Mi1A devices.
@ -56,7 +53,6 @@ public class MiBandFWHelper {
throw new IOException("No content resolver");
}
baseOffset = determineBaseOffset(uri);
String pebblePattern = ".*\\.(pbw|pbz|pbl)";
if (uri.getPath().matches(pebblePattern)) {
@ -65,62 +61,23 @@ public class MiBandFWHelper {
try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))) {
this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB
if (fw.length <= getOffsetFirmwareVersionMajor()) {
throw new IOException("This doesn't seem to be a Mi Band firmware, file size too small.");
}
byte firmwareVersionMajor = fw[getOffsetFirmwareVersionMajor()];
if (!isSupportedFirmwareVersionMajor(firmwareVersionMajor)) {
throw new IOException("Firmware major version not supported, either too new or this isn't a Mi Band firmware: " + firmwareVersionMajor);
}
this.firmwareInfo = determineFirmwareInfoFor(fw);
} catch (IOException ex) {
throw ex; // pass through
} catch (IllegalArgumentException ex) {
throw new IOException("This doesn't seem to be a Mi Band firmware: " + ex.getLocalizedMessage(), ex);
} catch (Exception e) {
throw new IOException("Error reading firmware file: " + uri.toString(), e);
}
}
private int getOffsetFirmwareVersionMajor() {
return baseOffset + 3;
}
private int getOffsetFirmwareVersionMinor() {
return baseOffset + 2;
}
private int getOffsetFirmwareVersionRevision() {
return baseOffset + 1;
}
private int getOffsetFirmwareVersionBuild() {
return baseOffset;
}
private int determineBaseOffset(Uri uri) throws IOException {
String name = uri.getLastPathSegment().toLowerCase();
if (name.startsWith("mili")) {
if (name.contains("_hr")) {
return MI1S_FW_BASE_OFFSET;
}
return MI_FW_BASE_OFFSET;
} else {
throw new IOException("Unknown file name " + name + "; cannot recognize firmware by it.");
}
}
private byte getFirmwareVersionMajor() {
return fw[getOffsetFirmwareVersionMajor()];
}
private byte getFirmwareVersionMinor() {
return fw[getOffsetFirmwareVersionMinor()];
}
private boolean isSupportedFirmwareVersionMajor(byte firmwareVersionMajor) {
return firmwareVersionMajor == 1 || firmwareVersionMajor == 4 || firmwareVersionMajor == 5;
}
public int getFirmwareVersion() {
return (fw[getOffsetFirmwareVersionMajor()] << 24) | (fw[getOffsetFirmwareVersionMinor()] << 16) | (fw[getOffsetFirmwareVersionRevision()] << 8) | fw[getOffsetFirmwareVersionBuild()];
// FIXME: UnsupportedOperationException!
return firmwareInfo.getFirst().getFirmwareVersion();
}
public int getFirmware2Version() {
return firmwareInfo.getFirst().getFirmwareVersion();
}
public static String formatFirmwareVersion(int version) {
@ -135,11 +92,11 @@ public class MiBandFWHelper {
}
public String getHumanFirmwareVersion() {
return String.format(Locale.US, "%d.%d.%d.%d", fw[getOffsetFirmwareVersionMajor()], fw[getOffsetFirmwareVersionMinor()], fw[getOffsetFirmwareVersionRevision()], fw[getOffsetFirmwareVersionBuild()]);
return format(getFirmwareVersion());
}
public String getHumanFirmwareVersion2() {
return format(Mi1SInfo.getFirmware2VersionFrom(getFw()));
return format(firmwareInfo.getSecond().getFirmwareVersion());
}
public String format(int version) {
@ -160,20 +117,24 @@ public class MiBandFWHelper {
}
public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
String deviceHW = device.getHardwareVersion();
if (MiBandConst.MI_1.equals(deviceHW)) {
return getFirmwareVersionMajor() == 1;
}
if (MiBandConst.MI_1A.equals(deviceHW)) {
return getFirmwareVersionMajor() == 5;
}
if (MiBandConst.MI_1S.equals(deviceHW)) {
return getFirmwareVersionMajor() == 4;
}
return false;
return firmwareInfo.isGenerallyCompatibleWith(device);
}
public boolean isSingleFirmware() {
return Mi1SInfo.isSingleMiBandFirmware(getFw());
return firmwareInfo.isSingleMiBandFirmware();
}
/**
*
* @param wholeFirmwareBytes
* @return
* @throws IllegalArgumentException when the data is not recognized as firmware data
*/
public static @NonNull AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
return AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes);
}
public AbstractMiFirmwareInfo getFirmwareInfo() {
return firmwareInfo;
}
}

View File

@ -85,7 +85,7 @@ public class UserInfo {
sequence[8] = (byte) (type & 0xff);
int aliasFrom = 9;
if (mDeviceInfo.isMili1A() || mDeviceInfo.isMilli1S()) {
if (mDeviceInfo.isMili1A() || mDeviceInfo.isMili1S()) {
sequence[9] = (byte) (mDeviceInfo.feature & 255);
sequence[10] = (byte) (mDeviceInfo.appearance & 255);
aliasFrom = 11;

View File

@ -0,0 +1,74 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
/**
* Some helper methods for Mi1 and Mi1A firmware.
*/
public abstract class AbstractMi1FirmwareInfo extends AbstractMiFirmwareInfo {
private static final Logger LOG = LoggerFactory.getLogger(AbstractMi1FirmwareInfo.class);
private static final int MI1_FW_BASE_OFFSET = 1056;
protected AbstractMi1FirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
}
@Override
public int getFirmwareOffset()
{
return 0;
}
public int getFirmwareLength()
{
return wholeFirmwareBytes.length;
}
public int getFirmwareVersion()
{
return (wholeFirmwareBytes[getOffsetFirmwareVersionMajor()] << 24)
| (wholeFirmwareBytes[getOffsetFirmwareVersionMinor()] << 16)
| (wholeFirmwareBytes[getOffsetFirmwareVersionRevision()] << 8)
| wholeFirmwareBytes[getOffsetFirmwareVersionBuild()];
}
private int getOffsetFirmwareVersionMajor() {
return MI1_FW_BASE_OFFSET + 3;
}
private int getOffsetFirmwareVersionMinor() {
return MI1_FW_BASE_OFFSET + 2;
}
private int getOffsetFirmwareVersionRevision() {
return MI1_FW_BASE_OFFSET + 1;
}
private int getOffsetFirmwareVersionBuild() {
return MI1_FW_BASE_OFFSET;
}
@Override
protected boolean isGenerallySupportedFirmware() {
if (!isSingleMiBandFirmware()) {
return false;
}
try {
int majorVersion = getFirmwareVersionMajor();
return majorVersion == getSupportedMajorVersion();
} catch (IllegalArgumentException e) {
return false;
} catch (IndexOutOfBoundsException ex) {
return false;
}
}
protected abstract int getSupportedMajorVersion();
}

View File

@ -0,0 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public abstract class AbstractMi1SFirmwareInfo extends AbstractMiFirmwareInfo {
public AbstractMi1SFirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
}
@Override
public boolean isGenerallyCompatibleWith(GBDevice device) {
return MiBandConst.MI_1S.equals(device.getHardwareVersion());
}
}

View File

@ -0,0 +1,94 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public abstract class AbstractMiFirmwareInfo {
/**
*
* @param wholeFirmwareBytes
* @return
* @throws IllegalArgumentException when the data is not recognized as firmware data
*/
public static @NonNull AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
AbstractMiFirmwareInfo[] candidates = getFirmwareInfoCandidatesFor(wholeFirmwareBytes);
if (candidates.length == 0) {
throw new IllegalArgumentException("Unsupported data (maybe not even a firmware?).");
}
if (candidates.length == 1) {
return candidates[0];
}
throw new IllegalArgumentException("don't know for which device the firmware is, matches multiple devices");
}
private static AbstractMiFirmwareInfo[] getFirmwareInfoCandidatesFor(byte[] wholeFirmwareBytes) {
AbstractMiFirmwareInfo[] candidates = new AbstractMiFirmwareInfo[3];
int i = 0;
Mi1FirmwareInfo mi1Info = Mi1FirmwareInfo.getInstance(wholeFirmwareBytes);
if (mi1Info != null) {
candidates[i++] = mi1Info;
}
Mi1AFirmwareInfo mi1aInfo = Mi1AFirmwareInfo.getInstance(wholeFirmwareBytes);
if (mi1aInfo != null) {
candidates[i++] = mi1aInfo;
}
Mi1SFirmwareInfo mi1sInfo = Mi1SFirmwareInfo.getInstance(wholeFirmwareBytes);
if (mi1sInfo != null) {
candidates[i++] = mi1sInfo;
}
return Arrays.copyOfRange(candidates, 0, i);
}
@NonNull
protected byte[] wholeFirmwareBytes;
public AbstractMiFirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
this.wholeFirmwareBytes = wholeFirmwareBytes;
}
public abstract int getFirmwareOffset();
public abstract int getFirmwareLength();
public abstract int getFirmwareVersion();
protected abstract boolean isGenerallySupportedFirmware();
public abstract boolean isGenerallyCompatibleWith(GBDevice device);
public @NonNull byte[] getFirmwareBytes() {
return Arrays.copyOfRange(wholeFirmwareBytes, getFirmwareOffset(), getFirmwareLength());
}
public int getFirmwareVersionMajor() {
int version = getFirmwareVersion();
if (version > 0) {
return (version >> 24);
}
throw new IllegalArgumentException("bad firmware version: " + version);
}
public boolean isSingleMiBandFirmware() {
// TODO: not sure if this is a correct check!
if ((wholeFirmwareBytes[7] & 255) != 1) {
return true;
}
return false;
}
public AbstractMiFirmwareInfo getFirst() {
if (isSingleMiBandFirmware()) {
return this;
}
throw new UnsupportedOperationException(getClass().getName() + " must override getFirst() and getSecond()");
}
public AbstractMiFirmwareInfo getSecond() {
if (isSingleMiBandFirmware()) {
throw new UnsupportedOperationException(getClass().getName() + " only supports on firmware");
}
throw new UnsupportedOperationException(getClass().getName() + " must override getFirst() and getSecond()");
}
}

View File

@ -1,7 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
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 {
@ -76,7 +75,7 @@ public class DeviceInfo extends AbstractInfo {
}
public boolean supportsHeartrate() {
return isMilli1S();
return isMili1S();
}
@Override
@ -100,7 +99,7 @@ public class DeviceInfo extends AbstractInfo {
return feature == 5 && appearance == 0 || feature == 0 && hwVersion == 208;
}
public boolean isMilli1S() {
public boolean isMili1S() {
// TODO: this is probably not quite correct, but hopefully sufficient for early 1S support
return feature == 4 && appearance == 0 || feature == 4 && hwVersion == 4;
}
@ -112,7 +111,7 @@ public class DeviceInfo extends AbstractInfo {
if (isMili1A()) {
return MiBandConst.MI_1A;
}
if (isMilli1S()) {
if (isMili1S()) {
return MiBandConst.MI_1S;
}
return "?";

View File

@ -0,0 +1,37 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class Mi1AFirmwareInfo extends AbstractMi1FirmwareInfo {
private static final Logger LOG = LoggerFactory.getLogger(Mi1AFirmwareInfo.class);
public static Mi1AFirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
Mi1AFirmwareInfo info = new Mi1AFirmwareInfo(wholeFirmwareBytes);
if (info.isGenerallySupportedFirmware()) {
return info;
}
LOG.info("firmware not supported");
return null;
}
protected Mi1AFirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
}
@Override
protected int getSupportedMajorVersion() {
return 5;
}
@Override
public boolean isGenerallyCompatibleWith(GBDevice device) {
String hwVersion = device.getHardwareVersion();
return MiBandConst.MI_1A.equals(hwVersion);
}
}

View File

@ -0,0 +1,37 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class Mi1FirmwareInfo extends AbstractMi1FirmwareInfo {
private static final Logger LOG = LoggerFactory.getLogger(Mi1FirmwareInfo.class);
public static Mi1FirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
Mi1FirmwareInfo info = new Mi1FirmwareInfo(wholeFirmwareBytes);
if (info.isGenerallySupportedFirmware()) {
return info;
}
LOG.info("firmware not supported");
return null;
}
protected Mi1FirmwareInfo(@NonNull byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
}
@Override
protected int getSupportedMajorVersion() {
return 1;
}
@Override
public boolean isGenerallyCompatibleWith(GBDevice device) {
String hwVersion = device.getHardwareVersion();
return MiBandConst.MI_1.equals(hwVersion);
}
}

View File

@ -0,0 +1,69 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* FW1 is Mi Band firmware
* FW2 is heartrate firmware
*/
public class Mi1SFirmwareInfo extends AbstractMi1SFirmwareInfo {
private static final Logger LOG = LoggerFactory.getLogger(AbstractMi1FirmwareInfo.class);
private final Mi1SFirmwareInfoFW1 fw1Info;
private final Mi1SFirmwareInfoFW2 fw2Info;
private Mi1SFirmwareInfo(byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
fw1Info = new Mi1SFirmwareInfoFW1(wholeFirmwareBytes);
fw2Info = new Mi1SFirmwareInfoFW2(wholeFirmwareBytes);
}
@Override
public AbstractMiFirmwareInfo getFirst() {
return fw1Info;
}
@Override
public AbstractMiFirmwareInfo getSecond() {
return fw2Info;
}
public static @Nullable Mi1SFirmwareInfo getInstance(byte[] wholeFirmwareBytes) {
Mi1SFirmwareInfo info = new Mi1SFirmwareInfo(wholeFirmwareBytes);
if (info.isGenerallySupportedFirmware()) {
return info;
}
LOG.info("firmware not supported");
return null;
}
@Override
protected boolean isGenerallySupportedFirmware() {
if (isSingleMiBandFirmware()) {
return false;
}
try {
return fw1Info.isGenerallySupportedFirmware() && fw2Info.isGenerallySupportedFirmware();
} catch (IndexOutOfBoundsException ex) {
return false;
}
}
@Override
public int getFirmwareOffset() {
throw new UnsupportedOperationException("call this method on getFirmwareXInfo()");
}
@Override
public int getFirmwareLength() {
throw new UnsupportedOperationException("call this method on getFirmwareXInfo()");
}
@Override
public int getFirmwareVersion() {
throw new UnsupportedOperationException("call this method on getFirmwareXInfo()");
}
}

View File

@ -0,0 +1,53 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
/**
* FW1 is Mi Band firmware
* FW2 is heartrate firmware
*/
public class Mi1SFirmwareInfoFW1 extends AbstractMi1SFirmwareInfo {
private static final int MI1S_FW_BASE_OFFSET = 1092;
Mi1SFirmwareInfoFW1(@NonNull byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
}
@Override
public int getFirmwareOffset()
{
return (wholeFirmwareBytes[12] & 255) << 24
| (wholeFirmwareBytes[13] & 255) << 16
| (wholeFirmwareBytes[14] & 255) << 8
| (wholeFirmwareBytes[15] & 255);
}
@Override
public int getFirmwareLength()
{
return (wholeFirmwareBytes[16] & 255) << 24
| (wholeFirmwareBytes[17] & 255) << 16
| (wholeFirmwareBytes[18] & 255) << 8
| (wholeFirmwareBytes[19] & 255);
}
@Override
public int getFirmwareVersion()
{
return (wholeFirmwareBytes[8] & 255) << 24
| (wholeFirmwareBytes[9] & 255) << 16
| (wholeFirmwareBytes[10] & 255) << 8
| wholeFirmwareBytes[11] & 255;
}
@Override
protected boolean isGenerallySupportedFirmware() {
try {
int majorVersion = getFirmwareVersionMajor();
return majorVersion == 4;
} catch (IllegalArgumentException e) {
return false;
}
}
}

View File

@ -0,0 +1,51 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
/**
* FW1 is Mi Band firmware
* FW2 is heartrate firmware
*/
public class Mi1SFirmwareInfoFW2 extends AbstractMi1SFirmwareInfo {
Mi1SFirmwareInfoFW2(@NonNull byte[] wholeFirmwareBytes) {
super(wholeFirmwareBytes);
}
@Override
public int getFirmwareOffset()
{
return (wholeFirmwareBytes[26] & 255) << 24
| (wholeFirmwareBytes[27] & 255) << 16
| (wholeFirmwareBytes[28] & 255) << 8
| (wholeFirmwareBytes[29] & 255);
}
@Override
public int getFirmwareLength()
{
return (wholeFirmwareBytes[30] & 255) << 24
| (wholeFirmwareBytes[31] & 255) << 16
| (wholeFirmwareBytes[32] & 255) << 8
| (wholeFirmwareBytes[33] & 255);
}
@Override
protected boolean isGenerallySupportedFirmware() {
try {
int majorVersion = getFirmwareVersionMajor();
return majorVersion == 1;
} catch (IllegalArgumentException e) {
return false;
}
}
@Override
public int getFirmwareVersion()
{
return (wholeFirmwareBytes[22] & 255) << 24
| (wholeFirmwareBytes[23] & 255) << 16
| (wholeFirmwareBytes[24] & 255) << 8
| wholeFirmwareBytes[25] & 255;
}
}

View File

@ -1,68 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.support.annotation.NonNull;
/**
* FW1 is Mi Band firmware
* FW2 is heartrate firmware
*/
public class Mi1SInfo {
public static int getFirmware2OffsetIn(@NonNull byte[] wholeFirmwareBytes)
{
return (wholeFirmwareBytes[26] & 255) << 24
| (wholeFirmwareBytes[27] & 255) << 16
| (wholeFirmwareBytes[28] & 255) << 8
| (wholeFirmwareBytes[29] & 255);
}
public static int getFirmware2LengthIn(@NonNull byte[] wholeFirmwareBytes)
{
return (wholeFirmwareBytes[30] & 255) << 24
| (wholeFirmwareBytes[31] & 255) << 16
| (wholeFirmwareBytes[32] & 255) << 8
| (wholeFirmwareBytes[33] & 255);
}
public static int getFirmware1OffsetIn(@NonNull byte[] wholeFirmwareBytes)
{
return (wholeFirmwareBytes[12] & 255) << 24
| (wholeFirmwareBytes[13] & 255) << 16
| (wholeFirmwareBytes[14] & 255) << 8
| (wholeFirmwareBytes[15] & 255);
}
public static int getFirmware1LengthIn(@NonNull byte[] wholeFirmwareBytes)
{
return (wholeFirmwareBytes[16] & 255) << 24
| (wholeFirmwareBytes[17] & 255) << 16
| (wholeFirmwareBytes[18] & 255) << 8
| (wholeFirmwareBytes[19] & 255);
}
public static int getFirmware1VersionFrom(@NonNull byte[] wholeFirmwareBytes)
{
return (wholeFirmwareBytes[8] & 255) << 24
| (wholeFirmwareBytes[9] & 255) << 16
| (wholeFirmwareBytes[10] & 255) << 8
| wholeFirmwareBytes[11] & 255;
}
public static int getFirmware2VersionFrom(@NonNull byte[] wholeFirmwareBytes)
{
return (wholeFirmwareBytes[22] & 255) << 24
| (wholeFirmwareBytes[23] & 255) << 16
| (wholeFirmwareBytes[24] & 255) << 8
| wholeFirmwareBytes[25] & 255;
}
// FIXME: this method is wrong. We don't know a way to check if a firmware file
// contains one or more firmwares.
public static boolean isSingleMiBandFirmware(@NonNull byte[] wholeFirmwareBytes) {
if ((wholeFirmwareBytes[7] & 255) != 1) {
return true;
}
return false;
}
}

View File

@ -18,7 +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.AbstractMiFirmwareInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -28,8 +28,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
private final Uri uri;
private boolean firmwareInfoSent = false;
// private byte[] newFirmware;
// private boolean rebootWhenBandReady = false;
private UpdateCoordinator updateCoordinator;
public UpdateFirmwareOperation(Uri uri, MiBandSupport support) {
@ -41,11 +39,16 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
protected void doPerform() throws IOException {
MiBandFWHelper mFwHelper = new MiBandFWHelper(uri, getContext());
// if (getSupport().supportsHeartRate()) {
updateCoordinator = prepareFirmwareInfo1S(mFwHelper.getFw());
// } else {
// updateCoordinator = sendFirmwareInfo(mFwHelper.getFw(), mFwHelper.getFirmwareVersion());
// }
AbstractMiFirmwareInfo firmwareInfo = mFwHelper.getFirmwareInfo();
if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
}
if (getSupport().supportsHeartRate()) {
updateCoordinator = prepareFirmwareInfo1S(firmwareInfo);
} else {
updateCoordinator = prepareFirmwareInfo(mFwHelper.getFw(), mFwHelper.getFirmwareVersion());
}
updateCoordinator.initNextOperation();
// updateCoordinator.initNextOperation(); // FIXME: remove, just testing mi band 1s fw update
@ -75,7 +78,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
}
/**
* React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
* React to messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
* characteristic,
* These messages appear to be always 1 byte long, with values that are listed in MiBandService.
* It is not excluded that there are further values which are still unknown.
@ -97,25 +100,19 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
switch (value[0]) {
case MiBandService.NOTIFY_FW_CHECK_SUCCESS:
// 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 {
if (!updateCoordinator.sendFwData()) {
//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;
}
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;
done();
break;
case MiBandService.NOTIFY_FIRMWARE_UPDATE_SUCCESS:
@ -130,7 +127,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
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;
} else {
LOG.error("BUG: Successful firmware update without reboot???");
}
@ -140,7 +136,6 @@ 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;
done();
break;
@ -150,63 +145,41 @@ 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.
// * <p/>
// * 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);
// }
/**
* 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.
* <p/>
* The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it.
*
* @param newFwVersion
* @see MiBandSupport#handleNotificationNotif
*/
private UpdateCoordinator prepareFirmwareInfo(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);
private UpdateCoordinator prepareFirmwareInfo1S(byte[] wholeFirmwareBytes) {
int fw2Version = Mi1SInfo.getFirmware2VersionFrom(wholeFirmwareBytes);
int fw2Offset = Mi1SInfo.getFirmware2OffsetIn(wholeFirmwareBytes);
int fw2Length = Mi1SInfo.getFirmware2LengthIn(wholeFirmwareBytes);
byte[] fwInfo = prepareFirmwareUpdateA(currentFwVersion, newFwVersion, newFwSize, checksum);
return new SingleUpdateCoordinator(fwInfo, fwBytes, true);
}
int fw1Version = Mi1SInfo.getFirmware1VersionFrom(wholeFirmwareBytes);
int fw1Offset = Mi1SInfo.getFirmware1OffsetIn(wholeFirmwareBytes);
int fw1Length = Mi1SInfo.getFirmware1LengthIn(wholeFirmwareBytes);
private UpdateCoordinator prepareFirmwareInfo1S(AbstractMiFirmwareInfo info) {
if (info.isSingleMiBandFirmware()) {
throw new IllegalArgumentException("preparing single fw not allowed for 1S");
}
int fw2Version = info.getSecond().getFirmwareVersion();
int fw1Version = info.getFirst().getFirmwareVersion();
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);
byte[] fw2Bytes = info.getSecond().getFirmwareBytes();
int fw2Checksum = CheckSums.getCRC16(fw2Bytes) ^ encodedMac;
byte[] fw1Bytes = new byte[fw1Length];
System.arraycopy(wholeFirmwareBytes, fw1Offset, fw1Bytes, 0, fw1Length);
byte[] fw1Bytes = info.getFirst().getFirmwareBytes();
int fw1Checksum = encodedMac ^ CheckSums.getCRC16(fw1Bytes);
// check firmware validity?
@ -215,22 +188,18 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
int fw2OldVersion = getSupport().getDeviceInfo().getHeartrateFirmwareVersion();
boolean rebootWhenFinished = true;
if (Mi1SInfo.isSingleMiBandFirmware(wholeFirmwareBytes)) {
if (info.isSingleMiBandFirmware()) {
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");
LOG.info("is multi Mi Band firmware, sending fw2 (hr) first");
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) {
@ -262,12 +231,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
(byte) checksum,
(byte) (checksum >> 8)
};
// 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;
}
@ -286,21 +249,8 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
(byte) (newFwSize >> 8),
(byte) checksum,
(byte) (checksum >> 8),
(byte) something // 0 TEST, only for Mi1S!
(byte) something
};
// // 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;
}
@ -388,6 +338,9 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
}
public boolean sendFwData() {
// if (true) {
// return true; // FIXME: temporarily disabled firmware sending
// }
return sendFirmwareData(getFirmwareBytes());
}

View File

@ -6,6 +6,7 @@ import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@ -18,19 +19,19 @@ public class FirmwareTest {
private static final int MI1S_FW1_VERSION = 0;
private static final int MI1S_FW2_VERSION = 0;
private static final int SINGLE = 1;
private static final int DOUBLE = 2;
@Test
public void testFirmwareMi() throws Exception{
public void testFirmwareMi1() throws Exception{
byte[] wholeFw = getFirmwareMi();
Assert.assertNotNull(wholeFw);
Assert.assertTrue(Mi1SInfo.isSingleMiBandFirmware(wholeFw));
int calculatedLength = Mi1SInfo.getFirmware1LengthIn(wholeFw);
Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength < wholeFw.length);
int calculatedVersion = Mi1SInfo.getFirmware1VersionFrom(wholeFw);
// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI_FW_VERSION, calculatedVersion);
AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, SINGLE);
int calculatedVersion = info.getFirmwareVersion();
String version = MiBandFWHelper.formatFirmwareVersion(calculatedVersion);
Assert.assertTrue(version.startsWith("1."));
// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI_FW_VERSION, calculatedVersion);
}
@Test
@ -38,14 +39,11 @@ public class FirmwareTest {
byte[] wholeFw = getFirmwareMi1A();
Assert.assertNotNull(wholeFw);
Assert.assertTrue(Mi1SInfo.isSingleMiBandFirmware(wholeFw));
int calculatedLength = Mi1SInfo.getFirmware1LengthIn(wholeFw);
Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength < wholeFw.length);
int calculatedVersion = Mi1SInfo.getFirmware1VersionFrom(wholeFw);
// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI1A_FW_VERSION, calculatedVersion);
AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, SINGLE);
int calculatedVersion = info.getFirmwareVersion();
String version = MiBandFWHelper.formatFirmwareVersion(calculatedVersion);
Assert.assertTrue(version.startsWith("5."));
// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI1A_FW_VERSION, calculatedVersion);
}
@Test
@ -53,31 +51,71 @@ public class FirmwareTest {
byte[] wholeFw = getFirmwareMi1S();
Assert.assertNotNull(wholeFw);
Assert.assertFalse(Mi1SInfo.isSingleMiBandFirmware(wholeFw));
AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, DOUBLE);
// Mi Band version
int calculatedLengthFw1 = Mi1SInfo.getFirmware1LengthIn(wholeFw);
int calculatedOffsetFw1 = Mi1SInfo.getFirmware1OffsetIn(wholeFw);
int calculatedLengthFw1 = info.getFirst().getFirmwareLength();
int calculatedOffsetFw1 = info.getFirst().getFirmwareOffset();
int endIndexFw1 = calculatedOffsetFw1 + calculatedLengthFw1;
int calculatedLengthFw2 = Mi1SInfo.getFirmware2LengthIn(wholeFw);
int calculatedOffsetFw2 = Mi1SInfo.getFirmware2OffsetIn(wholeFw);
int calculatedLengthFw2 = info.getSecond().getFirmwareLength();
int calculatedOffsetFw2 = info.getSecond().getFirmwareOffset();
int endIndexFw2 = calculatedOffsetFw2 + calculatedLengthFw2;
Assert.assertTrue(endIndexFw1 <= wholeFw.length - calculatedLengthFw2);
Assert.assertTrue(endIndexFw2 <= wholeFw.length);
Assert.assertTrue(endIndexFw1 <= calculatedOffsetFw2);
int calculatedVersionFw1 = Mi1SInfo.getFirmware1VersionFrom(wholeFw);
int calculatedVersionFw1 = info.getFirst().getFirmwareVersion();
// Assert.assertEquals("Unexpected firmware 1 version: " + calculatedVersionFw1, MI1S_FW1_VERSION, calculatedVersionFw1);
String version1 = MiBandFWHelper.formatFirmwareVersion(calculatedVersionFw1);
Assert.assertTrue(version1.startsWith("4."));
// HR version
int calculatedVersionFw2 = Mi1SInfo.getFirmware2VersionFrom(wholeFw);
int calculatedVersionFw2 = info.getSecond().getFirmwareVersion();
// Assert.assertEquals("Unexpected firmware 2 version: " + calculatedVersionFw2, MI1S_FW2_VERSION, calculatedVersionFw2);
String version2 = MiBandFWHelper.formatFirmwareVersion(calculatedVersionFw2);
Assert.assertTrue(version2.startsWith("1."));
try {
info.getFirmwareVersion();
Assert.fail("should not get fw version from AbstractMi1SFirmwareInfo");
} catch (UnsupportedOperationException expected) {}
Assert.assertNotEquals(info.getFirst().getFirmwareOffset(), info.getSecond().getFirmwareOffset());
Assert.assertFalse(Arrays.equals(info.getFirst().getFirmwareBytes(), info.getSecond().getFirmwareBytes()));
}
private AbstractMiFirmwareInfo getFirmwareInfo(byte[] wholeFw, int numFirmwares) {
AbstractMiFirmwareInfo info = AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFw);
switch (numFirmwares) {
case SINGLE: {
Assert.assertTrue("should be single miband firmware", info.isSingleMiBandFirmware());
Assert.assertSame(info, info.getFirst());
try {
info.getSecond();
Assert.fail("should throw UnsuportedOperationException");
} catch (UnsupportedOperationException expected) {}
int calculatedLength = info.getFirmwareLength();
Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength <= wholeFw.length);
break;
}
case DOUBLE: {
Assert.assertFalse("should not be single miband firmware", info.isSingleMiBandFirmware());
Assert.assertNotSame(info, info.getFirst());
Assert.assertNotSame(info, info.getSecond());
Assert.assertNotSame(info.getFirst(), info.getSecond());
int calculatedLength = info.getFirst().getFirmwareLength();
Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength <= wholeFw.length);
calculatedLength = info.getSecond().getFirmwareLength();
Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength <= wholeFw.length);
break;
}
default:
Assert.fail("unexpected numFirmwares: " + numFirmwares);
}
Assert.assertTrue(info.isGenerallySupportedFirmware());
return info;
}
private File getFirmwareDir() {