From 3c2daee508bfbb16b15b70d373dbda616d6f72cd Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 28 May 2024 12:47:44 +0200 Subject: [PATCH 1/4] Showing SUIT manifests on Image control fragment --- .../runtime/mcumgr/managers/SUITManager.java | 18 +- .../mcumgr/response/suit/KnownRole.java | 55 ++++++ .../suit/McuMgrManifestListResponse.java | 55 +----- .../suit/McuMgrManifestStateResponse.java | 86 ++++++++- .../fragment/mcumgr/ImageControlFragment.java | 45 ++++- .../mcumgr/ImageControlViewModel.java | 167 ++++++++++++++++-- .../main/res/values/strings_image_control.xml | 22 ++- 7 files changed, 378 insertions(+), 70 deletions(-) create mode 100644 mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/KnownRole.java diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/managers/SUITManager.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/managers/SUITManager.java index 2cf697ab..3df62cec 100644 --- a/mcumgr-core/src/main/java/io/runtime/mcumgr/managers/SUITManager.java +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/managers/SUITManager.java @@ -105,7 +105,19 @@ public void listManifests(@NotNull McuMgrCallback ca public void getManifestState(int role, @NotNull McuMgrCallback callback) { HashMap payloadMap = new HashMap<>(); payloadMap.put("role", role); - send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class, callback); + send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class, new McuMgrCallback<>() { + @Override + public void onResponse(@NotNull McuMgrManifestStateResponse response) { + // The role isn't returned in the response, so we need to set it manually. + response.role = role; + callback.onResponse(response); + } + + @Override + public void onError(@NotNull McuMgrException error) { + callback.onError(error); + } + }); } /** @@ -120,7 +132,9 @@ public void getManifestState(int role, @NotNull McuMgrCallback payloadMap = new HashMap<>(); payloadMap.put("role", role); - return send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class); + final McuMgrManifestStateResponse response = send(OP_READ, ID_MANIFEST_STATE, payloadMap, SHORT_TIMEOUT, McuMgrManifestStateResponse.class); + response.role = role; + return response; } /** diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/KnownRole.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/KnownRole.java new file mode 100644 index 00000000..e5444bc0 --- /dev/null +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/KnownRole.java @@ -0,0 +1,55 @@ +package io.runtime.mcumgr.response.suit; + +import org.jetbrains.annotations.NotNull; + +/** + * Known manifest roles. + * This enum is based on suit_metadata.h. + */ +public enum KnownRole { + UNKNOWN(0x00), + /** Manifest describes the entry-point for all Nordic-controlled manifests. */ + SEC_TOP(0x10), + /** Manifest describes SDFW firmware and recovery updates. */ + SEC_SDFW(0x11), + /** Manifest describes SYSCTRL firmware update and boot procedures. */ + SEC_SYSCTRL(0x12), + + /** Manifest describes the entry-point for all OEM-controlled manifests. */ + APP_ROOT(0x20), + /** Manifest describes OEM-specific recovery procedure. */ + APP_RECOVERY(0x21), + /** Manifest describes OEM-specific binaries, specific for application core. */ + APP_LOCAL_1(0x22), + /** Manifest describes OEM-specific binaries, specific for application core. */ + APP_LOCAL_2(0x23), + /** Manifest describes OEM-specific binaries, specific for application core. */ + APP_LOCAL_3(0x24), + + /** Manifest describes radio part of OEM-specific recovery procedure. */ + RAD_RECOVERY(0x30), + /** Manifest describes OEM-specific binaries, specific for radio core. */ + RAD_LOCAL_1(0x31), + /** Manifest describes OEM-specific binaries, specific for radio core. */ + RAD_LOCAL_2(0x32); + + /** The role value. */ + public final int id; + + KnownRole(int id) { + this.id = id; + } + + /** + * Returns the role as a {@link KnownRole} enum, or null if the role is unknown. + */ + @NotNull + public static KnownRole getOrNull(final int role) { + for (KnownRole knownRole : KnownRole.values()) { + if (knownRole.id == role) { + return knownRole; + } + } + return UNKNOWN; + } +} \ No newline at end of file diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestListResponse.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestListResponse.java index 9e0d568f..cc566b3a 100644 --- a/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestListResponse.java +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestListResponse.java @@ -9,8 +9,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.jetbrains.annotations.Nullable; - import java.util.List; import io.runtime.mcumgr.McuMgrCallback; @@ -21,60 +19,13 @@ public class McuMgrManifestListResponse extends McuMgrResponse { public static class Manifest { - /** - * Known manifest roles. - * This enum is based on suit_metadata.h. + * The manifest role. + * + * @see KnownRole */ - public enum KnownRole { - /** Manifest describes the entry-point for all Nordic-controlled manifests. */ - SEC_TOP(0x10), - /** Manifest describes SDFW firmware and recovery updates. */ - SEC_SDFW(0x11), - /** Manifest describes SYSCTRL firmware update and boot procedures. */ - SEC_SYSCTRL(0x12), - - /** Manifest describes the entry-point for all OEM-controlled manifests. */ - APP_ROOT(0x20), - /** Manifest describes OEM-specific recovery procedure. */ - APP_RECOVERY(0x21), - /** Manifest describes OEM-specific binaries, specific for application core. */ - APP_LOCAL_1(0x22), - /** Manifest describes OEM-specific binaries, specific for application core. */ - APP_LOCAL_2(0x23), - /** Manifest describes OEM-specific binaries, specific for application core. */ - APP_LOCAL_3(0x24), - - /** Manifest describes radio part of OEM-specific recovery procedure. */ - RAD_RECOVERY(0x30), - /** Manifest describes OEM-specific binaries, specific for radio core. */ - RAD_LOCAL_1(0x31), - /** Manifest describes OEM-specific binaries, specific for radio core. */ - RAD_LOCAL_2(0x32); - - /** The role value. */ - public final int id; - - KnownRole(int id) { - this.id = id; - } - } - @JsonProperty("role") public int role; - - /** - * Returns the role as a {@link KnownRole} enum, or null if the role is unknown. - */ - @Nullable - public KnownRole getRoleOrNull() { - for (KnownRole knownRole : KnownRole.values()) { - if (knownRole.id == role) { - return knownRole; - } - } - return null; - } } /** diff --git a/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestStateResponse.java b/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestStateResponse.java index df32010c..0039369b 100644 --- a/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestStateResponse.java +++ b/mcumgr-core/src/main/java/io/runtime/mcumgr/response/suit/McuMgrManifestStateResponse.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.nio.ByteBuffer; @@ -21,6 +22,13 @@ /** @noinspection unused*/ public class McuMgrManifestStateResponse extends McuMgrResponse { + /** + * The manifest role. + * + * @see KnownRole + */ + public int role; + /** * Use {@link #getClassId()} to get the class ID as UUID. */ @@ -42,7 +50,7 @@ public class McuMgrManifestStateResponse extends McuMgrResponse { @JsonProperty("digest") public byte[] digest; @JsonProperty("digest_algorithm") - public DigestAlgorithm digest_algorithm; + public DigestAlgorithm digestAlgorithm; @JsonProperty("signature_check") public SignatureVerification signatureCheck; @JsonProperty("sequence_number") @@ -96,6 +104,19 @@ public enum DigestAlgorithm { DigestAlgorithm(int code) { this.code = code; } + + @NotNull + @Override + public String toString() { + switch (this) { + case SHA_256: + return "SHA-256"; + case SHA_512: + return "SHA-512"; + default: + return super.toString(); + } + } } public enum SignatureVerification { @@ -109,6 +130,21 @@ public enum SignatureVerification { SignatureVerification(int code) { this.code = code; } + + @NotNull + @Override + public String toString() { + switch (this) { + case NOT_CHECKED: + return "Not checked"; + case FAILED: + return "Failed"; + case PASSED: + return "Passed"; + default: + return super.toString(); + } + } } public enum DowngradePreventionPolicy { @@ -125,6 +161,21 @@ public enum DowngradePreventionPolicy { DowngradePreventionPolicy(int code) { this.code = code; } + + @NotNull + @Override + public String toString() { + switch (this) { + case DISABLED: + return "Disabled"; + case ENABLED: + return "Enabled"; + case UNKNOWN: + return "Unknown"; + default: + return super.toString(); + } + } } public enum IndependentUpdateabilityPolicy { @@ -141,6 +192,21 @@ public enum IndependentUpdateabilityPolicy { IndependentUpdateabilityPolicy(int code) { this.code = code; } + + @NotNull + @Override + public String toString() { + switch (this) { + case DENIED: + return "Denied"; + case ALLOWED: + return "Allowed"; + case UNKNOWN: + return "Unknown"; + default: + return super.toString(); + } + } } public enum SignatureVerificationPolicy { @@ -159,6 +225,24 @@ public enum SignatureVerificationPolicy { SignatureVerificationPolicy(int code) { this.code = code; } + + @NotNull + @Override + public String toString() { + switch (this) { + case DISABLED: + return "Disabled"; + case ENABLED_ON_UPDATE: + return "Enabled on update"; + case ENABLED_ON_UPDATE_AND_BOOT: + return "Enabled on update and boot"; + case UNKNOWN: + return "Unknown"; + default: + return super.toString(); + + } + } } private enum ReleaseType { diff --git a/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ImageControlFragment.java b/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ImageControlFragment.java index 10d7a26d..ecdd1d62 100644 --- a/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ImageControlFragment.java +++ b/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ImageControlFragment.java @@ -27,18 +27,26 @@ import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; + +import java.util.List; +import java.util.Locale; + import io.runtime.mcumgr.McuMgrErrorCode; import io.runtime.mcumgr.exception.McuMgrErrorException; import io.runtime.mcumgr.exception.McuMgrException; import io.runtime.mcumgr.response.img.McuMgrImageStateResponse; +import io.runtime.mcumgr.response.suit.KnownRole; +import io.runtime.mcumgr.response.suit.McuMgrManifestStateResponse; import io.runtime.mcumgr.sample.R; import io.runtime.mcumgr.sample.databinding.FragmentCardImageControlBinding; import io.runtime.mcumgr.sample.di.Injectable; import io.runtime.mcumgr.sample.dialog.HelpDialogFragment; import io.runtime.mcumgr.sample.dialog.SelectImageDialogFragment; import io.runtime.mcumgr.sample.utils.StringUtils; +import io.runtime.mcumgr.sample.utils.Utils; import io.runtime.mcumgr.sample.viewmodel.mcumgr.ImageControlViewModel; import io.runtime.mcumgr.sample.viewmodel.mcumgr.McuMgrViewModelFactory; +import io.runtime.mcumgr.util.ByteUtil; public class ImageControlFragment extends Fragment implements Injectable, SelectImageDialogFragment.OnImageSelectedListener { private static final int REQUEST_TEST = 1; @@ -89,6 +97,7 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved ((ViewGroup) view).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); viewModel.getResponse().observe(getViewLifecycleOwner(), this::printImageSlotInfo); + viewModel.getManifests().observe(getViewLifecycleOwner(), this::printSuitManifests); viewModel.getError().observe(getViewLifecycleOwner(), this::printError); viewModel.getTestOperationAvailability().observe(getViewLifecycleOwner(), enabled -> binding.actionTest.setEnabled(enabled)); @@ -107,7 +116,10 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved // Other actions will be optionally enabled by other observers } }); - binding.actionRead.setOnClickListener(v -> viewModel.read()); + binding.actionRead.setOnClickListener(v -> { + binding.imageControlValue.setText(R.string.image_control_loading); + viewModel.read(); + }); binding.actionTest.setOnClickListener(v -> onActionClick(REQUEST_TEST)); binding.actionConfirm.setOnClickListener(v -> onActionClick(REQUEST_CONFIRM)); binding.actionErase.setOnClickListener(v -> onActionClick(REQUEST_ERASE)); @@ -138,6 +150,37 @@ private void onActionClick(final int requestId) { } } + private void printSuitManifests(@Nullable final List manifests) { + if (manifests != null) { + final SpannableStringBuilder builder = new SpannableStringBuilder(); + int i = 0; + for (McuMgrManifestStateResponse manifest: manifests) { + final KnownRole role = KnownRole.getOrNull(manifest.role); + final int start = builder.length(); + builder.append(getString(R.string.image_suit_manifest_role, ++i, role.toString(), manifest.role)); + builder.setSpan(new StyleSpan(Typeface.BOLD), + start, builder.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + final String vendor = manifest.isVendorNordic() ? "Nordic Semiconductor ASA" : "Unknown"; + final String version = manifest.getVersion(); + builder.append(getString(R.string.image_suit_manifest_details, + manifest.getClassId().toString().toUpperCase(Locale.ROOT), "Unknown", + manifest.getVendorId().toString().toUpperCase(Locale.ROOT), vendor, + manifest.downgradePreventionPolicy, + manifest.independentUpdateabilityPolicy, + manifest.signatureVerificationPolicy, + ByteUtil.byteArrayToHex(manifest.digest, "%02X"), + manifest.digestAlgorithm, + manifest.signatureCheck, + manifest.sequenceNumber, + version != null ? version : "Unknown")); + } + binding.imageControlValue.setText(builder); + binding.imageControlError.setVisibility(View.GONE); + } else { + binding.imageControlValue.setText(null); + } + } + @SuppressLint("StringFormatMatches") private void printImageSlotInfo(@Nullable final McuMgrImageStateResponse response) { if (response != null) { diff --git a/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ImageControlViewModel.java b/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ImageControlViewModel.java index c1339160..7e242055 100644 --- a/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ImageControlViewModel.java +++ b/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ImageControlViewModel.java @@ -11,19 +11,36 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + import javax.inject.Inject; import javax.inject.Named; import io.runtime.mcumgr.McuMgrCallback; +import io.runtime.mcumgr.exception.McuMgrErrorException; import io.runtime.mcumgr.exception.McuMgrException; +import io.runtime.mcumgr.exception.McuMgrTimeoutException; +import io.runtime.mcumgr.managers.DefaultManager; import io.runtime.mcumgr.managers.ImageManager; +import io.runtime.mcumgr.managers.SUITManager; +import io.runtime.mcumgr.response.dflt.McuMgrBootloaderInfoResponse; import io.runtime.mcumgr.response.img.McuMgrImageResponse; import io.runtime.mcumgr.response.img.McuMgrImageStateResponse; +import io.runtime.mcumgr.response.suit.McuMgrManifestListResponse; +import io.runtime.mcumgr.response.suit.McuMgrManifestStateResponse; public class ImageControlViewModel extends McuMgrViewModel { + private final DefaultManager osManager; private final ImageManager manager; + private final SUITManager suitManager; + private final MutableLiveData responseLiveData = new MutableLiveData<>(); + private final MutableLiveData> manifestsLiveData = new MutableLiveData<>(); private final MutableLiveData testAvailableLiveData = new MutableLiveData<>(); private final MutableLiveData confirmAvailableLiveData = new MutableLiveData<>(); private final MutableLiveData eraseAvailableLiveData = new MutableLiveData<>(); @@ -32,11 +49,25 @@ public class ImageControlViewModel extends McuMgrViewModel { @NonNull private final byte[][] hashes; + private interface OnBootloaderReceived { + void run(@NonNull BootloaderType type); + } + + private enum BootloaderType { + MCUBOOT, + SUIT + } + private BootloaderType bootloaderType = null; + @Inject - ImageControlViewModel(final ImageManager manager, + ImageControlViewModel(final DefaultManager osManager, + final ImageManager manager, + final SUITManager suitManager, @Named("busy") final MutableLiveData state) { super(state); + this.osManager = osManager; this.manager = manager; + this.suitManager = suitManager; // The current version supports 2 images. this.hashes = new byte[2][]; } @@ -46,6 +77,11 @@ public LiveData getResponse() { return responseLiveData; } + @NonNull + public LiveData> getManifests() { + return manifestsLiveData; + } + @NonNull public LiveData getTestOperationAvailability() { return testAvailableLiveData; @@ -74,10 +110,53 @@ public int[] getValidImages() { return new int[] { 1 }; } + private void withBootloader(@NonNull OnBootloaderReceived callback) { + if (bootloaderType != null) { + callback.run(bootloaderType); + return; + } + + setBusy(); + errorLiveData.setValue(null); + osManager.bootloaderInfo(DefaultManager.BOOTLOADER_INFO_QUERY_BOOTLOADER, new McuMgrCallback<>() { + @Override + public void onResponse(@NotNull McuMgrBootloaderInfoResponse response) { + if (response.bootloader != null && response.bootloader.equals("SUIT")) { + bootloaderType = BootloaderType.SUIT; + } else { + bootloaderType = BootloaderType.MCUBOOT; + } + postReady(); + callback.run(bootloaderType); + } + + @Override + public void onError(@NotNull McuMgrException error) { + if (error instanceof McuMgrErrorException) { + bootloaderType = BootloaderType.MCUBOOT; + callback.run(bootloaderType); + } else { + postError(error); + } + } + }); + } + public void read() { // This is called also from BLE thread after erase(), therefore postValue, not setValue. postBusy(); errorLiveData.postValue(null); + + withBootloader(type -> { + if (type == BootloaderType.SUIT) { + readSuit(); + } else { + readMcuboot(); + } + }); + } + + private void readMcuboot() { manager.list(new McuMgrCallback<>() { @Override public void onResponse(@NonNull final McuMgrImageStateResponse response) { @@ -96,8 +175,59 @@ public void onResponse(@NonNull final McuMgrImageStateResponse response) { @Override public void onError(@NonNull final McuMgrException error) { - errorLiveData.postValue(error); - postReady(null); + postError(error); + } + }); + } + + private void readSuit() { + // Hashes are not used in SUIT mode. + hashes[0] = hashes[1] = null; + + suitManager.listManifests(new McuMgrCallback<>() { + @Override + public void onResponse(@NonNull final McuMgrManifestListResponse list) { + final List manifests = new ArrayList<>(); + if (list.manifests == null || list.manifests.isEmpty()) { + postReady(manifests); + return; + } + // Oh, I wish this class was in Kotlin and I could just use coroutines... + final Object lock = new Object(); + new Thread(() -> { + for (McuMgrManifestListResponse.Manifest manifest : list.manifests) { + suitManager.getManifestState(manifest.role, new McuMgrCallback<>() { + @Override + public void onResponse(@NotNull McuMgrManifestStateResponse response) { + manifests.add(response); + synchronized (lock) { + lock.notifyAll(); + } + } + + @Override + public void onError(@NotNull McuMgrException error) { + synchronized (lock) { + lock.notifyAll(); + } + } + }); + try { + synchronized (lock) { + lock.wait(1000); + } + } catch (final InterruptedException e) { + postError(new McuMgrTimeoutException()); + return; + } + } + postReady(manifests); + }).start(); + } + + @Override + public void onError(@NonNull final McuMgrException error) { + postError(error); } }); } @@ -116,8 +246,7 @@ public void onResponse(@NonNull final McuMgrImageStateResponse response) { @Override public void onError(@NonNull final McuMgrException error) { - errorLiveData.postValue(error); - postReady(null); + postError(error); } }); } @@ -136,8 +265,7 @@ public void onResponse(@NonNull final McuMgrImageStateResponse response) { @Override public void onError(@NonNull final McuMgrException error) { - errorLiveData.postValue(error); - postReady(null); + postError(error); } }); } @@ -156,18 +284,26 @@ public void onResponse(@NonNull final McuMgrImageResponse response) { @Override public void onError(@NonNull final McuMgrException error) { - errorLiveData.postValue(error); - postReady(null); + postError(error); } }); } - private void postReady(@Nullable final McuMgrImageStateResponse response) { + private void postReady(@Nullable final List manifests) { + responseLiveData.postValue(null); + manifestsLiveData.postValue(manifests); + testAvailableLiveData.postValue(false); + confirmAvailableLiveData.postValue(false); + eraseAvailableLiveData.postValue(false); + postReady(); + } + + private void postReady(@NonNull final McuMgrImageStateResponse response) { boolean testEnabled = false; boolean confirmEnabled = false; boolean eraseEnabled = false; - if (response != null && response.images != null) { + if (response.images != null) { for (McuMgrImageStateResponse.ImageSlot image: response.images) { // Skip slots with active fw. if (image.active) @@ -190,4 +326,13 @@ private void postReady(@Nullable final McuMgrImageStateResponse response) { eraseAvailableLiveData.postValue(eraseEnabled); postReady(); } + + private void postError(McuMgrException error) { + errorLiveData.postValue(error); + responseLiveData.postValue(null); + testAvailableLiveData.postValue(false); + confirmAvailableLiveData.postValue(false); + eraseAvailableLiveData.postValue(false); + postReady(); + } } diff --git a/sample/src/main/res/values/strings_image_control.xml b/sample/src/main/res/values/strings_image_control.xml index 8da70970..a5474645 100644 --- a/sample/src/main/res/values/strings_image_control.xml +++ b/sample/src/main/res/values/strings_image_control.xml @@ -13,6 +13,8 @@ Confirm Erase + Loading… + Image is identical to the active one Split Status: %d Image: %d, Slot: %d @@ -25,14 +27,28 @@ • Active: %B\n • Permanent: %B + %d. %s (role: 0x%02X)\n + + • Class ID: %s (%s)\n + • Vendor ID: %s (%s)\n + • Downgrade Prevention Policy: %s\n + • Independent Updateability Policy: %s\n + • Signature Verification Policy: %s\n + • Digest: 0x%s\n + • Digest Algorithm: %s\n + • Signature Verification: %s\n + • Sequence number: %d\n + • Version: %s\n + Help Images card lets you read the status - of image slots on the device.\nWhen a new firmware has been sent to secondary slot, - the TEST, CONFIRM and ERASE buttons become available. + of image slots or SUIT manifests on the device.\n\nWhen a new firmware has been sent to + secondary slot, the TEST, CONFIRM and ERASE buttons become available. The TEST button tells the device to run the new image on its next boot. Such firmware may automatically confirm itself when it\'s configured to do so. You may also make the image swap permanent by tapping CONFIRM button. Each action requires rebooting the target to take effect. You can do this using the - Reset card below.\nERASE button erases the secondary slot(s). + Reset card below.\nERASE button erases the secondary slot(s).\n\nThese + actions are available only on MCUboot bootloader. \ No newline at end of file From e80469dc351c3bd99223798822c055ffc1b3a9f0 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 28 May 2024 12:48:09 +0200 Subject: [PATCH 2/4] Erase settings info update wrt SUIT --- sample/src/main/res/values/strings_image_erase_settings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sample/src/main/res/values/strings_image_erase_settings.xml b/sample/src/main/res/values/strings_image_erase_settings.xml index b87f3e1a..c64e6df5 100644 --- a/sample/src/main/res/values/strings_image_erase_settings.xml +++ b/sample/src/main/res/values/strings_image_erase_settings.xml @@ -13,7 +13,8 @@ Help The Settings card lets you erase application data, including bond information. This feature is supported on devices - running NCS 1.7 or later with settings erase feature enabled.\n\nIf your device is bonded + running NCS 1.7 or later with MCUboot bootloader with settings erase feature + enabled.\n\nIf your device is bonded remember to remove bond information also from the phone (Bluetooth settings -> Forget device). \n\nFor the change to take effect the device needs to be rebooted using the Reset card below, as some settings may be stored in RAM. From 636ede6dd2b99b03a5ed8b0d9d34121b083086af Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 28 May 2024 13:25:37 +0200 Subject: [PATCH 3/4] Displaying error details in Reset fragment --- .../mcumgr/sample/fragment/mcumgr/ResetFragment.java | 3 ++- .../mcumgr/sample/viewmodel/mcumgr/ResetViewModel.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ResetFragment.java b/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ResetFragment.java index 1c708494..80e89bb9 100644 --- a/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ResetFragment.java +++ b/sample/src/main/java/io/runtime/mcumgr/sample/fragment/mcumgr/ResetFragment.java @@ -20,6 +20,7 @@ import io.runtime.mcumgr.sample.databinding.FragmentCardResetBinding; import io.runtime.mcumgr.sample.di.Injectable; +import io.runtime.mcumgr.sample.utils.StringUtils; import io.runtime.mcumgr.sample.viewmodel.mcumgr.McuMgrViewModelFactory; import io.runtime.mcumgr.sample.viewmodel.mcumgr.ResetViewModel; @@ -52,7 +53,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - viewModel.getError().observe(getViewLifecycleOwner(), s -> binding.resetError.setText(s)); + viewModel.getError().observe(getViewLifecycleOwner(), e -> binding.resetError.setText(StringUtils.toString(requireContext(), e))); viewModel.getBusyState().observe(getViewLifecycleOwner(), busy -> binding.actionReset.setEnabled(!busy)); binding.actionReset.setOnClickListener(v -> viewModel.reset()); } diff --git a/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ResetViewModel.java b/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ResetViewModel.java index 15a4b855..22699d37 100644 --- a/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ResetViewModel.java +++ b/sample/src/main/java/io/runtime/mcumgr/sample/viewmodel/mcumgr/ResetViewModel.java @@ -22,7 +22,7 @@ public class ResetViewModel extends McuMgrViewModel { private final DefaultManager manager; - private final MutableLiveData errorLiveData = new MutableLiveData<>(); + private final MutableLiveData errorLiveData = new MutableLiveData<>(); @Inject ResetViewModel(final DefaultManager manager, @@ -32,7 +32,7 @@ public class ResetViewModel extends McuMgrViewModel { } @NonNull - public LiveData getError() { + public LiveData getError() { return errorLiveData; } @@ -57,7 +57,7 @@ public void onDisconnected() { @Override public void onError(@NonNull final McuMgrException error) { - errorLiveData.postValue(error.getMessage()); + errorLiveData.postValue(error); postReady(); } }); From df68671068ec433634ebcda71f3717aeca0d39bb Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 28 May 2024 14:14:54 +0200 Subject: [PATCH 4/4] Image upload info text --- sample/src/main/res/values/strings_image_upload.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sample/src/main/res/values/strings_image_upload.xml b/sample/src/main/res/values/strings_image_upload.xml index df0c1fee..6f2409db 100644 --- a/sample/src/main/res/values/strings_image_upload.xml +++ b/sample/src/main/res/values/strings_image_upload.xml @@ -35,7 +35,8 @@ Help Firmware Update card lets you send - a new firmware image onto the device to the available slot. After sending the image, - use Images card to test or confirm it, and the Reset card to reboot - the device. + a new firmware image or a SUIT candidate manifest onto the device to the available slot. + Depending on the bootloader, the uploaded image must be put into TEST or CONFIRM state + using the Images card. The state can be applied by resetting the device + using the Reset card below. \ No newline at end of file