Skip to content

Instantly share code, notes, and snippets.

@nathansizemore
Created December 15, 2014 20:37
Show Gist options
  • Save nathansizemore/ef43a545ab9a2fa13ae5 to your computer and use it in GitHub Desktop.
Save nathansizemore/ef43a545ab9a2fa13ae5 to your computer and use it in GitHub Desktop.
Android UsbHost Service
/*
* Service for UsbHost Communication with an Arduino Due
*/
import java.util.Arrays;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
public class ArduinoService extends Service {
// Logging support
private final String TAG = "ArduinoService";
private final boolean DEBUG = true;
// UsbInterface for bulk transfers
private final int DATA_INTERFACE = 1;
// Size of complete message packet
private final int MSG_SIZE = 6;
private final byte START_BYTE = (byte)0xFF; // TODO - Change for specific implementation
// I/O Threads
private ReceiverThread receiverThread;
private SenderThread senderThread;
// Usbdevice variables
private volatile UsbDevice usbDevice;
private volatile UsbDeviceConnection usbConnection;
private volatile UsbEndpoint readEndpoint;
private volatile UsbEndpoint writeEndpoint;
// Intents this service broadcasts
public static final String ARDUINO_CONNECTED = "com.package.intent.ARDUINO_CONNECTED";
public static final String ARDUINO_DISCONNECTED = "com.package.intent.ARDUINO_DISCONNECTED";
// Intents this service listens for
public static final String READ_AVAILABLE = "com.package.intent.READ_AVAILABLE";
public static final String WRITE_AVAILABLE = "com.package.intent.WRITE_AVAILABLE";
public static final String STOP_SERVICE = "com.package.intent.STOP_SERVICE";
// Serializable messages to senderThread for msg.what property
public static final int KILL = 99;
// Various flags
private volatile boolean readAvailable = false;
private volatile boolean writeAvailable = false;
private volatile boolean usbResourcesReleased = false;
/*
* Service is not meant to be bound to
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
if (DEBUG) Log.d(TAG, "onCreate");
super.onCreate();
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(ArduinoService.READ_AVAILABLE);
filter.addAction(ArduinoService.WRITE_AVAILABLE);
filter.addAction(ArduinoService.STOP_SERVICE);
registerReceiver(receiver, filter);
}
@Override
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy");
super.onDestroy();
killReceiverThread();
killSenderThread();
if (!usbResourcesReleased) {
releaseUsbResources();
}
unregisterReceiver(receiver);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) Log.d(TAG, "onStartCommand");
if (intent == null) {
stopSelf();
return Service.START_NOT_STICKY;
}
if (!intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (DEBUG) Log.d(TAG, "Permission denied");
if (!usbResourcesReleased) {
releaseUsbResources();
}
stopSelf();
return Service.START_NOT_STICKY;
}
if (readAvailable && writeAvailable) {
return Service.START_NOT_STICKY;
}
if (DEBUG) Log.d(TAG, "Permission granted");
usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (!initDevice()) {
if (DEBUG) Log.d(TAG, "Device initialization failed");
killReceiverThread();
killSenderThread();
releaseUsbResources();
stopSelf();
return Service.START_NOT_STICKY;
}
startReceiverThread();
startSenderThread();
return Service.START_NOT_STICKY;
}
/*
* Preforms device initialization
*/
private boolean initDevice() {
if (DEBUG) Log.d(TAG, "initDevice");
UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
if (usbManager == null) {
if (DEBUG) Log.d(TAG, "System did not return a UsbManager");
return false;
}
if (DEBUG) Log.d(TAG, "UsbManager found");
usbConnection = usbManager.openDevice(usbDevice);
if (usbConnection == null) {
if (DEBUG) Log.d(TAG, "Opening device failed");
return false;
}
if (DEBUG) Log.d(TAG, "Device opened");
UsbInterface usbInterface = usbDevice.getInterface(DATA_INTERFACE);
if (!usbConnection.claimInterface(usbInterface, true)) {
if (DEBUG) Log.d(TAG, "Claiming interface failed");
return false;
}
if (DEBUG) Log.d(TAG, "Interface claimed");
// Line state
int controlReturn = usbConnection.controlTransfer((byte)0x21, (byte)0x22, (byte)0, (byte)0, null, (byte)0, (byte)0);
if (controlReturn < 0) {
if (DEBUG) Log.d(TAG, "Sending line state frame failed.");
if (DEBUG) Log.d(TAG, "Return code: " + controlReturn);
return false;
}
// Line encoding @9600 Baudrate
controlReturn = usbConnection.controlTransfer((byte)0x21, (byte)0x20, (byte)0, (byte)0, getLineEncoding(9600), (byte)7, (byte)0);
if (controlReturn < 0) {
if (DEBUG) Log.d(TAG, "Sending line encoding frame failed.");
if (DEBUG) Log.d(TAG, "Return code: " + controlReturn);
return false;
}
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
if (usbInterface.getEndpoint(i).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) {
readEndpoint = usbInterface.getEndpoint(i);
} else if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_OUT) {
writeEndpoint = usbInterface.getEndpoint(i);
}
}
}
if (readEndpoint == null) {
if (DEBUG) Log.d(TAG, "No read endpoint found");
return false;
}
if (writeEndpoint == null) {
if (DEBUG) Log.d(TAG, "No write endpoint found");
return false;
}
return true;
}
private byte[] getLineEncoding(int baudRate) {
if (DEBUG) Log.d(TAG, "getLineEncoding");
final byte[] lineEncodingRequest = { (byte) 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
lineEncodingRequest[0] = (byte)(baudRate & 0xFF);
lineEncodingRequest[1] = (byte)((baudRate >> 8) & 0xFF);
lineEncodingRequest[2] = (byte)((baudRate >> 16) & 0xFF);
return lineEncodingRequest;
}
private void startReceiverThread() {
receiverThread = new ReceiverThread();
receiverThread.start();
}
private void startSenderThread() {
senderThread = new SenderThread("ArduinoSender");
senderThread.start();
}
private void checkArduinoConnected() {
if (DEBUG) Log.d(TAG, "checkOkToStartUnity");
if (readAvailable && writeAvailable) {
sendArduinoConnectedIntent();
}
}
private void broadcastDeviceDisconnected() {
if (DEBUG) Log.d(TAG, "broadcastDeviceDisconnected");
Intent intent = new Intent();
intent.setAction(ArduinoService.ARDUINO_DISCONNECTED);
sendBroadcast(intent);
}
private void killReceiverThread() {
if (DEBUG) Log.d(TAG, "killReceiverThread");
if (receiverThread != null) {
receiverThread.cancel();
try {
receiverThread.join();
} catch (InterruptedException e) {
if (DEBUG) Log.d(TAG, "Exception thrown joining receiverThread");
e.printStackTrace();
}
}
}
private void killSenderThread() {
if (DEBUG) Log.d(TAG, "killSenderThread");
if (senderThread != null) {
if (senderThread.pipeline != null) {
Message msg = Message.obtain();
msg.what = ArduinoService.KILL;
senderThread.pipeline.sendMessage(msg);
try {
senderThread.join();
} catch (InterruptedException e) {
if (DEBUG) Log.d(TAG, "Exception thrown joining senderThread");
e.printStackTrace();
}
}
}
}
private boolean releaseUsbResources() {
if (DEBUG) Log.d(TAG, "releaseUsbResources");
if (usbDevice == null || usbConnection == null) {
if (usbDevice == null) {
if (DEBUG) Log.d(TAG, "usbDevice pointer was null before releasing resources could occur");
}
if (usbConnection == null) {
if (DEBUG) Log.d(TAG, "usbConnection pointer was null before releasing resources could occur");
}
if (DEBUG) Log.d(TAG, "Device will need restarted before operations can continue...");
return false;
}
if (DEBUG) Log.d(TAG, "Releasing interface");
boolean releaseInterfaceSuccess = usbConnection.releaseInterface(
usbDevice.getInterface(DATA_INTERFACE));
if (!releaseInterfaceSuccess) {
if (DEBUG) Log.d(TAG, "UsbInterface release failed.");
if (DEBUG) Log.d(TAG, "Device will need restarted before operations can continue...");
return false;
}
if (DEBUG) Log.d(TAG, "Closing usbConnection");
usbConnection.close();
if (DEBUG) Log.d(TAG, "Nulling all usb related member variables");
usbDevice = null;
usbConnection = null;
readEndpoint = null;
writeEndpoint = null;
// Set the flag to notify other process resources have been released
usbResourcesReleased = true;
return true;
}
/*
* Receiver thread
*/
private class ReceiverThread extends Thread {
// Flag used to kill thread
private boolean connected = true;
public ReceiverThread() {}
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Receiver thread started");
readAvailable = true;
broadcastReadAvailable();
int msgPartsRecvd = 0;
boolean msgStartFound = false;
byte[] msgBuffer = new byte[MSG_SIZE];
byte[] buffer = new byte[4096];
Arrays.fill(buffer, (byte)0x00);
while (connected) {
final int numRecv = usbConnection.bulkTransfer(readEndpoint, buffer, buffer.length, 0);
if (numRecv > 0) {
if (DEBUG) Log.d(TAG, "numRecv: " + numRecv);
for (int i = 0; i < numRecv; i++) {
if (msgPartsRecvd < MSG_SIZE) {
if (msgPartsRecvd == 0 && !msgStartFound) {
if (buffer[i] == START_BYTE) {
msgStartFound = true;
continue;
}
} else {
msgBuffer[msgPartsRecvd] = buffer[i];
msgPartsRecvd++;
}
}
if (msgPartsRecvd == MSG_SIZE) {
// Copy the message
byte[] copy = new byte[MSG_SIZE];
System.arraycopy(msgBuffer, 0, copy, 0, MSG_SIZE);
// Send the message
parseMessage(copy);
// Clear the buffer
msgPartsRecvd = 0;
msgStartFound = false;
Arrays.fill(msgBuffer, (byte)0x00);
}
}
}
}
readAvailable = false;
if (DEBUG) Log.d(TAG, "Receiver thread stopped");
}
private void broadcastReadAvailable() {
Intent intent = new Intent();
intent.setAction(ArduinoService.READ_AVAILABLE);
sendBroadcast(intent);
}
private void parseMessage(byte[] buffer) {
logRecvBuffer(buffer);
// TODO - Message received implementation here
}
private void logRecvBuffer(byte[] buffer) {
if (DEBUG) Log.d(TAG, "Payload:");
for (int i = 0; i < buffer.length; i++) {
if (DEBUG) Log.d(TAG, "Byte " + i + ": " + buffer[i]);
}
}
public void cancel() {
connected = false;
}
}
/*
* Sender thread
*/
private class SenderThread extends Thread {
public Handler pipeline;
public SenderThread(String name) { super(name); }
@SuppressLint("HandlerLeak")
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Sender thread started");
writeAvailable = true;
broadcastWriteAvailable();
Looper.prepare();
pipeline = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ArduinoService.KILL:
Looper.myLooper().quit();
break;
}
}
};
Looper.loop();
writeAvailable = false;
if (DEBUG) Log.d(TAG, "Sender thread stopped");
}
private void broadcastWriteAvailable() {
if (DEBUG) Log.d(TAG, "broadcastWriteAvailable");
Intent intent = new Intent();
intent.setAction(ArduinoService.WRITE_AVAILABLE);
sendBroadcast(intent);
}
private void sendToArduino(byte[] buffer) {
if (DEBUG) Log.d(TAG, "Sending message to Arduino...");
final int numWritten = usbConnection.bulkTransfer(writeEndpoint, buffer, buffer.length, 0);
if (numWritten < 0) {
if (DEBUG) Log.d(TAG, "Error writing to device");
}
}
}
private void sendArduinoConnectedIntent() {
if (DEBUG) Log.d(TAG, "sendArduinoConnectedIntent");
Intent intent = new Intent();
intent.setAction(ArduinoService.ARDUINO_CONNECTED);
sendBroadcast(intent);
}
private void killServicePermanently() {
if (DEBUG) Log.d(TAG, "killServicePermanently");
killReceiverThread();
killSenderThread();
if (!usbResourcesReleased) {
releaseUsbResources();
}
stopSelf();
}
private void deviceDisconnected() {
if (DEBUG) Log.d(TAG, "deviceDisconnected");
killReceiverThread();
killSenderThread();
// Release usb resources
if (!usbResourcesReleased) {
releaseUsbResources();
}
// Send kill msg to Unity if it's listening
broadcastDeviceDisconnected();
// Stop service
stopSelf();
}
/*
* Broadcast Receiver
*/
public BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "onReceive");
final String action = intent.getAction();
if (DEBUG) Log.d(TAG, "Action: " + action);
if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
deviceDisconnected();
} else if (action.equals(ArduinoService.READ_AVAILABLE)) {
checkArduinoConnected();
} else if (action.equals(ArduinoService.WRITE_AVAILABLE)) {
checkArduinoConnected();
} else if (action.equals(ArduinoService.STOP_SERVICE)) {
killServicePermanently();
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment