Filter results by

An IoT remote control

Browse source for this tutorial.

In Your first IoT device, we built a device that sends sensor data to ARTIK Cloud. In this tutorial, we will build the following system:

  • An IoT device that acts on received commands and sends back its latest state.
  • An Android application that sends commands to the device and displays the latest state of the device.

You will learn:

Here are the related starter code:

For the sake of clarity, we will develop both the device and the application here. As an open ecosystem, ARTIK Cloud is designed to allow developers to build an application for any published devices on ARTIK Cloud. The IoT device type we build here could easily be published in the Developer Dashboard by its manufacturer, allowing different vendors to build this whole system without the pain of integration.

Basics

Before proceeding, you should be familar with the following Action basics:

These articles describe how to define and send Actions programmatically. Note also that you can define Actions via the Developer Dashboard when creating a Manifest.

Design overview

The diagram below shows the high-level architecture:

ARTIK Cloud IoT remote control architecture

This system works as follows:

  1. Click the "Turn on" or "Turn off" button in the Android app, which sends to ARTIK Cloud an Action via WebSocket 1.
  2. ARTIK Cloud routes the Action to a Raspberry Pi that is connected to WebSocket 2. Raspberry Pi then sets the pin of the LED to the proper value according to the Action. As a result, the light is switched on or off.
  3. Raspberry Pi sends the latest state of the LED light to ARTIK Cloud via WebSocket 2.
  4. ARTIK Cloud notifies the Android app of the light state via WebSocket 3. The app displays the latest state of the light.

The system uses three WebSockets with two different endpoints: /live and /websocket. Please refer to the linked documentation to learn the differences between the endpoints. Here is our reasoning about the design:

  • Using WebSockets instead of REST, the application can send Actions in near real-time. In order to send Actions or data, the application must use the /websocket endpoint instead of /live.
  • Using the /websocket endpoint for WebSocket 2, the Raspberry Pi can receive the Actions targeted to it and also send the LED state back to ARTIK Cloud.
  • Using /live for WebSocket 3 is appropriate for the monitoring functionality. In near real-time, the app is notified of changes to the LED state stored in ARTIK Cloud.

We use the following hardware components:

  • Raspberry Pi with an Internet connection
  • An LED light and a 270Ω resistor
  • A breadboard plus wiring
  • Android phone

We will write the following software:

  • A Node.js script running on the Raspberry Pi
  • An Android application running on the Android phone

You can check out the code of the above software on GitHub.

Create and connect a new device type

Go to the Developer Dashboard to create a private device type.

  1. First, sign into the ARTIK Cloud Developer Dashboard. If you don't have an ARTIK Cloud Account, you can create one at this step.
  2. Click "+ New Device Type".
  3. Name this device type "Smart Light" and give it a unique name such as "com.example.iot.light". Note the characters <,>,&,'," are not allowed.
  4. Click "Create Device Type". This creates the device type and takes you to a page listing your device types.

Now let's create a Manifest for our "Smart Light" device type.

  1. Click "Smart Light" in the left column.
  2. Click "Manifest" and then "+ New MANIFEST".
  3. In "Device Fields" tab, choose "STATE" in the dropdown menu as the Field Name and "Boolean" for Data Type.
  4. Click "Save" and then navigate to "Device Actions" tab.
  5. Click "NEW ACTION" and then add "setOn" in the dropdown menu for ACTION.
  6. Click "NEW ACTION" and then add "setOff" in the dropdown menu for ACTION.
  7. Navigate to the "Activate Manifest" tab. You should see the fields and Actions created as follows: Smart Light Manifest
  8. Click the "ACTIVATE MANIFEST" button.

Do not publish this device type, since it is for tutorial purposes only. You are able to see this private device type when you login to My ARTIK Cloud using the ARTIK Cloud Account credentials that you used to login to the ARTIK Cloud Developer Dashboard.

