
Nos aplicativos modernos do Android para interagir com outros dispositivos, os protocolos de transferência de dados sem fio, como o Bluetooth, são mais frequentemente usados. Nos anos em que alguns dispositivos têm carregamento sem fio, é difícil imaginar vários dispositivos e periféricos Android, que exigem o uso de interfaces com fio. No entanto, quando surge essa necessidade, o USB vem à mente imediatamente.
Vamos levar um caso hipotético com você. Imagine que um cliente chegue até você e diga: “Preciso de um aplicativo Android para controlar um dispositivo de coleta de dados e exibir esses dados na tela. Há um MAS - o aplicativo deve ser gravado em um computador de placa única com o sistema operacional Android e o dispositivo periférico está conectado via USB ”
Parece fantástico, mas às vezes acontece. E aqui é muito útil ter um conhecimento profundo da pilha USB e seus protocolos, mas este artigo não é sobre isso. Neste artigo, veremos como controlar um dispositivo periférico usando o protocolo USB Custom HID de um dispositivo Android. Por simplicidade, escreveremos um aplicativo Android (HOST) que controlará o LED no dispositivo periférico (DEVICE) e receberá o estado do botão (pressione). Eu não vou dar o código para a placa periférica, que está interessado - escreva nos comentários.
Então, vamos começar.
Teoria O mais curto possível
Primeiro, um pouco de teoria, o mais curto possível. Esse é um mínimo simplificado, suficiente para entender o código, mas para um melhor entendimento, aconselho que você se familiarize
com esse recurso .
Para se comunicar via USB em um dispositivo periférico, é necessário implementar uma interface de interação. Várias funções (por exemplo, USB HID, USB Mass Strorage ou USB CDC) implementarão suas interfaces e algumas terão várias interfaces. Cada interface contém pontos de extremidade - canais de comunicação especiais, uma espécie de área de transferência.
Meu periférico possui um HID personalizado com uma interface e dois pontos de extremidade, um para recebimento e outro para transmissão. Geralmente, as informações com as interfaces e terminais existentes no dispositivo são gravadas na especificação do dispositivo; caso contrário, podem ser determinadas por meio de programas especiais, por exemplo, USBlyzer.
Os dispositivos no USB HID se comunicam por meio de relatórios. O que são relatórios? Como os dados são transmitidos pelos terminais, precisamos identificá-los e analisá-los de acordo com o protocolo. Os dispositivos não apenas jogam bytes de dados entre si, mas trocam pacotes que possuem uma estrutura claramente definida, descrita no dispositivo em um descritor de relatório especial. Assim, de acordo com o descritor do relatório, podemos determinar com precisão qual identificador, estrutura, tamanho e frequência de transmissão possuem determinados dados. O pacote é identificado pelo primeiro byte, que é o ID do relatório. Por exemplo, o status do botão vai para o relatório com ID = 1 e controlamos o LED através do relatório com ID = 2.
Longe do ferro, mais perto do Android
No Android, o suporte para dispositivos USB apareceu a partir da API 12 (Android 3.1). Para trabalhar com um dispositivo periférico, precisamos implementar o modo host USB. Trabalhar com USB é muito bem descrito na documentação.
Primeiro, você precisa identificar o seu dispositivo conectado, entre toda a variedade de dispositivos USB. Os dispositivos USB são identificados por uma combinação de vid (identificação do fornecedor) e pid (identificação do produto). Na pasta xml, crie o arquivo device_filter.xml com o seguinte conteúdo:
<resources> <usb-device vendor-id="1155" product-id="22352" /> </resources>
Agora você precisa fazer as permissões e ações apropriadas (se precisar delas) no manifesto do aplicativo:
<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>
No android: resource, especificamos um arquivo com os filtros necessários para os dispositivos. Além disso, como eu disse anteriormente, você pode atribuir filtros de intenção para iniciar o aplicativo, por exemplo, como resultado da conexão do seu dispositivo.
Primeiro você precisa obter o UsbManager, encontrar o dispositivo, a interface e os pontos finais do dispositivo. Isso deve ser feito sempre que o dispositivo estiver conectado.
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) { 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 }
Aqui vemos as próprias interfaces e terminais discutidos na última seção. Conhecendo o número da interface, encontramos os dois pontos finais para receber e transmitir e iniciar uma conexão USB. Isso é tudo, agora você pode ler os dados.
Como eu disse anteriormente, os dispositivos se comunicam por meio de relatórios.
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 }
Enviamos uma matriz de bytes para o método sendReport, no qual o byte zero é o ID do relatório, levamos a conexão USB atual ao dispositivo e realizamos a transferência. Como parâmetros, transferimos o número do terminal, dados, tamanho e tempo limite de transmissão para o método BulkTransfer. Vale ressaltar que a classe UsbDeviceConnection possui métodos para implementar a troca de dados com um dispositivo USB - métodos bulkTransfer e controlTransfer. Seu uso depende do tipo de transmissão que um endpoint suporta. Nesse caso, usamos bulkTransfer, embora o uso de terminais com o tipo de controle seja mais típico para HIDs. Mas nós temos o HID personalizado, então fazemos o que queremos. Aconselho que você leia sobre o tipo de transmissão separadamente, pois o volume e a frequência dos dados transmitidos dependem disso.
Para receber dados, você precisa saber o tamanho dos dados que podem ser obtidos, como saber com antecedência e obter do ponto de extremidade.
O método de recebimento de dados via USB HID é síncrono e bloqueador e deve ser realizado em um encadeamento diferente; além disso, os relatórios do dispositivo podem ser recebidos constantemente ou a qualquer momento, portanto, é necessário implementar uma pesquisa constante do relatório para não perder os dados. Vamos fazer isso com o RxJava:
fun receive() { Observable.fromCallable<ByteArray> { getReport() } .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .repeat() .subscribe({ },{ }) }
Após receber uma matriz de bytes, devemos verificar o byte zero, pois é um ID de relatório e, de acordo com ele, analisar os dados recebidos.
Após a conclusão de todas as ações com USB, você precisa fechar a conexão. Você pode fazer isso na atividade onDestroy ou no onCleared no ViewModel.
fun close() { usbRequest?.close() usbConnection?.releaseInterface(usbInterface) usbConnection?.close() }
Conclusão
O artigo discute um código extremamente pequeno, primitivo e extremamente demonstrativo com a implementação para um dispositivo específico. Obviamente, existem muitas classes USB, não apenas a HID, e para elas a implementação será naturalmente diferente. No entanto, todos os métodos estão bastante bem documentados e com um bom entendimento da pilha USB, você pode descobrir facilmente como usá-los.
X. Materiais úteis