mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Pebble: Add support for dual slot firmware
This commit is contained in:
+16
@@ -30,10 +30,23 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
public class GBDeviceEventVersionInfo extends GBDeviceEvent {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventVersionInfo.class);
|
||||
|
||||
/**
|
||||
* Extra info key for dual-slot firmware target slot.
|
||||
* Value is Integer: 0 for slot 0, 1 for slot 1.
|
||||
*/
|
||||
public static final String EXTRA_FW_UPDATE_TARGET_SLOT = "pebble_fw_update_target_slot";
|
||||
|
||||
public String fwVersion = "N/A";
|
||||
public String fwVersion2 = null;
|
||||
public String hwVersion = "N/A";
|
||||
|
||||
/**
|
||||
* For dual-slot firmware devices (like Pebble Time 2), this indicates which slot
|
||||
* firmware updates should target. null means not a dual-slot device.
|
||||
* 0 = update slot 0, 1 = update slot 1
|
||||
*/
|
||||
public Integer fwUpdateTargetSlot = null;
|
||||
|
||||
public GBDeviceEventVersionInfo() {
|
||||
if (GBApplication.getContext() != null) {
|
||||
// Only get from context if there is one (eg. not in unit tests)
|
||||
@@ -63,6 +76,9 @@ public class GBDeviceEventVersionInfo extends GBDeviceEvent {
|
||||
if (hwVersion != null) {
|
||||
device.setModel(hwVersion);
|
||||
}
|
||||
if (fwUpdateTargetSlot != null) {
|
||||
device.setExtraInfo(EXTRA_FW_UPDATE_TARGET_SLOT, fwUpdateTargetSlot);
|
||||
}
|
||||
device.sendDeviceUpdateIntent(context);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -48,6 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleHardware;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
|
||||
public class PBWInstallHandler implements InstallHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PBWInstallHandler.class);
|
||||
@@ -75,9 +76,10 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
}
|
||||
|
||||
String platformName = PebbleHardware.getPlatformName(device.getModel());
|
||||
Integer targetSlot = (Integer) device.getExtraInfo(GBDeviceEventVersionInfo.EXTRA_FW_UPDATE_TARGET_SLOT);
|
||||
|
||||
try {
|
||||
mPBWReader = new PBWReader(mUri, mContext, platformName);
|
||||
mPBWReader = new PBWReader(mUri, mContext, platformName, targetSlot);
|
||||
} catch (FileNotFoundException e) {
|
||||
installActivity.setInfoText("file not found");
|
||||
installActivity.setInstallEnabled(false);
|
||||
|
||||
+104
-8
@@ -75,7 +75,18 @@ public class PBWReader {
|
||||
|
||||
private JSONObject mAppKeys = null;
|
||||
|
||||
/**
|
||||
* For dual-slot firmware devices, the slot to install to.
|
||||
* null for non-dual-slot devices, 0 or 1 for dual-slot devices.
|
||||
*/
|
||||
private final Integer targetSlot;
|
||||
|
||||
public PBWReader(Uri uri, Context context, String platform) throws IOException {
|
||||
this(uri, context, platform, null);
|
||||
}
|
||||
|
||||
public PBWReader(Uri uri, Context context, String platform, Integer targetSlot) throws IOException {
|
||||
this.targetSlot = targetSlot;
|
||||
uriHelper = UriHelper.get(uri, context);
|
||||
|
||||
if (uriHelper.getFileName().endsWith(".pbl")) {
|
||||
@@ -99,7 +110,11 @@ public class PBWReader {
|
||||
}
|
||||
|
||||
String platformDir = "";
|
||||
if (!uriHelper.getFileName().endsWith(".pbz")) {
|
||||
boolean isPbzFirmware = uriHelper.getFileName().endsWith(".pbz");
|
||||
int firmwareSlotCount = 0;
|
||||
String firstSlotHwRevision = null;
|
||||
|
||||
if (!isPbzFirmware) {
|
||||
platformDir = determinePlatformDir(uriHelper, platform);
|
||||
|
||||
if (platform.equals("chalk") && platformDir.equals("")) {
|
||||
@@ -125,7 +140,35 @@ public class PBWReader {
|
||||
try (ZipInputStream zis = new ZipInputStream(uriHelper.openInputStream())) {
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
String fileName = ze.getName();
|
||||
if (fileName.equals(platformDir + "manifest.json")) {
|
||||
|
||||
// For .pbz firmware files, check for slot-based manifests (slot0/, slot1/)
|
||||
// as well as root manifest.json
|
||||
String slotPrefix = "";
|
||||
boolean isManifest = fileName.equals(platformDir + "manifest.json");
|
||||
if (isPbzFirmware && !isManifest) {
|
||||
// Check for dual-slot firmware format (e.g., slot0/manifest.json, slot1/manifest.json)
|
||||
if (fileName.matches("slot\\d+/manifest\\.json")) {
|
||||
slotPrefix = fileName.substring(0, fileName.indexOf('/') + 1);
|
||||
|
||||
// If targetSlot is specified, only match that slot's manifest
|
||||
if (targetSlot != null) {
|
||||
String expectedPrefix = "slot" + targetSlot + "/";
|
||||
if (slotPrefix.equals(expectedPrefix)) {
|
||||
isManifest = true;
|
||||
LOG.info("Using target slot {} firmware manifest: {}", targetSlot, fileName);
|
||||
} else {
|
||||
LOG.info("Skipping slot manifest {} (target is slot {})", fileName, targetSlot);
|
||||
}
|
||||
} else {
|
||||
// No target slot specified, process all slots (legacy behavior)
|
||||
isManifest = true;
|
||||
LOG.info("Found slot-based firmware manifest: " + fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isManifest) {
|
||||
String manifestSlotPrefix = slotPrefix.isEmpty() ? platformDir : slotPrefix;
|
||||
long bytes = ze.getSize();
|
||||
if (bytes > 8192) // that should be too much
|
||||
break;
|
||||
@@ -138,17 +181,47 @@ public class PBWReader {
|
||||
String jsonString = baos.toString();
|
||||
try {
|
||||
JSONObject json = new JSONObject(jsonString);
|
||||
HashMap<String, Byte> fileTypeMap;
|
||||
HashMap<String, Byte> fileTypeMap = null;
|
||||
|
||||
try {
|
||||
JSONObject firmware = json.getJSONObject("firmware");
|
||||
fileTypeMap = fwFileTypesMap;
|
||||
isFirmware = true;
|
||||
hwRevision = firmware.getString("hwrev");
|
||||
String slotHwRevision = firmware.getString("hwrev");
|
||||
|
||||
// For dual-slot firmware, validate consistency
|
||||
if (!slotPrefix.isEmpty()) {
|
||||
firmwareSlotCount++;
|
||||
if (firstSlotHwRevision == null) {
|
||||
firstSlotHwRevision = slotHwRevision;
|
||||
hwRevision = slotHwRevision;
|
||||
} else if (!firstSlotHwRevision.equals(slotHwRevision)) {
|
||||
isValid = false;
|
||||
invalidReason = "Dual-slot firmware hwrev mismatch: " + firstSlotHwRevision + " vs " + slotHwRevision;
|
||||
LOG.error(invalidReason);
|
||||
// Will exit via invalidReason check below
|
||||
}
|
||||
LOG.info("Slot {} firmware hwrev: {}", slotPrefix, slotHwRevision);
|
||||
} else {
|
||||
hwRevision = slotHwRevision;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
fileTypeMap = appFileTypesMap;
|
||||
isFirmware = false;
|
||||
// For dual-slot .pbz files, all slots must be firmware
|
||||
if (isPbzFirmware && !slotPrefix.isEmpty()) {
|
||||
isValid = false;
|
||||
invalidReason = "Slot " + slotPrefix + " is not firmware (missing firmware object in manifest)";
|
||||
LOG.error(invalidReason);
|
||||
} else {
|
||||
fileTypeMap = appFileTypesMap;
|
||||
isFirmware = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit early if validation failed
|
||||
if (invalidReason != null || fileTypeMap == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Byte> entry : fileTypeMap.entrySet()) {
|
||||
try {
|
||||
JSONObject jo = json.getJSONObject(entry.getKey());
|
||||
@@ -156,8 +229,8 @@ public class PBWReader {
|
||||
int size = jo.getInt("size");
|
||||
long crc = jo.getLong("crc");
|
||||
byte type = entry.getValue();
|
||||
pebbleInstallables.add(new PebbleInstallable(platformDir + name, size, (int) crc, type));
|
||||
LOG.info("found file to install: " + platformDir + name);
|
||||
pebbleInstallables.add(new PebbleInstallable(manifestSlotPrefix + name, size, (int) crc, type));
|
||||
LOG.info("found file to install: " + manifestSlotPrefix + name);
|
||||
isValid = true;
|
||||
} catch (JSONException e) {
|
||||
// not fatal
|
||||
@@ -251,6 +324,29 @@ public class PBWReader {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate dual-slot firmware
|
||||
if (isValid && isFirmware && firmwareSlotCount > 0) {
|
||||
if (targetSlot != null) {
|
||||
// When target slot is specified, we should have exactly 1 slot
|
||||
if (firmwareSlotCount == 1) {
|
||||
LOG.info("Target slot {} firmware ready for install, hwrev '{}'", targetSlot, hwRevision);
|
||||
} else {
|
||||
// This shouldn't happen if our filtering is correct
|
||||
LOG.warn("Expected 1 slot for target {}, found {}", targetSlot, firmwareSlotCount);
|
||||
}
|
||||
} else {
|
||||
// Legacy behavior: when no target slot specified, require both slots
|
||||
if (firmwareSlotCount < 2) {
|
||||
isValid = false;
|
||||
invalidReason = "Dual-slot firmware incomplete: found only " + firmwareSlotCount + " slot(s), expected 2. " +
|
||||
"Device may need to report its running slot for single-slot install.";
|
||||
LOG.error(invalidReason);
|
||||
} else {
|
||||
LOG.info("Dual-slot firmware validated: both slots present with matching hwrev '{}'", hwRevision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set default invalid reason if still not valid and no specific reason
|
||||
if (!isValid && invalidReason == null) {
|
||||
if (pebbleInstallables == null || pebbleInstallables.isEmpty()) {
|
||||
|
||||
+2
-1
@@ -608,9 +608,10 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
}
|
||||
|
||||
String platformName = PebbleHardware.getPlatformName(gbDevice.getModel());
|
||||
Integer targetSlot = (Integer) gbDevice.getExtraInfo(GBDeviceEventVersionInfo.EXTRA_FW_UPDATE_TARGET_SLOT);
|
||||
|
||||
try {
|
||||
mPBWReader = new PBWReader(uri, getContext(), platformName);
|
||||
mPBWReader = new PBWReader(uri, getContext(), platformName, targetSlot);
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.warn("app file not found", e);
|
||||
return;
|
||||
|
||||
+15
@@ -293,6 +293,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
|
||||
int mFwMajor = 3;
|
||||
boolean isNewEraPebble = false;
|
||||
|
||||
// Dual-slot firmware support
|
||||
// Bit 2 (0x04) = IsDualSlot, Bit 3 (0x08) = IsSlot0
|
||||
private boolean mIsDualSlot = false;
|
||||
private boolean mIsSlot0 = false;
|
||||
boolean mEnablePebbleKit = false;
|
||||
boolean mAlwaysACKPebbleKit = false;
|
||||
private byte[] screenshotData = null;
|
||||
@@ -2423,6 +2428,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
String gitHash = getFixedString(buf, 8);
|
||||
int fwFlags = buf.get();
|
||||
LOG.info("git hash: {}, flags: {}", gitHash, fwFlags);
|
||||
|
||||
// Extract dual-slot firmware flags
|
||||
mIsDualSlot = (fwFlags & 0x04) != 0; // Bit 2: IsDualSlot
|
||||
mIsSlot0 = (fwFlags & 0x08) != 0; // Bit 3: IsSlot0
|
||||
if (mIsDualSlot) {
|
||||
int runningSlot = mIsSlot0 ? 0 : 1;
|
||||
int targetSlot = mIsSlot0 ? 1 : 0;
|
||||
versionCmd.fwUpdateTargetSlot = targetSlot;
|
||||
LOG.info("Dual-slot firmware detected: running slot {}, will update slot {}", runningSlot, targetSlot);
|
||||
}
|
||||
int hwRev = buf.get() & 0xFF; // Convert to unsigned
|
||||
String codename = PebbleHardware.getCodenameByHardwareId(hwRev);
|
||||
if (codename != null) {
|
||||
|
||||
Reference in New Issue
Block a user