-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Trying to do a Firmware-Update, but nothing is written. #202
Comments
Hello, Also, nothing gets uploaded. Why? Try modifying anything (e.g. a version number) in the firmware or signing process to generate firmware with different hashes and try updating. |
Yes, I am trying to update just the app core with my above code. Is it better to use
Well, I also got the upload working with the example app (nRF Connect Device Manager) but not with my code. We need the code in our app to be working since we don't want to deliver nRF Connect Device Manager to our customers. So what I am missing in my code?
I'll try this and report back. Thanks again for your help! |
Sorry, I meant |
I tried both and none worked in my code. On the other hand I just realized that the nRF Connect Device Manager app only allows me to downgrade the firmware, from 0.3.4+0 down to 0.3.1+0, but not the other way around. |
Please let me know, if you need some additional information! |
Could you share the logs from LogCat? |
Which logCat? That of my app or the one from the nRF Connect Device Manager app? |
From your app, as it's not working. I may compare it with logs from nRFCDM. |
This should be the relevant section from LogCat:
|
Btw. the nRF Connect Device Manager app currently allows me only to downgrade the firmware, for upgrading I currently use our iOS-App. |
You may use Lines 286 to 293 in e15a45d
When using nRF Connect Device Manager you may also see the runtime logs in nRF Logger app, if you have it installed. |
OK, so based on your logs from ^ here's what you get in response: {
"images":
[
{
"slot": 0,
"version": "0.3.2",
"hash": 0xA63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9,
"bootable": true,
"pending": false,
"confirmed": true,
"active": true,
"permanent": false
},
{
"slot": 1,
"version": "0.3.4",
"hash": 0x21962E20681DF1A6137579F91213C7A026C98C32B600DEFD5BB30A04C8131990,
"bootable": true,
"pending": false,
"confirmed": false,
"active": false,
"permanent": false
}
],
"splitStatus": 0
} It seems, like the fw has been successfully uploaded (it's in secondary slot (1)) and the library should send Confirm request at that moment (assuming Will it show the "perding" and "permanent" flags as true? If so, tap RESET button at the bottom to restart the device. It should make the swap. |
Here is the LogCat from my app with
|
Tried that with the nRF CDM and got a Connection timed out error. Here is the LogCat of nRF CDM:
Hope that helps … |
In BASIC-Mode inside the nRF CDM I get this:
|
I think you need to add dependency to |
In BASIC mode you're getting: {
"images": [
{
"slot":0,
"version":"0.3.2",
"hash":"pj/ztdWi0EPIIi1E1k8xsjCafXSaz9Ey5KW1qZoI7bk=",
"bootable":true,
"pending":false,
"confirmed":true,
"active":true,
"permanent":false
},
{
"slot":1,
"version":"0.3.4",
"hash":"IZYuIGgd8aYTdXn5EhPHoCbJjDK2AN79W7MKBMgTGZA=",
"bootable":true,
"pending":true,
"confirmed":false,
"active":false,
"permanent":true
}
],
"splitStatus":0
} Which would indicate, that the 0.3.4 will be set as active after the reset. This is a good response. As you see, the 2nd image was marked as "pending" (will be swapped) and "permanent" con't be reverted after second reset. After the device reboots and starts advertising you should read 0.3.4 in slot 0 with "active" flag on. |
The firmware downgrade works in nRF CDM:
|
Did that and now got the following:
|
Here's what you have before downgrade: {
"images": [
{
"version": "0.3.4",
"hash": "IZYuIGgd8aYTdXn5EhPHoCbJjDK2AN79W7MKBMgTGZA=",
"bootable": true,
"pending": false,
"confirmed": true,
"active": true,
"slot": 0,
"permanent": false
},
{
"slot": 1,
"version": "0.3.2",
"hash": "pj/ztdWi0EPIIi1E1k8xsjCafXSaz9Ey5KW1qZoI7bk=",
"bootable": true,
"active": false,
"confirmed": false,
"pending": false,
"permanent": false
}
],
"splitStatus": 0
} and after sending confirm request: {
"images": [
{
"slot": 0,
"version": "0.3.4",
"hash": "IZYuIGgd8aYTdXn5EhPHoCbJjDK2AN79W7MKBMgTGZA=",
"bootable": true,
"pending": false,
"confirmed": true,
"active": true,
"permanent": false
},
{
"slot": 1,
"version": "0.3.2",
"hash": "pj/ztdWi0EPIIi1E1k8xsjCafXSaz9Ey5KW1qZoI7bk=",
"bootable": true,
"pending": true,
"confirmed": false,
"active": false,
"permanent": true
}
],
"splitStatus": 0
} This is the same as #202 (comment), where you upgrade (only version and hash are swapped). IMO both up and downgrade should work. |
Based on you latest comment I deduct, that from your app you're trying to send 0.3.2, not 0.3.4. It checks that the hash of the "active" slot 0 is the same as in the binary and reports success, no action needed. |
Now I am baffled. I am pretty sure I am uploading 0.3.4 Is there any way to confirm this? |
You may open the file that you're sending from your app in nRF CDM and compare hashes. In your app you may also print the hash of the binaries from the ZIP file or teh BIN, depending on what you are sending.
|
Our Firmware developer tells me right now that it is required for our appliance to work to write to slot0, not slot1, to make the firmware work. How do I force a write to slot0? Thanks again, Lars |
Hmm.. the standard way of upgrading is by sending the image to slot 1 (secondary), then confirming them (sending confirm command with the right hash) and reseting the board (sending reset command). This will cause McuBoot to swap images, so the new one lands in slot 0 and is set as active. And I believe this is exactly the same as the iOS app is doing. |
Here is the LogCat with the hash of the image I am going to upload (produced by the following code): val imageSet = ZipPackage(firmwareFile.readBytes()).binaries
Timber.d("imageSet.images[0].image.hash: ${imageSet.images[0].image.hash.toHex()}")
dfuManager.setMode(FirmwareUpgradeManager.Mode.CONFIRM_ONLY)
try {
dfuManager.start(imageSet, dfuManagerSettings)
} catch (e: Exception) {
Timber.e(e)
}
{
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "A63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
},
{
"active": false,
"bootable": true,
"confirmed": false,
"hash": "21962E20681DF1A6137579F91213C7A026C98C32B600DEFD5BB30A04C8131990",
"pending": false,
"permanent": false,
"slot": 1,
"version": "0.3.4"
}
],
"splitStatus": 0
}
|
Btw. ZipPackage is derived from https://github.com/NordicSemiconductor/Android-nRF-Connect-Device-Manager/blob/main/sample/src/main/java/io/runtime/mcumgr/sample/utils/ZipPackage.java and has been adapted a bit (to provide getManifestFileVersion() which we need to compare the downloaded version of our firmware to the one on the device): ZipPackage.javapackage io.runtime.mcumgr.sample.utils;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import io.runtime.mcumgr.dfu.mcuboot.model.ImageSet;
import io.runtime.mcumgr.dfu.mcuboot.model.TargetImage;
import io.runtime.mcumgr.dfu.suit.model.CacheImageSet;
import io.runtime.mcumgr.exception.McuMgrException;
import timber.log.Timber;
public final class ZipPackage {
private static final String MANIFEST = "manifest.json";
@SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"})
@Keep
private static class Manifest {
private int formatVersion;
private File[] files;
@Keep
private static class File {
/**
* The file type. Expected vales are: "application", "bin", "suit-envelope".
*/
private String type;
/**
* The name of the image file.
*/
private String file;
/**
* The version of the image file.
*/
private String version_MCUBOOT;
/**
* The size of the image file in bytes. This is declared size and does not have to
* be equal to the actual file size.
*/
private int size;
/**
* Image index is used for multi-core devices. Index 0 is the main core (app core),
* index 1 is secondary core (net core), etc.
* <p>
* For single-core devices this is not present in the manifest file and defaults to 0.
*/
private int imageIndex = 0;
/**
* The slot number where the image is to be sent. By default images are sent to the
* secondary slot and then swapped to the primary slot after the image is confirmed
* and the device is reset.
* <p>
* However, if the device supports Direct XIP feature it is possible to run an app
* from a secondary slot. The image has to be compiled for this slot. A ZIP package
* can contain images for both slots. Only the one targeting the available one will
* be sent.
* @since NCS v 2.5, nRF Connect Device Manager 1.8.
*/
private int slot = 0; // FIXME MARC: 0 works!, was TargetImage.SLOT_SECONDARY;
/**
* The target partition ID. This parameter is valid for files with type `cache`.
*/
private int partition = 0;
}
}
private Manifest manifest;
private final Map<String, byte[]> entries = new HashMap<>();
public ArrayList<String> getManifestFileVersion() {
ArrayList<String> versions = new ArrayList<>(manifest.files.length);
for (Manifest.File file : manifest.files) {
versions.add(file.version_MCUBOOT);
}
return versions;
}
public ZipPackage(@NonNull final byte[] data) throws IOException {
ZipEntry ze;
// Unzip the file and look for the manifest.json.
final ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(data));
while ((ze = zis.getNextEntry()) != null) {
if (ze.isDirectory())
throw new IOException("Invalid ZIP");
final String name = validateFilename(ze.getName(), ".");
if (name.equals(MANIFEST)) {
final Gson gson = new GsonBuilder().create();
manifest = gson.fromJson(new InputStreamReader(zis), Manifest.class);
} else if (name.endsWith(".bin") || name.endsWith(".suit")) {
final byte[] content = getData(zis);
entries.put(name, content);
} else {
Timber.w("Unsupported file found: %s", name);
}
}
}
public ImageSet getBinaries() throws IOException, McuMgrException {
final ImageSet binaries = new ImageSet();
// Search for images.
for (final Manifest.File file: manifest.files) {
final String name = file.file;
final byte[] content = entries.get(name);
if (content == null)
throw new IOException("File not found: " + name);
binaries.add(new TargetImage(file.imageIndex, file.slot, content));
}
return binaries;
}
/**
* Returns the SUIT envelope.
* <p>
* This is valid only for SUIT updates using SUIT manager.
* @return The SUIT envelope, or null if not present in the ZIP.
*/
public byte[] getSuitEnvelope() {
// First, search for an entry of type "suit-envelope".
for (final Manifest.File file: manifest.files) {
if (file.type.equals("suit-envelope")) {
return entries.get(file.file);
}
}
// If not found, search for a file with the ".suit" extension.
for (final Manifest.File file: manifest.files) {
if (file.file.endsWith(".suit")) {
return entries.get(file.file);
}
}
// Not found.
return null;
}
/**
* Raw cache images are sent to the device together with the SUIT envelope before starting the
* update process. The cache images are stored in the cache partitions.
*
* @return The cache images, or null if not present in the ZIP.
* @throws IOException if at least one of the cache images is missing.
*/
public CacheImageSet getCacheBinaries() throws IOException {
final CacheImageSet cache = new CacheImageSet();
// Search for images.
for (final Manifest.File file: manifest.files) {
if (file.type.equals("cache")) {
final String name = file.file;
final byte[] content = entries.get(name);
if (content == null)
throw new IOException("File not found: " + name);
cache.add(file.partition, content);
}
}
if (cache.getImages().isEmpty())
return null;
return cache;
}
public byte[] getResource(@NonNull final String name) {
return entries.get(name);
}
private byte[] getData(@NonNull ZipInputStream zis) throws IOException {
final byte[] buffer = new byte[1024];
// Read file content to byte array
final ByteArrayOutputStream os = new ByteArrayOutputStream();
int count;
while ((count = zis.read(buffer)) != -1) {
os.write(buffer, 0, count);
}
return os.toByteArray();
}
/**
* Validates the path (not the content) of the zip file to prevent path traversal issues.
*
* <p> When unzipping an archive, always validate the compressed files' paths and reject any path
* that has a path traversal (such as ../..). Simply looking for .. characters in the compressed
* file's path may not be enough to prevent path traversal issues. The code validates the name of
* the entry before extracting the entry. If the name is invalid, the entire extraction is aborted.
* <p>
*
* @param filename The path to the file.
* @param intendedDir The intended directory where the zip should be.
* @return The validated path to the file.
* @throws java.io.IOException Thrown in case of path traversal issues.
*/
@SuppressWarnings("SameParameterValue")
private String validateFilename(@NonNull final String filename,
@NonNull final String intendedDir)
throws IOException {
File f = new File(filename);
String canonicalPath = f.getCanonicalPath();
File iD = new File(intendedDir);
String canonicalID = iD.getCanonicalPath();
if (canonicalPath.startsWith(canonicalID)) {
return canonicalPath.substring(1); // remove leading "/"
} else {
throw new IllegalStateException("File is outside extraction target directory.");
}
}
} |
Good morning, |
I went through the code and cannot find the reason. Could you go to the |
Which |
You could clone the repo to |
I did this and I have some new output for you:
{
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "A63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
},
{
"active": false,
"bootable": true,
"confirmed": false,
"hash": "4B19673BD336D0FAD78BBD2CD589B3F542DB6EDB953650B05AF8891FFBD0F715",
"pending": false,
"permanent": false,
"slot": 1,
"version": "0.3.4.1"
}
],
"splitStatus": 0
}
{
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "A63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
},
{
"active": false,
"bootable": true,
"confirmed": false,
"hash": "4B19673BD336D0FAD78BBD2CD589B3F542DB6EDB953650B05AF8891FFBD0F715",
"pending": false,
"permanent": false,
"slot": 1,
"version": "0.3.4.1"
}
],
"splitStatus": 0
}
I hope this was helpful! I also created #209 for you as a reference, you can close the PR later on, I don't expect it to be merged. Kind regards, Lars |
Seems like you stared 2 DFU operations at once? Everything seems duplicated. I also don't know why the last notifications are repeated 4 times, where only 2 requests were made. |
Also, your logs don't contain the newly added logs. In nRFCDM we're using |
Did so and got this:
{
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "pj/ztdWi0EPIIi1E1k8xsjCafXSaz9Ey5KW1qZoI7bk=",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
},
{
"active": false,
"bootable": true,
"confirmed": false,
"hash": "SxlnO9M20PrXi70s1Ymz9ULbbtuVNlCwWviJH/vQ9xU=",
"pending": false,
"permanent": false,
"slot": 1,
"version": "0.3.4.1"
}
],
"splitStatus": 0
}
Also, your nRF CDM app I checked which images are currently on our device and got this: |
OK, I found the issue. private int slot = 0; // FIXME MARC: 0 works!, was TargetImage.SLOT_SECONDARY; (see #202 (comment) -> expand ZipPackage) By modifying that this image is skipped, as an active image is found on that slot, therefore nothing can be send there. |
That was a change done by my boss, I've already asked him why he did this. Reverting the change seems to make it work, thanks again! |
Log analisisYou tries to send this image:
Connection part (boring...):
Checking bootloader mode, which are not supported (assuming McuBoot):
Validating what's on the device:
An image with the same hash was found on the secondary slot: {
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "A63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
},
{
"active": false,
"bootable": true,
"confirmed": false,
"hash": "4B19673BD336D0FAD78BBD2CD589B3F542DB6EDB953650B05AF8891FFBD0F715",
"pending": false,
"permanent": false,
"slot": 1,
"version": "0.3.4.1"
}
],
"splitStatus": 0
} Now magic happens:
|
I understand why the change was made. The image was compiled against the primary slot. However, from the library point of view, as it sends it to the secondary slot, the target slot should be If you'd be using Direct XIP feature, there it actually makes sense. A ZIP file may contain 2 binaries of the same app, compiled for the primary and secondary slot. The library chooses the one which slot isn't active and uploads only this one. |
May I know in which case changing the |
My boss said he always got a timeout for slot 1 so he changed it to slot 0. |
If possible, could you paste logs from such timeout? I wonder what's the root cause. |
My boss had a brilliant idea: erase the inactive slot and try again. I did so and now I am also getting the timeout:
I hope that helps you to nail down the issue! |
When you click this "expand" icon next to Firmware Upgrade - what value do you have as Number of mcumgr buffers? To enable pipelining, compile the firmware with |
The value was set to 4, I changed it to 1 as you recommended and tried again:
{
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "A63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
}
],
"splitStatus": 0
}
{
"images": [
{
"active": true,
"bootable": true,
"confirmed": true,
"hash": "A63FF3B5D5A2D043C8222D44D64F31B2309A7D749ACFD132E4A5B5A99A08EDB9",
"pending": false,
"permanent": false,
"slot": 0,
"version": "0.3.2"
}
],
"splitStatus": 0
}
|
If you have nRF Connect Device Manager running from code, could you open @Override
public int getMinLogPriority() {
return Log.DEBUG;
} method and try again? It will log also during uploading (by default logging on upload is disabled as speed optimization). |
Also, have a look at:
Looks like the device doesn't respond with notifications. Library tries 5 times and gives up. |
Hello, |
Hi @philips77 , I am currently working on something else. I'll coming back to you once I have new results. Please be patient. |
Hi @philips77 , some news. I found some time to add that Method to
If you want me to run our app please let me know! |
Hi,
I am trying to implement a Firmware-Update capability for a custom device employing the Nordic 5340 chip, in a Jetpack-Compose Android app.
For that purpose I use the following dependency:
and this Kotlin code:
the code does something, but it is not updating the firmware. We also have an iOS app for that purpose (using your iOSMcuManagerLibrary) and this is working with the same firmware file. It also takes quite a time longer than the stuff the "no.nordicsemi.android:mcumgr-ble:2.2.1" is doing, it finishes way to fast on Android. Also, the callback function
onUploadProgressChanged
seems never to be called, here is a log output (filtered for those callback functions):Am I missing something? Did I omit something important in my code?
Kind regards,
Lars
The text was updated successfully, but these errors were encountered: