Created
June 17, 2022 10:33
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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