esense_flutter 0.2.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 82

eSense Flutter #

This plugin supports the eSense earable computing platform on both Android and iOS.

pub package

Install (Flutter) #

Add esense_flutter as a dependency in pubspec.yaml. For help on adding as a dependency, view the pubspec documenation.

AndroidX support #

Only for Android API level 28

Update the contents of the android/gradle.properties file with the following:

android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536M

Next, add the following dependencies to your android/build.gradle file:

dependencies {
  classpath 'com.android.tools.build:gradle:3.3.0'
  classpath 'com.google.gms:google-services:4.2.0'
} 

And finally, set the Android compile- and minimum SDK versions to compileSdkVersion 28, and minSdkVersion 23 respectively, inside the android/app/build.gradle file.

Android Permissions #

The package uses your location and bluetooth to fetch data from the eSense ear plugs. Therefore location tracking and bluetooth must be enabled.

Add the following entry to your manifest.xml file, in the Android project of your application:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

In addition, your minimum SDK version should be 23.

iOS #

Requires iOS 10 or later. Hence, in your Podfile in the ios folder of your app, make sure that the platform is set to 10.0.

platform :ios, '10.0'

Add this permission in the Info.plist file located in ios/Runner:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>Uses bluetooth to connect to the eSense device</string>
<key>UIBackgroundModes</key>
  <array>
  <string>audio</string>
  <string>external-accessory</string>
  <string>fetch</string>
</array>

Usage #

The eSense Flutter plugin has been designed to resemble the Android eSense API almost 1:1. Hence, you should be able to recognize the names of the different classes and class variables.
For example, the methods on the ESenseManager class is mapped 1:1. See the eSense Android documentation on how it all works.

However, one major design change has been done; this eSense Flutter plugin complies to the Dart/Flutter reactive programming architecture using Streams. Hence, you do not 'add listerners' to an eSense device (as you do in Java) -- rather, you obtain a Dart stream and listen to this stream (and exploit all the other very nice stream operations which are available in Dart). Below, we shall describe how to use the eSense streams. But first -- let's see how to set up and connect to an eSense device in the first place.

Note that playing and recording audio are performed via the Bluetooth Classic interface and are not supported by the eSense library described here.

Setting up and Connecting to an eSense Device #

All operations on the eSense device happens via the ESenseManager. When connecting, specify the name of the device (typically on the form eSense-xxxx).

import 'package:esense/esense.dart';

...

