Effective Flutter library for all your Bluetooth Low Energy needs in Flutter

Introduction

A library is a collection of preprogrammed templates that implement a behavior when invoked. Libraries are well-defined and are designed for reuse throughout implementation. For example, a website may have multiple webpages that implement the same navigation bar or text-field, but none of these objects have relation to one another. via github.com

Flutter BLE library logo

FlutterBleLib

A library for all your Bluetooth Low Energy needs in Flutter. Internally utilises Polidea’s MultiPlatformBleAdapter, which runs on RxAndroidBle and RxBluetoothKit.

BLE Simulator

This library supports BLEmulator, the BLE simulator. The simulation allows one to develop without physical smartphone or BLE peripheral and use one’s production BLE–related code in automated testing.

Installation

To use this plugin, add flutter_ble_lib as a dependency in your pubspec.yaml file.

Android

Set minSDKVersion in [project]/android/app/build.gradle file to 18.

defaultConfig {
  ...
  minSdkVersion 18
  ...
}

Support for Bluetooth Low Energy has been added in API 18, hence the library requires minSDKVersion to be set to 18. If BLE is not core to your application, you can override it and handle support detection in your code.

Notice: You don’t need to add any permissions related to BLE to the AndroidManifest.xml, because they are already declared in the library’s native module. However, you still need to request ACCESS_FINE_LOCATION permission at runtime to be able to scan for peripheral. See example’s code and example’s pubspec.

iOS

Go to [project]/ios directory and run pod install.

Add Privacy – Bluetooth Always Usage Description key to [project]/ios/Runner/Info.plist file.

...
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Your own description of the purpose.</string>
...

Background mode

To support background capabilities add The bluetooth-central Background Execution Mode key to [project]/ios/Runner/Info.plist file.

...
<key>UIBackgroundModes</key>
<array>
  <string>bluetooth-central</string>
</array>
...

Usage

The library is organised around a few base entities, which are:

  • BleManager
  • Peripheral
  • Service
  • Characteristic
  • Descriptor

You have to create an instance BleManager and initialise underlying native resources. Using that instance you then obtain an instance of Peripheral, which can be used to run operations on the corresponding peripheral.

All operations passing the Dart-native bridge are asynchronous, hence all operations in the plugin return either Future or Stream.

For more informations, see REFERENCE.

Notice: this library will not handle any permissions for you. To be able to scan for peripherals on Android you need ACCESS_FINE_LOCATION according to Android Developer Guide.

Initialising

BleManager bleManager = BleManager();
await bleManager.createClient(); //ready to go!
// your peripheral logic
bleManager.destroyClient(); //remember to release native resources when you're done!

Following snippets assume the library has been initialised.

Handling Bluetooth adapter state

enum BluetoothState {
  UNKNOWN,
  UNSUPPORTED,
  UNAUTHORIZED,
  POWERED_ON,
  POWERED_OFF,
  RESETTING,
}


bleManager.enableRadio(); //ANDROID-ONLY turns on BT. NOTE: doesn't check permissions
bleManager.disableRadio() //ANDROID-ONLY turns off BT. NOTE: doesn't check permissions
BluetoothState currentState = await bleManager.bluetoothState();
bleManager.observeBluetoothState().listen((btState) {
  print(btState);
  //do your BT logic, open different screen, etc.
});

Scanning for peripherals

bleManager.startPeripheralScan(
  uuids: [
    "F000AA00-0451-4000-B000-000000000000",
  ],
).listen((scanResult) {
  //Scan one peripheral and stop scanning
  print("Scanned Peripheral ${scanResult.peripheral.name}, RSSI ${scanResult.rssi}");
  bleManager.stopPeripheralScan();
});

The snippet above starts peripheral scan and stops it after receiving first result. It filters the scan results to those that advertise a service with specified UUID.

NOTE: isConnectable and overflowServiceUuids fields of ScanResult are iOS-only and remain null on Android.

Connecting to saved peripheral

You can try to connect to a peripheral with known ID, be it previously scanned UUID on iOS or a MAC address on Android, and avoid the whole scanning operation in your application. To do so, you need to create an instance of Peripheral using:

Peripheral myPeripheral = bleManager.createUnsafePeripheral("< known id >");

Once you have the instance of the peripheral, you may proceed with the connection. But keep in mind that Android may still not find the peripheral without scanning it first.

Connecting to peripheral

First you must obtain a ScanResult from BleManager.startPeripheralScan().

Peripheral peripheral = scanResult.peripheral;
peripheral.observeConnectionState(emitCurrentValue: true, completeOnDisconnect: true)
  .listen((connectionState) {
    print("Peripheral ${scanResult.peripheral.identifier} connection state is $connectionState");
  });
await peripheral.connect();
bool connected = await peripheral.isConnected();
await peripheral.disconnectOrCancelConnection();

The snippet above starts observing the state of the connection to the peripheral, connects to it, checks if it’s connected and then disconnects from it.

Transactions

Methods that do not have counterpart with opposite effect and are asynchronous accept String transactionId as an optional argument, to allow the user to cancel such an operation. The Future returned to Dart will then finish with a BleError(BleErrorCode.operationCancelled…)but this will only discard the result of the operation, the operation itself will be executed either way.

For example, if I decided that I no longer want to run discovery on the selected peripheral:

//assuming peripheral is connected
peripheral.discoverAllServicesAndCharacteristics(transactionId: "discovery");
//will return operation cancelled error after calling the below
bleManager.cancelTransaction("discovery");

Each new operation with the same transactionId will cause the previous one to be cancelled with error, if it hasn’t finished yet. If transactionId is set to null or it isn’t specified at all, the library sets unique integer transactionId to such operation.

NOTE: Do not to set integers as transactionId as they are used by the library.

Obtaining characteristics

To be able to operate on the peripheral, discovery of its services and characteristics must be run first.

//assuming peripheral is connected
await peripheral.discoverAllServicesAndCharacteristics();
List<Service> services = await peripheral.services(); //getting all services
List<Characteristic> characteristics1 = await peripheral.characteristics("F000AA00-0451-4000-B000-000000000000");
List<Characteristic> characteristics2 = await services.firstWhere(
  (service) => service.uuid == "F000AA00-0451-4000-B000-000000000000").characteristics();

//characteristics1 and characteristics2 have the same contents

Objects representing characteristics have a unique identifer, so they point to one specific characteristic, even if there are multiple service/characteristic uuid matches.

Manipulating characteristics

Below are 3 methods of writing to a characteristic, which all result in the same effect given there’s only one service with specified UUID and only one characteristic with specified UUID.

peripheral.writeCharacteristic(
  "F000AA00-0451-4000-B000-000000000000",
  "F000AA02-0451-4000-B000-000000000000",
  Uint8List.fromList([0]),
  false); //returns Characteristic to chain operations more easily

service.writeCharacteristic(
  "F000AA02-0451-4000-B000-000000000000",
  Uint8List.fromList([0]),
  false); //returns Characteristic to chain operations more easily

characteristic.write(Uint8List.fromList([0]), false); //returns void

Monitoring or reading a characteristic from Peripheral/Service level return CharacteristicWithValue object, which is Characteristic with additional Uint8List value property.

Descriptor operations

List of descriptors from a single characteristic can be obtained in a similar fashion to a list of characteristics from a single service, either from Peripheral, Service or Characteristic object. Descriptors can be read/written from Peripheral, Service or Characteristic by supplying necessary UUIDs, or from Descriptor object.

Note: to enable monitoring of characteristic you should use characteristic.monitor() or (peripheral/service).monitorCharacteristic() instead of changing the value of the underlying descriptor yourself.

Example

Demonstrates how to use the flutter_ble_lib plugin.

  • Create a flutter app, using flutter command: flutter create app BLE. Here BLE being the name of the app.
  • In the lib folder is where we store all our .dart codes.
  • Create files main.dart and sensor_tag_config.dart.
  • Create device_details, device_list, model, repository, test_scenarios, util folders in lib itself.
  • Create files inside the above folders as given below:
  • main.dart
import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ble_lib_example/devices_list/devices_bloc_provider.dart';
import 'package:flutter_ble_lib_example/devices_list/devices_list_view.dart';

import 'device_details/device_detail_view.dart';
import 'device_details/devices_details_bloc_provider.dart';

void main() {
  Fimber.plantTree(DebugTree());
  runApp(MyApp());
}

final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FlutterBleLib example',
      theme: new ThemeData(
        primaryColor: new Color(0xFF0A3D91),
        accentColor: new Color(0xFFCC0000),
      ),
      initialRoute: "/",
      routes: <String, WidgetBuilder>{
        "/": (context) => DevicesBlocProvider(child: DevicesListScreen()),
        "/details": (context) =>
            DeviceDetailsBlocProvider(child: DeviceDetailsView()),
      },
      navigatorObservers: [routeObserver],
    );
  }
}
  • sensor_tag_config.dart
