Turn your (Linux-) Desktop / Arduino Yún / Raspberry Pi into an USB Android Accessory: How to use the Android Accessory Protocol with pyusb

py_acc

It has been a while since the post where we explained how to Turn your Linux computer into a huge Android USB Accessory. In the former post, the process of creating a C-application to communicate with your Android device has been discussed. Today, we would like to pick up on the same topic, this time however showing how communication can be established with the “pyusb” library using Python.

Since devices like the Arduino Yún or the Raspberry Pi offer a fully implemented USB stack (based on the Linux Kernel and libusb) it becomes increasingly interesting to use Python for this task.

The Android Accessory Protocol or “Android Open Accessory” Protocol, enables intercommunication between Android devices and microcontrollers (among them several ADK-kits). Low level microcontrollers like the Arduino DUE or the Mega ADK offer a way to communicate using the Accessory Protocol, however, they sometimes fall short in terms of compatibility and stability due to the complexity of the USB communication. A device with a Linux kernel however, usually has a much more sophisticated software implementation. This in turn makes it an attractive platform for USB communication.

If you have been playing around with an ADK-kit and came to no result with your Android device, then this blog post will enable you to determine if it is your ADK-kit’s or your Android device’s fault.

Contrary to the Android USB Host API, the Accessory API is supported on a much wider range of devices. There are pros and cons for both ways of communication.

In order to accomplish USB communication with python, the “pyusb” package will be the library of choice for this example. Be aware that it will require a small tweak to work properly on Ubuntu (tested with 13.04).

Python Application

Let’s have a look at a python code example. It will contain all steps required to put your Android device into “accessory” mode and start communication with your Desktop (or Arduino Yún / Raspberry Pi).

First off, a file named attribs.py will be created holding meta information about the accessory:

#!/usr/bin/python
# License GPLv2
# (c) Manuel Di Cerbo, Nexus-Computing GmbH
# attribs.py

MANUFACTURER = "Nexus-Computing GmbH"
MODEL_NAME = "Simple Slider"
DESCRIPTION = "A Simple Slider"
VERSION = "0.1"
URL = "http://www.nexus-computing.ch/files/SimpleAccessory.apk"
SERIAL_NUMBER = "1337"

These are the parameters required by the Open Accessory Protocol to initiate communication. If no application is installed on the Android device to handle this kind of “accessory”, the user will be prompted to head to the website described in the “URL” string. All other parameters are used to let the Android application identify the type of accessory.

Make sure that you create the “accessory.py” and “attribs.py” files in the same folder.

Furthermore, let’s have a look at the main application:

#!/usr/bin/python
# accessory.py
# License GPLv2
# (c) Manuel Di Cerbo, Nexus-Computing GmbH

import usb.core
import usb.util
import struct
import time
import threading
import os
import socket

from attribs import *

ACCESSORY_VID = 0x18D1
ACCESSORY_PID = (0x2D00, 0x2D01, 0x2D04, 0x2D05)
NETLINK_KOBJECT_UEVENT = 15

def main():
    while True:
        print("starting accessory task")
        try:
            accessory_task()
            time.sleep(5)
        except ValueError:
            pass
        print("accessory task finished")

def accessory_task():
    dev = usb.core.find(idVendor=ACCESSORY_VID);

    if dev is None:
        raise ValueError("No compatible device not found")

    print("compatible device found")

    if dev.idProduct in ACCESSORY_PID:
        print("device is in accessory mode")
    else:
        print("device is not in accessory mode yet")
        accessory(dev)
        dev = usb.core.find(idVendor=ACCESSORY_VID);
        if dev is None:
            raise ValueError("No compatible device not found")

        if dev.idProduct in ACCESSORY_PID:
            print("device is in accessory mode")
        else:
            raise ValueError("")

    dev.set_configuration()
    # even if the Android device is already in accessory mode
    # setting the configuration will result in the
    # UsbManager starting an "accessory connected" intent
    # and hence a small delay is required before communication
    # works properly
    time.sleep(1)

    dev = usb.core.find(idVendor = ACCESSORY_VID);
    cfg = dev.get_active_configuration()
    if_num = cfg[(0,0)].bInterfaceNumber
    intf = usb.util.find_descriptor(cfg, bInterfaceNumber = if_num)
    
    ep_out = usb.util.find_descriptor(
        intf,
        custom_match = \
        lambda e: \
            usb.util.endpoint_direction(e.bEndpointAddress) == \
            usb.util.ENDPOINT_OUT
    )

    ep_in = usb.util.find_descriptor(
        intf,
        custom_match = \
        lambda e: \
            usb.util.endpoint_direction(e.bEndpointAddress) == \
            usb.util.ENDPOINT_IN
    )
  

    writer_thread = threading.Thread(target = writer, args = (ep_out, ))
    writer_thread.start()
 
    length = -1 
    while True:
        try:
            data = ep_in.read(size = 1, timeout = 0)
            print("read value %d" % data[0])
        except usb.core.USBError:
            print("failed to send IN transfer")
            break

    writer_thread.join()
    print("exiting application")

