Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/movesense_plus/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.0

* added R-R interval to HR reading

## 1.0.0

Initial release supporting:
Expand Down
14 changes: 8 additions & 6 deletions packages/movesense_plus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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

Expand Down
3 changes: 0 additions & 3 deletions packages/movesense_plus/example/README.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />

Expand Down
11 changes: 6 additions & 5 deletions packages/movesense_plus/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ class MovesenseHomePage extends StatefulWidget {
}

class MovesenseHomePageState extends State<MovesenseHomePage> {
// 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<int>? hrSubscription;
StreamSubscription<MovesenseHR>? hrSubscription;
StreamSubscription<MovesenseState>? stateSubscription;

@override
Expand Down Expand Up @@ -60,10 +59,10 @@ class MovesenseHomePageState extends State<MovesenseHomePage> {
Text('Movesense [${device.address}] - ${device.status.name}'),
),
const Text('Your heart rate is:'),
StreamBuilder<int>(
StreamBuilder<MovesenseHR>(
stream: device.hr,
builder: (context, snapshot) => Text(
snapshot.hasData ? '${snapshot.data}' : '...',
snapshot.hasData ? '${snapshot.data?.average}' : '...',
style: Theme.of(context).textTheme.headlineMedium,
),
),
Expand Down Expand Up @@ -100,7 +99,9 @@ class MovesenseHomePageState extends State<MovesenseHomePage> {
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
Expand Down
9 changes: 2 additions & 7 deletions packages/movesense_plus/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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:
Expand Down
23 changes: 23 additions & 0 deletions packages/movesense_plus/lib/movesense_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<dynamic>)
.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
Expand Down
13 changes: 4 additions & 9 deletions packages/movesense_plus/lib/movesense_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> get hr => !isConnected
/// A stream of heart rate (HR) and R-R interval measurements from the
/// Movesense device.
Stream<MovesenseHR> 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<MovesenseECG> get ecg => !isConnected
? Stream.empty()
: MdsAsync.subscribe(
Expand All @@ -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<MovesenseIMU> get imu => !isConnected
? Stream.empty()
: MdsAsync.subscribe(
Expand All @@ -209,7 +207,6 @@ class MovesenseDevice {
.asBroadcastStream();

/// A stream of temperature measurements from the Movesense device.
/// Only available when the device is connected.
Stream<MovesenseTemperature> get temperature => !isConnected
? Stream.empty()
: MdsAsync.subscribe(
Expand All @@ -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<MovesenseState> getStateEvents(SystemStateComponent component) =>
!isConnected
? Stream.empty()
Expand Down
2 changes: 1 addition & 1 deletion packages/movesense_plus/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
10 changes: 0 additions & 10 deletions packages/movesense_plus/test/movesense_plus_test.dart

This file was deleted.