diff --git a/packages/movesense_plus/CHANGELOG.md b/packages/movesense_plus/CHANGELOG.md index 50daec0a6..112662609 100644 --- a/packages/movesense_plus/CHANGELOG.md +++ b/packages/movesense_plus/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* added R-R interval to HR reading + ## 1.0.0 Initial release supporting: diff --git a/packages/movesense_plus/README.md b/packages/movesense_plus/README.md index c7165cf06..cb22b6ded 100644 --- a/packages/movesense_plus/README.md +++ b/packages/movesense_plus/README.md @@ -18,6 +18,8 @@ This plugin supports the following features, which is the most commonly used sub Similar to the [mdsflutter](https://pub.dev/packages/mdsflutter) plugin, the Movesense library needs to be installed in your app. +> **NOTE:** This plugin does not handle permission to access Bluetooth. Use the [permission_handler](https://pub.dev/packages/permission_handler) plugin to request access to scan and connect to BLE devices. See the example app. + ### iOS Install the Movesense iOS library using CocoaPods with adding this line to your app's Podfile: @@ -130,7 +132,7 @@ print('Battery level: ${battery.name}'); The following sensor data is available as streams: -* `hr` - Heart rate as an `int` at 1 Hz. +* `hr` - Heart rate as average bpm and R-R interval at 1 Hz. * `ecg` - Electrocardiography (ECG) as a sample of reading at 125 Hz. * `imu` - 9-axis Inertial Movement Unit (IMU) at 13 Hz. * `temperature` - Surface temperature of the device in Kelvin. @@ -139,8 +141,8 @@ For example, you can listen to the heart rate stream like this: ```dart // Start listening to the stream of heart rate readings -var hrSubscription = device.hr.listen((hr) { - print('Heart Rate: $hr'); +hrSubscription = device.hr.listen((hr) { + print('Heart Rate: ${hr.average}, R-R Interval: ${hr.rr} ms'); }); // Stop listening. @@ -161,15 +163,15 @@ stateSubscription = device }); ``` -> **NOTE:** Listening to system state events on a Movensense device comes with a lot of limitations. First of all, you can [only listen to one type of state events at a time](https://github.com/petri-lipponen-movesense/mdsflutter/issues/15). Second, not all Movesense devices seems to support subscription of all types of state events. For example, it seems like only the 'connectors' and 'tap' states are supported on the Movesense MD and HR2 devices. +> **NOTE:** Listening to system state events on a Movensense device comes with some limitations. First of all, you can [only listen to one type of state event at a time](https://github.com/petri-lipponen-movesense/mdsflutter/issues/15). Second, not all Movesense devices seems to support subscription of all types of state events. For example, it seems like only the 'connectors' and 'tap' states are supported on the Movesense MD and HR2 devices. ## Example App -The included example app is very simple. It shows how to connect to a device with a known `address` and once connected, it listens to the heart rate (`hr`) stream and shows it in the app. Use the floating button to (i) connect, (ii) start streaming heart rate data, and (iii) stop streaming again. +The included example app is very simple. It shows how to connect to a device with a known `address` and once connected, it listens to the heart rate (`hr`) stream and shows average bpm in the app. Use the floating button to (i) connect, (ii) start streaming heart rate data, and (iii) stop streaming again. ## Features and bugs -Please read about existing issues and file new feature requests and bug reports at the issue tracker. +Please read about existing [issues](https://github.com/carp-dk/flutter-plugins/issues) and file new feature requests and bug reports as issues. We also happily accept contributions as [pull requests](https://github.com/carp-dk/flutter-plugins/pulls). ## License diff --git a/packages/movesense_plus/example/README.md b/packages/movesense_plus/example/README.md deleted file mode 100644 index 202279b44..000000000 --- a/packages/movesense_plus/example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# movesense_flutter_example - -A new Flutter project. diff --git a/packages/movesense_plus/example/android/app/src/main/AndroidManifest.xml b/packages/movesense_plus/example/android/app/src/main/AndroidManifest.xml index d010f5063..ee75ca5a7 100644 --- a/packages/movesense_plus/example/android/app/src/main/AndroidManifest.xml +++ b/packages/movesense_plus/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + diff --git a/packages/movesense_plus/example/lib/main.dart b/packages/movesense_plus/example/lib/main.dart index bfe26bf18..fc69e0b55 100644 --- a/packages/movesense_plus/example/lib/main.dart +++ b/packages/movesense_plus/example/lib/main.dart @@ -23,11 +23,10 @@ class MovesenseHomePage extends StatefulWidget { } class MovesenseHomePageState extends State { - // My device address: 0C:8C:DC:1B:23:BF, serial 233830000816 // Replace with your Movesense device address. final MovesenseDevice device = MovesenseDevice(address: '0C:8C:DC:1B:23:BF'); bool isSampling = false; - StreamSubscription? hrSubscription; + StreamSubscription? hrSubscription; StreamSubscription? stateSubscription; @override @@ -60,10 +59,10 @@ class MovesenseHomePageState extends State { Text('Movesense [${device.address}] - ${device.status.name}'), ), const Text('Your heart rate is:'), - StreamBuilder( + StreamBuilder( stream: device.hr, builder: (context, snapshot) => Text( - snapshot.hasData ? '${snapshot.data}' : '...', + snapshot.hasData ? '${snapshot.data?.average}' : '...', style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -100,7 +99,9 @@ class MovesenseHomePageState extends State { if (!isSampling) { // Example of subscribing to heart rate data hrSubscription = device.hr.listen((hr) { - debugPrint('>> Heart Rate: $hr'); + debugPrint( + '>> Heart Rate: ${hr.average}, R-R Interval: ${hr.rr} ms', + ); }); // Example of subscribing to tap state changes diff --git a/packages/movesense_plus/example/pubspec.yaml b/packages/movesense_plus/example/pubspec.yaml index 2d5e49cff..9f4bad8ce 100644 --- a/packages/movesense_plus/example/pubspec.yaml +++ b/packages/movesense_plus/example/pubspec.yaml @@ -1,7 +1,7 @@ name: movesense_plus_example description: "An example of how to use the movesense_plus plugin." publish_to: 'none' -version: 0.1.0 +version: 1.1.0 environment: sdk: ">=3.8.0 <4.0.0" @@ -11,15 +11,10 @@ dependencies: flutter: sdk: flutter + permission_handler: ^11.0.0 movesense_plus: path: ../../movesense_plus/ - cupertino_icons: ^1.0.2 - permission_handler: ^11.0.0 - polar: ^7.0.0 - sembast: ^3.5.0 - path_provider: ^2.1.0 - mdsflutter: ^2.0.0 dev_dependencies: flutter_test: diff --git a/packages/movesense_plus/lib/movesense_data.dart b/packages/movesense_plus/lib/movesense_data.dart index e7e3051c8..f63ec7b58 100644 --- a/packages/movesense_plus/lib/movesense_data.dart +++ b/packages/movesense_plus/lib/movesense_data.dart @@ -235,6 +235,29 @@ class MovesenseState extends MovesenseData { String toString() => state.name; } +/// Heart rate (HR) reading with average BPM and latest R-R interval. +/// +/// See https://www.movesense.com/docs/esw/api_reference/#meashr +class MovesenseHR { + /// The average heart rate (BPM). + final int average; + + /// The latest R-R measurement (ms). + final int? rr; + + MovesenseHR(this.average, [this.rr]); + + factory MovesenseHR.fromMovesenseData(dynamic data) { + num average = data["Body"]["average"] as num; + // returns a list of R-R measures with only one entry (the latest) + int rr = (data["Body"]["rrData"] as List) + .map((e) => e as int) + .toList()[0]; + + return MovesenseHR(average.toInt(), rr); + } +} + /// Electrocardiogram (ECG) reading. /// /// See https://www.movesense.com/docs/esw/api_reference/#measecg diff --git a/packages/movesense_plus/lib/movesense_device.dart b/packages/movesense_plus/lib/movesense_device.dart index bff0771f9..d3056f122 100644 --- a/packages/movesense_plus/lib/movesense_device.dart +++ b/packages/movesense_plus/lib/movesense_device.dart @@ -178,16 +178,15 @@ class MovesenseDevice { return completer.future; } - /// A stream of heart rate (HR) measurements from the Movesense device. - /// Only available when the device is connected. - Stream get hr => !isConnected + /// A stream of heart rate (HR) and R-R interval measurements from the + /// Movesense device. + Stream get hr => !isConnected ? Stream.empty() : MdsAsync.subscribe(Mds.createSubscriptionUri(serial!, "/Meas/HR"), "{}") - .map((data) => (data["Body"]["average"] as num).toInt()) + .map((data) => MovesenseHR.fromMovesenseData(data)) .asBroadcastStream(); /// A stream of ECG measurements from the Movesense device collected at 125 Hz. - /// Only available when the device is connected. Stream get ecg => !isConnected ? Stream.empty() : MdsAsync.subscribe( @@ -198,7 +197,6 @@ class MovesenseDevice { .asBroadcastStream(); /// A stream of IMU measurements from the Movesense device collected at 13 Hz (lowest). - /// Only available when the device is connected. Stream get imu => !isConnected ? Stream.empty() : MdsAsync.subscribe( @@ -209,7 +207,6 @@ class MovesenseDevice { .asBroadcastStream(); /// A stream of temperature measurements from the Movesense device. - /// Only available when the device is connected. Stream get temperature => !isConnected ? Stream.empty() : MdsAsync.subscribe( @@ -233,8 +230,6 @@ class MovesenseDevice { /// /// The returned stream emits [MovesenseState] objects representing /// the state change events. - /// - /// Only available when the device is connected. Stream getStateEvents(SystemStateComponent component) => !isConnected ? Stream.empty() diff --git a/packages/movesense_plus/pubspec.yaml b/packages/movesense_plus/pubspec.yaml index bd9c59747..05c00a07c 100644 --- a/packages/movesense_plus/pubspec.yaml +++ b/packages/movesense_plus/pubspec.yaml @@ -1,6 +1,6 @@ name: movesense_plus description: "A Flutter package for Movesense sensor integration. Wraps the MDS plugin and provides easy access to Movesense device features and data streams." -version: 1.0.0 +version: 1.1.0 homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages/movesense_plus environment: diff --git a/packages/movesense_plus/test/movesense_plus_test.dart b/packages/movesense_plus/test/movesense_plus_test.dart deleted file mode 100644 index 024d7e716..000000000 --- a/packages/movesense_plus/test/movesense_plus_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -// ignore_for_file: depend_on_referenced_packages - -import 'package:flutter_test/flutter_test.dart'; -import 'package:movesense_plus/movesense_plus.dart'; - -void main() { - test('...', () { - Movesense().scan(); - }); -}