mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-26 17:47:34 +01:00
Initial support for upgrading firmware of the MiBand.
This release seems to be working quite well with respect to the firmware upgrading itself. The user facing part needs more work. In order to update the firmware one has to: - open a file ending with .fw - switch from the firmware upgrade activity to the main one - connect to the miband - return to the firmware upgrade activity - press the "install" button (that became active when the device connection was established) Caveats: There are almost no check wrt. the integrity of the firmware files.
This commit is contained in:
parent
268e658e6f
commit
f16a96e9b2
@ -203,6 +203,36 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".miband.FwUpgrade"
|
||||
android:label="@string/title_activity_fw_upgrade">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="nodomain.freeyourgadget.gadgetbridge.ControlCenter" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="*/*" /> <!-- needed for android 5 -->
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
|
||||
<!-- as seen on openkeychain repo: https://github.com/open-keychain/open-keychain/blob/master/OpenKeychain/src/main/AndroidManifest.xml -->
|
||||
|
||||
<data android:pathPattern=".*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -10,11 +10,20 @@ public class DeviceInfo extends AbstractInfo {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public String getFirmwareVersion() {
|
||||
public String getHumanFirmwareVersion() {
|
||||
if (mData.length == 16) {
|
||||
int last = 15;
|
||||
return String.format(Locale.US, "%d.%d.%d.%d", mData[last], mData[last - 1], mData[last - 2], mData[last - 3]);
|
||||
}
|
||||
return GBApplication.getContext().getString(R.string._unknown_);
|
||||
}
|
||||
|
||||
public int getFirmwareVersion() {
|
||||
if (mData.length == 16) {
|
||||
int last = 15;
|
||||
return (mData[last] << 24) | (mData[last - 1] << 16) | (mData[last - 2] << 8) | mData[last - 3];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.miband;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BluetoothCommunicationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.ControlCenter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
|
||||
/*
|
||||
TODO: This could be moved to activities package and merged with pebble/PebbleAppInstallerActivity.java
|
||||
*/
|
||||
|
||||
public class FwUpgrade extends Activity {
|
||||
|
||||
|
||||
TextView fwUpgradeTextView;
|
||||
Button installButton;
|
||||
|
||||
private MiBandFWHelper mFwReader = null;
|
||||
private GBDevice dev;
|
||||
|
||||
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(ControlCenter.ACTION_QUIT)) {
|
||||
finish();
|
||||
} else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
|
||||
dev = intent.getParcelableExtra("device");
|
||||
if(dev.getType() == DeviceType.MIBAND) {
|
||||
if (dev.isInitialized() && mFwReader != null) {
|
||||
installButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_fw_upgrade);
|
||||
|
||||
fwUpgradeTextView = (TextView) findViewById(R.id.fwUpgradeTextView);
|
||||
installButton = (Button) findViewById(R.id.installButton);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ControlCenter.ACTION_QUIT);
|
||||
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
|
||||
final Uri uri = getIntent().getData();
|
||||
mFwReader = new MiBandFWHelper(uri, getApplicationContext());
|
||||
|
||||
fwUpgradeTextView.setText(getString(R.string.fw_upgrade_notice, mFwReader.getHumanFirmwareVersion()));
|
||||
|
||||
if (mFwReader.isFirmwareWhitelisted()) {
|
||||
fwUpgradeTextView.append(" " + getString(R.string.miband_firmware_known));
|
||||
}else {
|
||||
fwUpgradeTextView.append(" " + getString(R.string.miband_firmware_unknown_warning) + " " +
|
||||
getString(R.string.miband_firmware_suggest_whitelist, mFwReader.getFirmwareVersion()));
|
||||
}
|
||||
|
||||
installButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent startIntent = new Intent(FwUpgrade.this, BluetoothCommunicationService.class);
|
||||
startIntent.setAction(BluetoothCommunicationService.ACTION_INSTALL);
|
||||
startIntent.putExtra("uri", uri.toString());
|
||||
startService(startIntent);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.miband;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MiBandFWHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
|
||||
|
||||
private final Uri uri;
|
||||
private final ContentResolver cr;
|
||||
private byte[] fw;
|
||||
|
||||
private final int firmwareVersionBuild = 1056;
|
||||
private final int firmwareVersionRevision = 1057;
|
||||
private final int firmwareVersionMinor = 1058;
|
||||
private final int firmwareVersionMajor = 1059;
|
||||
|
||||
private final int[] whitelistedFirmwareVersion = {
|
||||
16779534, // 1.0.9.14 tested by developer
|
||||
16779547 //1.0.9.27 testd by developer
|
||||
};
|
||||
|
||||
public MiBandFWHelper(Uri uri, Context context) {
|
||||
this.uri = uri;
|
||||
cr = context.getContentResolver();
|
||||
|
||||
InputStream fin;
|
||||
|
||||
try {
|
||||
fin = new BufferedInputStream(cr.openInputStream(uri));
|
||||
this.fw = new byte[fin.available()];
|
||||
fin.read(fw);
|
||||
fin.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.fw = null;
|
||||
}
|
||||
|
||||
if (fw[firmwareVersionMajor] != 1 ) {
|
||||
LOG.error("Firmware major version should be 1, probably this isn't a MiBand firmware.");
|
||||
this.fw = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int getFirmwareVersion() {
|
||||
if(fw == null) {
|
||||
return -1;
|
||||
}
|
||||
return (fw[firmwareVersionMajor] << 24) | (fw[firmwareVersionMinor] << 16) | (fw[firmwareVersionRevision] << 8) | fw[firmwareVersionBuild];
|
||||
}
|
||||
|
||||
public String getHumanFirmwareVersion() {
|
||||
if(fw == null) {
|
||||
return "UNK";
|
||||
}
|
||||
return String.format(Locale.US, "%d.%d.%d.%d", fw[firmwareVersionMajor], fw[firmwareVersionMinor], fw[firmwareVersionRevision], fw[firmwareVersionBuild]);
|
||||
}
|
||||
|
||||
public byte[] getFw() {
|
||||
return fw;
|
||||
}
|
||||
|
||||
public boolean isFirmwareWhitelisted() {
|
||||
for (int wlf : whitelistedFirmwareVersion) {
|
||||
if (wlf == getFirmwareVersion()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//thanks http://stackoverflow.com/questions/13209364/convert-c-crc16-to-java-crc16
|
||||
public int getCRC16(byte[] seq) {
|
||||
int crc = 0xFFFF;
|
||||
|
||||
for (int j = 0; j < seq.length ; j++) {
|
||||
crc = ((crc >>> 8) | (crc << 8) )& 0xffff;
|
||||
crc ^= (seq[j] & 0xff);//byte to int, trunc sign
|
||||
crc ^= ((crc & 0xff) >> 4);
|
||||
crc ^= (crc << 12) & 0xffff;
|
||||
crc ^= ((crc & 0xFF) << 5) & 0xffff;
|
||||
}
|
||||
crc &= 0xffff;
|
||||
return crc;
|
||||
}
|
||||
|
||||
}
|
@ -137,6 +137,11 @@ public class MiBandService {
|
||||
|
||||
public static final byte COMMAND_SET_TIMER = 0x4;
|
||||
|
||||
public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7;
|
||||
|
||||
public static final byte COMMAND_SYNC = 0xb;
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
@ -145,7 +150,6 @@ public class MiBandService {
|
||||
|
||||
public static final byte COMMAND_GET_SENSOR_DATA = 0x12t
|
||||
|
||||
public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7t
|
||||
|
||||
public static final int COMMAND_SET_COLOR_THEME = et;
|
||||
|
||||
@ -159,8 +163,6 @@ public class MiBandService {
|
||||
|
||||
public static final COMMAND_STOP_SYNC_DATA = 0x11t
|
||||
|
||||
public static final COMMAND_SYNC = 0xbt
|
||||
|
||||
public static final CONNECTION_LATENCY_LEVEL_HIGH = 0x2t;
|
||||
|
||||
public static final CONNECTION_LATENCY_LEVEL_LOW = 0x0t;
|
||||
|
@ -80,6 +80,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private ActivityStruct activityStruct;
|
||||
|
||||
private DeviceInfo mDeviceInfo;
|
||||
|
||||
public MiBandSupport() {
|
||||
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
|
||||
}
|
||||
@ -531,7 +533,19 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
// not supported
|
||||
MiBandFWHelper mFwHelper = new MiBandFWHelper(uri, getContext());
|
||||
String mMac = getDevice().getAddress();
|
||||
String[] mMacOctets = mMac.split(":");
|
||||
|
||||
int newFwVersion = mFwHelper.getFirmwareVersion();
|
||||
int oldFwVersion = mDeviceInfo.getFirmwareVersion();
|
||||
int checksum = (Integer.decode("0x" + mMacOctets[4]) << 8 | Integer.decode("0x" + mMacOctets[5])) ^ mFwHelper.getCRC16(mFwHelper.getFw());
|
||||
|
||||
sendFirmwareInfo(oldFwVersion,newFwVersion, mFwHelper.getFw().length, checksum);
|
||||
sendFirmwareData(mFwHelper.getFw());
|
||||
onReboot();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -602,8 +616,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private void handleDeviceInfo(byte[] value, int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
DeviceInfo info = new DeviceInfo(value);
|
||||
getDevice().setFirmwareVersion(info.getFirmwareVersion());
|
||||
mDeviceInfo = new DeviceInfo(value);
|
||||
getDevice().setFirmwareVersion(mDeviceInfo.getHumanFirmwareVersion());
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
}
|
||||
@ -830,6 +844,102 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
LOG.info("MI Band pairing result: " + value);
|
||||
}
|
||||
|
||||
private void sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) {
|
||||
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)
|
||||
};
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("send firmware info");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), fwInfo);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send fwInfo to MI", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendFirmwareData(byte fwbytes[]) {
|
||||
int len = fwbytes.length;
|
||||
final int packetLength = 20;
|
||||
int packets = len / packetLength;
|
||||
byte fwChunk[] = new byte[packetLength];
|
||||
|
||||
int firmwareProgress = 0;
|
||||
|
||||
for (int i = 0; i < packets; i++) {
|
||||
fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength);
|
||||
|
||||
if (!sendFirmwareChunk(fwChunk)) {
|
||||
LOG.error("Firmware chunk write failed");
|
||||
return;
|
||||
}
|
||||
|
||||
firmwareProgress += packetLength;
|
||||
|
||||
if ((i > 0) && (i % 50 == 0)) {
|
||||
if(!sendFirmwareSync()) {
|
||||
LOG.error("Firmware sync failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.info("Firmware update progress:" + firmwareProgress + " total lenL:" + len + " progress:" + firmwareProgress / len);
|
||||
}
|
||||
|
||||
if (!(len % packetLength == 0)) {
|
||||
byte lastChunk[] = new byte[len % packetLength];
|
||||
lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len);
|
||||
if (!sendFirmwareChunk(lastChunk)) {
|
||||
LOG.error("Firmware chunk write failed");
|
||||
return;
|
||||
}
|
||||
firmwareProgress += len % packetLength;
|
||||
}
|
||||
|
||||
LOG.info("Firmware update progress:" + firmwareProgress +" total lenL:"+ len + " progress:" + firmwareProgress/len);
|
||||
|
||||
if(!sendFirmwareSync()) {
|
||||
LOG.error("Firmware sync failed");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean sendFirmwareChunk(byte fwChunk[]) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("send firmware packet");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), fwChunk);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send fw packet to MI", ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean sendFirmwareSync() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("send firmware sync");
|
||||
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[] {MiBandService.COMMAND_SYNC});
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send firmware sync to MI", ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder createTransactionBuilder(String taskName) {
|
||||
return new MiBandTransactionBuilder(taskName);
|
||||
|
21
app/src/main/res/layout/activity_fw_upgrade.xml
Normal file
21
app/src/main/res/layout/activity_fw_upgrade.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.miband.FwUpgrade">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fwUpgradeTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/appinstaller_install"
|
||||
android:id="@+id/installButton"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:enabled="false" />
|
||||
</RelativeLayout>
|
@ -153,4 +153,9 @@
|
||||
<string name="user_feedback_miband_set_alarms_ok">Alarms sent to device!</string>
|
||||
<string name="chart_no_data_synchronize">No data. Synchronize device?</string>
|
||||
<string name="user_feedback_miband_activity_data_transfer">About to transfer %1$s of data starting from %2$s</string>
|
||||
<string name="title_activity_fw_upgrade">FwUpgrade</string>
|
||||
<string name="fw_upgrade_notice">You are about to install firmware %s instead of the one currently on your MiBand.</string>
|
||||
<string name="miband_firmware_known">This firmware has been tested and is known to be compatible with GadgetBridge.</string>
|
||||
<string name="miband_firmware_unknown_warning">"This firmware is untested and may not be compatible with GadgetBridge. You are not encouraged to flash it to your MiBand. "</string>
|
||||
<string name="miband_firmware_suggest_whitelist">If you still want to proceed and things continue to work properly afterwards, please tell the GadgetBridge developers to whitelist firmware version: %s</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user