Android and Usb Story
Так как наметилась тенденция избавляться от аудиоразъема, то не остаётся в андроиде больше внешних интерфейсов кроме как USB. Надо изучить как с ним работать.
Раньше, во времена мамонтов, только некоторые андроид устройства позволяли подключать к ним по USB периферию. В этом случае говорят, что используется фича usb-host. Для этого покупали специальные OTG(On-The-Go)-usb переходники, которые одной стороной вставляются в android(mini/micro-usb), а с другой стороны обычный USB тип-A, в который можно вставлять другие USB устройства. Т.е. можно подключить флешку, видеокамеру, клавиатуру, мышку(sic!). Всё это конечно круто, но если вы хотите создать своё новое USB-slave устройство, которое можно будет подключать к host андроиду, то это не тривиальная задача совсем.
Но прогресс не стоял на месте и Android зарелизил новую фичу USB-accessory mode, доступную для всех андроид устройств старше 4.4. Так вот, этот режим позволяет подключать андроид к другим устройствам в режиме accessory, т.е. теперь не андроид является хостом и питает другое устройство, а наоборот. Вместе с этим, google любезно подготовили библиотеки совместимые со многими arduino и usb-shield-ами. Таким образом, для разработчика железа, можно просто купить arduino, плату расширения usb-host и всё — можно паять своё устройство. А процесс обмена данными будет не простым, а очень простым — чистая передача бинарных данных в обе стороны. Не надо будет разбираться в том как именно работает usb, как правильно подключаться, так как это реализовано с одной стороны на андроиде и с другой в библиотеках для плат расширения. Очень удобно.
Но перейдем ближе к делу.
Краткий обзор API
Есть два способа получить usb-accessory в андроиде, первый способ — это явно запросить usb менеджер выдать вам список всех подключенных устройств и явно запросить доступ к одному из них, а второй способ — это подписаться на событие подключения устройства. Для этого вам нужно узнать manufacturer
, name
, version
устройства и прописать это в файле src/main/res/xml/accessory_filter.xml
. А в манифесте объявить какая активность будет реагировать на это событие. И конечно же в манифесте надо прописать, что вы используете feature usb-accessory.
<!-- accessory_filter.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory manufacturer="SomeManufacturer" model="SUPER9000" version="1.0" />
</resources>
<!-- manifest.xml -->
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true" />
<application>
...
<activity>
...
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
</application>
Интересны факт. Можно не создавать accessory_filter.xml
, а пользоваться только первым подходом. Сейчас андроид не требует явного определения устройств с которыми вы будете работать.
// activity.kt
// вариант с явным запросом всех подключенных устройств.
val usbAccessory = context.usbManager.accessoryList?.firstOrNull()
// вариант с подписыванием на событие и его обработкой
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
log.info("USB device listener woke up")
log.info("Got intent $intent")
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED == intent.action) {
val refreshDeviceIntent = Intent(UsbCanManager.ACTION_USB_DEVICE_ATTACHED)
refreshDeviceIntent.putExtras(intent.extras)
sendBroadcast(refreshDeviceIntent)
log.info("Resend USB intent to service $refreshDeviceIntent")
}
finish()
}
Но в любом случае, вы должны попросить у пользователя права на работу с данным устройством.
context.usbManager.requestPermission(accessory, permissionIntent)
И уже потом можно будет начать работу с usb:
val parcelFd = usbManager.openAccessory(accessory) // открыть дескриптор
val inputStream = FileInputStream(pfd.fileDescriptor) // открыть потоки чтения и записи
val outputStream = FileOutputStream(pfd.fileDescriptor)
И естественно, если есть событие на подключение устройства, то есть и обратное ему: UsbManager.ACTION_USB_ACCESSORY_DETACHED
.
История из жизни. Пришлось мне разрабатывать приложение для какой-то штуки, которая снимает показатели с датчиков машины и может быть подключена к андроиду по usb. По счастливой случайности я не знал ни формат бинарного протокола, ни имя-версию устройства. Вообще ничего. Даже эмулятора всей штуки не было, а тестировать все будет клиент, который находится +6 часов от меня. И вот тут-то в режиме реального времени через VPN я отлаживал процесс подключения через accessory. И как раз таки первый подход, где можно получить список устройств и спас меня. Так как те крохи спецификации железяки были неверными и подход с событием совсем не работал.
Я рекомендую в приложении использовать сразу два подхода, на случай проблем с именем железяки, что приведет к неработоспособности подхода с фильтром. Стратегия работы будет тогда следующей — при запуске приложение проверяет подключен ли к нему девайс, если подключен, тогда запускаем вручную наши сервисы. Можно добавить кнопку, переподключиться (или swipe-to-refresh), чтобы можно было в любой момент повторить всю процедуру сначала.
Напоминание! Вся эта магия будет у вас происходить асинхронно и не однопоточно, поэтому подумайте о критических секциях и блокировках. Лучше конечно всё завернуть в один event-loop, чтобы проблем многопоточности у вас не было в принципе.
Тестирование
Всё это конечно отлично, даже замечательно. Но чудесная библиотека от гугла, внезапно не заработала для моего arduino UNO и sparkfun shield. Что же делать? В этот момент логично предположить, что мы бы могли использовать наш десктоп с linux на борту выступать в роли хоста для нашего девайса. Я потратил некоторое время, чтобы наконец-то найти пример этой чудесной программы на чистом C, которая не требует тучи зависимостей и работает как надо.
Круто, подумал было я, и принялся отлаживать! Но как дебажить, если USB уже занят? Для этих целей есть функция проброса adb через вайфай. Это описано в официальной документации. Перечислю кратко шаги:
- Подключить android к компу по USB.
- Прописать
adb tcpip 5555
. - Отсоединить девайс.
- Найти ip адрес андроид устройства в его настройках.
adb connect <DEVICE_IP_ADDRESS>:5555
- Profit!
Подводные камни? Какие подводные камни?
Вы было подумали, что это идеально работает? Теперь вы сможете отлаживать работу usb?? Как бы не так! В документации совсем не говорят, что почему-то после подключения устройства по USB к другому устройству, даже не к КОМПУ с которого разрешили отладку, соединение по wifi-adb пропадает! ВСЕГДА. Нужно переподключаться.
Если вы хотели отладить процесс подсоединения устройства, тот момент прилетания события от ОС о факте подключения usb-accessory, то ничего у вас никогда не получится. Андроид разорвет adb соединение. Вам придется опять писать
adb connect
. Но момент будет упущен!