Pebble: Firmware upgrade support

This commit is contained in:
Andreas Shimokawa 2015-04-17 12:23:19 +02:00
parent 235c603d92
commit 7c60e4b595
7 changed files with 101 additions and 33 deletions

View File

@ -1,9 +1,11 @@
###Changelog ###Changelog
####Next Release (probably 0.3.0) ####Next Release (probably 0.3.0)
* Fix installation problems with certain .pbw files * Pebble: Firmware installation (USE AT YOUR OWN RISK)
* Volume control for Pebble * Pebble: Fix installation problems with certain .pbw files
* Pebble: Volume control
* Add icon for activity tracker apps (icon by xphnx) * Add icon for activity tracker apps (icon by xphnx)
* Let the application quit when in reconnecting state
####Version 0.2.0 ####Version 0.2.0
* Experimental pbw installation support (watchfaces/apps) * Experimental pbw installation support (watchfaces/apps)

View File

@ -18,7 +18,8 @@ Features:
* Apollo playback info (artist, album, track) * Apollo playback info (artist, album, track)
* Music control: play/pause, next track, previous track, volume up, volume down * Music control: play/pause, next track, previous track, volume up, volume down
* List and remove installed apps/watchfaces * List and remove installed apps/watchfaces
* Install .pbw files (EXPERMIENTAL) * Install .pbw files
* Install firmware from .pbz files (EXPERIMENTAL)
How to use: How to use:

View File

@ -52,6 +52,7 @@
<data android:host="*" /> <data android:host="*" />
<data android:scheme="file" /> <data android:scheme="file" />
<data android:pathPattern=".*\\.pbw" /> <data android:pathPattern=".*\\.pbw" />
<data android:pathPattern=".*\\.pbz" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -14,6 +14,8 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -21,11 +23,28 @@ import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp;
public class PBWReader { public class PBWReader {
private static final String TAG = PebbleIoThread.class.getSimpleName(); private static final String TAG = PebbleIoThread.class.getSimpleName();
private static final HashMap<String, Byte> appFileTypesMap;
static {
appFileTypesMap = new HashMap<String, Byte>();
appFileTypesMap.put("application", PebbleProtocol.PUTBYTES_TYPE_BINARY);
appFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_RESOURCES);
appFileTypesMap.put("worker", PebbleProtocol.PUTBYTES_TYPE_WORKER);
}
private static final HashMap<String, Byte> fwFileTypesMap;
static {
fwFileTypesMap = new HashMap<String, Byte>();
fwFileTypesMap.put("firmware", PebbleProtocol.PUTBYTES_TYPE_FIRMWARE);
fwFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_SYSRESOURCES);
}
private GBDeviceApp app;
private final Uri uri; private final Uri uri;
private final ContentResolver cr; private final ContentResolver cr;
private GBDeviceApp app;
private ArrayList<PebbleInstallable> pebbleInstallables; private ArrayList<PebbleInstallable> pebbleInstallables;
private boolean isFirmware = false;
public PBWReader(Uri uri, Context context) { public PBWReader(Uri uri, Context context) {
this.uri = uri; this.uri = uri;
@ -60,35 +79,32 @@ public class PBWReader {
String jsonString = baos.toString(); String jsonString = baos.toString();
try { try {
JSONObject json = new JSONObject(jsonString); JSONObject json = new JSONObject(jsonString);
JSONObject application = json.getJSONObject("application"); String[] searchJSON;
HashMap<String, Byte> fileTypeMap;
String name = application.getString("name");
int size = application.getInt("size");
long crc = application.getLong("crc");
pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, PebbleProtocol.PUTBYTES_TYPE_BINARY));
Log.i(TAG, "found app binary to install: " + name);
try { try {
JSONObject resources = json.getJSONObject("resources"); json.getJSONObject("firmware");
name = resources.getString("name"); fileTypeMap = fwFileTypesMap;
size = resources.getInt("size"); isFirmware = true;
crc = resources.getLong("crc");
pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, PebbleProtocol.PUTBYTES_TYPE_RESOURCES));
Log.i(TAG, "found resources to install: " + name);
} catch (JSONException e) { } catch (JSONException e) {
// no resources, that is no problem fileTypeMap = appFileTypesMap;
isFirmware = false;
} }
try { for (Map.Entry<String, Byte> entry : fileTypeMap.entrySet()) {
JSONObject worker = json.getJSONObject("worker"); try {
name = worker.getString("name"); JSONObject jo = json.getJSONObject(entry.getKey());
size = worker.getInt("size"); String name = jo.getString("name");
crc = worker.getLong("crc"); int size = jo.getInt("size");
pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, PebbleProtocol.PUTBYTES_TYPE_WORKER)); long crc = jo.getLong("crc");
Log.i(TAG, "found worker to install: " + name); byte type = entry.getValue();
} catch (JSONException e) { pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, type));
// no worker, that is no problem Log.i(TAG, "found file to install: " + name);
} catch (JSONException e) {
// not fatal
}
} }
} catch (JSONException e) { } catch (JSONException e) {
// no application, that is a problem // no JSON at all that is a problem
e.printStackTrace(); e.printStackTrace();
break; break;
} }
@ -126,6 +142,10 @@ public class PBWReader {
} }
} }
protected boolean isFirmware() {
return isFirmware;
}
public GBDeviceApp getGBDeviceApp() { public GBDeviceApp getGBDeviceApp() {
return app; return app;
} }

