mcumgr_flutter 0.10.0
mcumgr_flutter: ^0.10.0 copied to clipboard
nRF Connect Device Manager library is a Flutter plugin based on Android, iOS and MacOS nRF Connect Device Manager libraries.
Note
(Flutter) nRF Connect Device Manager library is a plugin (aka "wrapper") around the existing Android and iOS nRF Connect Device Manager libraries. Please be aware this has consequences for both API design as well as for where the underlying issues you report may actually be.
(Flutter) nRF Connect Device Manager #
Supported Platforms #
- Android:
minSdkVersion 21 - iOS:
13.0 - macOS:
10.15 - Web: Chrome 56+ (Web Bluetooth required)
Getting Started #
Creating a manager #
Use UpdateManagerFactory to create an instance of FirmwareUpdateManager:
final managerFactory: UpdateManagerFactory = FirmwareUpdateManagerFactory()
// `deviceId` is a String with the device's MAC address (on Android) or UUID (on iOS)
final updateManager = await managerFactory.getUpdateManager(deviceId);
// call `setup` before using the manager
final updateStream = updateManager.setup();
Updating the device #
To update the device, call update method on the FirmwareUpdateManager instance:
// `firmware` is a List of Image objects
List<Image> firmwareImages = [];
for (final file in manifest.files) {
final image = Image(
image: file.image,
data: firmwareFileData,
);
firmwareImages.add(image);
}
final configuration = const FirmwareUpgradeConfiguration(
estimatedSwapTime: const Duration(seconds: 0),
byteAlignment: ImageUploadAlignment.fourByte,
eraseAppSettings: true,
pipelineDepth: 1,
);
// `configuration` is an optional parameter. If not provided, default values will be used.
updateManager.update(firmwareImages, configuration: configuration);
Alternatively, you can use updateWithImageData to update the device with a single image data:
await updateManager.updateWithImageData(image: fwImage!);
Tip
update and updateWithImageData methods are asynchronous, however, they do not return a result of the update process. They only start the update process. To listen for updates, subscribe to the updateStream and progressStream. See also Issue #63 for more information.
Listening for updates #
To listen for updates, subscribe to the updateStream and progressStream:
updateManager.updateStateStream?.listen((event) {
if (event == FirmwareUpgradeState.success) {
print("Update Success");
} else {
print(event);
}
});
updateManager.progressStream.listen((event) {
print("${event.bytesSent} / ${event.imageSize}} bytes sent");
});
Controlling the update #
To control the update, use FirmwareUpdateManager methods:
/// Pause the update process.
Future<void> pause();
/// Resume the update process.
Future<void> resume();
/// Cancel update.
Future<void> cancel();
/// Check if the progress is in process.
Future<bool> inProgress();
/// Check if the progress is paused.
Future<bool> isPaused();
Killing the manager #
After the update is finished, call kill to kill the manager, otherwise it will lead to memory leaks and other issues:
updateManager.kill();
Reading image list #
To read the current image list (installed firmware slots) from the device:
List<ImageSlot>? slots = await updateManager.readImageList();
Reading logs #
To listen for logs, subscribe to the logger.logMessageStream:
updateManager.logger.logMessageStream
.where((log) => log.level.rawValue > 1) // filter out debug messages
.listen((log) {
print(log.message);
});
To read logs from the device, use readLog method:
List<McuLogMessage> logs =
await updateManager.logger.readLogs(clearLogs: false);
Web Setup #
Web support uses the Web Bluetooth API, which is currently supported in Chrome and Chrome-based browsers. It requires a secure context (HTTPS or localhost).
Required: Add script tag to web/index.html #
Add the following <script> tag to your app's web/index.html before flutter_bootstrap.js. This installs an intercept that caches the BLE device obtained during scanning so the firmware update flow can reuse it without prompting the user a second time.
<body>
<script src="assets/packages/mcumgr_flutter/lib/src/mcumgr_web/mcumgr_setup.js"></script>
<script src="flutter_bootstrap.js" async></script>
</body>
Without this script, the browser will show a second Bluetooth device picker when the firmware update starts.
Advanced #
When you use the aforementioned update method with FirmwareUpdateManager, the library is on what we call "Basic" mode. This is the default way in which all of our apps such as nRF Connect Device Manager (Android, iOS) and nRF Connect (Android, iOS) work. However, as with everything, you might have particular uses cases or have your own needs regarding DFU. The specific Android and iOS DFU libraries have much further depth and complexity, which translates into what this wrapper library exposes.
For the most part, Flutter nRF Connect Device Manager library only exposes "Basic" Mode APIs. But we recommend digging into the iOS Readme page for more details.
Confirming an image #
When performing an update, one of the steps to enable the new uploaded firmware to run is to mark the images as confirmed. This happens out of the box. However, if when calling update with FirmwareUpdateManager the upgradeMode is changed to FirmwareUpgradeMode.testOnly, the library will not send confirm commands. Instead, the library/API will return success without a reset nor enabling the uploaded images to run, since they were not confirmed. This is because in this mode, the user is expected to do this; implication being the library user wants to perform their own validation step before allowing the new uploaded firmware to run.
Once your own validation process is complete, you may use confirmImage to permanently mark the uploaded firmware as active:
final slots = await updateManager.readImageList();
final activeSlot = slots!.firstWhere((s) => s.active && !s.confirmed);
await updateManager.confirmImage(activeSlot.hash);
If confirmImage is not called before the next reboot, the bootloader will revert to the previous firmware.
Settings Manager #
The Settings Manager provides functionality to read and write device configuration settings via the MCU Manager protocol.
Caution
This is for Advanced needs. Like calling confirm, if you don't have prior knowledge about this, it is very likely you don't need to worry about it. Anything that is part of standard DFU procedure is already handled by default for you.
Creating a Settings Manager
import 'package:mcumgr_flutter/mcumgr_flutter.dart';
final mcumgrSettings = McumgrSettings();
Initializing the Settings Manager
Before using the settings manager, you must initialize it with the device address:
await mcumgrSettings.init(
deviceAddress: deviceAddress, // MAC address (Android) or UUID (iOS)
encodeValueToCBOR: true, // Encode values to CBOR format (optional, default: false)
padTo4Bytes: true, // Pad values to 4-byte alignment (optional, default: false)
);
Reading Settings
You can read all settings or a specific setting:
// Read all settings
final allSettings = await mcumgrSettings.readSettings();
print('All settings: $allSettings');
// Read a specific setting
final rawBytes = await mcumgrSettings.readSetting('config/timeout/value');
// The result is returned as Uint8List, which you need to decode based on the expected type
Writing Settings
To write a setting:
// Write a string value
final result = await mcumgrSettings.writeSetting('device/name', 'MyDevice');
// Write other types of values
await mcumgrSettings.writeSetting('config/interval', 1000);
await mcumgrSettings.writeSetting('feature/enabled', true);
Decoding Setting Values
Settings are returned as raw bytes (Uint8List). You need to decode them based on their expected type:
// Decode as string (removing first byte which is typically a type indicator)
String decodeStringSettings(Uint8List bytes) {
return String.fromCharCodes(List.from(bytes)..removeAt(0));
}
// Decode as double
double decodeDoubleSetting(Uint8List bytes) {
if (bytes.length != 8) {
throw Exception("Expected 8 bytes for double, got ${bytes.length}");
}
return ByteData.sublistView(bytes).getFloat64(0, Endian.little);
}
// Decode as boolean
bool decodeBoolSetting(Uint8List bytes) {
if (bytes.isEmpty) throw Exception("Empty byte array for bool");
return bytes[0] != 0;
}
Disposing (of) the Settings Manager
When you're done using the settings manager:
await mcumgrSettings.dispose();
Usage Example
final mcumgrSettings = McumgrSettings();
try {
// Initialize
await mcumgrSettings.init(
deviceAddress: device.remoteId.str,
encodeValueToCBOR: true,
padTo4Bytes: true,
);
// Read a setting
final deviceName = await mcumgrSettings.readSetting('device/name');
final nameString = String.fromCharCodes(List.from(deviceName)..removeAt(0));
print('Device name: $nameString');
// Write a setting
await mcumgrSettings.writeSetting('device/name', 'NewDeviceName');
} catch (e) {
print('Error: $e');
} finally {
await mcumgrSettings.dispose();
}