abstract class SensorTagTemperatureUuids {
  static const String temperatureService =
      "F000AA00-0451-4000-B000-000000000000";
  static const String temperatureDataCharacteristic =
      "F000AA01-0451-4000-B000-000000000000";
  static const String temperatureConfigCharacteristic =
      "F000AA02-0451-4000-B000-000000000000";
  static const String characteristicUserDescriptionDescriptor =
      "00002901-0000-1000-8000-00805f9b34fb";
  static const String clientCharacteristicConfigurationDescriptor =
      "00002902-0000-1000-8000-00805f9b34fb";
}
  • Inside the device_details folder create the given files.dart and paste the given code:

  • device_details / device_detail_view.dart
import 'dart:async';

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart';
import 'package:flutter_ble_lib_example/device_details/devices_details_bloc_provider.dart';
import 'package:flutter_ble_lib_example/device_details/view/auto_test_view.dart';
import 'package:flutter_ble_lib_example/device_details/view/manual_test_view.dart';

class DeviceDetailsView extends StatefulWidget {
  @override
  State<DeviceDetailsView> createState() => DeviceDetailsViewState();
}

class DeviceDetailsViewState extends State<DeviceDetailsView> {
  DeviceDetailsBloc? _deviceDetailsBloc;
  StreamSubscription? _appStateSubscription;

  bool _shouldRunOnResume = true;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    Fimber.d("didChangeDependencies");
    if (_deviceDetailsBloc == null) {
      _deviceDetailsBloc = DeviceDetailsBlocProvider.of(context);
      if (_shouldRunOnResume) {
        _shouldRunOnResume = false;
        _onResume();
      }
    }
  }

  void _onResume() {
    Fimber.d("onResume");
    _deviceDetailsBloc?.init();
    _appStateSubscription =
        _deviceDetailsBloc?.disconnectedDevice.listen((bleDevice) async {
      Fimber.d("navigate to details");
      _onPause();
      Navigator.pop(context);
      _shouldRunOnResume = true;
      Fimber.d("back from details");
    });
  }

  void _onPause() {
    Fimber.d("onPause");
    _appStateSubscription?.cancel();
    _deviceDetailsBloc?.dispose();
  }

  @override
  void dispose() {
    Fimber.d("Dispose DeviceListScreenState");
    _onPause();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final deviceDetailsBloc = _deviceDetailsBloc;
    return WillPopScope(
      onWillPop: () {
        if (deviceDetailsBloc == null) {
          return Future<bool>.value(true);
        }
        return deviceDetailsBloc.disconnect().then((_) {
          return false;
        });
      },
      child: DefaultTabController(
        length: 2,
        child: Scaffold(
            backgroundColor: Colors.grey[300],
            appBar: AppBar(
              title: Text('Device Details'),
              bottom: TabBar(
                tabs: [
                  Tab(
                    icon: Icon(Icons.autorenew),
                    text: "Automatic",
                  ),
                  Tab(
                    icon: Icon(Icons.settings),
                    text: "Manual",
                  ),
                ],
              ),
            ),
            body: TabBarView(
              children: <Widget>[
                if (deviceDetailsBloc != null)
                  AutoTestView(deviceDetailsBloc),
                if (deviceDetailsBloc != null)
                  ManualTestView(deviceDetailsBloc),
              ],
            )),
      ),
    );
  }
}
  • device_details / device_details_bloc.dart
import 'dart:async';

import 'package:fimber/fimber.dart';
import 'package:flutter_ble_lib/flutter_ble_lib.dart';
import 'package:flutter_ble_lib_example/model/ble_device.dart';
import 'package:flutter_ble_lib_example/repository/device_repository.dart';
import 'package:flutter_ble_lib_example/test_scenarios/test_scenarios.dart';
import 'package:rxdart/rxdart.dart';

import '../model/ble_device.dart';
import '../repository/device_repository.dart';

class DeviceDetailsBloc {
  final BleManager _bleManager;
  final DeviceRepository _deviceRepository;

  late final BleDevice _bleDevice;

  BehaviorSubject<PeripheralConnectionState> _connectionStateController;

  ValueStream<PeripheralConnectionState> get connectionState =>
      _connectionStateController.stream;

  Subject<List<DebugLog>> _logsController;

  Stream<List<DebugLog>> get logs => _logsController.stream;

  Stream<Null?> get disconnectedDevice => _deviceRepository.pickedDevice
      .skipWhile((bleDevice) => bleDevice != null).cast<Null>();

  List<DebugLog> _logs = [];
  late Logger log;
  late Logger logError;

  DeviceDetailsBloc({
    DeviceRepository? deviceRepository, 
    BleManager? bleManager
  }) 
  : _deviceRepository = deviceRepository ?? DeviceRepository(),
    _bleManager = bleManager ?? BleManager(),
    _connectionStateController =
      BehaviorSubject<PeripheralConnectionState>.seeded(
        PeripheralConnectionState.disconnected
      ),
    _logsController = PublishSubject<List<DebugLog>>() {
    _bleDevice = _deviceRepository.pickedDevice.value!;

    log = (text) {
      var now = DateTime.now();
      _logs.insert(
          0,
          DebugLog(
            '${now.hour}:${now.minute}:${now.second}.${now.millisecond}',
            text,
          ));
      Fimber.d(text);
      _logsController.add(_logs);
    };

    logError = (text) {
      _logs.insert(0, DebugLog(DateTime.now().toString(), "ERROR: $text"));
      Fimber.e(text);
      _logsController.add(_logs);
    };
  }

  void init() {
    Fimber.d("init bloc");
    _bleManager.stopPeripheralScan();
  }

  Future<void> disconnect() async {
    _clearLogs();
    disconnectManual();
    return _deviceRepository.pickDevice(null);
  }

  Future<void> disconnectManual() async {
    _clearLogs();
    final bleDevice = _bleDevice;
    if (await bleDevice.peripheral.isConnected()) {
      log("DISCONNECTING...");
      await bleDevice.peripheral.disconnectOrCancelConnection();
    }
    log("Disconnected!");
  }

  void readRssi() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .testReadingRssi();
  }

  void discovery() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .discovery();
  }

  void fetchConnectedDevices() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .fetchConnectedDevice();
  }

  void fetchKnownDevices() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .fetchKnownDevice();
  }

  void readCharacteristicForPeripheral() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .readCharacteristicForPeripheral();
  }

  void readCharacteristicForService() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .readCharacteristicForService();
  }

  void readCharacteristicDirectly() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .readCharacteristic();
  }

  void writeCharacteristicForPeripheral() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .writeCharacteristicForPeripheral();
  }

  void writeCharacteristicForService() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .writeCharacteristicForService();
  }

  void writeCharacteristicDirectly() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .writeCharacteristic();
  }

  void monitorCharacteristicForPeripheral() {
    _clearLogs();
      PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
          .monitorCharacteristicForPeripheral();
  }

  void monitorCharacteristicForService() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .monitorCharacteristicForService();
  }

  void monitorCharacteristicDirectly() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .monitorCharacteristic();
  }

  void disableBluetooth() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .disableBluetooth();
  }

  void enableBluetooth() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .enableBluetooth();
  }

  void fetchBluetoothState() {
    _clearLogs();
    PeripheralTestOperations(_bleManager, _bleDevice.peripheral, log, logError)
        .fetchBluetoothState();
  }

  Future<void> connect() async {
    _clearLogs();
    var peripheral = _bleDevice.peripheral;

    peripheral
        .observeConnectionState(
            emitCurrentValue: true, completeOnDisconnect: true)
        .listen((connectionState) {
      log('Observed new connection state: \n$connectionState');
      _connectionStateController.add(connectionState);
    });

    try {
      log("Connecting to ${peripheral.name}");
      await peripheral.connect();
      log("Connected!");
    } on BleError catch (e) {
      logError(e.toString());
    }
  }

  void dispose() async {
    await _connectionStateController.drain();
    _connectionStateController.close();
  }

  void _connectTo(BleDevice bleDevice) async {
    log("Fetching log level");
    LogLevel logLevel = await _bleManager.logLevel();
    log("Current log level $logLevel");

    log("Setting log level to debug");
    await _bleManager.setLogLevel(LogLevel.debug);
    log("Set log level to debug");

    log("Fetching log level");
    logLevel = await _bleManager.logLevel();
    log("Current log level $logLevel");

    var peripheral = bleDevice.peripheral;
    peripheral
        .observeConnectionState(emitCurrentValue: true)
        .listen((connectionState) {
      log('Observed new connection state: \n$connectionState');
      _connectionStateController.add(connectionState);
    });

    SensorTagTestScenario(_bleManager, peripheral, log, logError)
        .runTestScenario();
  }

  void startAutoTest() {
    _clearLogs();

    Fimber.d("got bleDevice: $_bleDevice");
    _bleDevice.peripheral.isConnected().then((isConnected) {
      Fimber.d('The device is connected: $isConnected');
      if (!isConnected) {
        _connectTo(_bleDevice);
      }
    }).catchError((error) {
      logError('Connection problem: ${error.toString()}');
    });
  }

  void _clearLogs() {
    _logs = [];
    _logsController.add(_logs);
  }
}

class DebugLog {
  String time;
  String content;

  DebugLog(this.time, this.content);
}
  • device_details / devices_details_bloc_provider.dart
import 'package:flutter/widgets.dart';

import 'device_details_bloc.dart';

class DeviceDetailsBlocProvider extends InheritedWidget {
  final DeviceDetailsBloc _deviceDetailsBloc;

