Skip to content
CARP Docs
Ask AISystemLightDark
On this page

MDS requests

Send requests and subscriptions to any resource of the Movesense API through MDS.

Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor

The Movesense mobile library (MDS) gives access to every resource of a Movesense sensor in a REST style. You send a GET, PUT, POST or DELETE request to a resource path, or subscribe to the resource for notifications. Use MDS requests for what the typed API of MovesenseDevice does not cover, such as the LED, the sensor's clock or other sample rates.

The Movesense API reference lists the resources with their methods, parameters and data. The samples on this page use a connected MovesenseDevice named device and a Movesense client named movesense.

Send a request

device.request() sends one request to a resource of the sensor and completes with an MdsResponse:

Future<MdsResponse> request(
  MdsRequestMethod method,
  String resource, {
  String contract = '{}',
})

It takes these parameters:

ParameterTypeRequiredDefaultDescription
methodMdsRequestMethodYes—The request method, such as MdsRequestMethod.get.
resourceStringYes—The resource path, such as /Info. It must start with a slash.
contractStringNo'{}'The parameters of the request, as a JSON object.

The contract is a JSON object with the parameters of the request, named as in the Movesense API reference. Leave it out when the request takes no parameters: it defaults to the empty object '{}'.

MdsRequestMethod has one value for each MDS method:

ValueUse
getRead a resource.
putWrite a value, which replaces the previous value.
postSend a new record to the sensor.
deleteRemove data from the sensor.

The response has these properties:

PropertyTypeDescription
bodyStringThe response body as a JSON string, or an empty string when the response has no body.
statusCodeintThe HTTP-like status code from MDS, such as 200.

The body of a GET response is a JSON object whose Content holds the value of the resource. Decode it with jsonDecode():

import 'dart:convert';

final response = await device.request(MdsRequestMethod.get, '/Info');
final info = jsonDecode(response.body)['Content'];
print('Firmware ${info['sw']}, hardware type ${info['hw']}');

To read /Info as a typed MovesenseDeviceInfo, see Read the device information.

A PUT request takes the new value in its contract. This example turns on the LED of the sensor:

// Turn the LED on.
await device.request(MdsRequestMethod.put, '/Component/Led',
    contract: '{"isOn": true}');

The Movesense API reference marks /Component/Led as deprecated and documents /Component/Leds/<led_index> instead, where the sensor's only LED has the index 0.

Subscribe to a resource

device.subscribe() subscribes to a resource of the sensor and returns a stream of the raw JSON notification bodies, as strings. This example streams the accelerometer at 52 Hz:

// Accelerometer at 52 Hz, as raw JSON notification bodies.
device.subscribe('/Meas/Acc/52').listen(print);

The stream is single-subscription. Listening to it opens the MDS subscription, and canceling your StreamSubscription closes it. Each call to subscribe() returns a new stream with its own MDS subscription, which stays open until you cancel it.

Each notification is a JSON object that holds the data in Body. Decode it with jsonDecode():

import 'dart:convert';

device.subscribe('/Meas/Acc/52').listen((body) {
  final data = jsonDecode(body)['Body'];
  print('${data['Timestamp']}: ${data['ArrayAcc']}');
});

subscribe() also takes a named contract parameter for subscription parameters, which defaults to '{}'. Do not put the resource in the contract. On Android, the plugin adds the subscription URI to the contract as Uri itself, and on iOS, it passes the URI to MDS separately.

MovesenseHR, MovesenseECG, MovesenseIMU and MovesenseTemperature each have a fromMovesenseData() factory that parses a decoded notification. MovesenseState.fromMovesenseData() takes the MovesenseSystemState component first, then the decoded notification.

To parse ECG or inertial measurement unit (IMU) notifications into typed objects, see Use other sample rates.

Use the client without a device

The Movesense client sends requests and subscriptions without a MovesenseDevice. Its request() and subscribe() take a full URI that names the sensor by its serial number, not a resource path. Build the URIs with the static methods of Mds:

MethodURIBuilt withExample
movesense.request()suunto://<serial><resource>Mds.createRequestUri(serial, resource)suunto://233830000816/Info
movesense.subscribe()<serial><resource>Mds.createSubscriptionUri(serial, resource)233830000816/Meas/HR

MovesenseDevice builds the same URIs from its own serial number. This example sends a GET request and subscribes to the heart rate through the client:

final response = await movesense.request(
    MdsRequestMethod.get, Mds.createRequestUri(serial, '/Info'));
print(response.body);

movesense.subscribe(Mds.createSubscriptionUri(serial, '/Meas/HR')).listen(print);

To get typed data from the client, decode each notification and pass it to a fromMovesenseData() factory:

import 'dart:convert';

movesense
    .subscribe(Mds.createSubscriptionUri(serial, '/Meas/HR'))
    .map((body) => MovesenseHR.fromMovesenseData(jsonDecode(body)))
    .listen((hr) => print('${hr.average} bpm'));

A MovesenseDevice holds the serial number in serial while it is connected. Without a device, take the serial number from the connected event on movesense.connectionEvents:

final connected = movesense.connectionEvents.firstWhere(
  (event) => event.type == MovesenseConnectionEventType.connected,
);
await movesense.connect(address);
final serial = (await connected).serial!;

This sample waits for the next sensor that connects. With several sensors, also compare event.address, and add a timeout in case the connection fails. For connection events, see Follow all connections.

For code written for mdsflutter, see Migrate from mdsflutter.

Handle errors

Requests and subscriptions report failures in these ways:

FailureResult
device.request() while the sensor is not connectedThe Future fails with a StateError.
A MovesenseDevice stream created while the sensor is not connectedThe stream emits a StateError and closes.
MDS rejects a requestThe Future fails with an MdsError that holds the status code from MDS.
MDS rejects a subscriptionThe stream emits an MdsError that holds the status code from MDS, then nothing more. It does not close, so cancel your StreamSubscription.
MDS reports an error on a running subscription (Android only)The stream emits an MdsError that holds the status code from MDS and goes on with the next notification.
The request or subscription fails on the phoneAn MdsError with status 0, from the Future or on the stream.
A notification on a typed stream is not valid JSON or not a JSON objectThe stream emits a FormatException and goes on with the next notification.
A notification on a typed stream is emptyThe stream emits a reading with zero values and empty lists.

MdsError holds the status code in status and a readable reason in error. Status 0 means that MDS gave no status code. Failures on the phone have this status, for example:

  • The contract is not a JSON object. On Android, only subscriptions check the contract; requests pass it to MDS unchanged.
  • The app runs on macOS, where MDS is not available.

The sensor may not keep up when it streams several high-rate resources at once, such as heart rate, ECG and IMU data. A subscription may then fail with status 408. If this happens, subscribe to fewer resources at once.

To avoid the StateError, check device.isConnected before you send a request. Catch MdsError around the request:

try {
  final response = await device.request(MdsRequestMethod.get, '/Meas/Temp');
  print(response.body);
} on MdsError catch (error) {
  print('MDS error ${error.status}: ${error.error}');
}

On a stream, handle errors in onError. With cancelOnError: true, the first error cancels the subscription, which also closes the MDS subscription:

device.subscribe('/Meas/Temp').listen(
  print,
  onError: (Object error) => print('Subscription failed: $error'),
  cancelOnError: true,
);

Quick links

Page · DocumentationCARP DocumentationPage · DocumentationIntroductionPage · DocumentationQuickstartPage · DocumentationTerminologyPage · DocumentationArchitecture