Finally go to My ARTIK Cloud to connect a new "Smart Light" device:

  1. Sign into My ARTIK Cloud using the same account that you used to sign into the ARTIK Cloud Developer Dashboard.
  2. On the dashboard, click to connect a new device. Choose the "Smart Light" device type you just created. Make sure the ID or unique name of the device type you chosen is consistent with that of the private device type you created. Connect ARTIK Cloud device
  3. Click "Connect Device…". You're taken back to the dashboard.
  4. Click the Settings icon of the device you just added. In the pop-up, click "GENERATE DEVICE TOKEN…".
  5. Copy the device ID and device token on this screen. You will use these in the code. Generate ARTIK Cloud device token

Every ARTIK Cloud API call requires an access token. The device token is one of three types of access tokens. For the sake of simplicity, we use the device token in this tutorial.

Develop a smart LED light

Set up the hardware

The smart light includes a Raspberry Pi, LED light, 270Ω resistor, and breadboard. Let's wire these hardware components together.
smart LED hardware

We connect the positive side of the LED light to the programmable GPIO pin GPIO17. In Raspberry Pi 2 Model B V1.1, this GPIO pin's physical location is pin 11. Below is a close-up of the wiring.
Fritzing layout

Set up the software

Connect your Raspberry Pi to a monitor, mouse, and keyboard. Ensure that an Ethernet or WiFi connection is working, and make sure the OS is up-to-date:

1
2
$ sudo apt-get update
$ sudo apt-get upgrade

If not already installed, install Node.js for ARM, then add the packages rpi-gpio and ws via npm:

1
2
$ npm install ws
$ npm install rpi-gpio

Finally, download the Node.js program smart_light.js under the raspberrypi directory at GitHub to the Raspberry Pi. Replace the following placeholders in the code with the device token and device ID you collected after connecting the device in My ARTIK Cloud in the previous step.

1
2
var device_id = "<YOUR DEVICE ID>";
var device_token = "<YOUR DEVICE TOKEN>";

To run the program in the console of the Raspberry Pi, change to the directory where the program is located:

1
$ sudo node smart_light.js

You must run the program as root or with sudo. Otherwise, the Raspberry Pi cannot output to the GPIO, which turns the LED on or off.

Explain the code

Let's take a close look at the Node.js program smart_light.js. It establishes a device channel WebSocket connection between the Raspberry Pi and ARTIK Cloud. After the WebSocket connection is open, register() method registers the device with the WebSocket. Beyond setting up the WebSocket, the code also sets the GPIO pin for writing so that the LED light can be turned on or off. When the WebSocket connection is close, the GPIO pin is teared down. Below is the correspoding code snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * Create a /websocket connection and setup GPIO pin
 */
function start() {
    //Create the WebSocket connection
    isWebSocketReady = false;
    ws = new WebSocket(webSocketUrl);
    ws.on('open', function() {
        console.log("WebSocket connection is open ....");
        register();
    });
    ws.on('message', function(data) {
         handleRcvMsg(data);
    });
    ws.on('close', function() {
         console.log("WebSocket connection is closed ....");
         exitClosePins();
    });

    gpio.setup(myPin, gpio.DIR_OUT, function(err) {
        if (err) throw err;
        myLEDState = false; // default to false after setting up
        console.log('Setting pin ' + myPin + ' to out succeeded! \n');
     });
}

/**
 * Sends a register message to /websocket endpoint
 */
function register(){
    console.log("Registering device on the WebSocket connection");
    try{
        var registerMessage = '{"type":"register", "sdid":"'+device_id+'", "Authorization":"bearer '+device_token+'", "cid":"'+getTimeMillis()+'"}';
        console.log('Sending register message ' + registerMessage + '\n');
        ws.send(registerMessage, {mask: true});
        isWebSocketReady = true;
    }
    catch (e) {
        console.error('Failed to register messages. Error in registering message: ' + e.toString());
    }    
}

When ARTIK Cloud receives an Action targeted to this device, handleRcvMsg below is called. The method parses the content of the Action and toggles the LED.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Handle Actions
   Example of the received message with Action type:

   {
   "type":"action","cts":1451436813630,"ts":1451436813631,
   "mid":"37e1d61b61b74a3ba962726cb3ef62f1",
   "sdid”:”xxxx”,
   "ddid”:”xxxx”,
   "data":{"actions":[{"name":"setOn","parameters":{}}]},
   "ddtid":"dtf3cdb9880d2e418f915fb9252e267051","uid":"650xxxx”,”mv":1
   }
 */