  DeviceDetailsBlocProvider({
    Key? key,
    DeviceDetailsBloc? deviceDetailsBloc,
    required Widget child,
  }) 
  : _deviceDetailsBloc = deviceDetailsBloc ?? DeviceDetailsBloc(),
    super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static DeviceDetailsBloc of(BuildContext context) => context
      .dependOnInheritedWidgetOfExactType<DeviceDetailsBlocProvider>()
      !._deviceDetailsBloc;
}
  • Inside the device_details folder create another folder named view and add the given files.dart and paste the given code:

  • device_details / view / auto_test_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart';
import 'package:flutter_ble_lib_example/device_details/view/button_view.dart';
import 'package:flutter_ble_lib_example/device_details/view/logs_container_view.dart';

class AutoTestView extends StatelessWidget {
  final DeviceDetailsBloc _deviceDetailsBloc;

  AutoTestView(this._deviceDetailsBloc);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(children: <Widget>[
        Expanded(
          flex: 1,
          child: SingleChildScrollView(
            child: _createAutoTestControlPanel(),
          ),
        ),
        Expanded(
          flex: 9,
          child: LogsContainerView(_deviceDetailsBloc.logs),
        )
      ]),
    );
  }

  Widget _createAutoTestControlPanel() {
    return Row(
      children: <Widget>[
        ButtonView("Start Auto Test", action: _startAutoTest),
      ],
    );
  }

  void _startAutoTest() {
    _deviceDetailsBloc.startAutoTest();
  }
}
  • device_details / view / button_view.dart
import 'package:flutter/material.dart';

class ButtonView extends StatelessWidget {
  final String _text;
  final void Function()? action;

  ButtonView(this._text, {this.action});

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Padding(
        padding: const EdgeInsets.all(2.0),
        child: RaisedButton(
          color: Colors.blue,
          textColor: Colors.white,
          child: Text(_text),
          onPressed: action,
        ),
      ),
    );
  }
}
  • device_details / view / logs_container_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart';

class LogsContainerView extends StatelessWidget {
  final Stream<List<DebugLog>> _logs;

  LogsContainerView(this._logs);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 8.0),
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.all(Radius.circular(4.0))),
      child: SizedBox.expand(
        child: Column(
          children: <Widget>[
            Flexible(
              child: StreamBuilder<List<DebugLog>>(
                initialData: [],
                stream: _logs,
                builder: (context, snapshot) => _buildLogs(context, snapshot),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildLogs(BuildContext context, AsyncSnapshot<List<DebugLog>> logs) {
    final data = logs.data;
    return ListView.builder(
      itemCount: data?.length,
      shrinkWrap: true,
      itemBuilder: (buildContext, index) => Container(
        decoration: BoxDecoration(
          border: Border(
            top: BorderSide(
              color: Colors.grey,
              width: 0.5,
            ),
            bottom: BorderSide(
              color: Colors.grey,
              width: 0.5,
            ),
          ),
        ),
        child: Padding(
          padding: const EdgeInsets.only(top: 2.0),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.only(right: 8.0),
                child: Text(
                  data?[index].time ?? "",
                  style: TextStyle(fontSize: 9),
                ),
              ),
              Flexible(
                child: Text(
                  data?[index].content ?? "",
                  overflow: TextOverflow.ellipsis,
                  softWrap: true,
                  style: TextStyle(fontSize: 13)
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
  • device_details / view / manual_test_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_ble_lib_example/device_details/device_details_bloc.dart';
import 'package:flutter_ble_lib_example/device_details/view/button_view.dart';
import 'package:flutter_ble_lib_example/device_details/view/logs_container_view.dart';

class ManualTestView extends StatelessWidget {
  final DeviceDetailsBloc _deviceDetailsBloc;

  ManualTestView(this._deviceDetailsBloc);

  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(children: <Widget>[
        Expanded(
          flex: 3,
          child: SingleChildScrollView(
            child: _createControlPanel(),
          ),
        ),
        Expanded(
          flex: 7,
          child: LogsContainerView(_deviceDetailsBloc.logs),
        )
      ]),
    );
  }

  void _connect() {
    _deviceDetailsBloc.connect();
  }

  void _disconnect() {
    _deviceDetailsBloc.disconnectManual();
  }

  void _readRssi() {
    _deviceDetailsBloc.readRssi();
  }

  void _discovery() {
    _deviceDetailsBloc.discovery();
  }

  void _fetchConnectedDevices() {
    _deviceDetailsBloc.fetchConnectedDevices();
  }

  void _fetchKnownDevices() {
    _deviceDetailsBloc.fetchKnownDevices();
  }

  void _readCharacteristicForPeripheral() {
    _deviceDetailsBloc.readCharacteristicForPeripheral();
  }

  void _readCharacteristicForService() {
    _deviceDetailsBloc.readCharacteristicForService();
  }

  void _readCharacteristicDirectly() {
    _deviceDetailsBloc.readCharacteristicDirectly();
  }

  void _writeCharacteristicForPeripheral() {
    _deviceDetailsBloc.writeCharacteristicForPeripheral();
  }

  void _writeCharacteristicForService() {
    _deviceDetailsBloc.writeCharacteristicForService();
  }

  void _writeCharacteristicDirectly() {
    _deviceDetailsBloc.writeCharacteristicDirectly();
  }

  void _monitorCharacteristicForPeripheral() {
    _deviceDetailsBloc.monitorCharacteristicForPeripheral();
  }

  void _monitorCharacteristicForService() {
    _deviceDetailsBloc.monitorCharacteristicForService();
  }

  void _monitorCharacteristicDirectly() {
    _deviceDetailsBloc.monitorCharacteristicDirectly();
  }

  void _disableBluetooth() {
    _deviceDetailsBloc.disableBluetooth();
  }

  void _enableBluetooth() {
    _deviceDetailsBloc.enableBluetooth();
  }

  void _fetchBluetoothState() {
    _deviceDetailsBloc.fetchBluetoothState();
  }

  Column _createControlPanel() {
    return Column(
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Connect", action: _connect),
              ButtonView("Disconnect", action: _disconnect),
              ButtonView("Connected devices", action: _fetchConnectedDevices),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Read Rssi", action: _readRssi),
              ButtonView("Request MTU"),
              ButtonView("Known devices", action: _fetchKnownDevices),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Discovery", action: _discovery),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Write to temp config via peripheral",
                  action: _writeCharacteristicForPeripheral),
              ButtonView("Read temp via peripheral",
                  action: _readCharacteristicForPeripheral),
              ButtonView("Monitor temp via peripheral",
                  action: _monitorCharacteristicForPeripheral),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Write to temp config via service",
                  action: _writeCharacteristicForService),
              ButtonView("Read temp via service",
                  action: _readCharacteristicForService),
              ButtonView("Monitor temp via service",
                  action: _monitorCharacteristicForService),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Write to temp config directly",
                  action: _writeCharacteristicDirectly),
              ButtonView("Read temp directly",
                  action: _readCharacteristicDirectly),
              ButtonView("Monitor temp directly",
                  action: _monitorCharacteristicDirectly),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Monitor temp",
                  action: _monitorCharacteristicForPeripheral),
              ButtonView("Turn on temp",
                  action: _writeCharacteristicForPeripheral),
              ButtonView("Read temp", action: _readCharacteristicForPeripheral),
            ],
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 2.0),
          child: Row(
            children: <Widget>[
              ButtonView("Enable bluetooth", action: _enableBluetooth),
              ButtonView("Disable bluetooth", action: _disableBluetooth),
              ButtonView("Fetch BT State", action: _fetchBluetoothState),
            ],
          ),
        ),
      ],
    );
  }
}
  • Inside the device_list folder create the given files.dart and paste the given code:

  • devices_lsit / devices_bloc.dart
import 'dart:async';
import 'dart:io';

import 'package:fimber/fimber.dart';
import 'package:flutter_ble_lib_example/model/ble_device.dart';
import 'package:flutter_ble_lib_example/repository/device_repository.dart';
import 'package:flutter_ble_lib/flutter_ble_lib.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rxdart/rxdart.dart';

class DevicesBloc {
  final List<BleDevice> bleDevices = <BleDevice>[];

  BehaviorSubject<List<BleDevice>> _visibleDevicesController = BehaviorSubject<List<BleDevice>>.seeded(<BleDevice>[]);

  StreamController<BleDevice> _devicePickerController = StreamController<BleDevice>();

  StreamSubscription<ScanResult>? _scanSubscription;
  StreamSubscription<BleDevice>? _devicePickerSubscription;

  ValueStream<List<BleDevice>> get visibleDevices => _visibleDevicesController.stream;

  Sink<BleDevice> get devicePicker => _devicePickerController.sink;

  final DeviceRepository _deviceRepository;
  final BleManager _bleManager;

  Stream<BleDevice> get pickedDevice => _deviceRepository.pickedDevice
      .skipWhile((bleDevice) => bleDevice == null).cast<BleDevice>();

  DevicesBloc({
    DeviceRepository? deviceRepository, 
    BleManager? bleManager
  }) 
  : _deviceRepository = deviceRepository ?? DeviceRepository(),
    _bleManager = bleManager ?? BleManager();

