android: lifetime query + cali consistency check + real-device checklist

This commit is contained in:
ZXCLI
2026-09-10 23:34:10 +08:00
parent 087e15cb19
commit 06c1f30b13
5 changed files with 87 additions and 4 deletions
@@ -110,6 +110,11 @@ class IrSession(context: Context) {
var lastInfo1: ByteArray? = null
private set
/** Device power-on lifetime in ms (official getDevLifeTime); -1 = unknown. */
@Volatile
var deviceLifetimeMs: Long = -1
private set
fun identitySnapshot(): CameraIdentity = identity.copy()
/** Connect + start the live stream. Must be called after USB permission. */
@@ -207,6 +212,24 @@ class IrSession(context: Context) {
}
DebugLog.log("session", "cali info: size=$caliSize date=$caliDate")
// 3b) GetLifeTime (official getDevLifeTime): 675 -> 0x5BB5B561 {magic, ms}.
// Non-blocking: the official app just logs when this fails.
writeCmd(conn, epOut, MagProtocol.cmd4(MagProtocol.CMD_GET_LIFETIME), "GetLifeTime")
readResp(conn, epResp, "GetLifeTime")?.let { r ->
if (r.second == MagProtocol.RSP_SEND_LIFETIME) {
// protocol doc: payload {magic, i32 ms}; official Java reads the
// bare {i32 ms} right after the response magic — accept both.
val ms = when {
r.third.size >= 8 && MagProtocol.u32(r.third, 0) == MagProtocol.RSP_SEND_LIFETIME ->
MagProtocol.u32(r.third, 4)
r.third.size >= 4 -> MagProtocol.u32(r.third, 0)
else -> null
}
if (ms != null) deviceLifetimeMs = ms.toLong() and 0xFFFFFFFFL
}
}
DebugLog.log("session", "device lifetime=${deviceLifetimeMs}ms")
// 4) calibration file: cache hit or fetch from EP 0x84
val ddt = obtainCali(conn, ep84, epOut, epResp, caliSize, caliDate, bundledDdt)
DebugLog.log("session", "ddt bytes ${ddt.size}")
@@ -294,7 +317,14 @@ class IrSession(context: Context) {
if (cache.isFile && cache.length() == size) {
DebugLog.log("session", "cali cache hit: ${cache.name}")
return try {
cache.readBytes()
val cached = cache.readBytes()
// consistency check only: the cache stays authoritative
DebugLog.log(
"session",
"cali cache vs bundled DDT: " +
if (md5(cached).contentEquals(md5(bundled))) "identical" else "differ",
)
cached
} catch (e: Exception) {
DebugLog.log("session", "cali cache read failed -> bundled: $e")
bundled
@@ -349,6 +379,10 @@ class IrSession(context: Context) {
return out
}
/** MD5 digest of a byte array (cali consistency check only). */
private fun md5(bytes: ByteArray): ByteArray =
java.security.MessageDigest.getInstance("MD5").digest(bytes)
private fun notify(state: State, message: String?) {
DebugLog.log("session", "state -> $state msg=$message")
listener?.onStateChanged(state, message)
@@ -516,7 +550,8 @@ class IrSession(context: Context) {
"stream",
"stats: reads=$readCount frames=$frameCount rendered=$renderCount " +
"timeouts=$timeouts fps=%.1f ".format(Locale.US, fps) +
"renderState=${pipe.frameIndex()} ref=${pipe.hasReference()}",
"renderState=${pipe.frameIndex()} ref=${pipe.hasReference()}" +
" lifetime=${deviceLifetimeMs}",
)
lastLog = now
}
@@ -31,6 +31,9 @@ object MagProtocol {
const val RSP_SEND_CALI_FILE = 0x5BB5B55D
const val RSP_SEND_CALI_INFO = 0x5BB5B55E
/** D2P_SendLifeTime (1538635105) — reply to GetLifeTime: {u32 magic, i32 ms}. */
const val RSP_SEND_LIFETIME = 0x5BB5B561
/**
* 4-byte LITTLE-ENDIAN command — byte-identical to the official app's
* GlobalFunc.intToByteArray (a&255 first). CRITICAL: ByteBuffer.putInt