Thursday, February 20, 2014

Send Hello to Arduino from Android in USB Host Mode

This example send bytes of "Hello..." to Arduino Esplora board from Android in USB Host Mode. Please notice that it's just a middle stage in my exercise in USB Host Mode, not a final and mature example.


In Android Side:

MainActivity.java
package com.example.androidusbhost;

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

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.widget.TextView;
import android.widget.Toast;
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 {

 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;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  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();
 }
 
 private void connectUsb(){
  
  Toast.makeText(MainActivity.this, 
    "connectUsb()", 
    Toast.LENGTH_LONG).show();
  textStatus.setText("connectUsb()");

  searchEndPoint();

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

 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;
 }
 
 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){
   Toast.makeText(MainActivity.this, 
     "device not found", 
     Toast.LENGTH_LONG).show();
   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;
    usbResult = usbDeviceConnection.controlTransfer(
      0x21,        //requestType
      RQSID_SET_CONTROL_LINE_STATE, //SET_CONTROL_LINE_STATE 
      0,     //value
      0,     //index
      null,    //buffer
      0,     //length
      0);    //timeout
    
    Toast.makeText(MainActivity.this, 
      "controlTransfer(SET_CONTROL_LINE_STATE): " + usbResult, 
      Toast.LENGTH_LONG).show();
    
    //baud rate = 9600
    //8 data bit
    //1 stop bit
    byte[] encodingSetting = 
      new byte[] {(byte)0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
    usbResult = usbDeviceConnection.controlTransfer(
      0x21,       //requestType
      RQSID_SET_LINE_CODING,   //SET_LINE_CODING
      0,      //value
      0,      //index
      encodingSetting,  //buffer
      7,      //length
      0);     //timeout
    Toast.makeText(MainActivity.this, 
      "controlTransfer(RQSID_SET_LINE_CODING): " + usbResult, 
      Toast.LENGTH_LONG).show();
    
    byte[] bytesHello = 
      new byte[] {(byte)'H', 'e', 'l', 'l', 'o', ' ', 
      'f', 'r', 'o', 'm', ' ', 
      'A', 'n', 'd', 'r', 'o', 'i', 'd'};
    usbResult = usbDeviceConnection.bulkTransfer(
      endpointOut, 
      bytesHello, 
      bytesHello.length, 
      0);
    Toast.makeText(MainActivity.this, 
      "bulkTransfer: " + usbResult, 
      Toast.LENGTH_LONG).show();
   }

  }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) {
         Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_LONG).show();
         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)) {
      
      Toast.makeText(MainActivity.this, 
        "ACTION_USB_PERMISSION", 
        Toast.LENGTH_LONG).show();
      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("");
     }
    }
  
 };

}

The setting of usbDeviceConnection.controlTransfer() inside setupUsbComm() is copied from the post: Android USB Host + Arduino: How to communicate without rooting your Android Tablet or Phone

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://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <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:

testSerial.ino
#include <Esplora.h>
#include <TFT.h>
#include <SPI.h>

int prevSw1 = HIGH;
int incomingByte = 0;
String charsIn = "";
char printout[20];  //max char to print: 20
 
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);
    
}
 
void loop() {
    int sw1 = Esplora.readButton(SWITCH_1);
    if(sw1 != prevSw1){
      if(sw1 == LOW){
        Serial.println("Hello from Arduino Esplora");
      }
      prevSw1 = sw1;
    }
    
    while (Serial.available()) {
      char charRead = Serial.read();
      charsIn.concat(charRead);
    }
    if(charsIn != ""){
      Serial.println("How are you, " + charsIn);
      charsIn.toCharArray(printout, 21);
      EsploraTFT.background(0,0,0);
      EsploraTFT.text(printout, 0, 10);
      charsIn = "";
    }
}

It's same as the code in my another blog: Serial communication between Arduino Esplora and PC



Step-by-step: Android USB Host Mode programming



13 comments:

Michael Cifka said...

After a few hours, I finally got this working with Arduino Uno R3 and Samsung S3 Mini (without rooting). It wasn't exactly easy though.

Firstly, ProductID needed to be changed to 67 for Arduino Uno R3.

Secondly, it was needed to switch Samsung S3 Mini into the USB Host mode. This can be done by connecting the OTG cable to an USB power source and restarting the phone. Most easily, you can use a 2x USB cable (also called Y cable) commonly used for portable hard drives to interconnect OTG cable with a laptop. After restarting the phone, you can disconnect the 2x USB cable and connect Arduino. The phone will stay in the USB Host mode until the next restart (which also means that USB charging won't work so make sure to start with a fully charged phone).

Thirdly, this app was always crashing on my Samsung S3 Mini. I have fixed this by commenting out the function calls to showRawDescriptors() function which was crashing it.

Cheers for this great app and gut luck with Android and Arduino development!

Erik said...

Hello Michael Cifka,

Thx for your comment :)

Michael Cifka said...

Here goes a video of my version working: http://www.youtube.com/watch?v=RLtCVy_M4lw

Jorge Pujol said...

good day, I can implement this code with arduino mega ADK, which changes you should make, thanks and sorry for the English.

Anonymous said...

What is the value returned by the "usbDeviceConnection.releaseInterface(usbInterface);" method? I always get false.

teamfindr said...

Sir please help me to do the reverse. Print arduino data on android

Erik said...

hello sreenath tk,

please read more examples in Send data from Android to Arduino Uno, in USB Host Mode.

Unknown said...

he works on Arduino méga 256

Unknown said...

Is there any way to send "Hello" from Android phone to another Android phone?

uitwerkingen for life said...

Hello Eric,

This video looks a lot like something i am looking for! Do you know by any chance if the USB host also lets you control a stepper motor (using an h-bridge for an example).
Our goal is to connect two dc motors over usb cable to an android phone and control the motors with an application.

looking forward to an answer to help me further on the way (or that makes me decide something else).

Anchal said...

I am an android app developer and new to embedded systems.
While doing the control transfer, I receive -1 (ie failure) in the Toast.
Can you please tell me why is it so?(I am using FTDI board for communication between mobile app and FTDI board)
While doing control transfer for RQSID_SET_CONTROL_LINE_STATE, Why buffer is sent as null.
although in case of RQSID_SET_LINE_CODING, buffer is set to be a byte array of 255.
Please answer my queries as it's urgent for me to close this ASAP.

Unknown said...

Blogger Anchal said...
I am an android app developer and new to embedded systems.
While doing the control transfer, I receive -1 (ie failure) in the Toast.
Can you please tell me why is it so?(I am using FTDI board for communication between mobile app and FTDI board)
While doing control transfer for RQSID_SET_CONTROL_LINE_STATE, Why buffer is sent as null.
although in case of RQSID_SET_LINE_CODING, buffer is set to be a byte array of 255.
Please answer my queries as it's urgent for me to close this ASAP.

October 12, 2017 at 1:10 AM
------------------------------------------------------------------------------------
Hi, are you fix the problem?, i have the same problem :( and i don't know do :(
i wait you can help me
:)

Unknown said...

Not all the USB device need to do control transfer to set the configure. For my case, my USB device has preset configure, I skipped the control transfer & do bulk transfer directly.