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: pic_1.png

Then choose Minimum SDK >= 21: pic_2.png

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": pic_3.png

Click the "+" in the opening "Project Settings" view: pic_4.png

And choose the option "Import JAR / AAR package": pic_5.png

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:

pic_6.png

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":

pic_7.png

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:

  1. It was given to you from somewhere / by someone
  2. 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);
            }
        });
    }