com.openxc.interfaces.usb.UsbVehicleInterface Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openxc-examples Show documentation
Show all versions of openxc-examples Show documentation
Example Vehicle Dashboard application using OpenXC
package com.openxc.interfaces.usb;
import java.io.IOException;
import java.net.URI;
import android.annotation.TargetApi;
import android.app.PendingIntent;
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.util.Log;
import com.google.common.base.Objects;
import com.openxc.interfaces.UriBasedVehicleInterfaceMixin;
import com.openxc.interfaces.VehicleInterface;
import com.openxc.remote.RawMeasurement;
import com.openxc.sources.BytestreamDataSource;
import com.openxc.sources.DataSourceException;
import com.openxc.sources.DataSourceResourceException;
import com.openxc.sources.SourceCallback;
/**
* A vehicle data source reading measurements from an OpenXC USB device.
*
* This class looks for a USB device and expects to read OpenXC-compatible,
* newline separated JSON messages in USB bulk transfer packets.
*
* The device used (if different from the default) can be specified by passing
* an custom URI to the constructor. The expected format of this URI is defined
* in {@link UsbDeviceUtilities}.
*
* According to Android's USB device usage requirements, this class requests
* permission for the USB device from the user before accessing it. This may
* cause a pop-up dialog that the user must dismiss before the data source will
* become active.
*/
@TargetApi(12)
public class UsbVehicleInterface extends BytestreamDataSource
implements VehicleInterface {
private static final String TAG = "UsbVehicleInterface";
private static final int ENDPOINT_COUNT = 2;
public static final String ACTION_USB_PERMISSION =
"com.ford.openxc.USB_PERMISSION";
public static final String ACTION_USB_DEVICE_ATTACHED =
"com.ford.openxc.USB_DEVICE_ATTACHED";
private UsbManager mManager;
private UsbDeviceConnection mConnection;
private UsbInterface mInterface;
private UsbEndpoint mInEndpoint;
private UsbEndpoint mOutEndpoint;
private PendingIntent mPermissionIntent;
private URI mDeviceUri;
/**
* Construct an instance of UsbVehicleInterface with a receiver callback
* and custom device URI.
*
* If the device cannot be found at initialization, the object will block
* waiting for a signal to check again.
*
* @param context The Activity or Service context, used to get access to the
* Android UsbManager.
* @param callback An object implementing the
* SourceCallback that should receive data as it is
* received and parsed.
* @param deviceUri a USB device URI (see {@link UsbDeviceUtilities} for the
* format) to look for.
* @throws DataSourceException If the URI doesn't have the correct
* format
*/
public UsbVehicleInterface(SourceCallback callback, Context context,
URI deviceUri) throws DataSourceException {
super(callback, context);
mDeviceUri = createUri(deviceUri);
try {
mManager = (UsbManager) getContext().getSystemService(
Context.USB_SERVICE);
} catch(NoClassDefFoundError e) {
String message = "No USB service found on this device -- " +
"can't use USB vehicle interface";
Log.w(TAG, message);
throw new DataSourceException(message);
}
mPermissionIntent = PendingIntent.getBroadcast(getContext(), 0,
new Intent(ACTION_USB_PERMISSION), 0);
start();
}
/**
* Construct an instance of UsbVehicleInterface with a receiver callback
* and the default device URI.
*
* The default device URI is specified in {@link UsbDeviceUtilities}.
*
* @param context The Activity or Service context, used to get access to the
* Android UsbManager.
* @param callback An object implementing the
* SourceCallback that should receive data as it is
* received and parsed.
* @throws DataSourceException in exceptional circumstances, i.e.
* only if the default device URI is malformed.
*/
public UsbVehicleInterface(SourceCallback callback, Context context)
throws DataSourceException {
this(callback, context, null);
}
public UsbVehicleInterface(Context context) throws DataSourceException {
this(null, context);
}
public UsbVehicleInterface(Context context, String uriString)
throws DataSourceException {
this(null, context, createUri(uriString));
}
public synchronized void start() {
super.start();
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
getContext().registerReceiver(mBroadcastReceiver, filter);
filter = new IntentFilter();
filter.addAction(ACTION_USB_DEVICE_ATTACHED);
getContext().registerReceiver(mBroadcastReceiver, filter);
filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
getContext().registerReceiver(mBroadcastReceiver, filter);
initializeDevice();
}
@Override
public boolean isConnected() {
return mConnection != null && super.isConnected();
}
/**
* Unregister USB device intent broadcast receivers and stop waiting for a
* connection.
*
* This should be called before the object is given up to the garbage
* collector to avoid leaking a receiver in the Android framework.
*/
@Override
public void stop() {
super.stop();
try {
getContext().unregisterReceiver(mBroadcastReceiver);
} catch(IllegalArgumentException e) {
Log.d(TAG, "Unable to unregster receiver when stopping, probably not registered");
}
}
public boolean receive(RawMeasurement command) {
if(isConnected()) {
String message = command.serialize() + "\u0000";
Log.d(TAG, "Writing string to USB: " + message);
byte[] bytes = message.getBytes();
return write(bytes);
}
return false;
}
public boolean setResource(String otherUri) throws DataSourceException {
if(mDeviceUri == UsbDeviceUtilities.DEFAULT_USB_DEVICE_URI
&& otherUri != null &&
!UriBasedVehicleInterfaceMixin.sameResource(mDeviceUri,
otherUri)) {
mDeviceUri = createUri(otherUri);
stop();
start();
return true;
}
return false;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("device", mDeviceUri)
.add("connection", mConnection)
.add("in_endpoint", mInEndpoint)
.add("out_endpoint", mOutEndpoint)
.toString();
}
protected int read(byte[] bytes) throws IOException {
mConnectionLock.readLock().lock();
int bytesRead = 0;
try {
if(isConnected()) {
bytesRead = mConnection.bulkTransfer(mInEndpoint, bytes, bytes.length, 0);
}
} finally {
mConnectionLock.readLock().unlock();
}
return bytesRead;
}
protected String getTag() {
return TAG;
}
private void initializeDevice() {
try {
connectToDevice(mManager, mDeviceUri);
} catch(DataSourceException e) {
Log.i(TAG, "Unable to load USB device -- " +
"waiting for it to appear", e);
}
}
private boolean write(byte[] bytes) {
if(mConnection != null) {
if(mOutEndpoint != null) {
Log.d(TAG, "Writing bytes to USB: " + bytes);
int transferred = mConnection.bulkTransfer(
mOutEndpoint, bytes, bytes.length, 0);
if(transferred < 0) {
Log.w(TAG, "Unable to write CAN message to USB endpoint, error "
+ transferred);
return false;
}
} else {
Log.w(TAG, "No OUT endpoint available on USB device, " +
"can't send write command");
return false;
}
} else {
return false;
}
return true;
}
private void connectToDevice(UsbManager manager, URI deviceUri)
throws DataSourceResourceException {
connectToDevice(manager,
UsbDeviceUtilities.vendorFromUri(mDeviceUri),
UsbDeviceUtilities.productFromUri(mDeviceUri));
}
private void connectToDevice(UsbManager manager, int vendorId,
int productId) throws DataSourceResourceException {
UsbDevice device = findDevice(manager, vendorId, productId);
if(manager.hasPermission(device)) {
Log.d(TAG, "Already have permission to use " + device);
openConnection(device);
} else {
Log.d(TAG, "Requesting permission for " + device);
manager.requestPermission(device, mPermissionIntent);
}
}
private UsbDeviceConnection setupDevice(UsbManager manager,
UsbDevice device) throws UsbDeviceException {
if(device.getInterfaceCount() != 1) {
throw new UsbDeviceException("USB device didn't have an " +
"interface for us to open");
}
UsbInterface iface = null;
for(int i = 0; i < device.getInterfaceCount(); i++) {
iface = device.getInterface(i);
if(iface.getEndpointCount() == ENDPOINT_COUNT) {
break;
}
}
if(iface == null) {
Log.w(TAG, "Unable to find a USB device interface with the " +
"expected number of endpoints (" + ENDPOINT_COUNT + ")");
return null;
}
for(int i = 0; i < iface.getEndpointCount(); i++) {
UsbEndpoint endpoint = iface.getEndpoint(i);
if(endpoint.getType() ==
UsbConstants.USB_ENDPOINT_XFER_BULK) {
if(endpoint.getDirection() == UsbConstants.USB_DIR_IN) {
Log.d(TAG, "Found IN endpoint " + endpoint);
mInEndpoint = endpoint;
} else {
Log.d(TAG, "Found OUT endpoint " + endpoint);
mOutEndpoint = endpoint;
}
}
if(mInEndpoint != null && mOutEndpoint != null) {
break;
}
}
return openInterface(manager, device, iface);
}
private UsbDevice findDevice(UsbManager manager, int vendorId,
int productId) throws DataSourceResourceException {
Log.d(TAG, "Looking for USB device with vendor ID " + vendorId +
" and product ID " + productId);
for(UsbDevice candidateDevice : manager.getDeviceList().values()) {
if(candidateDevice.getVendorId() == vendorId
&& candidateDevice.getProductId() == productId) {
Log.d(TAG, "Found USB device " + candidateDevice);
return candidateDevice;
}
}
throw new DataSourceResourceException("USB device with vendor " +
"ID " + vendorId + " and product ID " + productId +
" not found");
}
private UsbDeviceConnection openInterface(UsbManager manager,
UsbDevice device, UsbInterface iface)
throws UsbDeviceException {
UsbDeviceConnection connection = manager.openDevice(device);
if(connection == null) {
throw new UsbDeviceException("Couldn't open a connection to " +
"device -- user may not have given permission");
}
mInterface = iface;
connection.claimInterface(mInterface, true);
return connection;
}
/**
* TODO we oddly need to "prime" this endpoint from Android because the
* first message we send, if it's over 1 packet in size, we only get the
* last packet.
*/
private void primeOutput() {
Log.d(TAG, "Priming output endpoint");
write(new String("prime\u0000").getBytes());
}
private void openConnection(UsbDevice device) {
if (device != null) {
mConnectionLock.writeLock().lock();
try {
mConnection = setupDevice(mManager, device);
connected();
Log.i(TAG, "Connected to USB device with " +
mConnection);
} catch(UsbDeviceException e) {
Log.w("Couldn't open USB device", e);
} finally {
mConnectionLock.writeLock().unlock();
}
} else {
Log.d(TAG, "Permission denied for device " + device);
}
}
protected void connected() {
super.connected();
primeOutput();
}
protected void connect() throws DataSourceException { }
protected void disconnect() {
if(!isConnected()) {
return;
}
Log.d(TAG, "Closing connection " + mConnection +
" with USB device");
mConnectionLock.writeLock().lock();
try {
mConnection.close();
mConnection = null;
mInEndpoint = null;
mOutEndpoint = null;
mInterface = null;
disconnected();
} finally {
mConnectionLock.writeLock().unlock();
}
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(
UsbManager.EXTRA_DEVICE);
if(intent.getBooleanExtra(
UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
openConnection(device);
} else {
Log.i(TAG, "User declined permission for device " +
device);
}
} else if(ACTION_USB_DEVICE_ATTACHED.equals(action)) {
Log.d(TAG, "Device attached");
try {
connectToDevice(mManager, mDeviceUri);
} catch(DataSourceException e) {
Log.i(TAG, "Unable to load USB device -- waiting for it " +
"to appear", e);
}
} else if(UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
Log.d(TAG, "Device detached");
disconnect();
}
}
};
private static URI createUri(String uriString) throws DataSourceException {
URI uri;
if(uriString == null) {
uri = null;
} else {
uri = UriBasedVehicleInterfaceMixin.createUri(uriString);
}
return createUri(uri);
}
private static URI createUri(URI uri) throws DataSourceResourceException {
if(uri == null) {
uri = UsbDeviceUtilities.DEFAULT_USB_DEVICE_URI;
Log.i(TAG, "No USB device specified -- using default " +
uri);
}
if(!validateResource(uri)) {
throw new DataSourceResourceException(
"USB device URI must have the usb:// scheme");
}
// will throw an exception if not in the correct format
UsbDeviceUtilities.vendorFromUri(uri);
UsbDeviceUtilities.productFromUri(uri);
return uri;
}
private static boolean validateResource(URI uri) {
return uri.getScheme() != null && uri.getScheme().equals("usb");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy