From 8b8e9906a08191a6a12e766a5acb415b582b04a3 Mon Sep 17 00:00:00 2001 From: R0m4in-dooz <72380352+R0m4in-dooz@users.noreply.github.com> Date: Wed, 16 Mar 2022 17:22:27 +0100 Subject: [PATCH] Prepare public release (#231) * update pubspec with higher dart constraint and links + remove default comments * document constants, models and extension on int * document mesh_network.dart * document mesh_manager_api.dart * document provisioning.dart * document ble_manager_callbacks.dart and ble_mesh_manager_callbacks.dart * document ble_manager.dart + ci script back to flutter stable * document ble_mesh_manager.dart * document ble_scanner.dart + move some constants to constants.dart * add a unit test! * remove useless meta dependency declaration * update CHANGELOG.md * remove unused dev dependency * update README.md * add license * update CHANGELOG.md --- .github/workflows/on_pr.yaml | 2 +- CHANGELOG.md | 8 + LICENSE | 30 ++- README.md | 15 +- example/lib/src/views/home/home.dart | 4 +- .../scan_and_provisioning.dart | 4 +- example/pubspec.lock | 2 +- ios/nordic_nrf_mesh.podspec | 6 +- lib/nordic_nrf_mesh.dart | 5 +- lib/src/ble/ble_manager.dart | 28 ++- lib/src/ble/ble_manager_callbacks.dart | 36 +++- lib/src/ble/ble_mesh_manager.dart | 84 ++++---- lib/src/ble/ble_mesh_manager_callbacks.dart | 24 +++ lib/src/ble/ble_scanner.dart | 31 ++- lib/src/{contants.dart => constants.dart} | 5 + lib/src/events/mesh_manager_api_events.dart | 5 + lib/src/exceptions/ble_manager_exception.dart | 2 +- .../nrf_mesh_provisioning_exception.dart | 2 +- lib/src/extensions/extensions.dart | 1 + lib/src/mesh_manager_api.dart | 126 ++++++++---- lib/src/mesh_network.dart | 62 +++++- lib/src/models/group/group.dart | 6 + .../mesh_node/provisioned_mesh_node.dart | 26 ++- .../mesh_node/unprovisioned_mesh_node.dart | 8 +- lib/src/models/network_key/network_key.dart | 6 + lib/src/models/provisioner/provisioner.dart | 6 + .../allocated_group_range.dart | 6 + .../allocated_scene_range.dart | 6 + .../allocated_unicast_range.dart | 6 + lib/src/nrf_mesh.dart | 53 ++++- lib/src/utils/provisioning.dart | 182 +++++++++++------- pubspec.lock | 9 +- pubspec.yaml | 48 +---- test/nordic_nrf_mesh_test.dart | 19 +- 34 files changed, 602 insertions(+), 261 deletions(-) rename lib/src/{contants.dart => constants.dart} (86%) diff --git a/.github/workflows/on_pr.yaml b/.github/workflows/on_pr.yaml index a117fd8b7..74e308195 100644 --- a/.github/workflows/on_pr.yaml +++ b/.github/workflows/on_pr.yaml @@ -18,7 +18,7 @@ jobs: java-version: "12.x" - uses: subosito/flutter-action@v1 with: - flutter-version: '2.5.3' + channel: "stable" - name: Get dependencies run: flutter packages get - name: Check format diff --git a/CHANGELOG.md b/CHANGELOG.md index 838bbd31c..e350a096d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.10.0 + +First open source release ! :rocket: + +- add missing documentation +- remove unused API and dependency +- refresh `example/`, `ios/` and `android/` folders to be up to date with the latest Flutter version (2.10.3) + ## 0.9.1 - revert app's manifest changes diff --git a/LICENSE b/LICENSE index ba75c69f7..3a54095d4 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +1,29 @@ -TODO: Add your license here. +BSD 3-Clause License + +Copyright (c) 2022, OZEO-DOOZ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 70505cf98..5ec8a68fe 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,7 @@ # nordic_nrf_mesh +A Flutter plugin to enable mesh network management and communication using Nordic's SDKs. It also provides the ability to open BLE connection with mesh nodes using some other Flutter [plugin](https://pub.dev/packages/flutter_reactive_ble). -A new flutter plugin project. +## How to use +_This section will be updated soon !_ -## Getting Started - -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +Please refer to example app. diff --git a/example/lib/src/views/home/home.dart b/example/lib/src/views/home/home.dart index c31846312..5a033edce 100644 --- a/example/lib/src/views/home/home.dart +++ b/example/lib/src/views/home/home.dart @@ -129,14 +129,14 @@ class _MeshManagerApiWidgetState extends State { ), TextButton( onPressed: () async { - final provisionerList = await _meshNetwork!.provisionerList; + final provisionerList = await _meshNetwork!.provisioners; debugPrint('# of provs : ${provisionerList.length}'); }, child: const Text('get provisioner list'), ), TextButton( onPressed: () async { - var provUUIDs = await _meshNetwork!.provisionerList; + var provUUIDs = await _meshNetwork!.provisioners; for (var value in provUUIDs) { debugPrint('$value'); } diff --git a/example/lib/src/views/scan_and_provisionning/scan_and_provisioning.dart b/example/lib/src/views/scan_and_provisionning/scan_and_provisioning.dart index 83c09d1d2..abc9ab2ec 100644 --- a/example/lib/src/views/scan_and_provisionning/scan_and_provisioning.dart +++ b/example/lib/src/views/scan_and_provisionning/scan_and_provisioning.dart @@ -62,8 +62,8 @@ class _ScanningAndProvisioningState extends State { meshProvisioningUuid, ], ).listen((device) async { - _serviceData[device.id] = Uuid.parse( - _meshManagerApi.getDeviceUuid(device.serviceData[_meshManagerApi.meshProvisioningUuidServiceKey]!.toList())); + _serviceData[device.id] = + Uuid.parse(_meshManagerApi.getDeviceUuid(device.serviceData[meshProvisioningUuid]!.toList())); if (_devices.every((d) => d.id != device.id)) { setState(() { _devices.add(device); diff --git a/example/pubspec.lock b/example/pubspec.lock index 4ac17505d..5042ca25a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -298,7 +298,7 @@ packages: path: ".." relative: true source: path - version: "0.9.1" + version: "0.10.0" package_config: dependency: transitive description: diff --git a/ios/nordic_nrf_mesh.podspec b/ios/nordic_nrf_mesh.podspec index e3bc1de5e..384e19dce 100644 --- a/ios/nordic_nrf_mesh.podspec +++ b/ios/nordic_nrf_mesh.podspec @@ -4,10 +4,10 @@ # Pod::Spec.new do |s| s.name = 'nordic_nrf_mesh' - s.version = '0.0.1' - s.summary = 'A Flutter plugin to enable mesh network management and communication using Nordic SDKs. It also provides the ability to open BLE connection with mesh nodes using some other flutter package.' + s.version = '0.10.0' + s.summary = 'A Flutter plugin to enable mesh network management and communication using Nordic SDKs. It also provides the ability to open BLE connection with mesh nodes using some other Flutter plugin.' s.description = <<-DESC -A Flutter plugin to enable mesh network management and communication using Nordic SDKs. It also provides the ability to open BLE connection with mesh nodes using some other flutter package. +A Flutter plugin to enable mesh network management and communication using Nordic SDKs. It also provides the ability to open BLE connection with mesh nodes using some other Flutter plugin. DESC s.homepage = 'http://dooz-domotique.com' s.license = { :file => '../LICENSE' } diff --git a/lib/nordic_nrf_mesh.dart b/lib/nordic_nrf_mesh.dart index 3b435d32f..a963b290b 100644 --- a/lib/nordic_nrf_mesh.dart +++ b/lib/nordic_nrf_mesh.dart @@ -1,3 +1,6 @@ +/// A Flutter plugin to enable mesh network management and communication using Nordic's SDKs. It also provides the ability to open BLE connection with mesh nodes using some other Flutter plugin. +library nordic_nrf_mesh; + export 'src/nrf_mesh.dart'; export 'src/mesh_network.dart' show IMeshNetwork; export 'src/mesh_manager_api.dart'; @@ -8,6 +11,6 @@ export 'src/ble/ble_manager_callbacks.dart'; export 'src/ble/ble_mesh_manager.dart'; export 'src/ble/ble_mesh_manager_callbacks.dart'; export 'src/utils/provisioning.dart' show ProvisioningEvent; -export 'src/contants.dart' show ProvisioningFailureCode, BleManagerFailureCode; +export 'src/constants.dart' show ProvisioningFailureCode, BleManagerFailureCode; export 'src/exceptions/exceptions.dart'; export 'src/extensions/extensions.dart'; diff --git a/lib/src/ble/ble_manager.dart b/lib/src/ble/ble_manager.dart index c6df146b0..2303d7bc4 100644 --- a/lib/src/ble/ble_manager.dart +++ b/lib/src/ble/ble_manager.dart @@ -5,9 +5,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:meta/meta.dart'; import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; -const mtuSizeMax = 517; -const maxPacketSize = 20; +// runtime constants final doozCustomServiceUuid = Platform.isAndroid ? Uuid.parse('00001400-0000-1000-8000-00805F9B34FB') : Uuid.parse('1400'); final doozCustomCharacteristicUuid = @@ -23,12 +23,16 @@ final meshProvisioningDataOut = Platform.isAndroid ? Uuid.parse('00002ADC-0000-1000-8000-00805F9B34FB') : Uuid.parse('2ADC'); final clientCharacteristicConfigDescriptorUuid = Platform.isAndroid ? Uuid.parse('00002902-0000-1000-8000-00805f9b34fb') : Uuid.parse('2902'); -const Duration _kConnectionTimeout = Duration(seconds: 30); +/// {@template ble_manager} +/// An abstract class that should be extended to handle BLE device interactions. +/// +/// It already implements most parts of the connection process and also triggers event on the given [BleManagerCallbacks]. +/// {@endtemplate} abstract class BleManager { /// The current BLE device being managed if any - DiscoveredDevice? _device; DiscoveredDevice? get device => _device; + DiscoveredDevice? _device; /// A [bool] used to adapt the connection process bool isProvisioningCompleted = false; @@ -46,34 +50,37 @@ abstract class BleManager { late final StreamSubscription _globalStatusListener; /// A [Completer] used to handle the async behavior of [connect] method - late Completer _connectCompleter; - @protected Completer get connectCompleter => _connectCompleter; + late Completer _connectCompleter; /// The entry point for BLE library - final FlutterReactiveBle _bleInstance; FlutterReactiveBle get bleInstance => _bleInstance; + final FlutterReactiveBle _bleInstance; + /// {@macro ble_manager} BleManager(this._bleInstance) { _globalStatusListener = _bleInstance.connectedDeviceStream.listen(_onGlobalStateUpdate); } + /// Will clear used resources Future dispose() async { await callbacks?.dispose(); await _connectedDeviceStatusListener?.cancel(); await _globalStatusListener.cancel(); } + /// A method that should be overriden to define a validation on the BLE device advertised services @visibleForOverriding Future isRequiredServiceSupported(bool shouldCheckDoozCustomService); /// A method that should implement the GATT initialization. /// - /// In our case, it means requesting the highest possible MTU size and subcribing to notifications + /// It should for instance request the highest possible MTU size and subscribe to notifications @visibleForOverriding Future initGatt(); + /// Will disconnect from the current device if any Future disconnect() async { if (_device == null) { _log('calling disconnect without connected device..'); @@ -98,6 +105,9 @@ abstract class BleManager { /// - add event in [callbacks] sinks /// - return any error on the stream or any given reason for [DeviceConnectionState.disconnected] events (usually a [GenericFailure]) /// + /// _DooZ specific API :_ + /// TODO remove before 1.0.0 + /// /// The [whitelist] has been introduced along with [doozCustomCharacteristicUuid] and is intended to be used like so : /// - the whitelist is populated with MAC addresses and [shouldCheckDoozCustomService] is true /// - it will connect to the BLE device and check the MAC address that should be stored in the DooZ custom charac @@ -106,7 +116,7 @@ abstract class BleManager { /// (because this is a DooZ application specific flow, we made these parameters optional) Future connect( final DiscoveredDevice discoveredDevice, { - Duration connectionTimeout = _kConnectionTimeout, + Duration connectionTimeout = kDefaultConnectionTimeout, List? whitelist, bool shouldCheckDoozCustomService = false, }) async { diff --git a/lib/src/ble/ble_manager_callbacks.dart b/lib/src/ble/ble_manager_callbacks.dart index f7dc3253a..97a052a27 100644 --- a/lib/src/ble/ble_manager_callbacks.dart +++ b/lib/src/ble/ble_manager_callbacks.dart @@ -2,18 +2,26 @@ import 'dart:async'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; +/// {@template discovered_device_and_service} +/// A data class used to hold a [DiscoveredDevice] and the corresponding BLE Mesh service +/// {@endtemplate} class BleManagerCallbacksDiscoveredServices { final DiscoveredDevice device; final DiscoveredService service; + /// {@macro discovered_device_and_service} const BleManagerCallbacksDiscoveredServices(this.device, this.service); } +/// {@template ble_error} +/// An error class use for propagating BLE errors +/// {@endtemplate} class BleManagerCallbacksError { final DiscoveredDevice? device; final String message; final Object? error; + /// {@macro ble_error} const BleManagerCallbacksError(this.device, this.message, this.error); @override @@ -23,37 +31,57 @@ class BleManagerCallbacksError { 'device name: ${device?.name})'; } +/// An abstract class that should be extended to access callbacks during BLE device interactions abstract class BleManagerCallbacks { + /// The [StreamController] that is used to trigger an event when a device is connecting final onDeviceConnectingController = StreamController(); + + /// The [Stream] that will contain the `connecting` event Stream get onDeviceConnecting => onDeviceConnectingController.stream; + /// The [StreamController] that is used to trigger an event when a device is connected final onDeviceConnectedController = StreamController(); + + /// The [Stream] that will contain the `connected` event Stream get onDeviceConnected => onDeviceConnectedController.stream; + /// The [StreamController] that is used to trigger an event when a device is disconnecting final onDeviceDisconnectingController = StreamController(); + + /// The [Stream] that will contain the `disconnecting` event Stream get onDeviceDisconnecting => onDeviceDisconnectingController.stream; + /// The [StreamController] that is used to trigger an event when a device is disconnected final onDeviceDisconnectedController = StreamController(); - Stream get onDeviceDisconnected => onDeviceDisconnectedController.stream; - // Stream onLinkLossOccurred; + /// The [Stream] that will contain the `disconnected` event + Stream get onDeviceDisconnected => onDeviceDisconnectedController.stream; + /// The [StreamController] that is used to trigger an event when BLE services are validated upon connection final onServicesDiscoveredController = StreamController(); + + /// The [Stream] that will contain a [BleManagerCallbacksDiscoveredServices] object when BLE services are validated upon connection Stream get onServicesDiscovered => onServicesDiscoveredController.stream; + /// The [StreamController] that is used to trigger an event when the phone is ready to interact with target BLE device final onDeviceReadyController = StreamController(); + + /// The [Stream] that will contain an event when the phone is ready to interact with target BLE device Stream get onDeviceReady => onDeviceReadyController.stream; + /// The [StreamController] that is used to trigger an event when an error occur during connection lifetime final onErrorController = StreamController(); - Stream get onError => onErrorController.stream; - // Stream onDeviceNotSupported; + /// The [Stream] that will contain any [BleManagerCallbacksError] that could occur during connection lifetime + Stream get onError => onErrorController.stream; /// A method that should be used to update the stored MTU so the native code properly constructs the PDUs Future sendMtuToMeshManagerApi(int mtu); + /// RFU bool shouldEnableBatteryLevelNotifications(DiscoveredDevice device) => false; + /// Will clear the used resources Future dispose() => Future.wait([ onDeviceConnectingController.close(), onDeviceConnectedController.close(), diff --git a/lib/src/ble/ble_mesh_manager.dart b/lib/src/ble/ble_mesh_manager.dart index 448ca607b..9af9bb6ba 100644 --- a/lib/src/ble/ble_mesh_manager.dart +++ b/lib/src/ble/ble_mesh_manager.dart @@ -6,23 +6,35 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; import 'package:retry/retry.dart'; +/// {@template ble_mesh_manager} +/// A Singleton that should be used to handle **BLE Mesh** connectivity features. +/// +/// It implements the methods to init GATT layer, subscribe to notifications and send PDUs for **BLE Mesh** nodes. +/// {@endtemplate} class BleMeshManager extends BleManager { static late final BleMeshManager _instance = BleMeshManager._(FlutterReactiveBle()); + /// The list of [DiscoveredService] that were discovered during the last connection process late List _discoveredServices; + /// The subscription for data when the connected node is a mesh proxy (ie. already provisioned in a network) StreamSubscription>? _meshProxyDataOutSubscription; + + /// The subscription for data when the connected node is a free mesh node (ie. waiting to be provisioned in a network) StreamSubscription>? _meshProvisioningDataOutSubscription; BleMeshManager._(FlutterReactiveBle _bleInstance) : super(_bleInstance); + /// {@macro ble_mesh_manager} factory BleMeshManager() => _instance as BleMeshManager; void _log(String msg) => debugPrint('[NordicNrfMesh] $msg'); - void onDeviceDisconnected() async { + /// A method to clear some resources when the device should be disconnected + void _onDeviceDisconnected() async { isProvisioningCompleted = false; await _meshProxyDataOutSubscription?.cancel(); _meshProxyDataOutSubscription = null; @@ -32,15 +44,18 @@ class BleMeshManager extends BleManager { @override Future disconnect() { - onDeviceDisconnected(); + _onDeviceDisconnected(); return super.disconnect(); } + /// A method to read the connected device's MAC address via [doozCustomServiceUuid] + /// + /// _(DooZ specific API)_ Future getServiceMacId() async { String? macId; - if (hasExpectedService(doozCustomServiceUuid)) { + if (_hasExpectedService(doozCustomServiceUuid)) { final service = _discoveredServices.firstWhere((service) => service.serviceId == doozCustomServiceUuid); - if (hasExpectedCharacteristicUuid(service, doozCustomCharacteristicUuid)) { + if (_hasExpectedCharacteristicUuid(service, doozCustomCharacteristicUuid)) { macId = await getMacId(); } } @@ -49,21 +64,22 @@ class BleMeshManager extends BleManager { @override Future isRequiredServiceSupported(bool shouldCheckDoozCustomService) async { + // In the case of a mesh node, the advertised service should be either 0x1827 or 0x1828 _discoveredServices = await bleInstance.discoverServices(device!.id); _log('services $_discoveredServices'); isProvisioningCompleted = false; - if (hasExpectedService(meshProxyUuid)) { + if (_hasExpectedService(meshProxyUuid)) { isProvisioningCompleted = true; // check for meshProxy characs final service = _discoveredServices.firstWhere((service) => service.serviceId == meshProxyUuid); - if (hasExpectedCharacteristicUuid(service, meshProxyDataIn) && - hasExpectedCharacteristicUuid(service, meshProxyDataOut)) { + if (_hasExpectedCharacteristicUuid(service, meshProxyDataIn) && + _hasExpectedCharacteristicUuid(service, meshProxyDataOut)) { // if shouldCheckDoozCustomService is true, will also check for the existence of doozCustomServiceUuid // that has been introduced in firmwares v1.1.0 so we can get the mac address even on iOS devices if (shouldCheckDoozCustomService) { - if (hasExpectedService(doozCustomServiceUuid)) { + if (_hasExpectedService(doozCustomServiceUuid)) { final service = _discoveredServices.firstWhere((service) => service.serviceId == doozCustomServiceUuid); - if (hasExpectedCharacteristicUuid(service, doozCustomCharacteristicUuid)) { + if (_hasExpectedCharacteristicUuid(service, doozCustomCharacteristicUuid)) { return service; } } @@ -77,10 +93,10 @@ class BleMeshManager extends BleManager { } return null; } else { - if (hasExpectedService(meshProvisioningUuid)) { + if (_hasExpectedService(meshProvisioningUuid)) { final service = _discoveredServices.firstWhere((service) => service.serviceId == meshProvisioningUuid); - if (hasExpectedCharacteristicUuid(service, meshProvisioningDataIn) && - hasExpectedCharacteristicUuid(service, meshProvisioningDataOut)) { + if (_hasExpectedCharacteristicUuid(service, meshProvisioningDataIn) && + _hasExpectedCharacteristicUuid(service, meshProvisioningDataOut)) { return service; } } @@ -90,44 +106,42 @@ class BleMeshManager extends BleManager { @override Future initGatt() async { + // request highest MTU (only useful on Android) final negotiatedMtu = await bleInstance.requestMtu(deviceId: device!.id, mtu: mtuSizeMax); if (Platform.isAndroid) { mtuSize = negotiatedMtu - 3; } else if (Platform.isIOS) { mtuSize = negotiatedMtu; } + // notify about negociated MTU size await callbacks!.sendMtuToMeshManagerApi(mtuSize); + // subscribe to notifications from the proper BLE service (proxy/provisioning) DiscoveredService? discoveredService; if (isProvisioningCompleted) { discoveredService = _discoveredServices.firstWhere((service) => service.serviceId == meshProxyUuid); await _meshProxyDataOutSubscription?.cancel(); _meshProxyDataOutSubscription = - getDataOutSubscription(getQualifiedCharacteristic(meshProxyDataOut, discoveredService.serviceId)); + _getDataOutSubscription(_getQualifiedCharacteristic(meshProxyDataOut, discoveredService.serviceId)); } else { discoveredService = _discoveredServices.firstWhere((service) => service.serviceId == meshProvisioningUuid); await _meshProvisioningDataOutSubscription?.cancel(); _meshProvisioningDataOutSubscription = - getDataOutSubscription(getQualifiedCharacteristic(meshProvisioningDataOut, discoveredService.serviceId)); + _getDataOutSubscription(_getQualifiedCharacteristic(meshProvisioningDataOut, discoveredService.serviceId)); } } - bool hasExpectedService(Uuid serviceUuid) { - return _discoveredServices.any((service) => service.serviceId == serviceUuid); - } + bool _hasExpectedService(Uuid serviceUuid) => _discoveredServices.any((service) => service.serviceId == serviceUuid); - bool hasExpectedCharacteristicUuid(DiscoveredService discoveredService, Uuid expectedCharacteristicId) { - return discoveredService.characteristicIds.any((uuid) => uuid == expectedCharacteristicId); - } + bool _hasExpectedCharacteristicUuid(DiscoveredService discoveredService, Uuid expectedCharacteristicId) => + discoveredService.characteristicIds.any((uuid) => uuid == expectedCharacteristicId); - QualifiedCharacteristic getQualifiedCharacteristic(Uuid characteristicId, Uuid serviceId) { - return QualifiedCharacteristic( - characteristicId: characteristicId, - serviceId: serviceId, - deviceId: device!.id, - ); - } + QualifiedCharacteristic _getQualifiedCharacteristic(Uuid characteristicId, Uuid serviceId) => QualifiedCharacteristic( + characteristicId: characteristicId, + serviceId: serviceId, + deviceId: device!.id, + ); - StreamSubscription> getDataOutSubscription(QualifiedCharacteristic qCharacteristic) => + StreamSubscription> _getDataOutSubscription(QualifiedCharacteristic qCharacteristic) => bleInstance.subscribeToCharacteristic(qCharacteristic).where((data) => data.isNotEmpty == true).listen((data) { if (!(callbacks?.onDataReceivedController.isClosed == true) && callbacks!.onDataReceivedController.hasListener) { @@ -151,6 +165,9 @@ class BleMeshManager extends BleManager { } }); + /// This method will send the given PDU. + /// + /// It may split the data in chunks based on the current [mtuSize]. Future sendPdu(final List pdu) async { final chunks = ((pdu.length / (mtuSize - 1)) + 1).floor(); var srcOffset = 0; @@ -159,15 +176,15 @@ class BleMeshManager extends BleManager { final length = math.min(pdu.length - srcOffset, mtuSize); final sublist = pdu.sublist(srcOffset, srcOffset + length); final segmentedBuffer = sublist; - await send(segmentedBuffer); + await _send(segmentedBuffer); srcOffset += length; } } else { - await send(pdu); + await _send(pdu); } } - Future send(final List data) async { + Future _send(final List data) async { if (data.isEmpty) { return; } @@ -177,7 +194,7 @@ class BleMeshManager extends BleManager { () async { final service = _discoveredServices.firstWhere((service) => service.serviceId == meshProxyUuid); await bleInstance.writeCharacteristicWithoutResponse( - getQualifiedCharacteristic(meshProxyDataIn, service.serviceId), + _getQualifiedCharacteristic(meshProxyDataIn, service.serviceId), value: data); }, retryIf: (e) => e is PlatformException, @@ -190,7 +207,7 @@ class BleMeshManager extends BleManager { () async { final service = _discoveredServices.firstWhere((service) => service.serviceId == meshProvisioningUuid); await bleInstance.writeCharacteristicWithoutResponse( - getQualifiedCharacteristic(meshProvisioningDataIn, service.serviceId), + _getQualifiedCharacteristic(meshProvisioningDataIn, service.serviceId), value: data); }, retryIf: (e) => e is PlatformException, @@ -200,6 +217,7 @@ class BleMeshManager extends BleManager { } } + /// A method to clear GATT cache (only useful in some cases in **Android**) Future refreshDeviceCache() async { if (Platform.isAndroid) { if (device != null) { diff --git a/lib/src/ble/ble_mesh_manager_callbacks.dart b/lib/src/ble/ble_mesh_manager_callbacks.dart index 0ef33c164..d7766eb89 100644 --- a/lib/src/ble/ble_mesh_manager_callbacks.dart +++ b/lib/src/ble/ble_mesh_manager_callbacks.dart @@ -3,36 +3,60 @@ import 'dart:async'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nordic_nrf_mesh/src/ble/ble_manager_callbacks.dart'; +/// {@template ble_data_event} +/// An abstract data class that should be extended to hold the in/out data of the current BLE connection +/// {@endtemplate} abstract class _BleMeshManagerCallbacksEvent { + /// The currently connected device final DiscoveredDevice device; + + /// The currently used MTU size final int mtu; + + /// The PDU of this in/out event final List pdu; + /// {@macro ble_data_event} const _BleMeshManagerCallbacksEvent(this.device, this.mtu, this.pdu); @override String toString() => '$device, $mtu, $pdu'; } +/// {@template ble_data_received} +/// A data class used when some PDU is received +/// {@endtemplate} class BleMeshManagerCallbacksDataReceived extends _BleMeshManagerCallbacksEvent { + /// {@macro ble_data_received} const BleMeshManagerCallbacksDataReceived(DiscoveredDevice device, int mtu, List pdu) : super(device, mtu, pdu); @override String toString() => 'BleMeshManagerCallbacksDataReceived{ ${super.toString()} }'; } +/// {@template ble_data_sent} +/// A data class used when some PDU is sent +/// {@endtemplate} class BleMeshManagerCallbacksDataSent extends _BleMeshManagerCallbacksEvent { + /// {@macro ble_data_sent} const BleMeshManagerCallbacksDataSent(DiscoveredDevice device, int mtu, List pdu) : super(device, mtu, pdu); @override String toString() => 'BleMeshManagerCallbacksDataSent{ ${super.toString()} }'; } +/// An abstract class that should be extended to access callbacks during BLE device interactions abstract class BleMeshManagerCallbacks extends BleManagerCallbacks { + /// The [StreamController] that is used to trigger an event when some data is received while connected to a BLE device final onDataReceivedController = StreamController(); + + /// The [Stream] that will contain any data received while connected to a BLE device Stream get onDataReceived => onDataReceivedController.stream; + /// The [StreamController] that is used to trigger an event when some data is sent to the connected BLE device final onDataSentController = StreamController(); + + /// The [Stream] that will contain any data sent to the connected BLE device Stream get onDataSent => onDataSentController.stream; @override diff --git a/lib/src/ble/ble_scanner.dart b/lib/src/ble/ble_scanner.dart index 5177195ca..04f3cc24b 100644 --- a/lib/src/ble/ble_scanner.dart +++ b/lib/src/ble/ble_scanner.dart @@ -4,35 +4,46 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; import 'ble_manager.dart'; // for mesh guid constants -const Duration defaultScanDuration = Duration(seconds: 5); - +/// {@template ble_scanner_error} +/// A data class used to hold some data when an ble scan error occur +/// {@endtemplate} class BleScannerError { final String message; final Object error; + /// {@macro ble_scanner_error} const BleScannerError(this.message, this.error); } +/// {@template ble_scanner} +/// This singleton is used to wrap the Bluetooth scanning features of [FlutterReactiveBle]. +/// {@endtemplate} class BleScanner { static late final BleScanner _instance = BleScanner._(); BleScanner._(); + /// {@macro ble_scanner} factory BleScanner() => _instance; + /// The entry point for BLE library final FlutterReactiveBle _flutterReactiveBle = FlutterReactiveBle(); + /// {@macro ble_status_stream} Stream get bleStatusStream => _flutterReactiveBle.statusStream; + /// {@macro ble_status} BleStatus get bleStatus => _flutterReactiveBle.status; - late final StreamController _onScanErrorController = StreamController.broadcast(); - + /// A [Stream] providing any [BleScannerError] that could occur when using (un)provisionedNodesInRange methods Stream get onScanErrorStream => _onScanErrorController.stream; + late final StreamController _onScanErrorController = StreamController.broadcast(); + /// Used to close the [onScanErrorStream] void dispose() { _onScanErrorController.close(); } @@ -45,7 +56,7 @@ class BleScanner { Future> _scanWithParamsAsFuture({ ScanMode scanMode = ScanMode.lowLatency, List withServices = const [], - Duration timeoutDuration = defaultScanDuration, + Duration timeoutDuration = kDefaultScanDuration, }) async { if (Platform.isIOS || Platform.isAndroid) { final scanResults = []; @@ -87,6 +98,7 @@ class BleScanner { } } + /// {@macro get_specific_node} Future searchForSpecificNode( String deviceNameOrId, Duration scanTimeout, @@ -105,25 +117,30 @@ class BleScanner { return result; } + /// {@macro get_unprovisioned} Future> unprovisionedNodesInRange({ - Duration timeoutDuration = defaultScanDuration, + Duration timeoutDuration = kDefaultScanDuration, }) => _scanWithParamsAsFuture( withServices: [meshProvisioningUuid], timeoutDuration: timeoutDuration, ); + /// {@macro scan_unprovisioned} Stream scanForUnprovisionedNodes() => _scanWithParamsAsStream(withServices: [meshProvisioningUuid]); + /// {@macro get_provisioned} Future> provisionedNodesInRange({ - Duration timeoutDuration = defaultScanDuration, + Duration timeoutDuration = kDefaultScanDuration, }) => _scanWithParamsAsFuture( withServices: [meshProxyUuid], timeoutDuration: timeoutDuration, ); + /// {@macro scan_provisioned} Stream scanForProxy() => _scanWithParamsAsStream(withServices: [meshProxyUuid]); + /// {@macro custom_scan} Stream scanWithServices(List services) => _scanWithParamsAsStream(withServices: services); } diff --git a/lib/src/contants.dart b/lib/src/constants.dart similarity index 86% rename from lib/src/contants.dart rename to lib/src/constants.dart index 6cc8659f4..c33bdbec4 100644 --- a/lib/src/contants.dart +++ b/lib/src/constants.dart @@ -1,4 +1,9 @@ +/// The namespace (used to identify flutter method and event channels) const namespace = 'fr.dooz.nordic_nrf_mesh'; +const Duration kDefaultScanDuration = Duration(seconds: 5); +const Duration kDefaultConnectionTimeout = Duration(seconds: 30); +const mtuSizeMax = 517; +const maxPacketSize = 20; /// Used when an error occured during the provisioning process enum ProvisioningFailureCode { diff --git a/lib/src/events/mesh_manager_api_events.dart b/lib/src/events/mesh_manager_api_events.dart index 9c8f6da8b..589b4d322 100644 --- a/lib/src/events/mesh_manager_api_events.dart +++ b/lib/src/events/mesh_manager_api_events.dart @@ -1,3 +1,8 @@ +/// {@template mesh_manager_event} +/// A class used to enumerate the events received from native side. +/// +/// _(may be refactored to an enum in future releases of Dart)_ +/// {@endtemplate} class MeshManagerApiEvent { final String value; diff --git a/lib/src/exceptions/ble_manager_exception.dart b/lib/src/exceptions/ble_manager_exception.dart index 8e40475a2..ad8c05353 100644 --- a/lib/src/exceptions/ble_manager_exception.dart +++ b/lib/src/exceptions/ble_manager_exception.dart @@ -1,4 +1,4 @@ -import 'package:nordic_nrf_mesh/src/contants.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; /// An [Exception] that can be thrown during the lifecycle of a BLE connection class BleManagerException implements Exception { diff --git a/lib/src/exceptions/nrf_mesh_provisioning_exception.dart b/lib/src/exceptions/nrf_mesh_provisioning_exception.dart index 0496e2ee7..8beac8fb8 100644 --- a/lib/src/exceptions/nrf_mesh_provisioning_exception.dart +++ b/lib/src/exceptions/nrf_mesh_provisioning_exception.dart @@ -1,4 +1,4 @@ -import 'package:nordic_nrf_mesh/src/contants.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; /// An [Exception] that can be thrown during the provisioning process class NrfMeshProvisioningException implements Exception { diff --git a/lib/src/extensions/extensions.dart b/lib/src/extensions/extensions.dart index 4c4c858be..a8c3b52c2 100644 --- a/lib/src/extensions/extensions.dart +++ b/lib/src/extensions/extensions.dart @@ -1,3 +1,4 @@ extension BitFieldString on int { + /// A method to "pretty-print" a given integer as a series of [width] bit(s) String bitField({int width = 8}) => toRadixString(2).padLeft(width, '0'); } diff --git a/lib/src/mesh_manager_api.dart b/lib/src/mesh_manager_api.dart index 9c80d46a8..63c5e758b 100644 --- a/lib/src/mesh_manager_api.dart +++ b/lib/src/mesh_manager_api.dart @@ -4,28 +4,32 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; -import 'package:nordic_nrf_mesh/src/contants.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; import 'package:nordic_nrf_mesh/src/events/mesh_manager_api_events.dart'; import 'package:nordic_nrf_mesh/src/mesh_network.dart'; import 'package:rxdart/rxdart.dart'; +/// {@template mesh_manager_api} +/// This class is used to expose Nordic's APIs and handle a mesh network. +/// +/// It listens to all native events and distribute it to broadcast [Stream]s that should in turn be listened by plugin consumer. +/// {@endtemplate} class MeshManagerApi { + // native channels late final _methodChannel = const MethodChannel('$namespace/mesh_manager_api/methods'); late final _eventChannel = const EventChannel('$namespace/mesh_manager_api/events'); + late Stream> _eventChannelStream; + // event controllers late final _onNetworkLoadedStreamController = StreamController.broadcast(); late final _onNetworkImportedController = StreamController.broadcast(); late final _onNetworkUpdatedController = StreamController.broadcast(); - late final _onMeshPduCreatedController = StreamController>.broadcast(); late final _sendProvisioningPduController = StreamController.broadcast(); - late final _onProvisioningStateChangedController = StreamController.broadcast(); late final _onProvisioningFailedController = StreamController.broadcast(); late final _onProvisioningCompletedController = StreamController.broadcast(); - late final _onConfigCompositionDataStatusController = StreamController.broadcast(); late final _onConfigAppKeyStatusController = StreamController.broadcast(); late final _onGenericLevelStatusController = StreamController.broadcast(); @@ -33,7 +37,6 @@ class MeshManagerApi { late final _onDoozEpochStatusController = StreamController.broadcast(); late final _onV2MagicLevelSetStatusController = StreamController.broadcast(); late final _onV2MagicLevelGetStatusController = StreamController.broadcast(); - late final _onGenericOnOffStatusController = StreamController.broadcast(); late final _onConfigModelAppStatusController = StreamController.broadcast(); late final _onConfigModelSubscriptionStatusController = StreamController.broadcast(); @@ -42,12 +45,11 @@ class MeshManagerApi { late final _onConfigNetworkTransmitStatusController = StreamController.broadcast(); late final _onConfigDefaultTtlStatusController = StreamController.broadcast(); late final _onConfigBeaconStatusController = StreamController.broadcast(); - late final _onLightLightnessStatusController = StreamController.broadcast(); late final _onLightCtlStatusController = StreamController.broadcast(); late final _onLightHslStatusController = StreamController.broadcast(); late final _onConfigKeyRefreshPhaseStatusController = StreamController.broadcast(); - + // stream subs late StreamSubscription _onNetworkLoadedSubscription; late StreamSubscription _onNetworkImportedSubscription; late StreamSubscription _onNetworkUpdatedSubscription; @@ -73,63 +75,55 @@ class MeshManagerApi { late StreamSubscription _onConfigNetworkTransmitStatusSubscription; late StreamSubscription _onConfigDefaultTtlStatusSubscription; late StreamSubscription _onConfigBeaconStatusSubscription; - late StreamSubscription _onLightLightnessStatusSubscription; late StreamSubscription _onLightCtlStatusSubscription; late StreamSubscription _onLightHslStatusSubscription; - late StreamSubscription _onConfigKeyRefreshPhaseStatusSubscription; - late Stream> _eventChannelStream; MeshNetwork? _lastMeshNetwork; - void _log(String msg) => debugPrint('[NordicNrfMesh] $msg'); - MeshManagerApi() { + // initialize main event stream listener _eventChannelStream = _eventChannel.receiveBroadcastStream().cast().map((event) => event.cast()); if (kDebugMode) { _eventChannelStream.doOnData((data) => debugPrint('$data')); } - + // network events _onNetworkLoadedSubscription = _onMeshNetworkEventSucceed(MeshManagerApiEvent.loaded).listen(_onNetworkLoadedStreamController.add); _onNetworkImportedSubscription = _onMeshNetworkEventSucceed(MeshManagerApiEvent.imported).listen(_onNetworkImportedController.add); _onNetworkUpdatedSubscription = _onMeshNetworkEventSucceed(MeshManagerApiEvent.updated).listen(_onNetworkUpdatedController.add); - _onNetworkLoadFailedSubscription = _onMeshNetworkEventFailed(MeshManagerApiEvent.loadFailed).listen(_onNetworkLoadedStreamController.addError); _onNetworkImportFailedSubscription = _onMeshNetworkEventFailed(MeshManagerApiEvent.importFailed).listen(_onNetworkImportedController.addError); - + // pdu events _onMeshPduCreatedSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.meshPduCreated.value) .map((event) => event['pdu'] as List) .map((event) => event.cast()) .listen(_onMeshPduCreatedController.add); - _sendProvisioningPduSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.sendProvisioningPdu.value) .map((event) => SendProvisioningPduData.fromJson(event)) .listen(_sendProvisioningPduController.add); - + // provisioning events _onProvisioningStateChangedSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.provisioningStateChanged.value) .map((event) => MeshProvisioningStatusData.fromJson(event)) .listen(_onProvisioningStateChangedController.add); - _onProvisioningCompletedSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.provisioningCompleted.value) .map((event) => MeshProvisioningCompletedData.fromJson(event)) .listen(_onProvisioningCompletedController.add); - _onProvisioningFailedSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.provisioningFailed.value) .map((event) => MeshProvisioningStatusData.fromJson(event)) .listen(_onProvisioningFailedController.add); - + // mesh status events _onConfigCompositionDataStatusSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.configCompositionDataStatus.value) .map((event) => ConfigCompositionDataStatusData.fromJson(event)) @@ -162,7 +156,6 @@ class MeshManagerApi { .where((event) => event['eventName'] == MeshManagerApiEvent.v2MagicLevelGetStatus.value) .map((event) => MagicLevelGetStatusData.fromJson(event)) .listen(_onV2MagicLevelGetStatusController.add); - _onLightLightnessStatusSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.lightLightnessStatus.value) .map((event) => LightLightnessStatusData.fromJson(event)) @@ -175,7 +168,6 @@ class MeshManagerApi { .where((event) => event['eventName'] == MeshManagerApiEvent.lightHslStatus.value) .map((event) => LightHslStatusData.fromJson(event)) .listen(_onLightHslStatusController.add); - _onConfigModelAppStatusSubscription = _eventChannelStream .where((event) => event['eventName'] == MeshManagerApiEvent.configModelAppStatus.value) .map((event) => ConfigModelAppStatusData.fromJson(event)) @@ -259,6 +251,7 @@ class MeshManagerApi { Stream get onV2MagicLevelGetStatus => _onV2MagicLevelGetStatusController.stream; + /// The currently loaded [IMeshNetwork] or null IMeshNetwork? get meshNetwork => _lastMeshNetwork; Stream get onLightLightnessStatus => _onLightLightnessStatusController.stream; @@ -270,8 +263,7 @@ class MeshManagerApi { Stream get onConfigKeyRefreshPhaseStatus => _onConfigKeyRefreshPhaseStatusController.stream; - Uuid get meshProvisioningUuidServiceKey => meshProvisioningUuid; - + /// Checks if the node is advertising with Node Identity Future isAdvertisedWithNodeIdentity(final List serviceData) async { final result = await _methodChannel.invokeMethod( 'isAdvertisedWithNodeIdentity', @@ -280,6 +272,7 @@ class MeshManagerApi { return result!; } + /// Checks if the node identity matches Future nodeIdentityMatches(List serviceData) async { final result = await _methodChannel.invokeMethod( 'nodeIdentityMatches', @@ -288,6 +281,7 @@ class MeshManagerApi { return result!; } + /// Checks if the node is advertising with Network Identity Future isAdvertisingWithNetworkIdentity(final List serviceData) async { final result = await _methodChannel.invokeMethod( 'isAdvertisingWithNetworkIdentity', @@ -296,6 +290,7 @@ class MeshManagerApi { return result!; } + /// Checks if the generated network ids match. The network ID contained in the service data would be checked against a network id of each network key. Future networkIdMatches(List serviceData) async { final result = await _methodChannel.invokeMethod( 'networkIdMatches', @@ -304,6 +299,7 @@ class MeshManagerApi { return result!; } + /// Should be called to clear all resources used by this class void dispose() => Future.wait([ _onNetworkLoadedSubscription.cancel(), _onNetworkImportedSubscription.cancel(), @@ -363,44 +359,60 @@ class MeshManagerApi { _onConfigBeaconStatusController.close() ]); + /// Loads the mesh network from the local database. Future loadMeshNetwork() async { final future = _onNetworkLoadedStreamController.stream.first; await _methodChannel.invokeMethod('loadMeshNetwork'); return future; } + /// Starts an asynchronous task that imports a network from the mesh configuration db json Future importMeshNetworkJson(final String json) async { final future = _onNetworkImportedController.stream.first; await _methodChannel.invokeMethod('importMeshNetworkJson', {'json': json}); return future; } + /// Notify native side about the current mtu size Future setMtu(final int mtuSize) => _methodChannel.invokeMethod('setMtuSize', {'mtuSize': mtuSize}); + /// Exports full mesh network to a JSON String. Future exportMeshNetwork() async { final json = await _methodChannel.invokeMethod('exportMeshNetwork'); return json; } + /// This method will clear the provisioned nodes, reset the sequence number and generate new network with new provisioning data. Future resetMeshNetwork() => _methodChannel.invokeMethod('resetMeshNetwork'); + /// Handles notifications received by the client. + /// + /// **Should be called whenever data is received from a mesh node, so the Nordic library do the parsing job** Future handleNotifications(int mtu, List pdu) => _methodChannel.invokeMethod( 'handleNotifications', {'mtu': mtu, 'pdu': pdu}, ); + /// Must be called to handle sent data. Future handleWriteCallbacks(int mtu, List pdu) => _methodChannel.invokeMethod( 'handleWriteCallbacks', {'mtu': mtu, 'pdu': pdu}, ); + /// Identifies the node that is to be provisioned. + /// + /// _WARNING: This method is not intended to be used by external user of nrf_mesh_plugin. It is used by the provisioning method._ Future identifyNode(String serviceUuid) => _methodChannel.invokeMethod( 'identifyNode', {'serviceUuid': serviceUuid}, ); + /// This method reset the unprovisioned nodes cache. + /// + /// _WARNING: This method is not intended to be used by external use of nrf_mesh_plugin. It is used for the provisioning process._ Future cleanProvisioningData() => _methodChannel.invokeMethod('cleanProvisioningData'); + /// Will send a GenericLevelSet message to the given [address]. Future sendGenericLevelSet( int address, int level, { @@ -424,6 +436,7 @@ class MeshManagerApi { return status; } + /// Will send a GenericLevelGet message to the given [address]. Future sendGenericLevelGet( int address, { int keyIndex = 0, @@ -439,6 +452,7 @@ class MeshManagerApi { return status; } + /// Will send a GenericLevelOnOff message to the given [address]. Future sendGenericOnOffSet( int address, bool value, @@ -464,6 +478,9 @@ class MeshManagerApi { return status; } + /// Will send a MagicLevelSet message to the given [address]. + /// + /// _(DooZ specific API)_ Future sendV2MagicLevelSet( int address, int io, @@ -487,6 +504,9 @@ class MeshManagerApi { return status; } + /// Will send a MagicLevelGet message to the given [address]. + /// + /// _(DooZ specific API)_ Future sendV2MagicLevelGet( int address, int io, @@ -508,11 +528,14 @@ class MeshManagerApi { return status; } + /// Will send a ConfigCompositionDataGet message to the given [dest]. Future sendConfigCompositionDataGet(int dest) => _methodChannel.invokeMethod('sendConfigCompositionDataGet', {'dest': dest}); + /// Will send a ConfigAppKeyAdd message to the given [dest]. Future sendConfigAppKeyAdd(int dest) => _methodChannel.invokeMethod('sendConfigAppKeyAdd', {'dest': dest}); + /// Will send a ConfigModelAppBind message to the given [nodeId]. Future sendConfigModelAppBind(int nodeId, int elementId, int modelId, {int appKeyIndex = 0}) async { final status = _onConfigModelAppStatusController.stream.firstWhere( @@ -529,6 +552,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigModelSubscriptionAdd message to the given [elementAddress]. Future sendConfigModelSubscriptionAdd( int elementAddress, int subscriptionAddress, int modelIdentifier) async { final status = _onConfigModelSubscriptionStatusController.stream.firstWhere( @@ -546,6 +570,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigModelSubscriptionDelete message to the given [elementAddress]. Future sendConfigModelSubscriptionDelete( int elementAddress, int subscriptionAddress, int modelIdentifier) async { final status = _onConfigModelSubscriptionStatusController.stream.firstWhere( @@ -563,6 +588,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigModelSubscriptionDeleteAll message to the given [elementAddress]. Future sendConfigModelSubscriptionDeleteAll(int elementAddress, int modelIdentifier) => _methodChannel.invokeMethod( 'sendConfigModelSubscriptionDeleteAll', @@ -572,6 +598,7 @@ class MeshManagerApi { }, ); + /// Will send a ConfigModelPublicationSet message to the given [elementAddress]. Future sendConfigModelPublicationSet( int elementAddress, int publishAddress, @@ -613,6 +640,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigModelPublicationGet message to the given [elementAddress]. Future getPublicationSettings( int elementAddress, int modelIdentifier, @@ -632,6 +660,7 @@ class MeshManagerApi { } } + /// Will send a LightLightnessSet message to the given [address]. Future sendLightLightness( int address, int lightness, @@ -651,6 +680,7 @@ class MeshManagerApi { return status; } + /// Will send a LightCtlSet message to the given [address]. Future sendLightCtl( int address, int lightness, @@ -674,6 +704,7 @@ class MeshManagerApi { return status; } + /// Will send a LightHslSet message to the given [address]. Future sendLightHsl( int address, int lightness, @@ -697,6 +728,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigDefaultTtlGet message to the given [address]. Future getDefaultTtl(int address) async { final status = _onConfigDefaultTtlStatusController.stream.firstWhere( (element) => element.source == address, @@ -706,6 +738,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigDefaultTtlSet message to the given [address]. Future setDefaultTtl(int address, int ttl) async { final status = _onConfigDefaultTtlStatusController.stream.firstWhere( (element) => element.source == address, @@ -718,6 +751,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigNetworkTransmitSet message to the given [address]. Future setNetworkTransmitSettings( int address, int transmitCount, @@ -735,6 +769,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigNetworkTransmitGet message to the given [address]. Future getNetworkTransmitSettings(int address) async { final status = _onConfigNetworkTransmitStatusController.stream.firstWhere( (element) => element.source == address, @@ -744,6 +779,7 @@ class MeshManagerApi { return status; } + /// Will send a ConfigKeyRefreshPhaseGet message to the given [address]. Future keyRefreshPhaseGet({ int address = 0xFFFF, int netKeyIndex = 0, @@ -770,6 +806,7 @@ class MeshManagerApi { static const int useNewKeys = 2; // Normal operation static const int revokeOldKeys = 3; // Key Distribution + /// Will send a ConfigKeyRefreshPhaseSet message to the given [address]. Future keyRefreshPhaseSet({ int address = 0xFFFF, int netKeyIndex = 0, @@ -829,6 +866,8 @@ class MeshManagerApi { /// Will send a DoozScenarioSet message (0x8219). /// Defaults to a scenario that apply a level 0 (OFF cmd with lights). + /// + /// _(DooZ specific API)_ Future doozScenarioSet( int address, int scenarioId, @@ -873,6 +912,8 @@ class MeshManagerApi { } /// Will send a DoozEpochSet message (0x8220). + /// + /// _(DooZ specific API)_ Future doozScenarioEpochSet( int address, int tzData, @@ -942,26 +983,34 @@ class MeshManagerApi { return '${_digits(msb >> 32, 8)}-${_digits(msb >> 16, 4)}-${_digits(msb, 4)}-${_digits(lsb >> 48, 4)}-${_digits(lsb, 12)}'; } - Future provisioningIos(String uuid) => _methodChannel.invokeMethod('provisioning', {'uuid': uuid}); - + /// Provision the given [meshNode]. + /// + /// _WARNING: This method is not intended to be used by external user of nrf_mesh_plugin. It is used by the provisioning method._ Future provisioning(UnprovisionedMeshNode meshNode) => _methodChannel.invokeMethod('provisioning', meshNode.toJson()); + /// {@macro deprovision} Future deprovision(ProvisionedMeshNode meshNode) async { - final unicastAddress = await meshNode.unicastAddress; - final status = _onConfigNodeResetStatusController.stream - .where((element) => element.source == unicastAddress) - .timeout(const Duration(seconds: 3), - onTimeout: (sink) => sink.add( - const ConfigNodeResetStatus(-1, -1, false), - )) - .first; - await _methodChannel.invokeMethod('deprovision', {'unicastAddress': unicastAddress}); - return status; + if (Platform.isIOS || Platform.isAndroid) { + final unicastAddress = await meshNode.unicastAddress; + final status = _onConfigNodeResetStatusController.stream + .where((element) => element.source == unicastAddress) + .timeout(const Duration(seconds: 3), + onTimeout: (sink) => sink.add( + const ConfigNodeResetStatus(-1, -1, false), + )) + .first; + await _methodChannel.invokeMethod('deprovision', {'unicastAddress': unicastAddress}); + return status; + } else { + throw UnsupportedError('Platform ${Platform.operatingSystem} is not supported'); + } } + /// A method that will return a mesh node uuid during provisioning process or null Future cachedProvisionedMeshNodeUuid() => _methodChannel.invokeMethod('cachedProvisionedMeshNodeUuid'); + /// A method to get the sequence number of a given mesh [node] Future getSequenceNumber(ProvisionedMeshNode node) async { if (Platform.isIOS || Platform.isAndroid) { final result = await _methodChannel.invokeMethod( @@ -973,6 +1022,7 @@ class MeshManagerApi { throw Exception('Platform not supported'); } + /// A method to set the sequence number of a given mesh [node] Future setSequenceNumber(ProvisionedMeshNode node, int seqNum) async => _methodChannel.invokeMethod( 'setSequenceNumberForAddress', {'address': await node.unicastAddress, 'sequenceNumber': seqNum}, @@ -996,4 +1046,6 @@ class MeshManagerApi { Stream _onMeshNetworkEventFailed(final MeshManagerApiEvent eventType) => _filterEventChannel(eventType).map((event) => MeshNetworkEventError.fromJson(event)); + + void _log(String msg) => debugPrint('[NordicNrfMesh] $msg'); } diff --git a/lib/src/mesh_network.dart b/lib/src/mesh_network.dart index 9b5c5be97..20d4989db 100644 --- a/lib/src/mesh_network.dart +++ b/lib/src/mesh_network.dart @@ -3,33 +3,58 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:nordic_nrf_mesh/src/contants.dart'; -import 'package:nordic_nrf_mesh/src/models/models.dart'; +import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; +/// {@template mesh_network} +/// The class defining the API to manage a bluetooth mesh network. +/// {@endtemplate} abstract class IMeshNetwork { - Future> get groups; - Future get highestAllocatableAddress; + /// The id of the network + String get id; + + /// The name of the network Future get name; + + /// The current list of provisioners + Future> get provisioners; + + /// The current list of nodes (provisioners + provisioned mesh devices) Future> get nodes; - String get id; - Future> get provisionerList; + /// The currently defined group(s) + Future> get groups; + + /// The max address that the current selected provisioner can allocate + Future get highestAllocatableAddress; + + /// Will try to add a new group in the network with the given [name] Future addGroupWithName(String name); + /// Will check if the given [unicastAddress] is free of use Future assignUnicastAddress(int unicastAddress); - Future> elementsForGroup(int id); + /// Will return the data of elements subscribed to the given [groupAddress] + Future> elementsForGroup(int groupAddress); + /// Will return the next free unicast address based on the number of elements of a node Future nextAvailableUnicastAddress(int elementSize); + /// Will return the next free unicast address based on the number of elements of a node. The address shall be greater than the given [minAddress] Future nextAvailableUnicastAddressWithMin(int minAddress, int elementSize); - Future removeGroup(int id); + /// Will remove the group with the given [groupAddress] + Future removeGroup(int groupAddress); + /// Will return the UUID of the current selected provisioner Future selectedProvisionerUuid(); + /// Will select the provisioner at the given index of the [provisioners] list Future selectProvisioner(int provisionerIndex); + /// Will add a provisioner in the current network using the given ranges and ttl. + /// + /// Will return false if the creation is a failure (e.g the unicast address range is incompatible with the current configuration) Future addProvisioner( int unicastAddressRange, int groupAddressRange, @@ -38,34 +63,53 @@ abstract class IMeshNetwork { String name, }); + /// Will update the given [provisioner] Future updateProvisioner(Provisioner provisioner); + /// Will remove the provisioner with the given uuid Future removeProvisioner(String provisionerUUID); + /// Will manually remove a node from the network. + /// + /// If you want to deprovision a node, please use the `deprovision` method of [NordicNrfMesh] Future deleteNode(String uid); + /// Will return the subscribed addresses of the given element and model Future getMeshModelSubscriptions(int elementAddress, int modelIdentifier); + /// Will return the elements that have subscribed either **Generic Level** models or **Generic ON/OFF** models to the given [groupAddress] Future getGroupElementIds(int groupAddress); + /// Will return the node corresponding to the given [address] Future getNode(int address); + /// Will return the node corresponding to the given [uuid] Future getNodeUsingUUID(String uuid); + /// Will return a newly generated Network Key Future generateNetKey(); + /// Will return the Network Key at the given index Future getNetKey(int netKeyIndex); + /// Will remove the Network Key at the given index Future removeNetKey(int netKeyIndex); + /// Will distribute the Network Key at the given index Future distributeNetKey(int netKeyIndex); } +/// {@template mesh_network_impl} +/// The implementation of [IMeshNetwork] used at runtime. +/// {@endtemplate} class MeshNetwork implements IMeshNetwork { + /// The [MethodChannel] used to interact with Nordic API final MethodChannel _methodChannel; + /// The unique id of the currently loaded [MeshNetwork] final String _id; + /// {@macro mesh_network_impl} MeshNetwork(this._id) : _methodChannel = MethodChannel('$namespace/mesh_network/$_id/methods'); @override @@ -134,7 +178,7 @@ class MeshNetwork implements IMeshNetwork { _methodChannel.invokeMethod('selectProvisioner', {'provisionerIndex': provisionerIndex}); @override - Future> get provisionerList async { + Future> get provisioners async { var provisioners = []; final result = await _methodChannel.invokeMethod('getProvisionersAsJson'); var prov = json.decode(result) as List; diff --git a/lib/src/models/group/group.dart b/lib/src/models/group/group.dart index ad9504aaa..b40e9547d 100644 --- a/lib/src/models/group/group.dart +++ b/lib/src/models/group/group.dart @@ -3,10 +3,16 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'group.freezed.dart'; part 'group.g.dart'; +/// {@template group_data} +/// A freezed data class used to hold a group data +/// {@endtemplate} @freezed class GroupData with _$GroupData { + /// {@macro group_data} const factory GroupData(String name, int address, String? addressLabel, String meshUuid, int parentAddress, String? parentAddressLabel) = _GroupData; + /// Provide a constructor to get [GroupData] from JSON [Map]. + /// {@macro group_data} factory GroupData.fromJson(Map json) => _$GroupDataFromJson(json); } diff --git a/lib/src/models/mesh_node/provisioned_mesh_node.dart b/lib/src/models/mesh_node/provisioned_mesh_node.dart index 7fd0ee9f5..ad9d2244d 100644 --- a/lib/src/models/mesh_node/provisioned_mesh_node.dart +++ b/lib/src/models/mesh_node/provisioned_mesh_node.dart @@ -1,9 +1,12 @@ import 'package:flutter/services.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:nordic_nrf_mesh/src/contants.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; part 'provisioned_mesh_node.g.dart'; +/// {@template model_data} +/// A serializable data class used to hold data about a specific model of a mesh node +/// {@endtemplate} @JsonSerializable(anyMap: true) class ModelData { final int key; @@ -11,16 +14,24 @@ class ModelData { final List subscribedAddresses; final List boundAppKey; + /// {@macro model_data} ModelData(this.key, this.modelId, this.subscribedAddresses, this.boundAppKey); + /// Provide a constructor to get [ModelData] from JSON [Map]. + /// {@macro model_data} factory ModelData.fromJson(Map json) => _$ModelDataFromJson(json); + /// Provide a constructor to get [Map] from [ModelData]. + /// {@macro model_data} Map toJson() => _$ModelDataToJson(this); @override String toString() => 'ModelData ${toJson()}'; } +/// {@template element_data} +/// A serializable data class used to hold data about a specific element of a mesh node +/// {@endtemplate} @JsonSerializable(anyMap: true) class ElementData { final int key; @@ -29,28 +40,41 @@ class ElementData { final int locationDescriptor; final List models; + /// {@macro element_data} ElementData(this.key, this.name, this.address, this.locationDescriptor, this.models); + /// Provide a constructor to get [ElementData] from JSON [Map]. + /// {@macro element_data} factory ElementData.fromJson(Map json) => _$ElementDataFromJson(json.cast()); + /// Provide a constructor to get [Map] from [ElementData]. + /// {@macro element_data} Map toJson() => _$ElementDataToJson(this); @override String toString() => 'ElementData ${toJson()}'; } +/// {@template provisioned_node} +/// A class used to expose some data of a given **provisioned** mesh node +/// {@endtemplate} class ProvisionedMeshNode { final MethodChannel _methodChannel; final String uuid; + /// {@macro provisioned_node} ProvisionedMeshNode(this.uuid) : _methodChannel = MethodChannel('$namespace/provisioned_mesh_node/$uuid/methods'); + /// Will return the unicast address of this node as stored in the local database Future get unicastAddress async => (await _methodChannel.invokeMethod('unicastAddress'))!; + /// Will set the name of this node to be stored in the local database set nodeName(String name) => _methodChannel.invokeMethod('nodeName', {'name': name}); + /// Will return the name of this node as stored in the local database Future get name async => (await _methodChannel.invokeMethod('name'))!; + /// Will return the list of elements of this node as stored in the local database Future> get elements async { final _elements = await _methodChannel.invokeMethod('elements'); return _elements!.map((e) => ElementData.fromJson(e)).toList(); diff --git a/lib/src/models/mesh_node/unprovisioned_mesh_node.dart b/lib/src/models/mesh_node/unprovisioned_mesh_node.dart index 9dafb74ce..c79b5c2ff 100644 --- a/lib/src/models/mesh_node/unprovisioned_mesh_node.dart +++ b/lib/src/models/mesh_node/unprovisioned_mesh_node.dart @@ -1,15 +1,21 @@ import 'package:flutter/services.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:nordic_nrf_mesh/src/contants.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; part 'unprovisioned_mesh_node.g.dart'; +/// {@template unprovisioned_node} +/// A class used to expose some data of a given **unprovisioned** mesh node. +/// +/// _Used by the plugin to handle the provisioning process._ +/// {@endtemplate} @JsonSerializable() class UnprovisionedMeshNode { final MethodChannel _methodChannel; final String uuid; final List? provisionerPublicKeyXY; + /// {@macro unprovisioned_node} UnprovisionedMeshNode(this.uuid, this.provisionerPublicKeyXY) : _methodChannel = MethodChannel('$namespace/unprovisioned_mesh_node/$uuid/methods'); diff --git a/lib/src/models/network_key/network_key.dart b/lib/src/models/network_key/network_key.dart index e6625c7f0..87bdfc883 100644 --- a/lib/src/models/network_key/network_key.dart +++ b/lib/src/models/network_key/network_key.dart @@ -3,8 +3,12 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'network_key.freezed.dart'; part 'network_key.g.dart'; +/// {@template network_key} +/// A freezed data class used to hold a given Network Key data +/// {@endtemplate} @freezed class NetworkKey with _$NetworkKey { + /// {@macro network_key} const factory NetworkKey( String name, int netKeyIndex, @@ -20,5 +24,7 @@ class NetworkKey with _$NetworkKey { int timestamp, ) = _NetworkKey; + /// Provide a constructor to get [NetworkKey] from JSON [Map]. + /// {@macro network_key} factory NetworkKey.fromJson(Map json) => _$NetworkKeyFromJson(json); } diff --git a/lib/src/models/provisioner/provisioner.dart b/lib/src/models/provisioner/provisioner.dart index 968594ec5..b70fff1d8 100644 --- a/lib/src/models/provisioner/provisioner.dart +++ b/lib/src/models/provisioner/provisioner.dart @@ -6,8 +6,12 @@ import 'package:nordic_nrf_mesh/src/models/provisioner/range_addresses/allocated part 'provisioner.freezed.dart'; part 'provisioner.g.dart'; +/// {@template provisioner} +/// A freezed data class used to hold data about a provisioner +/// {@endtemplate} @freezed class Provisioner with _$Provisioner { + /// {@macro provisioner} const factory Provisioner( String provisionerName, String provisionerUuid, @@ -18,5 +22,7 @@ class Provisioner with _$Provisioner { List allocatedSceneRanges, bool lastSelected) = _Provisioner; + /// Provide a constructor to get [Provisioner] from JSON [Map]. + /// {@macro provisioner} factory Provisioner.fromJson(Map json) => _$ProvisionerFromJson(json); } diff --git a/lib/src/models/provisioner/range_addresses/allocated_group_range.dart b/lib/src/models/provisioner/range_addresses/allocated_group_range.dart index 70a4ffea0..395d69f57 100644 --- a/lib/src/models/provisioner/range_addresses/allocated_group_range.dart +++ b/lib/src/models/provisioner/range_addresses/allocated_group_range.dart @@ -3,9 +3,15 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'allocated_group_range.freezed.dart'; part 'allocated_group_range.g.dart'; +/// {@template group_range} +/// A freezed data class used to hold a group address range +/// {@endtemplate} @freezed class AllocatedGroupRange with _$AllocatedGroupRange { + /// {@macro group_range} const factory AllocatedGroupRange(int lowAddress, int highAddress) = _AllocatedGroupRange; + /// Provide a constructor to get [AllocatedGroupRange] from JSON [Map]. + /// {@macro group_range} factory AllocatedGroupRange.fromJson(Map json) => _$AllocatedGroupRangeFromJson(json); } diff --git a/lib/src/models/provisioner/range_addresses/allocated_scene_range.dart b/lib/src/models/provisioner/range_addresses/allocated_scene_range.dart index 9f0e1cb11..472f4dae0 100644 --- a/lib/src/models/provisioner/range_addresses/allocated_scene_range.dart +++ b/lib/src/models/provisioner/range_addresses/allocated_scene_range.dart @@ -3,9 +3,15 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'allocated_scene_range.freezed.dart'; part 'allocated_scene_range.g.dart'; +/// {@template scene_range} +/// A freezed data class used to hold a scene range +/// {@endtemplate} @freezed class AllocatedSceneRange with _$AllocatedSceneRange { + /// {@macro scene_range} const factory AllocatedSceneRange(int firstScene, int lastScene) = _AllocatedSceneRange; + /// Provide a constructor to get [AllocatedSceneRange] from JSON [Map]. + /// {@macro scene_range} factory AllocatedSceneRange.fromJson(Map json) => _$AllocatedSceneRangeFromJson(json); } diff --git a/lib/src/models/provisioner/range_addresses/allocated_unicast_range.dart b/lib/src/models/provisioner/range_addresses/allocated_unicast_range.dart index 4730e9a71..cf57c0a95 100644 --- a/lib/src/models/provisioner/range_addresses/allocated_unicast_range.dart +++ b/lib/src/models/provisioner/range_addresses/allocated_unicast_range.dart @@ -3,9 +3,15 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'allocated_unicast_range.freezed.dart'; part 'allocated_unicast_range.g.dart'; +/// {@template unicast_range} +/// A freezed data class used to hold a unicast address range +/// {@endtemplate} @freezed class AllocatedUnicastRange with _$AllocatedUnicastRange { + /// {@macro unicast_range} const factory AllocatedUnicastRange(int lowAddress, int highAddress) = _AllocatedUnicastRange; + /// Provide a constructor to get [AllocatedUnicastRange] from JSON [Map]. + /// {@macro unicast_range} factory AllocatedUnicastRange.fromJson(Map json) => _$AllocatedUnicastRangeFromJson(json); } diff --git a/lib/src/nrf_mesh.dart b/lib/src/nrf_mesh.dart index 44c68e4f7..dff7ca305 100644 --- a/lib/src/nrf_mesh.dart +++ b/lib/src/nrf_mesh.dart @@ -4,22 +4,36 @@ import 'package:flutter/services.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; import 'package:nordic_nrf_mesh/src/ble/ble_scanner.dart'; -import 'package:nordic_nrf_mesh/src/contants.dart'; +import 'package:nordic_nrf_mesh/src/constants.dart'; import 'package:nordic_nrf_mesh/src/utils/provisioning.dart' as utils_provisioning; +/// {@template nordic_nrf_mesh} +/// The entry point for the plugin. +/// It exposes some important methods such as Bluetooth scanning and mesh (de)provisioning. +/// +/// To leverage all Bluetooth capabilities, one shall instantiate [BleMeshManager]. +/// +/// To use the Nordic APIs, one should use the [MeshManagerApi] available via the [meshManagerApi] getter of the [NordicNrfMesh] instance. +/// {@endtemplate} +/// +/// {@macro mesh_manager_api} class NordicNrfMesh { final _methodChannel = const MethodChannel('$namespace/methods'); - final BleScanner _bleScanner = BleScanner(); Future get platformVersion async { final version = await _methodChannel.invokeMethod('getPlatformVersion'); return version; } - late final MeshManagerApi _meshManagerApi = MeshManagerApi(); + /// {@macro ble_scanner} + late final BleScanner _bleScanner = BleScanner(); + + /// {@macro mesh_manager_api} MeshManagerApi get meshManagerApi => _meshManagerApi; + late final MeshManagerApi _meshManagerApi = MeshManagerApi(); - /// Will try to provision the specified [BluetoothDevice]. + /// {@template provisioning} + /// Will try to provision the specified [DiscoveredDevice]. /// /// After the process or if any error occurs, the [BleManager] will be disconnected from device. /// @@ -27,6 +41,7 @@ class NordicNrfMesh { /// /// Throws an [NrfMeshProvisioningException] if provisioning failed /// or an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Future provisioning( final MeshManagerApi meshManagerApi, final BleMeshManager bleMeshManager, @@ -37,39 +52,46 @@ class NordicNrfMesh { utils_provisioning.provisioning(meshManagerApi, bleMeshManager, _bleScanner, device, serviceDataUuid, events: events); - /// Will try to deprovision the specified [ProvisionedMeshNode] by sending a unicast [ConfigNodeReset] message. + /// {@template deprovision} + /// Will try to deprovision the specified [ProvisionedMeshNode] by sending [ConfigNodeReset] message via the unicast address. /// /// Returns a [ConfigNodeResetStatus] or null if timeout after 5sec. /// /// Throws a method channel error "NOT FOUND" if not found in the currently loaded mesh n/w /// or an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Future deprovision( final MeshManagerApi meshManagerApi, final ProvisionedMeshNode meshNode, ) => - utils_provisioning.deprovision(meshManagerApi, meshNode); + meshManagerApi.deprovision(meshNode); + /// {@template cancel_provisioning} /// Will try to cancel the provisioning. /// /// Returns `true` if the call has been successful, `false` otherwise. /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Future cancelProvisioning( final MeshManagerApi meshManagerApi, final BleMeshManager bleMeshManager, ) => utils_provisioning.cancelProvisioning(meshManagerApi, _bleScanner, bleMeshManager); + /// {@template get_unprovisioned} /// Will scan for **unprovisioned** nodes. /// /// Returns a [List] of [DiscoveredDevice] that may be empty if no device is in range. /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Future> unprovisionedNodesInRange({ - Duration timeoutDuration = defaultScanDuration, + Duration timeoutDuration = kDefaultScanDuration, }) => _bleScanner.unprovisionedNodesInRange(timeoutDuration: timeoutDuration); + /// {@template scan_unprovisioned} /// Will scan for **unprovisioned** nodes. /// /// Returns a [Stream] of [DiscoveredDevice]. @@ -77,18 +99,22 @@ class NordicNrfMesh { /// To stop the scan, make sure to cancel any subscription to this [Stream]. /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Stream scanForUnprovisionedNodes() => _bleScanner.scanForUnprovisionedNodes(); + /// {@template get_provisioned} /// Will scan for **provisioned** nodes. /// /// Returns a [List] of [DiscoveredDevice] that may be empty if no device is in range. /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Future> provisionedNodesInRange({ - Duration timeoutDuration = defaultScanDuration, + Duration timeoutDuration = kDefaultScanDuration, }) => _bleScanner.provisionedNodesInRange(timeoutDuration: timeoutDuration); + /// {@template scan_provisioned} /// Will scan for **provisioned** nodes. /// /// Returns a [Stream] of [DiscoveredDevice] for the user to listen to. @@ -96,8 +122,10 @@ class NordicNrfMesh { /// To stop the scan, user has to make sure to cancel any subscription to this [Stream]. /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Stream scanForProxy() => _bleScanner.scanForProxy(); + /// {@template custom_scan} /// Will scan for devices that broadcast given services. /// /// Returns a [Stream] of [DiscoveredDevice] for the user to listen to. @@ -105,8 +133,10 @@ class NordicNrfMesh { /// To stop the scan, user has to make sure to cancel any subscription to this [Stream]. /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Stream scanWithServices(List services) => _bleScanner.scanWithServices(services); + /// {@template get_specific_node} /// Will scan for the given device using name or id (MAC on Android or UUID on iOS). /// /// It will scan by default for **unprovisioned** nodes, but one can switch to proxy candidates using the [isProxy] bool flag. @@ -114,16 +144,21 @@ class NordicNrfMesh { /// Returns a [DiscoveredDevice] or null if not found after [timeoutDuration] (defaults to 5sec). /// /// Throws an [UnsupportedError] if the current OS is not supported. + /// {@endtemplate} Future searchForSpecificNode( String deviceNameOrId, { bool isProxy = false, - Duration timeoutDuration = defaultScanDuration, + Duration timeoutDuration = kDefaultScanDuration, }) => _bleScanner.searchForSpecificNode(deviceNameOrId, timeoutDuration, isProxy); + /// {@template ble_status_stream} /// Provide a [Stream] of the current [BleStatus] of the host device. + /// {@endtemplate} Stream get bleStatusStream => _bleScanner.bleStatusStream; + /// {@template ble_status} /// Will return the last known [BleStatus] (tracked via stream by BLE library, so it should always be up-to-date) + /// {@endtemplate} BleStatus get bleStatus => _bleScanner.bleStatus; } diff --git a/lib/src/utils/provisioning.dart b/lib/src/utils/provisioning.dart index 7c4de111e..0d0eeba0a 100644 --- a/lib/src/utils/provisioning.dart +++ b/lib/src/utils/provisioning.dart @@ -3,40 +3,44 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; -import 'package:nordic_nrf_mesh/src/ble/ble_manager_callbacks.dart'; -import 'package:nordic_nrf_mesh/src/ble/ble_mesh_manager.dart'; -import 'package:nordic_nrf_mesh/src/ble/ble_mesh_manager_callbacks.dart'; +import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; import 'package:nordic_nrf_mesh/src/ble/ble_scanner.dart'; -import 'package:nordic_nrf_mesh/src/contants.dart'; -import 'package:nordic_nrf_mesh/src/events/data/config_node_reset_status/config_node_reset_status.dart'; -import 'package:nordic_nrf_mesh/src/exceptions/exceptions.dart'; -import 'package:nordic_nrf_mesh/src/mesh_manager_api.dart'; -import 'package:nordic_nrf_mesh/src/models/models.dart'; -class _ProvisioningEvent { - final _provisioningController = StreamController(); - final _provisioningCapabilitiesController = StreamController(); - final _provisioningInvitationController = StreamController(); - final _provisioningReconnectController = StreamController(); - final _onConfigCompositionDataStatusController = StreamController(); - final _onConfigAppKeyStatusController = StreamController(); - final _provisioningGattErrorController = StreamController(); -} - -class ProvisioningEvent extends _ProvisioningEvent { +/// {@template provisioning_events} +/// A class that may be used to listen to provisioning progress. +/// +/// When the provisioning method is running, if the caller passed a [ProvisioningEvent] instance, events will be streamed to notify about the current state. +/// {@endtemplate} +class ProvisioningEvent { + /// A [Stream] that will contain the [DiscoveredDevice] when the main part of the provisioning is about to begin Stream get onProvisioning => _provisioningController.stream; + final _provisioningController = StreamController(); + /// A [Stream] that will contain the [DiscoveredDevice] when the provisioning capabilities have been received Stream get onProvisioningCapabilities => _provisioningCapabilitiesController.stream; + final _provisioningCapabilitiesController = StreamController(); + /// A [Stream] that will contain the [DiscoveredDevice] when the provisioning invite has been sent Stream get onProvisioningInvitation => _provisioningInvitationController.stream; + final _provisioningInvitationController = StreamController(); + /// A [Stream] that will contain the [DiscoveredDevice] when provisioning is completed and we try to reconnect to the new node Stream get onProvisioningReconnect => _provisioningReconnectController.stream; + final _provisioningReconnectController = StreamController(); + /// A [Stream] that will contain the [DiscoveredDevice] when the mesh composition data has been received Stream get onConfigCompositionDataStatus => _onConfigCompositionDataStatusController.stream; + final _onConfigCompositionDataStatusController = StreamController(); + /// A [Stream] that will contain the [DiscoveredDevice] when the app key has been received by the new node Stream get onConfigAppKeyStatus => _onConfigAppKeyStatusController.stream; + final _onConfigAppKeyStatusController = StreamController(); + + /// A [Stream] that will contain a [BleManagerCallbacksError] (unexpected error that should be handled by plugin user) Stream get onProvisioningGattError => _provisioningGattErrorController.stream; + final _provisioningGattErrorController = StreamController(); + /// Will clear used resources Future dispose() => Future.wait([ _provisioningController.close(), _provisioningCapabilitiesController.close(), @@ -48,19 +52,21 @@ class ProvisioningEvent extends _ProvisioningEvent { ]); } -StreamSubscription? onBleScannerError; -StreamSubscription? onProvisioningCompletedSubscription; -StreamSubscription? onProvisioningStateChangedSubscription; -StreamSubscription? onProvisioningFailedSubscription; -StreamSubscription? sendProvisioningPduSubscription; -StreamSubscription? onConfigCompositionDataStatusSubscription; -StreamSubscription? onConfigAppKeyStatusSubscription; -StreamSubscription? onDeviceReadySubscription; -StreamSubscription? onDataReceivedSubscription; -StreamSubscription? onMeshPduCreatedSubscription; -StreamSubscription? onGattErrorSubscription; -StreamSubscription? onDataSentSubscription; +// necessary subcriptions to handle the whole provisioning process +StreamSubscription? _onBleScannerError; +StreamSubscription? _onProvisioningCompletedSubscription; +StreamSubscription? _onProvisioningStateChangedSubscription; +StreamSubscription? _onProvisioningFailedSubscription; +StreamSubscription? _sendProvisioningPduSubscription; +StreamSubscription? _onConfigCompositionDataStatusSubscription; +StreamSubscription? _onConfigAppKeyStatusSubscription; +StreamSubscription? _onDeviceReadySubscription; +StreamSubscription? _onDataReceivedSubscription; +StreamSubscription? _onMeshPduCreatedSubscription; +StreamSubscription? _onGattErrorSubscription; +StreamSubscription? _onDataSentSubscription; +/// {@macro provisioning} Future provisioning(MeshManagerApi meshManagerApi, BleMeshManager bleMeshManager, BleScanner bleScanner, DiscoveredDevice device, String serviceDataUuid, {ProvisioningEvent? events}) async { @@ -71,6 +77,7 @@ Future provisioning(MeshManagerApi meshManagerApi, BleMeshM } } +/// {@macro provisioning} Future _provisioning( MeshManagerApi meshManagerApi, BleMeshManager bleMeshManager, @@ -82,18 +89,24 @@ Future _provisioning( throw NrfMeshProvisioningException(ProvisioningFailureCode.meshConfiguration, 'You need to load a meshNetwork before being able to provision a device'); } + // this completer will help providing a Future that corresponds to the process ending final completer = Completer(); + // when this boll is false, it will notify errors via _onGattErrorSubscription late bool isHandlingConnectErrors; + // the node that will be returned after the provisioning process ends late final ProvisionedMeshNode provisionedMeshNode; - //'Undocumented scan throttle' error caught here - onBleScannerError = bleScanner.onScanErrorStream.listen((event) { + _onBleScannerError = bleScanner.onScanErrorStream.listen((event) { _log('Scanner Error : ${event.error}'); }); - + // override callbacks so we can react to BLE events final provisioningCallbacks = BleMeshManagerProvisioningCallbacks(meshManagerApi); bleMeshManager.callbacks = provisioningCallbacks; - onProvisioningCompletedSubscription = meshManagerApi.onProvisioningCompleted.listen((event) async { + // define listeners to handle the provisioning process asynchronously + _onProvisioningCompletedSubscription = meshManagerApi.onProvisioningCompleted.listen((event) async { + // upon provisioning completion, we disconnect from the device, + // scan to check whether it advertises the proper services, + // and then reconnect to it try { await bleMeshManager.refreshDeviceCache(); await bleMeshManager.disconnect(); @@ -135,11 +148,13 @@ Future _provisioning( completer.completeError(NrfMeshProvisioningException(ProvisioningFailureCode.provisioningCompleted, _msg)); } }); - onProvisioningFailedSubscription = meshManagerApi.onProvisioningFailed.listen((event) async { + // If provisioning failed, stop process and notify caller by throwing an error + _onProvisioningFailedSubscription = meshManagerApi.onProvisioningFailed.listen((event) async { completer.completeError(NrfMeshProvisioningException( ProvisioningFailureCode.provisioningFailed, 'Failed to provision device ${deviceToProvision.id}')); }); - onProvisioningStateChangedSubscription = meshManagerApi.onProvisioningStateChanged.listen((event) async { + // define what should be done when receiving some events from the native side + _onProvisioningStateChangedSubscription = meshManagerApi.onProvisioningStateChanged.listen((event) async { if (event.state == 'PROVISIONING_CAPABILITIES') { events?._provisioningCapabilitiesController.add(deviceToProvision); final unprovisionedMeshNode = @@ -178,29 +193,35 @@ Future _provisioning( } } }); - onDeviceReadySubscription = bleMeshManager.callbacks!.onDeviceReady.listen((event) async { + // when connected to device, need to identify it in order to start the provisioning process + _onDeviceReadySubscription = bleMeshManager.callbacks!.onDeviceReady.listen((event) async { if (Platform.isIOS && bleMeshManager.isProvisioningCompleted) { + // this case is here because the 'PROVISIONING_INVITE' is not on iOS native code (for now?) final unicast = await provisionedMeshNode.unicastAddress; await meshManagerApi.sendConfigCompositionDataGet(unicast); } else { await meshManagerApi.identifyNode(serviceDataUuid); } }); - sendProvisioningPduSubscription = meshManagerApi.sendProvisioningPdu.listen((event) async { + // handle sending PDUs + _sendProvisioningPduSubscription = meshManagerApi.sendProvisioningPdu.listen((event) async { await bleMeshManager.sendPdu(event.pdu); }); - onMeshPduCreatedSubscription = meshManagerApi.onMeshPduCreated.listen((event) async { + _onMeshPduCreatedSubscription = meshManagerApi.onMeshPduCreated.listen((event) async { await bleMeshManager.sendPdu(event); }); if (Platform.isAndroid) { - onDataSentSubscription = bleMeshManager.callbacks!.onDataSent.listen((event) async { + // on Android need to call Nordic library to handle sent data parsing + _onDataSentSubscription = bleMeshManager.callbacks!.onDataSent.listen((event) async { await meshManagerApi.handleWriteCallbacks(event.mtu, event.pdu); }); } - onDataReceivedSubscription = bleMeshManager.callbacks!.onDataReceived.listen((event) async { + // handle received data parsing + _onDataReceivedSubscription = bleMeshManager.callbacks!.onDataReceived.listen((event) async { await meshManagerApi.handleNotifications(event.mtu, event.pdu); }); - onGattErrorSubscription = bleMeshManager.callbacks!.onError.listen((event) { + // will notify call and stop process in case of unexpected GATT error + _onGattErrorSubscription = bleMeshManager.callbacks!.onError.listen((event) { _log('received error event : $event'); if (!isHandlingConnectErrors) { // if not in a connection phase where auto retry are implemented, we should notify gatt errors @@ -215,31 +236,39 @@ Future _provisioning( } } }); - onConfigCompositionDataStatusSubscription = meshManagerApi.onConfigCompositionDataStatus.listen((event) async { + // when we received the mesh composition data from the newly provisioned node, we should bind to the network using ConfigAppKey msg + _onConfigCompositionDataStatusSubscription = meshManagerApi.onConfigCompositionDataStatus.listen((event) async { events?._onConfigCompositionDataStatusController.add(deviceToProvision); await meshManagerApi.sendConfigAppKeyAdd(await provisionedMeshNode.unicastAddress); }); - onConfigAppKeyStatusSubscription = meshManagerApi.onConfigAppKeyStatus.listen((event) async { + // when ConfigAppKey has been received, the provisioning is successful ! + _onConfigAppKeyStatusSubscription = meshManagerApi.onConfigAppKeyStatus.listen((event) async { events?._onConfigAppKeyStatusController.add(deviceToProvision); completer.complete(provisionedMeshNode); }); try { + // disconnect from device if any await bleMeshManager.refreshDeviceCache(); await bleMeshManager.disconnect(); + // auto retry connect to the target device _connectRetryCount = 0; isHandlingConnectErrors = true; await _connect(bleMeshManager, deviceToProvision); isHandlingConnectErrors = false; + // wait for listeners to do their job await completer.future; + // cleanup resources await meshManagerApi.cleanProvisioningData(); await bleMeshManager.refreshDeviceCache(); await bleMeshManager.disconnect(); - cancelProvisioningCallbackSubscription(bleMeshManager); + _cancelProvisioningCallbackSubscription(bleMeshManager); _log('provisioning success !'); return provisionedMeshNode; } catch (e) { _log('caught error during provisioning... $e'); + // need to clean up data/resources and properly cancel the provisioning process await cancelProvisioning(meshManagerApi, bleScanner, bleMeshManager); + // depending on the error, always try to throw a NrfMeshProvisioningException to ease downstream error handling if (e is NrfMeshProvisioningException) { rethrow; } else if (e is GenericFailure || e is BleManagerException || e is TimeoutException) { @@ -253,13 +282,16 @@ Future _provisioning( } throw NrfMeshProvisioningException(ProvisioningFailureCode.initialConnection, message); } else { - // unknown error that should be diagnosed + // unknown error that should be diagnosed (please file an issue) throw NrfMeshProvisioningException(ProvisioningFailureCode.unknown, '$e'); } } } late int _connectRetryCount; + +/// A method to handle the connections. +/// It may auto retry depending on the failures that could occur. Future _connect(BleMeshManager bleMeshManager, DiscoveredDevice deviceToConnect) async { _connectRetryCount++; await bleMeshManager @@ -267,6 +299,10 @@ Future _connect(BleMeshManager bleMeshManager, DiscoveredDevice deviceToCo .catchError((e) async => await _onConnectError(e, bleMeshManager, deviceToConnect)); } +/// The method that implements the error handling for BLE connection. +/// +/// Some errors can be overcome by a simple retry, +/// others are considered unhandled and this method will rethrow them. Future _onConnectError(Object e, BleMeshManager bleMeshManager, DiscoveredDevice deviceToConnect) async { _log('caught error during connect $e'); if (e is GenericFailure) { @@ -320,31 +356,36 @@ Future _onConnectError(Object e, BleMeshManager bleMeshManager, Discovered } } -void cancelProvisioningCallbackSubscription(BleMeshManager bleMeshManager) { - onProvisioningCompletedSubscription?.cancel(); - onProvisioningStateChangedSubscription?.cancel(); - onProvisioningFailedSubscription?.cancel(); - sendProvisioningPduSubscription?.cancel(); - onConfigCompositionDataStatusSubscription?.cancel(); - onConfigAppKeyStatusSubscription?.cancel(); - onDeviceReadySubscription?.cancel(); - onDataReceivedSubscription?.cancel(); - onMeshPduCreatedSubscription?.cancel(); - onGattErrorSubscription?.cancel(); - onBleScannerError?.cancel(); - if (Platform.isAndroid) onDataSentSubscription?.cancel(); +/// Will clear stream subscriptions used for the provisioning process +void _cancelProvisioningCallbackSubscription(BleMeshManager bleMeshManager) { + _onProvisioningCompletedSubscription?.cancel(); + _onProvisioningStateChangedSubscription?.cancel(); + _onProvisioningFailedSubscription?.cancel(); + _sendProvisioningPduSubscription?.cancel(); + _onConfigCompositionDataStatusSubscription?.cancel(); + _onConfigAppKeyStatusSubscription?.cancel(); + _onDeviceReadySubscription?.cancel(); + _onDataReceivedSubscription?.cancel(); + _onMeshPduCreatedSubscription?.cancel(); + _onGattErrorSubscription?.cancel(); + _onBleScannerError?.cancel(); + if (Platform.isAndroid) _onDataSentSubscription?.cancel(); if (bleMeshManager.callbacks != null) bleMeshManager.callbacks!.dispose(); } +/// {@macro cancel_provisioning} Future cancelProvisioning( MeshManagerApi meshManagerApi, BleScanner bleScanner, BleMeshManager bleMeshManager) async { if (Platform.isIOS || Platform.isAndroid) { _log('should cancel provisioning'); + // try to dispose any resources used by provisioning process try { bleScanner.dispose(); - final cachedProvisionedMeshNodeUuid = await meshManagerApi.cachedProvisionedMeshNodeUuid(); if (bleMeshManager.isProvisioningCompleted && cachedProvisionedMeshNodeUuid != null) { + // a node has been added to the network, but we want to cancel + + // get the unwanted node final nodes = await meshManagerApi.meshNetwork!.nodes; ProvisionedMeshNode? nodeToDelete; try { @@ -352,18 +393,21 @@ Future cancelProvisioning( } on StateError catch (e) { _log('node not found in network\n$e'); } - + // if found, try first to send a ConfigNodeReset if (nodeToDelete != null) { final status = await meshManagerApi.deprovision(nodeToDelete); if (status.success == false) { + // manually delete node from network (WARNING: the device may still be in provisioned state) await meshManagerApi.meshNetwork!.deleteNode(cachedProvisionedMeshNodeUuid); } } } + // remove any data in native side await meshManagerApi.cleanProvisioningData(); + // disconnect await bleMeshManager.refreshDeviceCache(); await bleMeshManager.disconnect(); - cancelProvisioningCallbackSubscription(bleMeshManager); + _cancelProvisioningCallbackSubscription(bleMeshManager); return true; } catch (e) { _log('ERROR - $e'); @@ -374,17 +418,13 @@ Future cancelProvisioning( } } -Future deprovision(MeshManagerApi meshManagerApi, ProvisionedMeshNode meshNode) { - if (Platform.isIOS || Platform.isAndroid) { - return meshManagerApi.deprovision(meshNode); - } else { - throw UnsupportedError('Platform ${Platform.operatingSystem} is not supported'); - } -} - +/// {@template prov_ble_manager} +/// A minimal implementation of [BleMeshManagerCallbacks] +/// {@endtemplate} class BleMeshManagerProvisioningCallbacks extends BleMeshManagerCallbacks { final MeshManagerApi meshManagerApi; + /// {@macro prov_ble_manager} BleMeshManagerProvisioningCallbacks(this.meshManagerApi); @override diff --git a/pubspec.lock b/pubspec.lock index 30977d5a5..e9f8e6d98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -320,7 +320,7 @@ packages: source: hosted version: "0.1.3" meta: - dependency: "direct main" + dependency: transitive description: name: meta url: "https://pub.dartlang.org" @@ -506,13 +506,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" - uuid: - dependency: "direct dev" - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.4" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b567d8428..7b53f791f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,12 @@ name: nordic_nrf_mesh -description: A Flutter plugin to enable mesh network management and communication using Nordic's SDKs. It also provides the ability to open BLE connection with mesh nodes using some other flutter package. -version: 0.9.1 +description: A Flutter plugin to enable mesh network management and communication using Nordic's SDKs. It also provides the ability to open BLE connection with mesh nodes using some other Flutter plugin. +version: 0.10.0 +homepage: https://www.dooz-domotique.com/ +repository: https://github.com/OZEO-DOOZ/nrf_mesh_plugin +issue_tracker: https://github.com/OZEO-DOOZ/nrf_mesh_plugin/issues environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.14.0 <3.0.0" flutter: ">=2.0.0 <3.0.0" dependencies: @@ -16,7 +19,6 @@ dependencies: # Models json_annotation: ^4.0.0 freezed_annotation: ^0.14.0 - meta: ^1.3.0 # static analysis flutter_lints: ^1.0.4 @@ -26,17 +28,8 @@ dev_dependencies: build_runner: ^2.1.0 json_serializable: ^4.1.0 freezed: ^0.14.0 - uuid: ^3.0.0-nullsafety.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' and Android 'package' identifiers should not ordinarily - # be modified. They are used by the tooling to maintain consistency when - # adding or updating assets for this project. plugin: platforms: android: @@ -44,32 +37,3 @@ flutter: pluginClass: NordicNrfMeshPlugin ios: pluginClass: NordicNrfMeshPlugin - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/nordic_nrf_mesh_test.dart b/test/nordic_nrf_mesh_test.dart index 4c28dd400..78b193b53 100644 --- a/test/nordic_nrf_mesh_test.dart +++ b/test/nordic_nrf_mesh_test.dart @@ -3,21 +3,28 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:nordic_nrf_mesh/nordic_nrf_mesh.dart'; void main() { + // variables const channel = MethodChannel('fr.dooz.nordic_nrf_mesh/methods'); - + late final NordicNrfMesh nordicNrfMesh; + late final MeshManagerApi meshManagerApi; TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { + // init/dispose + setUpAll(() { channel.setMockMethodCallHandler((MethodCall methodCall) async { return '42'; }); + nordicNrfMesh = NordicNrfMesh(); + meshManagerApi = nordicNrfMesh.meshManagerApi; }); - - tearDown(() { + tearDownAll(() { channel.setMockMethodCallHandler(null); + meshManagerApi.dispose(); }); - + // tests test('getPlatformVersion', () async { expect(await NordicNrfMesh().platformVersion, '42'); }); + test('mesh network is null by default', () { + expect(meshManagerApi.meshNetwork, isNull); + }); }