Android send command to Arduino in USB Host mode

This example show how to send command from Android to Arduino Esplora board, in USB Host Mode, to control the LED and Screen of Arduino Esplora. When user toggle the LED by clicking on the button, or change the screen color by slideing the bars, the commands will be add in a command queue, and then send to Arduino in background.


In Android Side:

MainActivity.java.
package com.example.androidusbhost;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

public class MainActivity extends Activity {

ToggleButton btnLED;
SeekBar barR, barG, barB;
TextView textRx;

TextView textInfo;
TextView textSearchedEndpoint;

TextView textDeviceName;
TextView textStatus;

private static final int targetVendorID= 9025;
private static final int targetProductID = 32828;
UsbDevice deviceFound = null;
UsbInterface usbInterfaceFound = null;
UsbEndpoint endpointIn = null;
UsbEndpoint endpointOut = null;

private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
PendingIntent mPermissionIntent;

UsbInterface usbInterface;
UsbDeviceConnection usbDeviceConnection;

ThreadUsbTx threadUsbTx;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

btnLED = (ToggleButton)findViewById(R.id.ledbutton);
btnLED.setOnClickListener(btnLEDOnClickListener);

barR = (SeekBar)findViewById(R.id.rbar);
barG = (SeekBar)findViewById(R.id.gbar);
barB = (SeekBar)findViewById(R.id.bbar);
barR.setOnSeekBarChangeListener(colorOnSeekBarChangeListener);
barG.setOnSeekBarChangeListener(colorOnSeekBarChangeListener);
barB.setOnSeekBarChangeListener(colorOnSeekBarChangeListener);

textRx = (TextView)findViewById(R.id.textrx);

textStatus = (TextView)findViewById(R.id.textstatus);

textDeviceName = (TextView)findViewById(R.id.textdevicename);
textInfo = (TextView) findViewById(R.id.info);
textSearchedEndpoint = (TextView)findViewById(R.id.searchedendpoint);

//register the broadcast receiver
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

registerReceiver(mUsbDeviceReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED));
registerReceiver(mUsbDeviceReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));

connectUsb();
}

@Override
protected void onDestroy() {
releaseUsb();
unregisterReceiver(mUsbReceiver);
unregisterReceiver(mUsbDeviceReceiver);
super.onDestroy();
}

OnClickListener btnLEDOnClickListener =
new OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

int usbResult;
byte[] cmdLED;
if(((ToggleButton)v).isChecked()){
cmdLED = new byte[] {(byte)'L', 'E', 'D', 'O', 'N', '\n'};
}else{
cmdLED = new byte[] {(byte)'L', 'E', 'D', 'O', 'F', 'F', '\n'};
}
threadUsbTx.insertCmd(cmdLED);
}

};

OnSeekBarChangeListener colorOnSeekBarChangeListener =
new OnSeekBarChangeListener(){

byte[] toAscii = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {

byte[] cmdCol = new byte[] {(byte)'C', 'O', 'L', '#',
'0', '0', '0', '0', '0', '0', '\n'};

cmdCol[4] = toAscii[(barR.getProgress()>>4) & 0x0f];
cmdCol[5] = toAscii[barR.getProgress() & 0x0f];
cmdCol[6] = toAscii[(barG.getProgress()>>4) & 0x0f];
cmdCol[7] = toAscii[barG.getProgress() & 0x0f];
cmdCol[8] = toAscii[(barB.getProgress()>>4) & 0x0f];
cmdCol[9] = toAscii[barB.getProgress() & 0x0f];
threadUsbTx.insertCmd(cmdCol.clone());
}

};

private void connectUsb(){

btnLED.setEnabled(false);
barR.setEnabled(false);
barG.setEnabled(false);
barB.setEnabled(false);

Toast.makeText(MainActivity.this,
"connectUsb()",
Toast.LENGTH_LONG).show();
textStatus.setText("connectUsb()");

searchEndPoint();

if(usbInterfaceFound != null){
setupUsbComm();

threadUsbTx = new ThreadUsbTx(usbDeviceConnection, endpointOut);
threadUsbTx.start();

btnLED.setEnabled(true);
barR.setEnabled(true);
barG.setEnabled(true);
barB.setEnabled(true);
//Turn On LED once connected
btnLED.setChecked(true);
barR.setProgress(0x80);
barG.setProgress(0x80);
barB.setProgress(0x80);

threadUsbTx.insertCmd(
new byte[] {(byte)'L', 'E', 'D', 'O', 'N', '\n'});
threadUsbTx.insertCmd(
new byte[] {(byte)'C', 'O', 'L', '#',
'8', '0', '8', '0', '8', '0', '\n'});

}

}