def writer (ep_out):
    while True:
        try:
            length = ep_out.write([0], timeout = 0)
            print("%d bytes written" % length)
            time.sleep(0.5)
        except usb.core.USBError:
            print("error in writer thread")
            break


def accessory(dev):
    version = dev.ctrl_transfer(
                usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
                51, 0, 0, 2)

    print("version is: %d" % struct.unpack('<H',version)) 

    assert dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            52, 0, 0, MANUFACTURER) == len(MANUFACTURER) 
    
    assert dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            52, 0, 1, MODEL_NAME) == len(MODEL_NAME) 
    
    
    assert dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            52, 0, 2, DESCRIPTION) == len(DESCRIPTION) 

    assert dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            52, 0, 3, VERSION) == len(VERSION) 

    assert dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            52, 0, 4, URL) == len(URL) 

    assert dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            52, 0, 5, SERIAL_NUMBER) == len(SERIAL_NUMBER)
    
    dev.ctrl_transfer(
            usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
            53, 0, 0, None)

    time.sleep(1)

if __name__ == "__main__":
    main()

Let’s have a closer look at the code.

The accessory_task() function is crucial to this program. It will try to find an Android device that is already in “accessory mode” indicated by the product ID of the device.

ACCESSORY_VID = 0x18D1
ACCESSORY_PID = (0x2D00, 0x2D01, 0x2D04, 0x2D05)

...

if dev.idProduct in ACCESSORY_PID:
        print("device is in accessory mode")

...

If a device in accessory mode is found, it is configured for communication.

dev.set_configuration()
time.sleep(1)

This will launch a dialog on your Android device, either indicating that the installed Android application should be started or showing a hint to the website of the application. Some Android devices like the old Nexus S seem to be requiring some time to process the communication initialization here, we therefore add a one second sleep.

    dev = usb.core.find(idVendor = ACCESSORY_VID);
    cfg = dev.get_active_configuration()
    if_num = cfg[(0,0)].bInterfaceNumber
    intf = usb.util.find_descriptor(cfg, bInterfaceNumber = if_num)
     
    ep_out = usb.util.find_descriptor(
        intf,
        custom_match = \
        lambda e: \
            usb.util.endpoint_direction(e.bEndpointAddress) == \
            usb.util.ENDPOINT_OUT
    )
 
    ep_in = usb.util.find_descriptor(
        intf,
        custom_match = \
        lambda e: \
            usb.util.endpoint_direction(e.bEndpointAddress) == \
            usb.util.ENDPOINT_IN
    )

After setting the device into accessory mode, the data endpoints for IN and OUT transfers are referenced for further communication.

With the commands

length = ep_out.write(data, timeout = 0)
data = ep_in.read(size = 1, timeout = 0)

It is now possible to send data between Android and the Desktop back and forth.

As you may have noted, a separate thread is created to continuously send data to the Android device. This is necessary since the Accessory protocol has a known bug that does not permit to close the connection to the USB accessory from the Android side. However, every time data is received by the Android device, the main loop of the Java application can check if the channel should be closed and the resources freed.

    ...
    writer_thread = threading.Thread(target = writer, args = (ep_out, ))
    writer_thread.start()
    ...

def writer (ep_out):
    while True:
        try:
            length = ep_out.write([0], timeout = 0)
            print("%d bytes written" % length)
            time.sleep(0.5)
        except usb.core.USBError:
            print("error in writer thread")
            break

The actually interesting code for program implementation is in the main loop of the “accessory_task” function.

    while True:
        try:
            data = ep_in.read(size = 1, timeout = 0)
            print("read value %d" % data[0])
        except usb.core.USBError:
            print("failed to send IN transfer")
            break

For this particular example, the Android application will send the current state of a SeekBar slider in the main activity, which is then printed to the console. You could rewrite the code and actually do something more useful, such as adjusting the Desktop’s screen brightness, or changing the current window’s size or limit the bandwidth of your current download… you get the idea. (If by any chance you write the next 1 Billion $ App, please be a good sport and mention this tutorial though 😉 )
screen

There is one particular flaw at this point. In the main() function, communication will be initiated every five seconds if there is an error (i.e. no device found) which causes unnecessary CPU wakeups. If you are working on Linux, you will instead be able to catch an “USB plugged” event and fire up your application logic selectively.
In order to do so, let’s change the main() method to listen to kernel messages indicating a change of an UEVENT state. Note that there might occur uninteresting events as well, but this will be dealt with by throwing a ValueError exception when unsuccessfully connecting to a potential accessory.

def main():
    while True:
        print("starting accessory task")
        try:
            sock = socket.socket(
                socket.AF_NETLINK,
                socket.SOCK_RAW,
                NETLINK_KOBJECT_UEVENT)
    
            sock.bind((os.getpid(), -1))
            sock.recv(512)
            sock.close()
            accessory_task()
        except ValueError:
            pass
        print("accessory task finished")

At this point you should be able to run the application. Make sure you disable ADB over USB since it will deny the communication as it claims the device first.

You could even enhance the script some more and extract information like vendor and product id from the uevent data. Have a look at the more sophisticated version of accessory.py. Note that the netlink part will most probably only run on Linux.

user@host:~/accessory$ chmod a+x accessory.py
user@host:~/accessory$ adb kill-server
user@host:~/accessory$ sudo ./accessory.py

Now connect your Android device and see a dialog pop up.

And the python application output should look something like this:

starting accessory task
compatible device found
device is not in accessory mode yet
version is: 2
device is in accessory mode

You are now able to download the prepared APK from the website or compile it yourself:
Sources for a simple Android accessory: SimpleAccessory.tar.gz

As soon as the application is installed, another dialog appears when connecting the device:

After giving the application permission to start the activity and moving the slider around, the output on the python side will look similar to this:

read value 210
1 bytes written
read value 211
read value 212
read value 211
read value 210
read value 208
read value 194
read value 176
read value 160
read value 147
read value 133
read value 122
read value 115
read value 101
read value 87
read value 78
read value 69
read value 62
read value 56
read value 54
read value 49
read value 46
read value 44
read value 42
read value 41
read value 44
read value 58
read value 72
read value 81

Note: If your Desktop does not find the connected Accessory, you should check if the Vendor ID of your Android Device is different from “ACCESSORY_VID = 0x18D1” and hence you should adjust it. You can check it using the lsusb command from the terminal on a Linux machine, or find it programmatically via pyusb. Have a look at the somewhat more sophisticated version of accessory.py if you are interested in this.

Enabling pyusb for the Arduino Yún

Sadly, the standard image of the Arduino Yún does not include pyusb, yet it includes the libusb1.0 library, which makes it kind of easy to add it to the build.

First, log into your Arduino via SSH and then install pyusb. Using opkg you can easily install the python dependency from the repositories. As mentioned in the Yún guide, the package index is not saved persistently and thus needs to be refreshed on every boot using “opkg update”. In the following instructions adjust the IP address for your Yún device.

user@host:~$ ssh -l root 192.168.1.99
root@192.168.1.99's password:
arduino
root@arduino:~# opkg update
Downloading http://download.linino.org/dogstick/all-in-one/latest/packages//Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker.
Downloading http://download.linino.org/dogstick/all-in-one/latest/packages//Packages.sig.
Signature check passed.
root@arduino:~# opkg install pyusb
Installing pyusb (0.4.2-1) to root...
Downloading http://download.linino.org/dogstick/all-in-one/latest/packages//pyusb_0.4.2-1_ar71xx.ipk.
Configuring pyusb.
root@arduino:~# curl http://www.nexus-computing.ch/files/accessory.py > accessory.py
root@arduino:~# curl http://www.nexus-computing.ch/files/attribs.py > attribs.py
root@arduino:~# chmod a+x accessory.py
root@arduino:~# ./accessory.py

This should deploy and start the pyusb setup as well as the application setup. If you connect your Android device to your Arduino Yún, the same output as on your Desktop machine should follow.

Using hotplug.d on the Arduino Yún

As you may have noticed, when executed with no arguments, the accessory.py script will run in a loop and listen to a socket for udev messages from the Kernel to be notified once a USB device is plugged in. While this is already a significant upgrade to the polling 5 seconds approach, the application is still using memory when it is not used.

OpenWrt features the hotplug.d system which is a very light weight variant of udev. Modify the file “/etc/hotplug.d/usb/10-usb” and adjust it to start the python script.

#!/bin/sh

# Copyright (C) 2009 OpenWrt.org

case "$ACTION" in
        add)
                /root/accessory.py $PRODUCT > /tmp/acc.log
                # update LEDs
                ;;
        remove)
                # update LEDs
                ;;
esac

This shell script will be started whenever a USB device is connected. The python script “accessory.py” will take care of connecting to the device using the $PRODUCT parameter. This way the script is only fired when a device actually connects to the Yún.

Did something not work for your setup or did you find a bug in the code? Please post a comment so we can keep the guide up to date.

We hope this tutorial got your project started and cleared some of your questions about Android and the Accessory Protocol. Give us a +1 if you liked it or even better leave a comment.

Python Application Source – accessory.py, attribs.py
Android Application Source – SimpleAccessory.tar.gz

15 Replies to “Turn your (Linux-) Desktop / Arduino Yún / Raspberry Pi into an USB Android Accessory: How to use the Android Accessory Protocol with pyusb”

  1. Hello.

    I have a received a pingback from this site about a post on pysub installation in Ubuntu. Well, it looks like we have written very similar posts about coding an Android accessory in Python :

    http://zombiebrainzjuice.cc/python-android-accessory-usb-proof-of-concept/

    Just to let you know, your post for the same feature in C has been really helpful (you are number 1 in my ressources list on the link above).

    I want to use this protocol in a robotics project. It is basically a “HRI” (human robot interface) project where the cognitive part will be assumed by an Android device. Android devices are low-cost and full-features sensors. Google now (or Siri on iPhone) is the kind of interface I want to build but for a robot.

    Everything is stored on GutHub, including the documentation:

    https://github.com/Arn-O/kuk-A-droid

    The progress are quite slow, since I lost a lot of time with ROS and Gazebo for the simulation part. And everything is highly experimental after all.

    I would say there is a good match between my project and what you want to develop for Android platform, especially for the cognitive part.

    So if you are interested in a collaboration, send me a mail.

    I will also send a mail to nexus-computing.

    Arnaud

  2. Excellent post – just what i needed!

    My only problem is that I only had it working a few times (like 2 or 3 maybe). Almost every time I try to get it started the host manages to get my phone (Samsung Galaxy IV) into accesory mode. Then in the writer thread the host (appearantly) manages to write to the device once (length = ep_out.write([0], timeout = 0)), and then it hangs. Output is:

    jakob@jakob-hp:~/dev/aoa/pyserver$ sudo python accessory.py
    [sudo] password for jakob:
    no arguments given, starting in daemon mode
    compatible device found
    device is not in accessory mode yet, VID 04E8
    version is: 2
    device is in accessory mode
    1 bytes written

    Any idea what might be happening?

    My system:
    Linux jakob-hp 3.11.0-14-generic #21-Ubuntu SMP Tue Nov 12 17:07:40 UTC 2013 i686 i686 i686 GNU/Linux

    Best regards Jakob

    1. I have the same problem, except in my case the python code terminates as soon as the device is in accessory mode. So I don’t get to see the ‘1 bytes written’ line.

    2. Did you install a fresh pyusb library?

      Because I had a similar problem. I installed a new pyusb library (v1.0.X), and it just so happens the author of this guide uses 0.4.X. So some code had to be adapted to the new lib:

      (After if dev is None…raise…)
      dev.set_configuration()
      cfg = dev.get_active_configuration()
      intf = cfg[(0, 0)]

      Hope this helps someone else 🙂

  3. Hello,

    In the AccessoryEngine class, Android side, mOutputStream.flush() as to be added after mOutputStream.write(data);
    If not the data are not sent. Certainly waiting for the internal buffer to be full.

    Very good job.

    1. Hi Samuel!

      Thank you so much for pointing out the broken links, the most likely did not survive our last website update. Anyways we uploaded the example files to a new location and fixed the link in the post. Glad you liked the article and thanks again for helping out!

      All the best
      Manuel

  4. I’m using this command to make accessories mode “python android-usb-audio.py ” and the phone is going to accessories mode but the problem is i need hdmi output as well as usb o/p and audio should play in hdmi-tv/usb-audio. I configure all the settings provide above but not getting sound.
    I m also tried using arecord & aplay for normal mic&Speaker ,It was working fine but when i use my android phone in accessories mode as a audio device i’m just getting a file of 44 bytes but nothing is recording in that file. please help me.
    pi@raspberrypi ~/usb $ cat /proc/asound/cards
    0 [ALSA ]: bcm2835 – bcm2835 ALSA
    bcm2835 ALSA
    1 [U0x6e60x7210 ]: USB-Audio – USB Device 0x6e6:0x7210
    USB Device 0x6e6:0x7210 at usb-bcm2708_usb-1.2, full speed
    2 [Lenovo ]: USB-Audio – Lenovo
    Lenovo Lenovo at usb-bcm2708_usb-1.3, high speed

    pi@raspberrypi ~/usb $ lsusb
    Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
    Bus 001 Device 006: ID 06e6:7210 Tiger Jet Network, Inc. Composite Device
    Bus 001 Device 008: ID 18d1:2d03 Google Inc.

    pi@raspberrypi ~/usb $ arecord -l
    **** List of CAPTURE Hardware Devices ****
    card 1: U0x6e60x7210 [USB Device 0x6e6:0x7210], device 0: USB Audio [USB Audio]
    Subdevices: 1/1
    Subdevice #0: subdevice #0
    card 2: Lenovo [Lenovo], device 0: USB Audio [USB Audio]
    Subdevices: 1/1
    Subdevice #0: subdevice #0

    pi@raspberrypi ~/android-usb-pi $ aplay -l
    **** List of PLAYBACK Hardware Devices ****
    card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
    Subdevices: 8/8
    Subdevice #0: subdevice #0
    Subdevice #1: subdevice #1
    Subdevice #2: subdevice #2
    Subdevice #3: subdevice #3
    Subdevice #4: subdevice #4
    Subdevice #5: subdevice #5
    Subdevice #6: subdevice #6
    Subdevice #7: subdevice #7
    card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
    Subdevices: 1/1
    Subdevice #0: subdevice #0
    card 1: U0x6e60x7210 [USB Device 0x6e6:0x7210], device 0: USB Audio [USB Audio]
    Subdevices: 1/1
    Subdevice #0: subdevice #0

    Android mobile is not showing in aplay -l command as a play device but it showing in arecord -l as a recording device.But sound is not recorded.

  5. Hi, the guide is very interesting. Thanks for it. When I connect the phone, I get an error related to the read function, as follows. Any help?

    no arguments given, starting in daemon mode
    compatible device found
    device is not in accessory mode yet, VID 0B05
    version is: 2
    device is in accessory mode
    Traceback (most recent call last):
    File “accessory.py”, line 220, in
    main()
    File “accessory.py”, line 51, in main
    accessory_task(vid)
    File “accessory.py”, line 153, in accessory_task
    data = ep_in.read(size = 1, timeout = 0)
    TypeError: read() got an unexpected keyword argument ‘size’
    error in writer thread

  6. Excellent tutorial Manuel!. I wanted to know why the python code is not able to trigger the SimpleAccessory App on my phone. I get the following message whenever the PC tries to talk with the phone “No installed apps work with this USB Accessory”. Kindly let me know why such an error is existing. Thanking you! In anticipation of your reply.

Leave a Reply

Your email address will not be published. Required fields are marked *


*