highsciguy
highsciguy

Reputation: 2647

Compile and link against libusb for android

I consider trying to compile a certain C-programm which allows the control of Gembird SilverShield power outlets via USB for android. On my android HDMI TV-stick this would be very useful. There is an open project for this. It works under Linux and depends on libusb. The target platform is android ICS. I want to develop on Ubuntu Linux. What are the chances I get it working? What are the required steps. Setup android SDK, NDK, crosscompiler ...
There is an older question here, related to libusb on android but no information how.
Is it maybe easier to port the application to androids own usb library?

Upvotes: 14

Views: 15916

Answers (4)

Arik Halperin
Arik Halperin

Reputation: 458

The solution I implemented is to open the USB device using the Java APIs and then using the file descriptor with libusb.I used libusb from openni project by primesense(https://github.com/OpenNI/OpenNI2)

Code bits:

Opening the device(Using swig):

int LibUsbAndroid::android_open(libusb_device *device, libusb_device_handle **devHandle)
{
    int fd = USBJNICallbacks::getCallback()->getDeviceFd(device->bus_number, device->device_address);

    __android_log_print(ANDROID_LOG_VERBOSE,"USB","Got  FD:%d",fd);
    if(fd==-1)
    {
        __android_log_print(ANDROID_LOG_ERROR,"USB","android_open, bad fd");
        return -1;
    }

    return libusb_open(device, devHandle, fd);
}

Opening the device in JAVA code(Not running on main thread!):

public int getDeviceFd(int busNumber, int deviceAddress) {
        UsbDevice device = findDevice(busNumber, deviceAddress);

        if(device!=null)
        {
            mReceivedPermission = false;
            PermissionRequester pr = new PermissionRequester(device);
            pr.run();

            if(!mUsbManager.hasPermission(device))
            {
                Log.v("USB", "Requesting permissiom to device");
                mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0);
                IntentFilter filterPermission = new IntentFilter(ACTION_USB_PERMISSION);
                mContext.registerReceiver(mUsbPermissionReceiver, filterPermission);
                mUsbManager.requestPermission(device, mPermissionIntent);
            }
            else
            {
                Log.v("USB", "Already has permission");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                mReceivedPermission = true;

                Log.v("USB", "Opening device");
                OpenDevice od = openDevice(device);
                Log.v("USB", "Adding to open devices");
                mOpenDevices.put(""+busNumber+"/"+deviceAddress, od);


            }

            Log.v("USB", "Waiting for permission");
            waitForPermissionResult();
            OpenDevice od = mOpenDevices.get(""+busNumber+"/"+deviceAddress);
            if(od!=null)
            {
                Log.v("USB", "Getting FD");
                int result = od.mConnection.getFileDescriptor();

                Log.i("USB","USB File desc:"+result);
                return result;
            }
            else
            {
                Log.v("USB", "Error getting FD");
                return -1;
            }
        }

        return -1;
    }

Permission handling code:

private BroadcastReceiver mUsbPermissionReceiver=new BroadcastReceiver(){

@Override
public void onReceive(Context context, Intent intent) {

    Log.v("USB", "Received permission result");
    String action = intent.getAction();
    if (ACTION_USB_PERMISSION.equals(action)) {
        synchronized (this) {
            UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                Log.v("USB", "Received permission result OK");
                if(device != null){
                    Log.v("USB", "Device OK");
                    mContext.unregisterReceiver(this);      
                    Log.v("USB", "Openning device");
                    OpenDevice od = openDevice(device);
                    Log.v("USB", "Adding to open device list");
                    mOpenDevices.put(""+od.mBus+"/"+od.mAddress,od);

                    Log.v("USB", "Received permission is true");
                    mReceivedPermission = true;
                } 
                else {
                    Log.d(TAG, "permission denied for device " + device);
                }
            }
        }
    }
}

};

Open function:

public OpenDevice openDevice(UsbDevice device) {
        UsbDeviceConnection connection = mUsbManager.openDevice(device);

        Log.i("USB","Device name="+device.getDeviceName());

        int bus = getBusNumber(device.getDeviceName());
        int address = getAddress(device.getDeviceName());

        return new OpenDevice(device, connection, bus, address);

    }

USB lib change(core.c):

int API_EXPORTED libusb_open(libusb_device *dev,
    libusb_device_handle **handle, int fd)
{
    struct libusb_context *ctx = DEVICE_CTX(dev);
    struct libusb_device_handle *_handle;
    size_t priv_size = usbi_backend->device_handle_priv_size;
    int r;
    usbi_dbg("open %d.%d", dev->bus_number, dev->device_address);

    _handle = malloc(sizeof(*_handle) + priv_size);
    if (!_handle)
        return LIBUSB_ERROR_NO_MEM;

    r = usbi_mutex_init(&_handle->lock, NULL);
    if (r) {
        free(_handle);
        return LIBUSB_ERROR_OTHER;
    }

    _handle->dev = libusb_ref_device(dev);
    _handle->claimed_interfaces = 0;
    memset(&_handle->os_priv, 0, priv_size);

    r = usbi_backend->open(_handle,fd);
    if (r < 0) {
        usbi_dbg("open %d.%d returns %d", dev->bus_number, dev->device_address, r);
        libusb_unref_device(dev);
        usbi_mutex_destroy(&_handle->lock);
        free(_handle);
        return r;
    }

    usbi_mutex_lock(&ctx->open_devs_lock);
    list_add(&_handle->list, &ctx->open_devs);
    usbi_mutex_unlock(&ctx->open_devs_lock);
    *handle = _handle;
    /* At this point, we want to interrupt any existing event handlers so
     * that they realise the addition of the new device's poll fd. One
     * example when this is desirable is if the user is running a separate
     * dedicated libusb events handling thread, which is running with a long
     * or infinite timeout. We want to interrupt that iteration of the loop,
     * so that it picks up the new fd, and then continues. */
    usbi_fd_notification(ctx);

    return 0;
}

