GithubHelp home page GithubHelp logo

nordicsemiconductor / ios-nrf-connect-device-manager Goto Github PK

View Code? Open in Web Editor NEW
86.0 9.0 37.0 3.41 MB

A mobile management library for devices supporting nRF Connect Device Manager.

Home Page: https://www.nordicsemi.com/Software-and-tools/Software/nRF-Connect-SDK

License: Apache License 2.0

Swift 98.35% Ruby 0.26% Shell 1.39%

ios-nrf-connect-device-manager's Introduction

Platforms License Release Swift Package Manager Compatible

nRF Connect Device Manager

nRF Connect Device Manager library is compatible with McuManager (McuMgr, for short), a management subsystem supported by nRF Connect SDK, Zephyr and Apache Mynewt. It is the recommended protocol for Device Firmware Update(s) on new Nordic-powered devices going forward and should not be confused with the previous protocol, NordicDFU, serviced by the Old DFU Library. McuManager uses the Simple Management Protocol, or SMP, to send and receive message requests from compatible devices. The SMP Transport definition for Bluetooth Low Energy, which this library implements, can be found here.

The library provides a transport agnostic implementation of the McuManager protocol. It contains a default implementation for BLE transport.

Minimum required iOS version is 9.0, originally released in Fall of 2015.

Note

This repository is a fork of the McuManager iOS Library, which is no longer being supported by its original maintainer. As of 2021, we have taken ownership of the library, so all new features and bug fixes will be added here. Please, migrate your projects to point to this Git repsository in order to get future updates. See migration guide.

Compatible Devices

nRF52 Series nRF53 Series nRF54 Series nRF91 Series

This library is designed to work with the SMP Transport over BLE. It is implemented and maintained by Nordic Semiconductor, but it should work any devices communicating via SMP Protocol. If you encounter an issue communicating with a device using any chip, not just Nordic, please file an Issue.

Library Adoption into an Existing Project (Install)

SPM or Swift Package Manager (Recommended)

In Xcode, open your root Project file. Then, switch to the Package Dependencies Tab, and hit the + button underneath your list of added Packages. A new modal window will pop-up. On the upper-right corner of this new window, there's a search box. Paste the URL for this GitHub project https://github.com/NordicSemiconductor/IOS-nRF-Connect-Device-Manager and the Add Package button should enable.

After Xcode fetches your new project dependency, you should now be able to add import iOSMcuManagerLibrary to the Swift files from where you'd like to call upon this library. And you're good to go.

CocoaPods

pod 'iOSMcuManagerLibrary'

Building the Example Project (Requires Xcode & CocoaPods)

"Cocoapods?"

Not to worry, we have you covered. Just follow the instructions here.

Instructions

First, clone the project:

git clone https://github.com/NordicSemiconductor/IOS-nRF-Connect-Device-Manager.git

Then, open the project's directory, navigate to the Example folder, and run pod install:

cd IOS-nRF-Connect-Device-Manager/
cd Example/
pod install

The output should look similar to this:

Analyzing dependencies
Downloading dependencies
Installing SwiftCBOR (0.4.4)
Installing ZIPFoundation (0.9.11)
Installing iOSMcuManagerLibrary (1.3.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 2 dependencies from the Podfile and 3 total pods installed.

You should now be able to open, build & run the Example project by opening the nRF Connect Device Manager.xcworkspace file:

open nRF\ Connect\ Device\ Manager.xcworkspace

Introduction

McuManager is an application layer protocol used to manage and monitor microcontrollers running Apache Mynewt and Zephyr. More specifically, McuManagr implements over-the-air (OTA) firmware upgrades, log and stat collection, and file-system and configuration management.

Command Groups

McuManager are organized by functionality into command groups. In mcumgr-ios, command groups are called managers and extend the McuManager class. The managers (groups) implemented in mcumgr-ios are:

  • DefaultManager: Contains commands relevant to the OS. This includes task and memory pool statistics, device time read & write, and device reset.
  • ImageManager: Manage image state on the device and perform image uploads.
  • StatsManager: Read stats from the device.
  • SettingsManager: Read/Write config values on the device.
  • LogManager: Collect logs from the device.
  • CrashManager: Run crash tests on the device.
  • RunTestManager: Runs tests on the device.
  • FileSystemManager: Download/upload files from the device file system.
  • BasicManager: Send 'Erase App Settings' command to the device.
  • ShellManager: Send McuMgr Shell commands to the device.
  • SuitManager: Send SUIT (Software Update for Internet of Things)-specific commands to the device.

Firmware Upgrade

Firmware upgrade is generally a four step process performed using commands from the image and default commands groups: upload, test, reset, and confirm.

This library provides FirmwareUpgradeManager as a convenience for upgrading the image running on a device.

FirmwareUpgradeManager

A FirmwareUpgradeManager provides an easy way to perform firmware upgrades on a device. A FirmwareUpgradeManager must be initialized with an McuMgrTransport which defines the transport scheme and device. Once initialized, a FirmwareUpgradeManager can perform one firmware upgrade at a time. Firmware upgrades are started using the start(hash: Data, data: Data) method and can be paused, resumed, and canceled using pause(), resume(), and cancel() respectively.

Legacy / App Core-Only Upgrade Example

import iOSMcuManagerLibrary

do {
    // Initialize the BLE transporter using a scanned peripheral
    let bleTransport = McuMgrBleTransport(cbPeripheral)

    // Initialize the FirmwareUpgradeManager using the transport and a delegate
    let dfuManager = FirmwareUpgradeManager(bleTransport, delegate)

    let imageData = /* Read Image Data */
    let imageHash = try McuMgrImage(data: imageData).hash

    // Start the firmware upgrade with the image data
    dfuManager.start(hash: imageHash, data: imageData)
} catch {
    // Reading File / Image, Hash, etc. errors here.
}

Note: Always make your start/pause/cancel DFU API calls from the Main Thread.

Why Hash, now?

Hash was added as a parameter as part of our support for SUIT and DirectXIP (for more information, see below). It is now possible, with SUIT, to selectively pick from within a single 'image' different hashes to Upload for the same 'slot'. Therefore, a way to determine which specific bag of bytes of the same Image the user wants to upload is required. There were two ways of solving this - trying to make the library 'smart' and remove work, or, provide the library user (developer) the freedom to decide. It is a pain to have to add a new parameter, but we have alternative APIs to try to help with this process.

Multi-Image DFU Example

public class ImageManager: McuManager {
    
    public struct Image {
        public let image: Int
        public let slot: Int
        public let hash: Data
        public let data: Data

        /* ... */
    }
}

The above is the input type for Multi-Image DFU call, where a value of 0 for the image parameter means App Core, and an input of 1 means Net Core. These representations are of course subject to change as we expand the capabilities of our products. For the slot parameter, you will typically want to set it to 1, which is the alternate slot that is currently not in use for that specific core. Then, after upload, the firmware device will reset to swap over its slots, making the contents previously uploaded to slot 1 (now in slot 0 after the swap) as active, and vice-versa.

With the Image struct at hand, it's straightforward to make a call to start DFU for either or both cores:

import iOSMcuManagerLibrary

try {
    // Initialize the BLE transport using a scanned peripheral
    let bleTransport = McuMgrBleTransport(cbPeripheral)

    // Initialize the FirmwareUpgradeManager using the transport and a delegate
    let dfuManager = FirmwareUpgradeManager(bleTransport, delegate)

    // Build Multi-Image DFU parameters
    let appCoreData = try Data(contentsOf: appCoreFileURL)
    let appCoreDataHash = try McuMgrImage(data: appCoreData).hash
    let netCoreData = try Data(contentsOf: netCoreFileURL)
    let netCoreDataHash = try McuMgrImage(data: netCoreData).hash
    
    let images: [ImageManager.Image] = [
        (image: 0, slot: 1, hash: appCoreDataHash, data: appCoreData),
        (image: 1, slot: 1, hash: netCoreDataHash, data: netCoreData)
    ]

    // Start Multi-Image DFU firmware upgrade
    dfuManager.start(images: images)
} catch {
    // Errors here.
}

Whereas non-DirectXIP packages target the secondary / non-active slot, also known as slot 1, for each ImageManager.Image, special attention must be given to DirectXIP packages. Since they provide multiple hashes of the same ImageManager.Image, one for each available slot. This is because firmware supporting DirectXIP can boot from either slot, not requiring a swap. So, for DirectXIP the [ImageManager.Image] array might closer to:

import iOSMcuManagerLibrary

try {
    /*
    Initialise transport & manager as above.
    */

    // Build DirectXIP parameters
    let appCoreSlotZeroData = try Data(contentsOf: appCoreSlotZeroURL)
    let appCoreSlotZeroHash = try McuMgrImage(data: appCoreSlotZeroData).hash
    let appCoreSlotOneData = try Data(contentsOf: appCoreSlotOneURL)
    let appCoreSlotOneHash = try McuMgrImage(data: appCoreSlotOneData).hash
    
    let directXIP: [ImageManager.Image] = [
        (image: 0, slot: 0, hash: appCoreSlotZeroHash, data: appCoreSlotZeroData),
        (image: 0, slot: 1, hash: appCoreSlotOneHash, data: appCoreSlotOneData)
    ]
    
    // Start DirectXIP Firmware Upgrade
    dfuManager.start(images: directXIP)
} catch {
    // Errors here.
}

Multi-Image DFU Format

Usually, when performing Multi-Image DFU, the delivery format of the attached images for each core will be in a .zip file. This is because the .zip file allows us to bundle the necessary information, including the images for each core and which image should be uploaded to each core. This association between the image files, usually in .bin format, and which core they should be uploaded to, is written in a mandatory JSON format called the Manifest. This manifest.json is generated by our nRF Connect SDK as part of our Zephyr build system, as documented here. You can look at the McuMgrManifest struct definition within the library for an insight into the information contained within the manifest.

Now, the issue is that there's a gap between the aforementioned API, and the output from our Zephyr build system, which is a .zip file. To bridge this gap, we wrote McuMgrPackage, which takes a URL in its init() function. So, given the URL to the .zip file, it is possible to kickstart Multi-Image DFU in this manner:

import iOSMcuManagerLibrary

do {
    // Initialize the BLE transporter using a scanned peripheral
    let bleTransport = McuMgrBleTransport(cbPeripheral)

    // Initialize the FirmwareUpgradeManager using the transport and a delegate
    let dfuManager = FirmwareUpgradeManager(bleTransport, delegate)

    // Read Multi-Image DFU package
    let dfuPackage = try McuMgrPackage(from: dfuPackageUrl)

    // Start Multi-Image DFU firmware upgrade
    dfuManager.start(images: dfuPackage.images)
} catch {
    // try McuMgrPackage(from:) will throw McuMgrPackage.Error(s) here.
}

Have a look at FirmwareUpgradeViewController.swift from the Example project for a more detailed usage sample.

Because of the JSON Manifest Parsing nature of the McuMgrPackage method, you might encounter corner cases / crashes. If you find these, please report them back to us. But regardless, the McuMgrPackage shortcut is a wrapper that initialises the aforementioned [ImageManager.Image] array API. So you can always fallback to that.

SUIT Example

SUIT, or Software Update for the Internet of Things, is another method of DFU that also makes use of the existing SMP Bluetooth Service. However, it places a lot of the logic (read: blame) onto the target firmware or device rather than the sender. This simplifies the internal process, but also makes parsing the raw CBOR more complicated. Regardless, from the sender's perspective, we only need to send the Data in full, and allow the target to figure things out. The only other argument needed is the Hash which, supports different Modes, also known as Types or Algorithms. The list of SUIT Algorithms includes SHA256, SHAKE128, SHA384, SHA512 and SHAKE256. Of these, the only currently supported mode is SHA256.

import iOSMcuManagerLibrary

do {
    // Initialize the BLE transporter using a scanned peripheral
    let bleTransport = McuMgrBleTransport(cbPeripheral)

    // Initialize the FirmwareUpgradeManager using the transport and a delegate
    let dfuManager = FirmwareUpgradeManager(bleTransport, delegate)

    // Parse McuMgrSuitEnvelope from File URL
    let envelope = try McuMgrSuitEnvelope(from: dfuSuitEnvelopeUrl)

    // Look for valid Algorithm Hash 
    guard let sha256Hash = envelope.digest.hash(for: .sha256) else {
        throw McuMgrSuitParseError.supportedAlgorithmNotFound
    }

    // Set Configuration for SUIT
    var configuration = FirmwareUpgradeConfiguration()
    configuration.suitMode = true
    // Other Upgrade Modes can be set, but upgrade will fail since
    // SUIT doesn't support TEST and CONFIRM commands for example. 
    configuration.upgradeMode = .uploadOnly
    try dfuManager.start(hash: sha256Hash, data: envelope.data, using: configuration)
} catch {
    // Handle errors from McuMgrSuitEnvelope init, start() API call, etc.
}

Firmware Upgrade Mode

McuManager firmware upgrades can be performed following slightly different procedures. These different upgrade modes determine the commands sent after the upload step. The FirmwareUpgradeManager can be configured to perform these upgrade variations by setting the upgradeMode in FirmwareUpgradeManager's configuration property, explained below. (NOTE: this was previously set with mode property of FirmwareUpgradeManager, now removed) The different firmware upgrade modes are as follows:

  • .testAndConfirm: This mode is the default and recommended mode for performing upgrades due to it's ability to recover from a bad firmware upgrade. The process for this mode is upload, test, reset, confirm.
  • .confirmOnly: This mode is not recommended, except for Multi-Image DFU where it is the only supported mode. If the device fails to boot into the new image, it will not be able to recover and will need to be re-flashed. The process for this mode is upload, confirm, reset.
  • .testOnly: This mode is useful if you want to run tests on the new image running before confirming it manually as the primary boot image. The process for this mode is upload, test, reset.
  • .uploadOnly: This is a very particular mode. It does not listen or acknowledge Bootloader Info, and plows through the upgrade process with just upload followed by reset. That's it. It is up to the user, since this is not a default, to decide this is the right mode to use.

Firmware Upgrade State

FirmwareUpgradeManager acts as a simple, mostly linear state machine which is determined by the mode. As the manager moves through the firmware upgrade process, state changes are provided through the FirmwareUpgradeDelegate's upgradeStateDidChange method.

The FirmwareUpgradeManager contains an additional state, validate, which precedes the upload. The validate state checks the current image state of the device in an attempt to bypass certain states of the firmware upgrade. For example, if the image to upgrade to already exists in slot 1 on the device, the FirmwareUpgradeManager will skip upload and move directly to test (or confirm if .confirmOnly mode has been set) from validate. If the uploaded image is already active, and confirmed in slot 0, the upgrade will succeed immediately. In short, the validate state makes it easy to reattempt an upgrade without needing to re-upload the image or manually determine where to start.

Firmware Upgrade Configuration

nRF53 Dual-Core SoC Diagram, which supports all of these features.

In version 1.2, new features were introduced to speed-up the Upload speeds, mirroring the work first done on the Android side, and they're all available through the new FirmwareUpgradeConfiguration struct.

  • pipelineDepth: (Represented as 'Number of Buffers' in the Example App UI.) For values larger than 1, this enables the SMP Pipelining feature. It means multiple write packets are sent concurrently, thereby providing a large speed increase the higher the number of buffers the receiving device is configured with. Set to 1 (Number of Buffers = Disabled) by default.
  • byteAlignment: This is required when used in conjunction with SMP Pipelining. By fixing the size of each chunk of Data sent for the Firmware Upgrade, we can predict the receiving device's offset jumps and therefore smoothly send multiple Data packets at the same time. When SMP Pipelining is not being used (pipelineDepth set to 1), the library still performs Byte Alignment if set, but it is not required for updates to work. Set to ImageUploadAlignment.disabled by default.
  • reassemblyBufferSize: SMP Reassembly is another speed-improving feature. It works on devices running NCS 2.0 firmware or later, and is self-adjusting. Before the Upload starts, a request is sent via DefaultManager asking for MCU Manager Paremeters. If received, it means the firmware can accept data in chunks larger than the MTU Size, therefore also increasing speed. This property will reflect the size of the buffer on the receiving device, and the McuMgrBleTransport will be set to chunk the data down within the same Sequence Number, keeping each packet transmission within the MTU boundaries. There is no work required for SMP Reassembly to work - on devices not supporting it, the MCU Manager Paremeters request will fail, and the Upload will proceed assuming no reassembly capabilities. Must not be larger than UInt16.max (65535)
  • eraseAppSettings: This is not a speed-related feature. Instead, setting this to true means all app data on the device, including Bond Information, Number of Steps, Login or anything else are all erased. If there are any major data changes to the new firmware after the update, like a complete change of functionality or a new update with different save structures, this is recommended. Set to false by default.
  • upgradeMode: Firmware Upgrade Mode. See Section above for an in-depth explanation of all possible Upgrade Modes.
  • bootloaderMode: The Bootloader Mode is not necessarily intended to be a setting. It behaves as a setting if the target firmware does not offer a valid response to Bootloader Info request, for example, if it's not supported. What it does is inform iOSMcuMgrLibrary of the supported operations by the Bootloader. For example, if upgradeMode is set to confirmOnly but the Bootloader is in DirectXIP with no Revert mode, sending a Confirm command will be returned with an error. Which means, no Confirm command will be sent, despite the upgradeMode being set so. So yes, it's yet another layer of complexity from SMP / McuManager we have to deal with.
  • suitMode: An internal flag which, due to how the library is structured, needs to be exposed somewhere. SUIT has the benefit of using SMP, but the detriment of skipping many of its steps. So we need a way to inform the DFU code several of these steps need to be jumped and other conditions to be disregarded. TL;DR only set to true for SUIT Data.

Configuration Example

This is the way to start DFU with your own custom FirmwareUpgradeConfiguration:

import iOSMcuManagerLibrary

// Setup
let bleTransport = McuMgrBleTransport(cbPeripheral)
let dfuManager = FirmwareUpgradeManager(bleTransport, delegate)

// Non-Pipelined Example
let nonPipelinedConfiguration = FirmwareUpgradeConfiguration(
    estimatedSwapTime: 10.0, eraseAppSettings: false, pipelineDepth: 2,
)

// Legacy / App-Core Only DFU Example
dfuManager.start(data: imageData, using: nonPipelinedConfiguration)

// Pipelined Example
let pipelinedConfiguration = FirmwareUpgradeConfiguration(
    estimatedSwapTime: 10.0, eraseAppSettings: true, pipelineDepth: 4,
    byteAlignment: .fourByte
)

// Multi-Image DFU Example
dfuManager.start(images: images, using: pipelinedConfiguration)

Note: You can of course mix-and-match configurations and the input parameter type of the images to upload.

Logging

Setting logDelegate property in a manager gives access to low level logs, that can help debugging both the app and your device. Messages are logged on 6 log levels, from .debug to .error, and additionally contain a McuMgrLogCategory, which identifies the originating component. Additionally, the logDelegate property of McuMgrBleTransport provides access to the BLE Transport logs.

Example

import iOSMcuManagerLibrary

// Initialize the BLE transporter using a scanned peripheral
let bleTransport = McuMgrBleTransport(cbPeripheral)
bleTransporter.logDelegate = UIApplication.shared.delegate as? McuMgrLogDelegate

// Initialize the DeviceManager using the transport and a delegate
let deviceManager = DeviceManager(bleTransport, delegate)
deviceManager.logDelegate = UIApplication.shared.delegate as? McuMgrLogDelegate

// Send echo
deviceManger.echo("Hello World!", callback)

OSLog integration

McuMgrLogDelegate can be easily integrated with the Unified Logging System. An example is provided in the example app in the AppDelegate.swift. A McuMgrLogLevel extension that can be found in that file translates the log level to one of OSLogType levels. Similarly, McuMgrLogCategory extension converts the category to OSLog type.

Related Projects

We've heard demand from developers for a single McuMgr DFU library to target multiple platforms. So we've made available a Flutter library that acts as a wrapper for both Android and iOS.

ios-nrf-connect-device-manager's People

Contributors

amorde avatar ball-hayden avatar bgiori avatar dinesharjani avatar florianbuerger avatar intellectsoftdeveloper avatar jazzychad avatar jefffhaynes avatar lm2s avatar markborazio avatar mikesnyder360 avatar nathanblamires avatar nrbrook avatar philips77 avatar phoenix7351 avatar rickgroenewegen avatar stuffmatic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ios-nrf-connect-device-manager's Issues

Insufficient MTU: 253.

What can be the reason that I got this 253?
The write is successful but at a low rate of 0.3-0.4 kb/sec.

Stuck at "validate" step on 1.2.5, works on 1.1.0

Only on 1.2.5, works on 1.1.0:

upgradeStateDidChange requestMcuMgrParameters
McuMgrLog message: Central Manager  ready
McuMgrLog message: Connecting...
McuMgrLog message: Peripheral connected
McuMgrLog message: Discovering services...
McuMgrPeripheral peripheral [381CA91F-3E34-5C92-8057-CDA22EA02871] state: [connecting]
McuMgrPeripheral peripheral [381CA91F-3E34-5C92-8057-CDA22EA02871] state: [initializing]
McuMgrLog message: Services discovered: 8D53DC1D-1DB7-4CD3-868B-8A527460AA84
McuMgrLog message: Discovering characteristics...
McuMgrLog message: Characteristics discovered: DA2E7828-FBCE-4E01-AE9E-261174997C48
McuMgrLog message: Enabling notifications...
McuMgrLog message: Notifications enabled
McuMgrPeripheral peripheral [381CA91F-3E34-5C92-8057-CDA22EA02871] state: [connected]
McuMgrLog message: Device ready
McuMgrLog message: -> 0x0000000100000006a0
McuMgrLog message: <- 0x0100000600000006bf62726308ff
upgradeStateDidChange validate
McuMgrLog message: -> 0x0000000100010000a0
McuMgrLog message: Sending the request timed out.

Xamarin porting

Hello folks,
is there a porting of Device Manager core for DFU OTA through BLE based on SMP, in Xamarin Forms?
I've found a porting only for secure DFU based on DFU Service with UUID 0xFE59 (and characteristcs 0x8EC90001-F315-4F60-9FB8-838830DAEA50 and 0x8EC90002-F315-4F60-9FB8-838830DAEA50) (https://github.com/Laerdal/Laerdal.Dfu).

DFUManager.start() failing with timeout on validate step

Hi, the DFUManager start method is timing out for me on the validate step. The issue also occurs for me with the provided sample project. The only solution I have found is to roll back to v1.1.0 (same version as app store).

Any idea on how to resolve this for the latest version?

Thanks!

Support SMP Pipeline in FileSystemManager

Hello,

There''s an option pipelineDepth in FirmwareUpgradeConfiguration, and with pipelineDepth set to larger than 1 can increase the transfer speed when using ImageManager to upload firmware files. According to the documents, I suppose the feature is called SMP Pipeline.

My question is,

Is it possible to supoort such feature in FileSystemManager?

Unlike ImageManager, FileSystemManager's upload method doesn't take FirmwareUpgradeConfiguration, thus it doens't benefit from SMP Pipeline.
We found that the android version of the library did supoort the feature in both of their ImageManager and FileSystemManager, just wondering if there're any concerns so it's not implemented on iOS.

Thanks in advance, really appreciate all the hard works from contributors.

Btw,
We're uploading files ~500kB to our devices, and we found that the upload speed on iOS was aorund 2kB/s.
It'd take longer than 4mins to upload a single file, and we've got 4+ files to transfer, so we're looking for solutions to boost up the speed.

Upgrade successful but upgradeDidComplete does not fire

I'm able to fully upgrade the firmware using this library.

However, upgradeDidComplete does not fire. Instead I'm getting an error:

2022-08-01 11:39:46.160235+0200 App[4548:292971] [CoreBluetooth] XPC connection invalid

However, when I comment line 252 in FirmwareUpgradeManager it does work. Line 252 sets the cyclicReferenceHolder to nil:

cyclicReferenceHolder = nil

Seems like the reference is removed too quickly or something for upgradeDidComplete to finish?

Default mode seems to be CONFIRM_ONLY instead of TEST_AND_CONFIRM

I'm working on an app that uses this project as well as the Android counterpart, and I noticed that, when using the default settings, the order of states during firmware upgrade is different from one platform to the other.

It seems like the iOS default mode is not TEST_AND_CONFIRM as indicated (and recommended) in the README, but actually CONFIRM_ONLY.

I confirmed this emprically—if I set the Android mode to CONFIRM_ONLY, then Android behaves like the iOS default mode. If I set the iOS mode to TEST_AND_CONFIRM, then iOS behaves like the Android default mode.

Looks like the default mode is set here

Regression 1.1.0 -> 1.2.1/1.2.2/1.2.3/1.2.4: Bad Header received. Maybe packet size is smaller than minimum header size?

Running mynewt OS and seeing a regression in reading image list on version 1.2.1 and 1.2.2.

Repro:

  1. Run a device with image slot 0 and image slot 1 and connect using nrf connect device manager
  2. In the Image tab at the top right, press advanced and then press read to observe the error

Image Upload feature still works but due to the image read failure the test and confirm process cannot be done on the app.
nrf connect dfu still works and is capable of doing all of the steps, the issue is just with the device manager app.

Image list can also still be read using:
newtmgr image list -c <conn-name>

Problem seems to go away if only one image slot is loaded rather than two. I changed the internal flash address of image slot 1 and the error goes away but only reads back the image from image slot 0

How to generate imageData?

I can't find any documentation or examples on how the imageData object should be created. I'm fairly new to Swift so any help or resources would be greatly appreciated. I'm working on a capacitor plugin to implement device firmware updates using Nordic's native device manager libraries.

Essentially the plugin receives the image data as an array of integers along with a device ID. I get an invalidHeaderMagic error on FirmwareUpgradeManager.start(), so I'm guessing I've done something wrong when creating the Data object. The Android implementation is working with the same integer array and device, so I'm quite confident the data being sent from the plugin is valid.

Here's how I convert the [Int] to Data:

let firmware = call.getArray("firmware")!.capacitor.replacingNullValues() as? [Int]
// firmware: [61, 184, 243, 150, 0, 0, 0, 0, 0, 2, 0, ...]
// firmware.count: 170644
let firmwareData = Data(bytes: firmware!, count: firmware!.count)

And here's where I try to initiate the DFU process:

  public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
      print("DFU: \(peripheral)")
      if (peripheral.identifier.uuidString == self.deviceUuid) {
          print("DFU: found device with uuid \(self.deviceUuid)")
          self.peripheral = peripheral
          
          let bleTransport = McuMgrBleTransport(peripheral)
          bleTransport.logDelegate = UIApplication.shared.delegate as? McuMgrLogDelegate
          let delegate = UpgradeDelegate()
          let dfuManager = FirmwareUpgradeManager(transporter: bleTransport, delegate: delegate)
          dfuManager.logDelegate = UIApplication.shared.delegate as? McuMgrLogDelegate
          
          print("DFU: firmwareData.count: \(self.firmwareData.count)") // 170644
          do {
              try dfuManager.start(data: self.firmwareData)
          } catch {
              print("DFU: Unexpected error: \(error)")
          }
      }
  }

For reference, this is the byte array processing in the Android implementation (which works):

final List<Object> list = firmware.toList();
byte[] bytes = new byte[list.size()];
for (int i = 0; i < list.size(); i++) {
    int val = (int) list.get(i);
    bytes[i] = (byte) val;
}

Thanks :)

Take off

{
CPN = 4b7quo9trDcX4gqZ;
date = "2023-02-28 02:44:07 +0000";
debugParameters = {
ad = none;
adcpn = "";
audioitag = 140;
connectiontype = 12;
errorscreen = no;
isdrm = no;
ishls = no;
islocal = no;
ismonetized = no;
ismuted = no;
mlplayer = {
docid = arPNKSZPFTo;
rate = 1;
type = "hamplayer_queue";
};
mobilecarrier = "Xfinity Mobile";
playertype = 15;
qoeerrorcode = "";
qoeerrordetails = "";
resolution = 1080p;
state = 6;
videoitag = 137;
xtags = "";
};
videoID = arPNKSZPFTo;
}

Unnecessary broadcast after reboot

Hi all, first, thank you for your contribution and what you have achieved with your SDK.

We have a somewhat specific flow and I noticed that the problem originates from the SDK.
Namely, when uploading firmware to a device with 2 cores and using the option to not reset it after completion, but on the next reboot, the device starts broadcasting for a connection. As for the device with 1 core, the behavior is as expected and does not start broadcasting after a reboot.
Can this automatic broadcast be excluded after a successful install of the firmware on the device with 2 cores as well?

When parsing more complicated CBOR structure some elements disappears

I have CBOR structure

As text:

{"files": [{"name": "/lfs/hr/1.hr", "time": 806548, "id": 1, "size": 5, "checksum": 758453377},{"name": "/lfs/hr/2.hr", "time": 806548, "id": 2, "size": 5, "checksum": 758453377}], "status":1}

As HEX:

A26566696C65736E616D656C2F6C66732F68722F312E68774696D651A000C4E9473697A65636865636B73756D1AD3514816E616D656C2F6C66732F68722F322E68774696D651A000C4E9473697A65636865636B73756D1A2D35148101

When adding break point at file McuMgrResponse.swift:129

payloadData contains correct data:

(lldb) po String(decoding: payloadData!, as: UTF8.self)
"�dnamel/lfs/hr/1.hrdtime\u{1A}\0\u{0C}N�bid\u{01}dsize\u{05}hchecksum\u{1A}-5\u{14}��dnamel/lfs/hr/2.hrdtime\u{1A}\0\u{0C}N�bid\u{02}dsize\u{05}hchecksum\u{1A}-5\u{14}�fstatus\u{01}"

Unfortunately parsed payload do not contains all data. It is missing /lfs/hr/2.hr and some other fields.

(lldb) po payload
▿ Optional<CBOR>
  ▿ some : {"checksum" : 758453377, "id" : 1, "name" : "/lfs/hr/1.hr", "size" : 5, "time" : 806548}
    ▿ map : 5 elements
      ▿ 0 : 2 elements
        ▿ key : "checksum"
          - utf8String : "checksum"
        ▿ value : 758453377
          - unsignedInt : 758453377
      ▿ 1 : 2 elements
        ▿ key : "id"
          - utf8String : "id"
        ▿ value : 1
          - unsignedInt : 1
      ▿ 2 : 2 elements
        ▿ key : "name"
          - utf8String : "name"
        ▿ value : "/lfs/hr/1.hr"
          - utf8String : "/lfs/hr/1.hr"
      ▿ 3 : 2 elements
        ▿ key : "size"
          - utf8String : "size"
        ▿ value : 5
          - unsignedInt : 5
      ▿ 4 : 2 elements
        ▿ key : "time"
          - utf8String : "time"
        ▿ value : 806548
          - unsignedInt : 806548

Is CBOR not support more complicated parsing structures?

Crash in the McuMgrBleTransport class

using: v1.1.0

Crash screenshot:
image

Communication stack and cash reasons:

debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> -> 0x0000000100480000a0
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> -> 0x0000000100480000a0
debug -> transport -> -> 0x0000001000470000a2666f666673657400656c696d697405
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> -> 0x0000000100480000a0
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff0100001200440001bf6566696c65739fff6673746174757303ff
debug -> transport -> -> 0x0000000100480000a0
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> -> 0x0000001000470000a2666f666673657400656c696d697405
debug -> transport -> -> 0x0000001000440001a2666f666673657400656c696d697405
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> -> 0x0000000100480000a0
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport -> <- 0x0100001700470000bf677265636f7264739fff6969737061727469616cf4ff
debug -> transport iOSMcuManagerLibrary/McuMgrBleTransport.swift:564: Fatal error: Unexpectedly found nil while unwrapping an Optional value
-> -> 0x0000001000470000a2666f666673657400656c696d697405
debug -> transport -> -> 0x0000000100480000a0
2022-05-17 13:35:25.835741+0300 MyHealth[3905:718199] iOSMcuManagerLibrary/McuMgrBleTransport.swift:564: Fatal error: Unexpectedly found nil while unwrapping an Optional value

[Question] How to tell what's the integer error-code when an upload fails?

I'm new to swift so please bare with me. I noticed that in McuManager.swift we have this piece of code:

extension McuMgrReturnCode: CustomStringConvertible {
    
    public var description: String {
        switch self {
        case .ok:
            return "OK (0)"
        case .unknown:
            return "Unknown (1)"
        case .noMemory:
            return "No Memory (2)"
        case .inValue:
            return "In Value (3)"
        case .timeout:
            return "Timeout (4)"
        case .noEntry:
            return "No Entry (5)"
        case .badState:
            return "Bad State (1)"
        default:
            return "Unrecognized (\(rawValue))"
        }
    }
}

When an upload fails the following callback gets triggered:

extension FooFileUploader: FileUploadDelegate {

   public func uploadDidFail(with error: Error) {
        //  ...
   }

}

How can I get the integer error-code (McuMgrReturnCode) from the 'error' parameter? Is it even possible or is it a lost cause? Appreciate any insights.

Client of FirmwareUpgradeManager has no way of knowing which images will be uploaded

Today, if you initialize a FirmwareUpgradeManager, invoke FirmwareUpgradeManager.start(images:), and provide more than one image, it seems impossible for the client/delegate of FirmwareUpgradeManager to know which of the images will be uploaded and which ones won't. This makes it difficult to display a progress bar to the user. My recommendation would be to make the following changes to FirmwareUpgradeDelegate:

  • Add a new method called willUploadImagesWithSizes(_ imageSizes: [Int]). This method will inform the delegate if only a subset of images will be uploaded, and what their sizes will be.
  • Change uploadProgressDidChange to include the index of the images currently being uploaded. For example: uploadProgressDidChange(imageIndex: Int, bytesSent: Int, imageSize: Int, timestamp: Date)

Please let me know if there's already a different way of obtaining the information I need.

(question) Does FileSystemManager support uploading one file after the other (provided that each file-upload completes first)

Essentially what the title says. I'm trying to upload multiple file one after the other making sure that each next upload is submitted if and only if the previous has completed. However I realized that the FileSystemManager is completely frozen upon trying to upload the second file.

BUT if I dispose of the FileSystemManager and re-instantiate it from scratch then the uploads are performed normally as intended which is weird.

        _fileSystemManager = FileSystemManager(transporter: _transporter)
        _fileSystemManager.logDelegate = self

        var success = _fileSystemManager.upload(
                name: "/remote/file/path/here/1.txt",
                data: data1,
                delegate: self
        )
        if !success {
            return false
        }
        
        // wait for the first upload to complete
        
        success = _fileSystemManager.upload( // this one is submitted without errors but nothing happens - the file doesnt get uploaded at all
                name: "/remote/file/path/here/2.txt",
                data: data2,
                delegate: self
        )
        if !success { // success=true 
            return false
        }

If I switch over to this approach everything works:

        _fileSystemManager = FileSystemManager(transporter: _transporter)
        _fileSystemManager.logDelegate = self

        var success = _fileSystemManager.upload(
                name: "/remote/file/path/here/1.txt",
                data: data1,
                delegate: self
        )
        if !success {
            return false
        }
        
        // wait for the first upload to complete
                
        _fileSystemManager = FileSystemManager(transporter: _transporter)  // REINSTANTIATION
        _fileSystemManager.logDelegate = self

        
        success = _fileSystemManager.upload( // with this approach everything works flawlessly
                name: "/remote/file/path/here/2.txt",
                data: data2,
                delegate: self
        )
        if !success { // success=true 
            return false
        }

This works but it's not optimal. We should be able to reuse the original instance of FileSystemManager right?

Am I missing something?

iOS binding from Xamarin, incomplete iOSMcuManagerLibrary.framework headers

I'm trying to import the native libraries to Xamarin forms, I need for the FirmwareUpgrade section of the iOSMcuManagerLibrary.
I compile the example project correctly and I can access to the iOSMcuManagerLibrary.framework, but seems that the .h is incomplete, infact starting the sharpie process the API definition.cs doesn't contains the fw update functions.
Can you please check that the .h contains all the needed interfaces? If not the binding process cannot generate the fw update functions that I need

How to use shell manager with iOS?

In Android example, we can use shell commands using shell manager but I didn't find any shell manager class here in iOS, is there any way we can implement this?

Which McuManager repo is the fork under active development?

The readme at https://github.com/JuulLabs-OSS/mcumgr-ios states to use this fork.

DEPRECATED

This repository is deprecated and no longer maintained, please use the following forks:

iOS: NordicSemiconductor/IOS-nRF-Connect-Device-Manager
Android: NordicSemiconductor/Android-nRF-Connect-Device-Manager

However the readme in the IOS-nRF-Connect-Device-Manager repo says to contribute at https://github.com/JuulLabs-OSS/mcumgr-ios. Which repo is the correct one?

The underlying library, as of now, is 1 to 1 copy of the original. All PRs should be submitted to the original repository.

Also, if this is the repo moving forward, I will make an issue to update the podspec to reference this repo.

Rase condition observed for McuManager.send function's response

I am seeding two group commands one after another:

verbose -> default -> Sending read command (Group: peruser(value: 71), ID: 0): ["limit": 5, "offset": 0]
verbose -> default -> Sending read command (Group: peruser(value: 72), ID: 0): nil

Then getting not expected response for group 72 (it comes from 71st response):

verbose -> default -> Response (Group: peruser(value: 72), ID: 0): {"rc" : 8}

An app gets wrong response for 72nd group. For me it looks response comes from the 71st group.

Could you please confirm/disconfirm, that 72nd response should be overwritten by 71st response?

No HEX output when BLE communication ongoing

In the latest, 1.2.0, version HEX output in the console not printed for some BLE communication packets. As example for this: Group: peruser(value: 72), SEQ No. 8, ID: all

I want to notice, that using 1.1.0 version group 72 with ID all command was working fine. Only when moved to 1.2.0 version started to get timeouts.

Some hints or help on this problem would really helped me.

Dump log:

verbose -> default -> Sending read command (Group: peruser(value: 72), SEQ No. 8, ID: all): nil
error -> transport -> Sending the request timed out.
error -> default -> Request (Group: peruser(value: 68), SEQ No. 5) failed: Sending the request timed out.)
debug -> transport -> -> (SEQ No. 2) 0x0000001a00080200a2646e616d656e2f6c66732f68722f3737362e6872636f666600
debug -> transport -> peripheralDidUpdateValueFor() SEQ No. 2)
debug -> transport -> <- (SEQ No. 2) Response: {"off" : 0, "len" : 0, "rc" : OK (0), "data" : ""})
debug -> transport -> <- (SEQ No(s). 2) 0x0100001600080200bf636f66660064646174614062726300636c656e00ff
verbose -> fs -> Response (Group: fs, SEQ No. 2, ID: 0): {"off" : 0, "len" : 0, "rc" : OK (0), "data" : ""}
debug -> transport -> -> (SEQ No. 6) 0x0000000100480600a0
application -> fs -> Download finished
verbose -> default -> Sending write command (Group: peruser(value: 68), SEQ No. 6, ID: clear): ["ids": [3]]
debug -> transport -> peripheralDidUpdateValueFor() SEQ No. 6)
debug -> transport -> peripheralDidUpdateValueFor() SEQ No. 97)
error -> transport -> Sending the request timed out.
debug -> transport -> -> (SEQ No. 7) 0x0000000100480700a0
error -> default -> Request (Group: peruser(value: 72), SEQ No. 6) failed: Sending the request timed out.)

Is it possible to check the existence of a file using FileSystemManager?

I need to be able to tell if a file exists or not.

To test this out, I am currently trying to use the FileSystemManager to download a file that I know does not exist. I am getting an error back, but it is simply returning FileTransferError.mcuMgrErrorCode(McuMgrReturnCode.unknown).

Is McuMgrReturnCode.unknown an accurate way to determine if a file is non-existent? I can see that the sample code in this repository considers this to be true, but the fact that it is simply an unknown error code makes me doubt that.

Handle multiple requests one after each other

