AndroidからUSB HIDでマイコンを制御するまとまった例が見当たらなかったので、試作してみました。
概要
Android端末のUSB端子にマイコンをつなぎ、マイコン上に搭載されたLEDの点灯をAndroidアプリから制御します。
マイコンにはARM mbedを用い、Androidとマイコン間はUSB HIDで通信します。
ソースコード
mbedとは
mbedはARM社のプロトタイピング用マイコンボードとそれの開発環境です。
mbedに似たプロトタイピング環境としてはArduinoがありますが、Arduinoに比較すると
- ブラウザ上で動くオンライン開発環境の上で開発ができるためにコンパイラ等のツールのインストールが必要なかったり1
- Arduinoにくらべてライブラリの抽象度が高めでプログラミングしやすいと
いった特徴があります。
今回はmbedがUSB HIDデバイスとして振る舞うことができるためこれを採用しています。
ほかにUSB HIDデバイスとして振る舞える開発ボードには、Arduino Leonardがあります。
USB HIDとは
USB HID(Human Interface Device)はUSBでのデータの流し型の規格のひとつで、キーボードやマウスといった人間が操作するデバイスを使うためのものです。
WindowsやMac・Androidなどには標準でUSB HIDのドライバが標準で入っているためドライバのインストールが必要ないことや、mbedやArduino LeonardといったボードはUSB HIDデバイスと振る舞うための機能があらかじめ入っていることから、マイコン制御に使いやすいです。
マイコンの制御の方法としてUSB-シリアルを用いることが多いですが、USB HIDを使うメリットは下記の通りです。
- ホスト側にドライバのインストールが必要ない
- データが壊れているかどうかをUSBのレイヤーで判定してくれるので、受信側プログラム側でデータが壊れているかどうかを判定しなくてもよい
- データが壊れている場合は、受信側プログラムにはデータが届かない
ハードウェア準備
準備するもの
- mbedボード
- ブレッドボード
- USBブレークアウト基板
- ブレッドボード・ジャンパーワイヤ(オス-オス) 4本
- Android端末
- USB OTGケーブル
mbedボード
mbedのボードとしてLPC11U24を使います。
確認はしていませんが、LPC1768でも同じように動くはずです。
画像はhttps://www.switch-science.com/catalog/850/より引用
ブレッドボード
ブレッドボードは部品やジャンパワイヤを刺すだけで回路が組み立てられる基板です。
刺すだけで回路が組み立てられるので、試作に便利です。(半田付けが必要ないので楽)
適当な大きさのものを使用してください。
幅が広めのブレッドボードがおすすめです。
画像はhttps://www.switch-science.com/catalog/3499/より引用
USBブレークアウト基板
USBブレークアウト基板はUSBポートをブレッドボードに接続するための基板です。
画像はhttps://www.switch-science.com/catalog/1599/より引用
USBブレークアウト基板をブレッドボードにつなぐには、USBブレークアウト基板にピンヘッダを半田付けする必要があります。
ブレッドボード・ジャンパーワイヤ(オス-オス) 4本
ブレッドボード上でmbedとUSBブレークアウト基板をつなぐための配線です。
画像はhttps://www.switch-science.com/catalog/620/より引用
USB OTGケーブル
私は普通のUSB-micro USBケーブルにダイソーのmicroUSB変換アダプタをつなげています。
mbedとUSBブレークアウト基板をつなぐ
mbedからAndroidに接続するためできるように、mbedとUSBブレークアウト基板をつなぎます。
配線は下記の通りです。
- mbedのVINとUSBブレークアウト基板のVCC
- mbedのGNDとUSBブレークアウト基板のGND
- mbedのD+とUSBブレークアウト基板のD+
- mbedのD-とUSBブレークアウト基板のD-
Androidとマイコンのデータのプロトコル
今回のAndroidとマイコンのデータのプロトコルはこのように単純なものとします。
Androidからマイコンに送るデータ
4バイト長のデータで、
1バイト目が0だったらマイコンのLED1を消灯・0以外だったらマイコンのLED1を点灯
2バイト目が0だったらマイコンのLED2を消灯・0以外だったらマイコンのLED2を点灯
3バイト目が0だったらマイコンのLED3を消灯・0以外だったらマイコンのLED3を点灯
4バイト目が0だったらマイコンのLED4を消灯・0以外だったらマイコンのLED4を点灯
マイコンからAndroidへのレスポンス
データが4バイト以上だったら1バイトで0
データが4バイト未満だったら1バイトで1
マイコン側(mbed)
前述のプロトコルをそのまま実装しています。
#include "mbed.h"
#include "USBHID.h"
// USB HIDデバイス
USBHID hid;
// HID読み込み
HID_REPORT hidReceive;
// HID書き込み
HID_REPORT hidSend;
// LED
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);
DigitalOut led4(LED4);
int main(void) {
// 起動時にはLEDは消灯する
led1 = 0;
led2 = 0;
led3 = 0;
led4 = 0;
while (true) {
// USBからデータを読み込む
bool readResult = hid.read(&hidReceive);
if (readResult) {
// USBからのデータが4バイト以上の場合...
if (hidReceive.length >= 4) {
// USBからのデータを解析する
//
// 4バイトのデータで、LEDを点灯するかどうかのデータが各1バイトずつに格納されている
// | LED1 | LED2 | LED3 | LED4 |
led1 = hidReceive.data[0] == 0 ? 0 : 1;
led2 = hidReceive.data[1] == 0 ? 0 : 1;
led3 = hidReceive.data[2] == 0 ? 0 : 1;
led4 = hidReceive.data[3] == 0 ? 0 : 1;
// USBで0(成功)を返す
hidSend.length = 1;
hidSend.data[0] = 0;
hid.sendNB(&hidSend);
}
// USBからのデータが4バイト以外の場合...
else if (hidReceive.length > 0) {
// USBで1(エラー)を返す
hidSend.length = 1;
hidSend.data[0] = 1;
hid.sendNB(&hidSend);
}
}
}
}
Android側
AndroidManifest.xml
AndroidManifest.xmlに <uses-feature android:name="android.hardware.usb.host" />
を追加してください。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.yhirano.usb_hid_sample.android">
<!-- USBホストをサポートしている端末である必要がある -->
<uses-feature android:name="android.hardware.usb.host" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
USB HID
USB HIDで通信するためのUsbHidクラスを作りました。
コピペで使えるはずです。
UsbHid.kt
UsbHidクラスを通してUSB HIDデバイスを制御できます。
デバイスと接続するには UsbHid#openDevice()
、デバイスにデータを送信するには UsbHid#write(data: ByteArray)
、デバイスと切断するには UsbHid#closeDevice()
を実行します。
USB HIDデバイスからのデータの読み込むのには UsbHid#readListener
を設定してください。
package com.github.yhirano.usbhid
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbDeviceConnection
import android.hardware.usb.UsbManager
import android.util.Log
import java.util.concurrent.Executors
class UsbHid constructor(
context: Context,
private val vendorId: Int,
private val productId: Int
) {
enum class State {
Uninitialized,
PermissionRequesting,
FailedInitialize,
Working
}
interface Listener {
/**
* Receive new data.
*/
fun onNewData(data: ByteArray)
/**
* Occurred error.
*
* @param e Occurred exception.
*/
fun onRunError(e: Exception)
/**
* Changed state.
*
* @param state state
*/
fun onStateChanged(state: State)
}
var listener: Listener? = null
/**
* Default number of retries on write error.
* If it is less than or equal to 0, no retries.
*/
var defaultRetry: Int = 0
set(value) {
field = value
writeManager?.defaultRetry = value
}
var state = State.Uninitialized
private set(value) {
val changed = field != value
field = value
if (changed) {
listener?.onStateChanged(field)
}
}
private val context = context.applicationContext
private val usbManager: UsbManager by lazy {
context.getSystemService(Context.USB_SERVICE) as UsbManager
}
private var device: UsbDevice? = null
private var port: Port? = null
private var readManager: ReadManager? = null
private var writeManager: WriteManager? = null
private var isRegisterUsbPermissionReceiver = false
fun openDevice(): State {
val usbAttachIntentFilter = IntentFilter().apply {
addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
}
context.registerReceiver(usbAttachReceiver, usbAttachIntentFilter)
state = connect()
return state
}
fun closeDevice() {
state = disconnect()
try {
context.unregisterReceiver(usbAttachReceiver)
} catch (e: IllegalArgumentException) {
Log.i(TAG, "Ignore IllegalArgumentException because usbAttachReceiver isn't attached.", e)
}
if (isRegisterUsbPermissionReceiver) {
context.unregisterReceiver(usbPermissionReceiver)
isRegisterUsbPermissionReceiver = false
}
}
/**
* @param retry Number of retries at data write error. If null, the number of times specified in [defaultRetry].
*/
fun write(data: ByteArray, retry: Int? = null) {
writeManager?.writeAsync(data, retry)
?: Log.w(TAG, "Failed to write data to USB HID because UsbHid class isn't open.")
}
private fun connect(): State {
val deviceList = usbManager.deviceList
val foundDevices = deviceList.values.filter {
it.vendorId == vendorId && it.productId == productId
}
if (foundDevices.isEmpty()) {
Log.d(TAG, "Not found devices.")
return State.FailedInitialize
}
Log.d(TAG, "Found ${foundDevices.size} device(s).")
foundDevices.forEachIndexed { i, device ->
Log.d(TAG, " ${i + 1}: DeviceName=\"${device.deviceName}\", ManufacturerName=\"${device.manufacturerName}\", ProductName=\"${device.productName}\", VendorId=${String.format("0x%04X", device.vendorId)}, ProductId=${String.format("0x%04X", device.productId)}")
}
val device = foundDevices[0]
this.device = device
Log.d(TAG, "Connect to \"${device.deviceName}\".")
val connection = usbManager.openDevice(device)
return if (connection == null) {
val usbSerialPermissionIntent =
PendingIntent.getBroadcast(context, 0, Intent(ACTION_USB_PERMISSION), 0)
val usbPermissionIntentFilter = IntentFilter().apply {
addAction(ACTION_USB_PERMISSION)
}
context.registerReceiver(usbPermissionReceiver, usbPermissionIntentFilter)
isRegisterUsbPermissionReceiver = true
usbManager.requestPermission(device, usbSerialPermissionIntent)
State.PermissionRequesting
} else {
initPort(device, connection)
}
}
private fun disconnect(): State {
device = null
port?.close()
port = null
stopIoManager()
return State.Uninitialized
}
private fun initPort(device: UsbDevice, connection: UsbDeviceConnection): State {
port = Port.create(device, connection)
return if (port != null) {
startIoManager()
State.Working
} else {
Log.d(TAG, "Couldn't initialize port. Device has no read and write endpoint. port=\"${port}\"")
State.FailedInitialize
}
}
private fun startIoManager() {
val port = port
if (port == null) {
Log.w(TAG, "Has no USB device. Maybe not initialize yet.")
return
}
val readManager = ReadManager(
port,
object : ReadManager.Listener {
override fun onNewData(data: ByteArray) {
listener?.onNewData(data)
}
override fun onRunError(e: Exception) {
listener?.onRunError(e)
}
})
val writeManager = WriteManager(port, object : WriteManager.Listener {
override fun onRunError(e: Exception) {
listener?.onRunError(e)
}
}).apply {
defaultRetry = this@UsbHid.defaultRetry
}
this.readManager = readManager
this.writeManager = writeManager
Executors.newSingleThreadExecutor().submit(readManager)
Executors.newSingleThreadExecutor().submit(writeManager)
}
private fun stopIoManager() {
readManager?.stop()
?: Log.w(TAG, "Has no data reading thread. Maybe not initialize yet.")
readManager = null
writeManager?.stop()
?: Log.w(TAG, "Has no data writing thread. Maybe not initialize yet.")
writeManager = null
}
private val usbPermissionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == ACTION_USB_PERMISSION) {
synchronized(this) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
val device = device
if (device == null) {
Log.w(TAG, "Failed to connect device.")
state = State.FailedInitialize
return
}
val connection = usbManager.openDevice(device)
if (connection == null) {
Log.w(TAG, "Failed to connect device.")
state = State.FailedInitialize
return
}
state = initPort(device, connection)
return
} else {
Log.i(
TAG,
"Couldn't obtain connecting permission to \"${device?.deviceName}\"."
)
state = State.FailedInitialize
}
}
}
}
}
private val usbAttachReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
Log.d(TAG, "USB Device attached.")
when (state) {
State.Uninitialized, State.FailedInitialize -> {
connect()
}
State.PermissionRequesting, State.Working -> {
// Ignore
}
}
}
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
Log.d(TAG, "USB Device detached.")
when (state) {
State.Working -> {
state = disconnect()
}
State.Uninitialized, State.FailedInitialize, State.PermissionRequesting -> {
// Ignore
}
}
}
}
}
}
companion object {
private const val TAG = "UsbHid"
private const val ACTION_USB_PERMISSION = "com.github.yhirano.usbhid.USB_PERMISSION"
}
}
Port.kt
USBのデバイスを管理するクラスです。
1デバイスにつき1つのPortインスタンスが作られます。
package com.github.yhirano.usbhid
import android.hardware.usb.*
import android.util.Log
import java.io.IOException
class Port private constructor(
private val device: UsbDevice,
private val connection: UsbDeviceConnection,
private val usbInterface: UsbInterface,
private val readEndpoint: UsbEndpoint,
private val writeEndpoint: UsbEndpoint
) {
override fun toString(): String {
return "Port(device=$device)"
}
fun close() {
connection.releaseInterface(usbInterface)
connection.close()
}
fun read(dest: ByteArray, timeout: Int): Int {
return connection.bulkTransfer(readEndpoint, dest, dest.size, timeout)
}
/**
* @exception IOException Failed to write data to USB.
*/
fun write(data: ByteArray, timeout: Int) {
val length = connection.bulkTransfer(writeEndpoint, data, data.size, timeout)
if (length <= 0) {
throw IOException("Failed to write data to USB. status=$length")
}
}
companion object {
private const val TAG = "UsbHid/Port"
fun create(device: UsbDevice, connection: UsbDeviceConnection): Port? {
val interfaceCount = device.interfaceCount
for (ii in 0 until interfaceCount) {
val usbInterface = device.getInterface(ii)
if (usbInterface.interfaceClass != UsbConstants.USB_CLASS_HID) {
continue
}
var readEndpoint: UsbEndpoint? = null
var writeEndpoint: UsbEndpoint? = null
val endpointCount = usbInterface.endpointCount
for (ei in 0 until endpointCount) {
val endPoint = usbInterface.getEndpoint(ei)
if (endPoint.direction == UsbConstants.USB_DIR_IN &&
(endPoint.type == UsbConstants.USB_ENDPOINT_XFER_BULK || endPoint.type == UsbConstants.USB_ENDPOINT_XFER_INT)
) {
readEndpoint = endPoint
} else if (endPoint.direction == UsbConstants.USB_DIR_OUT &&
(endPoint.type == UsbConstants.USB_ENDPOINT_XFER_BULK || endPoint.type == UsbConstants.USB_ENDPOINT_XFER_INT)
) {
writeEndpoint = endPoint
}
if (readEndpoint != null && writeEndpoint != null) {
val connected = connection.claimInterface(usbInterface, true)
if (!connected) {
Log.w(TAG, "Failed to connect to USB device. Interface could not be claimed.")
connection.close()
return null
}
return Port(
device,
connection,
usbInterface,
readEndpoint,
writeEndpoint
)
}
}
}
return null
}
}
}
データの読み込みにはUsbConnection#bulkTransfer
を使っています。
UsbConnection#bulkTransferはバギーだというレポートも目にしますが(実際にバギーです)、USB HIDの最大データ長である64バイトのデータを扱うのならば特に問題無さそうでした。
ReadManager.kt
非同期でのUSB HIDデバイスからの読み込みを司るクラスです。
UsbHidクラス内で使っているクラスで、外部からは触れません。
package com.github.yhirano.usbhid
import android.util.Log
import java.io.IOException
import java.nio.ByteBuffer
internal class ReadManager(
private val port: Port,
@Suppress("unused")
var listener: Listener? = null
) : Runnable {
interface Listener {
fun onNewData(data: ByteArray)
fun onRunError(e: Exception)
}
enum class State {
STOPPED, RUNNING, STOPPING
}
@Suppress("unused")
var timeout: Int = 30
private var state = State.STOPPED
private val readBuffer = ByteBuffer.allocate(4096)
override fun run() {
synchronized(state) {
check(state == State.STOPPED) { "Already running" }
state = State.RUNNING
}
try {
while (true) {
if (state != State.RUNNING) {
break
}
work()
if (state != State.RUNNING) {
break
}
Thread.sleep(10)
}
} catch (e: Exception) {
Log.w(TAG, "Occurred exception. exception=\"${e.message}\"", e)
listener?.onRunError(e)
} finally {
synchronized(state) {
state = State.STOPPED
}
}
}
fun stop() {
synchronized(state) {
if (state == State.RUNNING) {
state = State.STOPPING
}
}
}
private fun work() {
try {
val length = port.read(readBuffer.array(), timeout)
if (length > 0) {
val data = ByteArray(length)
readBuffer.get(data, 0, length)
listener?.onNewData(data)
}
} catch (e: IOException) {
Log.w(TAG, "Occurred exception when USB reading. exception=\"${e.message}\"", e)
listener?.onRunError(e)
} finally {
readBuffer.clear()
}
}
companion object {
private const val TAG = "UsbHid/ReadManager"
}
}
Timeout
が30ミリ秒なのは、これくらいなら大丈夫そうな値を適当に設定しているだけです。
不具合がある場合は調整してみて下さい。
WriteManager.kt
非同期でのUSB HIDデバイスへの書き込みを司るクラスです。
UsbHidクラス内で使っているクラスで、外部からは触れません。
package com.github.yhirano.usbhid
import android.util.Log
import java.io.IOException
import java.util.*
internal class WriteManager(private val port: Port, var listener: Listener? = null) : Runnable {
interface Listener {
fun onRunError(e: Exception)
}
enum class State {
STOPPED, RUNNING, STOPPING
}
private enum class WorkResult {
QUEUE_IS_EMPTY,
WROTE_DATA,
CAUSE_WRITE_ERROR,
}
private class WriteData(val data: ByteArray, val retry: Int?)
/**
* USB data write timeout. in milliseconds, 0 is infinite.
*/
@Suppress("unused")
var timeout: Int = 30
/**
* If there is data to write next, write the data without the thread to sleep.
* Not recommended for all devices.
*/
@Suppress("unused")
var writeDataImmediatelyIfExists = false
/**
* Default number of retries on write error.
* If it is less than or equal to 0, no retries.
*/
var defaultRetry: Int = 0
/**
* Sleeping time before retry bacause write data error.
*/
@Suppress("unused")
var sleepMillisSecBeforeRetry: Long = 10
private var state = State.STOPPED
private val writeDataQueue = LinkedList<WriteData>()
override fun run() {
synchronized(state) {
check(state == State.STOPPED) { "Already running" }
state = State.RUNNING
}
try {
while (true) {
if (state != State.RUNNING) {
break
}
val workResult = work()
if (state != State.RUNNING) {
break
}
if (workResult == WorkResult.QUEUE_IS_EMPTY || !writeDataImmediatelyIfExists) {
Thread.sleep(10)
}
}
} catch (e: Exception) {
Log.w(TAG, "Occurred exception. exception=\"${e.message}\"", e)
listener?.onRunError(e)
} finally {
synchronized(state) {
state = State.STOPPED
}
}
}
fun stop() {
synchronized(state) {
if (state == State.RUNNING) {
state = State.STOPPING
}
}
}
/**
* @param retry Number of retries at data write error. If null, the number of times specified in [defaultRetry].
*/
fun writeAsync(data: ByteArray, retry: Int? = null) {
synchronized(writeDataQueue) {
writeDataQueue.add(WriteData(data, retry))
}
}
private fun work(): WorkResult {
return try {
val writeData = synchronized(writeDataQueue) {
writeDataQueue.poll()
}
if (writeData != null) {
val data = writeData.data
val retry = writeData.retry ?: defaultRetry
write(data, timeout, retry, sleepMillisSecBeforeRetry)
WorkResult.WROTE_DATA
} else {
WorkResult.QUEUE_IS_EMPTY
}
} catch (e: IOException) {
Log.w(TAG, "Occurred exception when USB writing. exception=\"${e.message}\"", e)
listener?.onRunError(e)
WorkResult.CAUSE_WRITE_ERROR
}
}
/**
* Write data to USB device.
*
* @param data Writing data
* @param timeout USB data write timeout. in milliseconds, 0 is infinite.
* @param retry Number of retries; if this number is less than or equal to 0, no retries.
* @param sleepMillisSecBeforeRetry Sleeping time before retry.
* @exception IOException When data is not written to USB after the specified number of retries.
*/
private fun write(data: ByteArray, timeout: Int, retry: Int, sleepMillisSecBeforeRetry: Long) {
try {
port.write(data, timeout)
} catch (e: IOException) {
if (retry > 0) {
Log.d(TAG, "Retry send data because occurred exception when USB writing. data=${data.contentToHexString()}, retry=$retry, exception=\"${e.message}\"")
if (sleepMillisSecBeforeRetry > 0) {
Thread.sleep(sleepMillisSecBeforeRetry)
}
write(data, timeout, retry - 1, sleepMillisSecBeforeRetry)
} else {
Log.w(TAG, "Failed to retry send data because occurred exception when USB writing. data=${data.contentToHexString()} exception=\"${e.message}\"")
throw e
}
}
}
companion object {
private const val TAG = "UsbHid/WriteManager"
private fun ByteArray?.contentToHexString(): String {
if (this == null) return "null"
val iMax = this.size - 1
if (iMax == -1) return "[]"
val b = StringBuilder()
b.append('[')
var i = 0
while (true) {
b.append(String.format("0x%02X", this[i]))
if (i == iMax) return b.append(']').toString()
b.append(", ")
++i
}
}
}
}
Timeout
が30ミリ秒なのは、これくらいなら大丈夫そうな値を適当に設定しているだけです。
不具合がある場合は調整してみて下さい。
UI
package com.github.yhirano.usb_hid_sample.android
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatToggleButton
import com.github.yhirano.usbhid.UsbHid
class MainActivity : AppCompatActivity() {
private val led1Button by lazy {
findViewById<AppCompatToggleButton>(R.id.led1_button)
}
private val led2Button by lazy {
findViewById<AppCompatToggleButton>(R.id.led2_button)
}
private val led3Button by lazy {
findViewById<AppCompatToggleButton>(R.id.led3_button)
}
private val led4Button by lazy {
findViewById<AppCompatToggleButton>(R.id.led4_button)
}
private lateinit var usbHid : UsbHid
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
usbHid = UsbHid(applicationContext, 0x1234, 0x0006, object : UsbHid.ReadListener {
override fun onRunError(e: Exception) {
Log.w(TAG, "Occurred USB HID error.", e)
}
override fun onNewData(data: ByteArray) {
Log.i(TAG, "Receive data from USB HID. data=${data.contentToString()}")
runOnUiThread {
Toast
.makeText(this@MainActivity, data.contentToString(), Toast.LENGTH_SHORT)
.show()
}
}
}).apply {
openDevice()
}
led1Button.setOnCheckedChangeListener { _, _ -> sendLedsCommandToDevice() }
led2Button.setOnCheckedChangeListener { _, _ -> sendLedsCommandToDevice() }
led3Button.setOnCheckedChangeListener { _, _ -> sendLedsCommandToDevice() }
led4Button.setOnCheckedChangeListener { _, _ -> sendLedsCommandToDevice() }
}
override fun onDestroy() {
super.onDestroy()
usbHid.closeDevice()
}
private fun sendLedsCommandToDevice() {
val command = ByteArray(4)
command[0] = if (led1Button.isChecked) 1 else 0
command[1] = if (led2Button.isChecked) 1 else 0
command[2] = if (led3Button.isChecked) 1 else 0
command[3] = if (led4Button.isChecked) 1 else 0
usbHid.write(command)
}
companion object {
private const val TAG = "MainActivity"
}
}
UsbHidクラスのコンストラクタに指定している0x1234
はLPC11U24のVendorID, 0x0006
はProductIDです。
できたもの
参考文献
Android + USB HID
- USB ホストの概要 | Android デベロッパー | Android Developers
- Example of code that uses HID for Android to connect to the device through USB.
mbed + USB HID
Android + mbed + USB HID
-
オフラインでも開発が可能です。VSCode + PlatformIOならば開発環境のインストールも手軽です。 ↩