Skip to content

Instantly share code, notes, and snippets.

@kikuchy
Created June 17, 2022 10:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kikuchy/473a99a63a533ca32b05ad67b27c17e2 to your computer and use it in GitHub Desktop.
Save kikuchy/473a99a63a533ca32b05ad67b27c17e2 to your computer and use it in GitHub Desktop.
雑にAndroidでNFC-F (FeliCa)読み取りをしたときのコード 参考 -> https://www.kenichi-odo.com/articles/2020_10_08_read-suica-by-android
package com.example.nfcftest
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.NfcF
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.nfcftest.ui.theme.NfcFTestTheme
fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
class MainActivity : ComponentActivity(), NfcAdapter.ReaderCallback {
private val nfcAdapter by lazy { NfcAdapter.getDefaultAdapter(this) }
private fun startScanning() {
nfcAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_F, null)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NfcFTestTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Column(verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Greeting("Android")
Button(onClick = { startScanning() }) {
Text(text = "Scan")
}
}
}
}
}
}
override fun onTagDiscovered(tag: Tag) {
Log.d("HOGE", "Tagみつかった")
val idm = tag.id
Log.d("HOGE", idm.toHex())
try {
val card = NfcF.get(tag)
if (card != null) {
card.connect()
val polRes = card.transceive(byteArrayOf(
0x06,
0x00,
0x00,
0x03,
0x01,
0x0f
))
val polIdm = getIDm(polRes)
val blocks = (0 until 10).map {
createBlockListElement(it)
}.toTypedArray()
Log.d("HOGE", blocks.map { it.toByteArray().toHex() }.toString())
val p = arrayListOf<Byte>()
p += (14 + (blocks.size * 3)).toByte() // パケットサイズ
p += 0x06.toByte() // コマンドコード(固定値)
p.addAll(polIdm)
p += 0x01.toByte() // サービス数(履歴取得しかしないので1つ)
p.addAll(elements = ServiceCode.history.value.reversed()) // サービスコードリスト(リトルエンディアン)
p += blocks.size.toByte() // ブロック数
p.addAll(blocks.flatten().reversed())
Log.d("HOGE", p.toByteArray().toHex())
val rweRes = card.transceive(p.toByteArray())
if (!isSuccessful(rweRes)) {
throw Exception(rweRes.joinToString("") { "%02x".format(it) })
}
Log.d("HOGE", "履歴読めた")
Log.d("HOGE", rweRes.toHex())
Log.d("HOGE", getData(rweRes).map { it.toByteArray().toHex() }.toString())
card.close()
}
} catch (e: Exception) {
Log.e("HOGE", "NFCタグ読み取り中に死んだ", e)
} finally {
nfcAdapter.disableReaderMode(this)
}
}
override fun onPause() {
nfcAdapter.disableReaderMode(this)
super.onPause()
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
NfcFTestTheme {
Greeting("Android")
}
}
enum class BlockListSize(val value: Int) {
three_byte(0),
two_byte(1),
}
enum class ServiceCode(val value: ByteArray) {
attributes(byteArrayOf(0x00.toByte(), 0x8B.toByte())),
history(byteArrayOf(0x09.toByte(), 0x0F.toByte())),
}
fun createBlockListElement(block_number: Int) = arrayOf((
(BlockListSize.two_byte.value shl 7) and // b7(長さ)
(BlockListAccessMode.unuse_parse_service.value shl 4) and // b6-b4(アクセスモード)
0 // b3-b0(サービスコードリスト順番)
).toByte(), // D0
block_number.toByte(), // D1
0.toByte() // D2
)
enum class BlockListAccessMode(val value: Int) {
unuse_parse_service(0),
use_parse_service(1),
}
fun isSuccessful(response: ByteArray): Boolean {
val status_flag_1 = response[10]
val status_flag_2 = response[11]
return status_flag_1.toInt() == 0x00 && status_flag_2.toInt() == 0x00
}
fun getIDm(response: ByteArray) = response.copyOfRange(fromIndex = 2, toIndex = 10).toTypedArray()
fun getData(response: ByteArray) = response.slice(13 until response.size).chunked(16)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment