Created
December 15, 2014 20:37
-
-
Save nathansizemore/ef43a545ab9a2fa13ae5 to your computer and use it in GitHub Desktop.
Android UsbHost Service
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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