function handleRcvMsg(msg){
    var msgObj = JSON.parse(msg);
    if (msgObj.type != "action") return; //Early return;

    var actions = msgObj.data.actions;
    var actionName = actions[0].name; //assume that there is only one action in actions
    console.log("The received action is " + actionName);
    var newState;
    if (actionName.toLowerCase() == "seton") {
        newState = 1;
    }
    else if (actionName.toLowerCase() == "setoff") {
        newState = 0;
    } else {
        console.log('Do nothing since receiving unknown action ' + actionName);
        return;
    }
    toggleLED(newState);
}

Once the program writes a value to the GPIO pin, it wraps the latest light state in a message, and then sends the message to ARTIK Cloud. Here is the code snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function toggleLED(value) {
    gpio.write(myPin, value, function(err) {
        if (err) throw err;
        myLEDState = value;
        console.log('toggleLED: wrote ' + value + ' to pin #' + myPin);
        sendStateToArtikCloud();
    });
}

/**
 * Send one message to ARTIK Cloud
 */
function sendStateToArtikCloud(){
    try{
        ts = ', "ts": '+getTimeMillis();
        var data = {
              "state": myLEDState
            };
        var payload = '{"sdid":"'+device_id+'"'+ts+', "data": '+JSON.stringify(data)+', "cid":"'+getTimeMillis()+'"}';
        console.log('Sending payload ' + payload + '\n');
        ws.send(payload, {mask: true});
    } catch (e) {
        console.error('Error in sending a message: ' + e.toString() +'\n');
    }
}

As a best practice, the device should send its latest state back to ARTIK Cloud after it acts on the received Action. This ensures that ARTIK Cloud is up-to-date on the device state, which other applications may be monitoring.

Test the light

We can test the light before developing the Android app by using the tools at My ARTIK Cloud. See the instructions for sending an Action to your connected device.

Observe that the LED lights up. In addition, the device has sent back the latest state (true) to ARTIK Cloud, as you can see at "DATA LOGS":

IoT remote control

Try another Action to turn off the light. The LED should be off and the proper status change is observed at My ARTIK Cloud. So far, we have verified that the light works as expected—correctly acting on the received Actions and also sending back the latest state to ARTIK Cloud.

Congratulations! The smart LED works. Now we are ready to move into the next phase of the development.

Develop an Android app

Before diving into the implementation, let's see what the UI looks like.

  • Start the Android app ACIoTController.
  • Click "LOGIN" and enter your ARTIK Cloud credentials.
  • Click "Allow" when the app asks you to give it permission to access data from "Smart Light" on ARTIK Cloud. This screen appears when you use the app for the first time.
  • After you finish the authentication flow, you will see the control screen:
    data logs actions

There are three regions in the control screen:

  1. The device information and buttons for turning on/off the light.
  2. The status of WebSocket 1 and the received ACK message after sending an Action.
  3. The status of WebSocket 3 and the latest received messages on WebSocket 3.

Upon the screen is loaded, the two WebSockets will establish connections and show their respective statuses in the second and third regions. When the "Turn On" or "Turn Off" buttons in the first region are clicked, the second region shows any ACK received from ARTIK Cloud, and shortly thereafter the third region displays the new message sent by the light to ARTIK Cloud after it acts on the Action.

Create an application

Follow these instructions to create an application using the Developer Dashboard. For this tutorial, select the following:

  • Set "AUTHORIZATION METHODS" to "Implicit".
  • Set "Redirect URL" for your application to android-app://redirect.
  • Under "PERMISSIONS", check “Read” for “Profile”.
  • Click "Add Device Type" button. Choose the private device type you created in the previous step. Check both "Read" and "Write" permissions for this device type. Make sure the ID or unique name of the device type you chosen is consistent with that of the private device type you created.

Make a note of your client ID. This is your application ID, which you will need later.

Set up the Android project

Here are the prerequisites:

You can build ARTIK Cloud Java/Android SDK library from the source code at GitHub. In this example, we use the library downloaded from Maven Central Repository by specifying the library version in app/build.gradle.

Replace the following placeholders in ArtikCloudSession.java with the application ID and device token ID you collected in the previous steps:

1
2
3
4
5
// Copy from the corresponding application in the Developer Dashboard
public static final String CLIENT_ID = "<YOUR CLIENT ID>";

// Copy from the Device Info screen in My ARTIK Cloud
private final static String DEVICE_ID = "<YOUR DEVICE ID>";

The sample application uses Android SDK with API level 21.

You should be able to build and run the Android application. We'll explain the code in the following sections.

Login to obtain access token

LoginActivity.java uses a WebView to present the login screen. After login succeeds, the activity catches the ARTIK Cloud callback and then extracts the access token from the callback URL. The access token is used to establish WebSocket 1 and 3.

Consult obtaining an access token in Your first Android app to learn the implementation details.

Send Actions

The Android application sends Actions to the light via WebSocket 1, per the architecture diagram. WebSocket 1 uses the /websocket endpoint. The code snippet below shows the creation of such a WebSocket and the corresponding event handling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
private void createDeviceChannelWebSockets() {
    try {
        mDeviceChannelWS = new DeviceChannelWebSocket(true, new ArtikCloudWebSocketCallback() {
            @Override
            public void onOpen(int i, String s) {
                Log.d(TAG, "Registering " + DEVICE_ID);
                final Intent intent = new Intent(WEBSOCKET_WS_ONOPEN);
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);

                RegisterMessage registerMessage = new RegisterMessage();
                registerMessage.setAuthorization("bearer " + mAccessToken);
                registerMessage.setCid("myRegisterMessage");
                registerMessage.setSdid(DEVICE_ID);

                try {
                    Log.d(TAG, "DeviceChannelWebSocket::onOpen: registering" + DEVICE_ID);
                    mDeviceChannelWS.registerChannel(registerMessage);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onMessage(MessageOut messageOut) {
                Log.d(TAG, "DeviceChannelWebSocket::onMessage(" + messageOut.toString());
                final Intent intent = new Intent(WEBSOCKET_WS_ONMSG);
                intent.putExtra(ACK, messageOut.toString());
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onAction(ActionOut actionOut) { }

            @Override
            public void onAck(Acknowledgement acknowledgement) {
                Log.d(TAG, "DeviceChannelWebSocket::onAck(" + acknowledgement.toString());
                Intent intent;
                if (acknowledgement.getMessage() != null && acknowledgement.getMessage().equals("OK")) {
                    intent = new Intent(WEBSOCKET_WS_ONREG);
                } else {
                    intent = new Intent(WEBSOCKET_WS_ONACK);
                    intent.putExtra(ACK, acknowledgement.toString());
                }
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                final Intent intent = new Intent(WEBSOCKET_WS_ONCLOSE);
                intent.putExtra(ERROR, "mWebSocket is closed. code: " + code + "; reason: " + reason);
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);

            }

            @Override
            public void onError(WebSocketError error) {
                final Intent intent = new Intent(WEBSOCKET_WS_ONERROR);
                intent.putExtra(ERROR, "mWebSocket error: " + error.getMessage());
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onPing(long timestamp) {
                Log.d(TAG, "DeviceChannelWebSocket::onPing: " + timestamp);
            }
        });
    } catch (URISyntaxException|IOException e) {
        e.printStackTrace();
    }
}

After the WebSocket connection is open, mDeviceChannelWS.registerChannel() registers the application with the WebSocket. Upon receiving ACK messages, the message displays in the second region on the Android screen.

Construct an Action JSON object and then send it over the WebSocket:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
 * Example of Action sent to ARTIK Cloud over /websocket endpoint
 *  {
    cid:  setOff
    data:  {
             actions: [
                        {
                          name:  setOff
                          parameter: {}
                        }
                      ]
           }
    ddid:  fde8715961f84798a841be23480b8ce5
    sdid:  null
    ts:   1451606965889
    }
 *
 */
    private void sendActionInDeviceChannelWS(String actionName) {
        ActionIn actionIn = new ActionIn();
        ActionDetails action = new ActionDetails();
        ArrayList<ActionDetails> actions = new ArrayList<>();
        ActionDetailsArray actionDetailsArray = new ActionDetailsArray();

        action.setName(actionName);
        actions.add(action);
        actionDetailsArray.setActions(actions);
        actionIn.setData(actionDetailsArray);
        actionIn.setCid(actionName);
        actionIn.setDdid(DEVICE_ID);
        actionIn.setTs(System.currentTimeMillis());

        try {
            mDeviceChannelWS.sendAction(actionIn);
            Log.d(TAG, "DeviceChannelWebSocket sendAction:" + actionIn.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

The above method sendActionInDeviceChannelWS() cannot be called in the main thread, so it becomes a little bit complicated. We wrap it in sendActionInBackground, which is an AsyncTask subclass. Finally execute the method as following:

1
2
3
4
5
6
7
public void sendOnActionInDeviceChannelWS() {
    new sendActionInBackground().execute(ACTION_NAME_ON);
}

public void sendOffActionInDeviceChannelWS() {
    new sendActionInBackground().execute(ACTION_NAME_OFF);
}

Monitor light state

The Android application listens to the state change of the light via WebSocket 3, per the architecture diagram. WebSocket 3 uses the /live endpoint to monitor each message sent by the smart light. The code snippet below shows the creation of such a WebSocket and the corresponding event handling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void createFirehoseWebsocket() {
    try {
        mFirehoseWS = new FirehoseWebSocket(mAccessToken, DEVICE_ID, null, null, null, new ArtikCloudWebSocketCallback() {
            @Override
            public void onOpen(int i, String s) {
                Log.d(TAG, "FirehoseWebSocket: onOpen()");
                final Intent intent = new Intent(WEBSOCKET_LIVE_ONOPEN);
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onMessage(MessageOut messageOut) {
                Log.d(TAG, "FirehoseWebSocket: onMessage(" + messageOut.toString() + ")");
                final Intent intent = new Intent(WEBSOCKET_LIVE_ONMSG);
                intent.putExtra(SDID, messageOut.getSdid());
                intent.putExtra(DEVICE_DATA, messageOut.getData().toString());
                intent.putExtra(TIMESTEP, messageOut.getTs().toString());
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onAction(ActionOut actionOut) {}

            @Override
            public void onAck(Acknowledgement acknowledgement) {}

            @Override
            public void onClose(int code, String reason, boolean remote) {
                final Intent intent = new Intent(WEBSOCKET_LIVE_ONCLOSE);
                intent.putExtra("error", "mFirehoseWS is closed. code: " + code + "; reason: " + reason);
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onError(WebSocketError ex) {
                final Intent intent = new Intent(WEBSOCKET_LIVE_ONERROR);
                intent.putExtra("error", "mFirehoseWS error: " + ex.getMessage());
                LocalBroadcastManager.getInstance(ourContext).sendBroadcast(intent);
            }

            @Override
            public void onPing(long timestamp) {
                Log.d(TAG, "FirehoseWebSocket::onPing: " + timestamp);
            }
        });
    } catch (URISyntaxException|IOException e) {
        e.printStackTrace();
    }
}

In the WebSocket constructor FirehoseWebSocket(), the device ID and token of the smart light are passed in as the arguments. Therefore, via this WebSocket, the application can receive all messages sent by the smart light (as the source device).

Method onMessage() parses a received message, and displays it in the third region on the Android screen.

Test the app

Just as we tested the light without using the Android app, we can test the Android app using My ARTIK Cloud instead of a real device.

First, test the app's Action sending functionality. Click the "Turn On"/"Turn off" buttons in the Android app. Login to My ARTIK Cloud to see the Actions sent by the app to the light. Each button click generates one Action in the data log as shown below:

An IoT remote control

Next, test the app's light state monitoring functionality. We will use the Device Simulator. At My ARTIK Cloud, select the device you connected previously and then configure the simulator as follows:

  • Simulate data on the Boolean "state" field and keep the default interval to send every 5000 ms (5 secs).
  • Alternative between "true"/"false" values by setting the Data Pattern to Alternating Boolean.

The simulator sends a message every five seconds. You should see this message in the third region of the screen in your Android app. This verifies that the app's monitoring functionality works as expected.

Once finishing the testing, stops the simulation to avoid accuring unnecessary data usage.

We have used the various tools at My ARTIK Cloud to test the device and the application separately. Once you have developed both the smart light and the Android app, you can easily do end-to-end testing without using these tools.