Android Things 1.0.1搭載のRaspberry Pi 3に、USB接続のFelicaリーダーを繋いで、Felicaのカードを読んでみる

## 経緯
Android Things 1.0.1で遊んでいて、HIDのキーボードもマウスも繋がるなあと、USB結構使えるんじゃないか？と、USB接続のバーコードリーダーでキーイベント拾えるなあとやっていたら、そういえば、Androidには、USB HOSTのAPIあったよなあと思い出し、それなら、Felicaリーダー(RC-S370)を繋いでやってみようと！

コードは、kotlinで書いています。

## 環境
Android Studio 3.1.3
Android Things 1.0.1 for Raspberry Pi 3
SONY RC-S370　(　RC-S380じゃないので注意ね！　）

![IMG_9951.jpg](https://qiita-image-store.s3.amazonaws.com/0/33220/faf5530e-b8d9-92d7-fa78-2e001fb56c76.jpeg)

## コード
一応、USB Hostを指定しておいた。パーミッションは特に付けていない

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.eguchi.android.androidthingstest5">

    <uses-feature android:name="android.hardware.usb.host"/>

    <application>
        <uses-library android:name="com.google.android.things" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.IOT_LAUNCHER" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

```kotlin:MainActivity.kt
package jp.eguchi.android.androidthingstest5

// Android Things 1.0.1 で Raspberry Pi3 のUSBポートに、RC-S370を繋いで、FelicaのIDm読んでみよう！
// Progmramed by Kazuyuki Eguchi

import android.app.Activity
import android.content.Context
import android.hardware.usb.*
import android.os.Bundle
import android.os.Handler
import android.util.Log


class MainActivity : Activity() {
    private val TAG = "debug"

    private var manager : UsbManager? = null
    private var rcs370: UsbDevice? = null
    private var ep_in : UsbEndpoint? = null
    private var ep_out : UsbEndpoint? = null
    private var con: UsbDeviceConnection? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.d(TAG,"onCraete()")

        setContentView(R.layout.activity_main)

        manager = getSystemService(Context.USB_SERVICE) as UsbManager

        val devices = manager?.deviceList

        for(device in devices!!){
            val value = device.value
            // RC-S370
            if((value.productId == 0x2e1) && (value.vendorId == 0x54c)) {
                Log.d(TAG,"manufacturerName=" + value.manufacturerName)
                Log.d(TAG,"productName=" + value.productName)

                rcs370 = value;

                con = manager?.openDevice(rcs370)

                if(con == null)
                {
                    Log.d(TAG,"con is null");
                    return;
                }

                Log.d(TAG,"Interface count=" + rcs370?.interfaceCount)

                if(!con!!.claimInterface(rcs370?.getInterface(0),true)){
                    Log.d(TAG,"Error getInterface")
                    con?.close()
                    return
                }

                for(i in 0..rcs370?.getInterface(0)!!.endpointCount-1){
                    val ep = rcs370?.getInterface(0)!!.getEndpoint(i)
                    if(ep.direction == UsbConstants.USB_DIR_IN && ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK){
                        ep_in = ep;
                    }
                    if(ep.direction == UsbConstants.USB_DIR_OUT && ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK)
                    {
                        ep_out = ep;
                    }
                }

                // GetFirmwareVersion
                val cmd1 = byteArrayOf(0xd4.toByte(),0x02)
                rwCommand(cmd1,cmd1.size)

                // RFConfiguration RF OFF
                val cmd2 = byteArrayOf(0xd4.toByte(),0x32,0x1,0x0)
                rwCommand(cmd2,cmd2.size)

                // RFConfiguration Max Retry
                val cmd3 = byteArrayOf(0xd4.toByte(),0x32,0x5,0x0,0x0,0x0)
                rwCommand(cmd3,cmd3.size)

                // RFConfiguration Max Retry
                val cmd4 = byteArrayOf(0xd4.toByte(),0x32,0x81.toByte(),0xb7.toByte())
                rwCommand(cmd4,cmd4.size)

                read_felica()
            }
        }
    }

    private fun read_felica(){
        Log.d(TAG,"read_felica()")

        // InListPassiveTarget Felica
        val cmd5 = byteArrayOf(0xd4.toByte(),0x4a,0x1,0x1,0x0,0xff.toByte(),0xff.toByte(),0x1,0x0)
        rwCommand(cmd5,cmd5.size)

        // RFConfiguration RF OFF
        val cmd2 = byteArrayOf(0xd4.toByte(),0x32,0x1,0x0)
        rwCommand(cmd2,cmd2.size)

        Handler().postDelayed(Runnable {
            read_felica()
        },250)
    }

    private fun print_hex (mes: ByteArray, size:Int) : String {
        var res = ""
        for(i in 0..size-1){
            res = res + "%02x".format(mes.get(i))
        }
        return res;
    }

    private fun calcDCS(datas: ByteArray,size: Int) : Byte {
        var sum = 0
        for(i in 0..size-1){
            sum += datas.get(i)
        }
        return(((0x100)-(sum.and(0xff))).and(0xff).toByte())
    }

    private fun rwCommand(cmd: ByteArray,cmd_len: Int) : Boolean {
        var tmp = ByteArray(64)
        var buf = ByteArray(cmd_len + 7)
        val dcs = calcDCS(cmd,cmd_len)

        buf[0] = 0x0
        buf[1] = 0x0
        buf[2] = 0xff.toByte()

        buf[3] = cmd_len.and(0xff).toByte()
        buf[4] = (0x100 - buf[3]).and(0xff).toByte()

        var co = 5

        for(i in 0..cmd_len-1){
            buf[co] = cmd[i]
            co++
        }

        buf[co] = dcs;
        co++

        buf[co] = 0x0
        co++

        if(con?.bulkTransfer(ep_out,buf,buf.size,100) != buf.size){
            Log.d(TAG,"Error USB Send")
            return false
        }

        var nRet = con?.bulkTransfer(ep_in,tmp,tmp.size,100)

        if(nRet != 6){
            Log.d(TAG,"Error USB Recv")
            return false
        }

        if(tmp.get(0) != 0x0.toByte() || tmp.get(1) != 0x0.toByte() || tmp.get(2) != 0xff.toByte() || tmp.get(3) != 0x0.toByte() || tmp.get(4) != 0xff.toByte() || tmp.get(5) != 0x0.toByte()){
            Log.d(TAG,"Error Not ACK")
            return false
        }

        nRet = con?.bulkTransfer(ep_in,tmp,tmp.size,100)

        if(tmp.get(5) == 0xd5.toByte() && tmp.get(6) == 0x4b.toByte() && tmp.get(7)  == 0x1.toByte())
        {
            if(tmp.get(8) == 0x1.toByte()){
                var idm = ""
                for(i in 11..18){
                    idm += "%02x".format(tmp.get(i))
                }
                Log.d(TAG,"IDm=" + idm)
            }
        }

        return true
    }
}
```

## 実行結果
```txt:logcat
06-17 13:50:15.378 10069-10069/? I/zygote: Late-enabling -Xcheck:jni
06-17 13:50:16.047 10069-10069/jp.eguchi.android.androidthingstest5 I/InstantRun: starting instant run server: is main process
06-17 13:50:16.148 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: onCraete()
06-17 13:50:16.551 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: manufacturerName=Sony
06-17 13:50:16.552 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: productName=RC-S370/P
06-17 13:50:16.555 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: Interface count=1
06-17 13:50:16.574 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:16.871 10069-10069/jp.eguchi.android.androidthingstest5 D/vndksupport: Loading /vendor/lib/hw/android.hardware.graphics.mapper@2.0-impl.so from current namespace instead of sphal namespace.
06-17 13:50:16.948 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:17.237 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:19.256 10069-10069/jp.eguchi.android.androidthingstest5 I/chatty: uid=10033(jp.eguchi.android.androidthingstest5) identical 7 lines
06-17 13:50:19.545 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:19.593 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: IDm=01105000e10ddc04
06-17 13:50:19.849 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:19.897 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: IDm=01105000e10ddc04
06-17 13:50:20.152 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:20.201 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: IDm=01105000e10ddc04
06-17 13:50:20.457 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:20.745 10069-10069/jp.eguchi.android.androidthingstest5 I/chatty: uid=10033(jp.eguchi.android.androidthingstest5) identical 1 line
06-17 13:50:21.034 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
06-17 13:50:21.323 10069-10069/jp.eguchi.android.androidthingstest5 D/debug: read_felica()
```