Sensor data
Stream heart rate, R-R intervals, ECG, IMU and temperature data as typed Dart objects.
Each typed stream of a connected MovesenseDevice subscribes to one resource of the sensor through the Movesense mobile library (MDS). It turns the notifications of that resource into Dart objects:
| Stream | Data class | MDS resource | Rate |
|---|---|---|---|
hr | MovesenseHR | /Meas/HR | After each heartbeat |
ecg | MovesenseECG | /Meas/ECG/125 | 125 Hz |
imu | MovesenseIMU | /Meas/IMU9/13 | 13 Hz |
temperature | MovesenseTemperature | /Meas/Temp | On change |
The streams work like this:
- Each read of
hr,ecg,imuortemperaturereturns a new single-subscription stream. Listening to it opens one MDS subscription, and canceling yourStreamSubscriptioncloses it. - The plugin checks the connection when you read the property, not when you listen. A stream read before the sensor is connected emits a
StateErrorand closes. - When MDS rejects the subscription, the stream emits an
MdsErrorwith the status code from MDS. It then emits nothing more but does not close, so cancel yourStreamSubscription. - When MDS reports an error on a running subscription (Android only), the stream emits an
MdsErrorand goes on with the next notification. - A notification that is not valid JSON or not a JSON object arrives as a
FormatException, and the stream goes on with the next notification. An empty notification arrives as a reading with zero values and empty lists.
The samples on this page use a connected MovesenseDevice named device. To connect one, see Connect to a sensor. For taps and other state events, see Subscribe to state events.
Stream heart rate
hr emits a MovesenseHR for each notification from /Meas/HR, with the heart rate that the sensor computes from its ECG signal:
| Property | Type | Description |
|---|---|---|
average | double | Average heart rate in beats per minute, or 0.0 when the sensor has no valid heart rate. |
rrData | List<int> | The R-R intervals since the previous notification, in milliseconds. It may be empty. |
rr | int? | The latest R-R interval in milliseconds: the last item of rrData, or null when rrData is empty. |
An R-R interval is the time between two consecutive heartbeats, measured between the R peaks of the ECG. One notification may hold several, so use rrData, not rr, when you need every interval:
Stream ECG
ecg emits a MovesenseECG for each notification from /Meas/ECG/125, with a batch of single-channel ECG samples at 125 Hz:
| Property | Type | Description |
|---|---|---|
timestamp | int | The sensor's timestamp of the batch, in milliseconds. See Timestamps. |
samples | List<int> | The raw ECG samples, not scaled. |
The Movesense API reference defines one unit of a raw sample as 0.38147 µV. To get microvolts, multiply each sample by this value:
/Meas/ECG/Info reports the number of samples in a notification as ArraySize. See Use other sample rates for a request that reads this resource.
Stream IMU data
imu emits a MovesenseIMU for each notification from /Meas/IMU9/13. Each one holds a batch of synchronized samples from the three sensors of the inertial measurement unit (IMU), at 13 Hz, the lowest IMU rate:
| Property | Type | Description |
|---|---|---|
timestamp | int | The sensor's timestamp of the first sample in the batch, in milliseconds. The other samples follow at the sample rate. |
accelerometer | List<MovesenseAccelerometerSample> | Acceleration in m/s², including gravity. |
gyroscope | List<MovesenseGyroscopeSample> | Angular velocity in degrees per second. |
magnetometer | List<MovesenseMagnetometerSample> | Magnetic field in microtesla (µT). |
Each sample has the fields x, y and z, of type double. Because the accelerometer includes gravity, the acceleration of a sensor at rest has a magnitude of about 9.8 m/s²:
Stream temperature
temperature emits a MovesenseTemperature for each notification from /Meas/Temp, which measures the temperature inside the sensor. The sensor sends a notification when the temperature changes:
| Property | Type | Description |
|---|---|---|
timestamp | int | The sensor's timestamp of the reading, in milliseconds. |
measurement | double | Temperature in kelvin. |
celsius | double | Temperature in degrees Celsius, computed from measurement. |
The plugin documents its temperature stream for the Movesense MD only. Movesense lists a temperature sensor for the Movesense Active HR+ as well, and none for the Movesense HR2. To listen only on a Movesense MD, check deviceType first:
deviceType comes from the device information, and it is unknown when the plugin fails to read that information. See Identify the model.
To read the temperature once, send a GET request to /Meas/Temp. Pass the Content object of the response to MovesenseTemperature.fromMovesenseData(), not the whole response, which it reads as 0 K:
Use other sample rates
The typed streams use fixed sample rates. For another rate, subscribe to the resource with device.subscribe(), and parse each notification with MovesenseECG.fromMovesenseData() or MovesenseIMU.fromMovesenseData(). This example streams ECG at 256 Hz:
For IMU data, subscribe to /Meas/IMU9/<sample_rate>, and parse with MovesenseIMU.fromMovesenseData() in the same way. For the raw stream that device.subscribe() returns, see Subscribe to a resource.
The sensor lists the rates it supports in two info resources. The example responses in the Movesense API reference hold these rates:
| Resource | Field | Example rates |
|---|---|---|
/Meas/ECG/Info | AvailableSampleRates | 125, 128, 200, 250, 256, 500 and 512 Hz |
/Meas/IMU/Info | SampleRates | 13, 26, 52, 104, 208, 416, 833 and 1666 Hz |
Read the rates of your sensor with GET requests:
Higher rates send more data over Bluetooth Low Energy (BLE), and the BLE bandwidth may limit the highest rates.
ECG above 256 Hz
Over BLE, ECG rates above 256 Hz may overflow the small sample buffer of the sensor's ECG circuit, so samples may be lost. Use 256 Hz or lower, as the Movesense API reference advises.
The sensor does not allow ECG subscriptions at different rates at the same time. While another ECG rate is subscribed, do not listen to ecg. Several subscriptions at the same rate are allowed.
Movesense device library 2.3 added /Meas/ECG/<sample_rate>/mV, which reports the samples in millivolts as decimal numbers. MovesenseECG.fromMovesenseData() reads samples as integers and cuts off the decimals, so it does not fit this resource: decode its notifications yourself.
Stop a stream
listen() returns a StreamSubscription. Cancel it to stop the data and close the MDS subscription:
A subscribed stream stays open until you cancel it, even after the sensor disconnects. An await for loop over it does not end by itself.
To stream across reconnections, listen to statusEvents before you call connect(). Then listen to a new stream each time the status becomes connected, and cancel it when the status changes:
For how the sensor reconnects on each platform, see Handle a lost connection.
Timestamps
MovesenseECG, MovesenseIMU and MovesenseTemperature extend MovesenseData. Its timestamp comes from the sensor's own clock: the milliseconds since the sensor started or was reset. This clock does not give the time of day, and MovesenseHR has no timestamp.
To place data in time, pair the sensor clock with the phone's clock. This example takes the phone's time when the first ECG batch arrives, and places later batches by their sensor timestamps:
The arrival time of the first batch includes the Bluetooth delay, so all times are approximate. The intervals between them come from the sensor clock. For MovesenseHR, record DateTime.now() when each reading arrives.
The sensor can also map its clock to Coordinated Universal Time (UTC). A GET request to /Time/Detailed returns utcTime and relativeTime for the same moment. utcTime is the sensor's UTC time in microseconds since 1970, and relativeTime is the sensor clock in milliseconds.
utcTime is only as correct as the sensor's UTC clock. Until a PUT request to /Time sets that clock, it counts from 2015-01-01 or an earlier date, not from the real time. For details, see the Movesense API reference.