Filter results by

Introducing SAMI to Your Bluetooth Low Energy Devices

This blog post is obsoleted by Build an Android app with BLE and ARTIK Cloud.

Today we’re developing an Android app that illustrates how well SAMI and Bluetooth low energy (BLE) work together to get device data. Our Android app locates nearby BLE devices, collects their data and then sends it to SAMI.

This follows up on our series “Developing With SAMI”: Part 1, Part 2 and Part 3, which discusses the fundamentals of SAMI app development.

Introduction

Many health and fitness sensors today use Bluetooth low energy (BLE) technologies. With this in mind, I want to develop an Android application that scans and connects to nearby BLE devices, collects relevant measurements from them, and sends this data to SAMI.

I can see how SAMI is useful to BLE devices. I can use SAMI as backend storage for the data from such devices. Once in SAMI, the data is useful in many ways. For example, I can view it in near-real-time in the User Portal, or my app and other apps can retrieve the data. SAMI, as a data exchange broker, gives developers the opportunities to use the data from diverse devices to its fullest.

In this article, I won’t be discussing SAMI’s data exchange functionality, which I explored in Developing With SAMI. Instead, I want to focus on the first step toward data exchange: Collecting data from a BLE device, and then sending it to SAMI.

I’ll be using Heart Rate Measurement from a Polar H7 heart rate sensor as an example. But it is straightforward to modify my app to collect other Bluetooth measurements too.

Demo: Collect data from BLE and view in SAMI

Before we get started, here’s a video preview of my sample app SAMInBLE in action.

And now the step-by-step:

  • Start SAMInBLE on an Android phone.
  • Click “login” and enter your SAMI account credentials to login. Note that these are also your Samsung account credentials.
  • Click “Allow” when the app asks for permission to access the data of the Heart Rate Tracker device. This screen appears when you use the app for the first time.
  • After you finish the authentication flow, you will see the “SAMI Devices” screen:

SAMInBLE

The first time, you will see the “Create a new device” button. On future logins, you can simply select one of your Heart Rate Tracker devices in the “SAMI Devices” screen.

  • Click the “Create a new device” button to register your phone as a Heart Rate Tracker device in SAMI. Later on, all heart rate data will be sent to SAMI on behalf of this device. After clicking the button, the “SAMI Devices” screen transitions to the “BLE Device Scan” screen. The screen will list all BLE devices near your phone as follows:

SAMInBLE

  • Select one of the devices that sends Heart Rate Measurement—in this case, “Polar H7 Bluetooth Smart Heart Rate Sensor”. You will see the device detail screen:

SAMInBLE

The app attemps to connect to the BLE device. If the connection succeeds, the screen lists all services provided by the device. You will see Heart Rate Service listed for Polar H7.

  • Click “Heart Rate Service”, then “Heart Rate Measurement”. Now the app starts collecting heart rate data from the BLE device. The “Data” field refreshes when new data comes in from the Polar H7. Each data point is sent to SAMI on the behalf of the Heart Rate Tracker device that you created or selected in the “SAMI Devices” screen. The SAMI device ID is also shown in the device detail screen.

SAMInBLE

  • Different devices will provide different data points, and you can pick any to test. My focus for this prototype is only heart rate, so if you pick a different type, the app will stop receiving heart rate and show an “N/A” in the “Data” and “SAMI device ID” fields.
  • Click “BLE Disconnection” to stop the connection between SAMInBLE and the BLE device.
  • Use the back arrow on the top-left to navigate back to the “BLE Device Scan” screen.
  • Again, use the back arrow on the top-left to navigate back to the “SAMI Devices” screen. Click “logout” to logout from SAMI.

While the app is collecting heart rate data, you can log into the User Portal to view the data in near-real-time. Below is an animated screenshot of the User Portal stream.

Installation and setup

