2017年11月19日日曜日

Android Things をやってみよう - InputDriver 編

User-Space Drivers : input

InputDriver を使うと、タッチイベントやキーイベントをシステムに流すことができます。

これにより、例えば GPIO から入力があったときに Space キーや Enter キーなどの KeyEvent を流すことで、KeyEvent を処理する機能やライブラリをそのまま流用できます。

InputDriver を使うときは com.google.android.things.permission.MANAGE_INPUT_DRIVERS パーミッションが必要です。 <uses-permission android:name="com.google.android.things.permission.MANAGE_INPUT_DRIVERS" />

InputDriver.Builder を使って InputDriver を生成します。

UserDriverManagerregisterInputDriver() で InputDriver を登録し、最後に unregisterInputDriver() で登録を解除します。

InputDriver にキーイベントを流すときは InputDrivder.emit() に KeyEvent を渡します。 class InputDriverActivity : Activity() { private var gpio: Gpio? = null private var inputDriver: InputDriver? = null private val callback: GpioCallback = object : GpioCallback() { override fun onGpioEdge(gpio: Gpio): Boolean { val action = if (gpio.value) KeyEvent.ACTION_DOWN else KeyEvent.ACTION_UP // ドライバーに Enter キーイベントを流す inputDriver?.emit(arrayOf(KeyEvent(action, KeyEvent.KEYCODE_ENTER))) return true } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val pioService = PeripheralManagerService() try { gpio = pioService.openGpio("GPIO_174").apply { setDirection(Gpio.DIRECTION_IN) setEdgeTriggerType(Gpio.EDGE_BOTH) setActiveType(Gpio.ACTIVE_LOW) registerGpioCallback(callback) } } catch (e: IOException) { Log.e(TAG, "Error initializing GPIO", e) } // Enter キーをサポートするドライバーを用意 inputDriver = InputDriver.Builder(InputDevice.SOURCE_CLASS_BUTTON) .setName("Button") .setVersion(1) .setKeys(intArrayOf(KeyEvent.KEYCODE_ENTER)) .build() // ドライバーを登録 UserDriverManager.getManager().registerInputDriver(inputDriver) } override fun onDestroy() { if (inputDriver != null) { // ドライバーの登録を解除 UserDriverManager.getManager().unregisterInputDriver(inputDriver) inputDriver = null } gpio?.unregisterGpioCallback(callback) try { gpio?.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } super.onDestroy() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyDown : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyUp : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyUp(keyCode, event) } companion object { private const val TAG = "InputDriverActivity" } }

contrib-drivers

https://developer.android.com/things/training/first-device/drivers.html#initialize_the_driver_library

contrib-drivers の button にある ButtonInputDriver を利用すると、GPIO の入力を簡単に KeyEvent に割り当てられます。 implementation 'com.google.android.things.contrib:driver-button:0.3' class ButtonInputDriverActivity : Activity() { private var inputDriver: ButtonInputDriver? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) inputDriver = ButtonInputDriver("GPIO_174", Button.LogicState.PRESSED_WHEN_LOW, KeyEvent.KEYCODE_ENTER) } override fun onDestroy() { inputDriver?.unregister() try { inputDriver?.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } super.onDestroy() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyDown : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyUp : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyUp(keyCode, event) } companion object { private const val TAG = "ButtonInputDriverActivity" } }

moshi で独自の Adapter は KotlinJsonAdapterFactory より先に add すべし

Retrofit と一緒にmoshi を使っています。
moshi には Kotlin Support 機能があり、別途 moshi-kotlin を追加して、 implementation 'com.squareup.moshi:moshi-kotlin:1.5.0' KotlinJsonAdapterFactory を MoshiBuilder に add します。 val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build()

また、moshi には Custom Type Adapters 機能があり、JSON の値とオブジェクトとの変換をカスタマイズすることができます。

この CustomAdapter および、CustomAdapter の Factory は KotlinJsonAdapterFactory より先に add しなければいけません (Kotlin Support のところに書いてあるのに気づかずはまってしまった...)。 Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create( Moshi.Builder() .add(MyCustomAdapter()) // KotlinJsonAdapterFactory より先なので OK .add(KotlinJsonAdapterFactory()) .build())) ... Adapter を生成するときに、追加された順番で Factory に Adapter が生成できるか問い合わせていくため、KotlinJsonAdapterFactory を先にすると CustomAdapter に来ないので気をつけましょう。


2017年11月12日日曜日

Android Things をやってみよう - Peripheral 操作編

Peripheral の操作は PeripheralManagerService を使って行います。

GPIO の名前の一覧は PeripheralManagerService.getGpioList() で取得できます。 val pioService = PeripheralManagerService() for (name in pioService.gpioList) { Log.d(TAG, "gpio : $name") } 実行結果 gpio : GPIO_10 gpio : GPIO_128 gpio : GPIO_172 gpio : GPIO_173 gpio : GPIO_174 gpio : GPIO_175 gpio : GPIO_32 gpio : GPIO_33 gpio : GPIO_34 gpio : GPIO_35 gpio : GPIO_37 gpio : GPIO_39

PeripheralManagerService.openGpio() で GPIO を開きます。戻り値は Gpio です。引数には getGpioList() で取得した名前を指定します。 val ledPin = pioService.openGpio(name)
GPIO は General-purpose input/output のことで、GPIO pin はソフトウェアからコントロールできる集積回路上の物理的ピンです。GPIO pin は入力として使って電圧値を読んだり、出力として使って電圧値を変えたりできます。

扱えるのは論理値(true / false)のみで、low value(ピンがグラウンドと同じ電圧値)と high value(ピンが IOREF と同じ電圧値)が論理値にマッピングされます。論理値と high/low のマッピング(true が high/low どちらに対応するか)は設定で変えることができます。

GPIO pin を開くということは、システム全体でこのピンに対するオーナシップを取るということです。これにより close() が呼ばれるまで他から GPIO を開いたりアクセスしたりするのを防ぎます。close() を呼ぶのを忘れると、同じプロセス・アプリに関係なくどこからも GPIO が使えなくなるので注意が必要です。


論理値と high/low のマッピングは Gpio.setActiveType() で指定します。
true に high を対応させるときは Gpio.ACTIVE_HIGH を、true に low を対応させるときは Gpio.ACTIVE_LOW を指定します。 ledPin.setActiveType(Gpio.ACTIVE_HIGH)
ピンの方向(入力 or 出力)は Gpio.setDirection() で指定します。
ピンを入力として使うときは Gpio.Gpio.DIRECTION_IN を指定します。出力として使うときは、初期状態を high にするときは Gpio.DIRECTION_OUT_INITIALLY_HIGH を初期状態を low にするときは Gpio.DIRECTION_OUT_INITIALLY_LOW を指定します。 ledPin.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW)
値が変わったときのトリガーのタイミングを Gpio.setEdgeTriggerType() で指定することができます。
ピンを出力として使うときはトリガーの必要がないので Gpio.EDGE_NONE を指定します。
ピンを入力として使う場合、立ち上がり時(low から high になったとき)だけトリガーさせるなら Gpio.EDGE_RISING を指定します。立ち下がり時(high から low になったとき)だけトリガーさせるなら Gpio.EDGE_FALLING を指定します。立ち上がり、立ち下がり両方トリガーさせるなら Gpio.EDGE_BOTH を指定します。 ledPin.setEdgeTriggerType(Gpio.EDGE_NONE)
現在の値を変更するときは Gpio.setValue() を使います。このメソッドはピンが出力として設定されているときだけ使うことができます。 ledPin.value = isChecked
現在の値を読むときは Gpio.getValue() を使います。このメソッドはピンが入力として設定されているときだけ使うことができます。 val value = ledPin.value
ピンの値が変わった時にコールバックを受け取るには Gpio.registerGpioCallback()GpioCallback を登録します。登録した GpioCallback は Gpio.unregisterGpioCallback() で登録解除します。 private val callback: GpioCallback = object : GpioCallback() { override fun onGpioEdge(gpio: Gpio?): Boolean { Log.d(TAG, "onGpioEdge: ${gpio?.name}, ${gpio?.value}") return true } override fun onGpioError(gpio: Gpio?, error: Int) { super.onGpioError(gpio, error) /** error : [android.system.OsConstants] */ Log.d(TAG, "onGpioError: ${gpio?.name}, $error") } } override fun onCreate(savedInstanceState: Bundle?) { ... ledPin.registerGpioCallback(callback) } override fun onDestroy() { super.onDestroy() try { ledPin.unregisterGpioCallback(callback) ledPin.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } }