|
|||||||
|
Доступ к HID-устройствам из программы на Qt под Android
Время создания: 05.06.2018 00:10
Автор: Aunoor
Текстовые метки: qt, qt5, android, андроид, usb, libusb
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Библиотека Qt - USB
Запись: xintrea/mytetra_syncro/master/base/1528146621ojtgzk99su/text.html на raw.githubusercontent.com
|
|||||||
|
|
|||||||
|
Введение С выходом Qt 5 появилась удобная возможность расширить список поддерживаемых программой платформ и на мобильные ОС, в частности на Android. Сам процесс портирования программы с десктопной версии Qt на мобильную свелся к банальной перекомпиляции. Интерфейс и логика завелись сразу, за исключением той части, без которой, собственно, программа бесполезна: обмену с HID-устройством. С самого начала для общения с устройством используется библиотека HIDAPI. Она мультиплатформенная и ее легко использовать. Первая трудность, с которой пришлось столкнуться заключалась в том что под Android нет hidraw, который использовался для доступа к устройствам под десктопный Linux. Для обхода пришлось перейти на использование библиотеки libusb и ее интерфейса к ней в HIDAPI. Первый запуск показал, что работает перечисление устройств, но открыть файл устройства нельзя, из-за отсутствия прав у приложения. В файл README libusb/android есть описание возможных путей обхода этой проблемы: либо менять права у файла устройств, либо воспользоваться интерфейсом android.hardware.usb.UsbDevice, для открытия устройств. Простая смена маски доступа к файлу на 777 на рутованом устройстве подтвердила работоспособность выбранной схемы, но и привела к пониманию того, что это не совсем верный путь, т.к. он работоспособен на очень маленьком круге устройств. Поэтому пришлось лезть в дебри API Android. Чтение документации показало что есть два пути доступа к устройству: использование intent filter и простое перечисление устройств. Первый способ заставить работать не удалось, никаких событий при появлении устройства в системе программе не приходило. Собственно этот путь тоже тупиковый, т.к. подразумевает, что программа должна быть запущена раньше, чем подключено устройство, и это значит что нам следует в полной мере воспользоваться предоставляемым API для доступа к USB. Что бы обойтись минимумом переделок в существующем коде было решено вынести в Java-часть только код связанный с запросом разрешения доступа к устройству у пользователя и собственно открытие устройства. Всю остальную работу по перечислению устройств и обмена данными выполняет связка HIDAPI-libusb. Реализация. Первое, что пришлось сделать, это запрос разрешения у пользователя на открытие устройства. Опять же, подстраиваясь под существующий алгоритм получилось следующее: при нахождении устройства, путь к его файлу передается в функцию в класса Activity программы посредством JNI-интерфейса: int HidTransport::openAndroidDevice(QString devPath) { QAndroidJniObject dP = QAndroidJniObject::fromString(devPath); jint dFD = QAndroidJniObject::callStaticMethod<jint>("org/HidManager/HidDevice", "tryOpenDevice", "(Ljava/lang/String;)I", dP.object<jstring>()); return dFD; } Здесь хочется сделать некоторое отступление: в силу каких-то причин Qt интерфейс к JNI может вызывать только статические методы класса. Поэтому мы создаем фактически сингелтон. Ниже приведены конструктор и onCreate класса Activity: private static HidDevice m_instance; private static UsbManager m_usbManager; private static PendingIntent mPermissionIntent; private static HashMap<String, Integer> deviceCache = new HashMap<String, Integer>(); private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; public HidDevice() { m_instance = this; } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (m_usbManager==null) { m_usbManager = (UsbManager) m_instance.getSystemService(Context.USB_SERVICE); } mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(mUsbReceiver, filter); } @Override public void onDestroy() { super.onDestroy(); } Функция tryOpenDevice, вызываемая из нативного кода выглядит следующим образом: public static int tryOpenDevice(String devPath) { if (deviceCache.containsKey(devPath)) { int fd = deviceCache.get(devPath); return fd; } deviceCache.put(devPath, -1); HashMap<String, UsbDevice> deviceList = m_usbManager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while(deviceIterator.hasNext()) { UsbDevice device = deviceIterator.next(); if (devPath.compareTo(device.getDeviceName())==0) { m_usbManager.requestPermission(device, mPermissionIntent); break; } } } deviceCache выполняет роль промежуточного хранилища состояния процесса открытия устройства. Такой вариант был выбран потому, что алгоритм нативного кода пытается с некоторой периодичностью открыть каждое найденное, но еще не открытое устройство. Далее в работу вступает механизм разрешений Android и для получения результатов служит эта функция: 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){ m_instance.openDevice(device); } } } } } }; Как из нее видно, если разрешение получено, то вызывается функция открытия устройства: public void openDevice(UsbDevice device) { try { if (!res) return; UsbDeviceConnection devConn = m_usbManager.openDevice(device); Integer fd = devConn.getFileDescriptor(); deviceCache.put(device.getDeviceName(), fd); } catch (InterruptedException e) { return; } } Которая сохраняет полученный файловый дескриптор в deviceCache. После прохождения всех этих этапов мы получаем файловый дескриптор открытого устройства. Но тут появляется другая проблема: HIDAPI и libusb не умеют принимать дескрипторы в качестве указателя на устройство. К счастью, эта проблема решилась просто. Существует форк libusb, принимающий в качестве аргумента файловый дескриптор открытого устройства. Заключение Вот так, достаточно просто, можно получить доступ к USB из нативного кода. К сожалению, данный подход работает не на всех устройствах. Многие производители не включают разрешение android.hardware.usb.host в свои прошивки, что приводит к ситуации, когда физически планшет может работать в качестве хоста для флешек или мышей, но другие устройства не работают. При этом файл USB-устройства ядром создается, но даже UsbManager их не видит. В данном случае, это ограничение возможно обойти на устройствах с работоспособным root, меняя права доступа на файл, т.к. libusb способен видеть подключенные устройства. Но пока что это только теория. |
|||||||
|
Так же в этом разделе:
|
|||||||
|
|||||||
|
|||||||
|