I log into the Developer Portal to register my Android application on SAMI. To properly test my application, I have two separate Samsung accounts with different email addresses:

  1. My official developer account. This is the account I use to log into the Developer Portal and register my application.
  2. A simulated user account. This is the account that I used in the above demo to log into SAMI on phone and to log into the User Portal.

I follow these instructions to create an application. For this Android app, I select the following:

  • Set “Redirect URL” to android-app://redirect.
  • Choose “Client Credentials Flow & Implicit Flow”.
  • Under “PERMISSIONS”, check “Read” for “Profile”.
  • Click the “Add Device Type” button. Choose “SAMI Example Heart Rate Tracker” as the device type. Check “Read” and “Write” permissions for this device type.

Make a note of the client ID. This is my unique application ID, which I will use in my source file later.

Prepare source files and libraries

To make my development easier, I will use the SAMI Android SDK, which takes care of managing all my REST calls to SAMI. My other prerequisites are:

  • Android SDK v21
  • Android Build Tools v21.1.1
  • Android Studio 1.0.1

I will be testing SAMInBLE on a Nexus 5 phone with Android 5.0.1, and use a Polar H7 as the BLE device to collect heart rate data.

  1. Get SAMInBLE from github.
  2. Download and build SAMI’s Java/Android SDK libraries. The library JAR files are generated under the target and target/lib directories of the SDK Maven project.
  3. Copy all library JAR files to Application/libs of SAMInBLE.
  4. Start Android Studio. Choose “Import Non-Android Studio project” and then select SAMInBLE directory to import the project.
  5. Use the client ID (obtained above, when registering the app in the Developer Portal) to replace YOUR CLIENT APP ID in SAMISession.java.

Now build the project, and deploy the APK to an Android phone. I am able to play with the app like in the above demo.

Implementation

I need to implement the following functionalities at the high level:

  • Create a new device type in SAMI, “SAMI Example Heart Rate Tracker”.
  • Authenticate a user using the OAuth 2.0 workflow.
  • Collect user information such as the username and user ID.
  • Get the list of devices that the user owns in SAMI.
  • Add the user’s phone to SAMI as a Heart Rate Tracker device.
  • Send heart rate data to SAMI.
  • Scan BLE devices, connect to a BLE device, and read heart rate data from it.

For the last functionality above, I use the code of the Android sample application BluetoothLeGatt. I won’t talk about its implementation. After you read Android’s documentation on Bluetooth Low Energy, you should be able to understand the implementation related to BLE in this app.

I do want to point out that you can easily change the code to collect measurements other than Heart Rate Measurement from BLE devices. The following code from SampleGattAttributes.java identifies the heart rate measurement. You could do the same for other measurements by consulting Bluetooth Characteristics.

public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";

Create the Heart Rate Tracker device type

Creating a new device type for SAMInBLE should be the very first step of my development. But since I have already created and published this device type in SAMI, you can simply use my Heart Rate Tracker. The purpose of this section is to take you through creating a new device type for your own application. If you’re just interested in the SAMInBLE code, feel free to skip to the next section.

SAMI is designed to communicate with any device regardless of how data is structured. It uses the Manifest to achieve this. To create my new Heart Rate Tracker device type, I need to write its Manifest and then submit it in the Developer Portal for approval. After the Manifest is approved, I can publish the Manifest from the Developer Portal. After it is published, Heart Rate Tracker can be used as a device type by any app working with SAMI.

To create the Manifest, I must first design the data format of the message sent to SAMI. My data is very simple:

1 {
2     "heart_rate":70,
3     "comments":"blah"
4 }