// first listen to connection events before trying to connect
ESenseManager.connectionEvents.listen((event) {
  print('CONNECTION event: $event');
}

// try to connect to the eSense device with a given name
bool success = await ESenseManager.connect(eSenseName);

Everything with the eSense API happens asynchronously. Hence, the connect call merely initiates the connection process. In order to know the status of the connection process (successful or not), you should listen to connection events (ConnectionEvent). This is done via the connectionEvents stream. Note, that if you want to know if your connection to the device is successful, you should initiate listening before the connection is initiated, as shown above.

Listen to Sensor Events #

You can access a stream of SensorEvent events via the ESenseManager.sensorEvents stream. Sampling rate can be set when not listening.

StreamSubscription subscription = ESenseManager.sensorEvents.listen((event) {
  print('SENSOR event: $event'
});

...

subscription.cancel();
ESenseManager.setSamplingRate(5);

... 

subscription = ESenseManager.sensorEvents.listen((event) {
  print('SENSOR event: $event');
});

Read eSense Device Events #

Reading properties of the eSense device happens asynchronously. Hence, in order to obtain properties, you should do two things:

  1. listen to the ESenseManager.eSenseEvents stream
  2. invoke read operation on the ESenseManager

Invoking read operations will trigger ESenseEvent events of various kinds.

// set up a event listener
ESenseManager.eSenseEvents.listen((event) {
  print('ESENSE event: $event');
}

// now invoke read operations on the manager
ESenseManager.getDeviceName();

When the button on the eSense device is pressed, the eSenseEvents stream will send an ButtonEventChanged event.

Change the Configuration of the eSense Device #

The ESenseManager exposes methods to change the configuration of the eSense device. With the plugin, you can change the device name using setDeviceName(), change the advertising and connection interval using setAdvertisementAndConnectiontInterval(), and change the IMU sensor configuration using setSensorConfig().

Note: At the time of writing, the setSensorConfig() method is not implemented.

Limitations in the eSense BTLE interface #

Note that there is a limitation to the eSense BTLE interface which implie that you should not invoke methods on the ESenseManager in a fast pace after each other. For example, the following code will not work:

// set up a event listener
ESenseManager.eSenseEvents.listen((event) {
  print('ESENSE event: $event');
}

// now invoke read operations on the manager
// THIS WILL NOT WORK!
ESenseManager.getDeviceName();
ESenseManager.getAccelerometerOffset();
ESenseManager.getAdvertisementAndConnectionInterval();

In this case, the first operation (listening to the Esense Events) will succeed - the rest will fail. In the example app, this has been fixed by adding delays to method call, like;

// get the battery level every 10 secs
Timer.periodic(Duration(seconds: 10), (timer) async => await ESenseManager.getBatteryVoltage());

// wait 2, 3, 4, 5, ... secs before getting the name, offset, etc.
// it seems like the eSense BTLE interface does NOT like to get called
// several times in a row -- hence, delays are added in the following calls
Timer(Duration(seconds: 2), () async => await ESenseManager.getDeviceName());
Timer(Duration(seconds: 3), () async => await ESenseManager.getAccelerometerOffset());
Timer(Duration(seconds: 4), () async => await ESenseManager.getAdvertisementAndConnectionInterval());
Timer(Duration(seconds: 5), () async => await ESenseManager.getSensorConfig());

Authors #

Getting Started with Flutter #

For help getting started with Flutter, view the online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

0.2.1 #

  • updated Swift implementation to version 5

0.2.0+2 #

  • update to readme on iOS
  • added bluetooth permission to Info.plist

0.2.0+1 #

  • small update to documentation

0.2.0 #

  • support for iOS implemented
  • improved example app
  • improvement to documentation

0.1.3 #

  • sampling rate can be set before connecting to device.

0.1.2 #

  • small improvement to the example app (still not very good though...)

0.1.1+1 #

  • update of readme documentation on API.

0.1.0 #

  • Initial release supporting 1st released version of the Android eSense library, dated April 2019.

example/lib/main.dart

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

import 'package:flutter/services.dart';
import 'package:esense_flutter/esense.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _deviceName = 'Unknown';
  double _voltage = -1;
  String _deviceStatus = '';
  bool sampling = false;
  String _event = '';
  String _button = 'not pressed';

  // the name of the eSense device to connect to -- change this to your own device.
  String eSenseName = 'eSense-0332';

  @override
  void initState() {
    super.initState();
    _connectToESense();
  }

  Future<void> _connectToESense() async {
    bool con = false;

    // if you want to get the connection events when connecting, set up the listener BEFORE connecting...
    ESenseManager.connectionEvents.listen((event) {
      print('CONNECTION event: $event');

      // when we're connected to the eSense device, we can start listening to events from it
      if (event.type == ConnectionType.connected) _listenToESenseEvents();

      setState(() {
        switch (event.type) {
          case ConnectionType.connected:
            _deviceStatus = 'connected';
            break;
          case ConnectionType.unknown:
            _deviceStatus = 'unknown';
            break;
          case ConnectionType.disconnected:
            _deviceStatus = 'disconnected';
            break;
          case ConnectionType.device_found:
            _deviceStatus = 'device_found';
            break;
          case ConnectionType.device_not_found:
            _deviceStatus = 'device_not_found';
            break;
        }
      });
    });

    con = await ESenseManager.connect(eSenseName);

    setState(() {
      _deviceStatus = con ? 'connecting' : 'connection failed';
    });
  }

  void _listenToESenseEvents() async {
    ESenseManager.eSenseEvents.listen((event) {
      print('ESENSE event: $event');

      setState(() {
        switch (event.runtimeType) {
          case DeviceNameRead:
            _deviceName = (event as DeviceNameRead).deviceName;
            break;
          case BatteryRead:
            _voltage = (event as BatteryRead).voltage;
            break;
          case ButtonEventChanged:
            _button = (event as ButtonEventChanged).pressed ? 'pressed' : 'not pressed';
            break;
          case AccelerometerOffsetRead:
            // TODO
            break;
          case AdvertisementAndConnectionIntervalRead:
            // TODO
            break;
          case SensorConfigRead:
            // TODO
            break;
        }
      });
    });

    _getESenseProperties();
  }

  void _getESenseProperties() async {
    // get the battery level every 10 secs
    Timer.periodic(Duration(seconds: 10), (timer) async => await ESenseManager.getBatteryVoltage());

    // wait 2, 3, 4, 5, ... secs before getting the name, offset, etc.
    // it seems like the eSense BTLE interface does NOT like to get called
    // several times in a row -- hence, delays are added in the following calls
    Timer(Duration(seconds: 2), () async => await ESenseManager.getDeviceName());
    Timer(Duration(seconds: 3), () async => await ESenseManager.getAccelerometerOffset());
    Timer(Duration(seconds: 4), () async => await ESenseManager.getAdvertisementAndConnectionInterval());
    Timer(Duration(seconds: 5), () async => await ESenseManager.getSensorConfig());
  }

  StreamSubscription subscription;
  void _startListenToSensorEvents() async {
    // subscribe to sensor event from the eSense device
    subscription = ESenseManager.sensorEvents.listen((event) {
      print('SENSOR event: $event');
      setState(() {
        _event = event.toString();
      });
    });
    setState(() {
      sampling = true;
    });
  }

  void _pauseListenToSensorEvents() async {
    subscription.cancel();
    setState(() {
      sampling = false;
    });
  }

  void dispose() {
    _pauseListenToSensorEvents();
    ESenseManager.disconnect();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('eSense Demo App'),
        ),
        body: Align(
          alignment: Alignment.topLeft,
          child: ListView(
            children: [
              Text('eSense Device Status: \t$_deviceStatus'),
              Text('eSense Device Name: \t$_deviceName'),
              Text('eSense Battery Level: \t$_voltage'),
              Text('eSense Button Event: \t$_button'),
              Text(''),
              Text('$_event'),
            ],
          ),
        ),
        floatingActionButton: new FloatingActionButton(
          // a floating button that starts/stops listening to sensor events.
          // is disabled until we're connected to the device.
          onPressed:
              (!ESenseManager.connected) ? null : (!sampling) ? _startListenToSensorEvents : _pauseListenToSensorEvents,
          tooltip: 'Listen to eSense sensors',
          child: (!sampling) ? Icon(Icons.play_arrow) : Icon(Icons.pause),
        ),
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  esense_flutter: ^0.2.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:esense_flutter/esense.dart';
import 'package:esense_flutter/esense_config.dart';
import 'package:esense_flutter/esense_events.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
64
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
82
Learn more about scoring.

We analyzed this package on Apr 3, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6
  • Flutter: 1.12.13+hotfix.8

Health suggestions

Fix lib/esense.dart. (-0.50 points)

Analysis of lib/esense.dart reported 1 hint:

line 12 col 8: Unused import: 'dart:io'.

Format lib/esense_events.dart.

Run flutter format to format lib/esense_events.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test