  void _handlePickedDevice(BleDevice bleDevice) {
    _deviceRepository.pickDevice(bleDevice);
  }

  void dispose() {
    Fimber.d("cancel _devicePickerSubscription");
    _devicePickerSubscription?.cancel();
    _visibleDevicesController.close();
    _devicePickerController.close();
    _scanSubscription?.cancel();
  }

  void init() {
    Fimber.d("Init devices bloc");
    bleDevices.clear();

    maybeCreateClient()
      .then((_) => _checkPermissions())
      .catchError((e) => Fimber.d("Permission check error", ex: e))
      .then((_) => _waitForBluetoothPoweredOn())
      .then((_) => _startScan());

    if (_visibleDevicesController.isClosed) {
      _visibleDevicesController =
          BehaviorSubject<List<BleDevice>>.seeded(<BleDevice>[]);
    }

    if (_devicePickerController.isClosed) {
      _devicePickerController = StreamController<BleDevice>();
    }

    Fimber.d(" listen to _devicePickerController.stream");
    _devicePickerSubscription = _devicePickerController.stream.listen(_handlePickedDevice);
  }

  Future<void> maybeCreateClient() async {
    Fimber.d('Checking if client exists...');
    final clientAlreadyExists = await _bleManager.isClientCreated();
    Fimber.d('Client exists: $clientAlreadyExists');

    if (clientAlreadyExists) {
      Fimber.d("Client already exists");
      return Future.value();
    }

    Fimber.d("Create client");

    return _bleManager
        .createClient(
          restoreStateIdentifier: "example-restore-state-identifier",
          restoreStateAction: (peripherals) {
            peripherals.forEach((peripheral) {
              Fimber.d("Restored peripheral: ${peripheral.name}");
            });
          }
        )
        .catchError((e) {
          return Fimber.d("Couldn't create BLE client", ex: e);
        });
  }

  Future<void> _checkPermissions() async {
    if (Platform.isAndroid) {
      var locGranted = await Permission.location.isGranted;
      if (locGranted == false) {
        locGranted = (await Permission.location.request()).isGranted;
      }
      if (locGranted == false) {
        return Future.error(Exception("Location permission not granted"));
      }
    }
  }

  Future<void> _waitForBluetoothPoweredOn() async {
    Completer completer = Completer();
    StreamSubscription<BluetoothState>? subscription;
    subscription = _bleManager
      .observeBluetoothState(emitCurrentValue: true)
      .listen((bluetoothState) async {
        if (bluetoothState == BluetoothState.POWERED_ON && !completer.isCompleted) {
          await subscription?.cancel();
          completer.complete();
        }
      });

    return completer.future;
  }

  void _startScan() {
    Fimber.d("Ble start scan");
    _scanSubscription = _bleManager.startPeripheralScan()
      .listen((scanResult) {
        var bleDevice = BleDevice(scanResult);
        if (!bleDevices.contains(bleDevice)) {
          Fimber.d('found new device ${scanResult.advertisementData.localName} ${scanResult.peripheral.identifier}');
          bleDevices.add(bleDevice);
          _visibleDevicesController.add(bleDevices.sublist(0));
        }
      });
  }

  Future<void> refresh() async {
    await _bleManager.stopPeripheralScan();
    await _scanSubscription?.cancel();
    bleDevices.clear();

    _visibleDevicesController.add(bleDevices.sublist(0));

    await _checkPermissions()
        .then((_) => _startScan())
        .catchError((e) => Fimber.d("Couldn't refresh", ex: e));
  }
}
  • devices_lsit / devices_bloc_provider.dart
import 'package:flutter/widgets.dart';
import 'package:flutter_ble_lib_example/devices_list/devices_bloc.dart';

class DevicesBlocProvider extends InheritedWidget {
  final DevicesBloc _devicesBloc;

  DevicesBlocProvider({
    Key? key,
    DevicesBloc? devicesBloc,
    required Widget child,
  })  : _devicesBloc = devicesBloc ?? DevicesBloc(),
        super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static DevicesBloc of(BuildContext context) => context
      .dependOnInheritedWidgetOfExactType<DevicesBlocProvider>()
      !._devicesBloc;
}
  • devices_lsit / devices_list_view.dart
import 'dart:async';

import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';

import 'package:flutter_ble_lib_example/model/ble_device.dart';

import 'devices_bloc.dart';
import 'devices_bloc_provider.dart';
import 'hex_painter.dart';

typedef DeviceTapListener = void Function();

class DevicesListScreen extends StatefulWidget {
  @override
  State<DevicesListScreen> createState() => DeviceListScreenState();
}

class DeviceListScreenState extends State<DevicesListScreen> {
  DevicesBloc? _devicesBloc;
  StreamSubscription<BleDevice>? _appStateSubscription;
  bool _shouldRunOnResume = true;

  @override
  void didUpdateWidget(DevicesListScreen oldWidget) {
    super.didUpdateWidget(oldWidget);
    Fimber.d("didUpdateWidget");
  }

  void _onPause() {
    Fimber.d("onPause");
    _appStateSubscription?.cancel();
    _devicesBloc?.dispose();
  }

  void _onResume() {
    Fimber.d("onResume");
    final devicesBloc = _devicesBloc;
    if (devicesBloc == null) {
      Fimber.d("onResume:: no devicesBloc present");
      return;
    }
    devicesBloc.init();
    _appStateSubscription = devicesBloc.pickedDevice.listen((bleDevice) async {
      Fimber.d("navigate to details");
      _onPause();
      await Navigator.pushNamed(context, "/details");
      setState(() {
        _shouldRunOnResume = true;
      });
      Fimber.d("back from details");
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    Fimber.d("DeviceListScreenState didChangeDependencies");
    if (_devicesBloc == null) {
      _devicesBloc = DevicesBlocProvider.of(context);
      if (_shouldRunOnResume) {
        _shouldRunOnResume = false;
        _onResume();
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    Fimber.d("build DeviceListScreenState");
    if (_shouldRunOnResume) {
      _shouldRunOnResume = false;
      _onResume();
    }
    final devicesBloc = _devicesBloc;
    if (devicesBloc == null) {
      throw Exception();
    }
    return Scaffold(
      appBar: AppBar(
        title: Text('Bluetooth devices'),
      ),
      body: StreamBuilder<List<BleDevice>>(
        initialData: devicesBloc.visibleDevices.valueWrapper?.value ?? <BleDevice>[],
        stream: devicesBloc.visibleDevices,
        builder: (context, snapshot) => RefreshIndicator(
          onRefresh: devicesBloc.refresh ,
          child: DevicesList(devicesBloc, snapshot.data),
        ),
      ),
    );
  }

  @override
  void dispose() {
    Fimber.d("Dispose DeviceListScreenState");
    _onPause();
    super.dispose();
  }

  @override
  void deactivate() {
    print("deactivate");
    super.deactivate();
  }

  @override
  void reassemble() {
    Fimber.d("reassemble");
    super.reassemble();
  }
}

class DevicesList extends ListView {
  DevicesList(DevicesBloc devicesBloc, List<BleDevice>? devices)
      : super.separated(
            separatorBuilder: (context, index) => Divider(
                  color: Colors.grey[300],
                  height: 0,
                  indent: 0,
                ),
            itemCount: devices?.length ?? 0,
            itemBuilder: (context, i) {
              Fimber.d("Build row for $i");
              return _buildRow(context, devices![i],
                  _createTapListener(devicesBloc, devices[i]));
            });

  static DeviceTapListener _createTapListener(
      DevicesBloc devicesBloc, BleDevice bleDevice) {
    return () {
      Fimber.d("clicked device: ${bleDevice.name}");
      devicesBloc.devicePicker.add(bleDevice);
    };
  }

  static Widget _buildAvatar(BuildContext context, BleDevice device) {
    switch (device.category) {
      case DeviceCategory.sensorTag:
        return CircleAvatar(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Image.asset('assets/ti_logo.png'),
            ),
            backgroundColor: Theme.of(context).accentColor);
      case DeviceCategory.hex:
        return CircleAvatar(
            child: CustomPaint(painter: HexPainter(), size: Size(20, 24)),
            backgroundColor: Colors.black);
      case DeviceCategory.other:
      default:
        return CircleAvatar(
            child: Icon(Icons.bluetooth),
            backgroundColor: Theme.of(context).primaryColor,
            foregroundColor: Colors.white);
    }
  }

  static Widget _buildRow(BuildContext context, BleDevice device,
      DeviceTapListener deviceTapListener) {
    return ListTile(
      leading: Padding(
        padding: const EdgeInsets.only(top: 8),
        child: _buildAvatar(context, device),
      ),
      title: Text(device.name),
      trailing: Padding(
        padding: const EdgeInsets.only(top: 16),
        child: Icon(Icons.chevron_right, color: Colors.grey),
      ),
      subtitle: Column(
        children: <Widget>[
          Text(
            device.id.toString(),
            style: TextStyle(fontSize: 10),
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
          )
        ],
        crossAxisAlignment: CrossAxisAlignment.start,
      ),
      onTap: deviceTapListener,
      contentPadding: EdgeInsets.fromLTRB(16, 0, 16, 12),
    );
  }
}
  • devices_lsit / hex_painter.dart
import 'package:flutter/material.dart';

class HexPainter extends CustomPainter {
  final Color _foregroundColor;
  final Color _backgroundColor;

  HexPainter({
    Color? backgroundColor,
    Color? foregroundColor,
  })  : _backgroundColor = backgroundColor ?? Colors.white,
        _foregroundColor = foregroundColor ?? Colors.black,
        super();

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint();
    drawHexagon(canvas, size, paint);
    drawButton(canvas, size, paint);
  }

  /// Draws rounded hexagon shape imitating Humon Hex device.
  void drawHexagon(Canvas canvas, Size size, Paint paint) {
    paint.color = _backgroundColor;
    paint.strokeWidth = size.width * 0.5;
    paint.strokeJoin = StrokeJoin.round;
    paint.style = PaintingStyle.stroke;
    var path = Path();
    path.addPolygon([
      Offset(size.width * 0.25, size.height * 0.375),
      Offset(size.width * 0.5, size.height * 0.25),
      Offset(size.width * 0.75, size.height * 0.375),
      Offset(size.width * 0.75, size.height * 0.625),
      Offset(size.width * 0.5, size.height * 0.75),
      Offset(size.width * 0.25, size.height * 0.625)
    ], true);
    canvas.drawPath(path, paint);
  }

  /// Draws Humon Hex button.
  void drawButton(Canvas canvas, Size size, Paint paint) {
    paint.color = _foregroundColor;
    paint.style = PaintingStyle.fill;
    canvas.drawCircle(Offset(size.width * 0.5, size.height * 0.23),
        size.height * 0.08, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}
  • Inside the model folder create the given files.dart and paste the given code:

  • model / ble_device.dart
import 'package:collection/collection.dart';
import 'package:flutter_ble_lib/flutter_ble_lib.dart';

class BleDevice {
  final Peripheral peripheral;
  final String name;
  final DeviceCategory category;

  String get id => peripheral.identifier;

  BleDevice(ScanResult scanResult)
      : peripheral = scanResult.peripheral,
        name = scanResult.name,
        category = scanResult.category;

  @override
  int get hashCode => id.hashCode;

  @override
  bool operator ==(other) =>
      other is BleDevice &&
      compareAsciiLowerCase(this.name, other.name) == 0 &&
      this.id == other.id;

  @override
  String toString() {
    return 'BleDevice{name: $name}';
  }
}

enum DeviceCategory { sensorTag, hex, other }

extension on ScanResult {
  String get name =>
      peripheral.name ?? advertisementData.localName ?? "Unknown";

  DeviceCategory get category {
    if (name == "SensorTag") {
      return DeviceCategory.sensorTag;
    } else if (name.startsWith("Hex")) {
      return DeviceCategory.hex;
    } else {
      return DeviceCategory.other;
    }
  }
}
  • Inside the repository folder create the given files.dart and paste the given code:

  • repository / device_repository.dart
import 'package:flutter_ble_lib_example/model/ble_device.dart';
import 'package:rxdart/rxdart.dart';

class MissingPickedDeviceException implements Exception {}

class DeviceRepository {
  static BleDevice? _bleDevice;
  BehaviorSubject<BleDevice?> _deviceController;

  static final DeviceRepository _deviceRepository =
      DeviceRepository._internal();

   factory DeviceRepository() {
    return _deviceRepository;
  }

  DeviceRepository._internal() 
  : _deviceController = BehaviorSubject<BleDevice?>.seeded(_bleDevice);
  

  void pickDevice(BleDevice? bleDevice) {
    _bleDevice = bleDevice;
    _deviceController.add(_bleDevice);
  }

  ValueStream<BleDevice?> get pickedDevice =>
      _deviceController.stream.shareValueSeeded(_bleDevice);

  bool get hasPickedDevice => _bleDevice != null;
}
  • Inside the test_scenarios folder create the given files.dart and paste the given code:

  • test_scenarios / base.dart
import 'package:flutter_ble_lib_example/model/ble_device.dart';
import 'package:rxdart/rxdart.dart';

class MissingPickedDeviceException implements Exception {}

class DeviceRepository {
  static BleDevice? _bleDevice;
  BehaviorSubject<BleDevice?> _deviceController;

  static final DeviceRepository _deviceRepository =
      DeviceRepository._internal();

   factory DeviceRepository() {
    return _deviceRepository;
  }

  DeviceRepository._internal() 
  : _deviceController = BehaviorSubject<BleDevice?>.seeded(_bleDevice);
  

  void pickDevice(BleDevice? bleDevice) {
    _bleDevice = bleDevice;
    _deviceController.add(_bleDevice);
  }

  ValueStream<BleDevice?> get pickedDevice =>
      _deviceController.stream.shareValueSeeded(_bleDevice);

  bool get hasPickedDevice => _bleDevice != null;
}
  • test_scenarios / bluetooth_state_toggle_scenario.dart
part of test_scenarios;

class BluetoothStateTestScenario implements TestScenario {
  StreamSubscription<BluetoothState>? _radioStateSubscription;

  @override
  Future<void> runTestScenario(Logger log, Logger errorLogger) async {
    BleManager bleManager = BleManager();

    log("SCENARIO WON'T WORK IF BLUETOOTH IS ENABLED");
    log("Waiting 10 seconds for user to turn BT off");
    await Future.delayed(Duration(seconds: 10));

    log("Creating client...");
    await bleManager.createClient();
    log("Created client");

    log("Subscribe for radio state changes");
    _observeRadioState(bleManager, log);

    log("Get radio state: ${await bleManager.bluetoothState()}");

    log("Enabling radio...");
    try {
      await bleManager.enableRadio();
    } catch (e) {
      errorLogger(e.toString());
    }

    log("Enabled radio!");

    log("Get radio state: ${await bleManager.bluetoothState()}");

    log("Waiting 10 seconds before disabling radio...");
    await Future.delayed(Duration(seconds: 10));

    log("Disabling radio...");
    await bleManager.disableRadio().catchError((error) => errorLogger(error));
    log("Disabled radio!");

    log("Destroying client");
    await _radioStateSubscription?.cancel();
    await bleManager.destroyClient();
    log("Destroyed client!");
  }

  void _observeRadioState(BleManager bleManager, Logger log) async {
    await _radioStateSubscription?.cancel();
    _radioStateSubscription =
        bleManager.observeBluetoothState().listen((newState) {
      log("New radio state: $newState");
    }, onError: (error) {
      log("Error while observing radio state. Error: $error");
    });
  }
}
  • test_scenarios / peripheral_test_operations.dart
part of test_scenarios;

typedef TestedFunction = Future<void> Function();

class PeripheralTestOperations {
  final Peripheral peripheral;
  final Logger log;
  final Logger logError;
  StreamSubscription<double>? _monitoringStreamSubscription;
  final BleManager bleManager;

  PeripheralTestOperations(
      this.bleManager, this.peripheral, this.log, this.logError);

  Future<void> connect() async {
    await _runWithErrorHandling(() async {
      log("Connecting to ${peripheral.name}");
      await peripheral.connect();
      log("Connected!");
    });
  }

  Future<void> cancelTransaction() async {
    await _runWithErrorHandling(() async {
      log("Starting operation to cancel...");
      peripheral
          .discoverAllServicesAndCharacteristics(transactionId: "test")
          .catchError((error) {
        BleError bleError = error as BleError;
        return logError("Cancelled operation caught an error: "
            "\nerror code ${bleError.errorCode.value},"
            "\nreason: ${bleError.reason}");
      });
      log("Operation to cancel started: discover all"
          " services and characteristics");

      log("Cancelling operation...");
      await bleManager.cancelTransaction("test");
      log("Operation cancelled!");
    });
  }

  Future<void> discovery() async => await _runWithErrorHandling(() async {
        await peripheral.discoverAllServicesAndCharacteristics();
        List<Service> services = await peripheral.services();
        log("PRINTING SERVICES for \n${peripheral.name}");
        services.forEach((service) => log("Found service \n${service.uuid}"));
        Service service = services.first;
        log("PRINTING CHARACTERISTICS FOR SERVICE \n${service.uuid}");

        List<Characteristic> characteristics = await service.characteristics();
        characteristics.forEach((characteristic) {
          log("${characteristic.uuid}");
        });

        log("PRINTING CHARACTERISTICS FROM \nPERIPHERAL for the same service");
        List<Characteristic> characteristicFromPeripheral =
            await peripheral.characteristics(service.uuid);
        characteristicFromPeripheral.forEach((characteristic) =>
            log("Found characteristic \n ${characteristic.uuid}"));

        //------------ descriptors
        List<Descriptor>? descriptors;

        var printDescriptors = () => descriptors?.forEach((descriptor) {
              log("Descriptor: ${descriptor.uuid}");
            });

        log("Using IR Temperature service/IR Temperature Data "
            "characteristic for following descriptor tests");
        log("PRINTING DESCRIPTORS FOR PERIPHERAL");

        descriptors = await peripheral.descriptorsForCharacteristic(
            SensorTagTemperatureUuids.temperatureService,
            SensorTagTemperatureUuids.temperatureDataCharacteristic);

        printDescriptors();
        descriptors = null;

        log("PRINTING DESCRIPTORS FOR SERVICE");
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        descriptors = await chosenService.descriptorsForCharacteristic(
            SensorTagTemperatureUuids.temperatureDataCharacteristic);

        printDescriptors();
        descriptors = null;

        List<Characteristic> temperatureCharacteristics =
            await chosenService.characteristics();
        Characteristic chosenCharacteristic = temperatureCharacteristics.first;

        log("PRINTING DESCRIPTORS FOR CHARACTERISTIC");
        descriptors = await chosenCharacteristic.descriptors();

        printDescriptors();
      });

  Future<void> testReadingRssi() async {
    await _runWithErrorHandling(() async {
      int rssi = await peripheral.rssi();
      log("rssi $rssi");
    });
  }

  Future<void> testRequestingMtu() async {
    await _runWithErrorHandling(() async {
      int requestedMtu = 79;
      log("Requesting MTU = $requestedMtu");
      int negotiatedMtu = await peripheral.requestMtu(requestedMtu);
      log("negotiated MTU $negotiatedMtu");
    });
  }

  Future<void> readCharacteristicForPeripheral() async {
    await _runWithErrorHandling(() async {
      log("Reading temperature config");
      CharacteristicWithValue readValue = await peripheral.readCharacteristic(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureDataCharacteristic);
      log("Temperature config value: \n${_convertToTemperature(readValue.value)}C");
    });
  }

  Future<void> readCharacteristicForService() async {
    await _runWithErrorHandling(() async {
      log("Reading temperature config");
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));
      CharacteristicWithValue readValue = await service.readCharacteristic(
          SensorTagTemperatureUuids.temperatureDataCharacteristic);
      log("Temperature config value: \n${_convertToTemperature(readValue.value)}C");
    });
  }

  Future<void> readCharacteristic() async {
    await _runWithErrorHandling(() async {
      log("Reading temperature config");
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));

      List<Characteristic> characteristics = await service.characteristics();
      Characteristic characteristic = characteristics.firstWhere(
          (characteristic) =>
              characteristic.uuid ==
              SensorTagTemperatureUuids.temperatureDataCharacteristic
                  .toLowerCase());

      Uint8List readValue = await characteristic.read();
      log("Temperature config value: \n${_convertToTemperature(readValue)}C");
    });
  }

  Future<void> writeCharacteristicForPeripheral() async {
    await _runWithErrorHandling(() async {
      Uint8List currentValue = await peripheral
          .readCharacteristic(SensorTagTemperatureUuids.temperatureService,
              SensorTagTemperatureUuids.temperatureConfigCharacteristic)
          .then((characteristic) => characteristic.value);

      int valueToSave;
      if (currentValue[0] == 0) {
        log("Turning on temperature update via peripheral");
        valueToSave = 1;
      } else {
        log("Turning off temperature update via peripheral");
        valueToSave = 0;
      }

      await peripheral.writeCharacteristic(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureConfigCharacteristic,
          Uint8List.fromList([valueToSave]),
          false);

      log("Written \"$valueToSave\" to temperature config");
    });
  }

  Future<void> writeCharacteristicForService() async {
    await _runWithErrorHandling(() async {
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));

      Uint8List currentValue = await service
          .readCharacteristic(
              SensorTagTemperatureUuids.temperatureConfigCharacteristic)
          .then((characteristic) => characteristic.value);

      int valueToSave;
      if (currentValue[0] == 0) {
        log("Turning on temperature update via service");
        valueToSave = 1;
      } else {
        log("Turning off temperature update via service");
        valueToSave = 0;
      }

      await service.writeCharacteristic(
          SensorTagTemperatureUuids.temperatureConfigCharacteristic,
          Uint8List.fromList([valueToSave]),
          false);

      log("Written \"$valueToSave\" to temperature config");
    });
  }

  Future<void> writeCharacteristic() async {
    await _runWithErrorHandling(() async {
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));

      List<Characteristic> characteristics = await service.characteristics();
      Characteristic characteristic = characteristics.firstWhere(
          (characteristic) =>
              characteristic.uuid ==
              SensorTagTemperatureUuids.temperatureConfigCharacteristic
                  .toLowerCase());
      Uint8List currentValue = await characteristic.read();
      int valueToSave;
      if (currentValue[0] == 0) {
        log("Turning on temperature update via characteristic");
        valueToSave = 1;
      } else {
        log("Turning off temperature update via characteristic");
        valueToSave = 0;
      }
      await characteristic.write(Uint8List.fromList([valueToSave]), false);
      log("Written \"$valueToSave\" to temperature config");
    });
  }

  Future<void> monitorCharacteristicForPeripheral() async {
    await _runWithErrorHandling(() async {
      log("Start monitoring temperature update");
      _startMonitoringTemperature(
          peripheral
              .monitorCharacteristic(
                  SensorTagTemperatureUuids.temperatureService,
                  SensorTagTemperatureUuids.temperatureDataCharacteristic,
                  transactionId: "monitor")
              .map((characteristic) => characteristic.value),
          log);
    });
  }

  Future<void> monitorCharacteristicForService() async {
    await _runWithErrorHandling(() async {
      log("Start monitoring temperature update");
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));
      _startMonitoringTemperature(
          service
              .monitorCharacteristic(
                  SensorTagTemperatureUuids.temperatureDataCharacteristic,
                  transactionId: "monitor")
              .map((characteristic) => characteristic.value),
          log);
    });
  }

  Future<void> monitorCharacteristic() async {
    await _runWithErrorHandling(() async {
      log("Start monitoring temperature update");
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));

      List<Characteristic> characteristics = await service.characteristics();
      Characteristic characteristic = characteristics.firstWhere(
          (characteristic) =>
              characteristic.uuid ==
              SensorTagTemperatureUuids.temperatureDataCharacteristic
                  .toLowerCase());

      _startMonitoringTemperature(
          characteristic.monitor(transactionId: "monitor"), log);
    });
  }

  Future<void> readWriteMonitorCharacteristicForPeripheral() async {
    await _runWithErrorHandling(() async {
      log("Test read/write/monitor characteristic on device");
      log("Start monitoring temperature");
      _startMonitoringTemperature(
        peripheral
            .monitorCharacteristic(SensorTagTemperatureUuids.temperatureService,
                SensorTagTemperatureUuids.temperatureDataCharacteristic,
                transactionId: "1")
            .map((characteristic) => characteristic.value),
        log,
      );
      log("Turning off temperature update");
      await peripheral.writeCharacteristic(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureConfigCharacteristic,
          Uint8List.fromList([0]),
          false);
      log("Turned off temperature update");

      log("Waiting one second for the reading");
      await Future.delayed(Duration(seconds: 1));

      log("Reading temperature");
      CharacteristicWithValue readValue = await peripheral.readCharacteristic(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureDataCharacteristic);
      log("Read temperature value ${_convertToTemperature(readValue.value)}C");

      log("Turning on temperature update");
      await peripheral.writeCharacteristic(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureConfigCharacteristic,
          Uint8List.fromList([1]),
          false);

      log("Turned on temperature update");

      log("Waiting 1 second for the reading");
      await Future.delayed(Duration(seconds: 1));
      log("Reading temperature");
      readValue = await peripheral.readCharacteristic(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureDataCharacteristic);
      log("Read temperature value ${_convertToTemperature(readValue.value)}C");

      log("Canceling temperature monitoring");
      await bleManager.cancelTransaction("1");
    });
  }

  Future<void> readWriteMonitorCharacteristicForService() async {
    await _runWithErrorHandling(() async {
      log("Test read/write/monitor characteristic on service");
      log("Fetching service");

      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));

      log("Start monitoring temperature");
      _startMonitoringTemperature(
        service
            .monitorCharacteristic(
                SensorTagTemperatureUuids.temperatureDataCharacteristic,
                transactionId: "2")
            .map((characteristic) => characteristic.value),
        log,
      );

      log("Turning off temperature update");
      await service.writeCharacteristic(
        SensorTagTemperatureUuids.temperatureConfigCharacteristic,
        Uint8List.fromList([0]),
        false,
      );
      log("Turned off temperature update");

      log("Waiting one second for the reading");
      await Future.delayed(Duration(seconds: 1));

      log("Reading temperature value");
      CharacteristicWithValue dataCharacteristic =
          await service.readCharacteristic(
              SensorTagTemperatureUuids.temperatureDataCharacteristic);
      log("Read temperature value ${_convertToTemperature(dataCharacteristic.value)}C");

      log("Turning on temperature update");
      await service.writeCharacteristic(
          SensorTagTemperatureUuids.temperatureConfigCharacteristic,
          Uint8List.fromList([1]),
          false);
      log("Turned on temperature update");

      log("Waiting one second for the reading");
      await Future.delayed(Duration(seconds: 1));

      log("Reading temperature value");
      dataCharacteristic = await service.readCharacteristic(
          SensorTagTemperatureUuids.temperatureDataCharacteristic);
      log("Read temperature value ${_convertToTemperature(dataCharacteristic.value)}C");
      log("Canceling temperature monitoring");
      await bleManager.cancelTransaction("2");
    });
  }

  Future<void> readWriteMonitorCharacteristic() async {
    await _runWithErrorHandling(() async {
      log("Test read/write/monitor characteristic on characteristic");

      log("Fetching service");
      Service service = await peripheral.services().then((services) =>
          services.firstWhere((service) =>
              service.uuid ==
              SensorTagTemperatureUuids.temperatureService.toLowerCase()));

      log("Fetching config characteristic");
      List<Characteristic> characteristics = await service.characteristics();
      Characteristic configCharacteristic = characteristics.firstWhere(
          (characteristic) =>
              characteristic.uuid ==
              SensorTagTemperatureUuids.temperatureConfigCharacteristic
                  .toLowerCase());
      log("Fetching data characteristic");
      Characteristic dataCharacteristic = characteristics.firstWhere(
          (characteristic) =>
              characteristic.uuid ==
              SensorTagTemperatureUuids.temperatureDataCharacteristic
                  .toLowerCase());

      log("Start monitoring temperature");
      _startMonitoringTemperature(
        dataCharacteristic.monitor(transactionId: "3"),
        log,
      );

      log("Turning off temperature update");
      await configCharacteristic.write(Uint8List.fromList([0]), false);
      log("Turned off temperature update");

      log("Waiting one second for the reading");
      await Future.delayed(Duration(seconds: 1));

      log("Reading characteristic value");
      Uint8List value = await configCharacteristic.read();
      log("Read temperature config value \n$value");

      log("Turning on temperature update");
      await configCharacteristic.write(Uint8List.fromList([1]), false);
      log("Turned on temperature update");

      log("Waiting one second for the reading");
      await Future.delayed(Duration(seconds: 1));

      log("Reading characteristic value");
      value = await configCharacteristic.read();
      log("Read temperature config value \n$value");

      log("Canceling temperature monitoring");
      await bleManager.cancelTransaction("3");
    });
  }

  Future<void> disconnect() async {
    await _runWithErrorHandling(() async {
      log("WAITING 10 SECOND BEFORE DISCONNECTING");
      await Future.delayed(Duration(seconds: 10));
      log("DISCONNECTING...");
      await peripheral.disconnectOrCancelConnection();
      log("Disconnected!");
    });
  }

  Future<void> fetchConnectedDevice() async {
    await _runWithErrorHandling(() async {
      log("Fetch connected devices with no service specified");
      List<Peripheral> peripherals = await bleManager.connectedPeripherals([]);
      peripherals.forEach((peripheral) => log("\t${peripheral.toString()}"));
      log("Device fetched");
      log("Fetch connected devices with temperature service");
      peripherals = await bleManager
          .connectedPeripherals([SensorTagTemperatureUuids.temperatureService]);
      peripherals.forEach((peripheral) => log("\t${peripheral.toString()}"));
      log("Device fetched");
    });
  }

  Future<void> fetchKnownDevice() async {
    await _runWithErrorHandling(() async {
      log("Fetch known devices with no IDs specified");
      List<Peripheral> peripherals = await bleManager.knownPeripherals([]);
      peripherals.forEach((peripheral) => log("\t${peripheral.toString()}"));
      log("Device fetched");
      log("Fetch known devices with one known device's ID specified");
      peripherals = await bleManager.knownPeripherals([peripheral.identifier]);
      peripherals.forEach((peripheral) => log("\t${peripheral.toString()}"));
      log("Device fetched");
    });
  }

  Future<void> readDescriptorForPeripheral() async =>
      _runWithErrorHandling(() async {
        log("READ DESCRIPTOR FOR PERIPHERAL");
        log("Reading value...");
        Uint8List value = await peripheral
            .readDescriptor(
              SensorTagTemperatureUuids.temperatureService,
              SensorTagTemperatureUuids.temperatureDataCharacteristic,
              SensorTagTemperatureUuids
                  .clientCharacteristicConfigurationDescriptor,
            )
            .then((descriptorWithValue) => descriptorWithValue.value);
        log("Value $value read!");
      });

  Future<void> readDescriptorForService() async =>
      _runWithErrorHandling(() async {
        log("READ DESCRIPTOR FOR SERVICE");

        log("Fetching service");
        List<Service> services = await peripheral.services();
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        log("Reading value...");
        Uint8List value = await chosenService
            .readDescriptor(
              SensorTagTemperatureUuids.temperatureDataCharacteristic,
              SensorTagTemperatureUuids
                  .clientCharacteristicConfigurationDescriptor,
            )
            .then((descriptorWithValue) => descriptorWithValue.value);
        log("Value $value read!");
      });

  Future<void> readDescriptorForCharacteristic() async =>
      _runWithErrorHandling(() async {
        log("READ DESCRIPTOR FOR CHARACTERISTIC");

        log("Fetching service");
        List<Service> services = await peripheral.services();
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        log("Fetching characteristic");
        List<Characteristic> temperatureCharacteristics =
            await chosenService.characteristics();
        Characteristic chosenCharacteristic = temperatureCharacteristics.first;

        log("Reading value...");
        Uint8List value = await chosenCharacteristic
            .readDescriptor(
              SensorTagTemperatureUuids
                  .clientCharacteristicConfigurationDescriptor,
            )
            .then((descriptorWithValue) => descriptorWithValue.value);
        log("Value $value read!");
      });

  Future<void> readDescriptor() async => _runWithErrorHandling(() async {
        log("READ DESCRIPTOR FOR DESCRIPTOR");

        log("Fetching service");
        List<Service> services = await peripheral.services();
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        log("Fetching characteristic");
        List<Characteristic> temperatureCharacteristics =
            await chosenService.characteristics();
        Characteristic chosenCharacteristic = temperatureCharacteristics.first;

        log("Fetching descriptor");
        List<Descriptor> descriptors = await chosenCharacteristic.descriptors();
        Descriptor chosenDescriptor = descriptors.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids
                .clientCharacteristicConfigurationDescriptor);

        log("Reading value...");
        Uint8List value = await chosenDescriptor.read();
        log("Value $value read!");
      });

  Future<void> writeDescriptorForPeripheral({bool enable = false}) async =>
      _runWithErrorHandling(() async {
        log("WRITE DESCRIPTOR FOR PERIPHERAL");
        log("Writing value...");
        Descriptor value = await peripheral.writeDescriptor(
          SensorTagTemperatureUuids.temperatureService,
          SensorTagTemperatureUuids.temperatureDataCharacteristic,
          SensorTagTemperatureUuids.clientCharacteristicConfigurationDescriptor,
          Uint8List.fromList([enable ? 1 : 0, 0]),
        );
        log("Descriptor $value written to!");
      });

  Future<void> writeDescriptorForService({bool enable = false}) async =>
      _runWithErrorHandling(() async {
        log("WRITE DESCRIPTOR FOR SERVICE");

        log("Fetching service");
        List<Service> services = await peripheral.services();
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        log("Writing value...");
        Descriptor value = await chosenService.writeDescriptor(
          SensorTagTemperatureUuids.temperatureDataCharacteristic,
          SensorTagTemperatureUuids.clientCharacteristicConfigurationDescriptor,
          Uint8List.fromList([enable ? 1 : 0, 0]),
        );
        log("Descriptor $value written to!");
      });

  Future<void> writeDescriptorForCharacteristic({bool enable = false}) async =>
      _runWithErrorHandling(() async {
        log("WRITE DESCRIPTOR FOR CHARACTERISTIC");

        log("Fetching service");
        List<Service> services = await peripheral.services();
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        log("Fetching characteristic");
        List<Characteristic> temperatureCharacteristics =
            await chosenService.characteristics();
        Characteristic chosenCharacteristic = temperatureCharacteristics.first;

        log("Writing value...");
        Descriptor value = await chosenCharacteristic.writeDescriptor(
          SensorTagTemperatureUuids.clientCharacteristicConfigurationDescriptor,
          Uint8List.fromList([enable ? 1 : 0, 0]),
        );
        log("Descriptor $value written to!");
      });

  Future<void> writeDescriptor({bool enable = false}) async =>
      _runWithErrorHandling(() async {
        log("WRITE DESCRIPTOR FOR DESCRIPTOR");

        log("Fetching service");
        List<Service> services = await peripheral.services();
        Service chosenService = services.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids.temperatureService.toLowerCase());

        log("Fetching characteristic");
        List<Characteristic> temperatureCharacteristics =
            await chosenService.characteristics();
        Characteristic chosenCharacteristic = temperatureCharacteristics.first;

        log("Fetching descriptor");
        List<Descriptor> descriptors = await chosenCharacteristic.descriptors();
        Descriptor chosenDescriptor = descriptors.firstWhere((elem) =>
            elem.uuid ==
            SensorTagTemperatureUuids
                .clientCharacteristicConfigurationDescriptor);

        log("Writing value...");
        await chosenDescriptor.write(
          Uint8List.fromList([enable ? 1 : 0, 0]),
        );
        log("Descriptor $chosenDescriptor written to!");
      });

  Future<void> readWriteDescriptorForPeripheral() async =>
      _runWithErrorHandling(
        () async {
          log("READ/WRITE TEST FOR PERIPHERAL");
          await readDescriptorForPeripheral();
          await writeDescriptorForPeripheral(enable: true);
          await readDescriptorForPeripheral();
          await writeDescriptorForPeripheral(enable: false);
          await readDescriptorForPeripheral();
        },
      );

  Future<void> readWriteDescriptorForService() async => _runWithErrorHandling(
        () async {
          log("READ/WRITE TEST FOR SERVICE");
          await readDescriptorForService();
          await writeDescriptorForService(enable: true);
          await readDescriptorForService();
          await writeDescriptorForService(enable: false);
          await readDescriptorForService();
        },
      );

  Future<void> readWriteDescriptorForCharacteristic() async =>
      _runWithErrorHandling(
        () async {
          log("READ/WRITE TEST FOR CHARACTERISTIC");
          await readDescriptorForCharacteristic();
          await writeDescriptorForCharacteristic(enable: true);
          await readDescriptorForCharacteristic();
          await writeDescriptorForCharacteristic(enable: false);
          await readDescriptorForCharacteristic();
        },
      );

  Future<void> readWriteDescriptor() async => _runWithErrorHandling(
        () async {
          log("READ/WRITE TEST FOR DESCRIPTOR");
          await readDescriptor();
          await writeDescriptor(enable: true);
          await readDescriptor();
          await writeDescriptor(enable: false);
          await readDescriptor();
        },
      );

  void _startMonitoringTemperature(
      Stream<Uint8List> characteristicUpdates, Function log) async {
    await _monitoringStreamSubscription?.cancel();
    _monitoringStreamSubscription =
        characteristicUpdates.map(_convertToTemperature).listen(
      (temperature) {
        log("Temperature updated: ${temperature}C");
      },
      onError: (error) {
        log("Error while monitoring characteristic \n$error");
      },
      cancelOnError: true,
    );
  }

  double _convertToTemperature(Uint8List rawTemperatureBytes) {
    const double SCALE_LSB = 0.03125;
    int rawTemp = rawTemperatureBytes[3] << 8 | rawTemperatureBytes[2];
    return ((rawTemp) >> 2) * SCALE_LSB;
  }

  Future<void> disableBluetooth() async {
    await _runWithErrorHandling(() async {
      log("Disabling radio");
      await bleManager.disableRadio();
    });
  }

  Future<void> enableBluetooth() async {
    await _runWithErrorHandling(() async {
      log("Enabling radio");
      await bleManager.enableRadio();
    });
  }

  Future<void> fetchBluetoothState() async {
    await _runWithErrorHandling(() async {
      BluetoothState bluetoothState = await bleManager.bluetoothState();
      log("Radio state: $bluetoothState");
    });
  }

  Future<void> _runWithErrorHandling(TestedFunction testedFunction) async {
    try {
      await testedFunction();
    } on BleError catch (e) {
      logError("BleError caught: ${e.errorCode.value} ${e.reason}");
    } catch (e) {
      if (e is Error) {
        debugPrintStack(stackTrace: e.stackTrace);
      }
      logError("${e.runtimeType}: $e");
    }
  }
}
  • test_scenarios / sensor_tag_test_scenario.dart
