العمل باستخدام USB مخصص HID على Android

صممه Freepik
في تطبيقات Android الحديثة للتفاعل مع الأجهزة الأخرى ، غالبًا ما تستخدم بروتوكولات نقل البيانات اللاسلكية ، مثل Bluetooth. في السنوات التي تتمتع فيها بعض الأجهزة بشحن لاسلكي ، من الصعب تخيل مجموعة من الأجهزة والأجهزة الطرفية التي تعمل بنظام Android ، والتي تتطلب استخدام واجهات سلكية. ومع ذلك ، عندما تنشأ هذه الحاجة ، يتبادر USB على الفور إلى الذهن.

لنأخذ حالة افتراضية معك. تخيل أن أحد العملاء يأتي إليك ويقول: "أحتاج إلى تطبيق Android للتحكم في جهاز جمع البيانات وعرض هذه البيانات على الشاشة. هناك واحد ولكن - يجب أن يكون التطبيق مكتوبًا على كمبيوتر أحادي اللوحة باستخدام نظام التشغيل Android ، والجهاز المحيطي متصل عبر USB "

هذا يبدو رائعا ، لكنه يحدث في بعض الأحيان. ومن المفيد جدًا أن يكون لديك معرفة عميقة بمكدس USB وبروتوكولاته ، لكن هذا المقال لا يتعلق بذلك. في هذه المقالة ، سننظر في كيفية التحكم في جهاز طرفي باستخدام بروتوكول USB مخصص HID من جهاز Android. للبساطة ، سوف نكتب تطبيق Android (HOST) يتحكم في مؤشر LED على الجهاز المحيطي (الجهاز) وسيتلقى حالة الزر (اضغط). لن أعطي الكود الخاص باللوحة الطرفية ، المهتمين - اكتب التعليقات.

لذلك دعونا نبدأ.

النظرية. أقصر وقت ممكن


أولاً ، نظرية صغيرة ، قصيرة قدر الإمكان. هذا هو الحد الأدنى المبسط ، وهو ما يكفي لفهم الكود ، ولكن لفهم أفضل ، أنصحك أن تتعرف على هذا المورد .

للتواصل عبر USB على جهاز طرفي ، من الضروري تطبيق واجهة تفاعل. وظائف مختلفة (على سبيل المثال ، USB HID ، USB Mass Strorage أو USB CDC) سوف تنفذ واجهاتها ، وبعضها سيكون له عدة واجهات. تحتوي كل واجهة على نقاط النهاية - قنوات اتصال خاصة ، نوع من الحافظة.

يشتمل الجهاز المحيطي الخاص بي على HID مخصص مع واجهة واحدة ونقطتي نهاية ، واحدة للاستقبال وواحدة للإرسال. عادةً ما تتم كتابة المعلومات مع الواجهات ونقاط النهاية الموجودة على الجهاز في مواصفات الجهاز ، وإلا يمكن تحديدها من خلال برامج خاصة ، على سبيل المثال USBlyzer.

الأجهزة في USB HID التواصل من خلال التقارير. ما هي التقارير؟ نظرًا لأن البيانات تنتقل عبر نقاط النهاية ، نحتاج إلى تحديدها وتحليلها بطريقة أو بأخرى وفقًا للبروتوكول. لا تقوم الأجهزة بإلقاء وحدات البايت للبيانات على بعضها البعض فحسب ، بل تقوم أيضًا بتبادل الحزم التي لها بنية محددة بوضوح ، والتي يتم وصفها على الجهاز في واصف تقرير خاص. وبالتالي ، وفقًا لوصف التقرير ، يمكننا أن نحدد بدقة هوية وبنية وحجم وتواتر نقل بيانات معينة. يتم تعريف الحزمة بواسطة البايت الأول ، وهو معرف التقرير. على سبيل المثال ، تنتقل حالة الزر إلى التقرير ذي المعرف = 1 ، ونتحكم في مؤشر LED من خلال التقرير بالكود = 2.

بعيدا عن الحديد ، أقرب إلى أندرويد


في Android ، ظهر دعم لأجهزة USB بدءًا من الإصدار API 12 (Android 3.1). للعمل مع جهاز محيطي ، نحتاج إلى تطبيق وضع مضيف USB. العمل مع USB موصوف بشكل جيد في الوثائق.

تحتاج أولاً إلى تحديد جهازك المتصل ، من بين مجموعة كاملة من أجهزة USB. يتم التعرف على أجهزة USB من خلال مجموعة من vid (معرف البائع) و pid (معرف المنتج). في مجلد xml ، قم بإنشاء ملف device_filter.xml بالمحتويات التالية:

<resources> <usb-device vendor-id="1155" product-id="22352" /> </resources> 

تحتاج الآن إلى عمل الأذونات والإجراءات المناسبة (إذا كنت في حاجة إليها) في بيان التطبيق:

 <uses-permission android:name="android.permission.USB_PERMISSION" /> <uses-feature android:name="android.hardware.usb.host" /> <activity android:name=".MainActivity"> <intent-ilter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity> 

في android: مورد ، نحدد ملفًا يحتوي على المرشحات اللازمة للأجهزة. كما قلت سابقًا ، يمكنك تعيين عوامل تصفية نية لتشغيل التطبيق ، على سبيل المثال ، نتيجة لتوصيل جهازك.

