51 KiB
51 KiB
MAG160C 安卓统一 APP — 会话状态与心跳锚点
本文件是本任务("整合 4 个官方 APP 重写为 Android 16 现代化 APP")的心跳锚点。 每完成一个里程碑更新勾选;会话中断先读本文件 +
reverse_apk_features.md, 再按"下一步"继续。约定:不弹询问窗,按计划自主推进,重大分歧点才停下来问。
任务目标
- 逆向 C:\Project\MAG160C\app 4 个官方 APP(普通版/专业版/ThermoScope/demo)→ 已完成
- 整合全部功能开发一款安卓 APP:适配 Android 16(API 36)、现代 UI、横竖屏、 无流氓文件夹、干爽简洁 → 进行中
- 背景问题:专业版在用户 Android 16 手机上花屏(根因见 reverse_apk_features.md §6)
已完成的里程碑
- 工具链:jadx 1.5.1(C:\Tools\jadx)、Ghidra 12.1.2(C:\Tools)、IDA MCP 可用
- 4 APK 解包 + jadx 全量反编译(%TEMP%\opencode\apkwork\jadx_*)
- 普通版/专业版/ThermoScope/demo 功能清单 → docs/android_app/reverse_apk_features.md
- IDA:pro libcoresdk.so GetOutputImage/copyBitmap 位图格式证实(ARGB_8888)
- Ghidra:libcxsdk.so 导出 C++ API 清单(CFunctions 族,与 ARM64 coresdk 同源)
- 花屏根因分析(32 位专属库 + targetSdk25 legacy 渲染栈)
关键事实速查(开发时直接用)
- USB:VID 0x833C PID 1,EP 0x03/0x82/0x81/0x84,帧 28B 头+38400B+尾,15fps
- 协议/渲染/温度算法全部已逆向:analysis/protocol_spec.md + csdk/(逐像素验证过的官方管线)
- csdk 的 mag160c_render 无平台依赖:USB 帧 → 320×240 RGB24 + 探针测温(毫度)
- DDT 标定文件:可从设备经 0x6BB6B66D/E 下载(官方 prepareProcessImage 同款),仓库有 build-artifacts/mag160c_official.ddt(1.8MB,SN 160043865)
- MDT 文件格式:reverse_apk_features.md §4(152B Tail + 5 段布局 + ROI 256B 记录)
- 温度:毫度 int;MDT 解码走 ConvertResponse2Temperature + T2E(Revise) + CorrectTemperature
用户已确认的范围(2026-09-06)
- 云功能:预留 Retrofit 接口模块,默认关闭
- 扫码打标 + 任务巡检(sqlite/xml) + 红外基准图叠加:v1 全部实现
- 网络互连(用户新增需求):主机手机插 USB 热像仪,局域网内另一台手机 远程实时预览——UDP 广播自动发现 + 手动添加内网 IP;自定义轻量协议 (控制通道 JSON + 图像流),不走厂商 33596/33597 协议
当前阶段
阶段 2/8 完成:纯Kotlin核心管线移植并逐字节验证(2026-09-06)
已完成里程碑(本任务)
- 阶段1:轻量工具链定案——NDK/CMake/zig 全部卸载(-2.4GB),纯 Kotlin 方案: 渲染管线+温度算法+帧解析从 C 移植为 Kotlin,Gradle 8.14.3 + SDK 已有组件
- 阶段2:项目骨架(android/,AGP 8.11.1 + Kotlin 2.2.0 + Compose BOM, compileSdk/targetSdk 36,minSdk 26),最小APK可构建
- 阶段2:表数据自动生成 analysis/gen_kotlin_tables.py → OfficialTables.kt (palette256 ARGB / T2E 646 / T2E274 / E2T_ACC_Q10)
- 阶段2:核心移植 FrameStream / RenderPipeline / TempMath(毫度测温)
- 阶段2:PC端参考工具 analysis/render_offline.c(gcc 编译)+ 差分测试: Kotlin 输出与C参考逐字节一致(60帧序列,首渲染帧32,NUC/LUT/灰度/输出全同)
- 阶段2:FrameStream 分块重组测试 + 温度换算单调性测试全过
移植陷阱记录(教训)
- C 无符号32位回绕:乘积/减法必须 u32() 掩码(cdfdenom、0xffc0000-iv70x40000、 (v-win_lo)*S 等)
- lutRebuild 的 u12 基址是常量 iv7*0x10+0x10,不是链式 u21v;u21v 只用于曲线钳制比较
- Kotlin ByteArray 存 255 = -1:lut[bfmax] != 0xFF 判断必须 (b.toInt() and 0xFF)
- 暖机窗口内 FFC 状态机暂停(warm check 在 ffc_step 之前)——移植时保持顺序
用户反馈修复(2026-09-07)
- 中文乱码根因:部分源码曾被python以GBK重写(AppRoot.kt),编译出的字符串 mojibake。已写 fix_encoding.py 全量转UTF-8 + kotlin.daemon.jvmargs 锁UTF-8 + dex字节级验证(classes5/9.dex 中"媒体库"UTF-8正确)
- 权限弹窗:Manifest 加 USB_DEVICE_ATTACHED intent-filter + device_filter (插热像仪系统自动弹权限/启动);相册页运行时申请 READ_MEDIA_IMAGES (模拟器实测弹窗正常)
- 图标:全部重绘为可验证几何图形(双弧圆/直线/矩形),横竖屏截图确认渲染正确
- 专业版三大功能补齐:多点测温(点击加Pt1..PtN、再点删除)、右侧色标条 (渐变+最高/最低温标注)、最高温追踪开关(追踪·开/关)
- 演示模式:无设备时用真实管线+DDT渲染合成帧(热块场景),全UI可探索; demo温度用局部线性显示映射(真机温度走管线数学)
- Android 16.1 模拟器(Medium_Phone AVD)实机验证:中文/图标/布局/横竖屏正常, 预览图在 analysis/preview/preview_*.png
用户反馈修复 第二轮(2026-09-07)
- 竖屏时热像图旋转90°竖着显示(填满屏宽);横屏时保持传感器原生4:3方向 填满屏高——两个方向下显示区域都最大化(用户要求的官方行为)
- 横屏:控制栏移到屏幕右侧(竖列),竖屏保持底部横排6项等分——切换时 不再跳位
- 追踪/测温点标记改为大号"圆环+实心点"图标,字高随密度缩放(更大)
- 色标条放大(宽20dp、高0.8视口)+更大刻度字号
- ScreenOrientation=fullSensor:跟随传感器旋转(系统旋转锁定时也转, 与官方普通版一致)
- SurfaceView旋转存活:AppRoot/LiveScreen改为稳定单分支布局(导航/控制 栏做成overlay),SurfaceView不再因横竖屏切换被销毁重建(此前横屏花屏空 白的根因)
- 模拟器实测:竖屏/横屏截图确认全部生效(analysis/preview/)
用户反馈修复 第三轮(2026-09-07)
- 布局重构:控制项(FFC/变倍/追踪/调色板/拍照/录像/调色板名)移到顶部横栏, 横竖屏都显示;底栏导航(实时/相册/分析/设置)保持原位置(竖屏底部栏/横屏左侧栏)
- 图像内容世界固定:绘制角度 = (90 - 显示器rotation×90) mod 360, 四向旋转(竖屏/左横/右横/倒竖屏)图像内容都不随手机转,只转图标文字 (修复"向右横屏图像颠倒"问题)
- 状态栏沉浸:insetsController 隐藏状态栏(下滑临时显示),底栏导航仍可用
- inset感知渲染:渲染器 viewport = 全屏减顶栏/底栏inset(AppRoot横屏时 导航栏宽度不再误算为底部inset)
- 顶栏通过 onSizeChanged 上报 vm.uiTopPx;导航 overlay 高度经 UiInsets 单例 同步 vm.uiBottomPx
- (重要教训)python 裸写文件会用 GBK:所有 python 编辑必须显式 encoding='utf-8';本次 LiveScreen.kt 双重乱码已重写恢复,dex 验证 CJK 正常
- 模拟器验证:顶栏/左导航/图像/色标/标记布局正确,中文正常,状态栏隐藏生效
用户反馈修复 第四轮(2026-09-07,最终布局约定)
- 顶栏/底栏位置固定:顶栏=控制项(FFC/变倍/追踪/调色板/拍照/录像)恒在顶部; 底栏=导航恒在底部。横竖屏都不移动(取消横屏左栏),图标文字跟随屏幕方向(恒可读)
- 图像区域永不旋转:图像恒 90°CW 绘制为 3:4 竖向,填满顶栏~底栏之间可用区, 手机怎么转都不动;只转标记文字(文字恒屏幕水平,位置经 probeToScreen 固定90°映射)
- 顶栏适配挖孔屏:
WindowInsets.safeDrawing.only(Top)顶部安全区 padding - 去掉 display-rotation 依赖(uiRotation/drawRotationDeg 已删,tapImage/probeToScreen 用固定 90° 映射)
- 完整交接文档:docs/android_app/HANDOFF_DEVELOPMENT.md(含给新模型的读取提示词)
用户反馈修复 第五轮(2026-09-07,布局约定最终定稿:构图钉死竖屏框架)
- 用户澄清:"顶栏底栏位置不变"指绝对位置——永远贴着手机竖屏的物理顶边 (挖孔侧)与物理底边;热像区域也完全不动。不是"横屏后显示区域跟着屏幕转": 那样屏显方向会和镜头实际方向对不上。
- 根因与修复:此前 fullSensor 下渲染"相对当前屏幕固定 90°CW",手机横过来时
屏幕本身转 90°,整个构图相对手机框架也转 90° → 与镜头方向脱节。修复 =
Activity 锁定竖屏(manifest
screenOrientation="portrait"):屏幕相对手机框架 永不旋转,构图(顶栏/热像区/底栏)永远钉在竖屏框架上;手机怎么物理旋转都一样。 - 顺带清理:GalleryScreen 去掉 isLandscape() 死逻辑(恒 3 列); AppRoot/LiveScreen/LiveRenderer/UiInsets 注释同步更新。
- 模拟器四方向实测(0/90/180/270°,
adb emu rotate):四个方向截图逐字节一致 (MD5 相同)——构图绝对固定;演示画面/顶栏/底栏/色标/标记/中文均正常 (analysis/preview/preview_rot*_v5.png)。 - 单元测试全过;APK 已更新 build-artifacts/mag160c-app-debug.apk。
- 注:不要再改回 fullSensor/sensor 横屏;横竖屏适配类需求一律以"竖屏构图恒定"为准。
用户反馈修复 第六轮(2026-09-07,横屏持机图标文字可读)
- 用户反馈:竖屏锁定后,横过来拿手机时顶栏/底栏里的图标和文字不旋转(侧着)。 约定补全:构图钉死竖屏框架不变(条栏绝对位置+热像区域不动),但条栏内容 (图标+文字)与 OSD 文字要按物理持机朝向补偿旋转,保持可读。
- 新增
ui/DeviceOrientation.kt:加速度计→手机相对竖屏的顺时针物理转角 φ∈{0,90,180,270}(主轴判定+2.5m/s² 滞回;竖屏锁定下 Display.rotation 恒 0 不可用)。 - 顶栏 7 控制项/底部导航 4 项:
graphicsLayer rotationZ=-φ原位预旋转(布局不动)。 - 渲染器 OSD 文字(中心温/探针标注/色标最高最低数字)
canvas.rotate(-φ)绕锚点 旋转;标记圆点、色标条几何仍钉死在图像上。 - 模拟器实测(
adb emu sensor set acceleration驱动 4 姿态):四姿态下热像区域 完全一致,图标/文字按姿态正确补偿(analysis/preview/preview_pose*_v6.png); 姿态复位 0° 后输出与第五轮逐字节一致(MD5 相同)。 - 单元测试全过;APK 已更新。
- 注:对话框(调色板等)与其他页签内容未做补偿旋转(保持竖屏可读),如需再加。
用户反馈修复 第七轮(2026-09-07,真机朝向全反修复)
- 用户反馈:真机正持竖屏时屏幕内容倒立,其他方向也全反。
- 根因:第六轮把加速度计符号约定写反。Android 真机 TYPE_ACCELEROMETER 静止读数 = "加速度减重力",指向世界上方向(平放屏幕朝上 z=+9.81、竖屏正持 y=+9.81);模拟器虚拟传感器却是重力向量约定(正持 y=-9.81),故模拟器测试 "通过"而真机全反(所有姿态差 180°)。
- 修复:DeviceOrientation 映射改为 (0,+g)=0、(-g,0)=90、(0,-g)=180、(+g,0)=270; 代码注释明确标注"勿按模拟器默认值改回"。模拟器用反号值复测:姿态 0 恢复正常 正持显示,90/180/270 补偿几何不变(preview_fix_pose*_v7.png)。
- 单元测试全过;APK 已更新。
用户反馈修复 第八轮(2026-09-07,相机式按键布局)
- 顶栏精简为 4 个相机控制项(左→右,带小字标签,随持机朝向补偿旋转): FFC 校正 / 数码变倍(显示当前 1×/2×/4×,点击循环)/ 最高温追踪开关(追踪·开, 高亮+新 ic_target 准星图标)/ 调色板(显示当前调色板名,点击弹出选择)。
- 底部新增相机快门区(仅实时页,位于底导航上方,白/红色调): 相册快捷入口(跳转媒体库页签)/ 大圆形快门=拍照 / 录像-停止(红圈,录像中 红圈内容变红色方块)。快门区高度并入渲染器底部 inset(vm.uiBottomPx = 导航高+快门区高),测温点点击映射同步排除该区域。
- 底部导航(实时/相册/分析/设置)不变;快门区内容同样按持机朝向补偿旋转。
- 模拟器实测:0°/90° 姿态布局、旋转补偿、图像区域均正确 (preview_ui_v8.png / preview_ui90_v8.png);单元测试全过;APK 已更新。
用户反馈修复 第九轮(2026-09-07,真机热像顶端被顶栏遮挡)
- 用户反馈:真机上快门区把热像显示区顶满后,热像最顶端被顶栏盖住看不见。
- 根因:顶栏
onSizeChanged挂在safeDrawing挖孔 inset 内侧,uiTopPx不含挖孔安全区高度;模拟器无挖孔看不出来。快门区加入后剩余高度变小、热像 进入"高度受限"铺满视口状态,顶端误差直接表现为热像顶端插入顶栏背后。 - 修复:
onSizeChanged移到windowInsetsPadding之前(uiTopPx= 挖孔 安全区 + 顶栏全高,渲染器视口完整跳过顶栏);同时按要求缩小快门区 (相册/录像 42dp、快门 60dp、纵向 padding 6dp、间距 44dp)。 - 模拟器复测布局正常,单元测试全过;APK 已更新。
用户反馈修复 第十轮(2026-09-09,撤下演示热像图)
- 用户要求:撤下用于测试的合成热像图(演示模式),准备接真机+热像仪实测显示。
- LiveViewModel:
connect()无设备时不再启动演示渲染,改为status="no_device"(LiveScreen 既有占位文案"未检测到热像仪,请插入MAG160C",渲染器无帧即黑底); 删除 startDemo/buildDemoFrames/demoPipeline/demoNuc 与 refreshTemps 演示分支 (连带消除"演示循环与真机流同时写 latestFrame、演示温度覆盖真机温度"的隐患)。 勿把演示模式加回来。 - 工具链在本机重装并重建 APK(2026-09-09):JDK Temurin 21
C:\Tools\jdk-21、 SDKC:\Tools\android-sdk(platform 36 / build-tools 36.0.0 / platform-tools,许可已接受)、 Gradle 用工程 wrapper(8.14.3);交接文档旧路径 C:\Tools\gradle-8.14.3 / Zulu / AppData SDK 属另一台开发机已失效(HANDOFF §2/§5 已改为本机路径)。 assembleDebug+test 全过;dex 抽查中文 UTF-8 正常、无 startDemo 残留; build-artifacts/mag160c-app-debug.apk 已更新(11.8MB),可直接装真机。 - 待办不变(真机 USB 实测温度标定、网络互连远程预览、离线MDT温度解码、 调色板精确提取、PIP/云模块)。
用户反馈修复 第十一轮(2026-09-09,真机黑屏:日志落盘 + 修复缺失 START)
- 用户反馈:真机插热像仪有 USB 弹窗,但实时画面黑屏;要求把 debug 日志 保存到热成像相册目录,运行后把文件发回分析。
- 黑屏根因(代码审查定位):
IrSession.start移植时漏掉了硬件已验证的 启动序列(csdkmag160c_ir.c):读循环前必须 FFC(0)×2 → 300ms → START(73); 旧代码只发 66b/66c/66f 就进读循环,相机从未出流 → EP 0x81 静默 → 黑屏。 已按 C 参考补上(50ms → FFC(0)×2 → 300ms → START)。 - 新增
media/DebugLog.kt:每次 connect() 在 MediaStore 建一个debug_yyyyMMdd_HHmmss.log(DCIM/MAG160C,text/plain,API29+ 无需权限, 相册/文件管理器可见),逐行 flush;同时镜像到 logcat(tagMAG160C/*); MainActivity 装崩溃钩子(栈回写文件后再交前 handler)。 - 全链路插桩:UsbTransport(设备列表/权限结果/openDevice/claim/端点表)、 IrSession(命令交换 write/read/响应 magic+头16字节、首3次读头24字节、 前5帧 type/shutter、每2s 心跳 stats reads/frames/rendered/timeouts/fps/ renderState/ref、STOP)、LiveViewModel(connect/权限/首帧到达/5s 心跳)。
- 状态文案补全:open_fail/no_endpoints/exception:* 在 LiveScreen 显示 具体提示(原先一律"连接中…",掩盖故障)。
- 构建+单测全过;dex 抽查中文/日志器/启动序列字符串正常;APK 已更新
(11.87MB)。调试日志要点:若仍黑屏,看文件里
stats: reads=?—— reads=0 → START 被无视(查 cmd 日志);frames>0 rendered=0 → warm/FFC/ref 窗口(ref=false 持续 → 66c 响应异常);rendered>0 仍黑屏 → UI/渲染层问题(hb: uiFrames=?)。 - 注:调试日志文件由 MediaStore Files(非 Images)写入,部分相册 app 不显示 text/plain,可用系统"文件"应用或 PC 复制 DCIM/MAG160C/debug_*.log。
用户反馈修复 第十二轮(2026-09-10,打开即闪退:日志落点修复)
- 用户反馈:第 11 轮 APK 装好后直接打开就闪退。
- 根因:
DebugLog.startFile用MediaStore.Files往 DCIM/MAG160C 插 text/plain 文件;scoped storage 规定 DCIM 只收图片/视频,非媒体文件被拒,insert()抛 IllegalArgumentException,而该调用在LaunchedEffect → connect()协程里无 try/catch → 启动即崩。 - 修复:DebugLog 全链路 try/catch 永不抛异常;落点三级回退
DCIM/MAG160C → Download/MAG160C(非媒体允许目录)→ 应用私有外部目录;
文件头写实际落点
sink=;日志 4MB 封顶。startFile提前到 MainActivity.onCreate(启动崩溃也有记录);connect()/权限回调整体 try/catch,失败置connect_fail(statusText 显示"连接流程异常")。 - 构建+单测全过,dex 抽查通过;APK 已更新(11.87MB)。
- 注:取日志时 DCIM/MAG160C 和 Download/MAG160C 都看一眼(文件头 sink= 注明 实际位置);若在前者失败会自动落后者。
用户反馈修复 第十三轮(2026-09-10,真机日志分析:端点静默 + 重枚举)
- 用户回传 debug 日志(vivo V2509A, Android16):相机识别/DDT/66b 响应全正常 (SN 160043865),但 66c/66f 响应超时、FFC/START 第一轮写失败(write=-1, 端点疑似 halt)、流端点 19 秒 0 字节;00:51:10 相机重新枚举(权限再次弹出) ——相机在会话中途疑似断电/复位;且新旧两个 Activity 的会话同时在抢同一设备 (claimInterface 互相夺走)。
- IrSession 加固:流端点改 UsbRequest 异步(API30+,position=字节数; 低版本回退同步 bulkTransfer);任何 transfer 失败先 GET_STATUS 诊断 + CLEAR_FEATURE(HALT) 再重试一次;66b/66c/66f 响应读取缩短为 400ms 且 失败不阻断(与 C 参考一致);会话互斥(companion active,新会话先停旧 会话);stop() 时序修复(STOP 在 close 之前发,由 streamLoop 收尾)。
- LiveViewModel:注册 USB DETACHED 接收器(VID 匹配→停会话+no_device);
connect() 800ms 去抖;流 10 秒 0 数据 notify
no_stream_data(LiveScreen 屏显"已连接但无数据流")。 - 构建+单测全过;APK 已更新(11.87MB)。
- ⚠️ 下一步排查(若仍无流):相机中途重枚举强烈怀疑 OTG 供电不足
(vivo 口限流 → 相机 MCU 帧处理起来后掉电复位)。请用户:① 换一根好点的
短线/带供电的 OTG 转接器再试;② 在同一台手机装官方普通版 MAG-Cx 对照
(若官方也掉,就是供电/兼容问题,与我们的代码无关);③ 新日志看
usb detached与get_status halted=行。
用户反馈修复 第十四轮(2026-09-10,端点 halt 真凶确认:usbfs 超时遗留)
- 用户回传第二轮日志 + 关键信息"官方软件正常":每次 clear_halt 都成功 (rc=0)但下一次读仍 -1 且 halted=1 → 不是相机 STALL,而是 Linux/Android usbfs 在 bulkTransfer 超时后把端点标记 halted,后续传输全部瞬间失败。 流端点首次 500ms 读超时(相机 1~2s 才开始出流)→ 0x81 被标 halted → 之后 19 秒的读全是瞬间失败,帧全被错过。66c/66f 响应读不到同机理 (C 参考在 PC 上也忽略这些失败,完全吻合)。官方 App 正常 → 排除供电问题。
- 修复(核心一行):流循环每次 -1 后无条件 GET_STATUS + CLEAR_HALT (节流日志),端点永远保持可用;相机 1~2 秒启动延迟期间的若干次超时 不再杀死数据流。去掉异步 UsbRequest(usbfs 超时对 async 同样留 halt, 无收益);5 秒零数据自动重发一次 START 兜底;流内 FFC 响应读缩短为 400ms(不再阻塞渲染线程 2 秒)。
- 第 13 轮的会话互斥/DETACHED 恢复/去抖已验证生效(日志见 "stopping stale previous session")。
- 构建+单测全过;APK 已更新(11.87MB)。
- 预期:本轮装上后,日志应出现
first reads [0] n=...与递增的 frames/rendered, 画面出图。若 timeouts 持续上涨且 reads=0,再看 5s re-kick 与 halted 状态。
用户反馈修复 第十五轮(2026-09-10,BREAKTHROUGH:逆向官方 libcoresdk 找到缺失握手)
- 用户回传第三轮日志:clear_halt 修复已生效(0x81 每 500ms 健康轮询 25s 无遗漏),但相机确实 0 字节;66b 仅在相机上电后响应一次,随后对一切命令 沉默,~27s 后相机掉线重启。官方 App 在同一手机正常 → 排除供电/硬件。
- 逆向 analysis/ida/export 的 libcoresdk(arm64) 反编译(官方在用的库):
-
CNetComm命令层全貌:66f=ReadCaliInfo(响应 0x5BB5B55E pair={u64 标定 文件大小, u64 版本});670=GetCaliFile(响应带大小 → 从 EP 0x84 按 ≤512KB 块、60s 超时读回标定文件;日志串 "First running on new host, it will cost some time for initializing...");672=FFC;673=START; 674=STOP;676/677=省电参数(AutoPowerSave,非图像传输)。 - 官方安卓连接序列根本不发 66b/66c(66b 是 PC libmagcore 旧演示遗留), 也不发预启动 FFC;序列 = 66f →(缓存缺失)670+0x84 拉取 → 读线程 → 50ms → START。FFC 由帧驱动(pipeline onFfc)。 - 推断:我们一直发的 66b 把该固件带入旧式握手模式并卡死(66b 有响应、 之后全哑、看门狗 ~25s 重启)——与全部日志吻合。 - IrSession 按官方序列重写连接:66f(1000ms 读,解析 pair)→ 缓存
files/cali/magcore.cali.<version>命中则直接用 → 否则 670 + EP 0x84 拉取(60s/块,进度日志,成功后写缓存)→ loadDdt(拉取文件优先,失败回退 内置 DDT)→ 50ms → START。删除 66b/66c/预启动 FFC×2;首帧后补发一次 66b 拿 info 块(MDT 用)。UsbTransport 加 endpointByAddress()。 - 构建+单测全过;APK 已更新(11.87MB)。
- 预期日志特征:
66f cali-info ok+cali pair: size=1856416...→first run on this host: fetching cali file...→cali fetched+cached→first reads [0] n=...→ frames 递增 → 出图。
用户反馈修复 第十六轮(2026-09-10,反编译官方 MAG-Cx Java 源码,按其逐行重写 USB 层)
- jadx 反编译官方普通版(C:\Tools\jadx-1.5.1,产物 C:\Tools\jadx-out):
sdk/UsbCommunication.java就是能在这台手机上跑通的完整 USB 协议 Java 实现。 - 官方真实协议(P2DCmd/D2PCmd 常量全表):
- P2D:66b=GetParameter1、66c=GetParameter2、66f=GetCaliInfo、670=GetCaliFile、 671=SendCaliFile、672=SetShutterState(FFC)、673=StartTransferImg、 674=StopTransferImg、675=GetLifeTime、676=SetLaserState、677=PowerSave、 679=SetFrameRate;全部 4 字节小端(intToByteArray LE,排除字节序假设)。
- D2P 响应:0x5BB5B55B=BasePara1(serial/hw+devType/sw/宽/高/fps/gain/flip/ interFrame/interLine/gfid/gsk,14 int=56B+4=60B,与日志完全吻合)、 0x5BB5B55C=BasePara2、0x5BB5B55D=SendCaliFile ack、0x5BB5B55E={i32 size, i32 reserved, i64 date}、0x5BB5B561=lifetime。
- 官方连接序列(UsbCommunication.connect + startTransfer):
- 66b(不读 ack,waitAck 对 GetParameter1/2/GetCaliInfo 是 default=true) → recvCmd 64B/800ms → BasePara1;
- 66c → 64B/800ms → BasePara2;
- 66f → 64B/800ms → CaliInfo{size,reserved,date};
- 缓存
{caliDir}/{productType}.{serial}.{date}(devType3=core160)命中→ 直接 startTransfer;未命中→启动 ThreadCaliRecv(0x84、16KB 块、800ms/读、 无数据 5s 放弃)+ 发 670(waitForCmdAck 读 ack)→ 收满后 startTransfer; - startTransfer:native startProcess(…, caliPath)(= loadDdt 拉到的文件) → setEX/ExtPara/放大倍率(native,非 USB)→ 起线程 → 5ms → 673(读 ack)。
- TIMEOUT=800ms 全线统一(我们此前的 400ms 是响应缺失的直接嫌疑);
- 图像帧尾校验 drop=type 0/1;FFC 触发=按 GetLifeTime 开机时间表(devType3 走 imageStableCounter 分支)+ 双击/消息触发,均为 672。
- IrSession 按上述逐行重写(保留:会话互斥、CLEAR_HALT 恢复、DETACHED、 no_stream_data 提示;去掉:5s re-kick、延迟 66b)。BasePara1 解析出 identity (serial/devType/宽/高/fps)+ lastInfo0;BasePara2 存 lastInfo1(MDT 用)。 no_handshake 状态屏显"相机无应答,请拔插重试"。
- 构建+单测全过;APK 已更新(11.87MB)。
- 预期日志:GetParameter1 resp=0x5BB5B55B len=60 → GetParameter2 → GetCaliInfo (cali size=…date=…)→ 下载(首次)或 cache hit → frames 递增。
用户反馈修复 第十七轮(2026-09-10,根因确认:命令字节序反转)
- 第 16 轮日志:官方序列下 GetParameter1 仍唯一有响应(60B BasePara1 完整 解析成功:serial=160043865 devType=3=core160 160x120@15fps),66c/66f/673 全部 800ms 超时 → 与"字节内容"相关而非流程顺序。
- 根因:
MagProtocol.cmd4/cmd8用ByteBuffer.putInt—— 默认大端! 官方GlobalFunc.intToByteArray是小端。66b=0x6BB6B66B 是回文数,大小端 字节相同,所以 16 轮调试里它是唯一能被相机识别的命令,完美掩盖了 bug; 其余命令(66c/66f/670/672/673/674)发出去的字节全是反的 → 相机不应答、 收到垃圾触发看门狗重启。PC 的 C 参考(mag160c_ir.c)是手工小端打包,所以 PC 上一直正常。 - 修复:cmd4 改手工小端打包(与官方 intToByteArray 逐字节一致); cmd8 = cmd4(magic)+cmd4(param)。新增 MagProtocolTest 锁死 GetParameter2/StartTransferImg/SetShutterState 的线缆字节序。
- 构建+单测全过;APK 已更新。提交 623d62b。
- 按用户要求完成全量解包复核(产物入库 analysis/):
analysis/sdk_re/android_app/jadx_magcx/:官方普通版全量 Java 源码(64 文件);analysis/sdk_re/android_app/libcxsdk_decomp.txt:Ghidra 11.3.2 全量 反编译 libcxsdk.so(1290 函数);analysis/magcx_official_flow.md:权威协议结论(命令表/BasePara/CaliInfo/ 连接序列/FFC 逻辑/工具重跑命令)。- native 复核:
Controller::StartProcess必须LoadCalibrationTable(caliPath)(官方 startProcess 失败会删缓存文件重下)→ 我们的 loadDdt 等价正确;Controller::PushFrame输入=整帧缓冲(28B头+像素+28B尾),与本管线一致。
- 预期:66b/66c/66f 全部有响应 → cali 下载(首次)→ 流出帧。
待办(2026-09-10 更新:执行计划 A→F 已全部完成)
- 真机实测(唯一未完成的主线):照
docs/android_app/real_device_checklist.md24 步走一遍——1-12 单机(USB 出流/FFC/拍照/录像/媒体库/分析页温度条/PDF)、 13-15 可见光 PIP、16-24 双机远程预览。每步都有预期 DebugLog 原文可对照。 - 红热调色板(索引 11)精确表:官方 APK 预览图为占位副本,需真机抓帧反推。
- 云端点对接:当前为占位接口,需账号与接口文档。
- 任务巡检 UI(TaskParser 已就绪,尚无界面)——沿自早期计划,未列入本轮范围。
- 旧的"网络互连/离线MDT解码/调色板提取/PIP/云模块"五项已在本轮全部实现 (见下方阶段表),此处不再重复。
执行计划进度(2026-09-10,docs/android_app/execution_plan.md)
- Phase A(2026-09-10):GetLifeTime(675→0x5BB5B561) 查询 + deviceLifetimeMs; cali 缓存与内置 DDT MD5 一致性日志;新增 real_device_checklist.md; gradlew test 全绿,APK 已更新。commit: "android: lifetime query + cali consistency check + real-device checklist"
- Phase B(2026-09-10):Mdt.parse 返回 MdtFile(jpg 按 FFD9 裁尾、text 去 NUL 填充、framePixels/info0/info1 齐全);TempMath.tempMapFromPixels 毫度图; 分析页温度条(中心/最低/最高)+ 点击测温探针(白点+环+温度标签,再点清除); 新增 MdtTest/TempMathTest(17 个单测全绿)。注:分析页图像为原生横向显示, 故探针映射用直接映射(不是实时页的 90° 逆映射)。APK 已更新。
- Phase C(2026-09-10):结论:libcxsdk.so 无静态调色板表,12 调色板由 CFunctions::SetColorPalette 运行时算术生成(全文件扫描 alpha=0xFF 的 256-run 命中 0)。改走"移植生成函数"路线:case 2 与 OfficialTables PALETTE256_ARGB 256/256 逐字节一致(锚点锁死算术+字节序), 每 case 恰好写满 256 槽;官方 APK 预览图覆盖率自身恒为最高。 UI 索引 0..10 共 11 个调色板已换成官方精确表(VendorPalettes.kt, Palettes.kt 接线);索引 11 红热未解决(官方预览图是白热的占位副本、 case12/13 对任何预览覆盖率≤6%)→ 保留近似并注明。 新增 PalettesTest(8 项);产物见 palette_extraction_findings.md。 APK 已更新。
- Phase D(2026-09-10):云模块脚手架(Retrofit 2.11.0 opt-in,默认关闭): cloud/CloudApi.kt(接口+DATA class,CloudClient.api() 在未开启时直接 check() 抛异常——绝不静默联网);AppSettings.cloudEnabled(默认 false, 构造时同步 CloudClient);设置页"语言"与"关于"之间新增"云同步"行 + 说明对话框("当前版本仅预留接口,不会发起任何网络请求"); proguard-rules.pro 新建(-keep cloud.**;原文件缺失但被 build.gradle 引用)。新增 CloudClientTest(4 项,锁"默认关闭/api() 拒绝"契约)。 debug + release(R8) 双构建通过,29 单测全绿。APK 已更新。
- Phase E(2026-09-10):可见光 PIP 融合:Manifest 加 CAMERA 权限 + camera.any uses-feature(orientation 未动);LiveState 加 pipOn/pipSizeIndex/pipXf/pipYf + togglePip/cyclePipSize/setPipPos; 新增 ui/live/PipCameraView.kt(Camera2 最小实现,TextureView 预览, 全部异常只记 DebugLog("pip",…) 并 release,绝不崩溃);顶栏第 5 项 "画中画"+ ic_pip.xml(双弧圆+右下实心矩形);浮层三档 96/128/160dp (高=宽×3/4),位置相对热像视口、右边缘额外留 32dp 色标条位, 拖动/单击换档/双击关闭;PIP 关闭、离页、ON_STOP 三处释放相机; 运行时权限用 rememberLauncherForActivityResult。真机项已写入 real_device_checklist.md(第 13-15 步 + 失败表现)。APK 已更新。
- Phase F(2026-09-10):网络互连远程预览。net/RemoteContract.kt(协议常量+ 手写扁平 JSON 助手,无 Android 依赖便于 JVM 单测 + FramePacketReader 流式重组);net/RemoteHost.kt(UDP 47510 每秒广播 + TCP 47511 单客户端; 单一协程拥有 socket,控制回复与帧记录不会交错;帧队列 DROP_OLDEST 不拖慢 相机);net/RemoteClient.kt(discover 去重 3s;connect 行模式→收到 stream-start 后切帧模式,同一 chunk 内混合也能正确切分;10s 无数据判死); IrSession 加 rawHook(与 recorderHook 并列);LiveViewModel 接 rawHook → remoteHost.offerFrame + setRemoteHostEnabled(需活跃 USB 会话); 设置页新增"远程预览服务端"/"远程预览客户端"两行(前置校验弹"先连接热像仪"); ui/remote/ 三文件:RemoteClientListScreen(56dp 标题栏/64dp 卡片/重新扫描/ 手动添加/空态)、RemoteViewerViewModel(本地 RenderPipeline + 内置 DDT 渲染)、RemoteViewerScreen + RemoteRendererHost(与 LiveRenderer 同构图, 未改冻结文件)、AppRoot 加两个全屏目的地。Manifest 加 INTERNET。 单测 44 个全绿,含 loopback 端到端测试(真实 TCP:hello→welcome→ start→stream-start→5 帧逐字节往返→ffc→stop→stream-stop)与粘包/截断/ 坏长度/重同步用例。debug+release 双构建通过。真机双机步骤写入清单第 16-24 步。
- Phase Z(2026-09-10):收尾完成。docs 更新(本文件 + HANDOFF_DEVELOPMENT
§3 结构表加入 net/ cloud/ ui/remote/ 与 VendorPalettes/PipCameraView,
§7 状态与待办重写 + 禁改清单);全量
assembleDebug test(44 单测全绿) +assembleRelease(R8)通过;APK 已覆盖提交 (build-artifacts/mag160c-app-debug.apk,12.66MB,md5 76ff4e12…); dex 抽查 10 个新增中文串 UTF-8 正确、无 GBK 乱码;APK 校验: screenOrientation=portrait 保持、camera/camera.any 均为 required=false (CAMERA 权限原本会让相机变必需,已显式声明为可选)。 未 push(按用户指令)。
用户反馈修复 第十九轮(2026-09-11,第二轮真机:相册/录像/分析/设置持久化)
用户第二轮实机测试报出以下问题,本轮全部处理:
- 录像停止即闪退(严重)。两处根因:
①
Mp4Recorder.offerFrame先取inputSurface再判断,而stop()会 release 它——采集线程随后lockCanvas在已释放 Surface 上抛异常,该异常发生在 USB 读线程且无人捕获 → 进程崩溃。现改为所有 surface/encoder 访问同锁, 且offerFrame整体 try/catch 吞掉编码层异常(丢帧优于崩溃)。 ② 录完的文件从未保存(只改了状态文案,相册里什么都没有)。现新增MediaStore.Video保存(PhotoSaver.saveVideo)+ 临时文件清理, 状态区分rec_done/rec_save_fail/rec_fail。 - 相册点照片打不开:
AnalyzeViewer被放在fillMaxSize()的 Column 之后,布局到屏幕外,所以点击像"没反应"。改为Box内覆盖层, 并加BackHandler让返回键关闭查看器。 - 远程预览列表按返回键直接退出软件:缺
BackHandler。列表页与查看页 均补上(列表→返回设置;查看页→断开并回列表)。 - 设置不生效/不持久(用户:"每次重开设置就变回默认"):根因是
默认调色板/追踪模式从未被实时页读取(只有方向设置接了)。
AppSettings全部相关 setter 现在都 publish 到ImageOrientationSettings,init用持久化值播种;实时页 collect 后即时应用调色板+追踪模式。 (defaultEmissivityPercent/alarmTempC仍未被管线使用——见"诚实记录"。) - 照片要按设置竖直翻转/旋转后再保存:新增
PhotoSaver.encodeRendered(frame, orientation, probes),拍照时按与屏幕 一致的方向(翻转→旋转)生成 JPEG,不再保存传感器原始朝向。 - 照片上烧录测温点+温度:拍照时把探针(含
Pt* 温度标签)绘到 JPEG 上。 - MDT 新增探针数据块(
0x5BB5B55F,UTF-8 文本行x,y,label,tempMc), 分析页可重新载入原有测温点并可编辑。 - 分析页改造(按用户要求):只显示保存的原始渲染图(不再按调色板重渲染, 避免"改调色板看起来照片被改了");点击图像添加测温点、再点删除; "保存"生成新照片(标注烧录、探针入库),原文件不动; 最高/最低/中心温度与测温点列表显示在侧边栏。
- 追踪模式设置项:设置页新增"追踪"(最高温/最低温/最高+最低/关闭),
顶栏按钮显示当前模式(追高/追低/追高·低/追踪),录入
TraceMode。 - 操作反馈:拍照/录像完成后在实时页显示 2.5 秒提示(此前这些状态只在 "未连接"分支显示,正常出图时用户看不到任何反馈)。
- 单测 66 → 76 项全绿;debug + release(R8) 双构建通过;APK 已更新。
用户反馈修复 第二十轮(2026-09-11,第三轮真机:分析测温/标注/相册/界面)
用户第三轮实机测试(含截图)报出以下问题,本轮全部处理:
- 分析温度全错(截图显示最高 145.1℃ / 最低 -161.0℃ / 中心 76.5℃)。
双重根因:
① 数据源错了:实时读数用的是
copyNuc()的NUC 补偿后 counts, 而照片里存的BLOCK_FRAME是传感器原始响应,标定表对它无效 → 直接换算就是垃圾值。新增BLOCK_NUC(0x5BB5B560):存拍照当刻的 NUC counts,分析端用它测量(与实时屏幕同一数据源)。 ② 我自己引入的索引 bug:第一版把 160×120 的样本"散布"进 320×240 的 照片空间,76800 个槽位只填了 19200,其余全 0 →countsToTempMc(0)= -161.0℃(正是截图里的最低温)。改为buildPhotoOrderedCounts():每个照片像素一个样本,查表就是counts[iy*photoW+ix],不做任何旋转/翻转/缩放换算。 - 测温点位置标错:探针原先按传感器坐标存储,显示时却当照片像素用。
现在拍照时即转换到照片像素(
sensorToImage),并且新增photoToSensor()反变换用于测温;两者互逆性有单测覆盖(4 旋转 × 2 翻转)。 - 测温点太大:标注尺寸原先用屏幕密度(4.5×density 的点、9×density 的环)
画进 320×240 的位图 → 环直径约 36px / 320px 图宽,视觉上盖住画面。
新增
MarkStyle:尺寸按图像宽度比例(点 2.6、环 5.5、字 9 @320px), 与实时屏幕观感一致。实时页/分析页/保存照片三处统一。 - 分析页布局:数据面板从右侧竖栏改为底部横条(用户要求), 三个数值(最高/最低/中心)等分排布 + 测温点横向滚动列表,标题栏收窄。
- 分析页不显示极值标记:现在在图上用小环标出最高/最低位置(带"高/低"字样)。
- 相册与分析界面一样:分析 tab 原先直接渲染相册的网格(同一个
GalleryScreen)。拆分为: -GalleryScreen(相册 tab):像正常相册一样,点开进入全屏查看器 (双指缩放 1–8×、拖动平移、双击复位、删除含确认对话框); -AnalyzeScreen(分析 tab):只列出带温度数据的照片 (MdtProbe.isMeasurable),点开进入测温分析页。 - 旧照片优雅降级:没有
BLOCK_NUC的照片(本轮之前拍的)在分析页显示 "无温度数据",不再编造温度;新拍照片都带该数据块。 - 单测 76 → 83 项全绿(新增
PhotoNucMappingTest:密集性/中心对齐/ 探针往返/字节往返/无 NUC 判定);debug + release(R8) 双构建通过;APK 已更新。
已知取舍(诚实记录):
BLOCK_NUC是照片分辨率(76800 样本 = 153KB,未压缩),照片文件因此增大 约 150KB。选择它的理由:分析端查表无需任何坐标换算,从根上消除"索引对不上" 这一类缺陷(本轮的两个温度 bug 都源于此)。若日后要压缩,需保证查找端使用 同一套映射函数。- 默认发射率/报警温度仍未接入管线(同第十八轮记录)。
诚实记录(第十八轮未做):
defaultEmissivityPercent(默认发射率)与alarmTempC(报警温度)仍未被 测温管线使用:发射率需要官方CorrectTemperature的完整浮点公式(已从 libcxsdk 伪代码定位到@000298f0,但牵涉 T2E/环境温度/Energe2Temp多处 状态,属独立议题),报警温度需要超温提示 UI。当前这两项只保存与显示, 不声称已生效。
用户反馈修复 第十八轮(2026-09-11,真机实测:温度/朝向/远程三类缺陷)
用户实机安装测试(含两台手机远程预览)报出以下问题,本轮全部处理:
- 中心温度测量错误:
probeTemp()返回的已是毫度,updateTemps又调了 一次countsToTempMc()→ 真机显示 108.7℃(实际约 24℃)。 远程页同一 bug(显示 -161℃)。两处都改为只除 1000 一次,并在参数注释里 写明单位契约。 - FFC 时最高/最低温跳到 ~150℃:FFC 参考帧被解码进
nuc(OSD 采样的同一 缓冲区),未补偿的原始 counts 被当成温度。改为独立refScratch缓冲; 另加tempsReady()门控(首帧渲染前nuc全 0,而countsToTempMc(0)= -161.0℃,正是远程截图显示的读数)。 新增回归测试PipelineTemperatureStateTest(3 项)。 - 追踪开关只关最高温:最低温标记未受开关控制(
LiveRenderer.drawOsd)。 现在开=最高+最低都画("高"/"低"),关=都不画。 - 画面转向反了 / 横屏应翻 180°:先按"图像内容随握持角补偿(
rot = 90 - φ)" 改了一版,用户实测后指正:图像应保持锁定——传感器装在手机上跟着一起转, 锁定图像时场景相对世界方向自动正确,加速度计参与反而双重补偿(这正是 "右转画面往反方向转"的原因)。已改回锁定:ImageTransform.params()不再接受握持角参数(编译期保证),只保留官方同款手工修正。 用户还实测发现"竖直翻转 + 旋转 90°"能把画面转到另一方向——已用单测 证明它等价于"水平翻转 + 旋转 270°"(逐像素比对),两者只差一次水平镜像。 - 新增官方同款方向设置:设置页"旋转USB画面"(0/90/180/270,叠加在锁定
基准 90° 上)、"水平翻转"、"竖直翻转"(作用在传感器帧上,与官方一致),
持久化于
AppSettings。 - 色标条两端最高/最低温与色条错位:标签原先按未旋转的基线锚点定位。
改为按旋转后包围盒定位(
ImageTransform.rotatedBoxHalfExtents+drawGripText),任意握持角都贴着色条两端。 - 几何单一来源:新增
ui/live/ImageTransform.kt(纯 Kotlin),渲染器与 点击/探针映射共用同一套参数与互逆映射;新增ImageTransformTest(6 项, 覆盖 0/90/180/270 × 两种翻转的往返一致性)+ImageTransformOrientationTest(4 项,锁官方映射与"转身时图像反向")。 - 远程预览画面不正确(用户截图:-161℃、满屏噪声):根因是协议只传像素,
客户端独立跑 FFC 状态机且拿不到帧的相机温度,而 NUC 表要按快门温度插值
→ counts 饱和、参考帧缺失。协议改为传元数据:
[magic][counter][38400][flags][ffcPhase][shutter][像素](头 24B,记录 38424B), 客户端用RenderPipeline.frameRemote(frame, phase, shutter, out)复刻主机状态。 (用户建议"传原始数据本地渲染"正是此方向;此前的错误在于只传了数据的一半。) - 顺带修核查报告的两项证伪:
RemoteSession.send()改为单写协程 + 队列 (原先每条命令各起协程写同一 socket,hello/start可能乱序,~6.7% 丢welcome;新增commandOrderIsPreservedUnderRapidSends回归); 主机实现"后来者写 busy"(accept 循环不再阻塞在 serve 上,第二客户端立即 收到{"type":"busy"};新增secondClientIsRejectedWithBusy)。 另删除未使用的ACCESS_NETWORK_STATE权限。 - 设置页翻不动 + 死行(用户报"设置页选项翻不动"):
① 设置页
Column缺verticalScroll,11 行内容一屏放不下,下方几行 完全够不到 → 已加滚动; ② "语言"行点了没反应:when(dialog)里根本没有"language"分支, 点击后落进else -> {};且该项没有任何代码读取、也没有 i18n 资源 (全部界面为中文字面量、无 strings.xml)→ 补上对话框并如实说明"当前仅中文, 选择会被记录待后续翻译",不再假装能切语言; ③ "关于"行同样设dialog = null属死行 → 补上说明对话框; ④ 设置变更即时生效:原先实时/远程页每 400ms 轮询AppSettings,而该轮询 只在实时页处于组合状态时运行(在设置页改动后要切回实时页才生效)→ 新增ImageOrientationSettings(进程级 StateFlow),AppSettings三个方向 setter 写入后立即 publish,实时/远程页 collect 后即时应用。 新增ImageOrientationSettingsTest(4 项);单测 62 → 66 项全绿。 - 单测 62 → 66 项全绿;debug + release(R8) 双构建通过;APK 已更新
(12.66MB)。
screenOrientation=portrait复核保持、camera 系列仍 not-required、多余权限已消失。
⚠️ 规格说明(用户裁定):图像锁定在竖屏框架(第四/五轮约定维持不变);
新增的仅是官方同款三项手工方向修正。中途曾按握持角补偿图像,用户实测指正后
已改回,ImageTransform.params() 不再接受握持角参数以防回退。
本轮(2026-09-10 执行计划 A→F)总结
七个阶段全部落地,每阶段一次 commit:
| 阶段 | commit | 内容 |
|---|---|---|
| A | 06c1f30 |
lifetime 查询 + cali MD5 对照 + 真机自检清单 |
| B | 656d419 |
MDT 温度解码 + 分析页温度条/测温探针 |
| C | b7a928e |
厂商调色板提取(11/12 精确表) |
| D | c34940e |
云模块脚手架(opt-in 默认关) |
| E | f8b3200 |
可见光 PIP 叠加(Camera2,三档可拖) |
| F | 512508e |
局域网远程预览(UDP 发现 + 原始帧 TCP,客户端渲染) |
测试:29(D 结束)→ 44(F 结束),含真实 TCP loopback 端到端用例。
留给用户的实测:docs/android_app/real_device_checklist.md 共 24 步
(1-12 单机 USB/拍照/录像/分析;13-15 PIP;16-24 双机远程预览),
每步标注预期 DebugLog 原文。
已知未完成项(诚实记录):
- 红热调色板(索引 11)仍是近似——官方 APK 预览图是白热的占位副本, 生成的 case12/13 对任何预览覆盖率 ≤6%,不猜测;需真机抓帧反推。
- 全部真机验证(温度绝对值、PIP 相机、双机远程、MDT 温度条)需用户配合; 本机无模拟器(SDK 无 emulator 组件)、无连接设备,只能保证编译与单测。
- 云端点为占位,需账号与接口文档才能对接。
里程碑日志
- 阶段3b 完成(2026-09-06 晚): 拍照 MDT 容器(jpg+info块+raw帧+备注)存入 MediaStore DCIM/MAG160C;MP4 录像 = Surface 编码 H.264 @320×240 15fps 2Mbps
- 阶段4 完成: 媒体库(MediaStore 扫描 + 内嵌 JPEG 缩略图 + MDT 尾部校验)
- 阶段5a 完成: 分析查看器(detectTransformGestures 缩放1-4x/平移、12 调色板 重渲染原始帧、自动窗口、备注编辑回写容器、温度探针近似)
- 阶段5b 完成: PDF 巡检报告(PdfDocument:标题/7×4信息表/热像图/结果/建议/测试员)
- 阶段6a 完成: 设置页(默认调色板/发射率/报警温度/关于,SharedPreferences 私有)
- 任务巡检解析器 TaskParser(sqlite task/region/device/part/phase + XML title/target)
阶段3a 完成(2026-09-06 晚)
- USB传输层 usb/UsbTransport.kt(VID 0x833C 枚举/权限/claim/EP查找)
- 命令协议 usb/MagProtocol.kt(4B命令+FFC 8B;0x5BB5B55B 信息块解析)
- 直播会话 usb/IrSession.kt(66b/66c/66f → FFC(0)x2 → 300ms → START(73), 读线程→FrameStream→RenderPipeline→帧回调;STOP(74);手动FFC)
- 官方12调色板 core/Palettes.kt(铁虹=官方提取表,其余为标准曲线近似)
- UI骨架:底部导航4区(实时/相册/分析/设置,横屏自动切侧栏 NavigationRail)
- 实时画面 ui/live/:SurfaceView+软件Canvas渲染(letterbox+数码变倍+OSD 中心温/最高最低温标记),ViewModel驱动,慢速测温定时器
- DDT标定文件打包进 assets/mag160c.ddt
- 全APK assembleDebug 成功(11.7MB,无material-icons膨胀)
- 待实机验证USB流(需插设备)
开发计划(待确认后逐阶段执行)
- 阶段 1:环境(cmdline-tools/NDK r27+/gradle 8.x + 16KB 对齐配置 + AGP 8.x)
- 阶段 2:新仓库 app-android(Kotlin + Compose M3 + NDK 移植 csdk 渲染/温度 C 代码 + Kotlin USB 层)
- 阶段 3:实时画面(USB 流+官方管线+OSD+ROI+调色板+FFC+增强+PIP 可见光+拍照 MDT+MP4 录像)
- 阶段 4:媒体库(MediaStore,DCIM/MAG160C,无流氓文件夹)
- 阶段 5:MDT 离线分析(缩放/ROI/参数/PDF 报告/备注)
- 阶段 6:设置/任务巡检/可选云(Retrofit opt-in)
- 阶段 7:Android 16 专项适配验收(16KB 对齐/横竖屏/深色/预测性返回)
- 阶段 8:打包安装到用户手机实测
心跳约定
- 每完成一个阶段:更新本文件勾选 + git commit(不 push)。
- 中断恢复:读本文件 → 按"下一步"继续 → 环境检查命令:
Test-Path C:\Users\ZXC\AppData\Local\Android\Sdk\ndk\<ver> Get-Command java; Get-Command gradle