Controlling the Raspberry Pi with Android via the WebSocket protocol

Please Call Back

For those who still have questions about the my_callback() function that connects the server to the clients, a simpler version of the function is shown in Listing 7. Because the high-level interface found in the libwebsockets library hides the details of the connection setup, you can only implement responses to certain events in the program.

Listing 7

my_callback()

01 [...]
02 my_callback ()
03 {
04   struct per_session_data *psd = (struct per_session_data*) user;
05   int nbytes;
06
07   switch (reason)
08     {
09       case LWS_CALLBACK_ESTABLISHED:
10         /* connection established */
11       break;
12
13       case LWS_CALLBACK_CLOSED:
14         /* connection closed */
15       break;
16
17       case LWS_CALLBACK_SERVER_WRITEABLE:
18         nbytes = libwebsocket_write(/* data to write */);
19         print_log ("%d bytes written\n", nbytes);
20       break;
21
22       case LWS_CALLBACK_RECEIVE:
23         print_log ("received %d bytes\n", (int) len);
24         psd->len = prepare_reply (wsi, in, &psd->buf[PADDING]);
25         if (psd->len > 0)
26           libwebsocket_callback_on_writable (context, wsi);
27       break;
28     }
29   return 0;
30 }
31 [...]

The my_callback() function recognizes a total of four basic events that all begin with the prefix LWS_CALLBACK_. These events are used both to open (line 9) and close (line 13) a connection and to send (line 17) and receive (line 22) data.

If the server-side application receives data in its echo server function, the function will prepare an answer for the client application with the help of prepare_reply(). This occurs in the form of a JSON object:

{
  "Type":    "standard",
  "Message": "Hello"
}

The application sends it back to the client and the libwebsocket_callback_on_writable() helps. The JSON object looks as follows for asynchronous D-Bus messages that inform the client about a USB stick that has been connected:

{
  "Type":    "notification",
  "Message": "[Udisks] DeviceAdded"
}

Now you see that the code for the client-side application is still missing.

WebSockets Client Side

Choosing a library for the client side of a WebSocket application for Android proved to be reasonably complicated. Many of the projects and libraries that simplify working with WebSockets are written in Java – for example, Java-WebSocket [9] and the AutobahnAndroid project [10]. For the purposes of this project, I chose the SecureWebSockets library [11], which is a clone of the AutobahnAndroid project.

Most of the code for the application is found in the ActivityMain.java file. It includes, among other things, the definitions for the wsConnect() and wsDisconnect() functions that open and close connections to the server. The main class of the application, ActivityMain, implements the Websocket.WebSocketConnectionObserver interface and overrides the methods presented in Table 1. Listing 8 shows an abbreviated and simplified version of the client side of the WebSocket program.

Listing 8

ActivityMain.java (Simplified Excerpt)

01 [...]
02 public class ActivityMain extends Activity implements \
   WebSocket.WebSocketConnectionObserver {
03
04   private volatile boolean isConnected = false;
05   private WebSocketConnection wsConnection;
06   private WebSocketOptions wsOptions;
07
08   @Override
09   protected void onCreate (Bundle savedInstanceState) {
10
11     super.onCreate (savedInstanceState);
12     setContentView (R.layout.activity_main);
13
14     connectButton.setOnClickListener (new View.OnClickListener() {
15       @Override
16       public void onClick(View v) {
17         wsConnect());
18       }
19     });
20
21     cmdInput.setOnEditorActionListener (new EditText.OnEditorActionListener() {
22       @Override
23       public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
24         if (actionId == EditorInfo.IME_ACTION_DONE) {
25           wsSend();
26           return true;
27         }
28         return false;
29       }
30     });
31   }
32
33   boolean wsConnect() {
34
35     this.wsConnection = new WebSocketConnection();
36     this.wsOptions = new WebSocketOptions();
37
38     wsConnection.connect(wsURI, this, wsOptions);
39   }
40
41   void wsDisconnect() {
42     wsConnection.disconnect();
43   }
44
45   void wsSend() {
46
47     /* send message to the server */
48     wsConnection.sendTextMessage(cmdInput.getText().toString());
49     appendText(cmdOutput, "[CLIENT] " \
       + cmdInput.getText().toString()  + "\n", Color.RED);
50   }
51
52   @Override
53   public void onOpen() {
54     this.isConnected = true;
55   }
56
57   @Override
58   public void onClose (WebSocketCloseNotification code, String reason){
59     this.isConnected = false;
60   }
61
62   @Override
63   public void onTextMessage (String payload) {
64
65     JSONObject jsonObj = new JSONObject(payload);
66
67     if ((jsonObj.has(TAG_JSON_TYPE)) && (jsonObj.has(TAG_JSON_MSG))) {
68
69       if (jsonObj.getString(TAG_JSON_TYPE).equals("notification")) {
70
71         /* show notification */
72
73       } else if (jsonObj.getString(TAG_JSON_TYPE).equals("standard")) {
74
75         appendText(cmdOutput, "[SERVER]" \
           + jsonObj.getString(TAG_JSON_MSG)+"\n",Color.RED);
76
77       } else {
78
79         Log.e (TAG_LOG, "Received invalid JSON from server");
80
81       }
82     }
83   }
84
85   @Override
86   public void onRawTextMessage (byte[] payload) {
87     Log.wtf (TAG_LOG, "We didn't expect 'RawTextMessage'");
88   }
89
90   @Override
91   public void onBinaryMessage (byte[] payload) {
92     Log.wtf (TAG_LOG, "We didn't expect 'BinaryMessage'");
93   }
94 [...]

Table 1

Methods in ActivityMain

Method Name

Description

onOpen()

WebSocket connection was established.

onClose()

WebSocket connection was closed.

onTextMessage()

Text message was received.

onRawTextMessage()

Raw text message was received.

onBinaryMessage()

Binary file was received.

The complete application can be retrieved from the GitHub repository or downloaded from the Raspberry Pi Geek anonymous FTP site [1] and translated via the Gradle Android build system with the command from Listing 9. The smartphone owner will need a suitable Android SDK for the phone as well as the appropriate platform tools for the version of Android used by the mobile phone [12].

Listing 9

Compiling the Client-Side App

01 git clone https://github.com/lukasz-skalski/WebSocketsClient.git
02 cd WebSocketsClient/
03 export ANDROID_HOME=</path/to/sdk/>
04 ./gradlew build

The installable APK files will be in the $HOME/WebSocketsClient/app/build/outputs/apk folder at the end of the build. To save time, you can also use ready-made APK files from the GitHub package source.

The server is easy to start on the Raspberry Pi. The basic functions can be activated via:

./WebSocketsServer --port=8080

Further details on what is possible with libwebsockets are found in the documentation [13].

Buy this article as PDF

Express-Checkout as PDF

Pages: 8

Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content