mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
CMF Watch Pro: Firmware update
Tested by updating to 11.0.0.57
This commit is contained in:
+20
-14
@@ -473,8 +473,10 @@ public class BleNamesResolver {
|
||||
mServices.put("16187f00-0000-1000-8000-00807f9b34fb", "(Propr: Xiaomi Wear Service - Mi Smart Watch 4C/Redmi Band)");
|
||||
mServices.put("1314f000-1000-9000-7000-301291e21220", "(Propr: Xiaomi Wear Service - Mi Watch/Mi Watch Color/Mi Watch Color Sport)");
|
||||
mServices.put("7495fe00-a7f3-424b-92dd-4a006a3aef56", "(Propr: Xiaomi Wear Service - Mi Watch CN)");
|
||||
//mServices.put("0000fff0-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command");
|
||||
//mServices.put("02f00000-0000-0000-0000-00000000ffe0", "(Propr: Nothing CMF Data");
|
||||
mServices.put("0000fff0-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command");
|
||||
mServices.put("02f00000-0000-0000-0000-00000000ffe0", "(Propr: Nothing CMF Data");
|
||||
mServices.put("02f00000-0000-0000-0000-00000000fe00", "(Propr: Nothing CMF Firmware");
|
||||
mServices.put("77d4e67c-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell");
|
||||
mServices.put("9b012401-bc30-ce9a-e111-0f67e491abde", "(Propr: Garmin GFDI V0)");
|
||||
mServices.put("6a4e2401-667b-11e3-949a-0800200c9a66", "(Propr: Garmin GFDI V1)");
|
||||
mServices.put("6a4e2800-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML)");
|
||||
@@ -972,18 +974,22 @@ public class BleNamesResolver {
|
||||
mCharacteristics.put("6a4e2823-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML 3 TX)");
|
||||
mCharacteristics.put("6a4e2814-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML 4 RX)");
|
||||
mCharacteristics.put("6a4e2824-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML 4 TX)");
|
||||
//mCharacteristics.put("0000fff1-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command Read");
|
||||
//mCharacteristics.put("0000fff2-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command Write");
|
||||
//mCharacteristics.put("02f00000-0000-0000-0000-00000000ffe1", "(Propr: Nothing CMF Data Write");
|
||||
//mCharacteristics.put("02f00000-0000-0000-0000-00000000ffe2", "(Propr: Nothing CMF Data Read");
|
||||
//mCharacteristics.put("00010203-0405-0607-0809-0a0b0c0d2b12", "(Propr: Telink OTA Write)");
|
||||
//mCharacteristics.put("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd TIME)");
|
||||
//mCharacteristics.put("ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd BATTERY)");
|
||||
//mCharacteristics.put("ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd TEMPERATURE_UNIT)");
|
||||
//mCharacteristics.put("ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd CONN_INTERVAL)");
|
||||
//mCharacteristics.put("ebe0ccbc-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd HISTORY)");
|
||||
//mCharacteristics.put("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd LIVE_DATA)");
|
||||
//mCharacteristics.put("ebe0ccba-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd HISTORY_LAST_ID)");
|
||||
mCharacteristics.put("0000fff1-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command Read");
|
||||
mCharacteristics.put("0000fff2-0000-1000-8000-00805f9b34fb", "(Propr: Nothing CMF Command Write");
|
||||
mCharacteristics.put("02f00000-0000-0000-0000-00000000ffe1", "(Propr: Nothing CMF Data Write");
|
||||
mCharacteristics.put("02f00000-0000-0000-0000-00000000ffe2", "(Propr: Nothing CMF Data Read");
|
||||
mCharacteristics.put("77d4ff01-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell Write");
|
||||
mCharacteristics.put("77d4ff02-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell Read");
|
||||
mCharacteristics.put("02f00000-0000-0000-0000-00000000ff01", "(Propr: Nothing CMF Firmware Write");
|
||||
mCharacteristics.put("02f00000-0000-0000-0000-00000000ff02", "(Propr: Nothing CMF Firmware Read");
|
||||
mCharacteristics.put("00010203-0405-0607-0809-0a0b0c0d2b12", "(Propr: Telink OTA Write)");
|
||||
mCharacteristics.put("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd TIME)");
|
||||
mCharacteristics.put("ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd BATTERY)");
|
||||
mCharacteristics.put("ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd TEMPERATURE_UNIT)");
|
||||
mCharacteristics.put("ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd CONN_INTERVAL)");
|
||||
mCharacteristics.put("ebe0ccbc-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd HISTORY)");
|
||||
mCharacteristics.put("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd LIVE_DATA)");
|
||||
mCharacteristics.put("ebe0ccba-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd HISTORY_LAST_ID)");
|
||||
mCharacteristics.put("00000001-0000-3512-2118-0009af100700", "(Propr: Huami Raw Sensor Control)");
|
||||
mCharacteristics.put("00000002-0000-3512-2118-0009af100700", "(Propr: Huami Raw Sensor Data)");
|
||||
mCharacteristics.put("00000003-0000-3512-2118-0009af100700", "(Propr: Huami Configuration)");
|
||||
|
||||
+1
@@ -185,6 +185,7 @@ public class CmfCharacteristic {
|
||||
case AUTH_PAIR_REQUEST:
|
||||
case AUTH_PAIR_REPLY:
|
||||
case DATA_CHUNK_WRITE_AGPS:
|
||||
case DATA_CHUNK_WRITE_FIRMWARE:
|
||||
case DATA_CHUNK_WRITE_WATCHFACE:
|
||||
return false;
|
||||
}
|
||||
|
||||
+8
@@ -42,13 +42,21 @@ public enum CmfCommand {
|
||||
CONTACTS_GET(0x00d5, 0x0002),
|
||||
CONTACTS_SET(0x00d5, 0x0001),
|
||||
DATA_CHUNK_REQUEST_AGPS(0xffff, 0xa05f),
|
||||
DATA_CHUNK_REQUEST_FIRMWARE(0xffff, 0xa042),
|
||||
DATA_CHUNK_REQUEST_WATCHFACE(0xffff, 0xa064),
|
||||
DATA_CHUNK_WRITE_AGPS(0xffff, 0x905f),
|
||||
DATA_CHUNK_WRITE_FIRMWARE(0xffff, 0x9042),
|
||||
DATA_CHUNK_WRITE_WATCHFACE(0xffff, 0x9064),
|
||||
DATA_TRANSFER_AGPS_FINISH_ACK_1(0xffff, 0xa060),
|
||||
DATA_TRANSFER_AGPS_FINISH_ACK_2(0xffff, 0x9060),
|
||||
DATA_TRANSFER_AGPS_INIT_REPLY(0xffff, 0xa05e),
|
||||
DATA_TRANSFER_AGPS_INIT_REQUEST(0xffff, 0x905e),
|
||||
DATA_TRANSFER_FIRMWARE_INIT_1_REQUEST(0xffff, 0x9052),
|
||||
DATA_TRANSFER_FIRMWARE_INIT_1_REPLY(0xffff, 0xa052),
|
||||
DATA_TRANSFER_FIRMWARE_INIT_2_REQUEST(0xffff, 0x9040),
|
||||
DATA_TRANSFER_FIRMWARE_INIT_2_REPLY(0xffff, 0xa040),
|
||||
DATA_TRANSFER_FIRMWARE_FINISH_ACK_1(0xffff, 0xa041),
|
||||
DATA_TRANSFER_FIRMWARE_FINISH_ACK_2(0xffff, 0x9041),
|
||||
DATA_TRANSFER_WATCHFACE_FINISH_ACK_1(0xffff, 0xa065),
|
||||
DATA_TRANSFER_WATCHFACE_FINISH_ACK_2(0xffff, 0x9065),
|
||||
DATA_TRANSFER_WATCHFACE_INIT_1_REQUEST(0xffff, 0x8052),
|
||||
|
||||
+61
-6
@@ -45,7 +45,7 @@ public class CmfDataUploader implements CmfCharacteristic.Handler {
|
||||
@Override
|
||||
public void onCommand(final CmfCommand cmd, final byte[] payload) {
|
||||
switch (cmd) {
|
||||
case DATA_TRANSFER_WATCHFACE_INIT_1_REPLY:
|
||||
case DATA_TRANSFER_WATCHFACE_INIT_1_REPLY: {
|
||||
if (payload[0] != 0x01) {
|
||||
LOG.warn("Got unexpected transfer init 1 reply {}", payload[0]);
|
||||
fwHelper = null;
|
||||
@@ -63,7 +63,30 @@ public class CmfDataUploader implements CmfCharacteristic.Handler {
|
||||
buf.array()
|
||||
);
|
||||
return;
|
||||
}
|
||||
case DATA_TRANSFER_FIRMWARE_INIT_1_REPLY: {
|
||||
if (payload[0] != 0x01) {
|
||||
LOG.warn("Got unexpected firmware init 2 reply {}", payload[0]);
|
||||
fwHelper = null;
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
|
||||
// FIXME version a.b.c.d... how to know? this was from 11.0.0.57
|
||||
buf.put((byte) (0x0b));
|
||||
buf.put((byte) (0x00));
|
||||
buf.put((byte) (0x00));
|
||||
buf.put((byte) (0x39));
|
||||
|
||||
mSupport.sendFirmware(
|
||||
"transfer firmware init request",
|
||||
CmfCommand.DATA_TRANSFER_FIRMWARE_INIT_2_REQUEST,
|
||||
buf.array()
|
||||
);
|
||||
return;
|
||||
}
|
||||
case DATA_TRANSFER_AGPS_INIT_REPLY:
|
||||
case DATA_TRANSFER_FIRMWARE_INIT_2_REPLY:
|
||||
case DATA_TRANSFER_WATCHFACE_INIT_2_REPLY:
|
||||
if (payload[0] != 0x01) {
|
||||
LOG.warn("Got unexpected transfer 2 init reply {}", payload[0]);
|
||||
@@ -78,6 +101,11 @@ public class CmfDataUploader implements CmfCharacteristic.Handler {
|
||||
case DATA_TRANSFER_WATCHFACE_FINISH_ACK_1:
|
||||
handleAck1(CmfCommand.DATA_TRANSFER_WATCHFACE_FINISH_ACK_2, payload);
|
||||
return;
|
||||
case DATA_TRANSFER_FIRMWARE_FINISH_ACK_1:
|
||||
// TODO: Confirm if this is being sent in the right characteristic, although it looks
|
||||
// like it does not matter, since it restarts right away
|
||||
handleAck1(CmfCommand.DATA_TRANSFER_FIRMWARE_FINISH_ACK_2, payload);
|
||||
return;
|
||||
case DATA_TRANSFER_AGPS_FINISH_ACK_1:
|
||||
handleAck1(CmfCommand.DATA_TRANSFER_AGPS_FINISH_ACK_2, payload);
|
||||
return;
|
||||
@@ -95,6 +123,13 @@ public class CmfDataUploader implements CmfCharacteristic.Handler {
|
||||
}
|
||||
handleChunkRequest(CmfCommand.DATA_CHUNK_WRITE_WATCHFACE, payload);
|
||||
return;
|
||||
case DATA_CHUNK_REQUEST_FIRMWARE:
|
||||
if (fwHelper == null || !fwHelper.isFirmware()) {
|
||||
LOG.warn("We are not sending firmware - refusing request");
|
||||
return;
|
||||
}
|
||||
handleChunkRequest(CmfCommand.DATA_CHUNK_WRITE_FIRMWARE, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.warn("Got unknown data command {}", cmd);
|
||||
@@ -123,6 +158,18 @@ public class CmfDataUploader implements CmfCharacteristic.Handler {
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: This is disabled until we figure out how to send the firmware version
|
||||
if (fwHelper.isFirmware()) {
|
||||
mSupport.sendCommand(
|
||||
"transfer firmware init request",
|
||||
CmfCommand.DATA_TRANSFER_FIRMWARE_INIT_1_REQUEST,
|
||||
(byte) 0xa5
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
LOG.warn("Unsupported fwHelper for {}", fwHelper.getUri());
|
||||
fwHelper = null;
|
||||
}
|
||||
@@ -137,11 +184,19 @@ public class CmfDataUploader implements CmfCharacteristic.Handler {
|
||||
|
||||
final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunk offset " + offset);
|
||||
updateProgress(builder, progress, true);
|
||||
mSupport.sendData(
|
||||
"transfer watchface init request",
|
||||
commandReply,
|
||||
ArrayUtils.subarray(fwHelper.getBytes(), offset, offset + length)
|
||||
);
|
||||
if (commandReply == CmfCommand.DATA_CHUNK_WRITE_FIRMWARE) {
|
||||
mSupport.sendFirmware(
|
||||
"send firmware chunk",
|
||||
commandReply,
|
||||
ArrayUtils.subarray(fwHelper.getBytes(), offset, offset + length)
|
||||
);
|
||||
} else {
|
||||
mSupport.sendData(
|
||||
"send data chunk",
|
||||
commandReply,
|
||||
ArrayUtils.subarray(fwHelper.getBytes(), offset, offset + length)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAck1(final CmfCommand commandReply, final byte[] payload) {
|
||||
|
||||
+52
@@ -94,6 +94,10 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
public static final UUID UUID_CHARACTERISTIC_CMF_SHELL_WRITE = UUID.fromString("77d4ff01-2fe2-2334-0d35-9ccd078f529c");
|
||||
public static final UUID UUID_CHARACTERISTIC_CMF_SHELL_READ = UUID.fromString("77d4ff02-2fe2-2334-0d35-9ccd078f529c");
|
||||
|
||||
public static final UUID UUID_SERVICE_CMF_FIRMWARE = UUID.fromString("02f00000-0000-0000-0000-00000000fe00");
|
||||
public static final UUID UUID_CHARACTERISTIC_CMF_FIRMWARE_WRITE = UUID.fromString("02f00000-0000-0000-0000-00000000ff01");
|
||||
public static final UUID UUID_CHARACTERISTIC_CMF_FIRMWARE_READ = UUID.fromString("02f00000-0000-0000-0000-00000000ff02");
|
||||
|
||||
// An a5 byte is used a lot in single payloads, probably as a "proof of encryption"?
|
||||
public static final byte A5 = (byte) 0xa5;
|
||||
|
||||
@@ -101,6 +105,10 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
private CmfCharacteristic characteristicCommandWrite;
|
||||
private CmfCharacteristic characteristicDataRead;
|
||||
private CmfCharacteristic characteristicDataWrite;
|
||||
@Nullable
|
||||
private CmfCharacteristic characteristicFirmwareRead;
|
||||
@Nullable
|
||||
private CmfCharacteristic characteristicFirmwareWrite;
|
||||
|
||||
private final byte[] authRandom1 = new byte[16];
|
||||
private final byte[] authAppSecret = new byte[16];
|
||||
@@ -116,12 +124,14 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
addSupportedService(UUID_SERVICE_CMF_CMD);
|
||||
addSupportedService(UUID_SERVICE_CMF_DATA);
|
||||
addSupportedService(UUID_SERVICE_CMF_SHELL);
|
||||
addSupportedService(UUID_SERVICE_CMF_FIRMWARE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
|
||||
builder.setUpdateState(getDevice(), GBDevice.State.INITIALIZING, getContext());
|
||||
@@ -164,18 +174,35 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
LOG.warn("Characteristic shell read is null");
|
||||
}
|
||||
|
||||
final BluetoothGattCharacteristic btCharacteristicFirmwareWrite = getCharacteristic(UUID_CHARACTERISTIC_CMF_FIRMWARE_WRITE);
|
||||
if (btCharacteristicFirmwareWrite == null) {
|
||||
LOG.warn("Characteristic firmware write is null");
|
||||
}
|
||||
|
||||
final BluetoothGattCharacteristic btCharacteristicFirmwareRead = getCharacteristic(UUID_CHARACTERISTIC_CMF_FIRMWARE_READ);
|
||||
if (btCharacteristicFirmwareRead == null) {
|
||||
LOG.warn("Characteristic firmware read is null");
|
||||
}
|
||||
|
||||
dataUploader = new CmfDataUploader(this);
|
||||
|
||||
characteristicCommandRead = new CmfCharacteristic(btCharacteristicCommandRead, this);
|
||||
characteristicCommandWrite = new CmfCharacteristic(btCharacteristicCommandWrite, null);
|
||||
characteristicDataRead = new CmfCharacteristic(btCharacteristicDataRead, dataUploader);
|
||||
characteristicDataWrite = new CmfCharacteristic(btCharacteristicDataWrite, null);
|
||||
if (btCharacteristicFirmwareRead != null && btCharacteristicFirmwareWrite != null) {
|
||||
characteristicFirmwareRead = new CmfCharacteristic(btCharacteristicFirmwareRead, dataUploader);
|
||||
characteristicFirmwareWrite = new CmfCharacteristic(btCharacteristicFirmwareWrite, null);
|
||||
}
|
||||
|
||||
builder.notify(btCharacteristicCommandRead, true);
|
||||
builder.notify(btCharacteristicDataRead, true);
|
||||
if (btCharacteristicShellRead != null) {
|
||||
builder.notify(btCharacteristicShellRead, true);
|
||||
}
|
||||
if (btCharacteristicFirmwareRead != null) {
|
||||
builder.notify(btCharacteristicFirmwareRead, true);
|
||||
}
|
||||
|
||||
builder.setUpdateState(getDevice(), GBDevice.State.AUTHENTICATING, getContext());
|
||||
|
||||
@@ -186,6 +213,12 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
characteristicCommandWrite.setSessionKey(secretKey);
|
||||
characteristicDataRead.setSessionKey(secretKey);
|
||||
characteristicDataWrite.setSessionKey(secretKey);
|
||||
if (characteristicFirmwareRead != null) {
|
||||
characteristicFirmwareRead.setSessionKey(secretKey);
|
||||
}
|
||||
if (characteristicFirmwareWrite != null) {
|
||||
characteristicFirmwareWrite.setSessionKey(secretKey);
|
||||
}
|
||||
|
||||
sendCommand(builder, CmfCommand.AUTH_PHONE_NAME, ArrayUtils.addAll(new byte[]{A5}, Build.MODEL.getBytes(StandardCharsets.UTF_8)));
|
||||
} else if (btCharacteristicShellWrite != null) {
|
||||
@@ -221,6 +254,9 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
} else if (characteristicUUID.equals(characteristicDataRead.getCharacteristicUUID())) {
|
||||
characteristicDataRead.onCharacteristicChanged(value);
|
||||
return true;
|
||||
} else if (characteristicFirmwareRead != null && characteristicUUID.equals(characteristicFirmwareRead.getCharacteristicUUID())) {
|
||||
characteristicFirmwareRead.onCharacteristicChanged(value);
|
||||
return true;
|
||||
} else if (characteristicUUID.equals(UUID_CHARACTERISTIC_CMF_SHELL_READ)) {
|
||||
handleShellCommand(value);
|
||||
return true;
|
||||
@@ -255,6 +291,10 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case DATA_TRANSFER_FIRMWARE_INIT_1_REPLY:
|
||||
// This one comes in the command characteristic because of reasons
|
||||
dataUploader.onCommand(cmd, payload);
|
||||
return;
|
||||
case AUTH_PAIR_REPLY:
|
||||
final byte[] authRandom2 = ArrayUtils.subarray(payload, 0, 16);
|
||||
final byte[] signedAuthRandom2 = ArrayUtils.subarray(payload, 16, 48);
|
||||
@@ -326,6 +366,12 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
characteristicCommandWrite.setSessionKey(sessionKey);
|
||||
characteristicDataRead.setSessionKey(sessionKey);
|
||||
characteristicDataWrite.setSessionKey(sessionKey);
|
||||
if (characteristicFirmwareRead != null) {
|
||||
characteristicFirmwareRead.setSessionKey(sessionKey);
|
||||
}
|
||||
if (characteristicFirmwareWrite != null) {
|
||||
characteristicFirmwareWrite.setSessionKey(sessionKey);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to compute session key from auth nonce", e);
|
||||
return;
|
||||
@@ -452,6 +498,12 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
public void sendFirmware(final String taskName, final CmfCommand cmd, final byte... payload) {
|
||||
final TransactionBuilder builder = createTransactionBuilder(taskName);
|
||||
characteristicFirmwareWrite.sendCommand(builder, cmd, payload);
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
private void handleShellCommand(final byte[] bytes) {
|
||||
final String shellCommand = new String(bytes).strip();
|
||||
if (!shellCommand.startsWith("GETSECRET:")) {
|
||||
|
||||
Reference in New Issue
Block a user