2025-11-02 5:46 AM
Additional Details: I am attaching the files where i have the impletation code.
Please first refer this post where I sent all the details about this:
https://community.st.com/t5/st25-nfc-rfid-tags-and-readers/how-to-enable-the-mailbox-and-perform-write-read-on-st25dv-via/td-p/852648
import android.nfc.Tag
import android.nfc.tech.NfcV
import java.io.IOException
import kotlin.experimental.or
class NfcMailboxManager(private val logCallback: (String) -> Unit) {
data class OperationResult(val success: Boolean, val summary: String, val detail: String = "")
data class MailboxStatus(val summary: String, val mbCtrl: Int, val mbLen: Int)
data class ReadResult(val payload: ByteArray?, val summary: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ReadResult
if (!payload.contentEquals(other.payload)) return false
if (summary != other.summary) return false
return true
}
override fun hashCode(): Int {
var result = payload?.contentHashCode() ?: 0
result = 31 * result + summary.hashCode()
return result
}
}
private fun log(s: String) = logCallback("[NfcMailboxManager] $s")
// ST opcodes (datasheet)
companion object {
const val FLAGS_DEFAULT: Byte = 0x02
const val OPC_READ_CONFIG: Byte = 0xA0.toByte()
const val OPC_WRITE_CONFIG: Byte = 0xA1.toByte()
const val OPC_WRITE_MSG: Byte = 0xAA.toByte()
const val OPC_READ_MSG_LEN: Byte = 0xAB.toByte()
const val OPC_READ_MSG: Byte = 0xAC.toByte()
const val OPC_READ_DYN: Byte = 0xAD.toByte()
const val OPC_WRITE_DYN: Byte = 0xAE.toByte()
const val OPC_PRESENT_PWD: Byte = 0xB3.toByte()
// Pointers / register ptrs (RF view)
const val PTR_STATIC_FTM = 0x0D
const val PTR_MB_CTRL_DYN = 0x0D
private const val FTM_MB_MODE_MASK = 0x01 // bit1
}
// ------- High level ops -------
fun enableMailbox(
tag: Tag,
presentPwdIfNeeded: Boolean = true,
pwdNumber: Int = 0, // must be 0 for configuration
pwd: ByteArray? = null
): OperationResult {
val nfcv = NfcV.get(tag) ?: return OperationResult(false, "NfcV not supported on tag")
try {
nfcv.connect()
log("Connected for enableMailbox")
// 1) Read static FTM (0x0D) and ensure MB_MODE=1 or MB_MODE must be bit1 (0x02)
var ftm = tryReadConfiguration(nfcv, PTR_STATIC_FTM, tag.id)
log("FTM static reg (0x0D) = 0x${ftm.toString(16)}")
if ((ftm and FTM_MB_MODE_MASK) == 0) {
if (presentPwdIfNeeded && pwd != null) {
// Always use CONFIG password #0 for FTM writes
val okPwd = presentPasswordWithOpenSession(
nfcv,
tag.id,
0,
pwd
)// CONFIG present password #0
log("PresentPassword(CONFIG#0) result: $okPwd")
if (!okPwd) return OperationResult(false, "PresentPassword failed (CONFIG#0)")
}
val newFtm = (ftm or FTM_MB_MODE_MASK).toByte()
if (!writeConfiguration(nfcv, PTR_STATIC_FTM, newFtm, tag.id))
return OperationResult(false, "Failed to set MB_MODE (Write Configuration)")
// Re-read to confirm
ftm = tryReadConfiguration(nfcv, PTR_STATIC_FTM, tag.id)
if ((ftm and FTM_MB_MODE_MASK) == 0) return OperationResult(
false,
"MB_MODE still 0 after write (check password/VCC)"
)
log("MB_MODE set to 1 in static FTM")
} else {
log("MB_MODE already 1 (FTM=0x${ftm.toString(16)})")
}
// 2) Set MB_EN in MB_CTRL_Dyn (0x0D)
var mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
val newMbCtrl = (mbCtrl or 0x01).toByte()
if (!writeDynamic(nfcv, PTR_MB_CTRL_DYN, newMbCtrl, tag.id))
return OperationResult(false, "Failed to set MB_EN (Write Dynamic)")
// Re-read to confirm
mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
val mbLen = tryReadMessageLength(nfcv, tag.id)
if ((mbCtrl and 0x01) == 0) {
// force an addressed write once
val req = ByteArray(1 + 1 + tag.id.size + 2)
req[0] = (FLAGS_DEFAULT or 0x20) // addressed
req[1] = OPC_WRITE_DYN
System.arraycopy(tag.id, 0, req, 2, tag.id.size)
req[2 + tag.id.size] = PTR_MB_CTRL_DYN.toByte()
req[3 + tag.id.size] = newMbCtrl
val r = tryTransceive(nfcv, req)
if (r == null) return OperationResult(false, "MB_EN write (addressed retry) failed")
mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
}
val ok = (ftm and FTM_MB_MODE_MASK) != 0 && (mbCtrl and 0x01) != 0
//val sum = "FTM(MB_MODE)=${(ftm and 0x01)!=0}, MB_CTRL=0x${mbCtrl.toString(16)}, MB_LEN=$mbLen"
return OperationResult(
ok,
if (ok) "Mailbox enabled. FTM=0x${ftm.toString(16)} MB_CTRL=0x${mbCtrl.toString(16)}" else "Enable incomplete. FTM=0x${
ftm.toString(16)
} MB_CTRL=0x${mbCtrl.toString(16)}"
)
} catch (ex: Exception) {
return OperationResult(false, "Exception in enableMailbox", ex.message ?: "")
} finally {
try {
nfcv.close()
} catch (_: Exception) {
}
}
}
fun disableMailbox(tag: Tag): OperationResult {
val nfcv = NfcV.get(tag) ?: return OperationResult(false, "NfcV not supported")
try {
nfcv.connect()
val ok = writeDynamic(nfcv, PTR_MB_CTRL_DYN, 0x00, tag.id)
return OperationResult(ok, if (ok) "MB_EN cleared" else "Failed to clear MB_EN")
} catch (e: Exception) {
return OperationResult(false, "Exception", e.message ?: "")
} finally {
try {
nfcv.close()
} catch (_: Exception) {
}
}
}
fun readMailboxStatus(tag: Tag): MailboxStatus {
val nfcv = NfcV.get(tag) ?: throw IOException("NfcV not supported")
try {
nfcv.connect()
val mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
val mbLen = tryReadMessageLength(nfcv, tag.id)
return MailboxStatus("MB_CTRL=0x${mbCtrl.toString(16)} MB_LEN=$mbLen", mbCtrl, mbLen)
} finally {
try {
nfcv.close()
} catch (_: Exception) {
}
}
}
fun writeMessage(tag: Tag, payload: ByteArray): OperationResult {
val nfcv = NfcV.get(tag) ?: return OperationResult(false, "NfcV not supported")
try {
nfcv.connect()
val mbCtrl = tryReadDynamic(nfcv, PTR_MB_CTRL_DYN, tag.id)
if ((mbCtrl and 0x01) == 0) return OperationResult(false, "Mailbox disabled (MB_EN=0)")
// ensure mailbox free (no HOST_PUT_MSG)
if ((mbCtrl and 0x02) != 0) return OperationResult(
false,
"Mailbox not free: HOST_PUT_MSG=1"
)
val len = payload.size
if (len < 1 || len > 256) return OperationResult(false, "Invalid payload size")
val request = ByteArray(1 + 1 + 1 + len)
request[0] = FLAGS_DEFAULT
request[1] = OPC_WRITE_MSG
request[2] = (len - 1).toByte()
System.arraycopy(payload, 0, request, 3, len)
val resp = transceiveWithUidFallback(nfcv, request, tag.id)
return OperationResult(resp != null, if (resp != null) "Write OK" else "Write failed")
} catch (e: Exception) {
return OperationResult(false, "Exception", e.message ?: "")
} finally {
try {
nfcv.close()
} catch (_: Exception) {
}
}
}
fun readMessage(tag: Tag): ReadResult {
val nfcv = NfcV.get(tag) ?: return ReadResult(null, "NfcV not supported")
try {
nfcv.connect()
val mbLen = tryReadMessageLength(nfcv, tag.id)
if (mbLen == 0) return ReadResult(ByteArray(0), "Mailbox empty")
val request = byteArrayOf(FLAGS_DEFAULT, OPC_READ_MSG, 0x00, 0x00)
val resp = transceiveWithUidFallback(nfcv, request, tag.id) ?: return ReadResult(
null,
"No response"
)
return ReadResult(resp, "OK")
} catch (e: Exception) {
return ReadResult(null, "Exception ${e.message}")
} finally {
try {
nfcv.close()
} catch (_: Exception) {
}
}
}
fun resetMailbox(tag: Tag): OperationResult {
return disableMailbox(tag)
}
// ------- Low-level helpers -------
private fun tryTransceive(nfcv: NfcV, data: ByteArray): ByteArray? {
return try {
log("REQ: ${toHex(data)}")
val r = nfcv.transceive(data)
log("RSP: ${r?.let { toHex(it) } ?: "null"}")
r
} catch (e: Exception) {
log("transceive failed: ${e.message}")
null
}
}
private fun transceiveWithUidFallback(nfcv: NfcV, baseRequest: ByteArray, uid: ByteArray?): ByteArray? {
var resp = tryTransceive(nfcv, baseRequest)
if (resp != null) return resp
if (uid == null || uid.isEmpty()) return null
val flags = baseRequest[0]
val opcode = baseRequest[1]
val rest = if (baseRequest.size > 2) baseRequest.copyOfRange(2, baseRequest.size) else ByteArray(0)
// A) opcode + UID (same flags)
run {
val req = ByteArray(1 + 1 + uid.size + rest.size)
req[0] = flags
req[1] = opcode
System.arraycopy(uid, 0, req, 2, uid.size)
if (rest.isNotEmpty()) System.arraycopy(rest, 0, req, 2 + uid.size, rest.size)
resp = tryTransceive(nfcv, req); if (resp != null) return resp
}
// B) UID + opcode (same flags)
run {
val req = ByteArray(1 + uid.size + (baseRequest.size - 1))
req[0] = flags
System.arraycopy(uid, 0, req, 1, uid.size)
System.arraycopy(baseRequest, 1, req, 1 + uid.size, baseRequest.size - 1)
resp = tryTransceive(nfcv, req); if (resp != null) return resp
}
// C) opcode + UID with **addressed** flag (0x20)
run {
val req = ByteArray(1 + 1 + uid.size + rest.size)
req[0] = (flags or 0x20) // 0x22 typical
req[1] = opcode
System.arraycopy(uid, 0, req, 2, uid.size)
if (rest.isNotEmpty()) System.arraycopy(rest, 0, req, 2 + uid.size, rest.size)
resp = tryTransceive(nfcv, req); if (resp != null) return resp
}
// D) UID + opcode with **addressed** flag
run {
val req = ByteArray(1 + uid.size + (baseRequest.size - 1))
req[0] = (flags or 0x20)
System.arraycopy(uid, 0, req, 1, uid.size)
System.arraycopy(baseRequest, 1, req, 1 + uid.size, baseRequest.size - 1)
resp = tryTransceive(nfcv, req); if (resp != null) return resp
}
return null
}
private fun tryReadConfiguration(nfcv: NfcV, addr: Int, uid: ByteArray?): Int {
val req = byteArrayOf(FLAGS_DEFAULT, OPC_READ_CONFIG, addr.toByte())
val r = transceiveWithUidFallback(nfcv, req, uid)
?: throw IOException("No response to ReadConfiguration")
if (r.size < 2) throw IOException("Bad ReadConfiguration response: ${toHex(r)}")
// r[0]=flags, r[1]=data
return r[1].toInt() and 0xFF
}
private fun writeConfiguration(nfcv: NfcV, addr: Int, value: Byte, uid: ByteArray?): Boolean {
val req = byteArrayOf(FLAGS_DEFAULT, OPC_WRITE_CONFIG, addr.toByte(), value)
val r = transceiveWithUidFallback(nfcv, req, uid)
return r != null
}
private fun tryReadDynamic(nfcv: NfcV, ptr: Int, uid: ByteArray?): Int {
val req = byteArrayOf(FLAGS_DEFAULT, OPC_READ_DYN, ptr.toByte())
val r = transceiveWithUidFallback(nfcv, req, uid)
?: throw IOException("No response to ReadDynamic")
if (r.size < 2) throw IOException("Bad ReadDynamic response: ${toHex(r)}")
return r[1].toInt() and 0xFF
}
private fun writeDynamic(nfcv: NfcV, ptr: Int, value: Byte, uid: ByteArray?): Boolean {
val req = byteArrayOf(FLAGS_DEFAULT, OPC_WRITE_DYN, ptr.toByte(), value)
val r = transceiveWithUidFallback(nfcv, req, uid)
return r != null
}
private fun tryReadMessageLength(nfcv: NfcV, uid: ByteArray?): Int {
val req = byteArrayOf(FLAGS_DEFAULT, OPC_READ_MSG_LEN)
val r = transceiveWithUidFallback(nfcv, req, uid) ?: return 0
if (r.size < 2) return 0
return r[1].toInt() and 0xFF
}
/**
* Present RF password (B3h).
* pwdNumber: 0..3
* pwd: 8-byte password
*/
fun presentPassword(tag: Tag, pwdNumber: Int, pwd: ByteArray): Boolean {
val nfcv = NfcV.get(tag) ?: return false
try {
nfcv.connect()
if (pwd.size != 8) {
log("presentPassword: password length must be 8")
return false
}
val base = ByteArray(1 + 1 + 1 + 8)
base[0] = FLAGS_DEFAULT
base[1] = OPC_PRESENT_PWD
base[2] = pwdNumber.toByte()
System.arraycopy(pwd, 0, base, 3, 8)
val resp = transceiveWithUidFallback(nfcv, base, tag.id)
return resp != null
} catch (e: Exception) {
log("presentPassword exception: ${e.message}")
return false
} finally {
try {
nfcv.close()
} catch (_: Exception) {
}
}
}
private fun toHex(b: ByteArray) = b.joinToString(" ") { "%02X".format(it) }
private fun presentPasswordWithOpenSession(
nfcv: NfcV,
uid: ByteArray?,
pwdNumber: Int,
pwd: ByteArray
): Boolean {
if (pwd.size != 8) {
log("presentPassword: password length must be 8")
return false
}
val base = ByteArray(1 + 1 + 1 + 8)
base[0] = FLAGS_DEFAULT
base[1] = OPC_PRESENT_PWD
base[2] = pwdNumber.toByte() // must be 0 for config
System.arraycopy(pwd, 0, base, 3, 8)
val resp = transceiveWithUidFallback(nfcv, base, uid)
return resp != null
}
}