Next, I follow the Manifest documentation to write my Manifest in Groovy. Basically, I override two methods of the base class Manifest and use utility methods from the Manifest SDK to simplify the implementation.

 1 import groovy.json.JsonSlurper
 2 import com.samsung.sami.manifest.Manifest
 3 import com.samsung.sami.manifest.fields.*
 4 import static com.samsung.sami.manifest.fields.StandardFields.*
 5 import static com.samsung.sami.manifest.groovy.JsonUtil.*
 6 
 7 public class HeartRateTrackerManifest implements Manifest {
 8     static final COMMENTS = new FieldDescriptor("comments", String.class)
 9 
10     @Override
11     List<Field> normalize(String input) {
12         def slurper = new JsonSlurper()
13         def json = slurper.parseText(input)
14 
15         def fields = []
16 
17         addToList(fields, json, "heart_rate", HEART_RATE)
18         addToList(fields, json, COMMENTS)
19 
20         return fields
21     }
22 
23     @Override
24     List<FieldDescriptor> getFieldDescriptors() {
25         return [HEART_RATE, COMMENTS]
26     }
27 }

When receiving messages sent by a Heart Rate Tracker device, SAMI uses the above Manifest to parse and store the data properly.

Before submitting my Manifest on the Developer Portal, I need to test it for correctness. I use the sample Maven project as a template to create my own Maven project. You can get my Maven project at github and then follow this Quick start to build and run the test.

Finally I submit the Manifest by following the instructions for Creating a device type. When submitting in the Developer Portal, I give my device type the name “SAMI Example Heart Rate Tracker”, and a unique name io.samsungsami.example.heartRateTracker.

After receiving my approval notification via email, I go to the Developer Portal, click “SAMI Example Heart Rate Tracker”, and then click the “Publish” button. The status of my device type is updated to “PUBLISHED”. From now on, my Heart Rate Tracker is visible to other developers through the User Portal and Developer Portal, and can be used by any app, including SAMInBLE. Now I am ready to talk about the implementation of SAMInBLE.

Authenticate the user

SAMI supports multiple OAuth 2.0 workflows. Since this app is a standalone mobile application, I chose to implement the Implicit method, which has a straightforward implementation.

I implement the authentication in the following methods of the SAMILoginActivity class. This is similar to the implementation in Your first Android app. For more information, see that article in the documentation.

 1 private void loadWebView() {
 2     Log.v(TAG, "::loadWebView");
 3     mLoginView.setVisibility(View.GONE);
 4     mWebView.setVisibility(View.VISIBLE);
 5     mWebView.getSettings().setJavaScriptEnabled(true);
 6     
 7     mWebView.setWebViewClient(new WebViewClient() {
 8         @Override
 9         public boolean shouldOverrideUrlLoading(WebView view, String uri) {
10             if ( uri.startsWith(SAMISession.REDIRECT_URL) ) {
11                 // Redirect URL has format http://localhost:81/samidemo/index.php#expires_in=1209600&token_type=bearer&access_token=xxxx
12                 // Extract OAuth2 access_token in URL
13                 if ( uri.indexOf("access_token=") != -1 ) {
14                     String[] sArray = uri.split("access_token=");
15                     String accessToken = sArray[1];
16                     onGetAccessToken(accessToken);
17                 }
18                 return true;
19             }
20             // Load the web page from URL (login and grant access)
21             return super.shouldOverrideUrlLoading(view, uri);
22         }
23     });
24     
25     String url = getAuthorizationRequestUri();
26     Log.v(TAG, "webview loading url: " + url);
27     mWebView.loadUrl(url);
28 }
29 
30 public String getAuthorizationRequestUri() {
31     //https://accounts.samsungsami.io/authorize?client=mobile&client_id=xxxx&response_type=token&redirect_uri=http://localhost:81/samidemo/index.php
32     return SAMISession.SAMI_AUTH_BASE_URL + "/authorize?client=mobile&response_type=token&" +
33                  "client_id=" + SAMISession.CLIENT_ID + "&redirect_uri=" + SAMISession.REDIRECT_URL;
34 }

After the app gets the access token, the onGetAccessToken method above stores the token to a SAMISession object and sets up SAMI APIs so that other activities can make API calls later. The following method of SAMISession class sets up SAMI API objects.

 1 public void setupSamiApis() {
 2     // Invoke the appropriate API
 3     mUsersApi = new UsersApi();
 4     mUsersApi.setBasePath(SAMIHUB_BASE_PATH);
 5     mUsersApi.addHeader("Authorization", "bearer " + mAccessToken);
 6 
 7     mDevicesApi = new DevicesApi();
 8     mDevicesApi.setBasePath(SAMIHUB_BASE_PATH);
 9     mDevicesApi.addHeader("Authorization", "bearer " + mAccessToken);
10 
11     mMessagesApi = new MessagesApi();
12     mMessagesApi.setBasePath(SAMIHUB_BASE_PATH);
13     mMessagesApi.addHeader("Authorization", "bearer " + mAccessToken);
14 }

Get the user’s information

Before the app can query SAMI for the devices that the user owns, the app must get the user ID. SAMIDeviceActivity calls the following methods in onCreate to trigger an API call to SAMI to get user info.

new GetUserInfoInBackground().execute();

The implementation of GetUserInfoInBackground is below:

 1 class GetUserInfoInBackground extends AsyncTask<Void, Void, UserEnvelope> {
 2     final static String TAG = "GetUserInfoInBackground";
 3     @Override
 4     protected UserEnvelope doInBackground(Void... params) {
 5         UserEnvelope retVal = null;
 6         try {
 7             retVal= SAMISession.getInstance().getUsersApi().self();
 8         } catch (Exception e) {
 9             Log.v(TAG, "::doInBackground run into Exception");
10             e.printStackTrace();
11         }
12 
13         return retVal;
14     }
15 
16     @Override
17     protected void onPostExecute(UserEnvelope result) {
18         Log.v(TAG, "::setupSamiApi self name = " + result.getData().getFullName());
19         onGetUserInfo(result.getData());
20     }
21 }

Get the list of devices in SAMI

SAMIDeviceActivity queries SAMI to get the list of the devices that the user owns, and for which the app has read permissions. Then the list is parsed to identify Heart Rate Tracker devices. Only such devices show up in the UI. Later on, the user can choose which one to use to record heart rate data from a BLE. SAMIDeviceActivity uses the derived class of AsyncTask to achieve this as follows:

 1 class GetDeviceListInBackground extends AsyncTask<Void, Void, DeviceArray> {
 2     final static String TAG = "GetDeviceListInBackground";
 3     @Override
 4     protected DeviceArray doInBackground(Void... params) {
 5         DeviceArray deviceArray = null;
 6         try {
 7             DevicesEnvelope devicesEnvelope = SAMISession.getInstance().getUsersApi().getUserDevices(0, 100, false, SAMISession.getInstance().getUserId());
 8             deviceArray = devicesEnvelope.getData();
 9 
10         } catch (Exception e) {
11             Log.v(TAG, "::doInBackground run into Exception");
12             e.printStackTrace();
13         }
14 
15         return deviceArray;
16     }
17 
18     @Override
19     protected void onPostExecute(DeviceArray devices) {
20         mDeviceManager.updateDevices(devices);
21         refreshDeviceList();
22     }
23 }

Register your phone as a heart rate tracker

If the user does not have a Heart Rate Tracker device in SAMI, SAMIDeviceActivity registers the phone as a Heart Rate Tracker device in SAMI for the user. Later on, heart rate data from a BLE device will be sent to this Heart Rate Tracker device in SAMI. Again SAMIDeviceActivity uses the derived class of AsyncTask to achieve this as follows:

 1 class CreateDeviceInBackground extends AsyncTask<Void, Void, Device> {
 2     final static String TAG = "CreateDeviceInBackground";
 3     @Override
 4     protected Device doInBackground(Void... params) {
 5         Device retVal = null;
 6         try {
 7             Device device = new Device();
 8             device.setDtid(SAMISession.DEVICE_TYPE_ID_HEART_RATE_TRACKER);
 9             device.setUid(SAMISession.getInstance().getUserId());
10             device.setName("SAMInBLE test device"); //Note this is a limitation --the name is always this one.
11             retVal = SAMISession.getInstance().getDevicesApi().addDevice(device).getData();
12         } catch (Exception e) {
13             Log.v(TAG, "::doInBackground run into Exception");
14             e.printStackTrace();
15         }
16 
17         return retVal;
18     }
19 
20     @Override
21     protected void onPostExecute(Device result) {
22         Log.v(TAG, "::created device with Id: " + result.getId());
23         onDeviceCreationSucceed(result);
24     }
25 }

Send heart rate data to SAMI

I modified BluetoothLeService from the Google sample app to add the functionality of sending heart rate to SAMI. Below is the code I added. The method sendHeartRateToSami is called once there is new data from the BLE device.

 1 private void sendHeartRateToSami() {
 2     new PostMsgInBackground().execute();
 3 }
 4 
 5 public class PostMsgInBackground extends AsyncTask<Void, Void, MessageIDEnvelope> {
 6     final static String TAG = "BluetoothLeService::PostMsgInBackground";
 7     @Override
 8     protected MessageIDEnvelope doInBackground(Void... params) {
 9         MessageIDEnvelope retVal = null;
10         try {
11             HashMap<String, Object> data = new HashMap<String, Object>();
12             data.put("heart_rate", mHeartRateForSAMI);
13             Message msg = new Message();
14             msg.setSdid(SAMISession.getInstance().getDeviceId());
15             msg.setData(data);
16             msg.setTs(BigDecimal.valueOf(System.currentTimeMillis()));
17             retVal= SAMISession.getInstance().getMessagesApi().postMessage(msg);
18             Log.v(TAG, "::onPostExecute sending heart rate " + mHeartRateForSAMI);
19         } catch (Exception e) {
20             Log.w(TAG, "::doInBackground run into Exception");
21             e.printStackTrace();
22         }
23 
24         return retVal;
25     }
26 
27     @Override
28     protected void onPostExecute(MessageIDEnvelope result) {
29         if (result == null) {
30             Log.v(TAG, "::onPostExecute latestMessage is null!");
31             return;
32         }
33         Log.v(TAG, "::onPostExecute response to sending message = " + result.getData().toString());
34     }
35 }

What comes next?

It’s easy to connect Bluetooth low energy devices to SAMI, as this sample application demonstrates. Along with SAMI, a third-party app could create a service that is highly relevant to individual users, by bringing together their data from a number of different sources.

I have the following additional suggestions to turn SAMInBLE into a production-quality app:

  • Replace REST API calls with WebSocket API calls calls for better streaming performance. In addition, WebSockets have fewer rate limits than REST.
  • Add buffering capability in BluetoothLeService, which stores heart rate data when there is no Internet connection. Send data to SAMI once the Internet connection is recovered.

Check out Plot Your Location in Real-Time With SAMI to learn how to implement WebSockets in an Android app. Stay tuned for more development blog posts by joining our mailing list at http://developer.samsungsami.io/.

Also read my “Developing With SAMI” series of articles—Part 1: Making the Connection, Part 2: A Mobile App That Pulls Weight Data, and Part 3: Let’s Bring Your Data Together

Thanks to Mark and the Alto Velo Racing Team for helping us put together the video!

Get the ARTIK Newsletter

You like your news fresh! Sign up now and you will be the first to know about our latest software releases, coding tips, upcoming events, blog posts, datasheet updates, and more.

* By checking either box, you may receive notifications by phone, email, text, and/or other electronic means from Samsung Semiconductor, Inc. and its affiliates. If you choose to receive partner notifications, we may forward your contact information to our partners. You may unsubscribe from these services at any time by clicking on the unsubscribe link in our communications or by submitting a request here. Please see our Privacy Policy and Terms of Use for more information about how your data is stored and used.