In this paper, I will explain how to have a PC and an Android application to communicate to each other through USB and the Android Accessory protocol. This work takes place in a robotic project where a communication should be established between an Android device and a PC board running Ubuntu and ROS.
The final result will be a Python script handling a “sensor” value, which may vary randomly with time and an Android app reading this sensor value. The Android app will also be able to send “command”, which will change the value of the sensor. Check the screenshots bellow to have an idea of what it looks like. My code is also available on Github.
WHY USB? AND WHY ANDROID ACCESSORY PROTOCOL?
There are several way to send information between two devices:
- through a TCP/IP network with API (possibly involving internet)
- using Bluetooth
- using a port like a RS232 port or a more modern USB port
Based on the target (a communication between an Android device and a PC board), the first one has been rejected. This kind of technology is perfect for wide scale network. The drawback is that it depends on a network, which is constraint when only two pairs are involved.
Bluetooth is designed for personal networks, meaning networks between two devices. If this is unquestionably closer to the target, it implies that both devices should handle the protocol. This is now common to have a Bluetooth chip on laptops and on mobile devices, it is less popular to have ones on standard motherboards. A USB dongle should be used in this last case, often plugged in a USB port.
Since a USB port is involved, the last solution is finally the better. The USB protocol has a also a main advantage: power. USB has been designed to provide power, so a connected device can be powered by it directly without requiring an additional power supply. In the case of a mobile phone, this power supply is used to load the battery pack.
The problem is now to exchange information between an Android app and a PC through USB. Two options can be used to that, according to the Android developer documentation:
- the Android device acts as a host
- the peripheral (a PC board in our case) acts as a host
The data transfer is done in both way for each option. The difference is that in the first case, the power will be provided by the Android device, and therefore it will drain the battery. The second option is known as the “USB accessory mode”. To make it work, the peripheral should implement the “Open Accessory Protocol”. This is the recommended to way to connect any advance peripherals, including a robotic controller as stated in the documentation.
This last option is definitely the most convenient in the context of my project.
DECOMPOSITION OF THE TASK
Everything is described in the links above or in the resources links bellow. I provide here a summary and give a couple of non obvious thing I have noticed.
The steps are:
- the script is started and check for a connected Android accessory
- if no Android accessory is found, but an Android device is discovered, the script tries to switch the device to accessory mode
- the Android app is launched by this operation
- if it works, the script executes a loop, sending information to the Android device
- the loop is looking for incoming messages from the Android app
WORKING ENVIRONMENT SET-UP
You can check how to install Android Studio on Ubuntu. I have also described in a previous posts how to install PyUSB on Ubuntu.
I have used Python and GIT, without any specific challenge to install.
LOOKING FOR AN ANDROID ACCESSORY
This is done by the following piece of code:
VID_ANDROID_ACCESSORY = 0x18d1 PID_ANDROID_ACCESSORY = 0x2d01 def get_accessory(): print('Looking for Android Accessory') print('VID: 0x%0.4x - PID: 0x%0.4x' % (VID_ANDROID_ACCESSORY, PID_ANDROID_ACCESSORY)) dev = usb.core.find(idVendor=VID_ANDROID_ACCESSORY, idProduct=PID_ANDROID_ACCESSORY) return dev
They are several PID for the accessory mode, depending on the activation of the debug mode or not. The VID should always be the one from Google. If an Android accessory is found, a variable containing the device is sent back. The variable is still NULL otherwise.
TRIGGERING THE ANDROID ACCESSORY MODE
Before doing this operation, you have to define a variable with the Android device, which is a Galaxy Nexus in my case. You can use the same code as above, except for the VID and PID. The VID and PID of the Galaxy Nexus are:
VID_GALAXY_NEXUS_DEBUG = 0x04e8 PID_GALAXY_NEXUS_DEBUG = 0x6860
The PID is different for a different device, and it is also different if the debug mode is not activated.
We will now use a common feature of the USB protocol which is called “control request”. A control request is a special kind of request which is used to trigger actions on a USB device. It is one of the several request types of the USB standard protocol. This kind of request would have been perfect to exchange information between a controller and an actuator, with specific message types. Unfortunately I have discovered later that this cannot be done with the Open Accessory Protocol. Only “bulk request” can be used.
So control requests will be used to trigger the accessory mode on the Android device. Note that the device switches back to the regular mode when unplugged.
Check protocol
First of all, the protocol version has to be checked:
def set_protocol(ldev): try: ldev.set_configuration() except usb.core.USBError as e: if e.errno == 16: print('Device already configured, should be OK') else: sys.exit('Configuration failed') ret = ldev.ctrl_transfer(0xC0, 51, 0, 0, 2) # Dunno how to translate: array('B', [2, 0]) protocol = ret[0] print('Protocol version: %i' % protocol) if protocol < 2: sys.exit('Android Open Accessory protocol v2 not supported') return
An array is sent back from the request. The first value is the version protocol. The request direction and type are given by the Open Accessory Protocol documentation. For this first step, the request code is “51″.
Send Accessory parameters
In this step, the script will send a series of parameters that will help the mobile device to identify the application with which it should communicate. This is done by the following function:
def set_strings(ldev): send_string(ldev, 0, '_ArnO_') send_string(ldev, 1, 'PyAndroidAccessory') send_string(ldev, 2, 'A Python based Android accessory') send_string(ldev, 3, '0.1.0') send_string(ldev, 4, 'http://zombiebrainzjuice.cc/py-android-accessory/') send_string(ldev, 5, '2254711SerialNo.') return
The second value sent to the send_string function is the identifier of the string. The meaning of the strings are:
- “0” – manufacturer
- “1” – model
- “2” – description
- “3” – version
- “4” – URI
- “5” – serial
The values have to be consistent with the Android application. They should match the description from the res/xml/accessory_filter.xml
file.
The send_string function itself is described as follow:
def send_string(ldev, str_id, str_val): ret = ldev.ctrl_transfer(0x40, 52, 0, str_id, str_val, 0) if ret != len(str_val): sys.exit('Failed to send string %i' % str_id) return
It is a wrapper of the send control request to a USB device function. The code “52” is used for doing this operation.
Trigger accessory mode
The final step of the initialization procedure will switch the mobile phone to the Accessory mode. This is done by the new control request, with the code “53”.
def set_accessory_mode(ldev): ret = ldev.ctrl_transfer(0x40, 53, 0, 0, '', 0) if ret: sys.exit('Start-up failed') time.sleep(1) return
If it works, the Android application should be automatically started (if you have already loaded it on the mobile phone), or a pop-up should be displayed with the strings information and a link to the URI. The VID and the PID of the mobile should have gone, and the VID and PID of Google should be listed by the USB bus.
THE ANDROID ACCESSORY USB DESCRIPTION
At this step, you can take time to analyze the new USB interface. Here is the description before the switch to the Accessory mode:
Vendor: 0x04e8 - product: 0x6860 Configuration: 1 Interface: 0 - alternative setting: 0 - class 0xff End point: 0x0081 - attributes: 2 End point: 0x0002 - attributes: 2 End point: 0x0082 - attributes: 3 Interface: 1 - alternative setting: 0 - class 0xff End point: 0x0083 - attributes: 2 End point: 0x0003 - attributes: 2
2 interfaces are listed, including one for the debug mode. With the accessory mode activated, the information are slightly different:
Vendor: 0x18d1 - product: 0x2d01 Configuration: 1 Interface: 0 - alternative setting: 0 - class 0xff End point: 0x0081 - attributes: 2 End point: 0x0002 - attributes: 2 Interface: 1 - alternative setting: 0 - class 0xff End point: 0x0082 - attributes: 2 End point: 0x0003 - attributes: 2
Here again, an interface is dedicated to the debug mode. You can have this information with a Python script using PyUSB or by using the lsusb
command.
COMMUNICATION FROM THE PC TO THE ANDROID APP
Everything is now in place to exchange information from the PC to the Android device in Accessory mode. A periodic information will be sent by the Python script. It will be a value, which may vary randomly between 0 and 100. This is very similar to an information sent by a robotic sensor. In the next chapter, the Android app will send a command to increase or decrease this value.
On the script side, it is done with a loop, which will push information at each iteration:
try: ret = ldev.write(0x02, msg, 0, 150) if ret == len(msg): print(' - Write OK') except usb.core.USBError as e: print e
The error message should be caught, even if it is only to display it.
On the Android app side, the read loop is done in a background task, dedicated for that. The incoming stream is read by the following piece of code:
try { mMessage = ">>> "; ret = mInputStream.read(buffer); if (ret == 5) { String msg = new String(buffer); mMessage += msg; } else { mMessage += "Read error"; } } catch (IOException e) { e.printStackTrace(); mMessage += "Read error"; }
Here again, the error message should be caught.
The two pieces of code look similar, but the behavior is strongly different. In the Python script, the write command is waiting for the feedback until the timeout. The duration of the timeout is specified with the last parameter. In the Android app, the read command will wait for incoming data. That’s why it should be included in a background task, otherwise the app will freeze (and Android OS will kill the process).
The loop in the Python script is executed one time every 0.2 second, and the loop frequency on the Android app is 0.1. The timeout is 150 ms.
COMMUNICATION FROM THE ANDROID APP TO THE PC
Let’s now deal with the communication in the opposite direction. A command is sent to the script when the increase or decrease button is pushed by the user.
Like in the previous part, a loop should be listening to incoming messages, but on the Python script this time. In order to avoid multi-threading in the Python script, I have put the required code in the same loop like the previous one. So the first part of the loop will send the status of the sensor and the second part will be looking for command.
In the Python script, this is done using the following snippet:
try: ret = ldev.read(0x81, 5, 0, 150) sret = ''.join([chr(x) for x in ret]) print sret if sret == "A1111": variation = -3 else: if sret == "A0000": variation = 3 sensor = sensor_output(sensor, variation) except usb.core.USBError as e: print e
In this case, the error handling should be more sophisticated. Timeout will be triggered if no information are sent by the Android app. This is an “expected” error, since the user is free to send a command when he wants. A more elaborated version of the script should output that no message has been received, or even nothing at all. Only real errors should be displayed.
On the Android app side, when the user pushes one of the buttons, the message is sent using the following code:
byte[] buffer = new byte[5]; buffer[0] = (byte) 'A'; for (int i = 1; i < 5; i++) { buffer[i] = (byte) direction; } mMessage2 = "<<< "; try { mOutputStream.write(buffer); } catch (IOException e) { e.printStackTrace(); mMessage2 += "Send error"; } String msg = new String(buffer); mMessage2 += msg + System.getProperty("line.separator"); mText.post(mUpdateUI2);
The text string should be converted into an array of bytes before feeding the stream.
SCREENSHOTS
No extra time has been spent on the display. The idea was just to demonstrate that the Android Accessory protocol could be used for the task.
Clik here to view.
![Python Android Accessory USB communication [proof of concept] Python Android Accessory from the Python script side 1024x576 Python Android Accessory USB communication [proof of concept]](http://zombiebrainzjuice.cc/wp-content/uploads/2013/07/Python-Android-Accessory-from-the-Python-script-side-1024x576.png)
Python Android Accessory from the Python script side
The output of the Python script is displayed by the console directly. For the Android app, it was more complicated as there is no console-like functionality. I had to learn how to do it using multi-threading and background processes updating the screen. This is an investment that worth it, as any Android development should be using this technique.
Clik here to view.
![Python Android Accessory USB communication [proof of concept] Python Android Accessory from the Android app side 576x1024 Python Android Accessory USB communication [proof of concept]](http://zombiebrainzjuice.cc/wp-content/uploads/2013/07/Python-Android-Accessory-from-the-Android-app-side-576x1024.png)
Python Android Accessory from the Android app side
As you see on the screenshots above, an sensor value is maintained by the Python script. This value can randomly vary. The sensor value is pushed to the Android device which displays it. The Android app can also send command to modify the sensor (cannot be seen in the screenshots).
LIST OF IMPROVEMENTS
Here is a recollection of short issues, which should be fixed in a more stable version of the code:
- the python script should be able to work with regular authorization, and not superuser ones (due to the VID/PID not recognize by Ubuntu)
- the protocol version sent back after the version protocol request should be clarified
- any configuration should be allowed, like the device already in Accessory mode (note: when calling the configuration, the app is launched again)
- an acknowledgement could be sent by the receiver to confirm that a chunk of data has been received (loopback control)
- the script and the app could check whether the counter-part is available
- check if it is possible to swith back the Android device to the regular mode programmatically
SOURCE CODE
My code is available on Github:
- Android Accessory Python script: py-android-accessory-script
- Android Accessory app: py-android-accessory-app
You are really welcome to improve it.
RESOURCES
- Technical Blog of Nexus-Computing GmbH Switzerland, “Turn your Linux computer into a huge Android USB Accessory”, not very detailed, but there is everything inside, I owe a lot to this post
- Android developer documentation, “USB Host and Accessory”, the starting point for developing apps using the USB port Android devices
- Android developer documentation, “USB Accessory”, the explanation of the code expected from the Android side
- Android Accessories, “Build Accessories for Android”, the code expected from the peripheral side
The post Python Android Accessory USB communication [proof of concept] appeared first on Zombie Brainz' Juice.