Mi2: support for updating firmware fonts (*.ft, *.ft.en)

This is related to #560, but alas is not sufficient for enabling text
notifications.
master
cpfeiffer 2017-03-07 23:20:59 +01:00
parent 2b17d7fb14
commit 9411f80440
6 changed files with 157 additions and 15 deletions

View File

@ -89,6 +89,26 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\.ft" />
<data android:pathPattern="/.*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\.pbw" />
<data android:pathPattern="/.*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\.pbw" />
@ -142,6 +162,26 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\.ft" />
<data android:pathPattern="/.*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\.pbw" />
<data android:pathPattern="/.*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\.pbw" />

View File

@ -0,0 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
public enum FirmwareType {
FIRMWARE((byte) 0),
FONT((byte) 1),
UNKNOWN1((byte) 2),
UNKNOWN2((byte) 3),
INVALID(Byte.MIN_VALUE);
private final byte value;
FirmwareType(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
}

View File

@ -27,13 +27,23 @@ public class Mi2FirmwareInfo {
(byte) 0xf3,
(byte) 0xe7,
};
private static final int FW_HEADER_OFFSET = 0x150;
private static final byte[] FT_HEADER = new byte[] { // HMZK font file (*.ft, *.ft.xx)
0x48,
0x4d,
0x5a,
0x4b
};
private static Map<Integer,String> crcToVersion = new HashMap<>();
static {
crcToVersion.put(41899, "1.0.0.39");
}
private FirmwareType firmwareType = FirmwareType.FIRMWARE;
public static String toVersion(int crc16) {
return crcToVersion.get(crc16);
}
@ -51,6 +61,18 @@ public class Mi2FirmwareInfo {
this.bytes = bytes;
crc16 = CheckSums.getCRC16(bytes);
firmwareVersion = crcToVersion.get(crc16);
firmwareType = determineFirmwareType(bytes);
}
private FirmwareType determineFirmwareType(byte[] bytes) {
if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
return FirmwareType.FONT;
}
if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
// TODO: this is certainly not a correct validation, but it works for now
return FirmwareType.FIRMWARE;
}
return FirmwareType.INVALID;
}
public boolean isGenerallyCompatibleWith(GBDevice device) {
@ -58,8 +80,7 @@ public class Mi2FirmwareInfo {
}
public boolean isHeaderValid() {
// TODO: this is certainly not a correct validation, but it works for now
return ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET);
return getFirmwareType() != FirmwareType.INVALID;
}
public void checkValid() throws IllegalArgumentException {
@ -84,4 +105,8 @@ public class Mi2FirmwareInfo {
public int getFirmwareVersion() {
return getCrc16(); // HACK until we know how to determine the version from the fw bytes
}
public FirmwareType getFirmwareType() {
return firmwareType;
}
}

View File

@ -23,6 +23,7 @@ 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.miband2.AbstractMiBand2Operation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.FirmwareType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2FirmwareInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -113,7 +114,12 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
break;
}
case MiBand2Service.COMMAND_FIRMWARE_CHECKSUM: {
sendApplyReboot(getFirmwareInfo());
if (getFirmwareInfo().getFirmwareType() == FirmwareType.FIRMWARE) {
getSupport().onReboot();
} else {
GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
done();
}
break;
}
case MiBand2Service.COMMAND_FIRMWARE_REBOOT: {
@ -152,12 +158,20 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
int fwSize = getFirmwareInfo().getSize();
byte[] sizeBytes = BLETypeConversions.fromUint24(fwSize);
byte[] bytes = new byte[]{
MiBand2Service.COMMAND_FIRMWARE_INIT,
sizeBytes[0],
sizeBytes[1],
sizeBytes[2],
};
int arraySize = 4;
boolean isFirmwareCode = getFirmwareInfo().getFirmwareType() == FirmwareType.FIRMWARE;
if (!isFirmwareCode) {
arraySize++;
}
byte[] bytes = new byte[arraySize];
int i = 0;
bytes[i++] = MiBand2Service.COMMAND_FIRMWARE_INIT;
bytes[i++] = sizeBytes[0];
bytes[i++] = sizeBytes[1];
bytes[i++] = sizeBytes[2];
if (!isFirmwareCode) {
bytes[i++] = getFirmwareInfo().getFirmwareType().getValue();
}
builder.write(fwCControlChar, bytes);
builder.queue(getQueue());
@ -237,12 +251,6 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
builder.queue(getQueue());
}
private void sendApplyReboot(Mi2FirmwareInfo firmwareInfo) throws IOException {
TransactionBuilder builder = performInitialized("send firmware reboot");
builder.write(fwCControlChar, new byte[] { MiBand2Service.COMMAND_FIRMWARE_REBOOT});
builder.queue(getQueue());
}
private Mi2FirmwareInfo getFirmwareInfo() {
return firmwareInfo;
}

View File

@ -52,4 +52,14 @@ public class ArrayUtils {
}
return result;
}
/**
* Returns true if the given byte array starts with the given values
* @param array the array to check
* @param values the values which the other array is checked to start with
* @return
*/
public static boolean startsWith(byte[] array, byte[] values) {
return equals(array, values, 0);
}
}

View File

@ -111,6 +111,46 @@ public class ArrayUtilsTest extends TestBase {
assertFalse(ArrayUtils.equals(DATA_5, new byte[] {3, 4, 6}, 2));
}
@Test
public void testStartsWith1() throws Exception {
assertTrue(ArrayUtils.startsWith(DATA_5, new byte[] {1}));
}
@Test
public void testStartsWith2() throws Exception {
assertTrue(ArrayUtils.startsWith(DATA_5, new byte[] {1, 2}));
}
@Test
public void testStartsWithAll() throws Exception {
assertTrue(ArrayUtils.startsWith(DATA_5, DATA_5.clone()));
}
@Test
public void testStartsWithEmpty() throws Exception {
assertTrue(ArrayUtils.startsWith(DATA_5, EMPTY));
}
@Test
public void testStartsWithFail1() throws Exception {
try {
ArrayUtils.startsWith(DATA_5, null);
fail("should have thrown an exception");
} catch (IllegalArgumentException ex) {
// expected
}
}
@Test
public void testStartsWithFail3() throws Exception {
assertFalse(ArrayUtils.startsWith(DATA_5, new byte[] {2, 3}));
}
@Test
public void testStartsWithFail4() throws Exception {
assertFalse(ArrayUtils.startsWith(DATA_5, new byte[] {1, 2, 3, 4, 5, 6}));
}
private byte[] b(int b) {
return new byte[] {(byte) b};
}