Movesense Android App Developer Guide
In this document you will learn about programming Movesense compatible applications for Android using the Movesense Mobile library (MDS). The first part of the document explains the structure of Movesense mobile library (MDS) and what components and interfaces there are to communicate with the movesense device. In the second part you can find a step by step guide for creating a simple Android app (or integrating MDS with your existing app) for accessing Movesense sensor.
Structure and use of MDS
The MDS library is aiming to be a simple android like interface for communicating with Movesense compatible devices such as the Movesense sensor. Movesense communication framework is based on a very flexible and light weight service and communication framework called Whiteboard. It enables both mobile devices and small embedded devices to publish and access REST like services within the same device as well as over communication link such as BLE. This document concentrates for the most common use case of using a mobile app as Whiteboard client to access services exposed on the Movesense sensor.
The basic interface to MDS library is the Mds object which is defined in the com.movesense.mds -package. To create it just call the built in builder:
#!java
import com.movesense.mds.Mds;
....
Mds mds = Mds.builder().build(context); // context can be e.g. Application or Activity
The Mds object has two kinds of methods: connectivity API and REST API. Since both API's are asynchronous the results are given in callback interfaces (one for each kind of API).
MDS Connectivity API
The connectivity API contains two methods: connect() and disconnect() that take the BLE address of the device as a parameter. The results of the methods are returned with a MdsConnectionListener callback class:
#!java
public void connect(String deviceAddress, final MdsConnectionListener callback);
public void disconnect(String deviceAddress);
#!java
public interface MdsConnectionListener {
/**
* Called when Mds / Whiteboard link-layer connection (BLE) has been succesfully established
*
*/
void onConnect(String macAddr);
/**
* Called when the full Mds / Whiteboard connection has been succesfully established
*
*/
void onConnectionComplete(String macAddr, String serial);
/**
* Called when Mds connect() call fails with error
*
*/
void onError(MdsException error);
/**
* Called when Mds connection disconnects (e.g. device out of range)
*
*/
void onDisconnect(String macAddr);
}
The MDS takes care of reconnecting to device in case the BLE connection drops (e.g. device goes out of range) but the application is responsible to subscribe to services (see next chapter) after connection is re-established.
NOTE: The connectivity API is still being actively improved so there will be additions and possibly other changes (such as method name changes) with the future versions of the Movesense mobile library (MDS).
MDS REST API
The MDS library exposes the REST api on the Movesense devices via the following methods:
#!java
public void get(@NonNull String uri, String contract, MdsResponseListener callback);
public void put(@NonNull String uri, String contract, MdsResponseListener callback);
public void post(@NonNull String uri, String contract, MdsResponseListener callback);
public void delete(@NonNull String uri, String contract, MdsResponseListener callback);
public MdsSubscription subscribe(@NonNull String uri, String contract, MdsNotificationListener listener);
GET, PUT, POST, DELETE
The first four of the methods are familiar from the internet REST services. The contract contains the outgoing data and URI in JSON format, and the response and possible errors are returned using the callback interface:
#!java
public interface MdsResponseListener {
/**
* Called when Mds operation has been succesfully finished
*
* @param data Response in String format
*/
void onSuccess(String data);
/**
* Called when Mds operation failed for any reason
*
* @param error Object containing the error
*/
void onError(MdsException error);
}
The onSuccess() gets the result code and the returned data as a JSON string.
Subscribe
The fifth method subscribe() is a special one in the Movesense system. Using that method the application can subscribe to receive notifications from a service that provides them (such as accelerometer in the Movesense sensor).
To subscribe to notifications one can call subscribe as follows:
#!java
MdsSubscription subscription =
mds.subscribe("suunto://MDS/EventListener",
"{\"Uri\": \"" + deviceSerialNumber + "/" + uriToSubscribeTo + "\"}",
callback); // MdsNotificationListener callback class
E.g. if the uriToSubsribeTo is set to Meas/Acc/13, the application would receive the updates from the Movesense sensor's accelerometer measurements with 13Hz sampling rate. The notifications (in JSON formatted string) and errors are returned to the callback object given in the call:
#!java
public interface MdsNotificationListener {
void onNotification(String data);
void onError(MdsException error);
}
NOTE: After the application is done with the notifications it should call the unsubscribe() methods in the MdsSubscription object that was returned from the call to subscribe().
Integrating MDS in an Android App
Here's a simple step-by-step guide that explains how to integrate MDS in your Android application. The application uses RxAndroidBle-library for easy scanning of BLE devices (in future it is possible that the MDS will contain this functionality directly). Also note that the Minimum SDK for current MDS is api level 21 (Android Lollipop).
The example code here was written on the Android Studio using the "New Project" feature (App with empty Activity), but you can do the same changes to existing project as well.
Step 0: Create a new project
Note: This is in case you want to start a new app. Feel free to skip to "Step 1" if you have a project already
First click "New Project..." in menu "File"->"New" and choose a package and app name for your new app:
Then choose Minimum SDK >= 21:
Then choose the kind of app you want (I chose "Empty Activity") and click "Finish". Android Studio will create an app for you and open the project view.
Step 1: Add Movesense mobile library (MDS) to your project
Right click the "app"-project in the project tree view and choose "Open Module Settings":
Click the "+" in the opening "Project Settings" view:
And choose the option "Import JAR / AAR package":
In the opening dialog click the "..." on the row "File Name:" and choose the mdslib-x.y.z.aar -file that you have downloaded from the movesense-mobile-lib bitbucket repository. Click "Finish" and the MDS library will be added as a part of your project:
After that open the "Module Settings" again, choose "app", click "Dependencies" tab and click the "+" in top right corner. In the list that appears, choose "Module dependency":
Choose "mdslib" from the module list and we're done.
Now the mdslib will be included as a part of our application
Step 2: Add code to create and initialize the Mds object
To use MDS in the application, we must create MDS object. To do that, add the following code lines to a suitable place in your application (e.g. MainActivity). First declare MDS object as a member:
#!java
private Mds mMds;
private void initMds() {
mMds = Mds.builder().build(this);
}
and add the call to initMds() e.g. in onCreate() method.
Step 3: Find the address of the Movesense device
To connect to a Movesense device MDS needs the BLE mac address of the device. There are two ways to get it:
- It was given to you from somewhere / by someone
- Do a BLE Scan for it
Add the following code and set it as e.g. button click handler in the UI (here the UI contains two buttons buttonScan and buttonScanStop which are made visible/invisible to make UI easy to use and a listView to contain the scan results):
#!java
Subscription mScanSubscription;
public void onScanClicked(View view) {
findViewById(R.id.buttonScan).setVisibility(View.GONE);
findViewById(R.id.buttonScanStop).setVisibility(View.VISIBLE);
// Start with empty list
mScanResArrayList.clear();
mScanResArrayAdapter.notifyDataSetChanged();
mScanSubscription = getBleClient().scanBleDevices(
new ScanSettings.Builder()
// .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // change if needed
// .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
.build()
// add filters if needed
)
.subscribe(
scanResult -> {
Log.d(LOG_TAG,"scanResult: " + scanResult);
// Process scan result here. filter movesense devices.
if (scanResult.getBleDevice()!=null &&
scanResult.getBleDevice().getName() != null &&
scanResult.getBleDevice().getName().startsWith("Movesense")) {
// replace if exists already, add otherwise
MyScanResult msr = new MyScanResult(scanResult);
if (mScanResArrayList.contains(msr))
mScanResArrayList.set(mScanResArrayList.indexOf(msr), msr);
else
mScanResArrayList.add(0, msr);
mScanResArrayAdapter.notifyDataSetChanged();
}
},
throwable -> {
Log.e(LOG_TAG,"scan error: " + throwable);
// Handle an error here.
// Re-enable scan buttons, just like with ScanStop
onScanStopClicked(null);
}
);
}
public void onScanStopClicked(View view) {
if (mScanSubscription != null)
{
mScanSubscription.unsubscribe();
mScanSubscription = null;
}
findViewById(R.id.buttonScan).setVisibility(View.VISIBLE);
findViewById(R.id.buttonScanStop).setVisibility(View.GONE);
}
and add this to the onCreate()
#!java
// Init Scan UI
mScanResultListView = (ListView)findViewById(R.id.listScanResult);
mScanResArrayAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, mScanResArrayList);
mScanResultListView.setAdapter(mScanResArrayAdapter);
mScanResultListView.setOnItemLongClickListener(this);
mScanResultListView.setOnItemClickListener(this);
and these to the class member area:
#!java
// UI
private ListView mScanResultListView;
private ArrayList<MyScanResult> mScanResArrayList = new ArrayList<>();
ArrayAdapter<MyScanResult> mScanResArrayAdapter;
Step 4: open connection to the Movesense sensor
Now that our app has the capability to find existing BLE devices, we can make tell MDS to connect to them. We call the connect-method and wait for the callbacks that tells when connection happens and when it disconnects. In this app the connect is done with LongClick on the device list. When the device has connected we mark the device on the list as "connected". Connected device can be disconnected with another LongClick.
#!java
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (position < 0 || position >= mScanResArrayList.size())
return false;
MyScanResult device = mScanResArrayList.get(position);
if (!device.isConnected()) {
RxBleDevice bleDevice = getBleClient().getBleDevice(device.macAddress);
Log.i(LOG_TAG, "Connecting to BLE device: " + bleDevice.getMacAddress());
mMds.connect(bleDevice.getMacAddress(), new MdsConnectionListener() {
@Override
public void onConnect(String s) {
Log.d(LOG_TAG, "onConnect:" + s);
}
@Override
public void onConnectionComplete(String macAddress, String serial) {
for (MyScanResult sr : mScanResArrayList) {
if (sr.macAddress.equalsIgnoreCase(macAddress)) {
sr.markConnected(serial);
break;
}
}
mScanResArrayAdapter.notifyDataSetChanged();
}
@Override
public void onError(MdsException e) {
Log.e(LOG_TAG, "onError:" + e);
showConnectionError(e);
}
@Override
public void onDisconnect(String bleAddress) {
Log.d(LOG_TAG, "onDisconnect: " + bleAddress);
for (MyScanResult sr : mScanResArrayList) {
if (bleAddress.equals(sr.macAddress))
sr.markDisconnected();
}
mScanResArrayAdapter.notifyDataSetChanged();
}
});
}
else
{
Log.i(LOG_TAG, "Disconnecting from BLE device: " + device.macAddress);
mMds.disconnect(device.macAddress);
}
return true;
}
private void showConnectionError(MdsException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle("Connection Error:")
.setMessage(e.getMessage());
builder.create().show();
}
Step 5: Use the services on the Movesense device
Now all that remains is the actual communication with the Movesense device. One basic service that exists in all Movesense devices is the Info-service which can be found in the /Info -path. To query the device info one must do a GET -request to it, which is done here and connected to a simple click on the device list:
#!java
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (position < 0 || position >= mScanResArrayList.size())
return;
MyScanResult device = mScanResArrayList.get(position);
if (!device.isConnected()) {
return;
}
String uri = SCHEME_PREFIX + device.connectedSerial + "/Info";
final Context ctx = this;
mMds.get(uri, null, new MdsResponseListener() {
@Override
public void onSuccess(String s) {
Log.i(LOG_TAG, "Device " + device.connectedSerial + " /info request succesful: " + s);
// Display info in alert dialog
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Device info:")
.setMessage(s)
.show();
}
@Override
public void onError(MdsException e) {
Log.e(LOG_TAG, "Device " + device.connectedSerial + " /info returned error: " + e);
}
});
}