Hello folks and @dinesharjani - @philips77 - @roshanrajaratnam,
I'm struggling with the possibility of sending multiple requests one after each other without waiting for the response, and then receiving responses with matching sequence numbers.
Do you have more information about this? Is it still possibile, in order to speed up procedure? In my own implementation, it is nearly impossible to reach 8/9 kB7s as per NRC Connect dfu procedure or NRF Device manager (maybe with PRNs?).
image

Maybe, is it possible to know what happen in NRF Connect under the log "Uploading firmware"?

Reading firmware version number

Hi,

Does anybody have any example on how I would use this library to read the version number of the image in slot 0?

Rick

SUIT version does not update device

Hi,
I have tested v1.4.3 updating device using mcumgr, it was working. Now I am testing version v1.6 including SUIT, so I had SHA256 of my binary file into start method :

try self.dfuManager!.start(hash: hash256, data: self.imageData!, using: configuration)

With this new method upload is done until 100% then I have the status "COMPLETED", the device reboot but it is still with previous version. Do you know why?

The SHA256 of the signed binary file is calculated as follow :

shasum -a 256 binary.signed.bin

This return string and string is convert as hex and the as Data like this :

    func hexToData(hex: String) -> Data? {
        var hexWithoutPrefix = hex
        if hex.hasPrefix("0x") || hex.hasPrefix("0X") {
            hexWithoutPrefix = String(hex.dropFirst(2))
        }

        let len = hexWithoutPrefix.count
        guard len % 2 == 0 else { return nil }

        var data = Data(capacity: len / 2)
        var index = hexWithoutPrefix.startIndex

        for _ in 0 ..< len / 2 {
            let byteString = String(hexWithoutPrefix[index ..< hexWithoutPrefix.index(index, offsetBy: 2)])
            if let byte = UInt8(byteString, radix: 16) {
                data.append(byte)
            } else {
                return nil // Invalid hex string
            }
            index = hexWithoutPrefix.index(index, offsetBy: 2)
        }

        return data
    }

By advanced, thanks

A few build issues with xcode v13.2.1

I had to do a few extra steps to get this project to almost build.

FIRST, add the package dependency, THEN do the pod setup. (learning curve)

$ pod repo update
$ pod install (fails)
$ pod update SwiftCBOR (works)

in the workspace, the podfiles look good (they were red until I figured out the above steps)

Then when I build in xcode v13.2.1, I get 2 errors

/Users/mike/work/xxx/IOS-nRF-Connect-Device-Manager/Example/Example/View Controllers/Manager/FirmwareUploadViewController.swift:36:48: Cannot convert value of type 'Data' to expected argument type '[ImageManager.Image]' (aka 'Array<(image: Int, data: Data)>')

/Users/mike/work/xxx/IOS-nRF-Connect-Device-Manager/Example/Example/View Controllers/Manager/FirmwareUploadViewController.swift:36:32: Incorrect argument label in call (have 'data:delegate:', expected 'images:delegate:')

It's suggesting to replace 'data' with 'images' which also didn't work.

Variable binding in a condition requires an initializer

Since 1.2.8 a piece of code has been added in McuMgrBleTransport+CBPeripheralDelegate that has been throwing errors. The project cannot be build because of it:

Line 131 : Variable binding in a condition requires an initializer

if let previousUpdateNotificationSequenceNumber,

Only option currently is to roll back to <=1.2.7

Fatal Error : unexpectedly found nil - Problem with the upload method that uses callback response

Hi all,

There is a problem because the upload method with the callback does not set the uploadConfiguration and it is explicitly unwrapped.

image

The configuration is not set in the method, and as I saw in code, it is only set by using the method with the delegate option.

Screenshot 2024-04-10 at 1 37 40 PM

For now, I can use only the method with the delegate option, but sometimes my code requires me to use the callback since it is much easier for error handling.

Thank you all for this awesome SDK.

Readme instructions for "Multi-Image DFU Format"

I just wanted to note that the Multi-Image DFU Format example doesn't work without adding the example's Util/McuMgrPackage.swift and Util/UTI.swift as well as the ZIPFoundation pod. It would be useful to mention this or include them in the package.

Thanks,
Raemond

How to coexist with device manager?

Is there a recommended approach for coexisting with the device manager? I could be mistaken but it seems that (understandably) the device manager transport will assign itself as the peripheral callback delegate. In the case where our app is looking for its own notifications, it means we lose access to those notifications. Right now we're simply closing the transport and reassigning the delegate to ourselves, but is there a better way? Should we be going straight to the transport or possibly doing some delegate multicast trick?

sendTimeout error while the device is still swapping images after image upload

Hi,
I am getting a sendTimeout error and upgrade failed while the device is still rebooting and swapping images. I am using TestAndConfirm mode and since upgradeDidFail is called after this timeout, when the device powers on again, FirmwareUpgradeManager does not confirm the image.

The upgrade works fine in v1.3.0, but fails in 1.3.1, 1.3.2 and 1.3.3

Setting a value for estimatedSwapTime in the upload configuration in v1.3.3 also fixes this issue, however that then means that the iOS device doesn't try to reconnect till the estimated time has elapsed, even if the NRF device has already booted after image swap.

Is there a way to set the send timeout value manually? Am I missing something here?

Multi-core update doesn't work on versions 1.1-1.2.1

iOS library for mcumgr confirms only one image after multi core update. Restarting the device in this case bricks the device is the net core and app core images (one new and one old) are not compatible. Reprogramming with serial recover or debug connection is required.

No longer builds on Watch OS

I'm not sure when, but at some point some breaking dependencies were added that prevent this from building on Watch OS, even though I don't believe there's any intrinsic reason that it shouldn't be able to.

DFU update: invalidHeaderMagic?

Hi, I'm downloading the firmware using a URL and I try to start the DFU update using that data

do { try dfuManager.start(data: URLData) } catch let error { print(error) }

However, this code prints out the following:

invalidHeaderMagic

Does anybody have an example on how I should generate the data from an URL that I can pass on to dfuManager.start()?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.