private void releaseUsb(){

Toast.makeText(MainActivity.this,
"releaseUsb()",
Toast.LENGTH_LONG).show();
textStatus.setText("releaseUsb()");

if(usbDeviceConnection != null){
if(usbInterface != null){
usbDeviceConnection.releaseInterface(usbInterface);
usbInterface = null;
}
usbDeviceConnection.close();
usbDeviceConnection = null;
}

deviceFound = null;
usbInterfaceFound = null;
endpointIn = null;
endpointOut = null;

btnLED.setEnabled(false);
barR.setEnabled(false);
barG.setEnabled(false);
barB.setEnabled(false);

if(threadUsbTx!=null){
threadUsbTx.setRunning(false);
}
}

private void searchEndPoint(){

textInfo.setText("");
textSearchedEndpoint.setText("");

usbInterfaceFound = null;
endpointOut = null;
endpointIn = null;

//Search device for targetVendorID and targetProductID
if(deviceFound == null){
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();

if(device.getVendorId()==targetVendorID){
if(device.getProductId()==targetProductID){
deviceFound = device;
}
}
}
}

if(deviceFound==null){
textStatus.setText("device not found");
}else{
String s = deviceFound.toString() + "\n" +
"DeviceID: " + deviceFound.getDeviceId() + "\n" +
"DeviceName: " + deviceFound.getDeviceName() + "\n" +
"DeviceClass: " + deviceFound.getDeviceClass() + "\n" +
"DeviceSubClass: " + deviceFound.getDeviceSubclass() + "\n" +
"VendorID: " + deviceFound.getVendorId() + "\n" +
"ProductID: " + deviceFound.getProductId() + "\n" +
"InterfaceCount: " + deviceFound.getInterfaceCount();
textInfo.setText(s);

//Search for UsbInterface with Endpoint of USB_ENDPOINT_XFER_BULK,
//and direction USB_DIR_OUT and USB_DIR_IN

for(int i=0; i<deviceFound.getInterfaceCount(); i++){
UsbInterface usbif = deviceFound.getInterface(i);

UsbEndpoint tOut = null;
UsbEndpoint tIn = null;

int tEndpointCnt = usbif.getEndpointCount();
if(tEndpointCnt>=2){
for(int j=0; j<tEndpointCnt; j++){
if(usbif.getEndpoint(j).getType() ==
UsbConstants.USB_ENDPOINT_XFER_BULK){
if(usbif.getEndpoint(j).getDirection() ==
UsbConstants.USB_DIR_OUT){
tOut = usbif.getEndpoint(j);
}else if(usbif.getEndpoint(j).getDirection() ==
UsbConstants.USB_DIR_IN){
tIn = usbif.getEndpoint(j);
}
}
}

if(tOut!=null && tIn!=null){
//This interface have both USB_DIR_OUT
//and USB_DIR_IN of USB_ENDPOINT_XFER_BULK
usbInterfaceFound = usbif;
endpointOut = tOut;
endpointIn = tIn;
}
}

}

if(usbInterfaceFound==null){
textSearchedEndpoint.setText("No suitable interface found!");
}else{
textSearchedEndpoint.setText(
"UsbInterface found: " + usbInterfaceFound.toString() + "\n\n" +
"Endpoint OUT: " + endpointOut.toString() + "\n\n" +
"Endpoint IN: " + endpointIn.toString());
}
}
}

private boolean setupUsbComm(){

//for more info, search SET_LINE_CODING and
//SET_CONTROL_LINE_STATE in the document:
//"Universal Serial Bus Class Definitions for Communication Devices"
//at http://adf.ly/dppFt
final int RQSID_SET_LINE_CODING = 0x20;
final int RQSID_SET_CONTROL_LINE_STATE = 0x22;

boolean success = false;

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Boolean permitToRead = manager.hasPermission(deviceFound);

if(permitToRead){
usbDeviceConnection = manager.openDevice(deviceFound);
if(usbDeviceConnection != null){
usbDeviceConnection.claimInterface(usbInterfaceFound, true);

showRawDescriptors(); //skip it if you no need show RawDescriptors

int usbResult;

/*
* D7: 0 - Host to Device
* 1 = Device to Host
* D6..5 Type
* 0 = Standard
* 1 = Class
* 2 = Vendor
* 3 = Reserved
* D4..0 Recipient
* 0 = Device
* 1 = Interface
* 2 = Endpoint
* 3 = Other
*/

//int requestType = 0x21;
int requestType = 0x42;

usbResult = usbDeviceConnection.controlTransfer(
requestType,
RQSID_SET_CONTROL_LINE_STATE, //SET_CONTROL_LINE_STATE
0, //value
0, //index
null, //buffer
0, //length
0); //timeout

//baud rate = 9600
//8 data bit
//1 stop bit
byte[] encodingSetting =
new byte[] {(byte)0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
usbResult = usbDeviceConnection.controlTransfer(
requestType,
RQSID_SET_LINE_CODING, //SET_LINE_CODING
0, //value
0, //index
encodingSetting, //buffer
7, //length
0); //timeout

}

}else{
manager.requestPermission(deviceFound, mPermissionIntent);
Toast.makeText(MainActivity.this,
"Permission: " + permitToRead,
Toast.LENGTH_LONG).show();
textStatus.setText("Permission: " + permitToRead);
}

return success;
}

private void showRawDescriptors(){
final int STD_USB_REQUEST_GET_DESCRIPTOR = 0x06;
final int LIBUSB_DT_STRING = 0x03;

byte[] buffer = new byte[255];
int indexManufacturer = 14;
int indexProduct = 15;
String stringManufacturer = "";
String stringProduct = "";

byte[] rawDescriptors = usbDeviceConnection.getRawDescriptors();

int lengthManufacturer = usbDeviceConnection.controlTransfer(
UsbConstants.USB_DIR_IN|UsbConstants.USB_TYPE_STANDARD, //requestType
STD_USB_REQUEST_GET_DESCRIPTOR, //request ID for this transaction
(LIBUSB_DT_STRING << 8) | rawDescriptors[indexManufacturer], //value
0, //index
buffer, //buffer
0xFF, //length
0); //timeout
try {
stringManufacturer = new String(buffer, 2, lengthManufacturer-2, "UTF-16LE");
} catch (UnsupportedEncodingException e) {
textStatus.setText(e.toString());
}

int lengthProduct = usbDeviceConnection.controlTransfer(
UsbConstants.USB_DIR_IN|UsbConstants.USB_TYPE_STANDARD,
STD_USB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8) | rawDescriptors[indexProduct],
0,
buffer,
0xFF,
0);
try {
stringProduct = new String(buffer, 2, lengthProduct-2, "UTF-16LE");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

textStatus.setText("Manufacturer: " + stringManufacturer + "\n" +
"Product: " + stringProduct);
}

private final BroadcastReceiver mUsbReceiver =
new BroadcastReceiver() {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {

textStatus.setText("ACTION_USB_PERMISSION");

synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
connectUsb();
}
}
else {
Toast.makeText(MainActivity.this,
"permission denied for device " + device,
Toast.LENGTH_LONG).show();
textStatus.setText("permission denied for device " + device);
}
}
}
}
};

private final BroadcastReceiver mUsbDeviceReceiver =
new BroadcastReceiver() {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {

deviceFound = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Toast.makeText(MainActivity.this,
"ACTION_USB_DEVICE_ATTACHED: \n" +
deviceFound.toString(),
Toast.LENGTH_LONG).show();
textStatus.setText("ACTION_USB_DEVICE_ATTACHED: \n" +
deviceFound.toString());

connectUsb();
}else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {

UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

Toast.makeText(MainActivity.this,
"ACTION_USB_DEVICE_DETACHED: \n" +
device.toString(),
Toast.LENGTH_LONG).show();
textStatus.setText("ACTION_USB_DEVICE_DETACHED: \n" +
device.toString());

if(device!=null){
if(device == deviceFound){
releaseUsb();
}
}

textInfo.setText("");
}
}

};

class ThreadUsbTx extends Thread{
boolean running;

UsbDeviceConnection txConnection;
UsbEndpoint txEndpoint;
Queue<byte[]> cmdQueue;
byte[] cmdToSent;

ThreadUsbTx(UsbDeviceConnection conn, UsbEndpoint endpoint){
txConnection = conn;
txEndpoint = endpoint;
cmdQueue = new LinkedList<byte[]>();
cmdToSent = null;
running = true;
}

public void setRunning(boolean r){
running = r;
}

public void insertCmd(byte[] cmd){
synchronized(cmdQueue){
cmdQueue.add(cmd);
}
}

@Override
public void run() {

while(running){

synchronized(cmdQueue){
if(cmdQueue.size()>0){
cmdToSent = cmdQueue.remove();
}
}

if(cmdToSent!=null){
int usbResult = usbDeviceConnection.bulkTransfer(
endpointOut,
cmdToSent,
cmdToSent.length,
0);

final String s = new String(cmdToSent);

runOnUiThread(new Runnable(){

@Override
public void run() {
Toast.makeText(MainActivity.this,
s,
Toast.LENGTH_SHORT).show();
}});

cmdToSent = null;
}
}
}
}
}

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:autoLink="web"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold" />

<ToggleButton
android:id="@+id/ledbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textOn="LED ON"
android:textOff="LED OFF" />
<SeekBar
android:id="@+id/rbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:progress="0"/>
<SeekBar
android:id="@+id/gbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:progress="0"/>
<SeekBar
android:id="@+id/bbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:progress="0"/>
<TextView
android:id="@+id/textrx"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#ff0000" />

<TextView
android:id="@+id/textstatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold" />

<TextView
android:id="@+id/textdevicename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold|italic" />

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<TextView
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/searchedendpoint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold" />

</LinearLayout>
</ScrollView>

</LinearLayout>

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.androidusbhost"
android:versionCode="1"
android:versionName="1.0" >

<uses-feature android:name="android.hardware.usb.host" />
<uses-sdk
android:minSdkVersion="13"
android:targetSdkVersion="18" />

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.androidusbhost.MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|orientation" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />

<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
</intent-filter>

</activity>
</application>

</manifest>

download filesDownload the files.

In Arduino Side: re-use the code in my another blog Node.js + Arduino, running on PC.
#include <Esplora.h>
#include <TFT.h>
#include <SPI.h>

int MAX_CMD_LENGTH = 10;
char cmd[10];
int cmdIndex;
char incomingByte;

int prevSlider = 0;

void setup() {

EsploraTFT.begin();
EsploraTFT.background(0,0,0);
EsploraTFT.stroke(255,255,255); //preset stroke color

//Setup Serial Port with baud rate of 9600
Serial.begin(9600);

//indicate start
Esplora.writeRGB(255, 255, 255);
delay(250);
Esplora.writeRGB(0, 0, 0);

cmdIndex = 0;

}

void loop() {

if (incomingByte=Serial.available()>0) {

char byteIn = Serial.read();
cmd[cmdIndex] = byteIn;

if(byteIn=='\n'){
//command finished
cmd[cmdIndex] = '\0';
//Serial.println(cmd);
cmdIndex = 0;

String stringCmd = String(cmd);

if(strcmp(cmd, "LEDON") == 0){
//Serial.println("Command received: LEDON");
Esplora.writeRGB(255, 255, 255);
}else if (strcmp(cmd, "LEDOFF") == 0) {
//Serial.println("Command received: LEDOFF");
Esplora.writeRGB(0, 0, 0);
}else if(stringCmd.substring(0,4)="COL#"){
//Serial.println("Command received: COL#");
if(stringCmd.length()==10){
char * pEnd;
long int rgb = strtol(&cmd[4], &pEnd, 16);
int r = (rgb & 0xff0000) >> 16;
int g = (rgb & 0xff00) >> 8;
int b = rgb & 0xff;
//Serial.println(r);
//Serial.println(g);
//Serial.println(b);
EsploraTFT.background(b,g,r);
}
}else{
//Serial.println("Command received: unknown!");
}

}else{
if(cmdIndex++ >= MAX_CMD_LENGTH){
cmdIndex = 0;
}
}
}

//Read Slider
int slider = Esplora.readSlider();
//convert slider value from [0-1023] to [0x00-0xFF]
slider = slider>>2 & 0xff;

if(slider!=prevSlider){
prevSlider = slider;

String stringSlider = String(slider, HEX);
Serial.println("SLD#" + stringSlider +"\n");
}

}

Remark:
The device-to-host (Arduino-to-Android) communication have NOT been implemented on Android side in this example.


Step-by-step: Android USB Host Mode programming