View File

@ -35,8 +35,13 @@ public class PebbleAppInstallerActivity extends Activity {
PBWReader pbwReader = new PBWReader(uri, getApplicationContext()); PBWReader pbwReader = new PBWReader(uri, getApplicationContext());
GBDeviceApp app = pbwReader.getGBDeviceApp(); GBDeviceApp app = pbwReader.getGBDeviceApp();
if (pbwReader != null && app != null) { if (pbwReader != null) {
debugTextView.setText("THIS IS HIGHLY EXPERIMENTAL PROCEED AT YOUR OWN RISK\n\n\n" + app.getName() + " Version " + app.getVersion() + " by " + app.getCreator() + "\n"); if (pbwReader.isFirmware()) {
debugTextView.setText("YOUR ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK, MAKE SURE THIS FIRMWARE IS FOR YOUR PEBBLE REVISION, THERE ARE NO CHECKS.\n\n\n");
} else if (app != null) {
debugTextView.setText("You are about to install the following app:\n\n\n" + app.getName() + " Version " + app.getVersion() + " by " + app.getCreator() + "\n");
}
installButton.setEnabled(true); installButton.setEnabled(true);
installButton.setOnClickListener(new View.OnClickListener() { installButton.setOnClickListener(new View.OnClickListener() {
@Override @Override

View File

@ -121,16 +121,21 @@ public class PebbleIoThread extends GBDeviceIoThread {
} }
break; break;
case APP_START_INSTALL: case APP_START_INSTALL:
Log.i(TAG, "start installing app binary");
if (mPBWReader == null) { if (mPBWReader == null) {
mPBWReader = new PBWReader(mInstallURI, getContext()); mPBWReader = new PBWReader(mInstallURI, getContext());
mPebbleInstallables = mPBWReader.getPebbleInstallables(); mPebbleInstallables = mPBWReader.getPebbleInstallables();
mCurrentInstallableIndex = 0; mCurrentInstallableIndex = 0;
if (mPBWReader.isFirmware()) {
writeInstallApp(mPebbleProtocol.encodeInstallFirmwareStart());
mInstallSlot = 0;
Log.i(TAG, "starting firmware installation");
}
} }
Log.i(TAG, "start installing app binary");
PebbleInstallable pi = mPebbleInstallables[mCurrentInstallableIndex]; PebbleInstallable pi = mPebbleInstallables[mCurrentInstallableIndex];
mZis = mPBWReader.getInputStreamFile(pi.getFileName()); mZis = mPBWReader.getInputStreamFile(pi.getFileName());
mCRC = pi.getCRC(); mCRC = pi.getCRC();
int binarySize = pi.getFileSize(); // TODO: use for progrssbar int binarySize = pi.getFileSize(); // TODO: use for progressbar
writeInstallApp(mPebbleProtocol.encodeUploadStart(pi.getType(), (byte) mInstallSlot, binarySize)); writeInstallApp(mPebbleProtocol.encodeUploadStart(pi.getType(), (byte) mInstallSlot, binarySize));
mInstallState = PebbleAppInstallState.APP_WAIT_TOKEN; mInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
break; break;
@ -174,7 +179,12 @@ public class PebbleIoThread extends GBDeviceIoThread {
} }
break; break;
case APP_REFRESH: case APP_REFRESH:
writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot)); if (mPBWReader.isFirmware()) {
writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete());
finishInstall(false);
} else {
writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot));
}
break; break;
default: default:
break; break;
@ -239,7 +249,7 @@ public class PebbleIoThread extends GBDeviceIoThread {
gbDevice.sendDeviceUpdateIntent(getContext()); gbDevice.sendDeviceUpdateIntent(getContext());
GB.updateNotification("connection lost, trying to reconnect", getContext()); GB.updateNotification("connection lost, trying to reconnect", getContext());
while (mConnectionAttempts++ < 10) { while (mConnectionAttempts++ < 10 && !mQuit) {
Log.i(TAG, "Trying to reconnect (attempt " + mConnectionAttempts + ")"); Log.i(TAG, "Trying to reconnect (attempt " + mConnectionAttempts + ")");
mIsConnected = connect(gbDevice.getAddress()); mIsConnected = connect(gbDevice.getAddress());
if (mIsConnected) if (mIsConnected)

View File

@ -96,6 +96,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte PUTBYTES_TYPE_FILE = 6; static final byte PUTBYTES_TYPE_FILE = 6;
public static final byte PUTBYTES_TYPE_WORKER = 7; public static final byte PUTBYTES_TYPE_WORKER = 7;
private final byte SYSTEMMESSAGE_FIRMWARESTART = 1;
private final byte SYSTEMMESSAGE_FIRMWARECOMPLETE = 2;
private final byte SYSTEMMESSAGE_FIRMWAREFAIL = 3;
static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains
static final byte PHONEVERSION_APPVERSION_MAJOR = 2; static final byte PHONEVERSION_APPVERSION_MAJOR = 2;
static final byte PHONEVERSION_APPVERSION_MINOR = 3; static final byte PHONEVERSION_APPVERSION_MINOR = 3;
@ -132,6 +136,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final short LENGTH_UPLOADCOMMIT = 9; static final short LENGTH_UPLOADCOMMIT = 9;
static final short LENGTH_UPLOADCOMPLETE = 5; static final short LENGTH_UPLOADCOMPLETE = 5;
static final short LENGTH_UPLOADCANCEL = 5; static final short LENGTH_UPLOADCANCEL = 5;
static final short LENGTH_SYSTEMMESSAGE = 2;
private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) { private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) {
// Calculate length first // Calculate length first
@ -360,6 +365,30 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array(); return buf.array();
} }
private byte[] encodeSystemMessage(byte systemMessage) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SYSTEMMESSAGE);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_SYSTEMMESSAGE);
buf.putShort(ENDPOINT_SYSTEMMESSAGE);
buf.put((byte) 0);
buf.put(systemMessage);
return buf.array();
}
public byte[] encodeInstallFirmwareStart() {
return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARESTART);
}
public byte[] encodeInstallFirmwareComplete() {
return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARECOMPLETE);
}
public byte[] encodeInstallFirmwareError() {
return encodeSystemMessage(SYSTEMMESSAGE_FIRMWAREFAIL);
}
public byte[] encodeAppRefresh(int index) { public byte[] encodeAppRefresh(int index) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP);
buf.order(ByteOrder.BIG_ENDIAN); buf.order(ByteOrder.BIG_ENDIAN);