Pebble: Firmware upgrade support

master
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
####Next Release (probably 0.3.0)
* Fix installation problems with certain .pbw files
* Volume control for Pebble
* Pebble: Firmware installation (USE AT YOUR OWN RISK)
* Pebble: Fix installation problems with certain .pbw files
* Pebble: Volume control
* Add icon for activity tracker apps (icon by xphnx)
* Let the application quit when in reconnecting state
####Version 0.2.0
* Experimental pbw installation support (watchfaces/apps)

View File

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

View File

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

View File

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

View File

@ -35,8 +35,13 @@ public class PebbleAppInstallerActivity extends Activity {
PBWReader pbwReader = new PBWReader(uri, getApplicationContext());
GBDeviceApp app = pbwReader.getGBDeviceApp();
if (pbwReader != null && app != 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 != null) {
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.setOnClickListener(new View.OnClickListener() {
@Override

View File

@ -121,16 +121,21 @@ public class PebbleIoThread extends GBDeviceIoThread {
}
break;
case APP_START_INSTALL:
Log.i(TAG, "start installing app binary");
if (mPBWReader == null) {
mPBWReader = new PBWReader(mInstallURI, getContext());
mPebbleInstallables = mPBWReader.getPebbleInstallables();
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];
mZis = mPBWReader.getInputStreamFile(pi.getFileName());
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));
mInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
break;
@ -174,7 +179,12 @@ public class PebbleIoThread extends GBDeviceIoThread {
}
break;
case APP_REFRESH:
writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot));
if (mPBWReader.isFirmware()) {
writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete());
finishInstall(false);
} else {
writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot));
}
break;
default:
break;
@ -239,7 +249,7 @@ public class PebbleIoThread extends GBDeviceIoThread {
gbDevice.sendDeviceUpdateIntent(getContext());
GB.updateNotification("connection lost, trying to reconnect", getContext());
while (mConnectionAttempts++ < 10) {
while (mConnectionAttempts++ < 10 && !mQuit) {
Log.i(TAG, "Trying to reconnect (attempt " + mConnectionAttempts + ")");
mIsConnected = connect(gbDevice.getAddress());
if (mIsConnected)

View File

@ -96,6 +96,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte PUTBYTES_TYPE_FILE = 6;
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_MAJOR = 2;
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_UPLOADCOMPLETE = 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) {
// Calculate length first
@ -360,6 +365,30 @@ public class PebbleProtocol extends GBDeviceProtocol {
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) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP);
buf.order(ByteOrder.BIG_ENDIAN);