part of test_scenarios;

class SensorTagTestScenario {
  PeripheralTestOperations _peripheralTestOperations;

  SensorTagTestScenario(BleManager bleManager, Peripheral peripheral,
      Logger log, Logger logError)
  : _peripheralTestOperations =
        PeripheralTestOperations(bleManager, peripheral, log, logError);

  Future<void> runTestScenario() async {
    _peripheralTestOperations
        .connect()
        .then((_) => _peripheralTestOperations.cancelTransaction())
        .then((_) => _peripheralTestOperations.discovery())
        .then((_) => _peripheralTestOperations.testRequestingMtu())
        .then((_) => _peripheralTestOperations.testReadingRssi())
        .then((_) => _peripheralTestOperations
            .readWriteMonitorCharacteristicForPeripheral())
        .then((_) => _peripheralTestOperations
            .readWriteMonitorCharacteristicForService())
        .then((_) => _peripheralTestOperations.readWriteMonitorCharacteristic())
        .then((_) => Future.delayed(Duration(milliseconds: 100)))
        .then(
            (_) => _peripheralTestOperations.readWriteDescriptorForPeripheral())
        .then((_) => _peripheralTestOperations.readWriteDescriptorForService())
        .then((_) =>
            _peripheralTestOperations.readWriteDescriptorForCharacteristic())
        .then((_) => _peripheralTestOperations.readWriteDescriptor())
        .then((_) => _peripheralTestOperations.fetchConnectedDevice())
        .then((_) => _peripheralTestOperations.fetchKnownDevice())
        .then((_) => _peripheralTestOperations.disconnect())
        .catchError((error) => _peripheralTestOperations.logError(error));
  }
}
  • test_scenarios / sensor_tag_test_with_scan_and_connection_scenario.dart
