Android向けQtプログラムからHIDデバイスへのアクセス

はじめに



Qt 5のリリースにより、プログラムでサポートされるプラットフォームのリストをモバイルOS、特にAndroidに拡大する便利な機会が出現しました。

Qtのデスクトップバージョンからモバイルにプログラムを移植するプロセスは、通常の再コンパイルに削減されました。 実際にはプログラムが役に立たない部分を除いて、インターフェースとロジックはすぐに開始されました。HIDデバイスとの交換です。



最初の困難



当初から、デバイスとの通信にはHIDAPIライブラリが使用されてきました。 マルチプラットフォームで使いやすいです。

私が対処しなければならなかった最初の困難は、AndroidではデスクトップLinuxでデバイスにアクセスするために使用されるhidrawがないことでした。 回避策として、libusbライブラリとそのインターフェイスをHIDAPIで使用するように切り替える必要がありました。

最初の起動では、デバイスの列挙が機能することが示されましたが、アプリケーションの権限がないため、デバイスファイルを開くことができません。

README libusb / androidファイルには、この問題の考えられる回避策の説明が含まれています。デバイスファイルの権限を変更するか、android.hardware.usb.UsbDeviceインターフェイスを使用してデバイスを開きます。



ルート化されたデバイスでファイルアクセスマスクを777に変更するだけで、選択したスキームの操作性が確認されましたが、これは正しいパスではないという理解にもつながりました。 非常に小さなデバイスの輪で機能します。 したがって、Android APIのジャングルに登らなければなりませんでした。



ドキュメントを読むと、デバイスにアクセスする方法が2つあることがわかりました。インテントフィルターとデバイスの単純な列挙の使用です。

仕事を得るための最初の方法は失敗し、デバイスがシステムに現れたときにイベントは発生しませんでした。 実際、このパスも行き止まりです。 これは、デバイスを接続する前にプログラムを開始する必要があることを意味します。これは、提供されたAPIを利用してUSBにアクセスする必要があることを意味します。



既存のコードの最小限の変更を回避するために、ユーザーからのデバイスへのアクセス許可とデバイスの実際のオープンのリクエストに関連付けられたコードのみをJavaパーツに配置することが決定されました。 デバイスの列挙とデータの交換に関する他のすべての作業は、HIDAPI-libusbバンドルによって実行されます。



実装。



最初にやらなければならなかったのは、ユーザーにデバイスを開く許可を要求することでした。

繰り返しますが、既存のアルゴリズムに合わせて次のことが行われました。デバイスが見つかると、そのファイルへのパスがJNIインターフェースを介してプログラムのActivityクラスの関数に渡されます。



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; }
      
      







ここで余談になります。何らかの理由で、JNIへのQtインターフェイスはクラスの静的メソッドのみを呼び出すことができます。 したがって、仮想シングルトンを作成しています。 以下は、ActivityクラスのコンストラクターとonCreateです。



 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でもそれらを認識しません。



この場合、この制限は、次のようにファイルのアクセス許可を変更することにより、作業ルートを持つデバイスで回避できます。 libusbは接続されたデバイスを見ることができます。 しかし、これまでのところ、これは単なる理論です。



All Articles