op_open(libusb_fs.c) change:

static int op_open(struct libusb_device_handle *handle, int fd)
{
    struct linux_device_handle_priv *hpriv = _device_handle_priv(handle);
    char filename[PATH_MAX];

    _get_usbfs_path(handle->dev, filename);
    usbi_dbg("opening %s", filename);

    hpriv->fd = fd;

    if (hpriv->fd < 0) {
        if (errno == EACCES) {
            usbi_err(HANDLE_CTX(handle), "libusb couldn't open USB device %s: "
                "Permission denied.", filename);
            usbi_err(HANDLE_CTX(handle),
                "libusb requires write access to USB device nodes.");
            return LIBUSB_ERROR_ACCESS;
        } else if (errno == ENOENT) {
            usbi_err(HANDLE_CTX(handle), "libusb couldn't open USB device %s: "
                "No such file or directory.", filename);
            return LIBUSB_ERROR_NO_DEVICE;
        } else {
            usbi_err(HANDLE_CTX(handle),
                "open failed, code %d errno %d", hpriv->fd, errno);
            return LIBUSB_ERROR_IO;
        }
    }

    return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->fd, POLLOUT);
}

Upvotes: 4

Artemis
Artemis

Reputation: 4851

You can also try to use android serial port api

Here is example of serial port init.

private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;

public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {

    /* Check access permission */
    if (!device.canRead() || !device.canWrite()) {
        try {
            /* Missing read/write permission, trying to chmod the file */
            Process su;
            su = Runtime.getRuntime().exec("/system/bin/su");
            String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
                    + "exit\n";
            su.getOutputStream().write(cmd.getBytes());
            if ((su.waitFor() != 0) || !device.canRead()
                    || !device.canWrite()) {
                throw new SecurityException();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new SecurityException();
        }
    }

    mFd = open("/dev/ttyACM0", 9600, 0);
    if (mFd == null) {
        Log.e(TAG, "native open returns null");
        throw new IOException();
    }
    mFileInputStream = new FileInputStream(mFd);
    mFileOutputStream = new FileOutputStream(mFd);
}

So did it with use devices on USB host.

Upvotes: 1

Goz
Goz

Reputation: 62333

Libusb can work on a non-rooted android (provided the device supports USB host ... this is VERY important as not all devices do). You need to use the standard android USB stack. You can then get a device descriptor from the USBDevice and pass that over to libusb.

Unfortunately you also need to modify libusb. Fortunately other people have explained how you need to modify LibUSB.

LibUSB has been modified here.

Good luck!

Edit:

First you need to define a broadcast receiver:

private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() 
{
    public void onReceive(Context context, Intent intent)
    {
        String action = intent.getAction();
        if (ACTION_USB_PERMISSION.equals(action)) 
        {
            synchronized (this) 
            {
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) 
                {
                    if(device != null)
                    {
                        UsbDeviceConnection deviceConnection    = mUsbManager.openDevice( device );
                        Log.d( "USB",  deviceConnection.getSerial() );
                    }
                } 
                else 
                {
                    Log.d( "USB", "permission denied for device " + device);
                }
            }
        }
    }
}

Now you need to create a USBManager and enumerate the devices:

    mUsbManager         = (UsbManager) getSystemService( Context.USB_SERVICE );
    HashMap< String, UsbDevice > stringDeviceMap    =       mUsbManager.getDeviceList();
    Collection< UsbDevice > usbDevices              = stringDeviceMap.values();

    mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent( ACTION_USB_PERMISSION ), 0 );
    IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver( mUsbReceiver, filter );

    Iterator< UsbDevice > usbDeviceIter             = usbDevices.iterator();
    while( usbDeviceIter.hasNext() )
    {
        if ( USBDeviceValid( usbDevice ) )
        {
            // Request permission to access the device.
            mUsbManager.requestPermission( usbDevice, mPermissionIntent );

            // Open the device.
            UsbDeviceConnection connection = mUsbManager.openDevice( usbDevice );
            int fd = connection.getFileDescriptor();

            // Now pass the file descriptor to libusb through a native call.
        }
    }

Edit 2:

Getting libusb to build is just a matter of putting the files somewhere handy (I put them in jni/libusb) and then adding the following lines to your Android.mk:

include $(CLEAR_VARS)
LOCAL_MODULE    := libusb
LOCAL_SRC_FILES := libusb/core.c libusb/descriptor.c libusb/io.c libusb/sync.c libusb/os/linux_usbfs.c

LOCAL_LDLIBS    := -llog
include $(BUILD_SHARED_LIBRARY)

Upvotes: 26

mlc
mlc

Reputation: 1718

Even if you get it compiled, Android is probably not going to let you access the USB device through libusb unless your device is rooted. If it is feasible to port your app to Android's native USB stack, that would almost certainly be a more stable solution.

Upvotes: 1

Related Questions