part of test_scenarios;

class SensorTagTestWithScanAndConnectionScenario implements TestScenario {
  BleManager bleManager = BleManager();
  bool deviceConnectionAttempted = false;

  Future<void> runTestScenario(Logger log, Logger logError) async {
    log("CREATING CLIENT...");
    await bleManager.createClient(
        restoreStateIdentifier: "5",
        restoreStateAction: (devices) {
          log("RESTORED DEVICES: $devices");
        });

    log("CREATED CLIENT");
    log("STARTING SCAN...");
    log("Looking for Sensor Tag...");

    bleManager.startPeripheralScan().listen((scanResult) async {
      log("RECEIVED SCAN RESULT: "
          "\n name: ${scanResult.peripheral.name}"
          "\n identifier: ${scanResult.peripheral.identifier}"
          "\n rssi: ${scanResult.rssi}");

      if (scanResult.peripheral.name == "SensorTag" &&
          !deviceConnectionAttempted) {
        log("Sensor Tag found!");
        deviceConnectionAttempted = true;
        log("Stopping device scan...");
        await bleManager.stopPeripheralScan();
        return _tryToConnect(scanResult.peripheral, log, logError);
      }
    }, onError: (error) {
      logError(error);
    });
  }

  Future<void> _tryToConnect(
      Peripheral peripheral, Logger log, Logger logError) async {
    log("OBSERVING connection state \nfor ${peripheral.name},"
        " ${peripheral.identifier}...");

    peripheral
        .observeConnectionState(emitCurrentValue: true)
        .listen((connectionState) {
      log("Current connection state is: \n $connectionState");
      if (connectionState == PeripheralConnectionState.disconnected) {
        log("${peripheral.name} has DISCONNECTED");
      }
    });

    log("CONNECTING to ${peripheral.name}, ${peripheral.identifier}...");
    await peripheral.connect();
    log("CONNECTED to ${peripheral.name}, ${peripheral.identifier}!");
    deviceConnectionAttempted = false;

    await peripheral
        .discoverAllServicesAndCharacteristics()
        .then((_) => peripheral.services())
        .then((services) {
          log("PRINTING SERVICES for ${peripheral.name}");
          services.forEach((service) => log("Found service ${service.uuid}"));
          return services.first;
        })
        .then((service) async {
          log("PRINTING CHARACTERISTICS FOR SERVICE \n${service.uuid}");
          List<Characteristic> characteristics =
              await service.characteristics();
          characteristics.forEach((characteristic) {
            log("${characteristic.uuid}");
          });

          log("PRINTING CHARACTERISTICS FROM \nPERIPHERAL for the same service");
          return peripheral.characteristics(service.uuid);
        })
        .then((characteristics) => characteristics.forEach((characteristic) =>
            log("Found characteristic \n ${characteristic.uuid}")))
        .then((_) {
          log("WAITING 10 SECOND BEFORE DISCONNECTING");
          return Future.delayed(Duration(seconds: 10));
        })
        .then((_) {
          log("DISCONNECTING...");
          return peripheral.disconnectOrCancelConnection();
        })
        .then((_) {
          log("Disconnected!");
          log("WAITING 10 SECOND BEFORE DESTROYING CLIENT");
          return Future.delayed(Duration(seconds: 10));
        })
        .then((_) {
          log("DESTROYING client...");
          return bleManager.destroyClient();
        })
        .then((_) => log("\BleClient destroyed after a delay"));
  }
}
  • test_scenarios / test_scenarios.dart
library test_scenarios;

import 'dart:async';
import 'dart:typed_data';

import 'package:flutter/widgets.dart';
import 'package:flutter_ble_lib/flutter_ble_lib.dart';

import '../sensor_tag_config.dart';

part 'base.dart';
part 'sensor_tag_test_with_scan_and_connection_scenario.dart';
part 'bluetooth_state_toggle_scenario.dart';
part 'sensor_tag_test_scenario.dart';
part 'peripheral_test_operations.dart';
  • Inside the util folder create the given files.dart and paste the given code:

  • util / pair.dart
class Pair<T, S> {
  Pair(this.first, this.second);

  final T first;
  final S second;

  @override
  String toString() => 'Pair[$first, $second]';
}

Facilitated by Frontside

Frontside provided architectural advice and financial support for this library on behalf of Resideo.

Maintained by

This library is maintained by Polidea

License

Copyright 2019 Polidea Sp. z o.o

SHARE Effective Flutter library for all your Bluetooth Low Energy needs in Flutter

You may also like...

Leave a Reply

Your email address will not be published.

Share