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 |
---|---|
|
WebSocket connection was established. |
|
WebSocket connection was closed. |
|
Text message was received. |
|
Raw text message was received. |
|
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].
Infos
- Listings for the article: ftp://ftp.linux-magazine.com/pub/listings/raspberry-pi-geek.com/12
- WebSocketsServer: https://github.com/lukasz-skalski/WebSocketsServer
- WebSocketsClient: https://github.com/lukasz-skalski/WebSocketsClient
- Libwebsockets: https://github.com/lukasz-skalski/libwebsockets
- Wslay: http://wslay.sourceforge.net
- noPoll: http://www.aspl.es/nopoll/
- Installing Raspian: http://www.raspberrypi.org/documentation/installation/installing-images/linux.md
- IPC: https://en.wikipedia.org/wiki/Inter-process_communication
- Java-WebSocket: https://github.com/TooTallNate/Java-WebSocket
- AutobahnAndroid: https://github.com/tavendo/AutobahnAndroid
- SecureWebSockets: https://github.com/palmerc/SecureWebSockets
- More about Android SDK: http://developer.android.com/sdk/installing/index.html
- Libwebsockets documentation: https://libwebsockets.org/libwebsockets-api-doc.html
« Previous 1 2 3 4 Next »
Buy this article as PDF
Pages: 8
(incl. VAT)