android: lifetime query + cali consistency check + real-device checklist
This commit is contained in:
@@ -110,6 +110,11 @@ class IrSession(context: Context) {
|
|||||||
var lastInfo1: ByteArray? = null
|
var lastInfo1: ByteArray? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/** Device power-on lifetime in ms (official getDevLifeTime); -1 = unknown. */
|
||||||
|
@Volatile
|
||||||
|
var deviceLifetimeMs: Long = -1
|
||||||
|
private set
|
||||||
|
|
||||||
fun identitySnapshot(): CameraIdentity = identity.copy()
|
fun identitySnapshot(): CameraIdentity = identity.copy()
|
||||||
|
|
||||||
/** Connect + start the live stream. Must be called after USB permission. */
|
/** 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")
|
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
|
// 4) calibration file: cache hit or fetch from EP 0x84
|
||||||
val ddt = obtainCali(conn, ep84, epOut, epResp, caliSize, caliDate, bundledDdt)
|
val ddt = obtainCali(conn, ep84, epOut, epResp, caliSize, caliDate, bundledDdt)
|
||||||
DebugLog.log("session", "ddt bytes ${ddt.size}")
|
DebugLog.log("session", "ddt bytes ${ddt.size}")
|
||||||
@@ -294,7 +317,14 @@ class IrSession(context: Context) {
|
|||||||
if (cache.isFile && cache.length() == size) {
|
if (cache.isFile && cache.length() == size) {
|
||||||
DebugLog.log("session", "cali cache hit: ${cache.name}")
|
DebugLog.log("session", "cali cache hit: ${cache.name}")
|
||||||
return try {
|
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) {
|
} catch (e: Exception) {
|
||||||
DebugLog.log("session", "cali cache read failed -> bundled: $e")
|
DebugLog.log("session", "cali cache read failed -> bundled: $e")
|
||||||
bundled
|
bundled
|
||||||
@@ -349,6 +379,10 @@ class IrSession(context: Context) {
|
|||||||
return out
|
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?) {
|
private fun notify(state: State, message: String?) {
|
||||||
DebugLog.log("session", "state -> $state msg=$message")
|
DebugLog.log("session", "state -> $state msg=$message")
|
||||||
listener?.onStateChanged(state, message)
|
listener?.onStateChanged(state, message)
|
||||||
@@ -516,7 +550,8 @@ class IrSession(context: Context) {
|
|||||||
"stream",
|
"stream",
|
||||||
"stats: reads=$readCount frames=$frameCount rendered=$renderCount " +
|
"stats: reads=$readCount frames=$frameCount rendered=$renderCount " +
|
||||||
"timeouts=$timeouts fps=%.1f ".format(Locale.US, fps) +
|
"timeouts=$timeouts fps=%.1f ".format(Locale.US, fps) +
|
||||||
"renderState=${pipe.frameIndex()} ref=${pipe.hasReference()}",
|
"renderState=${pipe.frameIndex()} ref=${pipe.hasReference()}" +
|
||||||
|
" lifetime=${deviceLifetimeMs}",
|
||||||
)
|
)
|
||||||
lastLog = now
|
lastLog = now
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ object MagProtocol {
|
|||||||
const val RSP_SEND_CALI_FILE = 0x5BB5B55D
|
const val RSP_SEND_CALI_FILE = 0x5BB5B55D
|
||||||
const val RSP_SEND_CALI_INFO = 0x5BB5B55E
|
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
|
* 4-byte LITTLE-ENDIAN command — byte-identical to the official app's
|
||||||
* GlobalFunc.intToByteArray (a&255 first). CRITICAL: ByteBuffer.putInt
|
* GlobalFunc.intToByteArray (a&255 first). CRITICAL: ByteBuffer.putInt
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,38 @@
|
|||||||
|
# MAG160C 真机端到端自检清单(Phase A3)
|
||||||
|
|
||||||
|
> 用法:把最新 `build-artifacts/mag160c-app-debug.apk` 装到手机,插上 MAG160C
|
||||||
|
> 热像仪,按顺序做一遍。每步的"预期 DebugLog 行"可在应用内 debug 日志文件中
|
||||||
|
> 对照(DCIM/MAG160C/debug_*.log;若 DCIM 拒收则 Download/MAG160C;文件头
|
||||||
|
> `sink=` 注明实际落点),也可用 `adb logcat -s MAG160C/*` 实时看。
|
||||||
|
>
|
||||||
|
> 日志行格式:`HH:mm:ss.SSS [tag] message`。下表消息列为**现有代码中的原文**
|
||||||
|
> (变量处用 <...> 占位)。若某项对不上,把整个日志文件发回分析。
|
||||||
|
|
||||||
|
## 步骤表
|
||||||
|
|
||||||
|
| # | 操作 | 预期 DebugLog 行(原文) | 通过标准 |
|
||||||
|
|---|------|--------------------------|----------|
|
||||||
|
| 1 | 插入热像仪,系统弹出 USB 权限对话框,点"允许" | `[usb] requestPermission: no device`(无设备时)/ `[usb] requesting permission (dialog)` → `[usb] permission result: true` | 授予权限后自动开始连接 |
|
||||||
|
| 2 | 等待连接(首次约 1~3 s,首次装机需下载标定文件时更久) | `[vm] connect()` → `[vm] permission ok, ddt <N> bytes, starting session` → `[session] state -> LINKING msg=null` → `[cmd] GetParameter1 write=4/4` → `[cmd] GetParameter1 resp=0x5BB5B55B len=60 head=...` → `[cmd] BasePara1: serial=160043865 devType=3 160x120 @15fps` | 五条全出现;serial/devType 与实际相机一致(devType=3 即 core160) |
|
||||||
|
| 3 | 同上(参数/标定握手) | `[cmd] GetParameter2 resp=0x5BB5B55C len=...` → `[cmd] GetCaliInfo resp=0x5BB5B55E len=...` → `[session] cali info: size=<size> date=<date>` → `[session] device lifetime=<ms>ms` | GetParameter2/GetCaliInfo 有响应;lifetime 非 -1(0 或正数均可,首次上电后很小正常) |
|
||||||
|
| 4 | 首次安装运行(无 cali 缓存) | `[session] first run on this host: downloading cali file <size> B (EP 0x84)` → `[session] cali download <p>%`(若干行)→ `[session] cali downloaded+cached: <product>.<serial>.<date> (<size> B)`;第二次运行改为 `[session] cali cache hit: <name>` + `[session] cali cache vs bundled DDT: identical`(或 `differ`,两者都不算失败) | 缓存命中或下载成功二者之一 |
|
||||||
|
| 5 | 等待 DDT 加载 + 出流 | `[session] ddt bytes <N>` → `[session] state -> STREAMING msg=null` → `[cmd] StartTransferImg write=4/4` → `[cmd] StartTransferImg ack=0x...` → `[stream] reader loop start: sync bulkTransfer (800 ms) + halt recovery` → `[stream] first reads [0] n=<n> head=...` → `[vm] first rendered frame -> UI` | 出现 `first rendered frame -> UI` 即画面已出(实时页显示热像,不再黑屏) |
|
||||||
|
| 6 | 观察实时页 5 s 以上 | 每 2 s:`[stream] stats: reads=<n> frames=<n> rendered=<n> timeouts=<n> fps=15.0 renderState=<n> ref=true lifetime=<ms>`;每 ~5 s:`[vm] hb: state=streaming streaming=true uiFrames=<n> latest=true` | `frames`/`rendered` 持续递增,fps≈15,ref=true;timeouts 偶发可接受,不应持续增长而 frames 不涨 |
|
||||||
|
| 7 | 顶栏点 FFC 两次(每次间隔 ~1 s) | 每次:`[cmd] FFC(0) write=8/8` → `[cmd] FFC(0) ack=0x... len=...`(ack 读不到时日志为 `[cmd] FFC(0) ack=<n> bytes`,相机仍会执行校正,不算失败) | 画面出现一次快门校正(短暂黑/停顿后恢复) |
|
||||||
|
| 8 | 点底部大圆快门拍照 | `[vm] hb: state=saved ...`(下一次 5 s 心跳即可看到 status=saved);UI 上状态文案短暂显示 | 相册/文件管理器 DCIM/MAG160C 出现 `MAG160C_yyyyMMdd_HHmmss.jpg`(实际为 MDT 容器,扩展名 .jpg) |
|
||||||
|
| 9 | 点录像,录 10 s,再点停止 | `[vm] hb: state=recording ...` → 停止后 `[vm] hb: state=rec_done ...` | 状态回到 rec_done,无崩溃 |
|
||||||
|
| 10 | 切到"相册"页 | (无 DebugLog;纯 UI) | 列表出现步骤 8 的照片缩略图(MDT 尾部校验通过才显示) |
|
||||||
|
| 11 | 点该照片进入分析页 | (无 DebugLog;纯 UI) | 图片可缩放/平移;调色板重渲染可用;备注可编辑保存 |
|
||||||
|
| 12 | 分析页生成 PDF 报告 | (无 DebugLog;纯 UI) | 报告文件在 DCIM/MAG160C 或 Download/MAG160C 生成,可打开 |
|
||||||
|
|
||||||
|
## 失败时的快速定位(沿用第 11 轮起的诊断路径)
|
||||||
|
|
||||||
|
- `reads=0` 且 `timeouts` 持续上涨 → 相机没出流:查第 5 步 START 前后的 `[cmd]`
|
||||||
|
行与 `[usb] ep 0x81 fail#... halted=...`(halt 会自动清除并重试)。
|
||||||
|
- `frames>0 rendered=0` → 管线在暖机/FFC/参考窗口:看 `ref=false` 是否长期持续
|
||||||
|
(持续 false → BasePara2/66c 响应异常)。
|
||||||
|
- `GetParameter1 resp=` 超时 → `[session] parameter1 handshake failed -> abort`,
|
||||||
|
屏显"相机无应答,请拔插热像仪重试"。
|
||||||
|
- 相机中途重新枚举(`[vm] usb detached vid=0x833C` + 再次弹权限)→ 多为
|
||||||
|
OTG 供电/线材问题,换短线或带供电 OTG 再试(第 13 轮结论)。
|
||||||
|
- 任何闪退:日志文件开头 `[crash]` 行有完整堆栈(DebugLog 崩溃钩子)。
|
||||||
@@ -379,12 +379,19 @@
|
|||||||
## 待办
|
## 待办
|
||||||
|
|
||||||
- 真机USB实测(温度绝对值标定、FFC/录像/MDT保存端到端)——进行中:
|
- 真机USB实测(温度绝对值标定、FFC/录像/MDT保存端到端)——进行中:
|
||||||
第13轮加固 APK 待用户复测;若仍无流,按上方供电排查三步走
|
自检清单位于 docs/android_app/real_device_checklist.md(Phase A3 产出)
|
||||||
- 网络互连远程预览(用户需求:UDP自动发现+手动IP)
|
- 网络互连远程预览(用户需求:UDP自动发现+手动IP)
|
||||||
- 离线MDT温度解码(ConvertResponse2Temperature+标定参数,待真机文件对照)
|
- 离线MDT温度解码(ConvertResponse2Temperature+标定参数,待真机文件对照)
|
||||||
- 厂商12调色板精确提取(需运行时抓取)
|
- 厂商12调色板精确提取(需运行时抓取)
|
||||||
- 可见光PIP融合、云模块(预留结构)
|
- 可见光PIP融合、云模块(预留结构)
|
||||||
|
|
||||||
|
## 执行计划进度(2026-09-10,docs/android_app/execution_plan.md)
|
||||||
|
|
||||||
|
- [x] Phase A(2026-09-10):GetLifeTime(675→0x5BB5B55B61) 查询 + deviceLifetimeMs;
|
||||||
|
cali 缓存与内置 DDT MD5 一致性日志;新增 real_device_checklist.md;
|
||||||
|
gradlew test 全绿,APK 已更新。commit: "android: lifetime query + cali consistency check + real-device checklist"
|
||||||
|
|
||||||
|
|
||||||
## 里程碑日志
|
## 里程碑日志
|
||||||
|
|
||||||
- 阶段3b 完成(2026-09-06 晚): 拍照 MDT 容器(jpg+info块+raw帧+备注)存入
|
- 阶段3b 完成(2026-09-06 晚): 拍照 MDT 容器(jpg+info块+raw帧+备注)存入
|
||||||
|
|||||||
Reference in New Issue
Block a user