تحتاج أولاً إلى الحصول على UsbManager ، والعثور على الجهاز ، واجهة ونقاط النهاية للجهاز. يجب أن يتم ذلك في كل مرة يتم فيها توصيل الجهاز.

 val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager private var usbConnection: UsbDeviceConnection? = null private var usbInterface: UsbInterface? = null private var usbRequest: UsbRequest? = null private var usbInEndpoint: UsbEndpoint? = null private var usbOutEndpoint: UsbEndpoint? = null fun enumerate(): Boolean { val deviceList = usbManager.deviceList for (device in deviceList.values) { /*      VID  PID */ if ((device.vendorId == VENDOR_ID) and (device.productId == PRODUCT_ID)) { /*      */ usbInterface = device.getInterface(CUSTOM_HID_INTERFACE) /*            */ for (idx in 0 until usbInterface!!.endpointCount) { if (usbInterface?.getEndpoint(idx)?.direction == USB_DIR_IN) usbInEndpoint = usbInterface?.getEndpoint(idx) else usbOutEndpoint = usbInterface?.getEndpoint(idx) } usbConnection = usbManager.openDevice(device) usbConnection?.claimInterface(usbInterface, true) usbRequest = UsbRequest() usbRequest?.initialize(usbConnection, usbInEndpoint) } } /*    */ return usbConnection != null } 

هنا نرى نفس الواجهات ونقاط النهاية التي نوقشت في القسم الأخير. مع العلم برقم الواجهة ، نجد كل من نقاط النهاية ، لتلقي ونقل ، وبدء اتصال USB. هذا كل شيء ، الآن يمكنك قراءة البيانات.

كما قلت سابقًا ، تتواصل الأجهزة من خلال التقارير.

  fun sendReport(data: ByteArray) { usbConnection?.bulkTransfer(usbOutEndpoint, data, data.size, 0) } fun getReport(): ByteArray { val buffer = ByteBuffer.allocate(REPORT_SIZE) val report = ByteArray(buffer.remaining()) if (usbRequest.queue(buffer, REPORT_SIZE)) { usbConnection?.requestWait() buffer.rewind() buffer.get(report, 0, report.size) buffer.clear() } return report } 

نرسل مجموعة من البايتات إلى أسلوب sendReport ، حيث يكون صفر بايت هو معرف التقرير ، ونأخذ اتصال USB الحالي إلى الجهاز وننفذ عملية النقل. كمعلمات ، نقوم بنقل رقم نقطة النهاية والبيانات وحجمها ومهلة الإرسال إلى طريقة BulkTransfer. تجدر الإشارة إلى أن فئة UsbDeviceConnection لديها طرق لتنفيذ تبادل البيانات مع جهاز USB - أساليب bulkTransfer و controlTransfer. يعتمد استخدامها على نوع الإرسال الذي تدعمه نقطة النهاية. في هذه الحالة ، نستخدم bulkTransfer ، على الرغم من أن استخدام نقاط النهاية بنوع التحكم هو الأكثر شيوعًا لمصابيح التفريغ عالي الكثافة. لكن لدينا HID مخصص ، لذلك نحن نفعل ما نريد. أنصحك أن تقرأ عن نوع الإرسال بشكل منفصل ، لأن حجم وتكرار البيانات المرسلة يعتمد عليه.

لتلقي البيانات ، تحتاج إلى معرفة حجم البيانات التي يمكن الحصول عليها ، وكيفية معرفة مقدما ، والحصول عليها من نقطة النهاية.

طريقة تلقي البيانات عبر USB HID متزامن وحظر ويجب تنفيذها في سلسلة رسائل مختلفة ، بالإضافة إلى أن التقارير الواردة من الجهاز يمكن أن تأتي باستمرار أو في أي وقت ، لذلك تحتاج إلى تنفيذ استطلاع مستمر للتقرير حتى لا تفوت البيانات. دعونا نفعل ذلك مع RxJava:

 fun receive() { Observable.fromCallable<ByteArray> { getReport() } .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .repeat() .subscribe({ /* check it[0] (this is report id) and handle data */ },{ /* handle exeption */ }) } 

بعد تلقي صفيف من البايتات ، يجب علينا التحقق من صفر بايت ، لأنه معرف تقرير ووفقًا لذلك ، نقوم بتحليل البيانات المستلمة.

عند الانتهاء من جميع الإجراءات مع USB ، تحتاج إلى إغلاق الاتصال. يمكنك القيام بذلك في نشاط onDestroy أو في onCleared في ViewModel.

  fun close() { usbRequest?.close() usbConnection?.releaseInterface(usbInterface) usbConnection?.close() } 

استنتاج


يناقش المقال رمزًا صغيرًا جدًا وبدائيًا للغاية ، مع رمز توضيحي للغاية مع تطبيق لجهاز معين. بالطبع ، هناك العديد من فئات USB ، ليس فقط HID ، وبالنسبة لهم ، بالطبع ، سيكون التنفيذ مختلفًا. ومع ذلك ، فإن جميع الطرق موثقة جيدًا ولديها فهم جيد لمكدس USB ، يمكنك بسهولة معرفة كيفية استخدامها.

X. مواد مفيدة


Source: https://habr.com/ru/post/ar470365/


All Articles