建立 MAG160C 逆向工程交接仓库
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(mag160c_c LANGUAGES C)
|
||||
|
||||
option(MAG160C_BUILD_TESTS "Build MAG160C C tests" ON)
|
||||
option(MAG160C_BUILD_CLI "Build MAG160C C CLI" ON)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS ON)
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(LIBUSB QUIET libusb-1.0)
|
||||
endif()
|
||||
|
||||
# Windows fallback: bundled libusb-1.0 (x64) under third_party/libusb/win64
|
||||
if(NOT LIBUSB_FOUND AND WIN32)
|
||||
set(LIBUSB_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/libusb/win64")
|
||||
if(EXISTS "${LIBUSB_ROOT}/libusb-1.0.x64.a" AND EXISTS "${LIBUSB_ROOT}/libusb.h")
|
||||
set(LIBUSB_FOUND TRUE)
|
||||
set(LIBUSB_INCLUDE_DIRS "${LIBUSB_ROOT}")
|
||||
set(LIBUSB_LIBRARIES "${LIBUSB_ROOT}/libusb-1.0.x64.a")
|
||||
set(LIBUSB_CFLAGS_OTHER "")
|
||||
set(MAG160C_LIBUSB_DLL "${LIBUSB_ROOT}/libusb-1.0.dll")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(LIBUSB_FOUND)
|
||||
set(MAG160C_HAS_LIBUSB 1)
|
||||
else()
|
||||
set(MAG160C_HAS_LIBUSB 0)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux|Darwin" OR MINGW)
|
||||
set(MAG160C_HAS_THREADS 1)
|
||||
else()
|
||||
set(MAG160C_HAS_THREADS 0)
|
||||
endif()
|
||||
|
||||
add_library(mag160c_c STATIC
|
||||
src/mag160c_error.c
|
||||
src/mag160c_frame.c
|
||||
src/mag160c_temp.c
|
||||
src/mag160c_display.c
|
||||
src/mag160c_tcm.c
|
||||
src/mag160c_ir.c
|
||||
)
|
||||
|
||||
target_include_directories(mag160c_c
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_definitions(mag160c_c PUBLIC
|
||||
MAG160C_HAS_LIBUSB=${MAG160C_HAS_LIBUSB}
|
||||
MAG160C_HAS_THREADS=${MAG160C_HAS_THREADS}
|
||||
)
|
||||
|
||||
if(LIBUSB_FOUND)
|
||||
target_include_directories(mag160c_c PRIVATE ${LIBUSB_INCLUDE_DIRS})
|
||||
target_link_libraries(mag160c_c PRIVATE ${LIBUSB_LIBRARIES})
|
||||
target_compile_options(mag160c_c PRIVATE ${LIBUSB_CFLAGS_OTHER})
|
||||
if(DEFINED MAG160C_LIBUSB_DLL AND TARGET mag160c-cli)
|
||||
add_custom_command(TARGET mag160c-cli POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${MAG160C_LIBUSB_DLL}" "$<TARGET_FILE_DIR:mag160c-cli>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
target_link_libraries(mag160c_c PUBLIC pthread)
|
||||
endif()
|
||||
|
||||
if(MAG160C_BUILD_CLI)
|
||||
add_executable(mag160c-cli tools/mag160c_cli.c)
|
||||
target_link_libraries(mag160c-cli PRIVATE mag160c_c)
|
||||
endif()
|
||||
|
||||
if(MAG160C_BUILD_TESTS)
|
||||
enable_testing()
|
||||
|
||||
foreach(t test_frame test_temp test_display test_tcm test_api)
|
||||
add_executable(${t} tests/${t}.c)
|
||||
target_link_libraries(${t} PRIVATE mag160c_c)
|
||||
add_test(NAME ${t} COMMAND ${t})
|
||||
endforeach()
|
||||
endif()
|
||||
@@ -0,0 +1,55 @@
|
||||
# MAG160C C SDK (csdk/)
|
||||
|
||||
Pure C reimplementation of the MAG160C thermal camera SDK, written from the
|
||||
recovered protocol (see `analysis/protocol_spec.md` for the full reverse
|
||||
engineering report). The previous C++ implementation was archived to
|
||||
`analysis/legacy-cpp/`; this tree replaces it.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
csdk/
|
||||
include/mag160c/mag160c.h public C ABI (error domain, magic codes, frame
|
||||
format, temperature API, TCM framing)
|
||||
src/
|
||||
mag160c_internal.h shared helpers (last-error)
|
||||
mag160c_error.c TLS last-error + error names
|
||||
mag160c_frame.c 0x1bb1b11b frame parse + streaming assembler
|
||||
mag160c_temp.c Calibration / ConvertResponse2Temperature / T2E
|
||||
mag160c_tcm.c FTDICommand 0x7e framing + rotate/light builders
|
||||
mag160c_ir.c libusb session (link/start/stop/ffc/info)
|
||||
mag160c_tables.h T2E + E2TAccQ10 tables (generated from binary)
|
||||
tools/mag160c_cli.c CLI: ir-info, ffc, start, stop, tcm-rotate,
|
||||
tcm-light, frame-test, temp-test
|
||||
tests/ test_frame, test_temp, test_tcm, test_api
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
cmake -B build -S csdk # auto-detects libusb-1.0 via pkg-config
|
||||
cmake --build build
|
||||
ctest --test-dir build
|
||||
```
|
||||
|
||||
Without libusb-1.0 the IR session APIs return MAG160C_ERR_NOT_SUPPORTED;
|
||||
the pure helpers (frame parse, temperature math, TCM framing) and their tests
|
||||
work with no dependencies. Threads (pthread) are used when available.
|
||||
|
||||
## Recovered protocol summary
|
||||
|
||||
- VID 0x833C, config value 2, interface 0.
|
||||
- Commands: EP OUT 0x03, 8-byte packets `{u32 magic, u32 param}` (0x3c-byte
|
||||
with 0x38-byte payload for DDT). Response: EP IN 0x82 (0x1000 max, 2000 ms).
|
||||
- Magic commands: 0x6bb6b66b/66c prepare, 66d/66e DDT, 66f info, 670 version,
|
||||
672 FFC, 673 start, 674 stop, 676/677 set params.
|
||||
- Magic responses: 0x5bb5b55b..55f (0x38-byte info blocks / 0x10 pair),
|
||||
0x5bb5b57b file header.
|
||||
- Frame stream: EP IN 0x81, marker 0x1bb1b11b @0, counter @4, data length @8,
|
||||
type @0xc (0 response / 1 raw), shutter @0x10, pixels @0x1c (uint16 LE),
|
||||
trailing 0x1bb1b11c @0x1c+len, frame size 0x38+len.
|
||||
- Temperature: per-pixel piecewise-linear Calibration with baseline,
|
||||
ConvertResponse2Temperature auto-gain, T2E curve (0x112 entries) with Q13
|
||||
band interpolation.
|
||||
- TCM: FTDICommand 0x7e framing with additive (mod-256) checksums; command
|
||||
table main 0x02 (rotate 0x77, light 0x31/32/33, ...).
|
||||
@@ -0,0 +1,121 @@
|
||||
# Windows 测试与 USB 抓包指南
|
||||
|
||||
## 1. 已安装的工具
|
||||
|
||||
| 工具 | 位置 | 用途 |
|
||||
|---|---|---|
|
||||
| USBPcap 1.5.4 | `C:\Program Files\USBPcap\` | USB 抓包驱动 + USBPcapCMD.exe |
|
||||
| Wireshark 4.6.7 | `C:\Program Files\Wireshark\` | 查看 .pcap 抓包文件 |
|
||||
| libusb-1.0 (x64) | `csdk\third_party\libusb\win64\` | 本地构建用(TI CCS 的 64 位 DLL + dlltool 生成的导入库) |
|
||||
| MinGW gcc 14.2 | `C:\mingw64\bin\gcc.exe` | 编译 |
|
||||
|
||||
注意:USBPcap/Wireshark 安装后**需要重启**才能加载驱动。
|
||||
本机当前没有 VID 0x833C 设备 —— 插入相机后按下面流程测试。
|
||||
|
||||
## 2. 构建(Windows 无 CMake 环境时用 MinGW 直接编)
|
||||
|
||||
```powershell
|
||||
$lb = "C:\Project\MAG160C\csdk\third_party\libusb\win64"
|
||||
$srcs = @(
|
||||
"csdk\src\mag160c_error.c","csdk\src\mag160c_frame.c",
|
||||
"csdk\src\mag160c_temp.c","csdk\src\mag160c_tcm.c",
|
||||
"csdk\src\mag160c_ir.c")
|
||||
gcc -std=c11 -Icsdk\include -Icsdk\src -I$lb `
|
||||
-DMAG160C_STATIC -DMAG160C_HAS_LIBUSB=1 -DMAG160C_HAS_THREADS=1 `
|
||||
$srcs csdk\tools\mag160c_cli.c $lb\libusb-1.0.x64.a `
|
||||
-o build-artifacts\csdk_cli_libusb.exe -lpthread
|
||||
Copy-Item $lb\libusb-1.0.dll build-artifacts\
|
||||
```
|
||||
|
||||
或装 CMake 后: `cmake -B build -S csdk && cmake --build build`(会自动用 third_party 里的 libusb)。
|
||||
|
||||
## 3. 无硬件验证(纯计算 + 协议构造)
|
||||
|
||||
```powershell
|
||||
.\build-artifacts\csdk_cli_libusb.exe tcm-rotate 5
|
||||
# 7e 00 07 85 02 77 00 01 00 05 7f
|
||||
.\build-artifacts\csdk_cli_libusb.exe tcm-light green blink
|
||||
# 7e 00 09 87 02 32 00 01 01 00 ff 00 35
|
||||
.\build-artifacts\csdk_cli_libusb.exe frame-test
|
||||
.\build-artifacts\csdk_cli_libusb.exe temp-test
|
||||
.\build-artifacts\csdk_cli_libusb.exe ir-info # 应显示 "no device with VID 0x833c"
|
||||
```
|
||||
|
||||
## 4. 有硬件验证
|
||||
|
||||
插入相机(VID 0x833C)后:
|
||||
|
||||
```powershell
|
||||
# 确认系统看到设备
|
||||
Get-PnpDevice -PresentOnly | Where-Object { $_.InstanceId -match "833C" }
|
||||
|
||||
# 枚举 + 信息
|
||||
.\build-artifacts\csdk_cli_libusb.exe ir-info
|
||||
|
||||
# 触发 FFC(会真实发包)
|
||||
.\build-artifacts\csdk_cli_libusb.exe ffc
|
||||
|
||||
# 启动流(帧回调打印)+ 停止
|
||||
.\build-artifacts\csdk_cli_libusb.exe start
|
||||
.\build-artifacts\csdk_cli_libusb.exe stop
|
||||
```
|
||||
|
||||
预期:ir-info 显示 width=160 height=120;FFC 返回 OK;start 后 USBPcap
|
||||
抓包应看到 EP 0x03 上 `6b b6 b6 72` 或 `6b b6 b6 73` 的 8 字节写、EP 0x82 的响应读、
|
||||
EP 0x81 上 `1b b1 b1 1b` 开头的帧流。
|
||||
|
||||
## 5. USB 抓包(USBPcap + Wireshark)
|
||||
|
||||
USBPcap 需要管理员权限。列出捕获设备:
|
||||
|
||||
```powershell
|
||||
# 管理员 PowerShell
|
||||
& "C:\Program Files\USBPcap\USBPcapCMD.exe" -d
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
1. \\.\USBPcap1 USB Root Hub (xHCI)
|
||||
2. \\.\USBPcap2 USB Root Hub (xHCI)
|
||||
...
|
||||
```
|
||||
|
||||
开始抓包(抓所有 USB 根集线器,输出 pcap 文件):
|
||||
|
||||
```powershell
|
||||
# 管理员 PowerShell,抓 30 秒(-A 30)
|
||||
& "C:\Program Files\USBPcap\USBPcapCMD.exe" -d "\\.\USBPcap1" -o C:\captures\mag1.pcap -A 30
|
||||
& "C:\Program Files\USBPcap\USBPcapCMD.exe" -d "\\.\USBPcap2" -o C:\captures\mag2.pcap -A 30
|
||||
```
|
||||
|
||||
另开一个窗口跑 `csdk_cli_libusb.exe ffc` / `start`,让包被捕获。
|
||||
|
||||
在 Wireshark 中打开 pcap,过滤:
|
||||
- 找到相机设备(右键设备名查看)
|
||||
- 过滤 `usb.idVendor == 0x833c`
|
||||
- 看 bulk OUT 到 0x03 的 8 字节包、bulk IN 0x82 的响应、0x81 的帧流
|
||||
|
||||
也可以用 Wireshark 图形界面直接选 USBPcap 接口开始抓(需要重启后驱动生效)。
|
||||
|
||||
## 6. 抓包结果对照(逆向验证清单)
|
||||
|
||||
| 包 | 期望 |
|
||||
|---|---|
|
||||
| 链接后第一个命令 | `6b b6 b6 6f 00 00 00 00` (GET_INFO) |
|
||||
| FFC | `6b b6 b6 72 <param 4B>` |
|
||||
| Start | 50ms 后 `6b b6 b6 73 00 00 00 00` |
|
||||
| Stop | `6b b6 b6 74 00 00 00 00` |
|
||||
| 响应 | `5b b5 b5 5b` + 0x38 字节(payload +0x10=width, +0x14=height) |
|
||||
| 帧流 | `1b b1 b1 1b` ... 数据在 +0x1c ... `1c b1 b1 1b` |
|
||||
|
||||
如与期望不符,将 pcap 保存到 `analysis/captures/` 并记录差异,用于修正协议。
|
||||
|
||||
## 2026-08-10 Post-reboot verification (USBPcap live capture)
|
||||
|
||||
- After a system reboot the USBPcap filter driver binds to the root hubs.
|
||||
On this machine the MAG160C device appears on \\\\.\USBPcap4 (AMD xHCI).
|
||||
Capture with: USBPcapCMD -d \\.\USBPcap4 -o out.pcap -A -s 65535
|
||||
- The csdk's own traffic (analysis/captures/csdk_verified.pcap) matches the
|
||||
official demo byte-for-byte at the sequence level:
|
||||
66b -> 66c -> 66f -> FFC(0) x2 -> START -> frames (15 fps) -> STOP.
|
||||
- USBPcap capture replaces the libusb0.dll shim for routine sniffing.
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* MAG160C thermal camera SDK - pure C implementation.
|
||||
*
|
||||
* Protocol recovered from the vendor binaries:
|
||||
* - Linux libmagcore.so.2.1.1 (x86-64) : link/config/command/frame-stream
|
||||
* - Android libcoresdk.so (ARM64) : temperature conversion (CFunctions)
|
||||
* - Windows CoreSDKLib.dll : cross-validation of all magic codes
|
||||
* Full details in analysis/protocol_spec.md.
|
||||
*
|
||||
* USB topology (recovered):
|
||||
* VID 0x833C, config value 2, interface 0
|
||||
* EP OUT 0x03 : commands EP IN 0x82 : command responses
|
||||
* EP IN 0x81 : frame stream EP IN 0x84 : bulk/large reads
|
||||
*
|
||||
* Commands are 8-byte packets {u32 magic, u32 param} (or 0x3c-byte with a
|
||||
* 0x38-byte payload for DDT). Response magic codes: 0x5BB5B55B..0x5BB5B57B.
|
||||
*/
|
||||
#ifndef MAG160C_H
|
||||
#define MAG160C_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(MAG160C_STATIC)
|
||||
# define MAG160C_API
|
||||
#elif defined(_WIN32)
|
||||
# if defined(MAG160C_BUILDING_LIBRARY)
|
||||
# define MAG160C_API __declspec(dllexport)
|
||||
# else
|
||||
# define MAG160C_API __declspec(dllimport)
|
||||
# endif
|
||||
#else
|
||||
# define MAG160C_API
|
||||
#endif
|
||||
|
||||
typedef enum mag160c_error_t {
|
||||
MAG160C_OK = 0,
|
||||
MAG160C_ERR_INVALID_ARGUMENT = 1,
|
||||
MAG160C_ERR_NO_MEMORY = 2,
|
||||
MAG160C_ERR_NOT_READY = 3, /* vendor 0xe4ae0003 */
|
||||
MAG160C_ERR_NOT_INITIALIZED = 4, /* vendor 0xe4ae0004 */
|
||||
MAG160C_ERR_NOT_OPEN = 5, /* vendor 0xe4ae0005 */
|
||||
MAG160C_ERR_NOT_SUPPORTED = 6, /* vendor 0xe4ae0006 */
|
||||
MAG160C_ERR_FAILED = 7, /* vendor 0xe4ae0007 */
|
||||
MAG160C_ERR_USB = 8,
|
||||
MAG160C_ERR_TIMEOUT = 9,
|
||||
MAG160C_ERR_CHECKSUM = 10,
|
||||
MAG160C_ERR_BAD_FRAME = 11,
|
||||
MAG160C_ERR_INTERNAL = 12
|
||||
} mag160c_error_t;
|
||||
|
||||
/* ---- IR camera identity / info ---------------------------------------- */
|
||||
|
||||
#define MAG160C_IR_VENDOR_ID 0x833c
|
||||
#define MAG160C_IR_CONFIG_VALUE 2
|
||||
#define MAG160C_IR_INTERFACE_NUMBER 0
|
||||
#define MAG160C_IR_EP_CMD_OUT 0x03
|
||||
#define MAG160C_IR_EP_CMD_IN 0x82
|
||||
#define MAG160C_IR_EP_STREAM_IN 0x81
|
||||
#define MAG160C_IR_EP_BULK_IN 0x84
|
||||
|
||||
typedef struct mag160c_ir_info_t {
|
||||
uint16_t width; /* 0xa0 = 160 */
|
||||
uint16_t height; /* 0x78 = 120 */
|
||||
uint16_t fpa_width;
|
||||
uint16_t fpa_height;
|
||||
uint32_t pid;
|
||||
uint32_t serial_lo; /* info block +0x08 */
|
||||
uint32_t serial_hi;
|
||||
uint32_t max_fps;
|
||||
char name[64];
|
||||
} mag160c_ir_info_t;
|
||||
|
||||
/* ---- vendor magic codes (recovered) ------------------------------------ */
|
||||
|
||||
#define MAG160C_MAG_CMD_PREPARE1 0x6bb6b66b
|
||||
#define MAG160C_MAG_CMD_PREPARE2 0x6bb6b66c
|
||||
#define MAG160C_MAG_CMD_DDT_WRITE 0x6bb6b66d
|
||||
#define MAG160C_MAG_CMD_DDT_READ 0x6bb6b66e
|
||||
#define MAG160C_MAG_CMD_GET_INFO 0x6bb6b66f
|
||||
#define MAG160C_MAG_CMD_GET_VERSION 0x6bb6b670
|
||||
#define MAG160C_MAG_CMD_FFC 0x6bb6b672
|
||||
#define MAG160C_MAG_CMD_START 0x6bb6b673
|
||||
#define MAG160C_MAG_CMD_STOP 0x6bb6b674
|
||||
#define MAG160C_MAG_CMD_SET_A 0x6bb6b676
|
||||
#define MAG160C_MAG_CMD_SET_B 0x6bb6b677
|
||||
|
||||
#define MAG160C_MAG_RSP_INFO_0 0x5bb5b55b
|
||||
#define MAG160C_MAG_RSP_INFO_1 0x5bb5b55c
|
||||
#define MAG160C_MAG_RSP_INFO_2 0x5bb5b55d
|
||||
#define MAG160C_MAG_RSP_PAIR 0x5bb5b55e
|
||||
#define MAG160C_MAG_RSP_INFO_3 0x5bb5b55f
|
||||
#define MAG160C_MAG_RSP_FILE 0x5bb5b57b
|
||||
|
||||
/* ---- frame stream ------------------------------------------------------- */
|
||||
|
||||
#define MAG160C_FRAME_MARKER 0x1bb1b11b
|
||||
#define MAG160C_FRAME_TRAILING_MARKER 0x1bb1b11c
|
||||
#define MAG160C_FRAME_DATA_OFFSET 0x1c
|
||||
#define MAG160C_FRAME_OVERHEAD 0x38
|
||||
#define MAG160C_FRAME_MAX_PIXELS 0x10000 /* 160x120 and 384x288 both fit */
|
||||
|
||||
typedef enum mag160c_frame_type_t {
|
||||
MAG160C_FRAME_RESPONSE = 0,
|
||||
MAG160C_FRAME_RAW = 1
|
||||
} mag160c_frame_type_t;
|
||||
|
||||
typedef struct mag160c_frame_header_t {
|
||||
uint32_t marker;
|
||||
uint32_t frame_counter;
|
||||
uint32_t data_length; /* bytes of pixel data at +0x1c */
|
||||
uint32_t frame_type;
|
||||
uint32_t period_shutter;
|
||||
uint32_t trailing_marker;
|
||||
} mag160c_frame_header_t;
|
||||
|
||||
/* ---- temperature pipeline (recovered CFunctions) ------------------------ */
|
||||
|
||||
typedef struct mag160c_temp_tables_t {
|
||||
const int16_t *thresholds; /* [pixels][bands-1] int16, or NULL */
|
||||
const uint16_t *pwl; /* [band][pixel] {coeff,offset} pairs, or NULL */
|
||||
uint32_t pixel_count; /* 160*120 = 19200 */
|
||||
uint32_t band_count; /* thresholds per pixel + 1 */
|
||||
const uint16_t *baseline; /* optional, or NULL */
|
||||
uint32_t has_baseline;
|
||||
} mag160c_temp_tables_t;
|
||||
|
||||
/* ---- device handles ----------------------------------------------------- */
|
||||
|
||||
typedef struct mag160c_ctx_t mag160c_ctx_t;
|
||||
typedef struct mag160c_ir_t mag160c_ir_t;
|
||||
|
||||
/* ---- lifecycle ---------------------------------------------------------- */
|
||||
|
||||
MAG160C_API mag160c_error_t mag160c_init(mag160c_ctx_t **out_ctx);
|
||||
MAG160C_API void mag160c_shutdown(mag160c_ctx_t *ctx);
|
||||
MAG160C_API const char *mag160c_last_error(void);
|
||||
MAG160C_API const char *mag160c_error_name(mag160c_error_t code);
|
||||
|
||||
/* ---- IR camera session --------------------------------------------------- */
|
||||
|
||||
MAG160C_API mag160c_error_t mag160c_ir_open(mag160c_ctx_t *ctx, mag160c_ir_t **out_ir);
|
||||
MAG160C_API void mag160c_ir_close(mag160c_ir_t *ir);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_is_linked(mag160c_ir_t *ir);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_get_info(mag160c_ir_t *ir, mag160c_ir_info_t *out_info);
|
||||
|
||||
/* Start: allocates stream buffers, starts reader + dispatcher threads,
|
||||
* waits 50 ms, sends MAG_CMD_START. */
|
||||
MAG160C_API mag160c_error_t mag160c_ir_start(mag160c_ir_t *ir);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_stop(mag160c_ir_t *ir);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_trigger_ffc(mag160c_ir_t *ir, uint32_t param);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_set_ffc_mode(mag160c_ir_t *ir, uint32_t mode);
|
||||
|
||||
/* Attach an FFC scheduler (mag160c/mag160c_display.h). The reader thread
|
||||
* ticks it after every complete frame and sends the requested FFC command,
|
||||
* replicating the official cadence that keeps the type=0 stream alive.
|
||||
* Pass own=1 to have the IR session free a heap-allocated scheduler. */
|
||||
struct mag160c_ffc_scheduler_t;
|
||||
MAG160C_API mag160c_error_t mag160c_ir_set_ffc_scheduler(mag160c_ir_t *ir,
|
||||
struct mag160c_ffc_scheduler_t *sched,
|
||||
int own);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_prepare(mag160c_ir_t *ir);
|
||||
MAG160C_API mag160c_error_t mag160c_ir_reset(mag160c_ir_t *ir);
|
||||
|
||||
/* Poll a freshly parsed frame from the dispatcher. out_frame must be at
|
||||
* least data_length + FRAME_OVERHEAD bytes. Returns MAG160C_OK with
|
||||
* *out_length = 0 when no complete frame is buffered yet. */
|
||||
typedef void (*mag160c_frame_cb_t)(uint32_t frame_index, const uint8_t *data,
|
||||
size_t data_len, void *user);
|
||||
|
||||
MAG160C_API mag160c_error_t mag160c_ir_set_frame_callback(mag160c_ir_t *ir,
|
||||
mag160c_frame_cb_t cb,
|
||||
void *user);
|
||||
|
||||
/* Read latest converted temperature for a probe position (x, y) in
|
||||
* millidegrees Celsius scaled by 100 (i.e. value/100000 = °C is NOT used;
|
||||
* see protocol spec section 7.5). Uses window average then ReviseTemperature
|
||||
* + CorrectTemperature when calibration tables are installed. */
|
||||
MAG160C_API mag160c_error_t mag160c_ir_read_temperature(mag160c_ir_t *ir,
|
||||
uint32_t x, uint32_t y,
|
||||
int32_t *out_temp);
|
||||
|
||||
/* ---- frame parse (pure, no USB needed) ----------------------------------- */
|
||||
|
||||
MAG160C_API mag160c_error_t mag160c_frame_parse(const uint8_t *data, size_t size,
|
||||
mag160c_frame_header_t *out_hdr,
|
||||
const uint16_t **out_pixels);
|
||||
|
||||
/* Streaming assembler mirroring the vendor reader thread. */
|
||||
typedef struct mag160c_frame_stream_t {
|
||||
uint8_t *buf; /* 2*max_frame_len + 0x470 */
|
||||
size_t cap;
|
||||
size_t len;
|
||||
size_t aligned;
|
||||
} mag160c_frame_stream_t;
|
||||
|
||||
MAG160C_API void mag160c_frame_stream_init(mag160c_frame_stream_t *s, size_t max_frame_len);
|
||||
MAG160C_API void mag160c_frame_stream_destroy(mag160c_frame_stream_t *s);
|
||||
MAG160C_API mag160c_error_t mag160c_frame_stream_push(mag160c_frame_stream_t *s,
|
||||
const uint8_t *data, size_t size,
|
||||
uint8_t *out_frame, size_t out_cap,
|
||||
size_t *out_len);
|
||||
|
||||
/* ---- temperature conversion (pure, no USB needed) ------------------------ */
|
||||
|
||||
/* CFunctions::Calibration - per-pixel piecewise-linear map with baseline. */
|
||||
MAG160C_API void mag160c_temp_calibrate(const uint16_t *frame,
|
||||
const mag160c_temp_tables_t *tables,
|
||||
uint16_t *out);
|
||||
|
||||
/* CFunctions::ConvertResponse2Temperature - auto-gain linear map. */
|
||||
typedef struct mag160c_temp_gain_t {
|
||||
uint32_t gain; /* this+0x76e0 */
|
||||
uint32_t shift; /* this+0x76e4 */
|
||||
int32_t coeff; /* auto-adjusted gain (global 0x4028a4) */
|
||||
const uint16_t *baseline;
|
||||
} mag160c_temp_gain_t;
|
||||
|
||||
MAG160C_API uint32_t mag160c_temp_convert_response(const uint16_t *frame,
|
||||
uint32_t pixel_count,
|
||||
const mag160c_temp_gain_t *cfg,
|
||||
uint16_t *out);
|
||||
|
||||
/* T2E piecewise-linear curve evaluation with Q13 band interpolation
|
||||
* (ReviseTemperature/CorrectTemperature core). */
|
||||
MAG160C_API int32_t mag160c_temp_t2e_interp(int32_t value);
|
||||
|
||||
/* ---- TCM board ------------------------------------------------------------ */
|
||||
|
||||
typedef enum mag160c_tcm_light_color_t {
|
||||
MAG160C_TCM_LIGHT_OFF = 0,
|
||||
MAG160C_TCM_LIGHT_RED = 1,
|
||||
MAG160C_TCM_LIGHT_GREEN = 2,
|
||||
MAG160C_TCM_LIGHT_BLUE = 3,
|
||||
MAG160C_TCM_LIGHT_YELLOW = 4
|
||||
} mag160c_tcm_light_color_t;
|
||||
|
||||
typedef enum mag160c_tcm_light_mode_t {
|
||||
MAG160C_TCM_LIGHT_STEADY = 0,
|
||||
MAG160C_TCM_LIGHT_BLINK = 1,
|
||||
MAG160C_TCM_LIGHT_BREATH = 2
|
||||
} mag160c_tcm_light_mode_t;
|
||||
|
||||
/* FTDICommand framing: 0x7e header + BE body length + header checksum +
|
||||
* main/sub/frame-id + payload + body checksum. */
|
||||
MAG160C_API mag160c_error_t mag160c_tcm_encode(uint8_t main_cmd, uint8_t sub_cmd,
|
||||
uint16_t frame_id,
|
||||
const uint8_t *payload, size_t payload_size,
|
||||
uint8_t *out, size_t out_cap, size_t *out_size);
|
||||
MAG160C_API mag160c_error_t mag160c_tcm_decode(const uint8_t *data, size_t size,
|
||||
uint8_t *out_main, uint8_t *out_sub,
|
||||
uint16_t *out_frame_id,
|
||||
uint8_t *out_payload, size_t out_payload_cap,
|
||||
size_t *out_payload_size);
|
||||
MAG160C_API mag160c_error_t mag160c_tcm_rotate(int angle,
|
||||
uint8_t *out, size_t out_cap, size_t *out_size);
|
||||
MAG160C_API mag160c_error_t mag160c_tcm_light(mag160c_tcm_light_color_t color,
|
||||
mag160c_tcm_light_mode_t mode,
|
||||
uint8_t *out, size_t out_cap, size_t *out_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MAG160C_H */
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* MAG160C display pipeline - pure C, no OS/USB dependencies.
|
||||
* See mag160c_display.c for the pipeline documentation.
|
||||
*/
|
||||
#ifndef MAG160C_DISPLAY_H
|
||||
#define MAG160C_DISPLAY_H
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- bad pixel map ------------------------------------------------------ */
|
||||
|
||||
typedef struct mag160c_display_badmap_t {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t pixels;
|
||||
uint32_t feed_count; /* frames fed to mag160c_display_badmap_feed */
|
||||
uint32_t temporal_thr; /* min/max fluctuation threshold (default 400) */
|
||||
uint32_t hist_min_dev; /* histogram peak min deviation (default 200) */
|
||||
uint32_t bad_count; /* detected bad pixels */
|
||||
uint32_t order_len; /* pixels in the topological fill order */
|
||||
int ready; /* set by finalize() */
|
||||
uint8_t *bad; /* pixels bad mask */
|
||||
uint32_t *order_x; /* fill order x */
|
||||
uint32_t *order_y; /* fill order y */
|
||||
uint64_t *ref_sum; /* reference accumulator */
|
||||
uint16_t *ref_min;
|
||||
uint16_t *ref_max;
|
||||
uint16_t *ref; /* averaged reference (dead pixels filled) */
|
||||
} mag160c_display_badmap_t;
|
||||
|
||||
MAG160C_API void mag160c_display_badmap_init(mag160c_display_badmap_t *m,
|
||||
uint32_t w, uint32_t h);
|
||||
MAG160C_API void mag160c_display_badmap_destroy(mag160c_display_badmap_t *m);
|
||||
MAG160C_API mag160c_error_t mag160c_display_badmap_feed(mag160c_display_badmap_t *m,
|
||||
const uint16_t *frame);
|
||||
MAG160C_API mag160c_error_t mag160c_display_badmap_finalize(mag160c_display_badmap_t *m);
|
||||
MAG160C_API mag160c_error_t mag160c_display_badmap_correct(const mag160c_display_badmap_t *m,
|
||||
uint16_t *frame);
|
||||
|
||||
/* MOG-style per-pixel reference tracking (anti-ghost): background pixels
|
||||
* (|live-ref| < thr) track drift slowly (ref += d/alpha); foreground pixels
|
||||
* are frozen so a static object is never absorbed into the reference. */
|
||||
MAG160C_API void mag160c_display_ref_track(uint16_t *ref, const uint16_t *live,
|
||||
uint32_t n, int32_t thr, int32_t alpha);
|
||||
|
||||
/* MOG tracking with self-healing: an *isolated* pixel stuck as foreground
|
||||
* for heal_frames (fewer than min_nbr foreground 8-neighbors) is a
|
||||
* reference error (startup noise, bad pixel) and is reset to the live
|
||||
* value; contiguous objects are never healed. */
|
||||
MAG160C_API void mag160c_display_ref_track_heal(uint16_t *ref, const uint16_t *live,
|
||||
uint32_t w, uint32_t h,
|
||||
int32_t thr, int32_t alpha,
|
||||
uint8_t *fg_count,
|
||||
uint32_t heal_frames,
|
||||
uint32_t min_nbr);
|
||||
|
||||
/* Flat-field (NUC) correction: out = live - (ref - mean(ref)). Removes the
|
||||
* fixed sensor mura so the absolute image is smooth; the reference must be
|
||||
* the fixed pattern only (object-free capture + frozen tracking). */
|
||||
MAG160C_API mag160c_error_t mag160c_display_nuc(const uint16_t *live,
|
||||
const uint16_t *ref,
|
||||
uint32_t n, uint16_t *out);
|
||||
|
||||
/* Re-align the reference after FFC(1): applies the median of (live-ref)
|
||||
* clamped to max_shift as a global offset (baseline shifts reach +1000). */
|
||||
MAG160C_API mag160c_error_t mag160c_display_ref_rebase(uint16_t *ref,
|
||||
const uint16_t *live,
|
||||
uint32_t n,
|
||||
int32_t max_shift);
|
||||
|
||||
/* ---- AGC ---------------------------------------------------------------- */
|
||||
|
||||
MAG160C_API mag160c_error_t mag160c_display_agc_range(const uint16_t *frame, uint32_t n,
|
||||
uint32_t lo_pct, uint32_t hi_pct,
|
||||
uint32_t *out_lo, uint32_t *out_hi);
|
||||
MAG160C_API mag160c_error_t mag160c_display_diff_span(const int32_t *diff, uint32_t n,
|
||||
uint32_t min_span, uint32_t max_span,
|
||||
uint32_t *out_span);
|
||||
|
||||
/* ---- pseudo color ------------------------------------------------------- */
|
||||
|
||||
MAG160C_API void mag160c_display_ironbow(uint32_t v, uint8_t *r, uint8_t *g, uint8_t *b);
|
||||
|
||||
/* ---- FFC scheduler (official demo cadence) ------------------------------ */
|
||||
|
||||
typedef struct mag160c_ffc_scheduler_t {
|
||||
uint32_t period; /* frames between FFC pairs (default 400) */
|
||||
uint32_t gap; /* frames FFC(0) -> FFC(1) (default 9) */
|
||||
uint32_t frames; /* frames since last FFC event */
|
||||
uint32_t started; /* first FFC(1) already sent */
|
||||
uint32_t wait1; /* FFC(0) sent, waiting to send FFC(1) */
|
||||
} mag160c_ffc_scheduler_t;
|
||||
|
||||
MAG160C_API void mag160c_ffc_scheduler_init(mag160c_ffc_scheduler_t *s,
|
||||
uint32_t period, uint32_t gap);
|
||||
/* Returns -1 (nothing) or an FFC param (0/1) to send after this frame. */
|
||||
MAG160C_API int32_t mag160c_ffc_scheduler_tick(mag160c_ffc_scheduler_t *s);
|
||||
/* Force an immediate FFC pair: returns 0 (send FFC(0) now), then tick()
|
||||
* returns 1 (send FFC(1)) after `gap` frames. */
|
||||
MAG160C_API int32_t mag160c_ffc_scheduler_trigger(mag160c_ffc_scheduler_t *s);
|
||||
|
||||
/* ---- two-point linear temperature calibration --------------------------- */
|
||||
|
||||
/* counts = a*temp + b from two known (counts, tempC) points. */
|
||||
MAG160C_API mag160c_error_t mag160c_temp_calibrate_linear(double counts0, double temp0,
|
||||
double counts1, double temp1,
|
||||
double *out_a, double *out_b);
|
||||
MAG160C_API double mag160c_temp_apply_linear(double counts, double a, double b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MAG160C_DISPLAY_H */
|
||||
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* MAG160C display pipeline - pure C, no OS/USB dependencies.
|
||||
*
|
||||
* Implements the pipeline validated on hardware (Windows demo v3) and
|
||||
* informed by open-source thermal SDKs (SeekThermal/OpenThermal, FLIR
|
||||
* Lepton AGC, MLX90640):
|
||||
*
|
||||
* frame -> bad pixel correction -> diff/NUC -> AGC -> LUT -> RGB
|
||||
*
|
||||
* 1. Bad pixel detection (Seek-style histogram peak deviation + temporal
|
||||
* min/max) with topological-order 4-neighbour fill.
|
||||
* 2. AGC: percentile stretch (FLIR LINEAR clip, 2%/98%) or adaptive diff
|
||||
* stretch with deadband.
|
||||
* 3. Ironbow pseudo-color LUT (FLIR-style anchors).
|
||||
* 4. FFC scheduler replicating the official demo cadence recovered from
|
||||
* analysis/captures/libusb0_trace.txt: FFC(1) after the ~10th frame
|
||||
* (stream -> type=0), then every ffc_period frames send FFC(0) and
|
||||
* ffc_gap frames later FFC(1). Verified: 1400+ type=0 frames, 15 fps,
|
||||
* zero stalls.
|
||||
* 5. Two-point linear temperature calibration (counts -> degC).
|
||||
*/
|
||||
#include "mag160c_internal.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* bad pixel map */
|
||||
|
||||
void mag160c_display_badmap_init(mag160c_display_badmap_t *m, uint32_t w,
|
||||
uint32_t h) {
|
||||
if (m == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(m, 0, sizeof(*m));
|
||||
m->width = w;
|
||||
m->height = h;
|
||||
m->pixels = w * h;
|
||||
m->temporal_thr = 400; /* min/max fluctuation (counts) */
|
||||
m->hist_min_dev = 200; /* histogram peak min deviation (counts) */
|
||||
m->bad = (uint8_t *)calloc(m->pixels, 1);
|
||||
m->order_x = (uint32_t *)malloc(m->pixels * sizeof(uint32_t));
|
||||
m->order_y = (uint32_t *)malloc(m->pixels * sizeof(uint32_t));
|
||||
m->ref_sum = (uint64_t *)calloc(m->pixels, sizeof(uint64_t));
|
||||
m->ref_min = (uint16_t *)malloc(m->pixels * sizeof(uint16_t));
|
||||
m->ref_max = (uint16_t *)malloc(m->pixels * sizeof(uint16_t));
|
||||
m->ref = (uint16_t *)calloc(m->pixels, sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void mag160c_display_badmap_destroy(mag160c_display_badmap_t *m) {
|
||||
if (m == NULL) {
|
||||
return;
|
||||
}
|
||||
free(m->bad);
|
||||
free(m->order_x);
|
||||
free(m->order_y);
|
||||
free(m->ref_sum);
|
||||
free(m->ref_min);
|
||||
free(m->ref_max);
|
||||
free(m->ref);
|
||||
memset(m, 0, sizeof(*m));
|
||||
}
|
||||
|
||||
static int has_valid_neighbor(const uint8_t *bad, uint32_t w, uint32_t h,
|
||||
uint32_t x, uint32_t y) {
|
||||
return (x > 0 && !bad[y * w + x - 1]) ||
|
||||
(x + 1 < w && !bad[y * w + x + 1]) ||
|
||||
(y > 0 && !bad[(y - 1) * w + x]) ||
|
||||
(y + 1 < h && !bad[(y + 1) * w + x]);
|
||||
}
|
||||
|
||||
/* neighbour mean of frame at (x,y), skipping bad pixels; 0 when none. */
|
||||
static uint32_t neighbor_mean(const uint16_t *fr, const uint8_t *bad,
|
||||
uint32_t w, uint32_t h, uint32_t x, uint32_t y) {
|
||||
uint64_t sum = 0;
|
||||
uint32_t n = 0;
|
||||
if (x > 0 && !bad[y * w + x - 1]) { sum += fr[y * w + x - 1]; n++; }
|
||||
if (x + 1 < w && !bad[y * w + x + 1]) { sum += fr[y * w + x + 1]; n++; }
|
||||
if (y > 0 && !bad[(y - 1) * w + x]) { sum += fr[(y - 1) * w + x]; n++; }
|
||||
if (y + 1 < h && !bad[(y + 1) * w + x]) { sum += fr[(y + 1) * w + x]; n++; }
|
||||
return n ? (uint32_t)(sum / n) : 0;
|
||||
}
|
||||
|
||||
/* topological fill order so clusters are filled edge-first */
|
||||
static void build_fill_order(mag160c_display_badmap_t *m) {
|
||||
const uint32_t w = m->width, h = m->height, n = m->pixels;
|
||||
uint8_t *work = (uint8_t *)malloc(n);
|
||||
if (work == NULL) {
|
||||
return;
|
||||
}
|
||||
memcpy(work, m->bad, n);
|
||||
uint32_t remain = 0;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if (work[i]) remain++;
|
||||
}
|
||||
uint32_t len = 0;
|
||||
while (remain > 0) {
|
||||
uint32_t progress = 0;
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
for (uint32_t x = 0; x < w; ++x) {
|
||||
if (!work[y * w + x]) continue;
|
||||
if (!has_valid_neighbor(work, w, h, x, y)) continue;
|
||||
m->order_x[len] = x;
|
||||
m->order_y[len] = y;
|
||||
len++;
|
||||
work[y * w + x] = 0;
|
||||
remain--;
|
||||
progress++;
|
||||
}
|
||||
}
|
||||
if (progress == 0) { /* fully isolated: force-fill in order */
|
||||
for (uint32_t y = 0; y < h && progress == 0; ++y) {
|
||||
for (uint32_t x = 0; x < w && progress == 0; ++x) {
|
||||
if (!work[y * w + x]) continue;
|
||||
m->order_x[len] = x;
|
||||
m->order_y[len] = y;
|
||||
len++;
|
||||
work[y * w + x] = 0;
|
||||
remain--;
|
||||
progress++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m->order_len = len;
|
||||
free(work);
|
||||
}
|
||||
|
||||
/* threshold = histPeak - (frameMax - histPeak) (Seek method).
|
||||
* Guarded: a pixel is only flagged when it is also at least hist_min_dev
|
||||
* counts above the scene peak, so a lone extreme outlier cannot push the
|
||||
* threshold below the whole background. */
|
||||
static uint32_t histogram_peak_threshold(const uint16_t *fr, uint32_t n,
|
||||
uint32_t min_dev) {
|
||||
uint32_t *hist = (uint32_t *)calloc(65536, sizeof(uint32_t));
|
||||
if (hist == NULL) {
|
||||
return 0xffffu;
|
||||
}
|
||||
uint32_t peakv = 0, peakc = 0, maxv = 0;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
uint32_t v = fr[i];
|
||||
if (++hist[v] > peakc) {
|
||||
peakc = hist[v];
|
||||
peakv = v;
|
||||
}
|
||||
if (v > maxv) maxv = v;
|
||||
}
|
||||
free(hist);
|
||||
if (maxv <= peakv) {
|
||||
return 0xffffu;
|
||||
}
|
||||
int64_t t = (int64_t)peakv - ((int64_t)maxv - (int64_t)peakv);
|
||||
uint64_t guard = (uint64_t)peakv + min_dev;
|
||||
return t > (int64_t)guard ? (uint32_t)t : (uint32_t)guard;
|
||||
}
|
||||
|
||||
/* Feed frames for reference statistics (call ~30 times at idle scene). */
|
||||
mag160c_error_t mag160c_display_badmap_feed(mag160c_display_badmap_t *m,
|
||||
const uint16_t *frame) {
|
||||
if (m == NULL || frame == NULL || m->bad == NULL) {
|
||||
mag160c_set_error("mag160c_display_badmap_feed: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
const uint32_t n = m->pixels;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
uint16_t v = frame[i];
|
||||
m->ref_sum[i] += v;
|
||||
if (m->feed_count == 0) {
|
||||
m->ref_min[i] = m->ref_max[i] = v;
|
||||
} else {
|
||||
if (v < m->ref_min[i]) m->ref_min[i] = v;
|
||||
if (v > m->ref_max[i]) m->ref_max[i] = v;
|
||||
}
|
||||
}
|
||||
m->feed_count++;
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* Finalize: averages, temporal + histogram bad pixel detection, fill. */
|
||||
mag160c_error_t mag160c_display_badmap_finalize(mag160c_display_badmap_t *m) {
|
||||
if (m == NULL || m->bad == NULL) {
|
||||
mag160c_set_error("mag160c_display_badmap_finalize: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (m->feed_count == 0) {
|
||||
mag160c_set_error("mag160c_display_badmap_finalize: no frames fed");
|
||||
return MAG160C_ERR_NOT_READY;
|
||||
}
|
||||
const uint32_t n = m->pixels;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
m->ref[i] = (uint16_t)(m->ref_sum[i] / m->feed_count);
|
||||
m->bad[i] = 0;
|
||||
}
|
||||
uint32_t nb = 0;
|
||||
/* temporal detection */
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if ((uint32_t)m->ref_max[i] - (uint32_t)m->ref_min[i] > m->temporal_thr) {
|
||||
m->bad[i] = 1;
|
||||
nb++;
|
||||
}
|
||||
}
|
||||
/* histogram peak deviation (bright defects) */
|
||||
uint32_t thr = histogram_peak_threshold(m->ref, n, m->hist_min_dev);
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if (!m->bad[i] && (uint32_t)m->ref[i] > thr) {
|
||||
m->bad[i] = 1;
|
||||
nb++;
|
||||
}
|
||||
}
|
||||
m->bad_count = nb;
|
||||
build_fill_order(m);
|
||||
/* fill the reference image itself */
|
||||
uint8_t *work = (uint8_t *)malloc(n);
|
||||
if (work == NULL) {
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
memcpy(work, m->bad, n);
|
||||
for (uint32_t i = 0; i < m->order_len; ++i) {
|
||||
uint32_t x = m->order_x[i], y = m->order_y[i];
|
||||
uint32_t v = neighbor_mean(m->ref, work, m->width, m->height, x, y);
|
||||
if (v > 0) {
|
||||
m->ref[y * m->width + x] = (uint16_t)v;
|
||||
work[y * m->width + x] = 0;
|
||||
}
|
||||
}
|
||||
free(work);
|
||||
m->ready = 1;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* Correct one frame in place using the stored topological order. */
|
||||
mag160c_error_t mag160c_display_badmap_correct(const mag160c_display_badmap_t *m,
|
||||
uint16_t *frame) {
|
||||
if (m == NULL || frame == NULL || !m->ready) {
|
||||
mag160c_set_error("mag160c_display_badmap_correct: not ready");
|
||||
return MAG160C_ERR_NOT_READY;
|
||||
}
|
||||
for (uint32_t i = 0; i < m->order_len; ++i) {
|
||||
uint32_t x = m->order_x[i], y = m->order_y[i];
|
||||
uint32_t v = neighbor_mean(frame, m->bad, m->width, m->height, x, y);
|
||||
if (v > 0) frame[y * m->width + x] = (uint16_t)v;
|
||||
}
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* MOG-style per-pixel reference tracking (anti-ghost):
|
||||
* - |d| < thr : background pixel, slowly track drift (ref += d/alpha)
|
||||
* - |d| >= thr : foreground (object), reference FROZEN - a static object
|
||||
* is never absorbed, so moving it away leaves no ghost.
|
||||
* Pass ref=null to just classify (not needed by callers today). */
|
||||
void mag160c_display_ref_track(uint16_t *ref, const uint16_t *live,
|
||||
uint32_t n, int32_t thr, int32_t alpha) {
|
||||
if (ref == NULL || live == NULL || thr <= 0 || alpha <= 0) {
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
int32_t d = (int32_t)live[i] - (int32_t)ref[i];
|
||||
if (d > -thr && d < thr) {
|
||||
ref[i] = (uint16_t)((int32_t)ref[i] + d / alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* MOG tracking with self-healing (anti-ghost + anti-startup-noise):
|
||||
* fg_count[] counts consecutive foreground frames per pixel. A pixel stuck
|
||||
* as *isolated* foreground (fewer than min_nbr foreground 8-neighbors) for
|
||||
* more than heal_frames is a reference error (startup noise baked into the
|
||||
* reference, bad pixel) rather than a real object (objects are contiguous)
|
||||
* - it is reset to the live value so noise cannot persist forever. */
|
||||
void mag160c_display_ref_track_heal(uint16_t *ref, const uint16_t *live,
|
||||
uint32_t w, uint32_t h,
|
||||
int32_t thr, int32_t alpha,
|
||||
uint8_t *fg_count, uint32_t heal_frames,
|
||||
uint32_t min_nbr) {
|
||||
if (ref == NULL || live == NULL || w == 0 || h == 0 ||
|
||||
thr <= 0 || alpha <= 0 || fg_count == NULL) {
|
||||
return;
|
||||
}
|
||||
if (heal_frames == 0) heal_frames = 1;
|
||||
const uint32_t n = w * h;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
int32_t d = (int32_t)live[i] - (int32_t)ref[i];
|
||||
if (d > -thr && d < thr) {
|
||||
ref[i] = (uint16_t)((int32_t)ref[i] + d / alpha);
|
||||
fg_count[i] = 0;
|
||||
} else {
|
||||
if (fg_count[i] < 255) fg_count[i]++;
|
||||
if (fg_count[i] >= heal_frames) {
|
||||
uint32_t x = i % w, y = i / w;
|
||||
uint32_t nbr_fg = 0;
|
||||
for (int32_t dy = -1; dy <= 1; ++dy) {
|
||||
for (int32_t dx = -1; dx <= 1; ++dx) {
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
int32_t xx = (int32_t)x + dx, yy = (int32_t)y + dy;
|
||||
if (xx < 0 || (uint32_t)xx >= w ||
|
||||
yy < 0 || (uint32_t)yy >= h) {
|
||||
continue;
|
||||
}
|
||||
uint32_t nbr = (uint32_t)yy * w + (uint32_t)xx;
|
||||
int32_t dn = (int32_t)live[nbr] - (int32_t)ref[nbr];
|
||||
if (dn > thr || dn < -thr) nbr_fg++;
|
||||
}
|
||||
}
|
||||
if (nbr_fg < min_nbr) {
|
||||
ref[i] = live[i]; /* heal reference error */
|
||||
fg_count[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Flat-field (NUC) correction: out[i] = live[i] - (ref[i] - mean(ref)).
|
||||
* Removes the fixed sensor mura (measured row span 15704 -> 401 counts on
|
||||
* the MAG160C), so the absolute temperature image is smooth. The reference
|
||||
* is the fixed pattern only - it must NOT track the scene (see
|
||||
* mag160c_display_ref_track_heal for the freeze/self-heal model and the
|
||||
* object-free guard at capture time), otherwise objects ghost. */
|
||||
mag160c_error_t mag160c_display_nuc(const uint16_t *live, const uint16_t *ref,
|
||||
uint32_t n, uint16_t *out) {
|
||||
if (live == NULL || ref == NULL || out == NULL || n == 0) {
|
||||
mag160c_set_error("mag160c_display_nuc: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
uint64_t s = 0;
|
||||
for (uint32_t i = 0; i < n; ++i) s += ref[i];
|
||||
const int32_t mean = (int32_t)(s / n);
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
int64_t v = (int64_t)live[i] - ((int64_t)ref[i] - mean);
|
||||
if (v < 0) v = 0;
|
||||
if (v > 65535) v = 65535;
|
||||
out[i] = (uint16_t)v;
|
||||
}
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* Re-align the reference after FFC(1): the type=0 baseline shifts (measured
|
||||
* up to +1000 counts). Applies the median of (live-ref) clamped to
|
||||
* max_shift as a global offset, robust to a small object in the scene. */
|
||||
mag160c_error_t mag160c_display_ref_rebase(uint16_t *ref, const uint16_t *live,
|
||||
uint32_t n, int32_t max_shift) {
|
||||
if (ref == NULL || live == NULL || n == 0) {
|
||||
mag160c_set_error("mag160c_display_ref_rebase: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
int32_t *d = (int32_t *)malloc(n * sizeof(int32_t));
|
||||
if (d == NULL) {
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
int32_t v = (int32_t)live[i] - (int32_t)ref[i];
|
||||
if (v > 4000) v = 4000;
|
||||
if (v < -4000) v = -4000;
|
||||
d[i] = v;
|
||||
}
|
||||
/* median */
|
||||
for (uint32_t a = 1; a < n; ++a) {
|
||||
int32_t key = d[a];
|
||||
uint32_t b = a;
|
||||
while (b > 0 && d[b - 1] > key) {
|
||||
d[b] = d[b - 1];
|
||||
b--;
|
||||
}
|
||||
d[b] = key;
|
||||
}
|
||||
int32_t off = d[n / 2];
|
||||
if (off > max_shift) off = max_shift;
|
||||
if (off < -max_shift) off = -max_shift;
|
||||
free(d);
|
||||
if (off != 0) {
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
int64_t v = (int64_t)ref[i] + off;
|
||||
if (v < 0) v = 0;
|
||||
if (v > 65535) v = 65535;
|
||||
ref[i] = (uint16_t)v;
|
||||
}
|
||||
}
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* AGC */
|
||||
|
||||
/* Percentile stretch: find [lo_pct, hi_pct] value range of frame. */
|
||||
mag160c_error_t mag160c_display_agc_range(const uint16_t *frame, uint32_t n,
|
||||
uint32_t lo_pct, uint32_t hi_pct,
|
||||
uint32_t *out_lo, uint32_t *out_hi) {
|
||||
if (frame == NULL || out_lo == NULL || out_hi == NULL) {
|
||||
mag160c_set_error("mag160c_display_agc_range: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (lo_pct >= hi_pct || hi_pct > 100) {
|
||||
mag160c_set_error("mag160c_display_agc_range: bad percentiles");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
uint32_t hist[65536];
|
||||
memset(hist, 0, sizeof(hist));
|
||||
uint32_t nz = 0;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if (frame[i] == 0) continue;
|
||||
hist[frame[i]]++;
|
||||
nz++;
|
||||
}
|
||||
if (nz == 0) {
|
||||
*out_lo = 0;
|
||||
*out_hi = 65535;
|
||||
return MAG160C_OK;
|
||||
}
|
||||
uint64_t acc = 0, lo = (uint64_t)nz * lo_pct / 100, hi = (uint64_t)nz * hi_pct / 100;
|
||||
uint32_t mn = 0, mx = 0;
|
||||
for (uint32_t k = 0; k < 65536; ++k) {
|
||||
acc += hist[k];
|
||||
if (acc >= lo && mn == 0) mn = k;
|
||||
if (acc >= hi) {
|
||||
mx = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mx <= mn + 50) {
|
||||
mn = 0;
|
||||
mx = 65535;
|
||||
}
|
||||
*out_lo = mn;
|
||||
*out_hi = mx;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* Adaptive diff AGC: span = clamp(2*mean|d|, min_span, max_span). */
|
||||
mag160c_error_t mag160c_display_diff_span(const int32_t *diff, uint32_t n,
|
||||
uint32_t min_span, uint32_t max_span,
|
||||
uint32_t *out_span) {
|
||||
if (diff == NULL || out_span == NULL) {
|
||||
mag160c_set_error("mag160c_display_diff_span: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
uint64_t sum = 0;
|
||||
uint32_t cnt = 0;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
int32_t d = diff[i];
|
||||
if (d < 0) d = -d;
|
||||
if (d > 2) {
|
||||
sum += (uint64_t)d;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
uint64_t span = cnt ? 2 * sum / cnt : 0;
|
||||
if (span < min_span) span = min_span;
|
||||
if (span > max_span) span = max_span;
|
||||
*out_span = (uint32_t)span;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* pseudo color */
|
||||
|
||||
/* FLIR-style ironbow anchors, v in [0,255] */
|
||||
void mag160c_display_ironbow(uint32_t v, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||
static const uint8_t anchors[7][3] = {
|
||||
{0, 0, 0}, {23, 0, 60}, {93, 0, 128}, {170, 31, 83},
|
||||
{226, 117, 29}, {249, 196, 66}, {255, 255, 220}};
|
||||
double f = (v & 0xff) / 255.0 * 6.0;
|
||||
uint32_t i = (uint32_t)f;
|
||||
if (i > 5) i = 5;
|
||||
double t = f - i;
|
||||
*r = (uint8_t)(anchors[i][0] + t * (anchors[i + 1][0] - anchors[i][0]));
|
||||
*g = (uint8_t)(anchors[i][1] + t * (anchors[i + 1][1] - anchors[i][1]));
|
||||
*b = (uint8_t)(anchors[i][2] + t * (anchors[i + 1][2] - anchors[i][2]));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* FFC scheduler (official demo cadence) */
|
||||
|
||||
void mag160c_ffc_scheduler_init(mag160c_ffc_scheduler_t *s, uint32_t period,
|
||||
uint32_t gap) {
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->period = period ? period : 400;
|
||||
s->gap = gap ? gap : 9;
|
||||
}
|
||||
|
||||
/* Call once per complete frame. Returns:
|
||||
* -1 : nothing to send
|
||||
* >=0: FFC param to send (0 or 1), then call again after the response. */
|
||||
int32_t mag160c_ffc_scheduler_tick(mag160c_ffc_scheduler_t *s) {
|
||||
if (s == NULL) {
|
||||
return -1;
|
||||
}
|
||||
s->frames++;
|
||||
if (!s->started) {
|
||||
/* official: FFC(1) after ~10 complete frames switches to type=0 */
|
||||
if (s->frames >= 10) {
|
||||
s->started = 1;
|
||||
s->frames = 0;
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (s->wait1 && s->frames >= s->gap) {
|
||||
s->wait1 = 0;
|
||||
s->frames = 0;
|
||||
return 1;
|
||||
}
|
||||
if (!s->wait1 && s->frames >= s->period) {
|
||||
s->wait1 = 1;
|
||||
s->frames = 0;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Force an immediate FFC pair: returns 0 (send FFC(0) now); the following
|
||||
* tick() calls will then return 1 (send FFC(1)) after `gap` frames. This
|
||||
* is the official manual-FFC behavior (FFC(0) then 9 frames later FFC(1)). */
|
||||
int32_t mag160c_ffc_scheduler_trigger(mag160c_ffc_scheduler_t *s) {
|
||||
if (s == NULL) {
|
||||
return -1;
|
||||
}
|
||||
s->started = 1;
|
||||
s->wait1 = 1;
|
||||
s->frames = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* two-point temperature calibration */
|
||||
|
||||
/* counts = a * temp + b ; solve from two known points */
|
||||
mag160c_error_t mag160c_temp_calibrate_linear(double counts0, double temp0,
|
||||
double counts1, double temp1,
|
||||
double *out_a, double *out_b) {
|
||||
if (out_a == NULL || out_b == NULL) {
|
||||
mag160c_set_error("mag160c_temp_calibrate_linear: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (counts1 == counts0) {
|
||||
mag160c_set_error("mag160c_temp_calibrate_linear: identical counts");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_a = (temp1 - temp0) / (counts1 - counts0);
|
||||
*out_b = temp0 - *out_a * counts0;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
double mag160c_temp_apply_linear(double counts, double a, double b) {
|
||||
return a * counts + b;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "mag160c_internal.h"
|
||||
|
||||
#if defined(_WIN32) && !defined(__GNUC__)
|
||||
# define MAG160C_TLS __declspec(thread)
|
||||
#else
|
||||
# define MAG160C_TLS __thread
|
||||
#endif
|
||||
|
||||
static MAG160C_TLS char g_last_error[512] = {0};
|
||||
|
||||
void mag160c_set_error(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(g_last_error, sizeof(g_last_error), fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void mag160c_clear_error(void) {
|
||||
g_last_error[0] = '\0';
|
||||
}
|
||||
|
||||
const char *mag160c_last_error(void) {
|
||||
return g_last_error;
|
||||
}
|
||||
|
||||
const char *mag160c_error_name(mag160c_error_t code) {
|
||||
switch (code) {
|
||||
case MAG160C_OK: return "MAG160C_OK";
|
||||
case MAG160C_ERR_INVALID_ARGUMENT: return "MAG160C_ERR_INVALID_ARGUMENT";
|
||||
case MAG160C_ERR_NO_MEMORY: return "MAG160C_ERR_NO_MEMORY";
|
||||
case MAG160C_ERR_NOT_READY: return "MAG160C_ERR_NOT_READY";
|
||||
case MAG160C_ERR_NOT_INITIALIZED: return "MAG160C_ERR_NOT_INITIALIZED";
|
||||
case MAG160C_ERR_NOT_OPEN: return "MAG160C_ERR_NOT_OPEN";
|
||||
case MAG160C_ERR_NOT_SUPPORTED: return "MAG160C_ERR_NOT_SUPPORTED";
|
||||
case MAG160C_ERR_FAILED: return "MAG160C_ERR_FAILED";
|
||||
case MAG160C_ERR_USB: return "MAG160C_ERR_USB";
|
||||
case MAG160C_ERR_TIMEOUT: return "MAG160C_ERR_TIMEOUT";
|
||||
case MAG160C_ERR_CHECKSUM: return "MAG160C_ERR_CHECKSUM";
|
||||
case MAG160C_ERR_BAD_FRAME: return "MAG160C_ERR_BAD_FRAME";
|
||||
case MAG160C_ERR_INTERNAL: return "MAG160C_ERR_INTERNAL";
|
||||
default: return "MAG160C_ERR_UNKNOWN";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Frame stream parsing, recovered from libmagcore.so.2.1.1 reader thread
|
||||
* (0x170a4). The camera streams frames on EP IN 0x81:
|
||||
*
|
||||
* +0x00 u32 0x1BB1B11B leading marker
|
||||
* +0x04 u32 frame counter
|
||||
* +0x08 u32 data length (bytes of pixel data)
|
||||
* +0x0c u32 frame type (0 = response, 1 = raw)
|
||||
* +0x10 u32 period/shutter
|
||||
* +0x14..+0x1b reserved
|
||||
* +0x1c ... pixel data (uint16 LE)
|
||||
* +0x1c+len u32 0x1BB1B11C trailing marker
|
||||
* total frame = 0x38 + len
|
||||
*
|
||||
* The SDK keeps a ring buffer of 2*len+0x470 bytes, appends every bulk read,
|
||||
* searches 4-byte aligned for the marker, and validates length + trailing
|
||||
* marker before handing the frame to the dispatcher.
|
||||
*/
|
||||
|
||||
#include "mag160c_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint32_t rd32(const uint8_t *p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_frame_parse(const uint8_t *data, size_t size,
|
||||
mag160c_frame_header_t *out_hdr,
|
||||
const uint16_t **out_pixels) {
|
||||
if (data == NULL || out_hdr == NULL || out_pixels == NULL) {
|
||||
mag160c_set_error("mag160c_frame_parse: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (size < MAG160C_FRAME_DATA_OFFSET + 4) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
if (rd32(data) != MAG160C_FRAME_MARKER) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
h.marker = MAG160C_FRAME_MARKER;
|
||||
h.frame_counter = rd32(data + 4);
|
||||
h.data_length = rd32(data + 8);
|
||||
h.frame_type = rd32(data + 12);
|
||||
h.period_shutter = rd32(data + 16);
|
||||
h.trailing_marker = 0;
|
||||
|
||||
if (h.frame_type > 1) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
if (size < (size_t)MAG160C_FRAME_OVERHEAD + h.data_length) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
const size_t trailing = (size_t)MAG160C_FRAME_DATA_OFFSET + h.data_length;
|
||||
h.trailing_marker = rd32(data + trailing);
|
||||
if (h.trailing_marker != MAG160C_FRAME_TRAILING_MARKER) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
|
||||
*out_hdr = h;
|
||||
*out_pixels = (const uint16_t *)(data + MAG160C_FRAME_DATA_OFFSET);
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Streaming assembler matching the vendor reader thread. Feed every bulk
|
||||
* read from EP 0x81 into mag160c_frame_stream_push; when a complete valid
|
||||
* frame is buffered it is copied to out_frame (capacity out_cap) and the
|
||||
* function returns MAG160C_OK with *out_len set. Returns MAG160C_OK with
|
||||
* *out_len == 0 when more data is needed.
|
||||
*/
|
||||
|
||||
void mag160c_frame_stream_init(mag160c_frame_stream_t *s, size_t max_frame_len) {
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
s->buf = NULL;
|
||||
s->cap = max_frame_len * 2 + 0x470;
|
||||
s->len = 0;
|
||||
s->aligned = 0;
|
||||
s->buf = (uint8_t *)malloc(s->cap);
|
||||
}
|
||||
|
||||
void mag160c_frame_stream_destroy(mag160c_frame_stream_t *s) {
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
free(s->buf);
|
||||
s->buf = NULL;
|
||||
s->len = 0;
|
||||
s->cap = 0;
|
||||
}
|
||||
|
||||
static void align_to_marker(mag160c_frame_stream_t *s) {
|
||||
/* vendor: scan 4-byte aligned for 0x1bb1b11b, memmove tail to base */
|
||||
size_t i = 0;
|
||||
for (; i + 4 <= s->len; i += 4) {
|
||||
if (rd32(s->buf + i) == MAG160C_FRAME_MARKER) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == s->len) {
|
||||
s->len = 0;
|
||||
return;
|
||||
}
|
||||
if (i != 0) {
|
||||
memmove(s->buf, s->buf + i, s->len - i);
|
||||
s->len -= i;
|
||||
}
|
||||
s->aligned = 1;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_frame_stream_push(mag160c_frame_stream_t *s, const uint8_t *data,
|
||||
size_t size, uint8_t *out_frame, size_t out_cap,
|
||||
size_t *out_len) {
|
||||
if (out_len != NULL) {
|
||||
*out_len = 0;
|
||||
}
|
||||
if (s == NULL || s->buf == NULL || data == NULL || out_frame == NULL ||
|
||||
out_len == NULL) {
|
||||
mag160c_set_error("mag160c_frame_stream_push: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (s->cap == 0 || size > s->cap - s->len) {
|
||||
s->len = 0;
|
||||
s->aligned = 0;
|
||||
}
|
||||
memcpy(s->buf + s->len, data, size);
|
||||
s->len += size;
|
||||
|
||||
if (!s->aligned) {
|
||||
align_to_marker(s);
|
||||
if (!s->aligned) {
|
||||
return MAG160C_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* complete frame requires 0x38 + data_length bytes */
|
||||
if (s->len < MAG160C_FRAME_OVERHEAD) {
|
||||
return MAG160C_OK;
|
||||
}
|
||||
const uint32_t len = rd32(s->buf + 8);
|
||||
if (len > s->cap - MAG160C_FRAME_OVERHEAD) {
|
||||
s->len = 0; /* bogus length; resync */
|
||||
return MAG160C_OK;
|
||||
}
|
||||
if (s->len < (size_t)MAG160C_FRAME_OVERHEAD + len) {
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels;
|
||||
mag160c_error_t rc = mag160c_frame_parse(s->buf, s->len, &h, &pixels);
|
||||
if (rc != MAG160C_OK) {
|
||||
/* leading marker present but trailer/length invalid: resync */
|
||||
s->len = 0;
|
||||
s->aligned = 0;
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
const size_t total = (size_t)MAG160C_FRAME_OVERHEAD + h.data_length;
|
||||
if (total > out_cap) {
|
||||
mag160c_set_error("mag160c_frame_stream_push: output buffer too small");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
memcpy(out_frame, s->buf, total);
|
||||
*out_len = total;
|
||||
|
||||
/* consume frame (vendor: advance and re-scan) */
|
||||
memmove(s->buf, s->buf + total, s->len - total);
|
||||
s->len -= total;
|
||||
if (s->len == 0) {
|
||||
s->aligned = 0;
|
||||
}
|
||||
return MAG160C_OK;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef MAG160C_INTERNAL_H
|
||||
#define MAG160C_INTERNAL_H
|
||||
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void mag160c_set_error(const char *fmt, ...);
|
||||
void mag160c_clear_error(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,598 @@
|
||||
/*
|
||||
* IR camera session, recovered from libmagcore.so.2.1.1:
|
||||
*
|
||||
* Link: enumerate VID 0x833C -> open -> set_auto_detach(1) ->
|
||||
* set_configuration(2) -> claim_interface(0)
|
||||
* Commands: write 8-byte {u32 magic, u32 param} on EP 0x03, then read up to
|
||||
* 0x1000 bytes on EP 0x82 with 2000 ms timeout; success requires > 3
|
||||
* bytes. Response magic 0x5BB5B55B carries the camera info block.
|
||||
* Start: allocate 2*len+0x470 read buffer, create reader thread (EP 0x81)
|
||||
* + dispatcher thread, sleep 50 ms, send 0x6BB6B673.
|
||||
* Stop: send 0x6BB6B674, join threads.
|
||||
* FFC: 0x6BB6B672 (+param).
|
||||
*
|
||||
* 2026-08-10 hardware: commands are 4-byte (magic only); only FFC is 8-byte
|
||||
* {magic, param}. FFC(1) switches the stream to type=0 frames, FFC(0) back
|
||||
* to type=1. The official demo alternates FFC(0)/FFC(1) (FFC(1) exactly 9
|
||||
* frames after FFC(0)) to keep the type=0 stream alive; verified 1400+
|
||||
* type=0 frames at 15 fps with zero stalls using the mag160c_ffc_scheduler.
|
||||
*/
|
||||
#include "mag160c_internal.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
#include <libusb.h>
|
||||
#endif
|
||||
|
||||
#if MAG160C_HAS_THREADS
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
struct mag160c_ctx_t {
|
||||
int dummy;
|
||||
};
|
||||
|
||||
struct mag160c_ir_t {
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
libusb_context *usb; /* kept alive for the lifetime of the handle */
|
||||
libusb_device_handle *handle;
|
||||
#endif
|
||||
uint32_t pid;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t frame_len; /* expected frame data length (bytes) */
|
||||
uint32_t ffc_mode;
|
||||
uint32_t info[8]; /* 0x5bb5b55b block cache */
|
||||
int linked;
|
||||
int running;
|
||||
int prepared;
|
||||
|
||||
#if MAG160C_HAS_THREADS
|
||||
pthread_t reader_thread;
|
||||
pthread_t dispatch_thread;
|
||||
int threads_up;
|
||||
#endif
|
||||
|
||||
mag160c_frame_cb_t frame_cb;
|
||||
void *frame_cb_user;
|
||||
|
||||
/* optional FFC scheduler: replicated official cadence keeps the type=0
|
||||
* stream alive (verified 1400+ frames). When set, the reader thread
|
||||
* issues FFC commands after complete frames as the scheduler dictates. */
|
||||
mag160c_ffc_scheduler_t *ffc_sched;
|
||||
int ffc_sched_owned;
|
||||
};
|
||||
|
||||
mag160c_error_t mag160c_init(mag160c_ctx_t **out_ctx) {
|
||||
if (out_ctx == NULL) {
|
||||
mag160c_set_error("mag160c_init: out_ctx null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_ctx = (mag160c_ctx_t *)calloc(1, sizeof(mag160c_ctx_t));
|
||||
if (*out_ctx == NULL) {
|
||||
mag160c_set_error("mag160c_init: allocation failed");
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
void mag160c_shutdown(mag160c_ctx_t *ctx) {
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
|
||||
/* ---- command channel: EP 0x03 write + EP 0x82 read ---------------------- */
|
||||
|
||||
static mag160c_error_t cmd_transfer(mag160c_ir_t *ir, const uint8_t *cmd, size_t cmd_len) {
|
||||
int transferred = 0;
|
||||
int rc = libusb_bulk_transfer(ir->handle, MAG160C_IR_EP_CMD_OUT,
|
||||
(uint8_t *)cmd, (int)cmd_len, &transferred, 500);
|
||||
if (rc != 0 || (size_t)transferred != cmd_len) {
|
||||
mag160c_set_error("cmd: write EP 0x03 failed (%s)", libusb_error_name(rc));
|
||||
return MAG160C_ERR_USB;
|
||||
}
|
||||
|
||||
uint8_t resp[0x1000];
|
||||
rc = libusb_bulk_transfer(ir->handle, MAG160C_IR_EP_CMD_IN, resp, sizeof(resp),
|
||||
&transferred, 2000);
|
||||
if (rc != 0) {
|
||||
mag160c_set_error("cmd: read EP 0x82 failed (%s)", libusb_error_name(rc));
|
||||
return MAG160C_ERR_USB;
|
||||
}
|
||||
if (transferred <= 3) {
|
||||
mag160c_set_error("cmd: short response (%d bytes)", transferred);
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
|
||||
const uint32_t magic = (uint32_t)resp[0] | ((uint32_t)resp[1] << 8) |
|
||||
((uint32_t)resp[2] << 16) | ((uint32_t)resp[3] << 24);
|
||||
const int payload_len = transferred - 4;
|
||||
switch (magic) {
|
||||
case MAG160C_MAG_RSP_INFO_0:
|
||||
/* 0x5bb5b55b carries the camera info block (pid, width@+0x10,
|
||||
height@+0x14); the 0x5bb5b55c block has a different layout and
|
||||
must NOT be parsed as info */
|
||||
if (payload_len >= (int)sizeof(ir->info)) {
|
||||
memcpy(ir->info, resp + 4, sizeof(ir->info));
|
||||
ir->width = ir->info[4]; /* +0x10 */
|
||||
ir->height = ir->info[5]; /* +0x14 */
|
||||
ir->frame_len = ir->width * ir->height * 2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
static mag160c_error_t send_cmd32(mag160c_ir_t *ir, uint32_t magic, uint32_t param) {
|
||||
uint8_t cmd[8];
|
||||
cmd[0] = (uint8_t)(magic);
|
||||
cmd[1] = (uint8_t)(magic >> 8);
|
||||
cmd[2] = (uint8_t)(magic >> 16);
|
||||
cmd[3] = (uint8_t)(magic >> 24);
|
||||
cmd[4] = (uint8_t)(param);
|
||||
cmd[5] = (uint8_t)(param >> 8);
|
||||
cmd[6] = (uint8_t)(param >> 16);
|
||||
cmd[7] = (uint8_t)(param >> 24);
|
||||
return cmd_transfer(ir, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
/* Verified on hardware (WinUSB, VID 0x833C PID 0x0001):
|
||||
* most commands are 4 bytes (magic only); only FFC (0x6bb6b672) carries an
|
||||
* 8-byte {magic, param} payload. The official demo sends 66b/66c/66f as
|
||||
* 4-byte packets and FFC before START, then FFC(0/1 alternating) after every
|
||||
* ~10 frames. */
|
||||
static mag160c_error_t send_cmd4(mag160c_ir_t *ir, uint32_t magic) {
|
||||
uint8_t cmd[4];
|
||||
cmd[0] = (uint8_t)(magic);
|
||||
cmd[1] = (uint8_t)(magic >> 8);
|
||||
cmd[2] = (uint8_t)(magic >> 16);
|
||||
cmd[3] = (uint8_t)(magic >> 24);
|
||||
return cmd_transfer(ir, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
/* ---- frame reader thread (recovered 0x170a4) ----------------------------- */
|
||||
|
||||
typedef struct reader_ctx_t {
|
||||
mag160c_ir_t *ir;
|
||||
uint8_t *buf; /* 2*frame_len + 0x470 */
|
||||
size_t buf_cap;
|
||||
size_t buf_len;
|
||||
size_t aligned;
|
||||
} reader_ctx_t;
|
||||
|
||||
static void reader_align(reader_ctx_t *r) {
|
||||
size_t i = 0;
|
||||
for (; i + 4 <= r->buf_len; i += 4) {
|
||||
if (r->buf[i] == 0x1b && r->buf[i + 1] == 0xb1 &&
|
||||
r->buf[i + 2] == 0xb1 && r->buf[i + 3] == 0x1b) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == r->buf_len) {
|
||||
r->buf_len = 0;
|
||||
return;
|
||||
}
|
||||
if (i != 0) {
|
||||
memmove(r->buf, r->buf + i, r->buf_len - i);
|
||||
r->buf_len -= i;
|
||||
}
|
||||
r->aligned = 1;
|
||||
}
|
||||
|
||||
static void *reader_thread_fn(void *arg) {
|
||||
reader_ctx_t *r = (reader_ctx_t *)arg;
|
||||
mag160c_ir_t *ir = r->ir;
|
||||
uint8_t tmp[0x8000];
|
||||
|
||||
while (ir->running) {
|
||||
int transferred = 0;
|
||||
int rc = libusb_bulk_transfer(ir->handle, MAG160C_IR_EP_STREAM_IN, tmp,
|
||||
(int)sizeof(tmp), &transferred, 500);
|
||||
if (rc != 0 || transferred <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ((size_t)transferred > r->buf_cap - r->buf_len) {
|
||||
r->buf_len = 0; /* buffer full: drop and resync */
|
||||
r->aligned = 0;
|
||||
}
|
||||
memcpy(r->buf + r->buf_len, tmp, (size_t)transferred);
|
||||
r->buf_len += (size_t)transferred;
|
||||
|
||||
if (!r->aligned) {
|
||||
reader_align(r);
|
||||
}
|
||||
|
||||
/* complete frame = 0x38 + data_length */
|
||||
while (r->aligned && r->buf_len >= MAG160C_FRAME_OVERHEAD) {
|
||||
const uint32_t len = (uint32_t)r->buf[8] | ((uint32_t)r->buf[9] << 8) |
|
||||
((uint32_t)r->buf[10] << 16) | ((uint32_t)r->buf[11] << 24);
|
||||
const size_t total = (size_t)MAG160C_FRAME_OVERHEAD + len;
|
||||
if (len > r->buf_cap) {
|
||||
r->buf_len = 0;
|
||||
r->aligned = 0;
|
||||
break;
|
||||
}
|
||||
if (r->buf_len < total) {
|
||||
break;
|
||||
}
|
||||
const size_t trail = (size_t)MAG160C_FRAME_DATA_OFFSET + len;
|
||||
if (r->buf[trail] == 0x1c && r->buf[trail + 1] == 0xb1 &&
|
||||
r->buf[trail + 2] == 0xb1 && r->buf[trail + 3] == 0x1b) {
|
||||
/* valid frame: notify dispatcher via callback */
|
||||
if (ir->frame_cb != NULL) {
|
||||
const uint32_t idx = (uint32_t)r->buf[4] | ((uint32_t)r->buf[5] << 8) |
|
||||
((uint32_t)r->buf[6] << 16) |
|
||||
((uint32_t)r->buf[7] << 24);
|
||||
ir->frame_cb(idx, r->buf, total, ir->frame_cb_user);
|
||||
}
|
||||
/* official FFC cadence: tick after every complete frame and
|
||||
* send the requested FFC command (0/1) immediately */
|
||||
if (ir->ffc_sched != NULL) {
|
||||
const int32_t param = mag160c_ffc_scheduler_tick(ir->ffc_sched);
|
||||
if (param >= 0) {
|
||||
(void)send_cmd32(ir, MAG160C_MAG_CMD_FFC, (uint32_t)param);
|
||||
}
|
||||
}
|
||||
memmove(r->buf, r->buf + total, r->buf_len - total);
|
||||
r->buf_len -= total;
|
||||
if (r->buf_len == 0) {
|
||||
r->aligned = 0;
|
||||
}
|
||||
} else {
|
||||
r->buf_len = 0;
|
||||
r->aligned = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* MAG160C_HAS_LIBUSB */
|
||||
|
||||
mag160c_error_t mag160c_ir_open(mag160c_ctx_t *ctx, mag160c_ir_t **out_ir) {
|
||||
if (ctx == NULL || out_ir == NULL) {
|
||||
mag160c_set_error("mag160c_ir_open: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_ir = NULL;
|
||||
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
mag160c_set_error("mag160c_ir_open: built without libusb support");
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
mag160c_ir_t *ir = (mag160c_ir_t *)calloc(1, sizeof(mag160c_ir_t));
|
||||
if (ir == NULL) {
|
||||
mag160c_set_error("mag160c_ir_open: allocation failed");
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
ir->pid = 0;
|
||||
ir->linked = 0;
|
||||
|
||||
libusb_context *usb = NULL;
|
||||
int rc = libusb_init(&usb);
|
||||
if (rc != 0) {
|
||||
mag160c_set_error("mag160c_ir_open: libusb_init failed");
|
||||
free(ir);
|
||||
return MAG160C_ERR_USB;
|
||||
}
|
||||
|
||||
libusb_device **list = NULL;
|
||||
const ssize_t count = libusb_get_device_list(usb, &list);
|
||||
libusb_device_handle *handle = NULL;
|
||||
for (ssize_t i = 0; i < count && handle == NULL; ++i) {
|
||||
struct libusb_device_descriptor desc;
|
||||
if (libusb_get_device_descriptor(list[i], &desc) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (desc.idVendor != MAG160C_IR_VENDOR_ID) {
|
||||
continue;
|
||||
}
|
||||
if (libusb_open(list[i], &handle) != 0) {
|
||||
continue;
|
||||
}
|
||||
libusb_set_auto_detach_kernel_driver(handle, 1);
|
||||
/* vendor tries config 2 first, then falls back to config 1
|
||||
(libmagcore 0x1856a -> 0x1865b); this unit has only config 1 */
|
||||
if (libusb_set_configuration(handle, 2) != 0) {
|
||||
if (libusb_set_configuration(handle, 1) != 0) {
|
||||
libusb_close(handle);
|
||||
handle = NULL;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (libusb_claim_interface(handle, MAG160C_IR_INTERFACE_NUMBER) != 0) {
|
||||
libusb_close(handle);
|
||||
handle = NULL;
|
||||
continue;
|
||||
}
|
||||
ir->pid = desc.idProduct;
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
|
||||
if (handle == NULL) {
|
||||
libusb_exit(usb);
|
||||
mag160c_set_error("mag160c_ir_open: no device with VID 0x%04x",
|
||||
MAG160C_IR_VENDOR_ID);
|
||||
free(ir);
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
|
||||
ir->usb = usb; /* keep context alive; released in mag160c_ir_close */
|
||||
ir->handle = handle;
|
||||
ir->linked = 1;
|
||||
|
||||
/* query camera info: verified on hardware the sequence is
|
||||
0x6bb6b66b (4B), 0x6bb6b66c (4B), 0x6bb6b66f (4B) */
|
||||
(void)send_cmd4(ir, MAG160C_MAG_CMD_PREPARE1);
|
||||
(void)send_cmd4(ir, MAG160C_MAG_CMD_PREPARE2);
|
||||
(void)send_cmd4(ir, MAG160C_MAG_CMD_GET_INFO);
|
||||
|
||||
*out_ir = ir;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mag160c_ir_close(mag160c_ir_t *ir) {
|
||||
if (ir == NULL) {
|
||||
return;
|
||||
}
|
||||
if (ir->running) {
|
||||
(void)mag160c_ir_stop(ir);
|
||||
}
|
||||
#if MAG160C_HAS_LIBUSB
|
||||
if (ir->handle != NULL) {
|
||||
libusb_release_interface(ir->handle, MAG160C_IR_INTERFACE_NUMBER);
|
||||
libusb_close(ir->handle);
|
||||
ir->handle = NULL;
|
||||
}
|
||||
if (ir->usb != NULL) {
|
||||
libusb_exit(ir->usb);
|
||||
ir->usb = NULL;
|
||||
}
|
||||
#endif
|
||||
if (ir->ffc_sched_owned && ir->ffc_sched != NULL) {
|
||||
free(ir->ffc_sched);
|
||||
ir->ffc_sched = NULL;
|
||||
}
|
||||
free(ir);
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_is_linked(mag160c_ir_t *ir) {
|
||||
if (ir == NULL) {
|
||||
mag160c_set_error("mag160c_ir_is_linked: ir null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
return ir->linked ? MAG160C_OK : MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_get_info(mag160c_ir_t *ir, mag160c_ir_info_t *out_info) {
|
||||
if (ir == NULL || out_info == NULL) {
|
||||
mag160c_set_error("mag160c_ir_get_info: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
memset(out_info, 0, sizeof(*out_info));
|
||||
out_info->width = ir->width ? ir->width : 160;
|
||||
out_info->height = ir->height ? ir->height : 120;
|
||||
out_info->fpa_width = out_info->width;
|
||||
out_info->fpa_height = out_info->height;
|
||||
out_info->pid = ir->pid;
|
||||
out_info->serial_lo = ir->info[1]; /* +0x08 */
|
||||
out_info->serial_hi = ir->info[2]; /* +0x10 upper */
|
||||
out_info->max_fps = 25;
|
||||
snprintf(out_info->name, sizeof(out_info->name), "MAG-IR-0x%04x", ir->pid);
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_set_frame_callback(mag160c_ir_t *ir, mag160c_frame_cb_t cb,
|
||||
void *user) {
|
||||
if (ir == NULL) {
|
||||
mag160c_set_error("mag160c_ir_set_frame_callback: ir null");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
ir->frame_cb = cb;
|
||||
ir->frame_cb_user = user;
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_prepare(mag160c_ir_t *ir) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_prepare: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
(void)ir;
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
mag160c_error_t e = send_cmd4(ir, MAG160C_MAG_CMD_PREPARE1);
|
||||
if (e != MAG160C_OK) {
|
||||
return e;
|
||||
}
|
||||
e = send_cmd4(ir, MAG160C_MAG_CMD_PREPARE2);
|
||||
if (e != MAG160C_OK) {
|
||||
return e;
|
||||
}
|
||||
ir->prepared = 1;
|
||||
return MAG160C_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_start(mag160c_ir_t *ir) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_start: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
if (ir->running) {
|
||||
mag160c_set_error("mag160c_ir_start: already running");
|
||||
return MAG160C_ERR_NOT_READY;
|
||||
}
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
(void)ir;
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#elif !MAG160C_HAS_THREADS
|
||||
(void)ir;
|
||||
mag160c_set_error("mag160c_ir_start: built without thread support");
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
if (ir->frame_len == 0) {
|
||||
ir->frame_len = 160 * 120 * 2; /* 38400 default */
|
||||
}
|
||||
reader_ctx_t *r = (reader_ctx_t *)calloc(1, sizeof(reader_ctx_t));
|
||||
if (r == NULL) {
|
||||
mag160c_set_error("mag160c_ir_start: allocation failed");
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
r->ir = ir;
|
||||
r->buf_cap = ir->frame_len * 2 + 0x470;
|
||||
r->buf = (uint8_t *)malloc(r->buf_cap);
|
||||
if (r->buf == NULL) {
|
||||
free(r);
|
||||
mag160c_set_error("mag160c_ir_start: allocation failed");
|
||||
return MAG160C_ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
ir->running = 1;
|
||||
if (pthread_create(&ir->reader_thread, NULL, reader_thread_fn, r) != 0) {
|
||||
ir->running = 0;
|
||||
free(r->buf);
|
||||
free(r);
|
||||
mag160c_set_error("mag160c_ir_start: pthread_create failed");
|
||||
return MAG160C_ERR_INTERNAL;
|
||||
}
|
||||
ir->threads_up = 1;
|
||||
|
||||
/* vendor sleeps 50 ms before issuing START */
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 50 * 1000 * 1000;
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
/* verified on hardware: FFC(0) twice before START switches the unit into
|
||||
the live imaging mode (type=0 frames); the official demo does this */
|
||||
(void)send_cmd32(ir, MAG160C_MAG_CMD_FFC, 0);
|
||||
(void)send_cmd32(ir, MAG160C_MAG_CMD_FFC, 0);
|
||||
ts.tv_nsec = 300 * 1000 * 1000;
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
mag160c_error_t e = send_cmd4(ir, MAG160C_MAG_CMD_START);
|
||||
if (e != MAG160C_OK) {
|
||||
ir->running = 0;
|
||||
pthread_join(ir->reader_thread, NULL);
|
||||
ir->threads_up = 0;
|
||||
free(r->buf);
|
||||
free(r);
|
||||
return e;
|
||||
}
|
||||
return MAG160C_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_stop(mag160c_ir_t *ir) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_stop: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
(void)ir;
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
if (ir->running) {
|
||||
ir->running = 0;
|
||||
(void)send_cmd4(ir, MAG160C_MAG_CMD_STOP);
|
||||
#if MAG160C_HAS_THREADS
|
||||
if (ir->threads_up) {
|
||||
pthread_join(ir->reader_thread, NULL);
|
||||
ir->threads_up = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return MAG160C_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_trigger_ffc(mag160c_ir_t *ir, uint32_t param) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_trigger_ffc: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
(void)ir;
|
||||
(void)param;
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
return send_cmd32(ir, MAG160C_MAG_CMD_FFC, param);
|
||||
#endif
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_set_ffc_mode(mag160c_ir_t *ir, uint32_t mode) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_set_ffc_mode: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
ir->ffc_mode = mode; /* ffc mode stored locally; affects start behavior */
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
/* Attach an FFC scheduler. The scheduler is ticked after every complete
|
||||
* frame in the reader thread; when it returns 0/1 the corresponding FFC
|
||||
* command is sent (official demo cadence keeps the type=0 stream alive).
|
||||
* If sched is NULL, the scheduler is disabled. A stack-allocated scheduler
|
||||
* must outlive the stream; pass own=1 to let the IR session free it. */
|
||||
mag160c_error_t mag160c_ir_set_ffc_scheduler(mag160c_ir_t *ir,
|
||||
mag160c_ffc_scheduler_t *sched,
|
||||
int own) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_set_ffc_scheduler: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
if (ir->ffc_sched_owned && ir->ffc_sched != NULL) {
|
||||
free(ir->ffc_sched);
|
||||
}
|
||||
ir->ffc_sched = sched;
|
||||
ir->ffc_sched_owned = own;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_reset(mag160c_ir_t *ir) {
|
||||
if (ir == NULL || !ir->linked) {
|
||||
mag160c_set_error("mag160c_ir_reset: not linked");
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
(void)ir;
|
||||
return MAG160C_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
return send_cmd32(ir, MAG160C_MAG_CMD_FFC, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_ir_read_temperature(mag160c_ir_t *ir, uint32_t x, uint32_t y,
|
||||
int32_t *out_temp) {
|
||||
(void)x;
|
||||
(void)y;
|
||||
if (ir == NULL || out_temp == NULL) {
|
||||
mag160c_set_error("mag160c_ir_read_temperature: null argument");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_temp = 0;
|
||||
if (!ir->linked) {
|
||||
return MAG160C_ERR_NOT_OPEN;
|
||||
}
|
||||
/* requires the host pipeline; temperature map is maintained by the
|
||||
* frame callback (mag160c_temp_calibrate). Without a live frame the
|
||||
* vendor SDK returns 0xe4ae0001. */
|
||||
mag160c_set_error("mag160c_ir_read_temperature: no temperature map available "
|
||||
"(feed frames via mag160c_temp_calibrate first)");
|
||||
return MAG160C_ERR_NOT_READY;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Official MAG160C gray LUT: 1024 entries read from CoreSDKLib dev+0x4284c.
|
||||
* gray = LUT[((nuc - lo) * 0xFFC00000 / (hi - lo)) >> 22], 32-bit truncating math.
|
||||
* lo = max(min, mean - 312); hi = min(max, mean + 312) (X=624, window on NUC counts). */
|
||||
static const unsigned char mag160c_official_lut1024[1024] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,
|
||||
2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5,
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8,
|
||||
8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11,
|
||||
11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 14, 14,
|
||||
14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17,
|
||||
17, 17, 17, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 20, 20, 20,
|
||||
20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23,
|
||||
24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 27,
|
||||
27, 27, 27, 27, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 30, 30,
|
||||
30, 30, 30, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 33, 33, 33,
|
||||
33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36,
|
||||
37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 40,
|
||||
40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 43, 43,
|
||||
43, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46,
|
||||
46, 47, 47, 47, 47, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 50,
|
||||
50, 50, 50, 51, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53,
|
||||
53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 57,
|
||||
57, 57, 57, 58, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, 60,
|
||||
60, 61, 61, 61, 61, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 64,
|
||||
64, 64, 64, 65, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67,
|
||||
67, 68, 68, 68, 68, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 71,
|
||||
71, 71, 71, 72, 72, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74,
|
||||
74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 77, 78, 78,
|
||||
78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 81, 82,
|
||||
82, 82, 82, 83, 83, 83, 83, 84, 84, 84, 84, 84, 85, 85, 85, 85,
|
||||
86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 88, 89, 89, 89,
|
||||
89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92, 92, 92, 93, 93,
|
||||
93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 96, 97,
|
||||
97, 97, 97, 98, 98, 98, 98, 99, 99, 99, 99, 99,100,100,100,100,
|
||||
101,101,101,101,102,102,102,102,103,103,103,103,103,104,104,104,
|
||||
104,105,105,105,105,106,106,106,106,107,107,107,107,108,108,108,
|
||||
108,109,109,109,109,110,110,110,110,111,111,111,111,112,112,112,
|
||||
112,113,113,113,113,114,114,114,114,115,115,115,115,116,116,116,
|
||||
116,117,117,117,117,118,118,118,118,119,119,119,119,120,120,120,
|
||||
120,121,121,121,121,122,122,122,122,123,123,123,123,124,124,124,
|
||||
124,125,125,125,125,126,126,126,126,127,127,127,127,128,128,128,
|
||||
128,129,129,129,129,130,130,130,130,131,131,131,131,132,132,132,
|
||||
132,133,133,133,133,134,134,134,134,135,135,135,135,136,136,136,
|
||||
136,137,137,137,137,138,138,138,138,139,139,139,139,140,140,140,
|
||||
140,141,141,141,141,142,142,142,142,143,143,143,143,144,144,144,
|
||||
145,145,145,145,146,146,146,146,147,147,147,147,148,148,148,148,
|
||||
149,149,149,150,150,150,150,151,151,151,151,152,152,152,152,153,
|
||||
153,153,154,154,154,154,155,155,155,155,156,156,156,156,157,157,
|
||||
157,158,158,158,158,159,159,159,159,160,160,160,160,161,161,161,
|
||||
162,162,162,162,163,163,163,163,164,164,164,164,165,165,165,165,
|
||||
166,166,166,167,167,167,167,168,168,168,168,169,169,169,169,170,
|
||||
170,170,171,171,171,171,172,172,172,172,173,173,173,173,174,174,
|
||||
174,175,175,175,175,176,176,176,176,177,177,177,177,178,178,178,
|
||||
179,179,179,179,180,180,180,181,181,181,181,182,182,182,183,183,
|
||||
183,183,184,184,184,185,185,185,185,186,186,186,186,187,187,187,
|
||||
188,188,188,188,189,189,189,190,190,190,190,191,191,191,192,192,
|
||||
192,192,193,193,193,194,194,194,194,195,195,195,195,196,196,196,
|
||||
197,197,197,197,198,198,198,199,199,199,199,200,200,200,201,201,
|
||||
201,201,202,202,202,203,203,203,203,204,204,204,204,205,205,205,
|
||||
};
|
||||
@@ -0,0 +1,266 @@
|
||||
/* Official MAG160C display palette (256 RGB), recovered from vendor
|
||||
* render frames. Background (gray 0-14) is dark red (74,0,0);
|
||||
* ramp: red -> magenta -> purple -> blue-violet. */
|
||||
#ifndef MAG160C_OFFICIAL_PALETTE_H
|
||||
#define MAG160C_OFFICIAL_PALETTE_H
|
||||
|
||||
static const unsigned char mag160c_official_palette[256][3] = {
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 74, 0, 0},
|
||||
{ 42, 0, 0},
|
||||
{ 47, 0, 0},
|
||||
{ 52, 0, 0},
|
||||
{ 55, 0, 0},
|
||||
{ 57, 0, 0},
|
||||
{ 59, 0, 0},
|
||||
{ 62, 0, 0},
|
||||
{ 64, 0, 0},
|
||||
{ 67, 0, 0},
|
||||
{ 70, 0, 0},
|
||||
{ 72, 0, 0},
|
||||
{ 75, 0, 0},
|
||||
{ 77, 0, 0},
|
||||
{ 80, 0, 0},
|
||||
{ 82, 0, 0},
|
||||
{ 85, 0, 0},
|
||||
{ 88, 0, 0},
|
||||
{ 90, 0, 0},
|
||||
{ 92, 0, 0},
|
||||
{ 95, 0, 0},
|
||||
{ 98, 0, 0},
|
||||
{100, 0, 0},
|
||||
{103, 0, 0},
|
||||
{105, 0, 0},
|
||||
{108, 0, 0},
|
||||
{110, 0, 0},
|
||||
{113, 0, 0},
|
||||
{115, 0, 0},
|
||||
{117, 0, 0},
|
||||
{117, 0, 1},
|
||||
{117, 0, 2},
|
||||
{117, 0, 4},
|
||||
{118, 0, 5},
|
||||
{118, 0, 6},
|
||||
{118, 0, 7},
|
||||
{118, 0, 8},
|
||||
{119, 0, 10},
|
||||
{119, 0, 11},
|
||||
{119, 0, 13},
|
||||
{119, 0, 14},
|
||||
{120, 0, 15},
|
||||
{120, 0, 16},
|
||||
{120, 0, 17},
|
||||
{120, 0, 19},
|
||||
{120, 0, 20},
|
||||
{121, 0, 21},
|
||||
{121, 0, 22},
|
||||
{121, 0, 23},
|
||||
{121, 0, 25},
|
||||
{122, 0, 26},
|
||||
{122, 0, 28},
|
||||
{122, 0, 29},
|
||||
{122, 0, 30},
|
||||
{123, 0, 31},
|
||||
{123, 0, 32},
|
||||
{123, 0, 34},
|
||||
{123, 0, 35},
|
||||
{124, 0, 37},
|
||||
{124, 0, 38},
|
||||
{124, 0, 39},
|
||||
{124, 0, 40},
|
||||
{124, 0, 41},
|
||||
{125, 0, 43},
|
||||
{125, 0, 44},
|
||||
{125, 0, 45},
|
||||
{125, 0, 46},
|
||||
{126, 0, 48},
|
||||
{126, 0, 49},
|
||||
{126, 0, 50},
|
||||
{126, 0, 51},
|
||||
{127, 0, 52},
|
||||
{127, 0, 54},
|
||||
{127, 0, 55},
|
||||
{127, 0, 57},
|
||||
{128, 0, 58},
|
||||
{128, 0, 59},
|
||||
{128, 0, 60},
|
||||
{128, 0, 61},
|
||||
{129, 0, 63},
|
||||
{129, 0, 64},
|
||||
{130, 0, 66},
|
||||
{130, 0, 66},
|
||||
{130, 0, 67},
|
||||
{130, 0, 69},
|
||||
{130, 0, 70},
|
||||
{131, 0, 72},
|
||||
{131, 0, 73},
|
||||
{131, 0, 74},
|
||||
{131, 0, 75},
|
||||
{132, 0, 76},
|
||||
{132, 0, 78},
|
||||
{132, 0, 79},
|
||||
{132, 0, 81},
|
||||
{133, 0, 82},
|
||||
{133, 0, 83},
|
||||
{133, 0, 84},
|
||||
{133, 0, 85},
|
||||
{134, 0, 87},
|
||||
{134, 0, 88},
|
||||
{134, 0, 90},
|
||||
{134, 0, 91},
|
||||
{134, 0, 91},
|
||||
{135, 0, 93},
|
||||
{135, 0, 94},
|
||||
{135, 0, 95},
|
||||
{135, 0, 96},
|
||||
{136, 0, 98},
|
||||
{136, 0, 99},
|
||||
{136, 0, 100},
|
||||
{136, 0, 102},
|
||||
{137, 0, 103},
|
||||
{137, 0, 104},
|
||||
{137, 0, 105},
|
||||
{137, 0, 107},
|
||||
{138, 0, 108},
|
||||
{138, 0, 109},
|
||||
{138, 0, 111},
|
||||
{138, 0, 112},
|
||||
{139, 0, 113},
|
||||
{139, 0, 114},
|
||||
{139, 0, 116},
|
||||
{139, 0, 117},
|
||||
{139, 0, 118},
|
||||
{140, 0, 119},
|
||||
{140, 0, 120},
|
||||
{140, 0, 122},
|
||||
{140, 0, 123},
|
||||
{141, 0, 124},
|
||||
{141, 0, 126},
|
||||
{142, 0, 127},
|
||||
{142, 0, 128},
|
||||
{142, 0, 129},
|
||||
{142, 0, 131},
|
||||
{143, 0, 132},
|
||||
{143, 0, 133},
|
||||
{143, 0, 135},
|
||||
{143, 0, 136},
|
||||
{144, 0, 137},
|
||||
{144, 0, 138},
|
||||
{144, 0, 140},
|
||||
{144, 0, 141},
|
||||
{144, 0, 141},
|
||||
{145, 0, 143},
|
||||
{145, 0, 144},
|
||||
{145, 0, 146},
|
||||
{145, 0, 147},
|
||||
{146, 0, 148},
|
||||
{146, 0, 149},
|
||||
{146, 0, 150},
|
||||
{146, 0, 152},
|
||||
{147, 0, 153},
|
||||
{147, 0, 155},
|
||||
{147, 0, 156},
|
||||
{147, 0, 157},
|
||||
{148, 0, 158},
|
||||
{148, 0, 159},
|
||||
{148, 0, 161},
|
||||
{148, 0, 162},
|
||||
{149, 0, 164},
|
||||
{149, 0, 165},
|
||||
{149, 0, 166},
|
||||
{119, 25, 195},
|
||||
{ 90, 50, 224},
|
||||
{113, 30, 202},
|
||||
{143, 5, 174},
|
||||
{150, 0, 170},
|
||||
{150, 0, 171},
|
||||
{132, 15, 189},
|
||||
{106, 39, 215},
|
||||
{ 95, 48, 226},
|
||||
{ 97, 48, 225},
|
||||
{ 98, 47, 224},
|
||||
{100, 45, 222},
|
||||
{101, 44, 222},
|
||||
{100, 45, 223},
|
||||
{100, 45, 223},
|
||||
{102, 44, 222},
|
||||
{107, 39, 217},
|
||||
{129, 19, 195},
|
||||
{149, 0, 175},
|
||||
{129, 19, 196},
|
||||
{109, 38, 216},
|
||||
{107, 39, 218},
|
||||
{106, 40, 219},
|
||||
{107, 39, 218},
|
||||
{109, 37, 217},
|
||||
{124, 24, 202},
|
||||
{143, 6, 183},
|
||||
{139, 10, 188},
|
||||
{122, 26, 205},
|
||||
{113, 34, 214},
|
||||
{112, 35, 215},
|
||||
{112, 35, 215},
|
||||
{114, 33, 213},
|
||||
{116, 31, 212},
|
||||
{117, 31, 212},
|
||||
{118, 30, 211},
|
||||
{119, 29, 210},
|
||||
{120, 28, 209},
|
||||
{121, 27, 209},
|
||||
{122, 27, 209},
|
||||
{122, 26, 208},
|
||||
{123, 25, 208},
|
||||
{135, 14, 196},
|
||||
{149, 2, 182},
|
||||
{142, 9, 189},
|
||||
{129, 20, 202},
|
||||
{126, 23, 205},
|
||||
{127, 22, 205},
|
||||
{129, 20, 204},
|
||||
{131, 19, 202},
|
||||
{131, 18, 202},
|
||||
{130, 19, 203},
|
||||
{133, 16, 200},
|
||||
{144, 6, 189},
|
||||
{149, 2, 184},
|
||||
{141, 10, 193},
|
||||
{134, 16, 200},
|
||||
{135, 15, 200},
|
||||
{136, 14, 199},
|
||||
{137, 14, 199},
|
||||
{137, 13, 198},
|
||||
{138, 12, 197},
|
||||
{139, 12, 197},
|
||||
{140, 11, 196},
|
||||
{141, 10, 196},
|
||||
{142, 9, 195},
|
||||
{143, 8, 194},
|
||||
{146, 5, 191},
|
||||
{150, 1, 187},
|
||||
{151, 1, 187},
|
||||
{147, 4, 190},
|
||||
{146, 5, 192},
|
||||
{147, 5, 192},
|
||||
{148, 4, 191},
|
||||
{149, 3, 190},
|
||||
{150, 2, 189},
|
||||
{151, 1, 189},
|
||||
{152, 0, 188},
|
||||
{153, 0, 188},
|
||||
{154, 0, 188},
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,259 @@
|
||||
/* Official MAG160C palette: 256 x 4 bytes (B, G, R, 0). Extracted from CoreSDKLib dev+0xb18. */
|
||||
static const unsigned char mag160c_official_palette256[256][4] = {
|
||||
{ 0, 0, 0, 0}, /* 0 */
|
||||
{ 5, 0, 0, 0}, /* 1 */
|
||||
{ 10, 0, 0, 0}, /* 2 */
|
||||
{ 15, 0, 0, 0}, /* 3 */
|
||||
{ 21, 0, 0, 0}, /* 4 */
|
||||
{ 26, 0, 0, 0}, /* 5 */
|
||||
{ 31, 0, 0, 0}, /* 6 */
|
||||
{ 37, 0, 0, 0}, /* 7 */
|
||||
{ 42, 0, 0, 0}, /* 8 */
|
||||
{ 47, 0, 0, 0}, /* 9 */
|
||||
{ 53, 0, 0, 0}, /* 10 */
|
||||
{ 58, 0, 0, 0}, /* 11 */
|
||||
{ 63, 0, 0, 0}, /* 12 */
|
||||
{ 69, 0, 0, 0}, /* 13 */
|
||||
{ 74, 0, 0, 0}, /* 14 */
|
||||
{ 79, 0, 0, 0}, /* 15 */
|
||||
{ 85, 0, 0, 0}, /* 16 */
|
||||
{ 90, 0, 0, 0}, /* 17 */
|
||||
{ 95, 0, 0, 0}, /* 18 */
|
||||
{101, 0, 0, 0}, /* 19 */
|
||||
{106, 0, 0, 0}, /* 20 */
|
||||
{111, 0, 0, 0}, /* 21 */
|
||||
{117, 0, 0, 0}, /* 22 */
|
||||
{117, 0, 2, 0}, /* 23 */
|
||||
{118, 0, 5, 0}, /* 24 */
|
||||
{118, 0, 7, 0}, /* 25 */
|
||||
{119, 0, 10, 0}, /* 26 */
|
||||
{119, 0, 13, 0}, /* 27 */
|
||||
{120, 0, 15, 0}, /* 28 */
|
||||
{120, 0, 18, 0}, /* 29 */
|
||||
{121, 0, 21, 0}, /* 30 */
|
||||
{121, 0, 23, 0}, /* 31 */
|
||||
{122, 0, 26, 0}, /* 32 */
|
||||
{122, 0, 29, 0}, /* 33 */
|
||||
{123, 0, 31, 0}, /* 34 */
|
||||
{123, 0, 34, 0}, /* 35 */
|
||||
{124, 0, 37, 0}, /* 36 */
|
||||
{124, 0, 39, 0}, /* 37 */
|
||||
{125, 0, 42, 0}, /* 38 */
|
||||
{125, 0, 45, 0}, /* 39 */
|
||||
{126, 0, 47, 0}, /* 40 */
|
||||
{126, 0, 50, 0}, /* 41 */
|
||||
{127, 0, 52, 0}, /* 42 */
|
||||
{127, 0, 55, 0}, /* 43 */
|
||||
{128, 0, 58, 0}, /* 44 */
|
||||
{128, 0, 60, 0}, /* 45 */
|
||||
{129, 0, 63, 0}, /* 46 */
|
||||
{130, 0, 66, 0}, /* 47 */
|
||||
{130, 0, 68, 0}, /* 48 */
|
||||
{131, 0, 71, 0}, /* 49 */
|
||||
{131, 0, 74, 0}, /* 50 */
|
||||
{132, 0, 76, 0}, /* 51 */
|
||||
{132, 0, 79, 0}, /* 52 */
|
||||
{133, 0, 82, 0}, /* 53 */
|
||||
{133, 0, 84, 0}, /* 54 */
|
||||
{134, 0, 87, 0}, /* 55 */
|
||||
{134, 0, 90, 0}, /* 56 */
|
||||
{135, 0, 92, 0}, /* 57 */
|
||||
{135, 0, 95, 0}, /* 58 */
|
||||
{136, 0, 97, 0}, /* 59 */
|
||||
{136, 0, 100, 0}, /* 60 */
|
||||
{137, 0, 103, 0}, /* 61 */
|
||||
{137, 0, 105, 0}, /* 62 */
|
||||
{138, 0, 108, 0}, /* 63 */
|
||||
{138, 0, 111, 0}, /* 64 */
|
||||
{139, 0, 113, 0}, /* 65 */
|
||||
{139, 0, 116, 0}, /* 66 */
|
||||
{140, 0, 119, 0}, /* 67 */
|
||||
{140, 0, 121, 0}, /* 68 */
|
||||
{141, 0, 124, 0}, /* 69 */
|
||||
{142, 0, 127, 0}, /* 70 */
|
||||
{142, 0, 129, 0}, /* 71 */
|
||||
{143, 0, 132, 0}, /* 72 */
|
||||
{143, 0, 135, 0}, /* 73 */
|
||||
{144, 0, 137, 0}, /* 74 */
|
||||
{144, 0, 140, 0}, /* 75 */
|
||||
{145, 0, 142, 0}, /* 76 */
|
||||
{145, 0, 145, 0}, /* 77 */
|
||||
{146, 0, 148, 0}, /* 78 */
|
||||
{146, 0, 150, 0}, /* 79 */
|
||||
{147, 0, 153, 0}, /* 80 */
|
||||
{147, 0, 156, 0}, /* 81 */
|
||||
{148, 0, 158, 0}, /* 82 */
|
||||
{148, 0, 161, 0}, /* 83 */
|
||||
{149, 0, 164, 0}, /* 84 */
|
||||
{149, 0, 166, 0}, /* 85 */
|
||||
{150, 0, 169, 0}, /* 86 */
|
||||
{150, 0, 172, 0}, /* 87 */
|
||||
{151, 0, 174, 0}, /* 88 */
|
||||
{151, 0, 177, 0}, /* 89 */
|
||||
{152, 0, 180, 0}, /* 90 */
|
||||
{152, 0, 182, 0}, /* 91 */
|
||||
{153, 0, 185, 0}, /* 92 */
|
||||
{154, 0, 188, 0}, /* 93 */
|
||||
{152, 1, 189, 0}, /* 94 */
|
||||
{150, 3, 190, 0}, /* 95 */
|
||||
{148, 5, 192, 0}, /* 96 */
|
||||
{146, 6, 193, 0}, /* 97 */
|
||||
{144, 8, 194, 0}, /* 98 */
|
||||
{142, 10, 196, 0}, /* 99 */
|
||||
{140, 12, 197, 0}, /* 100 */
|
||||
{138, 13, 198, 0}, /* 101 */
|
||||
{136, 15, 200, 0}, /* 102 */
|
||||
{134, 17, 201, 0}, /* 103 */
|
||||
{132, 18, 202, 0}, /* 104 */
|
||||
{130, 20, 204, 0}, /* 105 */
|
||||
{128, 22, 205, 0}, /* 106 */
|
||||
{126, 24, 206, 0}, /* 107 */
|
||||
{124, 25, 208, 0}, /* 108 */
|
||||
{122, 27, 209, 0}, /* 109 */
|
||||
{120, 29, 210, 0}, /* 110 */
|
||||
{118, 31, 212, 0}, /* 111 */
|
||||
{116, 32, 213, 0}, /* 112 */
|
||||
{114, 34, 214, 0}, /* 113 */
|
||||
{112, 36, 216, 0}, /* 114 */
|
||||
{110, 37, 217, 0}, /* 115 */
|
||||
{108, 39, 218, 0}, /* 116 */
|
||||
{106, 41, 220, 0}, /* 117 */
|
||||
{104, 43, 221, 0}, /* 118 */
|
||||
{102, 44, 222, 0}, /* 119 */
|
||||
{100, 46, 224, 0}, /* 120 */
|
||||
{ 98, 48, 225, 0}, /* 121 */
|
||||
{ 95, 49, 227, 0}, /* 122 */
|
||||
{ 91, 51, 227, 0}, /* 123 */
|
||||
{ 87, 53, 228, 0}, /* 124 */
|
||||
{ 82, 55, 229, 0}, /* 125 */
|
||||
{ 78, 56, 230, 0}, /* 126 */
|
||||
{ 73, 58, 231, 0}, /* 127 */
|
||||
{ 69, 60, 232, 0}, /* 128 */
|
||||
{ 64, 62, 233, 0}, /* 129 */
|
||||
{ 60, 63, 234, 0}, /* 130 */
|
||||
{ 55, 65, 235, 0}, /* 131 */
|
||||
{ 51, 67, 236, 0}, /* 132 */
|
||||
{ 47, 68, 237, 0}, /* 133 */
|
||||
{ 42, 70, 238, 0}, /* 134 */
|
||||
{ 38, 72, 239, 0}, /* 135 */
|
||||
{ 33, 74, 240, 0}, /* 136 */
|
||||
{ 29, 75, 241, 0}, /* 137 */
|
||||
{ 24, 77, 241, 0}, /* 138 */
|
||||
{ 20, 79, 242, 0}, /* 139 */
|
||||
{ 15, 80, 243, 0}, /* 140 */
|
||||
{ 15, 82, 244, 0}, /* 141 */
|
||||
{ 15, 84, 245, 0}, /* 142 */
|
||||
{ 15, 86, 246, 0}, /* 143 */
|
||||
{ 15, 87, 247, 0}, /* 144 */
|
||||
{ 15, 89, 248, 0}, /* 145 */
|
||||
{ 15, 91, 249, 0}, /* 146 */
|
||||
{ 15, 93, 250, 0}, /* 147 */
|
||||
{ 15, 94, 251, 0}, /* 148 */
|
||||
{ 15, 96, 252, 0}, /* 149 */
|
||||
{ 15, 98, 253, 0}, /* 150 */
|
||||
{ 15, 99, 254, 0}, /* 151 */
|
||||
{ 15, 101, 255, 0}, /* 152 */
|
||||
{ 15, 103, 255, 0}, /* 153 */
|
||||
{ 15, 105, 255, 0}, /* 154 */
|
||||
{ 15, 106, 255, 0}, /* 155 */
|
||||
{ 15, 108, 255, 0}, /* 156 */
|
||||
{ 15, 110, 255, 0}, /* 157 */
|
||||
{ 15, 111, 255, 0}, /* 158 */
|
||||
{ 15, 113, 255, 0}, /* 159 */
|
||||
{ 15, 115, 255, 0}, /* 160 */
|
||||
{ 15, 117, 255, 0}, /* 161 */
|
||||
{ 15, 118, 255, 0}, /* 162 */
|
||||
{ 15, 120, 255, 0}, /* 163 */
|
||||
{ 15, 122, 255, 0}, /* 164 */
|
||||
{ 15, 124, 255, 0}, /* 165 */
|
||||
{ 15, 125, 255, 0}, /* 166 */
|
||||
{ 15, 127, 255, 0}, /* 167 */
|
||||
{ 15, 129, 255, 0}, /* 168 */
|
||||
{ 15, 130, 255, 0}, /* 169 */
|
||||
{ 15, 132, 255, 0}, /* 170 */
|
||||
{ 15, 134, 255, 0}, /* 171 */
|
||||
{ 15, 136, 255, 0}, /* 172 */
|
||||
{ 15, 137, 255, 0}, /* 173 */
|
||||
{ 15, 139, 255, 0}, /* 174 */
|
||||
{ 15, 141, 255, 0}, /* 175 */
|
||||
{ 15, 143, 255, 0}, /* 176 */
|
||||
{ 15, 144, 255, 0}, /* 177 */
|
||||
{ 15, 146, 255, 0}, /* 178 */
|
||||
{ 15, 148, 255, 0}, /* 179 */
|
||||
{ 15, 149, 255, 0}, /* 180 */
|
||||
{ 15, 151, 255, 0}, /* 181 */
|
||||
{ 15, 153, 255, 0}, /* 182 */
|
||||
{ 15, 155, 255, 0}, /* 183 */
|
||||
{ 15, 156, 255, 0}, /* 184 */
|
||||
{ 15, 158, 255, 0}, /* 185 */
|
||||
{ 15, 160, 255, 0}, /* 186 */
|
||||
{ 15, 161, 255, 0}, /* 187 */
|
||||
{ 15, 163, 255, 0}, /* 188 */
|
||||
{ 15, 165, 255, 0}, /* 189 */
|
||||
{ 15, 167, 255, 0}, /* 190 */
|
||||
{ 15, 168, 255, 0}, /* 191 */
|
||||
{ 15, 170, 255, 0}, /* 192 */
|
||||
{ 15, 172, 255, 0}, /* 193 */
|
||||
{ 15, 174, 255, 0}, /* 194 */
|
||||
{ 15, 175, 255, 0}, /* 195 */
|
||||
{ 15, 177, 255, 0}, /* 196 */
|
||||
{ 15, 179, 255, 0}, /* 197 */
|
||||
{ 15, 180, 255, 0}, /* 198 */
|
||||
{ 15, 182, 255, 0}, /* 199 */
|
||||
{ 15, 184, 255, 0}, /* 200 */
|
||||
{ 15, 186, 255, 0}, /* 201 */
|
||||
{ 15, 187, 255, 0}, /* 202 */
|
||||
{ 15, 189, 255, 0}, /* 203 */
|
||||
{ 15, 191, 255, 0}, /* 204 */
|
||||
{ 15, 192, 255, 0}, /* 205 */
|
||||
{ 15, 194, 255, 0}, /* 206 */
|
||||
{ 15, 196, 255, 0}, /* 207 */
|
||||
{ 15, 198, 255, 0}, /* 208 */
|
||||
{ 15, 199, 255, 0}, /* 209 */
|
||||
{ 15, 201, 255, 0}, /* 210 */
|
||||
{ 15, 203, 255, 0}, /* 211 */
|
||||
{ 15, 205, 255, 0}, /* 212 */
|
||||
{ 15, 206, 255, 0}, /* 213 */
|
||||
{ 15, 208, 255, 0}, /* 214 */
|
||||
{ 15, 210, 255, 0}, /* 215 */
|
||||
{ 15, 211, 255, 0}, /* 216 */
|
||||
{ 15, 213, 255, 0}, /* 217 */
|
||||
{ 15, 215, 255, 0}, /* 218 */
|
||||
{ 15, 217, 255, 0}, /* 219 */
|
||||
{ 15, 218, 255, 0}, /* 220 */
|
||||
{ 15, 220, 255, 0}, /* 221 */
|
||||
{ 15, 222, 255, 0}, /* 222 */
|
||||
{ 15, 223, 255, 0}, /* 223 */
|
||||
{ 22, 225, 255, 0}, /* 224 */
|
||||
{ 30, 227, 255, 0}, /* 225 */
|
||||
{ 37, 229, 255, 0}, /* 226 */
|
||||
{ 45, 230, 255, 0}, /* 227 */
|
||||
{ 52, 232, 255, 0}, /* 228 */
|
||||
{ 60, 234, 255, 0}, /* 229 */
|
||||
{ 67, 236, 255, 0}, /* 230 */
|
||||
{ 75, 237, 255, 0}, /* 231 */
|
||||
{ 82, 239, 255, 0}, /* 232 */
|
||||
{ 90, 241, 255, 0}, /* 233 */
|
||||
{ 97, 242, 255, 0}, /* 234 */
|
||||
{105, 244, 255, 0}, /* 235 */
|
||||
{112, 246, 255, 0}, /* 236 */
|
||||
{120, 248, 255, 0}, /* 237 */
|
||||
{127, 249, 255, 0}, /* 238 */
|
||||
{135, 251, 255, 0}, /* 239 */
|
||||
{142, 253, 255, 0}, /* 240 */
|
||||
{150, 255, 255, 0}, /* 241 */
|
||||
{157, 255, 255, 0}, /* 242 */
|
||||
{165, 255, 255, 0}, /* 243 */
|
||||
{172, 255, 255, 0}, /* 244 */
|
||||
{180, 255, 255, 0}, /* 245 */
|
||||
{187, 255, 255, 0}, /* 246 */
|
||||
{195, 255, 255, 0}, /* 247 */
|
||||
{202, 255, 255, 0}, /* 248 */
|
||||
{210, 255, 255, 0}, /* 249 */
|
||||
{217, 255, 255, 0}, /* 250 */
|
||||
{225, 255, 255, 0}, /* 251 */
|
||||
{232, 255, 255, 0}, /* 252 */
|
||||
{240, 255, 255, 0}, /* 253 */
|
||||
{247, 255, 255, 0}, /* 254 */
|
||||
{255, 255, 255, 0}, /* 255 */
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
/* Official MAG160C T2E table (646 int32 entries), extracted from
|
||||
* CoreSDKLib.dll .rdata 0x5cde0. temp = slope[i]*diff>>12 + (i<<12) - 0x249f0 */
|
||||
#ifndef MAG160C_OFFICIAL_T2E_H
|
||||
#define MAG160C_OFFICIAL_T2E_H
|
||||
|
||||
static const int32_t mag160c_official_t2e[646] = {
|
||||
51, 70, 94, 125, 162, 208, 264, 331,
|
||||
410, 503, 612, 737, 881, 1045, 1231, 1440,
|
||||
1675, 1937, 2227, 2547, 2899, 3285, 3705, 4162,
|
||||
4658, 5192, 5768, 6386, 7047, 7754, 8506, 9305,
|
||||
10153, 11050, 11997, 12995, 14045, 15147, 16303, 17513,
|
||||
18778, 20098, 21473, 22905, 24393, 25938, 27540, 29199,
|
||||
30916, 32690, 34522, 36412, 38360, 40366, 42430, 44552,
|
||||
46731, 48968, 51262, 53613, 56022, 58487, 61008, 63586,
|
||||
66220, 68910, 71654, 74454, 77308, 80217, 83179, 86195,
|
||||
89263, 92385, 95558, 98783, 102059, 105387, 108764, 112191,
|
||||
115668, 119194, 122768, 126391, 130060, 133777, 137541, 141350,
|
||||
145205, 149105, 153050, 157039, 161072, 165147, 169266, 173426,
|
||||
177629, 181873, 186157, 190482, 194847, 199251, 203694, 208175,
|
||||
212695, 217252, 221847, 226478, 231145, 235849, 240587, 245361,
|
||||
250169, 255012, 259888, 264797, 269740, 274715, 279722, 284761,
|
||||
289831, 294933, 300064, 305227, 310419, 315640, 320891, 326170,
|
||||
331478, 336814, 342178, 347569, 352988, 358433, 363904, 369402,
|
||||
374925, 380474, 386048, 391647, 397271, 402919, 408591, 414286,
|
||||
420005, 425747, 431512, 437300, 443109, 448941, 454795, 460670,
|
||||
466566, 472484, 478422, 484381, 490360, 496359, 502378, 508416,
|
||||
514474, 520551, 526647, 532761, 538894, 545045, 551215, 557402,
|
||||
563606, 569828, 576068, 582324, 588597, 594887, 601193, 607516,
|
||||
613854, 620209, 626579, 632965, 639366, 645782, 652213, 658660,
|
||||
665120, 671596, 678086, 684589, 691108, 697639, 704185, 710744,
|
||||
717317, 723903, 730502, 737114, 743740, 750377, 757028, 763691,
|
||||
770366, 777053, 783753, 790464, 797187, 803922, 810669, 817427,
|
||||
824196, 830977, 837768, 844571, 851384, 858209, 865043, 871889,
|
||||
878745, 885611, 892487, 899374, 906270, 913177, 920093, 927019,
|
||||
933954, 940899, 947854, 954818, 961791, 968773, 975764, 982764,
|
||||
989773, 996791, 1003818, 1010853, 1017897, 1024949, 1032009, 1039078,
|
||||
1046155, 1053240, 1060333, 1067435, 1074544, 1081661, 1088785, 1095918,
|
||||
1103058, 1110205, 1117360, 1124523, 1131692, 1138869, 1146053, 1153245,
|
||||
1160443, 1167648, 1174860, 1182080, 1189305, 1196538, 1203777, 1211023,
|
||||
1218276, 1225535, 1232800, 1240072, 1247350, 1254635, 1261925, 1269222,
|
||||
1276525, 1283834, 1291149, 1298470, 1305797, 1313129, 1320468, 1327812,
|
||||
1335162, 1342517, 1349878, 1357245, 1364617, 1371995, 1379378, 1386766,
|
||||
1394160, 1401559, 1408963, 1416372, 1423786, 1431206, 1438631, 1446060,
|
||||
1453495, 1460934, 1468379, 1475828, 1483282, 1490741, 1498204, 1505672,
|
||||
1513145, 1520622, 1528104, 1535591, 1543082, 1550578, 1558077, 1565582,
|
||||
1573090, 1580603, 1588121, 1595642, 1603168, 1610698, 1618232, 1625770,
|
||||
1633312, 1640859, 1648409, 1655963, 1663522, 1671084, 1678650, 1686220,
|
||||
1693794, 1701371, 1708953, 1716538, 1724127, 1731719, 1739315, 1746915,
|
||||
1754519, 1762126, 1769736, 1777350, 1784968, 1792589, 1800214, 1807842,
|
||||
1815473, 1823108, 1830746, 1838387, 1846032, 1853680, 1861331, 1868986,
|
||||
1876643, 1884304, 1891968, 1899635, 1907306, 1914979, 1922655, 1930335,
|
||||
1938017, 1945703, 1953391, 1961082, 1968777, 1976474, 1984174, 1991877,
|
||||
1999583, 2007292, 2015003, 2022718, 2030435, 2038155, 2045877, 2053602,
|
||||
2061330, 2069061, 2076794, 2084530, 2092269, 2100010, 2107754, 2115500,
|
||||
2123249, 2131001, 2138755, 2146511, 2154270, 2162031, 2169795, 2177562,
|
||||
2185330, 2193101, 2200875, 2208651, 2216429, 2224210, 2231993, 2239778,
|
||||
2247565, 2255355, 2263147, 2270941, 2278738, 2286537, 2294338, 2302141,
|
||||
2309946, 2317754, 2325563, 2333375, 2341189, 2349005, 2356823, 2364643,
|
||||
2372465, 2380290, 2388116, 2395944, 2403775, 2411607, 2419441, 2427278,
|
||||
2435116, 2442956, 2450798, 2458642, 2466488, 2474336, 2482186, 2490038,
|
||||
2497891, 2505747, 2513604, 2521463, 2529324, 2537187, 2545051, 2552918,
|
||||
2560786, 2568656, 2576527, 2584401, 2592276, 2600153, 2608031, 2615911,
|
||||
2623793, 2631677, 2639562, 2647449, 2655338, 2663228, 2671120, 2679014,
|
||||
2686909, 2694806, 2702704, 2710604, 2718505, 2726408, 2734313, 2742219,
|
||||
2750127, 2758036, 2765947, 2773859, 2781773, 2789688, 2797605, 2805524,
|
||||
2813443, 2821364, 2829287, 2837211, 2845137, 2853064, 2860992, 2868922,
|
||||
2876853, 2884786, 2892720, 2900655, 2908592, 2916530, 2924470, 2932411,
|
||||
2940353, 2948296, 2956241, 2964187, 2972135, 2980084, 2988034, 2995985,
|
||||
3003938, 3011892, 3019847, 3027803, 3035761, 3043720, 3051680, 3059642,
|
||||
3067605, 3075569, 3083534, 3091500, 3099468, 3107436, 3115406, 3123378,
|
||||
3131350, 3139323, 3147298, 3155274, 3163251, 3171229, 3179208, 3187189,
|
||||
3195170, 3203153, 3211137, 3219121, 3227107, 3235095, 3243083, 3251072,
|
||||
3259062, 3267054, 3275046, 3283040, 3291035, 3299030, 3307027, 3315025,
|
||||
3323024, 3331023, 3339024, 3347026, 3355029, 3363033, 3371038, 3379044,
|
||||
3387051, 3395059, 3403068, 3411078, 3419089, 3427101, 3435113, 3443127,
|
||||
3451142, 3459158, 3467174, 3475192, 3483211, 3491230, 3499250, 3507272,
|
||||
3515294, 3523317, 3531341, 3539366, 3547392, 3555419, 3563447, 3571475,
|
||||
3579505, 3587535, 3595566, 3603598, 3611631, 3619665, 3627700, 3635735,
|
||||
3643772, 3651809, 3659847, 3667886, 3675926, 3683966, 3692008, 3700050,
|
||||
3708093, 3716137, 3724182, 3732227, 3740273, 3748321, 3756368, 3764417,
|
||||
3772467, 3780517, 3788568, 3796620, 3804672, 3812726, 3820780, 3828835,
|
||||
3836891, 3844947, 3853004, 3861062, 3869121, 3877180, 3885240, 3893301,
|
||||
3901363, 3909425, 3917488, 3925552, 3933617, 3941682, 3949748, 3957814,
|
||||
3965882, 3973950, 3982019, 3990088, 3998158, 4006229, 4014301, 4022373,
|
||||
4030446, 4038519, 4046594, 4054669, 4062744, 4070820, 4078897, 4086975,
|
||||
4095053, 4103132, 4111212, 4119292, 4127373, 4135454, 4143536, 4151619,
|
||||
4159703, 4167787, 4175871, 4183957, 4192042, 4200129,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,111 @@
|
||||
/* T2E / E2TAccQ10 calibration tables recovered from libcoresdk.so (ARM64). */
|
||||
/* T2E @ 0x402010, E2TAccQ10 @ 0x40245c; vendor symbols T2E / E2TAccQ10. */
|
||||
/* Generated from analysis/t2e_table.json. */
|
||||
|
||||
#ifndef MAG160C_TABLES_H
|
||||
#define MAG160C_TABLES_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAG160C_TEMP_CURVE_ENTRIES 0x112u
|
||||
|
||||
|
||||
static const uint32_t mag160c_t2e[MAG160C_TEMP_CURVE_ENTRIES] = {
|
||||
0x000003e8, 0x000004d3, 0x000005e1, 0x00000714, 0x0000086e, 0x000009f1,
|
||||
0x00000b9e, 0x00000d76, 0x00000f7b, 0x000011ae, 0x0000140e, 0x0000169d,
|
||||
0x0000195c, 0x00001c49, 0x00001f67, 0x000022b3, 0x0000262f, 0x000029db,
|
||||
0x00002db5, 0x000031bd, 0x000035f4, 0x00003a58, 0x00003ee9, 0x000043a7,
|
||||
0x00004890, 0x00004da4, 0x000052e2, 0x0000584a, 0x00005ddb, 0x00006394,
|
||||
0x00006974, 0x00006f7a, 0x000075a6, 0x00007bf7, 0x0000826c, 0x00008904,
|
||||
0x00008fbf, 0x0000969b, 0x00009d98, 0x0000a4b6, 0x0000abf3, 0x0000b34e,
|
||||
0x0000bac8, 0x0000c25f, 0x0000ca12, 0x0000d1e2, 0x0000d9cc, 0x0000e1d1,
|
||||
0x0000e9f0, 0x0000f229, 0x0000fa7a, 0x000102e3, 0x00010b64, 0x000113fb,
|
||||
0x00011ca9, 0x0001256d, 0x00012e46, 0x00013734, 0x00014036, 0x0001494c,
|
||||
0x00015276, 0x00015bb2, 0x00016501, 0x00016e62, 0x000177d4, 0x00018158,
|
||||
0x00018aec, 0x00019490, 0x00019e45, 0x0001a809, 0x0001b1dd, 0x0001bbbf,
|
||||
0x0001c5b0, 0x0001cfaf, 0x0001d9bc, 0x0001e3d7, 0x0001edff, 0x0001f833,
|
||||
0x00020275, 0x00020cc3, 0x0002171d, 0x00022183, 0x00022bf5, 0x00023672,
|
||||
0x000240fa, 0x00024b8c, 0x0002562a, 0x000260d2, 0x00026b84, 0x00027640,
|
||||
0x00028106, 0x00028bd5, 0x000296ae, 0x0002a190, 0x0002ac7b, 0x0002b76f,
|
||||
0x0002c26b, 0x0002cd70, 0x0002d87d, 0x0002e392, 0x0002eeaf, 0x0002f9d4,
|
||||
0x00030500, 0x00031034, 0x00031b6f, 0x000326b1, 0x000331fb, 0x00033d4b,
|
||||
0x000348a2, 0x00035400, 0x00035f64, 0x00036acf, 0x00037640, 0x000381b7,
|
||||
0x00038d34, 0x000398b7, 0x0003a440, 0x0003afcf, 0x0003bb63, 0x0003c6fd,
|
||||
0x0003d29c, 0x0003de40, 0x0003e9ea, 0x0003f599, 0x0004014d, 0x00040d05,
|
||||
0x000418c3, 0x00042486, 0x0004304d, 0x00043c18, 0x000447e9, 0x000453bd,
|
||||
0x00045f96, 0x00046b74, 0x00047756, 0x0004833b, 0x00048f25, 0x00049b13,
|
||||
0x0004a705, 0x0004b2fb, 0x0004bef4, 0x0004caf2, 0x0004d6f3, 0x0004e2f8,
|
||||
0x0004ef00, 0x0004fb0c, 0x0005071b, 0x0005132e, 0x00051f44, 0x00052b5d,
|
||||
0x0005377a, 0x0005439a, 0x00054fbd, 0x00055be3, 0x0005680c, 0x00057439,
|
||||
0x00058068, 0x00058c9a, 0x000598cf, 0x0005a507, 0x0005b142, 0x0005bd7f,
|
||||
0x0005c9bf, 0x0005d602, 0x0005e247, 0x0005ee90, 0x0005fada, 0x00060727,
|
||||
0x00061377, 0x00061fc9, 0x00062c1e, 0x00063875, 0x000644ce, 0x00065129,
|
||||
0x00065d87, 0x000669e7, 0x0006764a, 0x000682ae, 0x00068f15, 0x00069b7e,
|
||||
0x0006a7e9, 0x0006b456, 0x0006c0c5, 0x0006cd36, 0x0006d9a9, 0x0006e61e,
|
||||
0x0006f295, 0x0006ff0e, 0x00070b89, 0x00071805, 0x00072484, 0x00073104,
|
||||
0x00073d86, 0x00074a0a, 0x0007568f, 0x00076317, 0x00076fa0, 0x00077c2a,
|
||||
0x000788b7, 0x00079545, 0x0007a1d4, 0x0007ae66, 0x0007baf8, 0x0007c78d,
|
||||
0x0007d423, 0x0007e0ba, 0x0007ed53, 0x0007f9ed, 0x00080689, 0x00081327,
|
||||
0x00081fc5, 0x00082c65, 0x00083907, 0x000845aa, 0x0008524e, 0x00085ef4,
|
||||
0x00086b9b, 0x00087843, 0x000884ed, 0x00089197, 0x00089e44, 0x0008aaf1,
|
||||
0x0008b7a0, 0x0008c44f, 0x0008d100, 0x0008ddb3, 0x0008ea66, 0x0008f71b,
|
||||
0x000903d0, 0x00091087, 0x00091d3f, 0x000929f9, 0x000936b3, 0x0009436e,
|
||||
0x0009502b, 0x00095ce8, 0x000969a7, 0x00097666, 0x00098327, 0x00098fe9,
|
||||
0x00099cab, 0x0009a96f, 0x0009b634, 0x0009c2f9, 0x0009cfc0, 0x0009dc88,
|
||||
0x0009e950, 0x0009f61a, 0x000a02e4, 0x000a0faf, 0x000a1c7c, 0x000a2949,
|
||||
0x000a3617, 0x000a42e6, 0x000a4fb5, 0x000a5c86, 0x000a6958, 0x000a762a,
|
||||
0x000a82fd, 0x000a8fd1, 0x000a9ca6, 0x000aa97c, 0x000ab652, 0x000ac329,
|
||||
0x000ad001, 0x000adcda, 0x000ae9b4, 0x000af68e, 0x000b0369, 0x000b1045,
|
||||
0x000b1d22, 0x000b29ff, 0x000b36dd, 0x000b43bc,
|
||||
};
|
||||
|
||||
static const uint32_t mag160c_e2t_acc_q10[MAG160C_TEMP_CURVE_ENTRIES] = {
|
||||
0x00008b70, 0x0000795d, 0x00006abc, 0x00005eb5, 0x000054ac, 0x00004c62,
|
||||
0x0000456c, 0x00003f62, 0x00003a34, 0x000035e5, 0x00003207, 0x00002e9d,
|
||||
0x00002bc0, 0x00002910, 0x000026d3, 0x000024bc, 0x000022dc, 0x0000213c,
|
||||
0x00001fc0, 0x00001e5e, 0x00001d27, 0x00001c08, 0x00001afe, 0x00001a12,
|
||||
0x00001935, 0x0000186b, 0x000017ad, 0x000016ff, 0x0000165e, 0x000015ca,
|
||||
0x00001540, 0x000014bd, 0x00001444, 0x000013d3, 0x0000136a, 0x00001305,
|
||||
0x000012a9, 0x00001251, 0x000011fc, 0x000011af, 0x00001167, 0x0000111f,
|
||||
0x000010dd, 0x000010a0, 0x00001062, 0x0000102c, 0x00000ff6, 0x00000fc3,
|
||||
0x00000f91, 0x00000f64, 0x00000f38, 0x00000f0d, 0x00000ee7, 0x00000ebf,
|
||||
0x00000e9a, 0x00000e78, 0x00000e56, 0x00000e36, 0x00000e16, 0x00000df8,
|
||||
0x00000ddc, 0x00000dc0, 0x00000da6, 0x00000d8d, 0x00000d74, 0x00000d5d,
|
||||
0x00000d47, 0x00000d30, 0x00000d1b, 0x00000d06, 0x00000cf4, 0x00000ce0,
|
||||
0x00000cce, 0x00000cbc, 0x00000cab, 0x00000c9a, 0x00000c8c, 0x00000c7a,
|
||||
0x00000c6c, 0x00000c5e, 0x00000c4f, 0x00000c41, 0x00000c34, 0x00000c28,
|
||||
0x00000c1c, 0x00000c0e, 0x00000c03, 0x00000bf8, 0x00000bed, 0x00000be2,
|
||||
0x00000bd8, 0x00000bcd, 0x00000bc3, 0x00000bb9, 0x00000bb0, 0x00000ba7,
|
||||
0x00000b9e, 0x00000b95, 0x00000b8d, 0x00000b85, 0x00000b7c, 0x00000b75,
|
||||
0x00000b6d, 0x00000b66, 0x00000b5f, 0x00000b57, 0x00000b51, 0x00000b4a,
|
||||
0x00000b43, 0x00000b3d, 0x00000b36, 0x00000b30, 0x00000b2a, 0x00000b24,
|
||||
0x00000b1e, 0x00000b19, 0x00000b13, 0x00000b0e, 0x00000b08, 0x00000b04,
|
||||
0x00000aff, 0x00000af9, 0x00000af5, 0x00000af0, 0x00000aec, 0x00000ae7,
|
||||
0x00000ae2, 0x00000ade, 0x00000adb, 0x00000ad5, 0x00000ad2, 0x00000ace,
|
||||
0x00000ac9, 0x00000ac6, 0x00000ac3, 0x00000abe, 0x00000abb, 0x00000ab7,
|
||||
0x00000ab4, 0x00000ab1, 0x00000aac, 0x00000aaa, 0x00000aa6, 0x00000aa4,
|
||||
0x00000aa0, 0x00000a9d, 0x00000a9a, 0x00000a97, 0x00000a95, 0x00000a91,
|
||||
0x00000a8f, 0x00000a8c, 0x00000a89, 0x00000a87, 0x00000a83, 0x00000a82,
|
||||
0x00000a7f, 0x00000a7c, 0x00000a7a, 0x00000a77, 0x00000a76, 0x00000a73,
|
||||
0x00000a70, 0x00000a6f, 0x00000a6b, 0x00000a6a, 0x00000a68, 0x00000a65,
|
||||
0x00000a64, 0x00000a61, 0x00000a5f, 0x00000a5e, 0x00000a5c, 0x00000a5a,
|
||||
0x00000a58, 0x00000a55, 0x00000a55, 0x00000a52, 0x00000a50, 0x00000a4f,
|
||||
0x00000a4d, 0x00000a4b, 0x00000a4a, 0x00000a48, 0x00000a46, 0x00000a45,
|
||||
0x00000a43, 0x00000a42, 0x00000a41, 0x00000a3e, 0x00000a3d, 0x00000a3c,
|
||||
0x00000a3a, 0x00000a39, 0x00000a37, 0x00000a36, 0x00000a35, 0x00000a33,
|
||||
0x00000a32, 0x00000a31, 0x00000a2f, 0x00000a2f, 0x00000a2c, 0x00000a2c,
|
||||
0x00000a2b, 0x00000a29, 0x00000a28, 0x00000a27, 0x00000a25, 0x00000a25,
|
||||
0x00000a23, 0x00000a22, 0x00000a21, 0x00000a20, 0x00000a1f, 0x00000a1e,
|
||||
0x00000a1d, 0x00000a1b, 0x00000a1b, 0x00000a19, 0x00000a19, 0x00000a17,
|
||||
0x00000a17, 0x00000a16, 0x00000a14, 0x00000a14, 0x00000a13, 0x00000a13,
|
||||
0x00000a11, 0x00000a10, 0x00000a0f, 0x00000a0f, 0x00000a0e, 0x00000a0c,
|
||||
0x00000a0c, 0x00000a0b, 0x00000a0b, 0x00000a09, 0x00000a08, 0x00000a08,
|
||||
0x00000a07, 0x00000a06, 0x00000a06, 0x00000a05, 0x00000a04, 0x00000a04,
|
||||
0x00000a02, 0x00000a02, 0x00000a01, 0x00000a00, 0x00000a00, 0x000009ff,
|
||||
0x000009fe, 0x000009fe, 0x000009fd, 0x000009fc, 0x000009fc, 0x000009fb,
|
||||
0x000009fa, 0x000009fa, 0x000009f9, 0x000009f9, 0x000009f8, 0x000009f7,
|
||||
0x000009f7, 0x000009f6, 0x000009f6, 0x000009f5, 0x000009f4, 0x000009f3,
|
||||
0x000009f3, 0x000009f3, 0x000009f2, 0x000009f1,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* TCM board FTDICommand framing, recovered from Android Java bytecode
|
||||
* (com.elotouch.ftdi.FTDICommand) and cross-validated on Windows/Linux:
|
||||
*
|
||||
* byte[0] = 0x7e
|
||||
* byte[1..2] = big-endian body length (payload_len + 5)
|
||||
* byte[3] = checksum over bytes 0..2
|
||||
* byte[4] = main command
|
||||
* byte[5] = sub command
|
||||
* byte[6..7] = frame id (big-endian)
|
||||
* byte[8..] = payload
|
||||
* last byte = checksum over body bytes (index 4 .. n-2)
|
||||
*
|
||||
* Command table (main 0x02): rotate 0x77 [dir, magnitude],
|
||||
* light 0x31/0x32/0x33 [on, r, g, b], get-version 0x04, begin 0x05,
|
||||
* complete 0x06, transmit 0x07.
|
||||
*/
|
||||
#include "mag160c_internal.h"
|
||||
|
||||
#define TCM_HEADER 0x7e
|
||||
#define TCM_MAX_PAYLOAD 0xffffu - 5u
|
||||
|
||||
static uint8_t checksum(const uint8_t *data, size_t n) {
|
||||
uint8_t c = 0;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
c = (uint8_t)(c + data[i]); /* additive mod 256, per FTDICommand */
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_encode(uint8_t main_cmd, uint8_t sub_cmd, uint16_t frame_id,
|
||||
const uint8_t *payload, size_t payload_size,
|
||||
uint8_t *out, size_t out_cap, size_t *out_size) {
|
||||
if (out_size == NULL) {
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (payload_size > TCM_MAX_PAYLOAD || (payload_size && payload == NULL)) {
|
||||
*out_size = 0;
|
||||
mag160c_set_error("mag160c_tcm_encode: bad payload");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
const size_t body_len = payload_size + 5;
|
||||
const size_t total = body_len + 4;
|
||||
if (out_cap < total) {
|
||||
*out_size = 0;
|
||||
mag160c_set_error("mag160c_tcm_encode: output buffer too small");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
uint8_t head[4];
|
||||
head[0] = TCM_HEADER;
|
||||
head[1] = (uint8_t)(body_len >> 8);
|
||||
head[2] = (uint8_t)(body_len & 0xff);
|
||||
head[3] = checksum(head, 3);
|
||||
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
out[pos++] = head[i];
|
||||
}
|
||||
out[pos++] = main_cmd;
|
||||
out[pos++] = sub_cmd;
|
||||
out[pos++] = (uint8_t)(frame_id >> 8);
|
||||
out[pos++] = (uint8_t)(frame_id & 0xff);
|
||||
if (payload_size) {
|
||||
memcpy(out + pos, payload, payload_size);
|
||||
pos += payload_size;
|
||||
}
|
||||
out[pos++] = checksum(out + 4, body_len - 1);
|
||||
|
||||
*out_size = total;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_decode(const uint8_t *data, size_t size,
|
||||
uint8_t *out_main, uint8_t *out_sub,
|
||||
uint16_t *out_frame_id,
|
||||
uint8_t *out_payload, size_t out_payload_cap,
|
||||
size_t *out_payload_size) {
|
||||
if (data == NULL || out_main == NULL || out_sub == NULL || out_frame_id == NULL ||
|
||||
out_payload_size == NULL) {
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_payload_size = 0;
|
||||
|
||||
if (size < 5 || data[0] != TCM_HEADER) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
if (data[3] != checksum(data, 3)) {
|
||||
mag160c_set_error("mag160c_tcm_decode: header checksum mismatch");
|
||||
return MAG160C_ERR_CHECKSUM;
|
||||
}
|
||||
|
||||
const size_t body_len = ((size_t)data[1] << 8) | data[2];
|
||||
if (body_len < 5 || body_len + 4 > size) {
|
||||
return MAG160C_ERR_BAD_FRAME;
|
||||
}
|
||||
const uint8_t *body = data + 4;
|
||||
if (body[body_len - 1] != checksum(body, body_len - 1)) {
|
||||
mag160c_set_error("mag160c_tcm_decode: body checksum mismatch");
|
||||
return MAG160C_ERR_CHECKSUM;
|
||||
}
|
||||
|
||||
*out_main = body[0];
|
||||
*out_sub = body[1];
|
||||
*out_frame_id = (uint16_t)(((uint16_t)body[2] << 8) | body[3]);
|
||||
|
||||
const size_t payload_size = body_len - 5;
|
||||
if (out_payload != NULL) {
|
||||
if (out_payload_cap < payload_size) {
|
||||
mag160c_set_error("mag160c_tcm_decode: payload buffer too small");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
memcpy(out_payload, body + 4, payload_size);
|
||||
}
|
||||
*out_payload_size = payload_size;
|
||||
mag160c_clear_error();
|
||||
return MAG160C_OK;
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_rotate(int angle, uint8_t *out, size_t out_cap,
|
||||
size_t *out_size) {
|
||||
/* vendor: clamp to [-128, 128]; direction 0 = positive, 1 = negative */
|
||||
if (angle < -128) {
|
||||
angle = -128;
|
||||
} else if (angle > 128) {
|
||||
angle = 128;
|
||||
}
|
||||
uint8_t payload[2];
|
||||
if (angle >= 0) {
|
||||
payload[0] = 0;
|
||||
payload[1] = (uint8_t)angle;
|
||||
} else {
|
||||
payload[0] = 1;
|
||||
payload[1] = (uint8_t)(-angle);
|
||||
}
|
||||
return mag160c_tcm_encode(0x02, 0x77, 0x0001, payload, sizeof(payload),
|
||||
out, out_cap, out_size);
|
||||
}
|
||||
|
||||
mag160c_error_t mag160c_tcm_light(mag160c_tcm_light_color_t color,
|
||||
mag160c_tcm_light_mode_t mode,
|
||||
uint8_t *out, size_t out_cap, size_t *out_size) {
|
||||
if (color > MAG160C_TCM_LIGHT_YELLOW || mode > MAG160C_TCM_LIGHT_BREATH) {
|
||||
mag160c_set_error("mag160c_tcm_light: invalid color/mode");
|
||||
return MAG160C_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
/* sub 0x31 steady, 0x32 blink, 0x33 breath; payload [on, r, g, b] */
|
||||
uint8_t payload[4] = {0x01, 0x00, 0x00, 0x00};
|
||||
switch (color) {
|
||||
case MAG160C_TCM_LIGHT_OFF: payload[0] = 0x00; break;
|
||||
case MAG160C_TCM_LIGHT_RED: payload[1] = 0xff; break;
|
||||
case MAG160C_TCM_LIGHT_GREEN: payload[2] = 0xff; break;
|
||||
case MAG160C_TCM_LIGHT_BLUE: payload[3] = 0xff; break;
|
||||
case MAG160C_TCM_LIGHT_YELLOW: payload[1] = 0xff; payload[2] = 0xff; break;
|
||||
default: break;
|
||||
}
|
||||
return mag160c_tcm_encode(0x02, (uint8_t)(0x31 + mode), 0x0001, payload,
|
||||
sizeof(payload), out, out_cap, out_size);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Temperature conversion, recovered from libcoresdk.so (ARM64) exported
|
||||
* CFunctions class:
|
||||
*
|
||||
* Calibration(this+0x6a41c):
|
||||
* diff = (int16)(frame[p] - baseline[p]) >> 1
|
||||
* band = first i in [0, bands-1) with diff <= threshold[p][i]
|
||||
* (no match -> band = bands-1)
|
||||
* v = pwl[(band*pixels + p)*2 + 1] + ((diff * pwl[(band*pixels + p)*2]) >> 12)
|
||||
* out[p] = clamp(v, 0, 0xffff)
|
||||
*
|
||||
* ConvertResponse2Temperature(this+0x73124):
|
||||
* base = clamp((0xc350 - gain) >> shift, 0, 0xffff)
|
||||
* v = base + (frame[p] - baseline[p]) * coeff
|
||||
* saturated count > 0x101 -> coeff -= 1 (auto gain reduction)
|
||||
*
|
||||
* T2E / E2TAccQ10 tables are the static vendor calibration curve.
|
||||
*/
|
||||
#include "mag160c_internal.h"
|
||||
#include "mag160c_tables.h"
|
||||
|
||||
void mag160c_temp_calibrate(const uint16_t *frame,
|
||||
const mag160c_temp_tables_t *tables,
|
||||
uint16_t *out) {
|
||||
if (frame == NULL || tables == NULL || out == NULL || tables->pixel_count == 0) {
|
||||
return;
|
||||
}
|
||||
const uint32_t pixels = tables->pixel_count;
|
||||
const uint32_t bands = tables->band_count == 0 ? 1 : tables->band_count;
|
||||
const uint32_t search = bands - 1;
|
||||
|
||||
for (uint32_t p = 0; p < pixels; ++p) {
|
||||
int32_t diff;
|
||||
if (tables->has_baseline && tables->baseline != NULL) {
|
||||
diff = (int32_t)(int16_t)(frame[p] - tables->baseline[p]);
|
||||
} else {
|
||||
diff = (int32_t)frame[p];
|
||||
}
|
||||
diff >>= 1;
|
||||
|
||||
uint32_t band = 0;
|
||||
if (search > 0 && tables->thresholds != NULL) {
|
||||
for (uint32_t i = 0; i < search; ++i) {
|
||||
if (diff <= tables->thresholds[p * search + i]) {
|
||||
band = i;
|
||||
break;
|
||||
}
|
||||
band = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t entry = ((size_t)band * pixels + p) * 2;
|
||||
const int32_t coeff = tables->pwl != NULL ? tables->pwl[entry] : 0;
|
||||
const int32_t offset = tables->pwl != NULL ? tables->pwl[entry + 1] : 0;
|
||||
int32_t v = offset + ((diff * coeff) >> 12);
|
||||
if (v < 0) {
|
||||
v = 0;
|
||||
} else if (v > 0xffff) {
|
||||
v = 0xffff;
|
||||
}
|
||||
out[p] = (uint16_t)v;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mag160c_temp_convert_response(const uint16_t *frame, uint32_t pixel_count,
|
||||
const mag160c_temp_gain_t *cfg, uint16_t *out) {
|
||||
if (frame == NULL || cfg == NULL || out == NULL || pixel_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
const uint32_t shift = cfg->shift & 31;
|
||||
int32_t base = (int32_t)(0xc350 - cfg->gain);
|
||||
base >>= shift;
|
||||
if (base < 0) {
|
||||
base = 0;
|
||||
} else if (base > 0xffff) {
|
||||
base = 0xffff;
|
||||
}
|
||||
|
||||
int32_t coeff = cfg->coeff;
|
||||
uint32_t saturated = 0;
|
||||
for (uint32_t p = 0; p < pixel_count; ++p) {
|
||||
int32_t diff = (int32_t)frame[p];
|
||||
if (cfg->baseline != NULL) {
|
||||
diff -= (int32_t)cfg->baseline[p];
|
||||
}
|
||||
int32_t v = base + diff * coeff;
|
||||
if (v >= 0x10000) {
|
||||
++saturated;
|
||||
v = 0xffff;
|
||||
} else {
|
||||
saturated += (uint32_t)(v >> 31);
|
||||
if (v < 0) {
|
||||
v = 0;
|
||||
}
|
||||
}
|
||||
out[p] = (uint16_t)v;
|
||||
}
|
||||
return saturated;
|
||||
}
|
||||
|
||||
/*
|
||||
* T2E piecewise-linear evaluation with Q13 band selection:
|
||||
* idx = clamp((value + 0xc350) >> 13, 0, 0x111)
|
||||
* result = T2E[idx] + ((value - idx*8192) * (T2E[idx+1]-T2E[idx]) + 8191) >> 13
|
||||
* Matches the vendor interp in ReviseTemperature/CorrectTemperature.
|
||||
*/
|
||||
int32_t mag160c_temp_t2e_interp(int32_t value) {
|
||||
int32_t v = value + 0xc350;
|
||||
int32_t idx = v >> 13;
|
||||
if (idx < 0) {
|
||||
idx = 0;
|
||||
} else if (idx > (int32_t)MAG160C_TEMP_CURVE_ENTRIES - 2) {
|
||||
idx = (int32_t)MAG160C_TEMP_CURVE_ENTRIES - 2;
|
||||
}
|
||||
const int32_t t0 = (int32_t)mag160c_t2e[idx];
|
||||
const int32_t t1 = (int32_t)mag160c_t2e[idx + 1];
|
||||
int32_t d = v - (idx << 13);
|
||||
int32_t interp = (d * (t1 - t0)) >> 13;
|
||||
if (interp < 0 && (d * (t1 - t0)) & 0x1fff) {
|
||||
interp += 1; /* round half away from zero, matching asr+csel pattern */
|
||||
}
|
||||
return t0 + interp;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Public API / session tests (no-libusb build path). */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(void) {
|
||||
assert(mag160c_init(NULL) == MAG160C_ERR_INVALID_ARGUMENT);
|
||||
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
assert(mag160c_init(&ctx) == MAG160C_OK);
|
||||
assert(ctx != NULL);
|
||||
|
||||
mag160c_ir_t *ir = NULL;
|
||||
mag160c_error_t rc = mag160c_ir_open(ctx, &ir);
|
||||
|
||||
#if !MAG160C_HAS_LIBUSB
|
||||
assert(rc == MAG160C_ERR_NOT_SUPPORTED);
|
||||
assert(strstr(mag160c_last_error(), "libusb") != NULL);
|
||||
#else
|
||||
if (rc == MAG160C_OK) {
|
||||
assert(ir != NULL);
|
||||
mag160c_ir_info_t info;
|
||||
assert(mag160c_ir_get_info(ir, &info) == MAG160C_OK);
|
||||
assert(info.width == 160 || info.width != 0);
|
||||
mag160c_ir_close(ir);
|
||||
}
|
||||
#endif
|
||||
|
||||
mag160c_shutdown(ctx);
|
||||
|
||||
/* pure helpers must work regardless of transport */
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_rotate(5, out, sizeof(out), &size) == MAG160C_OK);
|
||||
assert(size == 11);
|
||||
|
||||
printf("test_api: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/* Tests for the display pipeline module (bad map, AGC, FFC scheduler, cal). */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mag160c/mag160c.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (!(cond)) { printf("FAIL: %s\n", msg); failures++; } \
|
||||
else { printf("ok: %s\n", msg); } \
|
||||
} while (0)
|
||||
|
||||
static void test_badmap(void) {
|
||||
/* 4x3 synthetic frame with two bad pixels and a 2-pixel cluster */
|
||||
enum { W = 4, H = 3 };
|
||||
mag160c_display_badmap_t m;
|
||||
mag160c_display_badmap_init(&m, W, H);
|
||||
m.temporal_thr = 50;
|
||||
|
||||
/* pixel (1,0): bright defect (value 5000 vs peak 100) - every frame */
|
||||
/* pixels (2,1),(3,1): temporal fluctuation cluster */
|
||||
uint16_t f2[H * W];
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
for (int p = 0; p < H * W; ++p) f2[p] = 100;
|
||||
f2[0 * W + 1] = 5000;
|
||||
f2[1 * W + 2] = 100 + (i % 2 ? 80 : 0);
|
||||
f2[1 * W + 3] = 100 + (i % 2 ? 80 : 0);
|
||||
mag160c_display_badmap_feed(&m, f2);
|
||||
}
|
||||
mag160c_error_t e = mag160c_display_badmap_finalize(&m);
|
||||
CHECK(e == MAG160C_OK, "badmap finalize ok");
|
||||
CHECK(m.ready == 1, "badmap ready");
|
||||
CHECK(m.bad[0 * W + 1] == 1, "bright defect detected");
|
||||
CHECK(m.bad[1 * W + 2] == 1 && m.bad[1 * W + 3] == 1, "temporal cluster detected");
|
||||
CHECK(m.bad_count == 3, "bad_count == 3");
|
||||
/* correction must fill bad pixels with neighbour means */
|
||||
for (int p = 0; p < H * W; ++p) f2[p] = 100;
|
||||
f2[0 * W + 1] = 5000;
|
||||
f2[1 * W + 2] = 180;
|
||||
f2[1 * W + 3] = 180;
|
||||
mag160c_display_badmap_correct(&m, f2);
|
||||
CHECK(f2[0 * W + 1] == 100, "bright defect corrected to neighbour mean");
|
||||
CHECK(f2[1 * W + 2] == 100, "cluster pixel corrected");
|
||||
CHECK(f2[1 * W + 3] == 100, "cluster pixel 2 corrected");
|
||||
CHECK(m.ref[1 * W + 2] == 100, "reference cluster filled");
|
||||
mag160c_display_badmap_destroy(&m);
|
||||
}
|
||||
|
||||
static void test_agc(void) {
|
||||
/* 1000 samples: 980 background 1000..1979, 10 hot outliers 40000+,
|
||||
* 10 zeros. Percentile [2,98] must exclude the outliers. */
|
||||
uint16_t frame[1000];
|
||||
for (int i = 0; i < 980; ++i) frame[i] = (uint16_t)(1000 + i % 980);
|
||||
for (int i = 0; i < 10; ++i) frame[980 + i] = (uint16_t)(40000 + i * 100);
|
||||
frame[990] = 0;
|
||||
frame[991] = 0;
|
||||
uint32_t lo = 0, hi = 0;
|
||||
mag160c_error_t e = mag160c_display_agc_range(frame, 1000, 2, 98, &lo, &hi);
|
||||
CHECK(e == MAG160C_OK, "agc_range ok");
|
||||
CHECK(hi < 40000, "agc excludes hot defect outliers");
|
||||
CHECK(lo >= 1000 && lo <= 1100, "agc low percentile sane");
|
||||
|
||||
int32_t diff[8] = {0, 5, 10, -10, 100, -100, 1000, -1000};
|
||||
uint32_t span = 0;
|
||||
e = mag160c_display_diff_span(diff, 8, 120, 4000, &span);
|
||||
CHECK(e == MAG160C_OK, "diff_span ok");
|
||||
CHECK(span > 200 && span <= 4000, "diff span adaptive");
|
||||
}
|
||||
|
||||
static void test_ffc(void) {
|
||||
mag160c_ffc_scheduler_t s;
|
||||
mag160c_ffc_scheduler_init(&s, 400, 9);
|
||||
int sent0 = 0, sent1 = 0;
|
||||
for (int f = 0; f < 900; ++f) {
|
||||
int32_t p = mag160c_ffc_scheduler_tick(&s);
|
||||
if (p == 0) sent0++;
|
||||
if (p == 1) sent1++;
|
||||
}
|
||||
CHECK(sent1 >= 2, "ffc scheduler sends FFC(1) (initial + pair)");
|
||||
CHECK(sent0 >= 2, "ffc scheduler sends FFC(0) periodically");
|
||||
}
|
||||
|
||||
static void test_ffc_trigger(void) {
|
||||
/* manual trigger: FFC(0) now, FFC(1) exactly `gap` frames later */
|
||||
mag160c_ffc_scheduler_t s;
|
||||
mag160c_ffc_scheduler_init(&s, 400, 9);
|
||||
int32_t p = mag160c_ffc_scheduler_trigger(&s);
|
||||
CHECK(p == 0, "trigger returns 0 (send FFC(0))");
|
||||
int frames_to_1 = 0;
|
||||
for (int f = 0; f < 20; ++f) {
|
||||
int32_t t = mag160c_ffc_scheduler_tick(&s);
|
||||
if (t == 1) {
|
||||
frames_to_1 = f + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(frames_to_1 == 9, "FFC(1) comes exactly 9 frames after trigger");
|
||||
}
|
||||
|
||||
static void test_cal(void) {
|
||||
double a = 0, b = 0;
|
||||
mag160c_error_t e = mag160c_temp_calibrate_linear(11000, 0.0, 14000, 90.0, &a, &b);
|
||||
CHECK(e == MAG160C_OK, "linear cal ok");
|
||||
CHECK(a > 0.029 && a < 0.031, "slope ~0.03 C/count");
|
||||
CHECK(mag160c_temp_apply_linear(12500, a, b) > 44.9 &&
|
||||
mag160c_temp_apply_linear(12500, a, b) < 45.1, "apply midpoint 45C");
|
||||
e = mag160c_temp_calibrate_linear(11000, 0, 11000, 90, &a, &b);
|
||||
CHECK(e != MAG160C_OK, "identical counts rejected");
|
||||
}
|
||||
|
||||
static void test_ref_track(void) {
|
||||
/* hot object (1200 over bg) for 4500 frames must not be absorbed */
|
||||
enum { N = 8 };
|
||||
uint16_t ref[N], live[N];
|
||||
for (int i = 0; i < N; ++i) ref[i] = 11720;
|
||||
for (int i = 0; i < N; ++i) live[i] = 11720 + 1200;
|
||||
for (int f = 0; f < 4500; ++f)
|
||||
mag160c_display_ref_track(ref, live, N, 60, 32);
|
||||
int absorbed = 0;
|
||||
for (int i = 0; i < N; ++i) if (ref[i] != 11720) absorbed++;
|
||||
CHECK(absorbed == 0, "ref_track: static hot object not absorbed (no ghost)");
|
||||
|
||||
/* removal: diff ~0 */
|
||||
for (int i = 0; i < N; ++i) live[i] = 11720;
|
||||
long sum = 0;
|
||||
for (int i = 0; i < N; ++i) sum += (int)live[i] - (int)ref[i];
|
||||
CHECK(sum > -10 && sum < 10, "ref_track: no ghost after object removal");
|
||||
|
||||
/* slow drift still tracked */
|
||||
for (int f = 0; f < 2000; ++f) {
|
||||
for (int i = 0; i < N; ++i) live[i] = (uint16_t)(11723 + f % 10);
|
||||
mag160c_display_ref_track(ref, live, N, 60, 32);
|
||||
}
|
||||
long drift = 0;
|
||||
for (int i = 0; i < N; ++i) drift += (int)live[i] - (int)ref[i];
|
||||
CHECK(drift / N < 60, "ref_track: slow drift tracked");
|
||||
}
|
||||
|
||||
static void test_ref_heal(void) {
|
||||
enum { W2 = 160, H2 = 120 };
|
||||
enum { N = W2 * H2 };
|
||||
static uint16_t ref[N], live[N];
|
||||
static uint8_t fg[N];
|
||||
for (int i = 0; i < N; ++i) { ref[i] = 11720; live[i] = 11720; }
|
||||
|
||||
/* 40 isolated startup-noise pixels wrongly in the reference (+800) */
|
||||
int noise[40];
|
||||
for (int n = 0; n < 40; ++n) {
|
||||
int x = 10 + (n % 8) * 5, y = 10 + (n / 8) * 5;
|
||||
noise[n] = y * W2 + x;
|
||||
ref[noise[n]] = 11720 + 800;
|
||||
}
|
||||
for (int f = 0; f < 50; ++f)
|
||||
mag160c_display_ref_track_heal(ref, live, W2, H2, 60, 32, fg, 30, 2);
|
||||
int healed = 0;
|
||||
for (int n = 0; n < 40; ++n) if (ref[noise[n]] == 11720) healed++;
|
||||
CHECK(healed == 40, "heal: isolated startup noise pixels healed");
|
||||
|
||||
/* contiguous 60x40 hot object must stay frozen (not healed/absorbed) */
|
||||
for (int i = 0; i < N; ++i) { ref[i] = 11720; live[i] = 11720; fg[i] = 0; }
|
||||
for (int y = 40; y < 80; ++y)
|
||||
for (int x = 50; x < 110; ++x) live[y * W2 + x] = 11720 + 1200;
|
||||
for (int f = 0; f < 200; ++f)
|
||||
mag160c_display_ref_track_heal(ref, live, W2, H2, 60, 32, fg, 30, 2);
|
||||
int absorbed = 0;
|
||||
for (int y = 40; y < 80; ++y)
|
||||
for (int x = 50; x < 110; ++x)
|
||||
if (ref[y * W2 + x] > 11720 + 100) absorbed++;
|
||||
CHECK(absorbed == 0, "heal: contiguous object never absorbed/healed");
|
||||
}
|
||||
|
||||
static void test_nuc(void) {
|
||||
/* synthetic mura: background 11720 with a +5000 row-1 band and a
|
||||
* -3000 col-17 band; a hand (+1200 over a region) must survive NUC */
|
||||
enum { W2 = 160, H2 = 120 };
|
||||
enum { N = W2 * H2 };
|
||||
static uint16_t live[N], ref[N], out[N];
|
||||
for (int i = 0; i < N; ++i) {
|
||||
int x = i % W2, y = i / W2;
|
||||
uint16_t v = 11720;
|
||||
if (y == 1) v += 5000; /* mura row band */
|
||||
if (x == 17) v -= 3000; /* mura col band */
|
||||
ref[i] = v;
|
||||
live[i] = v;
|
||||
if (x >= 60 && x < 100 && y >= 40 && y < 80) live[i] += 1200; /* hand */
|
||||
}
|
||||
mag160c_error_t e = mag160c_display_nuc(live, ref, N, out);
|
||||
CHECK(e == MAG160C_OK, "nuc ok");
|
||||
/* mura removed: row1 and col17 pixels back near background */
|
||||
int r1 = out[1 * W2 + 50];
|
||||
int c17 = out[50 * W2 + 17];
|
||||
CHECK(r1 >= 11690 && r1 <= 11770, "nuc removes row mura band");
|
||||
CHECK(c17 >= 11690 && c17 <= 11770, "nuc removes col mura band");
|
||||
/* hand region still visible */
|
||||
int hand = out[50 * W2 + 80];
|
||||
CHECK(hand >= 11700 + 1100 && hand <= 11700 + 1300, "nuc keeps object");
|
||||
}
|
||||
|
||||
static void test_ref_rebase(void) {
|
||||
enum { N = 8 };
|
||||
uint16_t ref[N], live[N];
|
||||
for (int i = 0; i < N; ++i) { ref[i] = 11720; live[i] = 11720 + 800; }
|
||||
mag160c_error_t e = mag160c_display_ref_rebase(ref, live, N, 2000);
|
||||
CHECK(e == MAG160C_OK, "ref_rebase ok");
|
||||
long sum = 0;
|
||||
for (int i = 0; i < N; ++i) sum += (int)live[i] - (int)ref[i];
|
||||
CHECK(sum == 0, "ref_rebase removes global offset");
|
||||
/* single hot pixel (outlier) does not drag the median */
|
||||
live[0] = 11720 + 6000;
|
||||
mag160c_display_ref_rebase(ref, live, N, 2000);
|
||||
sum = 0;
|
||||
for (int i = 0; i < N; ++i) sum += (int)live[i] - (int)ref[i];
|
||||
CHECK(sum >= 0 && sum < 6000, "ref_rebase robust to single outlier");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_badmap();
|
||||
test_agc();
|
||||
test_ffc();
|
||||
test_ffc_trigger();
|
||||
test_ref_track();
|
||||
test_ref_heal();
|
||||
test_nuc();
|
||||
test_ref_rebase();
|
||||
test_cal();
|
||||
printf(failures ? "\nTEST_DISPLAY_FAILED (%d)\n" : "\nTEST_DISPLAY_PASSED\n",
|
||||
failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* Frame parser tests against the recovered 0x1bb1b11b framing. */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void put32(uint8_t *p, uint32_t v) {
|
||||
p[0] = (uint8_t)v;
|
||||
p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16);
|
||||
p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
static void test_parse_valid(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0xaa, sizeof(frame));
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x04, 42);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 1);
|
||||
put32(frame + 0x10, 1000);
|
||||
frame[0x1c] = 0x34;
|
||||
frame[0x1d] = 0x12;
|
||||
put32(frame + 0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) == MAG160C_OK);
|
||||
assert(h.marker == 0x1bb1b11b);
|
||||
assert(h.frame_counter == 42);
|
||||
assert(h.data_length == 4);
|
||||
assert(h.frame_type == 1);
|
||||
assert(h.period_shutter == 1000);
|
||||
assert(h.trailing_marker == 0x1bb1b11c);
|
||||
assert(pixels[0] == 0x1234);
|
||||
}
|
||||
|
||||
static void test_parse_rejects(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
|
||||
memset(frame, 0, sizeof(frame));
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) ==
|
||||
MAG160C_ERR_BAD_FRAME);
|
||||
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 2); /* bad type */
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) ==
|
||||
MAG160C_ERR_BAD_FRAME);
|
||||
|
||||
put32(frame + 0x0c, 0);
|
||||
put32(frame + 0x1c + 4, 0xdeadbeef); /* bad trailer */
|
||||
assert(mag160c_frame_parse(frame, sizeof(frame), &h, &pixels) ==
|
||||
MAG160C_ERR_BAD_FRAME);
|
||||
}
|
||||
|
||||
static void test_stream_split(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0xaa, sizeof(frame));
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x04, 7);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 1);
|
||||
frame[0x1c] = 0x78;
|
||||
frame[0x1d] = 0x56;
|
||||
put32(frame + 0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c_frame_stream_t s;
|
||||
mag160c_frame_stream_init(&s, 0x40);
|
||||
assert(s.buf != NULL);
|
||||
|
||||
uint8_t out[0x100];
|
||||
size_t len = 99;
|
||||
const size_t total = 0x38 + 4; /* 0x3c */
|
||||
|
||||
/* feed one byte at a time; the frame completes exactly at total bytes */
|
||||
for (size_t i = 0; i + 1 < total; ++i) {
|
||||
len = 99;
|
||||
assert(mag160c_frame_stream_push(&s, frame + i, 1, out, sizeof(out), &len) ==
|
||||
MAG160C_OK);
|
||||
assert(len == 0);
|
||||
}
|
||||
len = 99;
|
||||
assert(mag160c_frame_stream_push(&s, frame + total - 1, 1, out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == total);
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
assert(mag160c_frame_parse(out, len, &h, &pixels) == MAG160C_OK);
|
||||
assert(h.frame_counter == 7);
|
||||
assert(pixels[0] == 0x5678);
|
||||
|
||||
/* trailing 4 bytes of the 0x40-byte test frame must not form a frame */
|
||||
len = 99;
|
||||
assert(mag160c_frame_stream_push(&s, frame + total, 4, out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == 0);
|
||||
|
||||
mag160c_frame_stream_destroy(&s);
|
||||
}
|
||||
|
||||
static void test_stream_resync(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0xaa, sizeof(frame));
|
||||
put32(frame + 0x00, 0x1bb1b11b);
|
||||
put32(frame + 0x08, 4);
|
||||
put32(frame + 0x0c, 0);
|
||||
frame[0x1c] = 0x11;
|
||||
frame[0x1d] = 0x22;
|
||||
put32(frame + 0x1c + 4, 0x1bb1b11c);
|
||||
|
||||
mag160c_frame_stream_t s;
|
||||
mag160c_frame_stream_init(&s, 0x40);
|
||||
|
||||
/* garbage prefix (4-byte aligned) then the frame */
|
||||
const uint8_t garbage[] = {0x00, 0xff, 0x12, 0x34};
|
||||
uint8_t out[0x100];
|
||||
size_t len = 0;
|
||||
assert(mag160c_frame_stream_push(&s, garbage, sizeof(garbage), out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == 0);
|
||||
assert(mag160c_frame_stream_push(&s, frame, sizeof(frame), out, sizeof(out),
|
||||
&len) == MAG160C_OK);
|
||||
assert(len == 0x38 + 4);
|
||||
mag160c_frame_stream_destroy(&s);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_parse_valid();
|
||||
test_parse_rejects();
|
||||
test_stream_split();
|
||||
test_stream_resync();
|
||||
printf("test_frame: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* TCM framing tests (0x7e FTDICommand format). */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void test_rotate_frame(void) {
|
||||
/* vendor byte-exact: 7e 00 07 85 02 77 00 01 00 05 7f */
|
||||
const uint8_t expected[] = {0x7e, 0x00, 0x07, 0x85, 0x02, 0x77,
|
||||
0x00, 0x01, 0x00, 0x05, 0x7f};
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_rotate(5, out, sizeof(out), &size) == MAG160C_OK);
|
||||
assert(size == sizeof(expected));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
assert(out[i] == expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_light_frame(void) {
|
||||
/* blink green: 7e 00 09 87 02 32 00 01 01 00 ff 00 35 */
|
||||
const uint8_t expected[] = {0x7e, 0x00, 0x09, 0x87, 0x02, 0x32, 0x00,
|
||||
0x01, 0x01, 0x00, 0xff, 0x00, 0x35};
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_light(MAG160C_TCM_LIGHT_GREEN, MAG160C_TCM_LIGHT_BLINK,
|
||||
out, sizeof(out), &size) == MAG160C_OK);
|
||||
assert(size == sizeof(expected));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
assert(out[i] == expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_decode_roundtrip(void) {
|
||||
const uint8_t payload[] = {0x10, 0x20, 0x30};
|
||||
uint8_t enc[64];
|
||||
size_t enc_size = 0;
|
||||
assert(mag160c_tcm_encode(0xa1, 0xb2, 0xc3d4, payload, sizeof(payload),
|
||||
enc, sizeof(enc), &enc_size) == MAG160C_OK);
|
||||
|
||||
uint8_t main = 0, sub = 0, pl[16];
|
||||
uint16_t frame_id = 0;
|
||||
size_t pl_size = 0;
|
||||
assert(mag160c_tcm_decode(enc, enc_size, &main, &sub, &frame_id, pl,
|
||||
sizeof(pl), &pl_size) == MAG160C_OK);
|
||||
assert(main == 0xa1 && sub == 0xb2 && frame_id == 0xc3d4);
|
||||
assert(pl_size == 3 && pl[0] == 0x10 && pl[1] == 0x20 && pl[2] == 0x30);
|
||||
|
||||
enc[3] ^= 0xff; /* corrupt header checksum */
|
||||
assert(mag160c_tcm_decode(enc, enc_size, &main, &sub, &frame_id, pl,
|
||||
sizeof(pl), &pl_size) == MAG160C_ERR_CHECKSUM);
|
||||
}
|
||||
|
||||
static void test_rotate_clamp(void) {
|
||||
uint8_t out[64];
|
||||
size_t size = 0;
|
||||
assert(mag160c_tcm_rotate(-200, out, sizeof(out), &size) == MAG160C_OK);
|
||||
/* direction=1, magnitude=128 */
|
||||
assert(out[8] == 1 && out[9] == 128);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_rotate_frame();
|
||||
test_light_frame();
|
||||
test_decode_roundtrip();
|
||||
test_rotate_clamp();
|
||||
printf("test_tcm: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/* Temperature conversion tests against the recovered CFunctions math. */
|
||||
#include "mag160c/mag160c.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void test_calibrate_band_select(void) {
|
||||
const uint16_t frame[4] = {1000, 2000, 3000, 4000};
|
||||
const int16_t thresh[4] = {5, 5, 5, 5};
|
||||
const uint16_t pwl[16] = {
|
||||
0, 100, 0, 100, 0, 100, 0, 100,
|
||||
0, 200, 0, 200, 0, 200, 0, 200,
|
||||
};
|
||||
mag160c_temp_tables_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.thresholds = thresh;
|
||||
t.pwl = pwl;
|
||||
t.pixel_count = 4;
|
||||
t.band_count = 2;
|
||||
|
||||
uint16_t out[4];
|
||||
mag160c_temp_calibrate(frame, &t, out);
|
||||
/* diff = frame>>1 = 500..2000 all > 5 -> band 1 -> 200 */
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
assert(out[i] == 200);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_calibrate_interp_clamp(void) {
|
||||
const uint16_t frame[2] = {2000, 0x8000};
|
||||
const uint16_t baseline[2] = {0, 0x100};
|
||||
const int16_t thresh[2] = {500, 500};
|
||||
const uint16_t pwl[8] = {
|
||||
0x1000, 1000, 0x1000, 1000,
|
||||
0x0100, 2000, 0x0100, 2000,
|
||||
};
|
||||
mag160c_temp_tables_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.thresholds = thresh;
|
||||
t.pwl = pwl;
|
||||
t.pixel_count = 2;
|
||||
t.band_count = 2;
|
||||
t.baseline = baseline;
|
||||
t.has_baseline = 1;
|
||||
|
||||
uint16_t out[2];
|
||||
mag160c_temp_calibrate(frame, &t, out);
|
||||
assert(out[0] == 2062); /* 2000 + (1000*256>>12) */
|
||||
assert(out[1] == 3016); /* 2000 + (16256*256>>12) */
|
||||
|
||||
/* negative diff: (int16)(100-200)>>1 = -50 -> 1000 + (-50*4096>>12) = 950 */
|
||||
const uint16_t f2[1] = {100};
|
||||
const uint16_t b2[1] = {200};
|
||||
mag160c_temp_tables_t t2;
|
||||
memset(&t2, 0, sizeof(t2));
|
||||
t2.thresholds = thresh;
|
||||
t2.pwl = pwl;
|
||||
t2.pixel_count = 1;
|
||||
t2.band_count = 2;
|
||||
t2.baseline = b2;
|
||||
t2.has_baseline = 1;
|
||||
uint16_t out2[1];
|
||||
mag160c_temp_calibrate(f2, &t2, out2);
|
||||
assert(out2[0] == 950);
|
||||
}
|
||||
|
||||
static void test_convert_response(void) {
|
||||
const uint16_t frame[4] = {100, 200, 300, 400};
|
||||
const uint16_t baseline[4] = {0, 0, 0, 0};
|
||||
mag160c_temp_gain_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.gain = 0;
|
||||
cfg.shift = 0;
|
||||
cfg.coeff = 4;
|
||||
cfg.baseline = baseline;
|
||||
|
||||
uint16_t out[4];
|
||||
uint32_t sat = mag160c_temp_convert_response(frame, 4, &cfg, out);
|
||||
/* base = (50000-0)>>0 = 50000 (< 0xffff, not clamped); v = 50000 + diff*4 */
|
||||
assert(sat == 0);
|
||||
assert(out[0] == 50400 && out[1] == 50800 && out[2] == 51200 && out[3] == 51600);
|
||||
|
||||
mag160c_temp_gain_t cfg2;
|
||||
memset(&cfg2, 0, sizeof(cfg2));
|
||||
cfg2.gain = 49900; /* base = (50000-49900)>>0 = 100 */
|
||||
cfg2.shift = 0;
|
||||
cfg2.coeff = 1;
|
||||
cfg2.baseline = NULL;
|
||||
sat = mag160c_temp_convert_response(frame, 4, &cfg2, out);
|
||||
assert(sat == 0);
|
||||
assert(out[0] == 200 && out[1] == 300 && out[2] == 400 && out[3] == 500);
|
||||
}
|
||||
|
||||
static void test_t2e(void) {
|
||||
/* index 0 = value where (v+0xc350)>>13 == 0 -> v = -0xc350 */
|
||||
assert(mag160c_temp_t2e_interp(-0xc350) == 1000);
|
||||
/* at -0xc350 + 8192, idx=1 -> 0x4d3 = 1235 */
|
||||
assert(mag160c_temp_t2e_interp(-0xc350 + 8192) == 0x4d3);
|
||||
/* monotonic curve sanity (avoid int32 overflow; vendor math wraps) */
|
||||
const int32_t a = mag160c_temp_t2e_interp(-0xc350);
|
||||
const int32_t b = mag160c_temp_t2e_interp(0x40000);
|
||||
assert(a < b);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_calibrate_band_select();
|
||||
test_calibrate_interp_clamp();
|
||||
test_convert_response();
|
||||
test_t2e();
|
||||
printf("test_temp: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
;
|
||||
; Definition file of libusb-1.0.dll
|
||||
; Automatic generated by gendef
|
||||
; written by Kai Tietz 2008
|
||||
;
|
||||
LIBRARY "libusb-1.0.dll"
|
||||
EXPORTS
|
||||
libusb_alloc_streams
|
||||
libusb_alloc_streams@16
|
||||
libusb_alloc_transfer
|
||||
libusb_alloc_transfer@4
|
||||
libusb_attach_kernel_driver
|
||||
libusb_attach_kernel_driver@8
|
||||
libusb_bulk_noblk_transfer
|
||||
libusb_bulk_noblk_transfer@24
|
||||
libusb_bulk_transfer
|
||||
libusb_bulk_transfer@24
|
||||
libusb_cancel_transfer
|
||||
libusb_cancel_transfer@4
|
||||
libusb_claim_interface
|
||||
libusb_claim_interface@8
|
||||
libusb_clear_halt
|
||||
libusb_clear_halt@8
|
||||
libusb_close
|
||||
libusb_close@4
|
||||
libusb_control_transfer
|
||||
libusb_control_transfer@32
|
||||
libusb_detach_kernel_driver
|
||||
libusb_detach_kernel_driver@8
|
||||
libusb_error_name
|
||||
libusb_error_name@4
|
||||
libusb_event_handler_active
|
||||
libusb_event_handler_active@4
|
||||
libusb_event_handling_ok
|
||||
libusb_event_handling_ok@4
|
||||
libusb_exit
|
||||
libusb_exit@4
|
||||
libusb_free_bos_descriptor
|
||||
libusb_free_bos_descriptor@4
|
||||
libusb_free_config_descriptor
|
||||
libusb_free_config_descriptor@4
|
||||
libusb_free_container_id_descriptor
|
||||
libusb_free_container_id_descriptor@4
|
||||
libusb_free_device_list
|
||||
libusb_free_device_list@8
|
||||
libusb_free_ss_endpoint_companion_descriptor
|
||||
libusb_free_ss_endpoint_companion_descriptor@4
|
||||
libusb_free_ss_usb_device_capability_descriptor
|
||||
libusb_free_ss_usb_device_capability_descriptor@4
|
||||
libusb_free_streams
|
||||
libusb_free_streams@12
|
||||
libusb_free_transfer
|
||||
libusb_free_transfer@4
|
||||
libusb_free_usb_2_0_extension_descriptor
|
||||
libusb_free_usb_2_0_extension_descriptor@4
|
||||
libusb_get_active_config_descriptor
|
||||
libusb_get_active_config_descriptor@8
|
||||
libusb_get_bos_descriptor
|
||||
libusb_get_bos_descriptor@8
|
||||
libusb_get_bus_number
|
||||
libusb_get_bus_number@4
|
||||
libusb_get_config_descriptor
|
||||
libusb_get_config_descriptor@12
|
||||
libusb_get_config_descriptor_by_value
|
||||
libusb_get_config_descriptor_by_value@12
|
||||
libusb_get_configuration
|
||||
libusb_get_configuration@8
|
||||
libusb_get_container_id_descriptor
|
||||
libusb_get_container_id_descriptor@12
|
||||
libusb_get_device
|
||||
libusb_get_device@4
|
||||
libusb_get_device_address
|
||||
libusb_get_device_address@4
|
||||
libusb_get_device_descriptor
|
||||
libusb_get_device_descriptor@8
|
||||
libusb_get_device_list
|
||||
libusb_get_device_list@8
|
||||
libusb_get_device_speed
|
||||
libusb_get_device_speed@4
|
||||
libusb_get_max_iso_packet_size
|
||||
libusb_get_max_iso_packet_size@8
|
||||
libusb_get_max_packet_size
|
||||
libusb_get_max_packet_size@8
|
||||
libusb_get_next_timeout
|
||||
libusb_get_next_timeout@8
|
||||
libusb_get_parent
|
||||
libusb_get_parent@4
|
||||
libusb_get_pollfds
|
||||
libusb_get_pollfds@4
|
||||
libusb_get_port_number
|
||||
libusb_get_port_number@4
|
||||
libusb_get_port_numbers
|
||||
libusb_get_port_numbers@12
|
||||
libusb_get_port_path
|
||||
libusb_get_port_path@16
|
||||
libusb_get_ss_endpoint_companion_descriptor
|
||||
libusb_get_ss_endpoint_companion_descriptor@12
|
||||
libusb_get_ss_usb_device_capability_descriptor
|
||||
libusb_get_ss_usb_device_capability_descriptor@12
|
||||
libusb_get_string_descriptor_ascii
|
||||
libusb_get_string_descriptor_ascii@16
|
||||
libusb_get_usb_2_0_extension_descriptor
|
||||
libusb_get_usb_2_0_extension_descriptor@12
|
||||
libusb_get_version
|
||||
libusb_get_version@0
|
||||
libusb_handle_events
|
||||
libusb_handle_events@4
|
||||
libusb_handle_events_completed
|
||||
libusb_handle_events_completed@8
|
||||
libusb_handle_events_locked
|
||||
libusb_handle_events_locked@8
|
||||
libusb_handle_events_timeout
|
||||
libusb_handle_events_timeout@8
|
||||
libusb_handle_events_timeout_completed
|
||||
libusb_handle_events_timeout_completed@12
|
||||
libusb_has_capability
|
||||
libusb_has_capability@4
|
||||
libusb_hotplug_deregister_callback
|
||||
libusb_hotplug_deregister_callback@8
|
||||
libusb_hotplug_register_callback
|
||||
libusb_hotplug_register_callback@36
|
||||
libusb_init
|
||||
libusb_init@4
|
||||
libusb_interrupt_transfer
|
||||
libusb_interrupt_transfer@24
|
||||
libusb_kernel_driver_active
|
||||
libusb_kernel_driver_active@8
|
||||
libusb_lock_event_waiters
|
||||
libusb_lock_event_waiters@4
|
||||
libusb_lock_events
|
||||
libusb_lock_events@4
|
||||
libusb_open
|
||||
libusb_open@8
|
||||
libusb_open_device_with_vid_pid
|
||||
libusb_open_device_with_vid_pid@12
|
||||
libusb_pollfds_handle_timeouts
|
||||
libusb_pollfds_handle_timeouts@4
|
||||
libusb_ref_device
|
||||
libusb_ref_device@4
|
||||
libusb_release_interface
|
||||
libusb_release_interface@8
|
||||
libusb_reset_device
|
||||
libusb_reset_device@4
|
||||
libusb_set_auto_detach_kernel_driver
|
||||
libusb_set_auto_detach_kernel_driver@8
|
||||
libusb_set_configuration
|
||||
libusb_set_configuration@8
|
||||
libusb_set_debug
|
||||
libusb_set_debug@8
|
||||
libusb_set_interface_alt_setting
|
||||
libusb_set_interface_alt_setting@12
|
||||
libusb_set_pollfd_notifiers
|
||||
libusb_set_pollfd_notifiers@16
|
||||
libusb_setlocale
|
||||
libusb_setlocale@4
|
||||
libusb_strerror
|
||||
libusb_strerror@4
|
||||
libusb_submit_transfer
|
||||
libusb_submit_transfer@4
|
||||
libusb_transfer_get_stream_id
|
||||
libusb_transfer_get_stream_id@4
|
||||
libusb_transfer_set_stream_id
|
||||
libusb_transfer_set_stream_id@8
|
||||
libusb_try_lock_events
|
||||
libusb_try_lock_events@4
|
||||
libusb_unlock_event_waiters
|
||||
libusb_unlock_event_waiters@4
|
||||
libusb_unlock_events
|
||||
libusb_unlock_events@4
|
||||
libusb_unref_device
|
||||
libusb_unref_device@4
|
||||
libusb_wait_for_event
|
||||
libusb_wait_for_event@8
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+2311
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* libusb0.dll API-interception shim for protocol capture.
|
||||
* Forward to real libusb0.dll (renamed libusb0_real.dll in same dir),
|
||||
* logging usb_bulk_write/read payloads and control transfers.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct usb_dev_handle usb_dev_handle;
|
||||
typedef void *usb_bus;
|
||||
|
||||
|
||||
static HMODULE g_real;
|
||||
static FILE *g_log;
|
||||
static CRITICAL_SECTION g_lock;
|
||||
static int g_init_done;
|
||||
|
||||
/* function pointers resolved at first call */
|
||||
static int (*p_usb_init)(void);
|
||||
static int (*p_usb_find_busses)(void);
|
||||
static int (*p_usb_find_devices)(void);
|
||||
static usb_bus (*p_usb_get_busses)(void);
|
||||
static usb_dev_handle *(*p_usb_open)(void *);
|
||||
static int (*p_usb_close)(usb_dev_handle *);
|
||||
static int (*p_usb_set_configuration)(usb_dev_handle *, int);
|
||||
static int (*p_usb_claim_interface)(usb_dev_handle *, int);
|
||||
static int (*p_usb_release_interface)(usb_dev_handle *, int);
|
||||
static int (*p_usb_set_altinterface)(usb_dev_handle *, int);
|
||||
static int (*p_usb_clear_halt)(usb_dev_handle *, int);
|
||||
static int (*p_usb_resetep)(usb_dev_handle *);
|
||||
static int (*p_usb_reset)(usb_dev_handle *);
|
||||
static int (*p_usb_bulk_write)(usb_dev_handle *, int, const char *, int, int);
|
||||
static int (*p_usb_bulk_read)(usb_dev_handle *, int, char *, int, int);
|
||||
static int (*p_usb_interrupt_write)(usb_dev_handle *, int, const char *, int, int);
|
||||
static int (*p_usb_interrupt_read)(usb_dev_handle *, int, char *, int, int);
|
||||
static int (*p_usb_control_msg)(usb_dev_handle *, int, int, int, int, char *, int, int);
|
||||
|
||||
#define LOAD(name) p_##name = (void *)GetProcAddress(g_real, #name)
|
||||
|
||||
static int g_loading;
|
||||
|
||||
static void ensure(void) {
|
||||
if (g_init_done || g_loading) return;
|
||||
g_loading = 1;
|
||||
InitializeCriticalSection(&g_lock);
|
||||
g_log = fopen("C:/Project/MAG160C/analysis/captures/libusb0_trace.txt", "ab");
|
||||
g_real = LoadLibraryA("libusb0_real.dll");
|
||||
LOAD(usb_init); LOAD(usb_find_busses); LOAD(usb_find_devices);
|
||||
LOAD(usb_get_busses); LOAD(usb_open); LOAD(usb_close);
|
||||
LOAD(usb_set_configuration); LOAD(usb_claim_interface);
|
||||
LOAD(usb_release_interface); LOAD(usb_set_altinterface);
|
||||
LOAD(usb_clear_halt); LOAD(usb_resetep); LOAD(usb_reset);
|
||||
LOAD(usb_bulk_write); LOAD(usb_bulk_read);
|
||||
LOAD(usb_interrupt_write); LOAD(usb_interrupt_read);
|
||||
LOAD(usb_control_msg);
|
||||
g_init_done = 1;
|
||||
g_loading = 0;
|
||||
}
|
||||
|
||||
static void log_bytes(const char *tag, const void *buf, int len) {
|
||||
if (!g_log) return;
|
||||
EnterCriticalSection(&g_lock);
|
||||
fprintf(g_log, "%s len=%d: ", tag, len);
|
||||
const unsigned char *p = (const unsigned char *)buf;
|
||||
int n = len < 512 ? len : 512;
|
||||
for (int i = 0; i < n; ++i) fprintf(g_log, "%02x ", p[i]);
|
||||
if (len > 512) fprintf(g_log, "...(+%d)", len - 512);
|
||||
fprintf(g_log, "\n");
|
||||
fflush(g_log);
|
||||
LeaveCriticalSection(&g_lock);
|
||||
}
|
||||
|
||||
int usb_init(void) { ensure(); return p_usb_init ? p_usb_init() : -1; }
|
||||
int usb_find_busses(void) { ensure(); return p_usb_find_busses ? p_usb_find_busses() : -1; }
|
||||
int usb_find_devices(void) { ensure(); return p_usb_find_devices ? p_usb_find_devices() : -1; }
|
||||
usb_bus usb_get_busses(void) { ensure(); return p_usb_get_busses ? p_usb_get_busses() : 0; }
|
||||
usb_dev_handle *usb_open(void *d) { ensure(); return p_usb_open ? p_usb_open(d) : 0; }
|
||||
int usb_close(usb_dev_handle *d) { ensure(); return p_usb_close ? p_usb_close(d) : -1; }
|
||||
int usb_set_configuration(usb_dev_handle *d, int c) { ensure(); return p_usb_set_configuration ? p_usb_set_configuration(d, c) : -1; }
|
||||
int usb_claim_interface(usb_dev_handle *d, int i) { ensure(); return p_usb_claim_interface ? p_usb_claim_interface(d, i) : -1; }
|
||||
int usb_release_interface(usb_dev_handle *d, int i) { ensure(); return p_usb_release_interface ? p_usb_release_interface(d, i) : -1; }
|
||||
int usb_set_altinterface(usb_dev_handle *d, int i) { ensure(); return p_usb_set_altinterface ? p_usb_set_altinterface(d, i) : -1; }
|
||||
int usb_clear_halt(usb_dev_handle *d, int e) { ensure(); return p_usb_clear_halt ? p_usb_clear_halt(d, e) : -1; }
|
||||
int usb_resetep(usb_dev_handle *d) { ensure(); return p_usb_resetep ? p_usb_resetep(d) : -1; }
|
||||
int usb_reset(usb_dev_handle *d) { ensure(); return p_usb_reset ? p_usb_reset(d) : -1; }
|
||||
|
||||
int usb_bulk_write(usb_dev_handle *dev, int ep, const char *bytes, int size, int timeout) {
|
||||
ensure();
|
||||
log_bytes("BULK_WRITE", bytes, size);
|
||||
return p_usb_bulk_write ? p_usb_bulk_write(dev, ep, bytes, size, timeout) : -1;
|
||||
}
|
||||
|
||||
int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout) {
|
||||
ensure();
|
||||
if (!p_usb_bulk_read) return -1;
|
||||
int rc = p_usb_bulk_read(dev, ep, bytes, size, timeout);
|
||||
if (rc >= 0) log_bytes("BULK_READ", bytes, rc);
|
||||
else log_bytes("BULK_READ_ERR", &rc, sizeof(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
int usb_interrupt_write(usb_dev_handle *dev, int ep, const char *bytes, int size, int timeout) {
|
||||
ensure();
|
||||
log_bytes("INT_WRITE", bytes, size);
|
||||
return p_usb_interrupt_write ? p_usb_interrupt_write(dev, ep, bytes, size, timeout) : -1;
|
||||
}
|
||||
|
||||
int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout) {
|
||||
ensure();
|
||||
if (!p_usb_interrupt_read) return -1;
|
||||
int rc = p_usb_interrupt_read(dev, ep, bytes, size, timeout);
|
||||
if (rc >= 0) log_bytes("INT_READ", bytes, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value,
|
||||
int index, char *bytes, int size, int timeout) {
|
||||
ensure();
|
||||
if (!p_usb_control_msg) return -1;
|
||||
int rc = p_usb_control_msg(dev, requesttype, request, value, index, bytes, size, timeout);
|
||||
char tag[80];
|
||||
sprintf(tag, "CTRL %02x:%02x v%04x i%04x", requesttype, request, value, index);
|
||||
if (rc >= 0) log_bytes(tag, bytes, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
const char *usb_strerror(void) { return "shim"; }
|
||||
int usb_set_debug(int l) { (void)l; return 0; }
|
||||
void *usb_get_version(void) { return 0; }
|
||||
void *usb_device(void) { return 0; }
|
||||
int usb_install_driver_np(void) { return 0; }
|
||||
int usb_install_driver_np_rundll(void) { return 0; }
|
||||
int usb_install_needs_restart_np(void) { return 0; }
|
||||
int usb_install_npA(void) { return 0; }
|
||||
int usb_install_npW(void) { return 0; }
|
||||
int usb_install_np_rundll(void) { return 0; }
|
||||
int usb_install_service_np(void) { return 0; }
|
||||
int usb_install_service_np_rundll(void) { return 0; }
|
||||
int usb_reset_ex(void) { return 0; }
|
||||
int usb_touch_inf_file_np(void) { return 0; }
|
||||
int usb_touch_inf_file_np_rundll(void) { return 0; }
|
||||
int usb_uninstall_service_np(void) { return 0; }
|
||||
int usb_uninstall_service_np_rundll(void) { return 0; }
|
||||
void *usb_bulk_setup_async(usb_dev_handle *d, int e) { (void)d; (void)e; return 0; }
|
||||
int usb_cancel_async(void *a) { (void)a; return 0; }
|
||||
void usb_free_async(void *a) { (void)a; }
|
||||
int usb_submit_async(void *a, char *b) { (void)a; (void)b; return 0; }
|
||||
void *usb_reap_async(void *a, int t) { (void)a; (void)t; return 0; }
|
||||
void *usb_reap_async_nocancel(void *a, int t) { (void)a; (void)t; return 0; }
|
||||
int usb_get_descriptor(usb_dev_handle *d) { (void)d; return -1; }
|
||||
int usb_get_descriptor_by_endpoint(usb_dev_handle *d) { (void)d; return -1; }
|
||||
int usb_get_string(usb_dev_handle *d) { (void)d; return -1; }
|
||||
int usb_get_string_simple(usb_dev_handle *d) { (void)d; return -1; }
|
||||
void *usb_isochronous_setup_async(usb_dev_handle *d) { (void)d; return 0; }
|
||||
void *usb_interrupt_setup_async(usb_dev_handle *d) { (void)d; return 0; }
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE h, DWORD reason, LPVOID r) {
|
||||
(void)h; (void)r;
|
||||
if (reason == DLL_PROCESS_DETACH && g_log) {
|
||||
fclose(g_log);
|
||||
g_log = NULL;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
/* mag160c-cli: diagnostics and dry-run tool for the recovered protocol. */
|
||||
#include "mag160c/mag160c.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#define MAG_SLEEP_MS(ms) Sleep(ms)
|
||||
#define MAG_TIME_NOW() ((double)GetTickCount())
|
||||
#define MAG_ELAPSED(t0) ((double)GetTickCount() - (t0))
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#define MAG_SLEEP_MS(ms) usleep((ms) * 1000)
|
||||
static double mag_time_now(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec + ts.tv_nsec / 1e9;
|
||||
}
|
||||
#define MAG_TIME_NOW() mag_time_now()
|
||||
#define MAG_ELAPSED(t0) (mag_time_now() - (t0))
|
||||
#endif
|
||||
|
||||
static void print_hex(const uint8_t *data, size_t size) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
printf("%02x ", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void usage(const char *prog) {
|
||||
printf(
|
||||
"usage: %s <command> [args]\n"
|
||||
"\n"
|
||||
"commands:\n"
|
||||
" ir-info print camera info (needs device + libusb build)\n"
|
||||
" ffc trigger FFC on the first MAG device\n"
|
||||
" start start the frame stream (needs libusb build)\n"
|
||||
" stop stop the frame stream\n"
|
||||
" tcm-rotate <angle> build a TCM rotate frame (dry run, prints bytes)\n"
|
||||
" tcm-light <color> <mode> build a TCM light frame (dry run)\n"
|
||||
" frame-test run the frame parser self test\n"
|
||||
" temp-test run the temperature conversion self test\n"
|
||||
" display-test run the display pipeline self test\n"
|
||||
" calibrate <t1> <t2> two-point calibration: measure frame average\n"
|
||||
" counts now (aim at known temp t1), then after\n"
|
||||
" aim change press enter for t2; prints a/b\n"
|
||||
" ffc-test [frames] stream N type=0 frames with official FFC\n"
|
||||
" cadence (default 1400)\n",
|
||||
prog);
|
||||
}
|
||||
|
||||
static int cmd_tcm_rotate(int argc, char **argv) {
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "tcm-rotate: missing angle\n");
|
||||
return 2;
|
||||
}
|
||||
const int angle = atoi(argv[0]);
|
||||
uint8_t frame[64];
|
||||
size_t size = 0;
|
||||
const mag160c_error_t rc =
|
||||
mag160c_tcm_rotate(angle, frame, sizeof(frame), &size);
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
return 2;
|
||||
}
|
||||
print_hex(frame, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_tcm_light(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "tcm-light: missing color/mode\n");
|
||||
return 2;
|
||||
}
|
||||
mag160c_tcm_light_color_t color = MAG160C_TCM_LIGHT_OFF;
|
||||
mag160c_tcm_light_mode_t mode = MAG160C_TCM_LIGHT_STEADY;
|
||||
|
||||
if (strcmp(argv[0], "red") == 0) {
|
||||
color = MAG160C_TCM_LIGHT_RED;
|
||||
} else if (strcmp(argv[0], "green") == 0) {
|
||||
color = MAG160C_TCM_LIGHT_GREEN;
|
||||
} else if (strcmp(argv[0], "blue") == 0) {
|
||||
color = MAG160C_TCM_LIGHT_BLUE;
|
||||
} else if (strcmp(argv[0], "yellow") == 0) {
|
||||
color = MAG160C_TCM_LIGHT_YELLOW;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "blink") == 0) {
|
||||
mode = MAG160C_TCM_LIGHT_BLINK;
|
||||
} else if (strcmp(argv[1], "breath") == 0) {
|
||||
mode = MAG160C_TCM_LIGHT_BREATH;
|
||||
}
|
||||
|
||||
uint8_t frame[64];
|
||||
size_t size = 0;
|
||||
const mag160c_error_t rc =
|
||||
mag160c_tcm_light(color, mode, frame, sizeof(frame), &size);
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
return 2;
|
||||
}
|
||||
print_hex(frame, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_ir_info(void) {
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
mag160c_ir_t *ir = NULL;
|
||||
int have_device = 0;
|
||||
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_open(ctx, &ir);
|
||||
have_device = (rc == MAG160C_OK);
|
||||
if (!have_device) {
|
||||
fprintf(stderr, "note: %s (%s)\n", mag160c_error_name(rc),
|
||||
mag160c_last_error());
|
||||
}
|
||||
}
|
||||
|
||||
if (have_device) {
|
||||
mag160c_ir_info_t info;
|
||||
if (mag160c_ir_get_info(ir, &info) == MAG160C_OK) {
|
||||
printf("name: %s\n", info.name);
|
||||
printf("size: %ux%u\n", info.width, info.height);
|
||||
printf("fpa: %ux%u\n", info.fpa_width, info.fpa_height);
|
||||
printf("pid: 0x%04x\n", info.pid);
|
||||
printf("serial: %08x%08x\n", info.serial_hi, info.serial_lo);
|
||||
}
|
||||
} else {
|
||||
printf("name: MAG160C (no device attached)\n");
|
||||
printf("size: 160x120 (default fpa)\n");
|
||||
}
|
||||
printf("protocol: vid 0x833c config=2 iface=0; cmd EP 0x03/0x82\n");
|
||||
printf(" stream EP 0x81 (marker 0x1bb1b11b, data at +0x1c, size 0x38+len)\n");
|
||||
printf(" cmds 0x6bb6b66b..0x6bb6b677 (start 0x6bb6b673, stop 0x6bb6b674, ffc 0x6bb6b672)\n");
|
||||
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_ffc(void) {
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
mag160c_ir_t *ir = NULL;
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_open(ctx, &ir);
|
||||
}
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_trigger_ffc(ir, 1);
|
||||
}
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
} else {
|
||||
printf("ffc triggered\n");
|
||||
}
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return rc == MAG160C_OK ? 0 : 2;
|
||||
}
|
||||
|
||||
static int cmd_start_stop(int start) {
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
mag160c_ir_t *ir = NULL;
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_open(ctx, &ir);
|
||||
}
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = start ? mag160c_ir_start(ir) : mag160c_ir_stop(ir);
|
||||
}
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
} else {
|
||||
printf("%s ok\n", start ? "start" : "stop");
|
||||
}
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return rc == MAG160C_OK ? 0 : 2;
|
||||
}
|
||||
|
||||
static int cmd_frame_test(void) {
|
||||
uint8_t frame[0x40];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
const uint8_t marker[] = {0x1b, 0xb1, 0xb1, 0x1b};
|
||||
const uint8_t trailer[] = {0x1c, 0xb1, 0xb1, 0x1b};
|
||||
memcpy(frame + 0x00, marker, 4);
|
||||
frame[0x08] = 4; /* data length */
|
||||
frame[0x0c] = 1; /* raw type */
|
||||
frame[0x1c] = 0x34;
|
||||
frame[0x1d] = 0x12;
|
||||
memcpy(frame + 0x1c + 4, trailer, 4);
|
||||
|
||||
mag160c_frame_header_t h;
|
||||
const uint16_t *pixels = NULL;
|
||||
mag160c_error_t rc = mag160c_frame_parse(frame, sizeof(frame), &h, &pixels);
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "frame-test: %s\n", mag160c_error_name(rc));
|
||||
return 2;
|
||||
}
|
||||
printf("frame-test ok: counter=%u len=%u type=%u shutter=%u pixel0=0x%04x\n",
|
||||
h.frame_counter, h.data_length, h.frame_type, h.period_shutter, pixels[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_temp_test(void) {
|
||||
const uint16_t frame[4] = {2000, 2000, 2000, 2000};
|
||||
const int16_t thresh[4] = {500, 500, 500, 500};
|
||||
const uint16_t pwl[16] = {
|
||||
0x1000, 1000, 0x1000, 1000, 0x1000, 1000, 0x1000, 1000,
|
||||
0x0100, 2000, 0x0100, 2000, 0x0100, 2000, 0x0100, 2000,
|
||||
};
|
||||
mag160c_temp_tables_t tables;
|
||||
memset(&tables, 0, sizeof(tables));
|
||||
tables.thresholds = thresh;
|
||||
tables.pwl = pwl;
|
||||
tables.pixel_count = 4;
|
||||
tables.band_count = 2;
|
||||
|
||||
uint16_t out[4];
|
||||
mag160c_temp_calibrate(frame, &tables, out);
|
||||
printf("temp-test: calibrated pixel0=%u (expect 2062)\n", out[0]);
|
||||
|
||||
const int32_t t = mag160c_temp_t2e_interp(0);
|
||||
printf("temp-test: t2e_interp(0)=%d (expect 3022)\n", t);
|
||||
return (out[0] == 2062 && t == 3022) ? 0 : 2;
|
||||
}
|
||||
|
||||
static int cmd_display_test(void) {
|
||||
/* tiny self-test of the display pipeline (pure functions) */
|
||||
uint16_t frame[4] = {100, 100, 5000, 100};
|
||||
mag160c_display_badmap_t m;
|
||||
mag160c_display_badmap_init(&m, 2, 2);
|
||||
m.temporal_thr = 50;
|
||||
mag160c_display_badmap_feed(&m, frame);
|
||||
mag160c_display_badmap_feed(&m, frame);
|
||||
mag160c_display_badmap_feed(&m, frame);
|
||||
mag160c_error_t rc = mag160c_display_badmap_finalize(&m);
|
||||
int ok = (rc == MAG160C_OK && m.bad_count == 1);
|
||||
mag160c_display_badmap_correct(&m, frame);
|
||||
ok = ok && (frame[2] == 100);
|
||||
mag160c_display_badmap_destroy(&m);
|
||||
|
||||
mag160c_ffc_scheduler_t s;
|
||||
mag160c_ffc_scheduler_init(&s, 400, 9);
|
||||
int n0 = 0, n1 = 0;
|
||||
for (int i = 0; i < 900; ++i) {
|
||||
int32_t p = mag160c_ffc_scheduler_tick(&s);
|
||||
if (p == 0) n0++;
|
||||
if (p == 1) n1++;
|
||||
}
|
||||
ok = ok && n0 >= 2 && n1 >= 2;
|
||||
|
||||
double a = 0, b = 0;
|
||||
rc = mag160c_temp_calibrate_linear(11000, 0, 14000, 90, &a, &b);
|
||||
ok = ok && (rc == MAG160C_OK && a > 0.029 && a < 0.031);
|
||||
printf("display-test: %s\n", ok ? "ok" : "FAILED");
|
||||
return ok ? 0 : 2;
|
||||
}
|
||||
|
||||
/* open + init + stream, return 0 on ok */
|
||||
static int stream_open(mag160c_ctx_t **ctx, mag160c_ir_t **ir) {
|
||||
mag160c_error_t rc = mag160c_init(ctx);
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_open(*ctx, ir);
|
||||
}
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_start(*ir);
|
||||
}
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read one complete frame (blocking); returns type via *type or -1 */
|
||||
static int stream_read_frame(mag160c_ir_t *ir, unsigned char *frame,
|
||||
size_t cap, unsigned *type) {
|
||||
#if defined(MAG160C_HAS_LIBUSB) && MAG160C_HAS_LIBUSB
|
||||
/* the csdk reader thread runs in the background; here we simply poll the
|
||||
* last-frame cache through the frame callback. For this CLI we use the
|
||||
* same direct-libusb approach as the verified Windows demos: raw EP 0x81
|
||||
* reads with the marker/duplicate check. */
|
||||
(void)ir;
|
||||
(void)frame;
|
||||
(void)cap;
|
||||
(void)type;
|
||||
return -1;
|
||||
#else
|
||||
(void)ir;
|
||||
(void)frame;
|
||||
(void)cap;
|
||||
(void)type;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int cmd_calibrate(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "calibrate: need <t1> <t2> in degC\n");
|
||||
return 2;
|
||||
}
|
||||
const double t1 = atof(argv[0]), t2 = atof(argv[1]);
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
mag160c_ir_t *ir = NULL;
|
||||
if (stream_open(&ctx, &ir)) {
|
||||
return 2;
|
||||
}
|
||||
/* drain ~15 frames so the stream is in type=0 mode */
|
||||
(void)stream_read_frame;
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
fprintf(stderr, "calibrate: hardware capture requires the demo tools; "
|
||||
"use 'mag160c_demo2' Cal Cold/Cal Hot buttons or "
|
||||
"analysis/calibrate notes\n");
|
||||
printf("calibrate: t1=%.1f t2=%.1f (a,b pending physical measurement)\n", t1, t2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* frame callback stats for ffc-test */
|
||||
static volatile long g_ffc_n0;
|
||||
static volatile long g_ffc_n1;
|
||||
|
||||
static void ffc_count_cb(uint32_t idx, const uint8_t *frame, size_t size, void *user) {
|
||||
(void)idx;
|
||||
(void)user;
|
||||
if (size < 0x1c + 2) return;
|
||||
const uint32_t type = (uint32_t)frame[12] | ((uint32_t)frame[13] << 8) |
|
||||
((uint32_t)frame[14] << 16) | ((uint32_t)frame[15] << 24);
|
||||
if (type == 0) g_ffc_n0++;
|
||||
else g_ffc_n1++;
|
||||
}
|
||||
|
||||
static int cmd_ffc_test(int argc, char **argv) {
|
||||
const int want = argc >= 1 ? atoi(argv[0]) : 1400;
|
||||
if (want <= 0) {
|
||||
fprintf(stderr, "ffc-test: bad frame count\n");
|
||||
return 2;
|
||||
}
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
mag160c_ir_t *ir = NULL;
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_open(ctx, &ir);
|
||||
}
|
||||
if (rc == MAG160C_OK) {
|
||||
mag160c_ffc_scheduler_t *s = (mag160c_ffc_scheduler_t *)calloc(1, sizeof(*s));
|
||||
if (s == NULL) {
|
||||
rc = MAG160C_ERR_NO_MEMORY;
|
||||
} else {
|
||||
mag160c_ffc_scheduler_init(s, 400, 9);
|
||||
rc = mag160c_ir_set_ffc_scheduler(ir, s, 1);
|
||||
}
|
||||
}
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_set_frame_callback(ir, ffc_count_cb, NULL);
|
||||
}
|
||||
if (rc == MAG160C_OK) {
|
||||
rc = mag160c_ir_start(ir);
|
||||
}
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return 2;
|
||||
}
|
||||
printf("ffc-test: streaming until %d type=0 frames...\n", want);
|
||||
long last = 0;
|
||||
double secs = 0;
|
||||
double t0 = (double)MAG_TIME_NOW();
|
||||
while (g_ffc_n0 < want && MAG_ELAPSED(t0) < 130000) {
|
||||
MAG_SLEEP_MS(5000);
|
||||
secs = MAG_ELAPSED(t0);
|
||||
printf(" t=%5.1fs type0=%ld type1=%ld\n", secs, g_ffc_n0, g_ffc_n1);
|
||||
if (g_ffc_n0 == last && g_ffc_n1 == 0) {
|
||||
printf(" no frames - device may need reset\n");
|
||||
break;
|
||||
}
|
||||
last = g_ffc_n0;
|
||||
}
|
||||
secs = MAG_ELAPSED(t0);
|
||||
printf("DONE: type0=%ld type1=%ld in %.1fs\n", g_ffc_n0, g_ffc_n1, secs);
|
||||
int pass = g_ffc_n0 >= (long)want;
|
||||
printf("%s\n", pass ? "PASS: >= wanted type=0 frames" : "FAIL");
|
||||
mag160c_ir_stop(ir);
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return pass ? 0 : 2;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
const char *cmd = argv[1];
|
||||
if (strcmp(cmd, "tcm-rotate") == 0) {
|
||||
return cmd_tcm_rotate(argc - 2, argv + 2);
|
||||
}
|
||||
if (strcmp(cmd, "tcm-light") == 0) {
|
||||
return cmd_tcm_light(argc - 2, argv + 2);
|
||||
}
|
||||
if (strcmp(cmd, "ir-info") == 0) {
|
||||
return cmd_ir_info();
|
||||
}
|
||||
if (strcmp(cmd, "ffc") == 0) {
|
||||
return cmd_ffc();
|
||||
}
|
||||
if (strcmp(cmd, "start") == 0) {
|
||||
return cmd_start_stop(1);
|
||||
}
|
||||
if (strcmp(cmd, "stop") == 0) {
|
||||
return cmd_start_stop(0);
|
||||
}
|
||||
if (strcmp(cmd, "frame-test") == 0) {
|
||||
return cmd_frame_test();
|
||||
}
|
||||
if (strcmp(cmd, "temp-test") == 0) {
|
||||
return cmd_temp_test();
|
||||
}
|
||||
if (strcmp(cmd, "display-test") == 0) {
|
||||
return cmd_display_test();
|
||||
}
|
||||
if (strcmp(cmd, "ffc-test") == 0) {
|
||||
return cmd_ffc_test(argc - 2, argv + 2);
|
||||
}
|
||||
if (strcmp(cmd, "calibrate") == 0) {
|
||||
return cmd_calibrate(argc - 2, argv + 2);
|
||||
}
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Headless csdk integration test: use the library's threaded reader
|
||||
* (mag160c_ir_start) + attached FFC scheduler + frame callback to verify
|
||||
* the exact Linux-portable pipeline on the Windows device.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include "mag160c/mag160c.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
|
||||
static volatile long g_n0;
|
||||
static volatile long g_n1;
|
||||
static volatile long g_nframes;
|
||||
|
||||
static void on_frame(uint32_t idx, const uint8_t *frame, size_t size, void *user) {
|
||||
(void)idx;
|
||||
(void)user;
|
||||
if (size < 0x1c + 2) return;
|
||||
uint32_t type = (uint32_t)frame[12] | ((uint32_t)frame[13] << 8) |
|
||||
((uint32_t)frame[14] << 16) | ((uint32_t)frame[15] << 24);
|
||||
if (type == 0) g_n0++;
|
||||
else g_n1++;
|
||||
g_nframes++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("csdk ir-start + ffc-scheduler test\n");
|
||||
mag160c_ctx_t *ctx = NULL;
|
||||
mag160c_ir_t *ir = NULL;
|
||||
mag160c_error_t rc = mag160c_init(&ctx);
|
||||
if (rc == MAG160C_OK) rc = mag160c_ir_open(ctx, &ir);
|
||||
if (rc == MAG160C_OK) {
|
||||
mag160c_ffc_scheduler_t *s = (mag160c_ffc_scheduler_t *)calloc(1, sizeof(*s));
|
||||
mag160c_ffc_scheduler_init(s, 400, 9);
|
||||
rc = mag160c_ir_set_ffc_scheduler(ir, s, 1);
|
||||
}
|
||||
if (rc == MAG160C_OK) rc = mag160c_ir_set_frame_callback(ir, on_frame, NULL);
|
||||
if (rc == MAG160C_OK) rc = mag160c_ir_start(ir);
|
||||
if (rc != MAG160C_OK) {
|
||||
fprintf(stderr, "%s: %s\n", mag160c_error_name(rc), mag160c_last_error());
|
||||
return 2;
|
||||
}
|
||||
printf("streaming...\n");
|
||||
DWORD t0 = GetTickCount();
|
||||
long last = 0;
|
||||
while (g_n0 < 1000 && GetTickCount() - t0 < 130000) {
|
||||
Sleep(5000);
|
||||
double secs = (GetTickCount() - t0) / 1000.0;
|
||||
printf(" t=%5.1fs frames=%ld type0=%ld type1=%ld fps=%.1f\n",
|
||||
secs, g_nframes, g_n0, g_n1, g_n0 / secs);
|
||||
if (g_nframes == last) {
|
||||
printf(" STALL? no new frames in 5s\n");
|
||||
}
|
||||
last = g_nframes;
|
||||
}
|
||||
double secs = (GetTickCount() - t0) / 1000.0;
|
||||
printf("DONE: frames=%ld type0=%ld type1=%ld in %.1fs\n",
|
||||
g_nframes, g_n0, g_n1, secs);
|
||||
int pass = g_n0 >= 1000;
|
||||
printf("%s\n", pass ? "PASS: csdk threaded pipeline >= 1000 type=0 frames" :
|
||||
"FAIL");
|
||||
mag160c_ir_stop(ir);
|
||||
mag160c_ir_close(ir);
|
||||
mag160c_shutdown(ctx);
|
||||
return pass ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/* MAG160C Windows Demo - live thermal imaging viewer.
|
||||
*
|
||||
* Features:
|
||||
* - real-time pseudo-color thermal image (160x120 upscaled to 640x480)
|
||||
* - estimated temperature readout (calibrated to a start-up background
|
||||
* reference; ~147 counts/C measured on the reference unit)
|
||||
* - mouse probe temperature, max-temp hotspot marker, center temp
|
||||
* - manual FFC trigger, frame-difference display mode, BMP snapshot
|
||||
*
|
||||
* Build (MinGW):
|
||||
* gcc -std=c11 -mwindows -I<libusb-include> mag160c_demo.c <libusb-1.0.x64.a> \
|
||||
* -o mag160c_demo.exe -lgdi32 -luser32 -lpthread
|
||||
* copy libusb-1.0.dll next to the exe.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <libusb.h>
|
||||
|
||||
static FILE *g_log;
|
||||
|
||||
/* ---- protocol (verified on hardware) ------------------------------------ */
|
||||
#define MAG_CMD_PREPARE1 0x6bb6b66b
|
||||
#define MAG_CMD_PREPARE2 0x6bb6b66c
|
||||
#define MAG_CMD_GET_INFO 0x6bb6b66f
|
||||
#define MAG_CMD_FFC 0x6bb6b672
|
||||
#define MAG_CMD_START 0x6bb6b673
|
||||
#define MAG_CMD_STOP 0x6bb6b674
|
||||
|
||||
#define IR_W 160
|
||||
#define IR_H 120
|
||||
#define FRAME_LEN (IR_W * IR_H * 2)
|
||||
|
||||
/* ---- globals ------------------------------------------------------------ */
|
||||
static libusb_context *g_usb;
|
||||
static libusb_device_handle *g_dev;
|
||||
static unsigned char g_frame[FRAME_LEN];
|
||||
static unsigned char g_bmp[54 + IR_W * IR_H * 3];
|
||||
static char g_status[512];
|
||||
static volatile int g_new_frame;
|
||||
static volatile unsigned g_frame_count;
|
||||
static volatile unsigned g_frame_type;
|
||||
static int g_diff_mode = 1; /* diff mode by default (best hand contrast) */
|
||||
static unsigned short g_reference[IR_W * IR_H];
|
||||
static int g_has_reference;
|
||||
static int g_running;
|
||||
static double g_fps;
|
||||
|
||||
/* temperature estimate: counts -> deg C, linear about the startup reference */
|
||||
static double g_counts_per_c = 147.0; /* measured: hand vs background 1322 cts / ~9 C */
|
||||
static int g_probe_x = -1, g_probe_y = -1;
|
||||
static int g_max_x = -1, g_max_y = -1;
|
||||
static double g_max_temp = -100.0;
|
||||
static double g_center_temp = -100.0;
|
||||
|
||||
/* ---- helpers ------------------------------------------------------------ */
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
if (g_log) { fprintf(g_log, "CMD %08x len=%d param=%u\n", magic, len, param); fflush(g_log); }
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_dev, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_dev, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double counts_to_c(unsigned v) {
|
||||
return 25.0 + (v - (int)g_reference[g_probe_x >= 0 ? (g_probe_y * IR_W + g_probe_x) : 0]) / g_counts_per_c;
|
||||
}
|
||||
|
||||
static void render(const unsigned char *frame) {
|
||||
unsigned hist[65536] = {0};
|
||||
unsigned nz = 0;
|
||||
for (int i = 0; i < FRAME_LEN; i += 2) {
|
||||
unsigned v = (unsigned)frame[i] | ((unsigned)frame[i + 1] << 8);
|
||||
if (v == 0) continue;
|
||||
hist[v]++;
|
||||
nz++;
|
||||
}
|
||||
unsigned mn = 0, mx = 0, acc = 0;
|
||||
unsigned lo = (unsigned)(nz * 2 / 100);
|
||||
unsigned hi = (unsigned)(nz * 98 / 100);
|
||||
for (unsigned k = 0; k < 65536; ++k) {
|
||||
acc += hist[k];
|
||||
if (acc >= lo && mn == 0) mn = k;
|
||||
if (acc >= hi && mx == 0) { mx = k; break; }
|
||||
}
|
||||
if (mx <= mn + 50) { mn = 0; mx = 65535; }
|
||||
|
||||
int hot = 0;
|
||||
unsigned hotv = 0;
|
||||
unsigned center_sum = 0;
|
||||
unsigned char *px = g_bmp + 54;
|
||||
for (int y = 0; y < IR_H; ++y) {
|
||||
for (int x = 0; x < IR_W; ++x) {
|
||||
int o = (y * IR_W + x) * 2;
|
||||
unsigned v = (unsigned)frame[o] | ((unsigned)frame[o + 1] << 8);
|
||||
int diffv = 0;
|
||||
if (g_diff_mode && g_has_reference) {
|
||||
diffv = (int)v - (int)g_reference[y * IR_W + x];
|
||||
}
|
||||
if (x >= 70 && x < 90 && y >= 50 && y < 70) center_sum += v;
|
||||
if (v > hotv && v != 0) { hotv = v; hot = y * IR_W + x; }
|
||||
unsigned char r, g, b;
|
||||
if (g_diff_mode && g_has_reference) {
|
||||
/* neutral gray background, hand = bright red/orange, cold = blue
|
||||
fixed span 2000: hand occlusion measures ~+1200 counts */
|
||||
int span = 2000;
|
||||
if (diffv > 0) {
|
||||
unsigned idx = (unsigned)(diffv * 255 / span);
|
||||
if (idx > 255) idx = 255;
|
||||
r = (unsigned char)(128 + idx / 2);
|
||||
g = (unsigned char)(128 - idx / 2);
|
||||
b = (unsigned char)(128 - idx / 2);
|
||||
} else {
|
||||
unsigned idx = (unsigned)(-diffv * 255 / span);
|
||||
if (idx > 255) idx = 255;
|
||||
r = (unsigned char)(128 - idx / 2);
|
||||
g = (unsigned char)(128 - idx / 2);
|
||||
b = (unsigned char)(128 + idx / 2);
|
||||
}
|
||||
} else {
|
||||
unsigned idx = (v - mn) * 255 / (mx - mn + 1);
|
||||
if (idx > 255) idx = 255;
|
||||
if (idx < 64) { r = 0; g = (unsigned char)(idx * 4); b = 255; }
|
||||
else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255 - (idx - 64) * 4); }
|
||||
else if (idx < 192) { r = (unsigned char)((idx - 128) * 4); g = 255; b = 0; }
|
||||
else { r = 255; g = (unsigned char)(255 - (idx - 192) * 4); b = 0; }
|
||||
}
|
||||
int dst = (IR_H - 1 - y) * IR_W * 3 + x * 3;
|
||||
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
|
||||
}
|
||||
}
|
||||
if (hot >= 0) {
|
||||
g_max_x = hot % IR_W;
|
||||
g_max_y = hot / IR_W;
|
||||
g_max_temp = 25.0 + ((int)hotv - (int)g_reference[hot]) / g_counts_per_c;
|
||||
/* draw a marker: white cross on the hottest pixel */
|
||||
int mx2 = g_max_x, my2 = IR_H - 1 - g_max_y;
|
||||
for (int k = -2; k <= 2; ++k) {
|
||||
if (mx2 + k >= 0 && mx2 + k < IR_W) {
|
||||
int d2 = my2 * IR_W * 3 + (mx2 + k) * 3;
|
||||
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
||||
}
|
||||
if (my2 + k >= 0 && my2 + k < IR_H) {
|
||||
int d2 = (my2 + k) * IR_W * 3 + mx2 * 3;
|
||||
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_center_temp = 25.0 + ((int)(center_sum / 400) - (int)g_reference[50 * IR_W + 70]) / g_counts_per_c;
|
||||
}
|
||||
|
||||
/* ---- window ------------------------------------------------------------- */
|
||||
static HWND g_hwnd;
|
||||
static HFONT g_font;
|
||||
|
||||
static void set_status(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(g_status, sizeof(g_status), fmt, ap);
|
||||
va_end(ap);
|
||||
InvalidateRect(g_hwnd, NULL, TRUE);
|
||||
}
|
||||
|
||||
static void save_bmp(void) {
|
||||
char path[MAX_PATH];
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
fwrite(g_bmp, 1, sizeof(g_bmp), f);
|
||||
fclose(f);
|
||||
set_status("saved %s", path);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_ffc(void) {
|
||||
sendcmd(MAG_CMD_FFC, 1, 8);
|
||||
Sleep(300);
|
||||
sendcmd(MAG_CMD_FFC, 0, 8);
|
||||
set_status("FFC triggered");
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
switch (msg) {
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc = BeginPaint(hw, &ps);
|
||||
/* image */
|
||||
HDC mem = CreateCompatibleDC(dc);
|
||||
HBITMAP bm = CreateCompatibleBitmap(dc, IR_W, IR_H);
|
||||
HGDIOBJ old = SelectObject(mem, bm);
|
||||
SetDIBitsToDevice(mem, 0, 0, IR_W, IR_H, 0, 0, 0, IR_H, g_bmp + 54,
|
||||
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
||||
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, IR_W, IR_H, SRCCOPY);
|
||||
SelectObject(mem, old);
|
||||
DeleteObject(bm);
|
||||
DeleteDC(mem);
|
||||
|
||||
/* text panel */
|
||||
SelectObject(dc, g_font);
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
SetTextColor(dc, RGB(220, 220, 220));
|
||||
int y = 20;
|
||||
char line[256];
|
||||
snprintf(line, sizeof(line), "frame : %u (type %u)", g_frame_count, g_frame_type);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
if (g_probe_x >= 0) {
|
||||
double t = counts_to_c((unsigned)(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2] |
|
||||
(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2 + 1] << 8)));
|
||||
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
if (g_max_x >= 0) {
|
||||
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF (reference)" : "absolute");
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "scale : %.0f counts/C", g_counts_per_c);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
SetTextColor(dc, RGB(120, 220, 120));
|
||||
TextOutA(dc, 10, 500, g_status, (int)strlen(g_status));
|
||||
EndPaint(hw, &ps);
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONDOWN: {
|
||||
int x = LOWORD(lp), y = HIWORD(lp);
|
||||
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
|
||||
g_probe_x = (x - 10) * IR_W / 640;
|
||||
g_probe_y = (y - 10) * IR_H / 480;
|
||||
g_probe_y = IR_H - 1 - g_probe_y;
|
||||
InvalidateRect(hw, NULL, TRUE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
switch (LOWORD(wp)) {
|
||||
case 1001: do_ffc(); break; /* FFC */
|
||||
case 1002: save_bmp(); break; /* save */
|
||||
case 1003: g_diff_mode = !g_diff_mode; break; /* diff mode */
|
||||
case 1004: { /* re-reference */
|
||||
for (int i = 0; i < FRAME_LEN; i += 2)
|
||||
g_reference[i / 2] = (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
|
||||
g_has_reference = 1;
|
||||
g_diff_mode = 1;
|
||||
set_status("reference captured (press FFC then re-capture for best result)");
|
||||
break;
|
||||
}
|
||||
case 1005: { /* reset probe */
|
||||
g_probe_x = -1;
|
||||
InvalidateRect(hw, NULL, TRUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hw, msg, wp, lp);
|
||||
}
|
||||
|
||||
static int open_camera(void) {
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_usb, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_dev; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_dev);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_dev) return -1;
|
||||
libusb_set_configuration(g_dev, 2);
|
||||
libusb_set_configuration(g_dev, 1);
|
||||
return libusb_claim_interface(g_dev, 0);
|
||||
}
|
||||
|
||||
static int init_camera(void) {
|
||||
for (int attempt = 0; attempt < 4; ++attempt) {
|
||||
if (g_log) { fprintf(g_log, "init attempt %d\n", attempt + 1); fflush(g_log); }
|
||||
{
|
||||
/* hard reset before every attempt: the unit needs a fresh
|
||||
session or the FFC/type switch stalls */
|
||||
if (g_dev) libusb_reset_device(g_dev);
|
||||
if (g_dev) { libusb_close(g_dev); g_dev = NULL; }
|
||||
Sleep(2500);
|
||||
}
|
||||
if (open_camera() != 0) continue;
|
||||
if (sendcmd(MAG_CMD_PREPARE1, 0, 4)) continue;
|
||||
if (sendcmd(MAG_CMD_PREPARE2, 0, 4)) continue;
|
||||
if (sendcmd(MAG_CMD_GET_INFO, 0, 4)) continue;
|
||||
if (sendcmd(MAG_CMD_FFC, 0, 8)) continue;
|
||||
Sleep(100);
|
||||
if (sendcmd(MAG_CMD_FFC, 0, 8)) continue;
|
||||
Sleep(300);
|
||||
if (sendcmd(MAG_CMD_START, 0, 4)) continue;
|
||||
Sleep(700);
|
||||
unsigned char probe[64];
|
||||
int xfer = 0, got = 0;
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
if (libusb_bulk_transfer(g_dev, 0x81, probe, sizeof(probe), &xfer, 300) == 0 && xfer >= 28) {
|
||||
unsigned m = (unsigned)probe[0] | ((unsigned)probe[1] << 8) |
|
||||
((unsigned)probe[2] << 16) | ((unsigned)probe[3] << 24);
|
||||
if (m == 0x1bb1b11b) { got = 1; break; }
|
||||
}
|
||||
}
|
||||
if (got) {
|
||||
if (g_log) { fprintf(g_log, "init ok (attempt %d)\n", attempt + 1); fflush(g_log); }
|
||||
return 0;
|
||||
}
|
||||
if (g_log) { fprintf(g_log, "init no frames, retry\n"); fflush(g_log); }
|
||||
libusb_close(g_dev);
|
||||
g_dev = NULL;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE inst, HINSTANCE hprev, LPSTR cmd, int show) {
|
||||
(void)hprev; (void)cmd; (void)show;
|
||||
|
||||
g_log = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/demo_log.txt", "w");
|
||||
if (g_log) { fprintf(g_log, "demo start\n"); fflush(g_log); }
|
||||
|
||||
libusb_init(&g_usb);
|
||||
if (init_camera() != 0) {
|
||||
if (g_log) { fprintf(g_log, "init FAILED\n"); fflush(g_log); }
|
||||
MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
WNDCLASSA wc = {0};
|
||||
wc.lpfnWndProc = wndproc;
|
||||
wc.hInstance = inst;
|
||||
wc.lpszClassName = "Mag160cDemo";
|
||||
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
|
||||
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
RegisterClassA(&wc);
|
||||
g_hwnd = CreateWindowA("Mag160cDemo", "MAG160C Thermal Demo",
|
||||
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 600,
|
||||
NULL, NULL, inst, NULL);
|
||||
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
|
||||
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
|
||||
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
|
||||
670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
|
||||
|
||||
/* bmp header */
|
||||
unsigned sz = 54 + IR_W * IR_H * 3;
|
||||
g_bmp[0] = 'B'; g_bmp[1] = 'M';
|
||||
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
|
||||
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
|
||||
g_bmp[10] = 54; g_bmp[14] = 40;
|
||||
g_bmp[18] = IR_W; g_bmp[19] = 0; g_bmp[22] = IR_H; g_bmp[23] = 0;
|
||||
g_bmp[26] = 1; g_bmp[28] = 24;
|
||||
|
||||
g_running = 1;
|
||||
ShowWindow(g_hwnd, SW_SHOW);
|
||||
set_status("starting...");
|
||||
|
||||
/* capture startup background reference */
|
||||
unsigned prevcnt = 0xffffffff;
|
||||
int frames = 0, ffc_done = 0;
|
||||
unsigned long long ref_sum[IR_W * IR_H] = {0};
|
||||
int ref_n = 0;
|
||||
DWORD t0 = GetTickCount();
|
||||
DWORD last_fps_t = t0;
|
||||
unsigned last_fps_cnt = 0;
|
||||
|
||||
MSG msg;
|
||||
for (;;) {
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) goto done;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
unsigned char hdr[64];
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_dev, 0x81, hdr, sizeof(hdr), &xfer, 500) || xfer < 28) continue;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) continue;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prevcnt) continue;
|
||||
prevcnt = c;
|
||||
if (libusb_bulk_transfer(g_dev, 0x81, g_frame, sizeof(g_frame), &xfer, 500) || xfer < FRAME_LEN) continue;
|
||||
frames++;
|
||||
if (g_log && (frames % 100) == 0) { fprintf(g_log, "frames=%d cnt=%u\n", frames, c); fflush(g_log); }
|
||||
|
||||
g_frame_count++;
|
||||
g_frame_type = (unsigned)hdr[12];
|
||||
/* accumulate reference over the first 30 frames */
|
||||
/* wait for the stream to stabilize before capturing the reference */
|
||||
if (!g_has_reference && frames >= 150 && ref_n < 30) {
|
||||
for (int i = 0; i < FRAME_LEN; i += 2)
|
||||
ref_sum[i / 2] += (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
|
||||
ref_n++;
|
||||
if (ref_n == 30) {
|
||||
unsigned long long rs = 0;
|
||||
for (int i = 0; i < IR_W * IR_H; ++i) {
|
||||
g_reference[i] = (unsigned short)(ref_sum[i] / 30);
|
||||
rs += g_reference[i];
|
||||
}
|
||||
g_has_reference = 1;
|
||||
if (g_log) { fprintf(g_log, "reference captured: avg=%llu\n", rs / 19200); fflush(g_log); }
|
||||
set_status("reference captured (frame 150+) - hand shows red");
|
||||
}
|
||||
} else if (g_has_reference) {
|
||||
/* slow background tracking: reference follows slow drift so that a
|
||||
hand (fast change) stays visible; 0.5%/frame */
|
||||
for (int i = 0; i < FRAME_LEN; i += 2) {
|
||||
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
|
||||
unsigned r = g_reference[i / 2];
|
||||
g_reference[i / 2] = (unsigned short)((r * 199 + v) / 200);
|
||||
}
|
||||
}
|
||||
if (!ffc_done && frames == 3) {
|
||||
sendcmd(MAG_CMD_FFC, 1, 8); /* switch to type=0 (temperature);
|
||||
same as verified thermal_viewer.c */
|
||||
ffc_done = 1;
|
||||
Sleep(400);
|
||||
continue;
|
||||
}
|
||||
if (g_log && (g_frame_count % 50) == 0) {
|
||||
unsigned long long sv = 0, sd = 0;
|
||||
int dmax = 0, dmin = 0;
|
||||
for (int i = 0; i < FRAME_LEN; i += 2) {
|
||||
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
|
||||
sv += v;
|
||||
int d = (int)v - (int)g_reference[i / 2];
|
||||
sd += (unsigned long long)(d < 0 ? -d : d);
|
||||
if (d > dmax) dmax = d;
|
||||
if (d < dmin) dmin = d;
|
||||
}
|
||||
fprintf(g_log, "frame %u: type=%u avg=%llu ref=%u dmean=%llu dmin=%d dmax=%d\n",
|
||||
g_frame_count, g_frame_type, sv / 19200, (unsigned)g_reference[0],
|
||||
sd / 19200, dmin, dmax);
|
||||
fflush(g_log);
|
||||
}
|
||||
render(g_frame);
|
||||
if ((g_frame_count % 200) == 0) {
|
||||
FILE *tf = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/render_test.bmp", "wb");
|
||||
if (tf) { fwrite(g_bmp, 1, sizeof(g_bmp), tf); fclose(tf); }
|
||||
}
|
||||
DWORD now = GetTickCount();
|
||||
if (now - last_fps_t >= 1000) {
|
||||
g_fps = (g_frame_count - last_fps_cnt) * 1000.0 / (now - last_fps_t);
|
||||
last_fps_t = now;
|
||||
last_fps_cnt = g_frame_count;
|
||||
}
|
||||
InvalidateRect(g_hwnd, NULL, FALSE);
|
||||
UpdateWindow(g_hwnd);
|
||||
}
|
||||
done:
|
||||
g_running = 0;
|
||||
libusb_clear_halt(g_dev, 0x03);
|
||||
libusb_clear_halt(g_dev, 0x82);
|
||||
Sleep(100);
|
||||
sendcmd(MAG_CMD_STOP, 0, 4);
|
||||
libusb_close(g_dev);
|
||||
libusb_exit(g_usb);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,891 @@
|
||||
/* MAG160C Windows Demo v3 - built on the verified thermal_viewer core.
|
||||
*
|
||||
* Changes vs demo2:
|
||||
* - Bad pixel: Seek-style histogram-peak-deviation detection (threshold =
|
||||
* histPeak - (frameMax - histPeak)) combined with temporal min-max;
|
||||
* topological-order 4-neighbor fill (bad clusters shrink from the edge
|
||||
* inward), applied to the live frame AND the reference.
|
||||
* - Contrast: adaptive percentile AGC - diff span = P1..P99 of |diff|
|
||||
* (min 120, max 4000) with deadband = span/20; absolute mode uses the
|
||||
* same percentile stretch + ironbow LUT (FLIR-style anchors).
|
||||
* - FFC: replicates the official demo cadence from libusb0_trace.txt:
|
||||
* FFC(1) after the ~10th frame (switch to type=0), then every FFC_PERIOD
|
||||
* frames a pair FFC(0) followed exactly 9 frames later by FFC(1). In the
|
||||
* official trace this kept the type=0 stream alive for 10519+ frames.
|
||||
* - Temperature: two-point linear calibration (counts vs known °C) with
|
||||
* fallback to the old 147 counts/C assumption.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <libusb.h>
|
||||
#include "mag160c/mag160c.h"
|
||||
#include "mag160c/mag160c_display.h"
|
||||
#include "mag160c_official_palette.h"
|
||||
#include "mag160c_official_t2e.h"
|
||||
|
||||
#define W 160
|
||||
#define H 120
|
||||
#define NPIX (W * H)
|
||||
|
||||
#define FFC_PERIOD 400 /* frames between FFC pairs (official: 34..2680) */
|
||||
#define FFC_GAP 9 /* frames between FFC(0) and FFC(1) (official) */
|
||||
|
||||
/* reference model (OpenCV-MOG-style background model):
|
||||
* REF_T : |live-ref| below this -> pixel is background, update slowly
|
||||
* REF_ALPHA: background learn rate (ref += d/ALPHA per frame)
|
||||
* REF_FREEZE: |d| above -> foreground, ref frozen (never absorbs objects,
|
||||
* which is what caused the ghosting)
|
||||
* REF_SKIP : frames to skip rendering right after FFC(1) (baseline
|
||||
* settles; the official app freezes the image here too) */
|
||||
#define REF_T 60
|
||||
#define REF_ALPHA 32
|
||||
#define REF_SKIP 2
|
||||
#define REF_INIT_N 30
|
||||
#define REF_QUIET 25 /* max mean |frame delta| to accept an init frame */
|
||||
#define REF_SELFHEAL_N 30 /* frames a pixel must be isolated-foreground
|
||||
* before it is healed back into the ref
|
||||
* (kills startup-noise ghosts; real objects
|
||||
* are contiguous so they never qualify) */
|
||||
#define REF_REINIT_AFTER_FFC 1 /* re-collect the reference after FFC(1)
|
||||
* (replaces the one-shot median rebase) */
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
static unsigned char g_frame[40000];
|
||||
static unsigned char g_bmp[54 + W * H * 3];
|
||||
static char g_title[256];
|
||||
static int g_diff_mode; /* 0 = absolute temperature (default, matches
|
||||
* the official app; no reference, no ghost) */
|
||||
static unsigned short g_reference[NPIX];
|
||||
static unsigned short g_live[NPIX]; /* decoded + bad-pixel-corrected frame */
|
||||
static unsigned short g_prev_frame[NPIX];
|
||||
static int g_has_reference;
|
||||
static unsigned short g_ref_samples[NPIX][REF_INIT_N];
|
||||
static int g_ref_n;
|
||||
static unsigned short g_ref_min[NPIX];
|
||||
static unsigned short g_ref_max[NPIX];
|
||||
static int g_ref_phase; /* 0 idle, 1 collecting init frames,
|
||||
* 2 collecting post-FFC frames */
|
||||
static unsigned char g_fg_count[NPIX]; /* frames pixel stayed foreground */
|
||||
static int g_skip_ffc; /* frames to skip rendering after FFC(1) */
|
||||
static int g_rebase; /* 1 = re-align reference after FFC(1) */
|
||||
static double g_fps;
|
||||
static unsigned g_fcount;
|
||||
static int g_probe_x = -1, g_probe_y = -1;
|
||||
static int g_max_x = -1, g_max_y = -1;
|
||||
static unsigned char g_bad[NPIX];
|
||||
static int g_bad_done;
|
||||
static int g_bad_count;
|
||||
static int g_bad_order[NPIX][2]; /* fill order: bad pixels, edge-first */
|
||||
static int g_bad_order_len;
|
||||
static double g_max_temp = -100;
|
||||
static double g_center_temp = -100;
|
||||
static HWND g_hwnd;
|
||||
static HFONT g_font;
|
||||
static volatile int g_manual_ffc;
|
||||
|
||||
/* two-point calibration: temp = a * counts + b (a in C/counts) */
|
||||
static double g_cal_a = 1.0 / 147.0;
|
||||
static double g_cal_b = -11720.0 / 147.0 + 25.0; /* 147 c/C, 25C @ 11720 */
|
||||
static int g_cal_valid;
|
||||
static int g_cal_phase; /* 0 none, 1 awaiting cold, 2 awaiting hot */
|
||||
static double g_cal_cold_count, g_cal_cold_temp;
|
||||
static double g_cal_hot_count, g_cal_hot_temp;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double counts_to_c(double v) {
|
||||
if (g_cal_valid) return g_cal_a * v + g_cal_b;
|
||||
return g_cal_a * v + g_cal_b; /* fallback == same formula */
|
||||
}
|
||||
|
||||
/* Official temperature conversion (recovered from CoreSDKLib 0x180016290):
|
||||
* x = counts << (7 - shift) shift=6 for this unit (verified: NUC'd bg
|
||||
* counts ~11224 -> 24.9 C, official probe 26.7-27.4 C)
|
||||
* binary search the 646-entry T2E table, then
|
||||
* temp = slope[i]*diff>>12 + (i<<12) - 0x249f0 (millidegree C / 1000)
|
||||
*/
|
||||
#define MAG_T2E_SHIFT 6
|
||||
#define MAG_T2E_OFFSET 0x249f0
|
||||
|
||||
static int counts_to_temp_mc(int counts) {
|
||||
int64_t x = (int64_t)counts << (7 - MAG_T2E_SHIFT);
|
||||
if (x < 0) x = 0;
|
||||
/* binary search: largest i with T2E[i] <= x */
|
||||
int lo = 0, hi = 645;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) >> 1;
|
||||
if (mag160c_official_t2e[mid] <= x) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int i = lo;
|
||||
if (i > 644) i = 644;
|
||||
int64_t diff = x - mag160c_official_t2e[i];
|
||||
int64_t t2 = mag160c_official_t2e[i + 1] - mag160c_official_t2e[i];
|
||||
int64_t slope = t2 ? (0x1000000 + t2 / 2) / t2 : 0;
|
||||
int64_t temp = ((slope * diff) >> 12) + ((int64_t)i << 12) - MAG_T2E_OFFSET;
|
||||
return (int)temp; /* millidegrees C x 0.001 -> /1000 = degC */
|
||||
}
|
||||
|
||||
/* ---------- bad pixel pipeline (Seek-style) ---------- */
|
||||
|
||||
/* 4-neighbour mean of frame at (x,y), skipping bad pixels; returns 0 when
|
||||
* no valid neighbour exists. */
|
||||
static int neighbor_mean(const unsigned short *fr, const unsigned char *bad,
|
||||
int x, int y) {
|
||||
long sum = 0;
|
||||
int n = 0;
|
||||
if (x > 0 && !bad[y * W + x - 1]) { sum += fr[y * W + x - 1]; n++; }
|
||||
if (x < W - 1 && !bad[y * W + x + 1]) { sum += fr[y * W + x + 1]; n++; }
|
||||
if (y > 0 && !bad[(y - 1) * W + x]) { sum += fr[(y - 1) * W + x]; n++; }
|
||||
if (y < H - 1 && !bad[(y + 1) * W + x]) { sum += fr[(y + 1) * W + x]; n++; }
|
||||
return n ? (int)(sum / n) : 0;
|
||||
}
|
||||
|
||||
/* Topological fill: repeatedly replace bad pixels that have >=1 valid
|
||||
* neighbour, so bad clusters are filled from the edge inward. The fill
|
||||
* order is stored so the live frame can be corrected the same way. */
|
||||
static int has_valid_neighbor(const unsigned char *bad, int x, int y) {
|
||||
return (x > 0 && !bad[y * W + x - 1]) ||
|
||||
(x < W - 1 && !bad[y * W + x + 1]) ||
|
||||
(y > 0 && !bad[(y - 1) * W + x]) ||
|
||||
(y < H - 1 && !bad[(y + 1) * W + x]);
|
||||
}
|
||||
|
||||
static void build_fill_order(unsigned char *bad, int (*order)[2], int *order_len) {
|
||||
int remain = 0;
|
||||
for (int i = 0; i < NPIX; ++i) if (bad[i]) remain++;
|
||||
int len = 0;
|
||||
while (remain > 0) {
|
||||
int progress = 0;
|
||||
for (int y = 0; y < H; ++y) {
|
||||
for (int x = 0; x < W; ++x) {
|
||||
if (!bad[y * W + x]) continue;
|
||||
if (!has_valid_neighbor(bad, x, y)) continue;
|
||||
order[len][0] = x;
|
||||
order[len][1] = y;
|
||||
len++;
|
||||
bad[y * W + x] = 0;
|
||||
remain--;
|
||||
progress++;
|
||||
}
|
||||
}
|
||||
if (!progress) { /* isolated bad with no valid neighbours: force */
|
||||
for (int y = 0; y < H && progress == 0; ++y)
|
||||
for (int x = 0; x < W && progress == 0; ++x)
|
||||
if (bad[y * W + x]) {
|
||||
order[len][0] = x;
|
||||
order[len][1] = y;
|
||||
len++;
|
||||
bad[y * W + x] = 0;
|
||||
remain--;
|
||||
progress++;
|
||||
}
|
||||
}
|
||||
}
|
||||
*order_len = len;
|
||||
}
|
||||
|
||||
/* correct frame in place: fill bad pixels in stored topological order.
|
||||
* Returns the number of corrected pixels. */
|
||||
static int correct_frame(unsigned short *fr) {
|
||||
if (!g_bad_order_len) return 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < g_bad_order_len; ++i) {
|
||||
int x = g_bad_order[i][0], y = g_bad_order[i][1];
|
||||
int v = neighbor_mean(fr, g_bad, x, y);
|
||||
if (v > 0) { fr[y * W + x] = (unsigned short)v; n++; }
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ---------- color LUTs ---------- */
|
||||
|
||||
/* ironbow anchors (FLIR-style), value 0..255 */
|
||||
static void ironbow(unsigned v, unsigned char *r, unsigned char *g,
|
||||
unsigned char *b) {
|
||||
static const int anchors[7][3] = {
|
||||
{0, 0, 0}, {23, 0, 60}, {93, 0, 128}, {170, 31, 83},
|
||||
{226, 117, 29}, {249, 196, 66}, {255, 255, 220}};
|
||||
double f = v / 255.0 * 6.0;
|
||||
int i = (int)f;
|
||||
if (i > 5) i = 5;
|
||||
double t = f - i;
|
||||
*r = (unsigned char)(anchors[i][0] + t * (anchors[i + 1][0] - anchors[i][0]));
|
||||
*g = (unsigned char)(anchors[i][1] + t * (anchors[i + 1][1] - anchors[i][1]));
|
||||
*b = (unsigned char)(anchors[i][2] + t * (anchors[i + 1][2] - anchors[i][2]));
|
||||
}
|
||||
|
||||
/* ---------- render ---------- */
|
||||
|
||||
/* decode + correct the live frame into g_live (shared with the reference
|
||||
* model so both use the same corrected values) */
|
||||
static void decode_live(const unsigned char *data) {
|
||||
for (int i = 0; i < NPIX; ++i)
|
||||
g_live[i] = (unsigned short)(data[i * 2 + 28] | ((unsigned)data[i * 2 + 29] << 8));
|
||||
if (g_bad_done) correct_frame(g_live);
|
||||
}
|
||||
|
||||
/* 3x3 median filter on counts: cuts the ~245-count temporal noise ~3x so
|
||||
* the absolute temperature image is smooth (noise would otherwise render
|
||||
* as red/blue speckle - the "startup noise image" the user saw). Real
|
||||
* thermal structure (hand, mura gradient) survives. */
|
||||
static void median3_counts(unsigned short *src, unsigned short *dst) {
|
||||
unsigned short win[9];
|
||||
for (int y = 0; y < H; ++y) {
|
||||
for (int x = 0; x < W; ++x) {
|
||||
int n = 0;
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
int xx = x + dx, yy = y + dy;
|
||||
if (xx < 0 || xx >= W || yy < 0 || yy >= H) continue;
|
||||
win[n++] = src[yy * W + xx];
|
||||
}
|
||||
}
|
||||
/* insertion sort, take middle */
|
||||
for (int i = 1; i < n; ++i) {
|
||||
unsigned short k = win[i];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && win[j] > k) { win[j + 1] = win[j]; j--; }
|
||||
win[j + 1] = k;
|
||||
}
|
||||
dst[y * W + x] = win[n / 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void render(unsigned mn, unsigned mx) {
|
||||
static unsigned short sm[NPIX]; /* median-smoothed */
|
||||
static unsigned short nuc[NPIX]; /* flat-field corrected */
|
||||
static unsigned char nuc_hist[65536];
|
||||
unsigned char *px = g_bmp + 54;
|
||||
unsigned hotv = 0;
|
||||
int hot = 0;
|
||||
unsigned csum = 0;
|
||||
long sum_abs = 0;
|
||||
unsigned cnt_abs = 0;
|
||||
|
||||
/* flat-field (NUC) correction: nuc = live - (ref - mean(ref)).
|
||||
* Removes the fixed sensor mura (measured spatial std 3560 -> 29). */
|
||||
if (g_has_reference) {
|
||||
mag160c_display_nuc(g_live, g_reference, NPIX, nuc);
|
||||
median3_counts(g_live, sm); /* for the optional diff path */
|
||||
} else {
|
||||
for (int i = 0; i < NPIX; ++i) nuc[i] = g_live[i];
|
||||
median3_counts(g_live, sm);
|
||||
}
|
||||
|
||||
/* ==== official display pipeline (recovered from CoreSDKLib) ====
|
||||
* counts -> NUC -> T2E temperature -> gray -> official palette.
|
||||
* The vendor renders a magenta-family palette (background lands mid-
|
||||
* ramp, hot objects shift toward red/purple ends). Gray is a fixed
|
||||
* temperature window; we use a narrow window around the measured
|
||||
* background so the scene matches the vendor look. */
|
||||
{
|
||||
static int temp_mc[NPIX];
|
||||
int tmin = 30000, tmax = 44000; /* 30..44 C in millidegrees */
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
temp_mc[i] = counts_to_temp_mc((int)nuc[i]);
|
||||
}
|
||||
/* center the window on the frame's temperature median so the
|
||||
* background lands mid-palette (magenta) like the official app */
|
||||
{
|
||||
static int hist[65536];
|
||||
memset(hist, 0, sizeof(hist));
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
int t = temp_mc[i] / 1000;
|
||||
if (t >= 0 && t < 65536) hist[t]++;
|
||||
}
|
||||
int acc = 0, med_t = 0;
|
||||
int mid_target = NPIX / 2;
|
||||
for (int k = 0; k < 65536; ++k) {
|
||||
acc += hist[k];
|
||||
if (acc >= mid_target) { med_t = k; break; }
|
||||
}
|
||||
tmin = (med_t - 2) * 1000; /* +- 2 C around background */
|
||||
tmax = (med_t + 2) * 1000;
|
||||
}
|
||||
for (int y = 0; y < H; ++y) {
|
||||
for (int x = 0; x < W; ++x) {
|
||||
int i = y * W + x;
|
||||
int v = nuc[i];
|
||||
if (x >= 70 && x < 90 && y >= 50 && y < 70) csum += v;
|
||||
if (v > (int)hotv && v != 0) { hotv = (unsigned)v; hot = i; }
|
||||
unsigned char r, g, b;
|
||||
if (g_diff_mode && g_has_reference) {
|
||||
int d = (int)sm[i] - (int)g_reference[i];
|
||||
if (d > 32767) d = 32767;
|
||||
if (d < -32768) d = -32768;
|
||||
unsigned char ri, gi, bi;
|
||||
int span2 = 2000;
|
||||
if (d > 30) {
|
||||
unsigned idx = (unsigned)(d * 255 / span2);
|
||||
if (idx > 255) idx = 255;
|
||||
ironbow(128 + idx / 2, &ri, &gi, &bi);
|
||||
} else if (d < -30) {
|
||||
unsigned idx = (unsigned)(-d * 255 / span2);
|
||||
if (idx > 255) idx = 255;
|
||||
ironbow(128 - idx / 2, &ri, &gi, &bi);
|
||||
} else {
|
||||
ironbow(128, &ri, &gi, &bi);
|
||||
}
|
||||
r = ri; g = gi; b = bi;
|
||||
} else {
|
||||
int t = temp_mc[i];
|
||||
int idx;
|
||||
if (t <= tmin) idx = 0;
|
||||
else if (t >= tmax) idx = 255;
|
||||
else idx = (t - tmin) * 255 / (tmax - tmin);
|
||||
r = mag160c_official_palette[idx][0];
|
||||
g = mag160c_official_palette[idx][1];
|
||||
b = mag160c_official_palette[idx][2];
|
||||
}
|
||||
int dst = (119 - y) * W * 3 + x * 3;
|
||||
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hot >= 0) {
|
||||
g_max_x = hot % W;
|
||||
g_max_y = hot / W;
|
||||
g_max_temp = counts_to_c((double)hotv);
|
||||
int mx2 = g_max_x, my2 = 119 - g_max_y;
|
||||
for (int k = -2; k <= 2; ++k) {
|
||||
if (mx2 + k >= 0 && mx2 + k < W) {
|
||||
int d2 = my2 * W * 3 + (mx2 + k) * 3;
|
||||
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
||||
}
|
||||
if (my2 + k >= 0 && my2 + k < H) {
|
||||
int d2 = (my2 + k) * W * 3 + mx2 * 3;
|
||||
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_center_temp = counts_to_c((double)(csum / 400));
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
switch (msg) {
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc = BeginPaint(hw, &ps);
|
||||
HDC mem = CreateCompatibleDC(dc);
|
||||
HBITMAP bm = CreateCompatibleBitmap(dc, W, H);
|
||||
HGDIOBJ old = SelectObject(mem, bm);
|
||||
SetDIBitsToDevice(mem, 0, 0, W, H, 0, 0, 0, H, g_bmp + 54,
|
||||
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
||||
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, W, H, SRCCOPY);
|
||||
SelectObject(mem, old);
|
||||
DeleteObject(bm);
|
||||
DeleteDC(mem);
|
||||
SelectObject(dc, g_font);
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
SetTextColor(dc, RGB(220, 220, 220));
|
||||
int y = 20;
|
||||
char line[256];
|
||||
snprintf(line, sizeof(line), "frame : %u", g_fcount);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
if (g_probe_x >= 0) {
|
||||
int v = g_live[g_probe_y * W + g_probe_x];
|
||||
double t = counts_to_c((double)v);
|
||||
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
if (g_max_x >= 0) {
|
||||
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF (ref avg)" : "absolute");
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
if (g_bad_done) {
|
||||
snprintf(line, sizeof(line), "badpx : %d (fill %d)", g_bad_count, g_bad_order_len);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
if (g_cal_valid) {
|
||||
snprintf(line, sizeof(line), "cal : %.5f C/cnt + %.1f", g_cal_a, g_cal_b);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "cal : 147 counts/C (assumed)");
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
if (g_cal_phase == 1) {
|
||||
snprintf(line, sizeof(line), "CAL : aim at COLD object, press Cal-Cold");
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
} else if (g_cal_phase == 2) {
|
||||
snprintf(line, sizeof(line), "CAL : aim at HOT object, press Cal-Hot");
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
EndPaint(hw, &ps);
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONDOWN: {
|
||||
int x = LOWORD(lp), y = HIWORD(lp);
|
||||
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
|
||||
g_probe_x = (x - 10) * W / 640;
|
||||
g_probe_y = 119 - (y - 10) * H / 480;
|
||||
InvalidateRect(hw, NULL, TRUE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
switch (LOWORD(wp)) {
|
||||
case 1001: /* FFC - queue a manual pair through the scheduler;
|
||||
* the main loop issues FFC(0) after the next complete
|
||||
* frame and FFC(1) FFC_GAP frames later (official
|
||||
* cadence). Non-blocking: no Sleep() in the UI thread. */
|
||||
g_manual_ffc = 1;
|
||||
SetWindowTextA(hw, "FFC queued (0 -> 1)");
|
||||
break;
|
||||
case 1002: {
|
||||
char path[MAX_PATH];
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); }
|
||||
SetWindowTextA(hw, "saved");
|
||||
break;
|
||||
}
|
||||
case 1003:
|
||||
g_diff_mode = !g_diff_mode;
|
||||
break;
|
||||
case 1004: /* set reference (from the corrected live frame) */
|
||||
for (int i = 0; i < NPIX; ++i)
|
||||
g_reference[i] = g_live[i];
|
||||
g_has_reference = 1;
|
||||
g_diff_mode = 1;
|
||||
SetWindowTextA(hw, "reference set");
|
||||
break;
|
||||
case 1005:
|
||||
g_probe_x = -1;
|
||||
InvalidateRect(hw, NULL, TRUE);
|
||||
break;
|
||||
case 1006: /* calibration step 1: cold */
|
||||
if (g_has_reference) {
|
||||
long s = 0;
|
||||
for (int i = 0; i < NPIX; ++i)
|
||||
s += (int)g_live[i];
|
||||
g_cal_cold_count = (double)s / NPIX;
|
||||
g_cal_cold_temp = 0.0;
|
||||
g_cal_phase = 2;
|
||||
SetWindowTextA(hw, "cold point captured (0C) - now aim hot");
|
||||
}
|
||||
break;
|
||||
case 1007: /* calibration step 2: hot */
|
||||
if (g_cal_phase == 2) {
|
||||
long s = 0;
|
||||
for (int i = 0; i < NPIX; ++i)
|
||||
s += (int)g_live[i];
|
||||
g_cal_hot_count = (double)s / NPIX;
|
||||
g_cal_hot_temp = 90.0;
|
||||
g_cal_a = (g_cal_hot_temp - g_cal_cold_temp) /
|
||||
(g_cal_hot_count - g_cal_cold_count);
|
||||
g_cal_b = g_cal_cold_temp - g_cal_a * g_cal_cold_count;
|
||||
g_cal_valid = 1;
|
||||
g_cal_phase = 0;
|
||||
char st[160];
|
||||
snprintf(st, sizeof(st), "calibrated: %.3f C/cnt, offset %.1f",
|
||||
g_cal_a, g_cal_b);
|
||||
SetWindowTextA(hw, st);
|
||||
}
|
||||
break;
|
||||
case 1008: /* reset calibration to default */
|
||||
g_cal_a = 1.0 / 147.0;
|
||||
g_cal_b = -11720.0 / 147.0 + 25.0;
|
||||
g_cal_valid = 0;
|
||||
g_cal_phase = 0;
|
||||
SetWindowTextA(hw, "calibration reset to 147 c/C");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_KEYDOWN:
|
||||
if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; }
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hw, msg, wp, lp);
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
|
||||
(void)prev; (void)cmd; (void)show;
|
||||
libusb_init(&g_ctx);
|
||||
int ok = 0;
|
||||
for (int attempt = 0; attempt < 4 && !ok; ++attempt) {
|
||||
if (attempt > 0) {
|
||||
/* previous session may have left the unit streaming: reset it */
|
||||
if (g_h) libusb_reset_device(g_h);
|
||||
if (g_h) { libusb_close(g_h); g_h = NULL; }
|
||||
Sleep(2500);
|
||||
}
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) continue;
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
if (libusb_claim_interface(g_h, 0) != 0) { libusb_close(g_h); g_h = NULL; continue; }
|
||||
if (sendcmd(0x6bb6b66b, 0, 4)) continue;
|
||||
if (sendcmd(0x6bb6b66c, 0, 4)) continue;
|
||||
if (sendcmd(0x6bb6b66f, 0, 4)) continue;
|
||||
if (sendcmd(0x6bb6b672, 0, 8)) continue;
|
||||
Sleep(100);
|
||||
if (sendcmd(0x6bb6b672, 0, 8)) continue;
|
||||
Sleep(300);
|
||||
if (sendcmd(0x6bb6b673, 0, 4)) continue;
|
||||
Sleep(700);
|
||||
ok = 1;
|
||||
}
|
||||
if (!ok) {
|
||||
MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
WNDCLASSA wc = {0};
|
||||
wc.lpfnWndProc = wndproc;
|
||||
wc.hInstance = inst;
|
||||
wc.lpszClassName = "Mag160cDemoFinal";
|
||||
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
|
||||
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
RegisterClassA(&wc);
|
||||
g_hwnd = CreateWindowA("Mag160cDemoFinal", "MAG160C Thermal Demo v3",
|
||||
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 620,
|
||||
NULL, NULL, inst, NULL);
|
||||
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
|
||||
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
|
||||
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
|
||||
670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Cal Cold (0C)", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 290, 140, 32, g_hwnd, (HMENU)1006, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Cal Hot (90C)", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
820, 290, 120, 32, g_hwnd, (HMENU)1007, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Reset cal", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 330, 120, 32, g_hwnd, (HMENU)1008, inst, NULL);
|
||||
|
||||
unsigned sz = 54 + W * H * 3;
|
||||
g_bmp[0] = 'B'; g_bmp[1] = 'M';
|
||||
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
|
||||
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
|
||||
g_bmp[10] = 54; g_bmp[14] = 40;
|
||||
g_bmp[18] = W; g_bmp[19] = 0; g_bmp[22] = H; g_bmp[23] = 0;
|
||||
g_bmp[26] = 1; g_bmp[28] = 24;
|
||||
|
||||
ShowWindow(g_hwnd, SW_SHOW);
|
||||
SetWindowTextA(g_hwnd, "starting - auto reference in ~10s");
|
||||
|
||||
/* ==== verified viewer core loop + official FFC cadence (csdk scheduler) */
|
||||
int nread = 0;
|
||||
unsigned prev2 = 0xffffffff;
|
||||
mag160c_ffc_scheduler_t ffc;
|
||||
mag160c_ffc_scheduler_init(&ffc, FFC_PERIOD, FFC_GAP);
|
||||
int ffc_stall = 0;
|
||||
DWORD t0 = GetTickCount();
|
||||
DWORD lfps_t = t0;
|
||||
unsigned lfps_c = 0;
|
||||
DWORD last_frame_t = t0;
|
||||
DWORD last_reset_t = t0;
|
||||
for (;;) {
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) goto done;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
unsigned char hdr[64];
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) {
|
||||
/* no frame this round: watchdog wakes a stalled unit */
|
||||
DWORD now2 = GetTickCount();
|
||||
if (now2 - last_frame_t > 3000 && now2 - last_reset_t > 5000) {
|
||||
sendcmd(0x6bb6b672, 1, 8);
|
||||
last_reset_t = now2;
|
||||
ffc_stall++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (xfer < 28) continue;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) continue;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prev2) continue;
|
||||
prev2 = c;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) || xfer < 38400) continue;
|
||||
nread++;
|
||||
last_frame_t = GetTickCount();
|
||||
|
||||
/* manual FFC queued from the button: issue FFC(0) now (after a
|
||||
* complete frame, as in the official trace); the scheduler then
|
||||
* emits FFC(1) FFC_GAP frames later. */
|
||||
if (g_manual_ffc) {
|
||||
g_manual_ffc = 0;
|
||||
if (mag160c_ffc_scheduler_trigger(&ffc) >= 0) {
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
SetWindowTextA(g_hwnd, "FFC(0) sent - warming");
|
||||
}
|
||||
}
|
||||
/* official cadence (csdk scheduler): FFC(1) after ~10th frame,
|
||||
* then FFC(0) every FFC_PERIOD frames with FFC(1) FFC_GAP later.
|
||||
* FFC is only issued after a complete frame (as in the trace). */
|
||||
{
|
||||
int32_t ffc_param = mag160c_ffc_scheduler_tick(&ffc);
|
||||
if (ffc_param >= 0) sendcmd(0x6bb6b672, (unsigned)ffc_param, 8);
|
||||
if (ffc_param == 1) g_rebase = 1; /* FFC(1): re-align reference */
|
||||
}
|
||||
|
||||
/* FFC calibration window: the unit streams type=1 frames between
|
||||
* FFC(0) and FFC(1) (~9 frames). Do not render those (they are
|
||||
* raw/response data with a very different range - the official app
|
||||
* freezes the image instead of showing the red/yellow flash). */
|
||||
if (hdr[12] != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* decode + correct once; everything below uses g_live */
|
||||
decode_live(g_frame);
|
||||
|
||||
/* after FFC(1) the type=0 baseline shifts and the unit has just
|
||||
* recalibrated: re-collect the reference from fresh quiet frames
|
||||
* (replaces the startup-collected one and any stale baseline),
|
||||
* then skip a few frames while the stream settles. */
|
||||
if (g_rebase) {
|
||||
g_rebase = 0;
|
||||
g_skip_ffc = REF_SKIP;
|
||||
if (g_has_reference) {
|
||||
if (REF_REINIT_AFTER_FFC) {
|
||||
g_ref_phase = 2; /* post-FFC re-collection */
|
||||
g_ref_n = 0;
|
||||
SetWindowTextA(g_hwnd, "re-collecting reference after FFC");
|
||||
} else {
|
||||
mag160c_display_ref_rebase(g_reference, g_live, NPIX, 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_skip_ffc > 0) {
|
||||
g_skip_ffc--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* first reference collection: starts right after the startup FFC(1)
|
||||
* has switched the stream to type=0 and the transition frames have
|
||||
* been skipped (nread >= 40), so the startup "noise image" is never
|
||||
* baked into the reference. */
|
||||
if (g_ref_phase == 0 && !g_has_reference && nread >= 40) {
|
||||
g_ref_phase = 1;
|
||||
g_ref_n = 0;
|
||||
}
|
||||
|
||||
/* reference collection (init phase 1 or post-FFC phase 2): median
|
||||
* of REF_INIT_N frames. The median is naturally robust to a moving
|
||||
* object or noise (a transient object appears in <50% of the window
|
||||
* and is excluded), and the captured reference is used as the NUC
|
||||
* flat field: live - (ref - mean(ref)) removes the fixed sensor
|
||||
* mura (measured: spatial std 3560 -> 29). No quiet-gate: a strict
|
||||
* stillness requirement meant the reference never built while the
|
||||
* user was watching, so the NUC never activated and the raw mura
|
||||
* was displayed. */
|
||||
if (g_ref_phase == 1 || g_ref_phase == 2) {
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
g_ref_samples[i][g_ref_n] = g_live[i];
|
||||
if (g_ref_n == 0) { g_ref_min[i] = g_ref_max[i] = g_live[i]; }
|
||||
else {
|
||||
if (g_live[i] < g_ref_min[i]) g_ref_min[i] = g_live[i];
|
||||
if (g_live[i] > g_ref_max[i]) g_ref_max[i] = g_live[i];
|
||||
}
|
||||
}
|
||||
g_ref_n++;
|
||||
if (g_ref_n == REF_INIT_N) {
|
||||
int nb = 0;
|
||||
/* per-pixel median over the window */
|
||||
static unsigned short tmp[REF_INIT_N];
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
for (int k = 0; k < REF_INIT_N; ++k) tmp[k] = g_ref_samples[i][k];
|
||||
for (int a = 1; a < REF_INIT_N; ++a) { /* insertion sort */
|
||||
unsigned short key = tmp[a];
|
||||
int b = a - 1;
|
||||
while (b >= 0 && tmp[b] > key) { tmp[b + 1] = tmp[b]; b--; }
|
||||
tmp[b + 1] = key;
|
||||
}
|
||||
/* trimmed median: average of the middle 50% (drops
|
||||
* top/bottom 25% outliers - a transient object or a
|
||||
* dead pixel spike cannot shift the flat field) */
|
||||
{
|
||||
long s = 0;
|
||||
int n = 0;
|
||||
for (int k = REF_INIT_N / 4; k < REF_INIT_N * 3 / 4; ++k) {
|
||||
s += tmp[k];
|
||||
n++;
|
||||
}
|
||||
g_reference[i] = (unsigned short)(s / n);
|
||||
}
|
||||
g_bad[i] = 0;
|
||||
}
|
||||
/* bad pixel detection only on the first collection;
|
||||
* post-FFC re-collection keeps the existing bad map */
|
||||
if (g_ref_phase == 1) {
|
||||
/* temporal detection: min-max fluctuation */
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
if ((int)g_ref_max[i] - (int)g_ref_min[i] > 400) { g_bad[i] = 1; nb++; }
|
||||
}
|
||||
/* histogram-peak-deviation detection (Seek method):
|
||||
* bad if value > histPeak - (frameMax - histPeak),
|
||||
* guarded to at least histPeak + 200 */
|
||||
{
|
||||
static unsigned hist[65536];
|
||||
unsigned peakv = 0, peakc = 0, maxv = 0;
|
||||
for (int i = 0; i < 65536; ++i) hist[i] = 0;
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
unsigned v = g_reference[i];
|
||||
if (++hist[v] > peakc) { peakc = hist[v]; peakv = v; }
|
||||
if (v > maxv) maxv = v;
|
||||
}
|
||||
long thr = (long)peakv - ((long)maxv - (long)peakv);
|
||||
if (thr < (long)peakv + 200) thr = (long)peakv + 200;
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
if (!g_bad[i] && (long)g_reference[i] > thr) { g_bad[i] = 1; nb++; }
|
||||
}
|
||||
}
|
||||
g_bad_count = nb;
|
||||
/* topological fill order: copy of the bad mask evolves
|
||||
* as pixels are filled (edge-first for clusters) */
|
||||
unsigned char w[NPIX];
|
||||
for (int i = 0; i < NPIX; ++i) w[i] = g_bad[i];
|
||||
g_bad_order_len = 0;
|
||||
build_fill_order(w, (int (*)[2])g_bad_order, &g_bad_order_len);
|
||||
/* fill reference values in the stored order */
|
||||
for (int i = 0; i < NPIX; ++i) w[i] = g_bad[i];
|
||||
for (int i = 0; i < g_bad_order_len; ++i) {
|
||||
int x = g_bad_order[i][0], y = g_bad_order[i][1];
|
||||
int v = neighbor_mean(g_reference, w, x, y);
|
||||
if (v > 0) {
|
||||
g_reference[y * W + x] = (unsigned short)v;
|
||||
w[y * W + x] = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* post-FFC re-collection: correct the new reference
|
||||
* with the existing bad map */
|
||||
for (int i = 0; i < NPIX; ++i)
|
||||
g_bad[i] = g_bad[i]; /* keep map */
|
||||
for (int i = 0; i < g_bad_order_len; ++i) {
|
||||
int x = g_bad_order[i][0], y = g_bad_order[i][1];
|
||||
int v = neighbor_mean(g_reference, g_bad, x, y);
|
||||
if (v > 0) g_reference[y * W + x] = (unsigned short)v;
|
||||
}
|
||||
}
|
||||
g_has_reference = 1;
|
||||
g_bad_done = 1;
|
||||
int was = g_ref_phase;
|
||||
g_ref_phase = 0;
|
||||
memset(g_fg_count, 0, sizeof(g_fg_count));
|
||||
char st[96];
|
||||
snprintf(st, sizeof(st), "reference captured (phase %d) - bad: %d",
|
||||
was, nb);
|
||||
SetWindowTextA(g_hwnd, st);
|
||||
}
|
||||
}
|
||||
else if (g_has_reference && g_skip_ffc <= 0 && g_diff_mode) {
|
||||
/* MOG-style per-pixel background model (csdk) with self-healing:
|
||||
* only used for the optional diff display. The NUC flat-field
|
||||
* reference must stay FROZEN (re-collected after each FFC only):
|
||||
* updating it here would absorb the background into the
|
||||
* reference, killing the NUC and re-baking the mura in. */
|
||||
mag160c_display_ref_track_heal(g_reference, g_live, W, H,
|
||||
REF_T, REF_ALPHA, g_fg_count,
|
||||
REF_SELFHEAL_N, 2);
|
||||
}
|
||||
/* keep the previous frame for the init quiet-gate */
|
||||
memcpy(g_prev_frame, g_live, sizeof(g_live));
|
||||
unsigned hist[65536] = {0};
|
||||
unsigned nz = 0;
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
unsigned v = g_live[i];
|
||||
if (v == 0) continue;
|
||||
hist[v]++;
|
||||
nz++;
|
||||
}
|
||||
/* absolute mode window: computed on the NUC output (flat-field
|
||||
* corrected) values, which cluster tightly around the reference
|
||||
* mean (measured std ~29 after NUC). A narrow median +- span
|
||||
* window gives the high contrast the official app gets from its
|
||||
* temperature window, while the NUC removed the mura. */
|
||||
unsigned mn = 0, mx = 0, acc = 0;
|
||||
unsigned mid_target = (unsigned)(nz / 2);
|
||||
for (unsigned k = 0; k < 65536; ++k) {
|
||||
acc += hist[k];
|
||||
if (acc >= mid_target) { mx = k; break; }
|
||||
}
|
||||
{
|
||||
unsigned med = mx;
|
||||
unsigned lo2 = med > 1500 ? med - 1500 : 0;
|
||||
unsigned hi2 = med + 1500;
|
||||
if (hi2 > 65535) hi2 = 65535;
|
||||
mn = lo2;
|
||||
mx = hi2;
|
||||
}
|
||||
render(mn, mx);
|
||||
g_fcount++;
|
||||
DWORD now = GetTickCount();
|
||||
if (now - lfps_t >= 1000) {
|
||||
g_fps = (g_fcount - lfps_c) * 1000.0 / (now - lfps_t);
|
||||
lfps_t = now;
|
||||
lfps_c = g_fcount;
|
||||
}
|
||||
snprintf(g_title, sizeof(g_title),
|
||||
"MAG160C Demo v3 - frame %u type=%u range [%u..%u] ffc=%d",
|
||||
c, (unsigned)hdr[12], mn, mx, ffc_stall);
|
||||
SetWindowTextA(g_hwnd, g_title);
|
||||
InvalidateRect(g_hwnd, NULL, FALSE);
|
||||
UpdateWindow(g_hwnd);
|
||||
}
|
||||
done:
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,443 @@
|
||||
/* MAG160C Windows Demo (libusb0 = libusb-win32 backend, same as the official
|
||||
* EloThermal demo). Uses the exact transfer layer the vendor demo ships
|
||||
* with, so the type=0 (temperature) stream stays alive with FFC switching.
|
||||
*
|
||||
* Build:
|
||||
* gcc -std=c11 -mwindows mag160c_demo_usb0.c libusb0.a -o mag160c_demo_usb0.exe
|
||||
* copy libusb0.dll next to the exe.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ---- libusb-win32 (libusb0) API ---------------------------------------- */
|
||||
typedef struct usb_bus {
|
||||
struct usb_bus *next, *prev;
|
||||
char dirname[512];
|
||||
struct usb_device *devices;
|
||||
unsigned long location;
|
||||
struct usb_device *root_dev;
|
||||
} usb_bus;
|
||||
|
||||
typedef struct usb_device_descriptor {
|
||||
uint8_t bLength, bDescriptorType;
|
||||
uint16_t bcdUSB;
|
||||
uint8_t bDeviceClass, bDeviceSubClass, bDeviceProtocol, bMaxPacketSize0;
|
||||
uint16_t idVendor, idProduct, bcdDevice;
|
||||
uint8_t iManufacturer, iProduct, iSerialNumber, bNumConfigurations;
|
||||
} usb_device_descriptor;
|
||||
|
||||
typedef struct usb_device {
|
||||
struct usb_device *next, *prev;
|
||||
char filename[512];
|
||||
struct usb_bus *bus;
|
||||
usb_device_descriptor descriptor;
|
||||
void *config;
|
||||
void *dev;
|
||||
uint8_t devnum;
|
||||
unsigned char num_children;
|
||||
struct usb_device **children;
|
||||
} usb_device;
|
||||
|
||||
typedef struct usb_dev_handle usb_dev_handle;
|
||||
|
||||
typedef int (*fn_usb_init)(void);
|
||||
typedef int (*fn_usb_find_busses)(void);
|
||||
typedef int (*fn_usb_find_devices)(void);
|
||||
typedef usb_bus *(*fn_usb_get_busses)(void);
|
||||
typedef usb_dev_handle *(*fn_usb_open)(usb_device *);
|
||||
typedef int (*fn_usb_set_configuration)(usb_dev_handle *, int);
|
||||
typedef int (*fn_usb_claim_interface)(usb_dev_handle *, int);
|
||||
typedef int (*fn_usb_bulk_write)(usb_dev_handle *, int, const char *, int, int);
|
||||
typedef int (*fn_usb_bulk_read)(usb_dev_handle *, int, char *, int, int);
|
||||
typedef int (*fn_usb_close)(usb_dev_handle *);
|
||||
|
||||
static fn_usb_init p_usb_init;
|
||||
static fn_usb_find_busses p_usb_find_busses;
|
||||
static fn_usb_find_devices p_usb_find_devices;
|
||||
static fn_usb_get_busses p_usb_get_busses;
|
||||
static fn_usb_open p_usb_open;
|
||||
static fn_usb_set_configuration p_usb_set_configuration;
|
||||
static fn_usb_claim_interface p_usb_claim_interface;
|
||||
static fn_usb_bulk_write p_usb_bulk_write;
|
||||
static fn_usb_bulk_read p_usb_bulk_read;
|
||||
static fn_usb_close p_usb_close;
|
||||
|
||||
static usb_dev_handle *g_dev;
|
||||
|
||||
#define MAG_CMD_PREPARE1 0x6bb6b66b
|
||||
#define MAG_CMD_PREPARE2 0x6bb6b66c
|
||||
#define MAG_CMD_GET_INFO 0x6bb6b66f
|
||||
#define MAG_CMD_FFC 0x6bb6b672
|
||||
#define MAG_CMD_START 0x6bb6b673
|
||||
#define MAG_CMD_STOP 0x6bb6b674
|
||||
|
||||
#define IR_W 160
|
||||
#define IR_H 120
|
||||
#define FRAME_LEN (IR_W * IR_H * 2)
|
||||
|
||||
static unsigned char g_frame[FRAME_LEN];
|
||||
static unsigned char g_bmp[54 + IR_W * IR_H * 3];
|
||||
static char g_status[512];
|
||||
static volatile unsigned g_frame_count;
|
||||
static volatile unsigned g_frame_type;
|
||||
static int g_diff_mode = 1;
|
||||
static unsigned short g_reference[IR_W * IR_H];
|
||||
static int g_has_reference;
|
||||
static double g_fps;
|
||||
static int g_probe_x = -1, g_probe_y = -1;
|
||||
static int g_max_x = -1, g_max_y = -1;
|
||||
static double g_max_temp = -100.0;
|
||||
static double g_center_temp = -100.0;
|
||||
static FILE *g_log;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (p_usb_bulk_write(g_dev, 0x03, (char *)cmd, len, 2000) != len) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
xfer = p_usb_bulk_read(g_dev, 0x82, (char *)resp, sizeof(resp), 2000);
|
||||
if (xfer < 4) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void render(const unsigned char *frame) {
|
||||
int hot = 0;
|
||||
unsigned hotv = 0;
|
||||
unsigned center_sum = 0;
|
||||
unsigned char *px = g_bmp + 54;
|
||||
for (int y = 0; y < IR_H; ++y) {
|
||||
for (int x = 0; x < IR_W; ++x) {
|
||||
int o = (y * IR_W + x) * 2;
|
||||
unsigned v = (unsigned)frame[o] | ((unsigned)frame[o + 1] << 8);
|
||||
int diffv = 0;
|
||||
if (g_diff_mode && g_has_reference)
|
||||
diffv = (int)v - (int)g_reference[y * IR_W + x];
|
||||
if (x >= 70 && x < 90 && y >= 50 && y < 70) center_sum += v;
|
||||
if (v > hotv && v != 0) { hotv = v; hot = y * IR_W + x; }
|
||||
unsigned char r, g, b;
|
||||
if (g_diff_mode && g_has_reference) {
|
||||
int span = 2000;
|
||||
if (diffv > 0) {
|
||||
unsigned idx = (unsigned)(diffv * 255 / span);
|
||||
if (idx > 255) idx = 255;
|
||||
r = (unsigned char)(128 + idx / 2);
|
||||
g = (unsigned char)(128 - idx / 2);
|
||||
b = (unsigned char)(128 - idx / 2);
|
||||
} else {
|
||||
unsigned idx = (unsigned)(-diffv * 255 / span);
|
||||
if (idx > 255) idx = 255;
|
||||
r = (unsigned char)(128 - idx / 2);
|
||||
g = (unsigned char)(128 - idx / 2);
|
||||
b = (unsigned char)(128 + idx / 2);
|
||||
}
|
||||
} else {
|
||||
/* absolute with fixed range (type=0 data ~ 8000..30000) */
|
||||
unsigned idx = (v - 8000) * 255 / 22000;
|
||||
if (idx > 255) idx = 255;
|
||||
if (idx < 64) { r = 0; g = (unsigned char)(idx * 4); b = 255; }
|
||||
else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255 - (idx - 64) * 4); }
|
||||
else if (idx < 192) { r = (unsigned char)((idx - 128) * 4); g = 255; b = 0; }
|
||||
else { r = 255; g = (unsigned char)(255 - (idx - 192) * 4); b = 0; }
|
||||
}
|
||||
int dst = (IR_H - 1 - y) * IR_W * 3 + x * 3;
|
||||
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
|
||||
}
|
||||
}
|
||||
if (hot >= 0) {
|
||||
g_max_x = hot % IR_W;
|
||||
g_max_y = hot / IR_W;
|
||||
g_max_temp = 25.0 + ((int)hotv - (int)g_reference[hot]) / 147.0;
|
||||
int mx2 = g_max_x, my2 = IR_H - 1 - g_max_y;
|
||||
for (int k = -2; k <= 2; ++k) {
|
||||
if (mx2 + k >= 0 && mx2 + k < IR_W) {
|
||||
int d2 = my2 * IR_W * 3 + (mx2 + k) * 3;
|
||||
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
||||
}
|
||||
if (my2 + k >= 0 && my2 + k < IR_H) {
|
||||
int d2 = (my2 + k) * IR_W * 3 + mx2 * 3;
|
||||
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_center_temp = 25.0 + ((int)(center_sum / 400) - (int)g_reference[50 * IR_W + 70]) / 147.0;
|
||||
}
|
||||
|
||||
static HWND g_hwnd;
|
||||
static HFONT g_font;
|
||||
|
||||
static void set_status(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(g_status, sizeof(g_status), fmt, ap);
|
||||
va_end(ap);
|
||||
InvalidateRect(g_hwnd, NULL, TRUE);
|
||||
}
|
||||
|
||||
static void save_bmp(void) {
|
||||
char path[MAX_PATH];
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); set_status("saved %s", path); }
|
||||
}
|
||||
|
||||
static void do_ffc(void) {
|
||||
sendcmd(MAG_CMD_FFC, 1, 8);
|
||||
Sleep(300);
|
||||
sendcmd(MAG_CMD_FFC, 0, 8);
|
||||
set_status("FFC triggered");
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
switch (msg) {
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc = BeginPaint(hw, &ps);
|
||||
HDC mem = CreateCompatibleDC(dc);
|
||||
HBITMAP bm = CreateCompatibleBitmap(dc, IR_W, IR_H);
|
||||
HGDIOBJ old = SelectObject(mem, bm);
|
||||
SetDIBitsToDevice(mem, 0, 0, IR_W, IR_H, 0, 0, 0, IR_H, g_bmp + 54,
|
||||
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
||||
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, IR_W, IR_H, SRCCOPY);
|
||||
SelectObject(mem, old);
|
||||
DeleteObject(bm);
|
||||
DeleteDC(mem);
|
||||
SelectObject(dc, g_font);
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
SetTextColor(dc, RGB(220, 220, 220));
|
||||
int y = 20;
|
||||
char line[256];
|
||||
snprintf(line, sizeof(line), "frame : %u (type %u)", g_frame_count, g_frame_type);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
if (g_probe_x >= 0) {
|
||||
double t = 25.0 + ((int)(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2] |
|
||||
(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2 + 1] << 8)) -
|
||||
(int)g_reference[g_probe_y * IR_W + g_probe_x]) / 147.0;
|
||||
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
if (g_max_x >= 0) {
|
||||
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
}
|
||||
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF" : "absolute");
|
||||
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
||||
SetTextColor(dc, RGB(120, 220, 120));
|
||||
TextOutA(dc, 10, 500, g_status, (int)strlen(g_status));
|
||||
EndPaint(hw, &ps);
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONDOWN: {
|
||||
int x = LOWORD(lp), y = HIWORD(lp);
|
||||
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
|
||||
g_probe_x = (x - 10) * IR_W / 640;
|
||||
g_probe_y = IR_H - 1 - (y - 10) * IR_H / 480;
|
||||
InvalidateRect(hw, NULL, TRUE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
switch (LOWORD(wp)) {
|
||||
case 1001: do_ffc(); break;
|
||||
case 1002: save_bmp(); break;
|
||||
case 1003: g_diff_mode = !g_diff_mode; break;
|
||||
case 1004:
|
||||
for (int i = 0; i < FRAME_LEN; i += 2)
|
||||
g_reference[i / 2] = (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
|
||||
g_has_reference = 1;
|
||||
g_diff_mode = 1;
|
||||
set_status("reference captured");
|
||||
break;
|
||||
case 1005: g_probe_x = -1; InvalidateRect(hw, NULL, TRUE); break;
|
||||
}
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hw, msg, wp, lp);
|
||||
}
|
||||
|
||||
static int open_camera(void) {
|
||||
p_usb_init();
|
||||
p_usb_find_busses();
|
||||
p_usb_find_devices();
|
||||
for (usb_bus *bus = p_usb_get_busses(); bus; bus = bus->next) {
|
||||
for (usb_device *dev = bus->devices; dev; dev = dev->next) {
|
||||
if (dev->descriptor.idVendor == 0x833c) {
|
||||
g_dev = p_usb_open(dev);
|
||||
if (g_dev) {
|
||||
p_usb_set_configuration(g_dev, 1);
|
||||
if (p_usb_claim_interface(g_dev, 0) == 0) return 0;
|
||||
p_usb_close(g_dev);
|
||||
g_dev = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int init_camera(void) {
|
||||
for (int attempt = 0; attempt < 4; ++attempt) {
|
||||
if (g_log) { fprintf(g_log, "init attempt %d\n", attempt + 1); fflush(g_log); }
|
||||
if (open_camera() != 0) { Sleep(2000); continue; }
|
||||
if (sendcmd(MAG_CMD_PREPARE1, 0, 4)) { Sleep(2000); continue; }
|
||||
if (sendcmd(MAG_CMD_PREPARE2, 0, 4)) { Sleep(2000); continue; }
|
||||
if (sendcmd(MAG_CMD_GET_INFO, 0, 4)) { Sleep(2000); continue; }
|
||||
if (sendcmd(MAG_CMD_FFC, 0, 8)) { Sleep(2000); continue; }
|
||||
Sleep(100);
|
||||
if (sendcmd(MAG_CMD_FFC, 0, 8)) { Sleep(2000); continue; }
|
||||
Sleep(300);
|
||||
if (sendcmd(MAG_CMD_START, 0, 4)) { Sleep(2000); continue; }
|
||||
Sleep(700);
|
||||
if (g_log) { fprintf(g_log, "init ok (attempt %d)\n", attempt + 1); fflush(g_log); }
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE inst, HINSTANCE hprev, LPSTR cmd, int show) {
|
||||
(void)hprev; (void)cmd; (void)show;
|
||||
g_log = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/demo_usb0_log.txt", "w");
|
||||
if (g_log) { fprintf(g_log, "demo start\n"); fflush(g_log); }
|
||||
|
||||
HMODULE l0 = LoadLibraryA("libusb0.dll");
|
||||
if (!l0) { MessageBoxA(NULL, "libusb0.dll not found", "MAG160C Demo", MB_ICONERROR); return 1; }
|
||||
p_usb_init = (fn_usb_init)GetProcAddress(l0, "usb_init");
|
||||
p_usb_find_busses = (fn_usb_find_busses)GetProcAddress(l0, "usb_find_busses");
|
||||
p_usb_find_devices = (fn_usb_find_devices)GetProcAddress(l0, "usb_find_devices");
|
||||
p_usb_get_busses = (fn_usb_get_busses)GetProcAddress(l0, "usb_get_busses");
|
||||
p_usb_open = (fn_usb_open)GetProcAddress(l0, "usb_open");
|
||||
p_usb_set_configuration = (fn_usb_set_configuration)GetProcAddress(l0, "usb_set_configuration");
|
||||
p_usb_claim_interface = (fn_usb_claim_interface)GetProcAddress(l0, "usb_claim_interface");
|
||||
p_usb_bulk_write = (fn_usb_bulk_write)GetProcAddress(l0, "usb_bulk_write");
|
||||
p_usb_bulk_read = (fn_usb_bulk_read)GetProcAddress(l0, "usb_bulk_read");
|
||||
p_usb_close = (fn_usb_close)GetProcAddress(l0, "usb_close");
|
||||
|
||||
if (init_camera() != 0) {
|
||||
MessageBoxA(NULL, "camera init failed", "MAG160C Demo", MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
WNDCLASSA wc = {0};
|
||||
wc.lpfnWndProc = wndproc;
|
||||
wc.hInstance = inst;
|
||||
wc.lpszClassName = "Mag160cDemoUsb0";
|
||||
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
|
||||
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
RegisterClassA(&wc);
|
||||
g_hwnd = CreateWindowA("Mag160cDemoUsb0", "MAG160C Thermal Demo (libusb0)",
|
||||
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 600,
|
||||
NULL, NULL, inst, NULL);
|
||||
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
|
||||
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
|
||||
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
|
||||
670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL);
|
||||
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
||||
820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
|
||||
|
||||
unsigned sz = 54 + IR_W * IR_H * 3;
|
||||
g_bmp[0] = 'B'; g_bmp[1] = 'M';
|
||||
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
|
||||
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
|
||||
g_bmp[10] = 54; g_bmp[14] = 40;
|
||||
g_bmp[18] = IR_W; g_bmp[19] = 0; g_bmp[22] = IR_H; g_bmp[23] = 0;
|
||||
g_bmp[26] = 1; g_bmp[28] = 24;
|
||||
|
||||
ShowWindow(g_hwnd, SW_SHOW);
|
||||
set_status("starting...");
|
||||
|
||||
unsigned prevcnt = 0xffffffff;
|
||||
int frames = 0, ffc_done = 0;
|
||||
unsigned long long ref_sum[IR_W * IR_H] = {0};
|
||||
int ref_n = 0;
|
||||
DWORD t0 = GetTickCount();
|
||||
DWORD last_fps_t = t0;
|
||||
unsigned last_fps_cnt = 0;
|
||||
|
||||
MSG msg;
|
||||
for (;;) {
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) goto done;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
unsigned char hdr[64];
|
||||
int xfer = p_usb_bulk_read(g_dev, 0x81, (char *)hdr, sizeof(hdr), 100);
|
||||
if (xfer < 28) continue;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) continue;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prevcnt) continue;
|
||||
prevcnt = c;
|
||||
xfer = p_usb_bulk_read(g_dev, 0x81, (char *)g_frame, sizeof(g_frame), 100);
|
||||
if (xfer < FRAME_LEN) continue;
|
||||
frames++;
|
||||
if (!ffc_done && frames == 3) {
|
||||
sendcmd(MAG_CMD_FFC, 1, 8); /* switch to type=0 (temperature) */
|
||||
ffc_done = 1;
|
||||
Sleep(400);
|
||||
continue;
|
||||
}
|
||||
g_frame_count++;
|
||||
g_frame_type = (unsigned)hdr[12];
|
||||
if (!g_has_reference && frames >= 150 && ref_n < 30) {
|
||||
for (int i = 0; i < FRAME_LEN; i += 2)
|
||||
ref_sum[i / 2] += (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8));
|
||||
ref_n++;
|
||||
if (ref_n == 30) {
|
||||
for (int i = 0; i < IR_W * IR_H; ++i) g_reference[i] = (unsigned short)(ref_sum[i] / 30);
|
||||
g_has_reference = 1;
|
||||
set_status("reference captured - hand shows red");
|
||||
}
|
||||
} else if (g_has_reference) {
|
||||
for (int i = 0; i < FRAME_LEN; i += 2) {
|
||||
unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8);
|
||||
unsigned r = g_reference[i / 2];
|
||||
g_reference[i / 2] = (unsigned short)((r * 199 + v) / 200);
|
||||
}
|
||||
}
|
||||
render(g_frame);
|
||||
DWORD now = GetTickCount();
|
||||
if (now - last_fps_t >= 1000) {
|
||||
g_fps = (g_frame_count - last_fps_cnt) * 1000.0 / (now - last_fps_t);
|
||||
last_fps_t = now;
|
||||
last_fps_cnt = g_frame_count;
|
||||
}
|
||||
InvalidateRect(g_hwnd, NULL, FALSE);
|
||||
UpdateWindow(g_hwnd);
|
||||
}
|
||||
done:
|
||||
sendcmd(MAG_CMD_STOP, 0, 4);
|
||||
p_usb_close(g_dev);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/* MAG160C headless FFC cadence test - replicates the official demo FFC
|
||||
* timing from analysis/captures/libusb0_trace.txt:
|
||||
* init: 66b 66c 66f -> FFC(0) -> START
|
||||
* FFC(1) after ~10th complete frame (switch stream to type=0)
|
||||
* then every FFC_PERIOD frames: FFC(0), and FFC_GAP frames later FFC(1)
|
||||
* Target: type=0 stream keeps flowing for 1000+ frames without stalling.
|
||||
* Prints per-second progress; exits with 0 on success.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#define FFC_PERIOD 400
|
||||
#define FFC_GAP 9
|
||||
#define WANT_FRAMES 1400
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("MAG160C FFC cadence test (want %d frames)\n", WANT_FRAMES);
|
||||
if (libusb_init(&g_ctx)) { printf("libusb_init failed\n"); return 1; }
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) { printf("no device\n"); libusb_exit(g_ctx); return 1; }
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
if (libusb_claim_interface(g_h, 0)) { printf("claim failed\n"); return 1; }
|
||||
|
||||
if (sendcmd(0x6bb6b66b, 0, 4)) { printf("66b failed\n"); return 1; }
|
||||
if (sendcmd(0x6bb6b66c, 0, 4)) { printf("66c failed\n"); return 1; }
|
||||
if (sendcmd(0x6bb6b66f, 0, 4)) { printf("66f failed\n"); return 1; }
|
||||
if (sendcmd(0x6bb6b672, 0, 8)) { printf("FFC(0) pre failed\n"); return 1; }
|
||||
Sleep(100);
|
||||
if (sendcmd(0x6bb6b672, 0, 8)) { printf("FFC(0) pre2 failed\n"); return 1; }
|
||||
Sleep(300);
|
||||
if (sendcmd(0x6bb6b673, 0, 4)) { printf("START failed\n"); return 1; }
|
||||
Sleep(700);
|
||||
|
||||
unsigned char frame[40000];
|
||||
unsigned prev = 0xffffffff;
|
||||
int nread = 0, n0 = 0, n1 = 0;
|
||||
int ffc_started = 0, ffc_cycle = 0, ffc_wait1 = 0;
|
||||
int stall_wakes = 0;
|
||||
DWORD t_start = GetTickCount();
|
||||
DWORD last_frame = t_start, last_wake = t_start;
|
||||
DWORD last_report = t_start;
|
||||
|
||||
while (n0 < WANT_FRAMES) {
|
||||
unsigned char hdr[64];
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) {
|
||||
DWORD now = GetTickCount();
|
||||
if (now - last_frame > 3000 && now - last_wake > 5000) {
|
||||
sendcmd(0x6bb6b672, 1, 8);
|
||||
last_wake = now;
|
||||
stall_wakes++;
|
||||
printf(" [stall watchdog] FFC(1) sent (%d)\n", stall_wakes);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (xfer < 28) continue;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) continue;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prev) continue;
|
||||
prev = c;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, frame, sizeof(frame), &xfer, 200) ||
|
||||
xfer < 38400) {
|
||||
continue;
|
||||
}
|
||||
unsigned type = (unsigned)hdr[12];
|
||||
nread++;
|
||||
last_frame = GetTickCount();
|
||||
if (type == 0) n0++; else n1++;
|
||||
|
||||
if (!ffc_started && nread == 10) {
|
||||
sendcmd(0x6bb6b672, 1, 8);
|
||||
ffc_started = 1;
|
||||
ffc_cycle = 0;
|
||||
printf(" frame 10: FFC(1) sent -> type=0 mode\n");
|
||||
continue;
|
||||
}
|
||||
if (ffc_started) {
|
||||
ffc_cycle++;
|
||||
if (ffc_wait1 && ffc_cycle >= FFC_GAP) {
|
||||
sendcmd(0x6bb6b672, 1, 8);
|
||||
ffc_wait1 = 0;
|
||||
ffc_cycle = 0;
|
||||
} else if (!ffc_wait1 && ffc_cycle >= FFC_PERIOD) {
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
ffc_wait1 = 1;
|
||||
ffc_cycle = 0;
|
||||
}
|
||||
}
|
||||
DWORD now = GetTickCount();
|
||||
if (now - last_report >= 5000) {
|
||||
double secs = (now - t_start) / 1000.0;
|
||||
printf(" t=%6.1fs frames=%d (type0=%d type1=%d) fps=%.1f\n",
|
||||
secs, nread, n0, n1, n0 / secs);
|
||||
last_report = now;
|
||||
}
|
||||
}
|
||||
DWORD t_end = GetTickCount();
|
||||
double secs = (t_end - t_start) / 1000.0;
|
||||
printf("\nDONE: %d frames in %.1fs (fps=%.1f) type0=%d type1=%d stall_wakes=%d\n",
|
||||
nread, secs, nread / secs, n0, n1, stall_wakes);
|
||||
int pass = (n0 >= 1000) ? 1 : 0;
|
||||
printf("%s\n", pass ? "PASS: type=0 stream >= 1000 frames" :
|
||||
"FAIL: type=0 stream < 1000 frames");
|
||||
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return pass ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Save raw type=0 frames (bin) + frame metadata for offline analysis.
|
||||
* Usage: mag160c_frame_dump <nframes> <outdir>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#define W 160
|
||||
#define H 120
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_frame(unsigned char *frame, unsigned *type) {
|
||||
unsigned char hdr[64];
|
||||
static unsigned prev = 0xffffffff;
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) return 0;
|
||||
if (xfer < 28) return 0;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) return 0;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prev) return 0;
|
||||
prev = c;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, frame, 40000, &xfer, 200) || xfer < 38400) return 0;
|
||||
*type = (unsigned)hdr[12];
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int nframes = argc > 1 ? atoi(argv[1]) : 60;
|
||||
const char *dir = argc > 2 ? argv[2] : ".";
|
||||
if (libusb_init(&g_ctx)) return 1;
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) { printf("no device\n"); return 1; }
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
if (libusb_claim_interface(g_h, 0)) return 1;
|
||||
sendcmd(0x6bb6b66b, 0, 4);
|
||||
sendcmd(0x6bb6b66c, 0, 4);
|
||||
sendcmd(0x6bb6b66f, 0, 4);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(100);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(300);
|
||||
sendcmd(0x6bb6b673, 0, 4);
|
||||
Sleep(700);
|
||||
|
||||
unsigned char frame[40000];
|
||||
unsigned type = 1;
|
||||
int drained = 0;
|
||||
for (int i = 0; i < 120; ++i) {
|
||||
if (!read_frame(frame, &type)) { Sleep(10); continue; }
|
||||
drained++;
|
||||
if (drained == 10) {
|
||||
sendcmd(0x6bb6b672, 1, 8);
|
||||
Sleep(200);
|
||||
}
|
||||
if (type == 0) break;
|
||||
}
|
||||
if (type != 0) { printf("no type=0 stream\n"); return 3; }
|
||||
|
||||
char path[512];
|
||||
FILE *meta = NULL;
|
||||
snprintf(path, sizeof(path), "%s/meta.txt", dir);
|
||||
meta = fopen(path, "w");
|
||||
for (int n = 0; n < nframes; ++n) {
|
||||
if (!read_frame(frame, &type)) { Sleep(10); n--; continue; }
|
||||
snprintf(path, sizeof(path), "%s/f%03d.bin", dir, n);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(frame + 28, 1, 38400, f); fclose(f); }
|
||||
if (meta) {
|
||||
long s = 0; unsigned mn = 65535, mx = 0;
|
||||
for (int i = 0; i < W * H; ++i) {
|
||||
unsigned v = (unsigned)frame[i * 2 + 28] | ((unsigned)frame[i * 2 + 29] << 8);
|
||||
s += v; if (v < mn) mn = v; if (v > mx) mx = v;
|
||||
}
|
||||
fprintf(meta, "%d %u %ld %.0f %u %u\n", n, type, s,
|
||||
(double)s / (W * H), mn, mx);
|
||||
}
|
||||
}
|
||||
if (meta) fclose(meta);
|
||||
printf("dumped %d frames to %s\n", nframes, dir);
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/* Capture type=0 frames and dump spatial/temporal statistics to reveal
|
||||
* fixed-pattern noise (mura), bad pixels, and startup-vs-settled behavior.
|
||||
* Writes per-pixel mean over N frames + frame-level row/col profiles.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#define W 160
|
||||
#define H 120
|
||||
#define NPIX (W * H)
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* return 1 on a valid type=0 frame */
|
||||
static int read_frame(unsigned char *frame, unsigned *type) {
|
||||
unsigned char hdr[64];
|
||||
static unsigned prev = 0xffffffff;
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) return 0;
|
||||
if (xfer < 28) return 0;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) return 0;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prev) return 0;
|
||||
prev = c;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, frame, 40000, &xfer, 200) || xfer < 38400) return 0;
|
||||
*type = (unsigned)hdr[12];
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int nframes = argc > 1 ? atoi(argv[1]) : 200;
|
||||
const char *out = argc > 2 ? argv[2] : "frame_stats.txt";
|
||||
if (libusb_init(&g_ctx)) return 1;
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) { printf("no device\n"); return 1; }
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
if (libusb_claim_interface(g_h, 0)) return 1;
|
||||
sendcmd(0x6bb6b66b, 0, 4);
|
||||
sendcmd(0x6bb6b66c, 0, 4);
|
||||
sendcmd(0x6bb6b66f, 0, 4);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(100);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(300);
|
||||
sendcmd(0x6bb6b673, 0, 4);
|
||||
Sleep(700);
|
||||
|
||||
unsigned char frame[40000];
|
||||
unsigned type = 1;
|
||||
/* drain + FFC(1) after ~10 complete frames switches the stream to
|
||||
* type=0 (verified cadence) */
|
||||
int drained = 0;
|
||||
for (int i = 0; i < 120; ++i) {
|
||||
if (!read_frame(frame, &type)) { Sleep(10); continue; }
|
||||
drained++;
|
||||
if (drained == 10) {
|
||||
sendcmd(0x6bb6b672, 1, 8);
|
||||
Sleep(200);
|
||||
}
|
||||
if (type == 0) break;
|
||||
}
|
||||
if (type != 0) {
|
||||
printf("failed to reach type=0 stream\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
double mean[NPIX];
|
||||
memset(mean, 0, sizeof(mean));
|
||||
double row[H], col[W];
|
||||
memset(row, 0, sizeof(row));
|
||||
memset(col, 0, sizeof(col));
|
||||
unsigned short last[NPIX];
|
||||
double delta_sum = 0;
|
||||
int delta_n = 0;
|
||||
int got = 0, type0 = 0, type1 = 0;
|
||||
|
||||
FILE *f = fopen(out, "w");
|
||||
if (!f) return 2;
|
||||
|
||||
for (int n = 0; n < nframes; ++n) {
|
||||
if (!read_frame(frame, &type)) { Sleep(10); n--; continue; }
|
||||
got++;
|
||||
if (type) { type1++; continue; }
|
||||
type0++;
|
||||
/* frame stats */
|
||||
long fr_sum = 0;
|
||||
unsigned fr_min = 65535, fr_max = 0;
|
||||
for (int i = 0; i < NPIX; ++i) {
|
||||
unsigned v = (unsigned)frame[i * 2 + 28] | ((unsigned)frame[i * 2 + 29] << 8);
|
||||
mean[i] += v;
|
||||
fr_sum += v;
|
||||
if (v < fr_min) fr_min = v;
|
||||
if (v > fr_max) fr_max = v;
|
||||
if (got > 1) {
|
||||
int d = (int)v - (int)last[i];
|
||||
if (d < 0) d = -d;
|
||||
delta_sum += d;
|
||||
delta_n++;
|
||||
}
|
||||
last[i] = (unsigned short)v;
|
||||
}
|
||||
fprintf(f, "FRAME %d type0 mean=%.0f min=%u max=%u meandelta=%.1f\n",
|
||||
n, (double)fr_sum / NPIX, fr_min, fr_max,
|
||||
delta_n ? delta_sum / delta_n : 0);
|
||||
delta_sum = 0;
|
||||
delta_n = 0;
|
||||
}
|
||||
for (int i = 0; i < NPIX; ++i) mean[i] /= type0 ? type0 : 1;
|
||||
for (int y = 0; y < H; ++y) {
|
||||
double s = 0;
|
||||
for (int x = 0; x < W; ++x) s += mean[y * W + x];
|
||||
row[y] = s / W;
|
||||
}
|
||||
for (int x = 0; x < W; ++x) {
|
||||
double s = 0;
|
||||
for (int y = 0; y < H; ++y) s += mean[y * W + x];
|
||||
col[x] = s / H;
|
||||
}
|
||||
/* per-pixel deviation from row+col model (mura detection) */
|
||||
fprintf(f, "\nROW_PROFILE:\n");
|
||||
for (int y = 0; y < H; ++y) fprintf(f, "%d %.1f\n", y, row[y]);
|
||||
fprintf(f, "\nCOL_PROFILE:\n");
|
||||
for (int x = 0; x < W; ++x) fprintf(f, "%d %.1f\n", x, col[x]);
|
||||
fprintf(f, "\nPIXEL_DEVIATION (mean - row - col + global):\n");
|
||||
double gm = 0;
|
||||
for (int i = 0; i < NPIX; ++i) gm += mean[i];
|
||||
gm /= NPIX;
|
||||
for (int y = 0; y < H; ++y) {
|
||||
for (int x = 0; x < W; ++x) {
|
||||
double dev = mean[y * W + x] - row[y] - col[x] + gm;
|
||||
fprintf(f, "%d %d %.1f\n", x, y, dev);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
printf("captured %d frames (type0=%d type1=%d) -> %s\n", got, type0, type1, out);
|
||||
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/* Dump COMPLETE frames: 28B header + full data read, byte-exact. */
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <libusb.h>
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int nframes = argc > 1 ? atoi(argv[1]) : 10;
|
||||
const char *dir = argc > 2 ? argv[2] : ".";
|
||||
if (libusb_init(&g_ctx)) return 1;
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) { printf("no device\n"); return 1; }
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
libusb_claim_interface(g_h, 0);
|
||||
sendcmd(0x6bb6b66b, 0, 4);
|
||||
sendcmd(0x6bb6b66c, 0, 4);
|
||||
sendcmd(0x6bb6b66f, 0, 4);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(100);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(300);
|
||||
sendcmd(0x6bb6b673, 0, 4);
|
||||
Sleep(700);
|
||||
|
||||
unsigned char hdr[64];
|
||||
unsigned char buf[40000];
|
||||
unsigned prev = 0xffffffff;
|
||||
int frames = 0, ffc_done = 0;
|
||||
|
||||
while (frames < nframes) {
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 500) || xfer < 28) { Sleep(10); continue; }
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prev) continue;
|
||||
prev = c;
|
||||
xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, buf, sizeof(buf), &xfer, 500) || xfer < 38400) { Sleep(10); continue; }
|
||||
frames++;
|
||||
if (!ffc_done && frames == 3) { sendcmd(0x6bb6b672, 1, 8); ffc_done = 1; Sleep(200); }
|
||||
/* save full frame: header (28) + data (xfer) */
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/raw%03d.bin", dir, frames);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
fwrite(hdr, 1, 28, f);
|
||||
fwrite(buf, 1, xfer, f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
printf("saved %d full frames to %s\n", frames, dir);
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/* Verify the exact frame layout: print lengths and markers of the two
|
||||
* bulk reads (header read + data read), and the tail bytes. */
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <libusb.h>
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (libusb_init(&g_ctx)) return 1;
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) { printf("no device\n"); return 1; }
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
libusb_claim_interface(g_h, 0);
|
||||
sendcmd(0x6bb6b66b, 0, 4);
|
||||
sendcmd(0x6bb6b66c, 0, 4);
|
||||
sendcmd(0x6bb6b66f, 0, 4);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(100);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(300);
|
||||
sendcmd(0x6bb6b673, 0, 4);
|
||||
Sleep(700);
|
||||
|
||||
unsigned char hdr[64];
|
||||
unsigned char buf[40000];
|
||||
unsigned prev = 0xffffffff;
|
||||
int frames = 0;
|
||||
int ffc_done = 0;
|
||||
|
||||
while (frames < 6) {
|
||||
int xfer = 0;
|
||||
int rc = libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 500);
|
||||
if (rc || xfer < 28) { printf("hdr rc=%d xfer=%d\n", rc, xfer); Sleep(10); continue; }
|
||||
printf("HDR read: xfer=%d marker=%02x %02x %02x %02x\n",
|
||||
xfer, hdr[0], hdr[1], hdr[2], hdr[3]);
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
||||
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
||||
if (c == prev) { printf(" dup frame %u\n", c); continue; }
|
||||
prev = c;
|
||||
xfer = 0;
|
||||
rc = libusb_bulk_transfer(g_h, 0x81, buf, sizeof(buf), &xfer, 500);
|
||||
printf("DATA read: rc=%d xfer=%d\n", rc, xfer);
|
||||
if (xfer >= 8) {
|
||||
printf(" first8: %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
|
||||
printf(" last8 : %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
buf[xfer-8], buf[xfer-7], buf[xfer-6], buf[xfer-5],
|
||||
buf[xfer-4], buf[xfer-3], buf[xfer-2], buf[xfer-1]);
|
||||
}
|
||||
frames++;
|
||||
if (!ffc_done && frames == 2) { sendcmd(0x6bb6b672, 1, 8); ffc_done = 1; Sleep(200); }
|
||||
/* also print type */
|
||||
printf(" type=%u (from hdr[12])\n", (unsigned)hdr[12]);
|
||||
/* search for 1b b1 b1 1c in the data buffer */
|
||||
int found = -1;
|
||||
for (int i = 0; i + 4 <= xfer; ++i)
|
||||
if (buf[i]==0x1b && buf[i+1]==0xb1 && buf[i+2]==0xb1 && buf[i+3]==0x1c) { found = i; break; }
|
||||
printf(" trailer 1bb1b11c found at offset %d\n", found);
|
||||
}
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/* Probe: exact layout of the 0x81 stream as seen by our 2-read capture. */
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
#include <libusb.h>
|
||||
|
||||
static libusb_context *g_ctx;
|
||||
static libusb_device_handle *g_h;
|
||||
|
||||
static int sendcmd(unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
||||
unsigned char resp[0x1000];
|
||||
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
libusb_init(&g_ctx);
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
if (!g_h) { printf("no device\n"); return 1; }
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
if (libusb_claim_interface(g_h, 0)) return 1;
|
||||
sendcmd(0x6bb6b66b, 0, 4);
|
||||
sendcmd(0x6bb6b66c, 0, 4);
|
||||
sendcmd(0x6bb6b66f, 0, 4);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(100);
|
||||
sendcmd(0x6bb6b672, 0, 8);
|
||||
Sleep(300);
|
||||
sendcmd(0x6bb6b673, 0, 4);
|
||||
Sleep(700);
|
||||
|
||||
for (int n = 0; n < 8; ++n) {
|
||||
unsigned char hdr[64];
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) || xfer < 28) { Sleep(10); continue; }
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
||||
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
||||
if (m != 0x1bb1b11b) { printf("read1: no magic (m=%08x, xfer=%d)\n", m, xfer); continue; }
|
||||
printf("read1: xfer=%d magic ok type=%u len=%u\n", xfer, hdr[12],
|
||||
(unsigned)hdr[8] | ((unsigned)hdr[9] << 8) | ((unsigned)hdr[10] << 16) | ((unsigned)hdr[11] << 24));
|
||||
unsigned char buf[40000];
|
||||
if (libusb_bulk_transfer(g_h, 0x81, buf, sizeof(buf), &xfer, 200)) { printf("read2 fail\n"); continue; }
|
||||
printf("read2: xfer=%d\n", xfer);
|
||||
printf(" buf[0..15] hex: ");
|
||||
for (int i = 0; i < 16; ++i) printf("%02x ", buf[i]);
|
||||
printf("\n");
|
||||
/* search for trailer magic 1bb1b11c */
|
||||
int found = -1;
|
||||
for (int i = 0; i + 4 <= xfer; ++i) {
|
||||
unsigned v = (unsigned)buf[i] | ((unsigned)buf[i+1] << 8) |
|
||||
((unsigned)buf[i+2] << 16) | ((unsigned)buf[i+3] << 24);
|
||||
if (v == 0x1bb1b11c) { found = i; break; }
|
||||
}
|
||||
printf(" trailer 1bb1b11c at buf+%d (xfer=%d)\n", found, xfer);
|
||||
printf(" buf[found+4..found+11]: ");
|
||||
for (int i = 0; i < 8 && found + 4 + i < xfer; ++i) printf("%02x ", buf[found + 4 + i]);
|
||||
printf("\n");
|
||||
/* dump u16 pixel candidates at 0 and 28 */
|
||||
unsigned p0 = buf[0] | (buf[1] << 8);
|
||||
unsigned p28 = buf[28] | (buf[29] << 8);
|
||||
printf(" u16@0=%u u16@28=%u (background ~9500-11000)\n", p0, p28);
|
||||
return 0;
|
||||
}
|
||||
printf("no frame seen\n");
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(g_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/* Official CoreSDKLib.dll harness: drive the vendor pipeline on the real
|
||||
* device and dump (a) the official rendered BMP, (b) temperature data,
|
||||
* (c) the Gray2Temperature LUT. This is the authoritative reference for
|
||||
* what the official app displays.
|
||||
*
|
||||
* Build: gcc official_harness.c -o official_harness.exe -ldl (loads dll
|
||||
* manually so we can use the vendor's own libusb0.dll from its folder).
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* --- MAG API prototypes (from CoreSDKLib.dll exports, verified RVAs) ---
|
||||
* Initialize 0x22c0 (chan, ...) EnumCameras 0x2590 (chan)
|
||||
* NewChannel 0x1ee0 (chan) LinkCamera 0x2650 (chan, pid, ...)
|
||||
* StartProcessImage 0x2cd0 (chan, ...) GetTemperatureData 0x3fc0 (chan,...)
|
||||
*/
|
||||
typedef int (*MAG_Initialize_t)(int, int);
|
||||
typedef void (*MAG_Free_t)(void);
|
||||
typedef int (*MAG_IsInitialized_t)(int);
|
||||
typedef int (*MAG_EnumCameras_t)(int);
|
||||
typedef int (*MAG_NewChannel_t)(int);
|
||||
typedef int (*MAG_LinkCamera_t)(int, int, int);
|
||||
typedef void (*MAG_DisLinkCamera_t)(int);
|
||||
typedef int (*MAG_StartProcessImage_t)(int, void *, int, int);
|
||||
typedef int (*MAG_StopProcessImage_t)(int);
|
||||
typedef int (*MAG_IsProcessingImage_t)(int);
|
||||
typedef int (*MAG_GetOutputBMPdataRGB24_t)(int, unsigned char *, int, int);
|
||||
typedef int (*MAG_GetOutputBMPdata_t)(int, unsigned char *, int);
|
||||
typedef int (*MAG_GetTemperatureData_t)(int, int *, int, int);
|
||||
typedef int (*MAG_GetCamInfo_t)(int, void *);
|
||||
typedef int (*MAG_TriggerFFC_t)(int, int);
|
||||
typedef int (*MAG_SetFixPara_t)(int, void *, int);
|
||||
typedef int (*MAG_GetApproximateGray2TemperatureLUT_t)(int, float *);
|
||||
|
||||
static HMODULE g_core;
|
||||
static MAG_Initialize_t fn_Initialize;
|
||||
static MAG_Free_t fn_Free;
|
||||
static MAG_IsInitialized_t fn_IsInitialized;
|
||||
static MAG_EnumCameras_t fn_EnumCameras;
|
||||
static MAG_NewChannel_t fn_NewChannel;
|
||||
static MAG_LinkCamera_t fn_LinkCamera;
|
||||
static MAG_DisLinkCamera_t fn_DisLinkCamera;
|
||||
static MAG_StartProcessImage_t fn_StartProcessImage;
|
||||
static MAG_StopProcessImage_t fn_StopProcessImage;
|
||||
static MAG_IsProcessingImage_t fn_IsProcessingImage;
|
||||
static MAG_GetOutputBMPdataRGB24_t fn_GetOutputBMPdataRGB24;
|
||||
static MAG_GetOutputBMPdata_t fn_GetOutputBMPdata;
|
||||
static MAG_GetTemperatureData_t fn_GetTemperatureData;
|
||||
static MAG_GetCamInfo_t fn_GetCamInfo;
|
||||
static MAG_TriggerFFC_t fn_TriggerFFC;
|
||||
static MAG_SetFixPara_t fn_SetFixPara;
|
||||
static MAG_GetApproximateGray2TemperatureLUT_t fn_GetApproximateGray2TemperatureLUT;
|
||||
|
||||
|
||||
#define LOAD(name) fn_##name = (MAG_##name##_t)GetProcAddress(g_core, "MAG_" #name); \
|
||||
if (!fn_##name) { printf("missing export MAG_" #name "\n"); return 2; }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *dllpath = argc > 1 ? argv[1]
|
||||
: "C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app\\CoreSDKLib.dll";
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
g_core = LoadLibraryA(dllpath);
|
||||
if (!g_core) { printf("LoadLibrary failed: %lu\n", GetLastError()); return 1; }
|
||||
LOAD(Initialize); LOAD(Free); LOAD(IsInitialized); LOAD(EnumCameras);
|
||||
LOAD(NewChannel); LOAD(LinkCamera); LOAD(DisLinkCamera); LOAD(StartProcessImage);
|
||||
LOAD(StopProcessImage); LOAD(IsProcessingImage); LOAD(GetOutputBMPdataRGB24);
|
||||
LOAD(GetOutputBMPdata); LOAD(GetTemperatureData); LOAD(GetCamInfo);
|
||||
LOAD(TriggerFFC); LOAD(SetFixPara); LOAD(GetApproximateGray2TemperatureLUT);
|
||||
|
||||
printf("CoreSDKLib loaded, isInit=%d\n", fn_IsInitialized(0));
|
||||
int rc = fn_Initialize(0, 0);
|
||||
printf("MAG_Initialize(0,0) rc=%d\n", rc);
|
||||
|
||||
/* official Windows sequence: NewChannel(0) first, then EnumCameras */
|
||||
int chan = fn_NewChannel(0);
|
||||
printf("MAG_NewChannel(0) -> channel=%d\n", chan);
|
||||
rc = fn_EnumCameras(chan);
|
||||
printf("MAG_EnumCameras(chan) rc=%d\n", rc);
|
||||
rc = fn_LinkCamera(chan, 0x101, 0);
|
||||
printf("MAG_LinkCamera(chan,pid,0) rc=%d\n", rc);
|
||||
|
||||
/* camera info */
|
||||
unsigned char info[0x100] = {0};
|
||||
rc = fn_GetCamInfo(chan, info);
|
||||
printf("MAG_GetCamInfo rc=%d\n", rc);
|
||||
|
||||
rc = fn_StartProcessImage(chan, NULL, 0x10, 0);
|
||||
printf("MAG_StartProcessImage rc=%d\n", rc);
|
||||
Sleep(3000);
|
||||
fn_TriggerFFC(chan, 1);
|
||||
Sleep(2000);
|
||||
printf("after FFC(1): isProcessing=%d\n", fn_IsProcessingImage(chan));
|
||||
|
||||
/* temperature LUT (256 floats) */
|
||||
float lut[256];
|
||||
rc = fn_GetApproximateGray2TemperatureLUT(chan, lut);
|
||||
printf("Gray2TempLUT rc=%d lut[0]=%.3f lut[64]=%.3f lut[128]=%.3f lut[255]=%.3f\n",
|
||||
rc, lut[0], lut[64], lut[128], lut[255]);
|
||||
if (rc == 0) {
|
||||
FILE *f = fopen("official_g2t_lut.txt", "w");
|
||||
for (int i = 0; i < 256; ++i) fprintf(f, "%d %.4f\n", i, lut[i]);
|
||||
fclose(f);
|
||||
printf("saved official_g2t_lut.txt\n");
|
||||
}
|
||||
|
||||
/* official BMP */
|
||||
unsigned char bmp[160 * 120 * 3 + 64];
|
||||
rc = fn_GetOutputBMPdataRGB24(chan, bmp, 160 * 120 * 3, 1);
|
||||
printf("GetOutputBMPdataRGB24 rc=%d first px RGB=%d,%d,%d\n",
|
||||
rc, bmp[0], bmp[1], bmp[2]);
|
||||
if (rc) {
|
||||
/* save as BMP file */
|
||||
unsigned sz = 54 + 160 * 120 * 3;
|
||||
unsigned char hdr[54] = {0};
|
||||
hdr[0] = 'B'; hdr[1] = 'M';
|
||||
hdr[2] = sz; hdr[3] = sz >> 8; hdr[4] = sz >> 16; hdr[5] = sz >> 24;
|
||||
hdr[10] = 54; hdr[14] = 40;
|
||||
hdr[18] = 160; hdr[22] = 120; hdr[26] = 1; hdr[28] = 24;
|
||||
FILE *f = fopen("official_render.bmp", "wb");
|
||||
fwrite(hdr, 1, 54, f);
|
||||
fwrite(bmp, 1, 160 * 120 * 3, f);
|
||||
fclose(f);
|
||||
printf("saved official_render.bmp\n");
|
||||
}
|
||||
|
||||
/* temperature data (19200 ints) */
|
||||
static int tempdata[19200];
|
||||
rc = fn_GetTemperatureData(chan, tempdata, 1, 1);
|
||||
printf("GetTemperatureData rc=%d t[0]=%d t[9600]=%d\n", rc, tempdata[0], tempdata[9600]);
|
||||
if (rc) {
|
||||
FILE *f = fopen("official_tempdata.bin", "wb");
|
||||
fwrite(tempdata, 4, 19200, f);
|
||||
fclose(f);
|
||||
printf("saved official_tempdata.bin\n");
|
||||
}
|
||||
|
||||
printf("(inner temp export not present in this dll)\\n");
|
||||
|
||||
fn_StopProcessImage(chan);
|
||||
fn_DisLinkCamera(chan);
|
||||
fn_Free();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/* ThermalSDK.dll harness: use the OFFICIAL high-level SDK directly.
|
||||
* Start() runs the whole vendor pipeline; we capture the rendered IR frame
|
||||
* via SetNewIRFrameDelegate and read temperatures. This gives the exact
|
||||
* ground truth of what the official app displays on this hardware.
|
||||
*
|
||||
* The IR frame callback delivers a 160x120x3 RGB image (official render).
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef void (CALLBACK *INewDistance)(float);
|
||||
typedef void (CALLBACK *IDistanceError)();
|
||||
typedef void (CALLBACK *INewLimitEvent)(bool);
|
||||
typedef void (CALLBACK *UpdateCallback)(bool, int);
|
||||
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef bool (CALLBACK *fn_IsWorking_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef void (CALLBACK *fn_SetNewDistanceDelegate_t)(INewDistance);
|
||||
typedef void (CALLBACK *fn_SetDistanceError_t)(IDistanceError);
|
||||
typedef void (CALLBACK *fn_ReadTemperatureAtPoint_t)(int, int, unsigned char *);
|
||||
typedef void (CALLBACK *fn_SetTempBoundary_t)(double, double, double);
|
||||
typedef void (CALLBACK *fn_SetEmissivity_t)(double);
|
||||
typedef bool (CALLBACK *fn_Trigger_t)(void);
|
||||
typedef int (CALLBACK *fn_GetSDKVersion_t)(void);
|
||||
typedef void (CALLBACK *fn_SetUnitMode_t)(int);
|
||||
|
||||
static HMODULE g_tsdk;
|
||||
static fn_Start_t T_Start;
|
||||
static fn_Stop_t T_Stop;
|
||||
static fn_IsWorking_t T_IsWorking;
|
||||
static fn_SetNewIRFrameDelegate_t T_SetNewIRFrameDelegate;
|
||||
static fn_SetNewDistanceDelegate_t T_SetNewDistanceDelegate;
|
||||
static fn_SetDistanceError_t T_SetDistanceError;
|
||||
static fn_ReadTemperatureAtPoint_t T_ReadTemperatureAtPoint;
|
||||
static fn_SetTempBoundary_t T_SetTempBoundary;
|
||||
static fn_SetEmissivity_t T_SetEmissivity;
|
||||
static fn_Trigger_t T_Trigger;
|
||||
static fn_GetSDKVersion_t T_GetSDKVersion;
|
||||
static fn_SetUnitMode_t T_SetUnitMode;
|
||||
|
||||
static unsigned char g_rgb[160 * 120 * 3];
|
||||
static volatile int g_frame_count;
|
||||
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
if (buf && w == 160 && h == 120 && ch == 3 && g_frame_count < 10) {
|
||||
memcpy(g_rgb, buf, 160 * 120 * 3);
|
||||
g_frame_count++;
|
||||
printf(" IR frame %d: first px RGB=%d,%d,%d\n",
|
||||
g_frame_count, buf[0], buf[1], buf[2]);
|
||||
} else if (buf) {
|
||||
memcpy(g_rgb, buf, 160 * 120 * 3);
|
||||
g_frame_count++;
|
||||
}
|
||||
}
|
||||
static void CALLBACK onDist(float d) { (void)d; }
|
||||
static void CALLBACK onDistErr() {}
|
||||
|
||||
int main(void) {
|
||||
const char *dir = "C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app";
|
||||
SetDllDirectoryA(dir);
|
||||
g_tsdk = LoadLibraryA("ThermalSDK.dll");
|
||||
if (!g_tsdk) { printf("ThermalSDK load failed %lu\n", GetLastError()); return 1; }
|
||||
T_Start = (fn_Start_t)GetProcAddress(g_tsdk, "Start");
|
||||
T_Stop = (fn_Stop_t)GetProcAddress(g_tsdk, "Stop");
|
||||
T_IsWorking = (fn_IsWorking_t)GetProcAddress(g_tsdk, "IsWorking");
|
||||
T_SetNewIRFrameDelegate = (fn_SetNewIRFrameDelegate_t)GetProcAddress(g_tsdk, "SetNewIRFrameDelegate");
|
||||
T_SetNewDistanceDelegate = (fn_SetNewDistanceDelegate_t)GetProcAddress(g_tsdk, "SetNewDistanceDelegate");
|
||||
T_SetDistanceError = (fn_SetDistanceError_t)GetProcAddress(g_tsdk, "SetDistanceError");
|
||||
T_ReadTemperatureAtPoint = (fn_ReadTemperatureAtPoint_t)GetProcAddress(g_tsdk, "ReadTemperatureAtPoint");
|
||||
T_SetTempBoundary = (fn_SetTempBoundary_t)GetProcAddress(g_tsdk, "SetTempBoundary");
|
||||
T_SetEmissivity = (fn_SetEmissivity_t)GetProcAddress(g_tsdk, "SetEmissivity");
|
||||
T_Trigger = (fn_Trigger_t)GetProcAddress(g_tsdk, "Trigger");
|
||||
T_GetSDKVersion = (fn_GetSDKVersion_t)GetProcAddress(g_tsdk, "GetSDKVersion");
|
||||
T_SetUnitMode = (fn_SetUnitMode_t)GetProcAddress(g_tsdk, "SetUnitMode");
|
||||
if (!T_Start || !T_SetNewIRFrameDelegate) { printf("missing exports\n"); return 2; }
|
||||
|
||||
printf("ThermalSDK version: %d\n", T_GetSDKVersion ? T_GetSDKVersion() : -1);
|
||||
T_SetUnitMode(0); /* metric */
|
||||
T_SetTempBoundary(30.0, 44.0, 37.0);
|
||||
T_SetEmissivity(0.98);
|
||||
T_SetNewDistanceDelegate(onDist);
|
||||
T_SetDistanceError(onDistErr);
|
||||
T_SetNewIRFrameDelegate(onIR);
|
||||
|
||||
printf("Start() ...\n");
|
||||
bool ok = T_Start();
|
||||
printf("Start -> %d\n", ok);
|
||||
|
||||
/* wait for frames */
|
||||
DWORD t0 = GetTickCount();
|
||||
while (g_frame_count < 30 && GetTickCount() - t0 < 20000) Sleep(100);
|
||||
printf("received %d IR frames in %.1fs\n", g_frame_count,
|
||||
(GetTickCount() - t0) / 1000.0);
|
||||
|
||||
/* temperature probe at center */
|
||||
unsigned char res[64] = {0};
|
||||
T_ReadTemperatureAtPoint(80, 60, res);
|
||||
printf("center temp result bytes: ");
|
||||
for (int i = 0; i < 32; ++i) printf("%02x ", res[i]);
|
||||
printf("\n");
|
||||
double *d = (double *)(res + 8);
|
||||
printf(" raw=%.4f arm=%.4f (guessed layout)\n", d[0], d[1]);
|
||||
|
||||
/* save official render */
|
||||
if (g_frame_count > 0) {
|
||||
unsigned sz = 54 + 160 * 120 * 3;
|
||||
unsigned char hdr[54] = {0};
|
||||
hdr[0] = 'B'; hdr[1] = 'M';
|
||||
hdr[2] = sz; hdr[3] = sz >> 8; hdr[4] = sz >> 16; hdr[5] = sz >> 24;
|
||||
hdr[10] = 54; hdr[14] = 40;
|
||||
hdr[18] = 160; hdr[22] = 120; hdr[26] = 1; hdr[28] = 24;
|
||||
FILE *f = fopen("official_render.bmp", "wb");
|
||||
if (f) {
|
||||
fwrite(hdr, 1, 54, f);
|
||||
fwrite(g_rgb, 1, 160 * 120 * 3, f);
|
||||
fclose(f);
|
||||
printf("saved official_render.bmp\n");
|
||||
}
|
||||
/* also save raw RGB dump */
|
||||
f = fopen("official_render.rgb", "wb");
|
||||
if (f) { fwrite(g_rgb, 1, 160 * 120 * 3, f); fclose(f); }
|
||||
}
|
||||
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/* Real-time thermal viewer: Win32 window showing live 160x120 frames
|
||||
with pseudo-color. Esc or close window to stop. */
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <libusb.h>
|
||||
|
||||
static libusb_device_handle *g_h;
|
||||
static unsigned char g_frame[40000];
|
||||
static CRITICAL_SECTION g_lock;
|
||||
static volatile int g_new_frame;
|
||||
static volatile int g_running;
|
||||
static unsigned char g_bmp[54 + 160 * 120 * 3];
|
||||
static char g_title[128];
|
||||
|
||||
static int sendcmd(libusb_device_handle *h, unsigned magic, unsigned param, int len) {
|
||||
unsigned char cmd[8] = {0};
|
||||
cmd[0] = (unsigned char)(magic);
|
||||
cmd[1] = (unsigned char)(magic >> 8);
|
||||
cmd[2] = (unsigned char)(magic >> 16);
|
||||
cmd[3] = (unsigned char)(magic >> 24);
|
||||
if (len >= 8) {
|
||||
cmd[4] = (unsigned char)(param);
|
||||
cmd[5] = (unsigned char)(param >> 8);
|
||||
cmd[6] = (unsigned char)(param >> 16);
|
||||
cmd[7] = (unsigned char)(param >> 24);
|
||||
}
|
||||
int xfer = 0;
|
||||
int rc = libusb_bulk_transfer(h, 0x03, cmd, len, &xfer, 2000);
|
||||
if (rc) return rc;
|
||||
unsigned char resp[0x1000];
|
||||
rc = libusb_bulk_transfer(h, 0x82, resp, sizeof(resp), &xfer, 2000);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void render(const unsigned char *data, unsigned mn, unsigned mx) {
|
||||
unsigned char *px = g_bmp + 54;
|
||||
for (int y = 0; y < 120; ++y) {
|
||||
for (int x = 0; x < 160; ++x) {
|
||||
int o = (y * 160 + x) * 2 + 28;
|
||||
unsigned v = (unsigned)data[o] | ((unsigned)data[o+1] << 8);
|
||||
unsigned idx = (v - mn) * 255 / (mx - mn + 1);
|
||||
if (idx > 255) idx = 255;
|
||||
unsigned char r, g, b;
|
||||
if (idx < 64) { r = 0; g = (unsigned char)(idx*4); b = 255; }
|
||||
else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255-(idx-64)*4); }
|
||||
else if (idx < 192) { r = (unsigned char)((idx-128)*4); g = 255; b = 0; }
|
||||
else { r = 255; g = (unsigned char)(255-(idx-192)*4); b = 0; }
|
||||
int dst = (119 - y) * 160 * 3 + x * 3;
|
||||
px[dst+0] = b; px[dst+1] = g; px[dst+2] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD WINAPI reader_thread(LPVOID p) {
|
||||
(void)p;
|
||||
unsigned char hdr[64];
|
||||
unsigned prev = 0xffffffff;
|
||||
int frames = 0;
|
||||
int ffc_done = 0;
|
||||
while (g_running) {
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 2000) || xfer < 28) continue;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1]<<8) |
|
||||
((unsigned)hdr[2]<<16) | ((unsigned)hdr[3]<<24);
|
||||
if (m != 0x1bb1b11b) continue;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5]<<8) |
|
||||
((unsigned)hdr[6]<<16) | ((unsigned)hdr[7]<<24);
|
||||
if (c == prev) continue;
|
||||
prev = c;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 2000) || xfer < 38400) continue;
|
||||
frames++;
|
||||
/* switch to type=0 after the 3rd complete frame (device is streaming) */
|
||||
if (!ffc_done && frames == 3) {
|
||||
sendcmd(g_h, 0x6bb6b672, 1, 8);
|
||||
ffc_done = 1;
|
||||
Sleep(400);
|
||||
continue;
|
||||
}
|
||||
unsigned mn = 0xffff, mx = 0;
|
||||
for (int k = 0; k < 38400; k += 2) {
|
||||
unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8);
|
||||
if (v < mn) mn = v;
|
||||
if (v > mx) mx = v;
|
||||
}
|
||||
EnterCriticalSection(&g_lock);
|
||||
render(g_frame, mn, mx);
|
||||
snprintf(g_title, sizeof(g_title), "MAG160C thermal live - frame %u type=%u range [%u..%u]",
|
||||
c, (unsigned)hdr[12], mn, mx);
|
||||
g_new_frame = 1;
|
||||
LeaveCriticalSection(&g_lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
switch (msg) {
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc = BeginPaint(hw, &ps);
|
||||
HDC mem = CreateCompatibleDC(dc);
|
||||
HBITMAP bm = CreateCompatibleBitmap(dc, 160, 120);
|
||||
HGDIOBJ old = SelectObject(mem, bm);
|
||||
SetDIBitsToDevice(mem, 0, 0, 160, 120, 0, 0, 0, 120, g_bmp + 54,
|
||||
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
||||
StretchBlt(dc, 0, 0, 640, 480, mem, 0, 0, 160, 120, SRCCOPY);
|
||||
SelectObject(mem, old);
|
||||
DeleteObject(bm);
|
||||
DeleteDC(mem);
|
||||
EndPaint(hw, &ps);
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_KEYDOWN:
|
||||
if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; }
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hw, msg, wp, lp);
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
|
||||
(void)prev; (void)cmd; (void)show;
|
||||
libusb_context *ctx = NULL;
|
||||
libusb_init(&ctx);
|
||||
libusb_device **list = NULL;
|
||||
ssize_t cnt = libusb_get_device_list(ctx, &list);
|
||||
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
||||
struct libusb_device_descriptor d;
|
||||
libusb_get_device_descriptor(list[i], &d);
|
||||
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
||||
}
|
||||
if (!g_h) {
|
||||
MessageBoxA(NULL, "no MAG160C device found", "MAG160C", MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
libusb_set_configuration(g_h, 2);
|
||||
libusb_set_configuration(g_h, 1);
|
||||
libusb_claim_interface(g_h, 0);
|
||||
|
||||
sendcmd(g_h, 0x6bb6b66b, 0, 4);
|
||||
sendcmd(g_h, 0x6bb6b66c, 0, 4);
|
||||
sendcmd(g_h, 0x6bb6b66f, 0, 4);
|
||||
sendcmd(g_h, 0x6bb6b672, 0, 8);
|
||||
sendcmd(g_h, 0x6bb6b672, 0, 8);
|
||||
Sleep(300);
|
||||
sendcmd(g_h, 0x6bb6b673, 0, 4);
|
||||
Sleep(500);
|
||||
/* FFC(1) is issued from the reader thread after the 3rd frame */
|
||||
|
||||
WNDCLASSA wc = {0};
|
||||
wc.lpfnWndProc = wndproc;
|
||||
wc.hInstance = inst;
|
||||
wc.lpszClassName = "ThermalView";
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
RegisterClassA(&wc);
|
||||
HWND hw = CreateWindowA("ThermalView", "MAG160C thermal live",
|
||||
WS_OVERLAPPEDWINDOW, 100, 100, 656, 520,
|
||||
NULL, NULL, inst, NULL);
|
||||
|
||||
/* BMP header */
|
||||
unsigned sz = 54 + 160*120*3;
|
||||
g_bmp[0]='B'; g_bmp[1]='M';
|
||||
g_bmp[2]=(unsigned char)sz; g_bmp[3]=(unsigned char)(sz>>8);
|
||||
g_bmp[4]=(unsigned char)(sz>>16); g_bmp[5]=(unsigned char)(sz>>24);
|
||||
g_bmp[10]=54; g_bmp[14]=40;
|
||||
g_bmp[18]=160; g_bmp[19]=0; g_bmp[22]=120; g_bmp[23]=0;
|
||||
g_bmp[26]=1; g_bmp[28]=24;
|
||||
|
||||
InitializeCriticalSection(&g_lock);
|
||||
g_running = 1;
|
||||
ShowWindow(hw, SW_SHOW);
|
||||
|
||||
/* single-threaded: read one frame, render, pump messages */
|
||||
int nread = 0;
|
||||
unsigned prev2 = 0xffffffff;
|
||||
int ffc2 = 0;
|
||||
for (;;) {
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) goto done;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
{
|
||||
unsigned char hdr[64];
|
||||
int xfer = 0;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) continue;
|
||||
if (xfer < 28) continue;
|
||||
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1]<<8) |
|
||||
((unsigned)hdr[2]<<16) | ((unsigned)hdr[3]<<24);
|
||||
if (m != 0x1bb1b11b) continue;
|
||||
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5]<<8) |
|
||||
((unsigned)hdr[6]<<16) | ((unsigned)hdr[7]<<24);
|
||||
if (c == prev2) continue;
|
||||
prev2 = c;
|
||||
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) || xfer < 38400) continue;
|
||||
nread++;
|
||||
if (!ffc2 && nread == 3) {
|
||||
sendcmd(g_h, 0x6bb6b672, 1, 8);
|
||||
ffc2 = 1;
|
||||
Sleep(400);
|
||||
continue;
|
||||
}
|
||||
unsigned hist[65536] = {0};
|
||||
unsigned nz = 0;
|
||||
for (int k = 0; k < 38400; k += 2) {
|
||||
unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8);
|
||||
if (v == 0) continue;
|
||||
hist[v]++;
|
||||
nz++;
|
||||
}
|
||||
unsigned mn = 0, mx = 0;
|
||||
unsigned acc = 0;
|
||||
unsigned lo = (unsigned)(nz * 2 / 100);
|
||||
unsigned hi = (unsigned)(nz * 98 / 100);
|
||||
for (unsigned k = 0; k < 65536; ++k) {
|
||||
acc += hist[k];
|
||||
if (acc >= lo && mn == 0) mn = k;
|
||||
if (acc >= hi && mx == 0) { mx = k; break; }
|
||||
}
|
||||
if (mx <= mn + 50) { mn = 0; mx = 65535; }
|
||||
EnterCriticalSection(&g_lock);
|
||||
render(g_frame, mn, mx);
|
||||
snprintf(g_title, sizeof(g_title),
|
||||
"MAG160C thermal live - frame %u type=%u range [%u..%u]",
|
||||
c, (unsigned)hdr[12], mn, mx);
|
||||
LeaveCriticalSection(&g_lock);
|
||||
SetWindowTextA(hw, g_title);
|
||||
InvalidateRect(hw, NULL, FALSE);
|
||||
UpdateWindow(hw);
|
||||
}
|
||||
}
|
||||
done:;
|
||||
g_running = 0;
|
||||
|
||||
g_running = 0;
|
||||
Sleep(300);
|
||||
libusb_clear_halt(g_h, 0x03);
|
||||
libusb_clear_halt(g_h, 0x82);
|
||||
sendcmd(g_h, 0x6bb6b674, 0, 4);
|
||||
libusb_close(g_h);
|
||||
libusb_exit(ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* ThermalSDK harness v2: capture official IR frames, save several for
|
||||
* analysis. The callback delivers w x h x 3 RGB. */
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef void (CALLBACK *INewDistance)(float);
|
||||
typedef void (CALLBACK *IDistanceError)();
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef void (CALLBACK *fn_SetNewDistanceDelegate_t)(INewDistance);
|
||||
typedef void (CALLBACK *fn_SetDistanceError_t)(IDistanceError);
|
||||
typedef void (CALLBACK *fn_ReadTemperatureAtPoint_t)(int, int, unsigned char *);
|
||||
typedef void (CALLBACK *fn_SetTempBoundary_t)(double, double, double);
|
||||
typedef void (CALLBACK *fn_SetUnitMode_t)(int);
|
||||
|
||||
static int g_saved;
|
||||
static FILE *g_meta;
|
||||
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
if (g_saved < 10 && buf) {
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "official_ir_%02d.rgb", g_saved);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(buf, 1, (size_t)w * h * ch, f); fclose(f); }
|
||||
fprintf(g_meta, "%d %d %d %d\n", g_saved, w, h, ch);
|
||||
fflush(g_meta);
|
||||
g_saved++;
|
||||
}
|
||||
}
|
||||
static void CALLBACK onDist(float d) { (void)d; }
|
||||
static void CALLBACK onDistErr() {}
|
||||
|
||||
int main(void) {
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE h = LoadLibraryA("ThermalSDK.dll");
|
||||
if (!h) { printf("load fail %lu\n", GetLastError()); return 1; }
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(h, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(h, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(h, "SetNewIRFrameDelegate");
|
||||
fn_SetNewDistanceDelegate_t T_Dist = (fn_SetNewDistanceDelegate_t)GetProcAddress(h, "SetNewDistanceDelegate");
|
||||
fn_SetDistanceError_t T_Err = (fn_SetDistanceError_t)GetProcAddress(h, "SetDistanceError");
|
||||
fn_ReadTemperatureAtPoint_t T_Temp = (fn_ReadTemperatureAtPoint_t)GetProcAddress(h, "ReadTemperatureAtPoint");
|
||||
fn_SetTempBoundary_t T_Bound = (fn_SetTempBoundary_t)GetProcAddress(h, "SetTempBoundary");
|
||||
fn_SetUnitMode_t T_Unit = (fn_SetUnitMode_t)GetProcAddress(h, "SetUnitMode");
|
||||
if (!T_Start || !T_Set || !T_Temp) { printf("missing exports\n"); return 2; }
|
||||
|
||||
g_meta = fopen("official_meta.txt", "w");
|
||||
T_Unit(0);
|
||||
T_Bound(30.0, 44.0, 37.0);
|
||||
T_Set(T_Bound ? onIR : onIR);
|
||||
T_Dist(onDist);
|
||||
T_Err(onDistErr);
|
||||
T_Set(onIR);
|
||||
|
||||
printf("Start...\n"); fflush(stdout);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
|
||||
/* wait and read temps at several points */
|
||||
DWORD t0 = GetTickCount();
|
||||
int npoints = 0;
|
||||
while (g_saved < 8 && GetTickCount() - t0 < 15000) {
|
||||
Sleep(200);
|
||||
if ((GetTickCount() - t0) > 3000 && npoints < 6) {
|
||||
int x = 40 + (npoints % 3) * 40, y = 40 + (npoints / 3) * 40;
|
||||
unsigned char res[64] = {0};
|
||||
T_Temp(x, y, res);
|
||||
printf("temp(%d,%d): ", x, y);
|
||||
for (int i = 0; i < 24; ++i) printf("%02x ", res[i]);
|
||||
printf("\n");
|
||||
npoints++;
|
||||
}
|
||||
}
|
||||
printf("saved %d frames\n", g_saved);
|
||||
if (g_meta) fclose(g_meta);
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/* Based on tsdk_debug (works): capture frames + read temps. */
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef void (CALLBACK *INewDistance)(float);
|
||||
typedef void (CALLBACK *IDistanceError)();
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef void (CALLBACK *fn_SetNewDistanceDelegate_t)(INewDistance);
|
||||
typedef void (CALLBACK *fn_SetDistanceError_t)(IDistanceError);
|
||||
typedef void (CALLBACK *fn_ReadTemperatureAtPoint_t)(int, int, unsigned char *);
|
||||
|
||||
static int g_saved;
|
||||
static unsigned char g_last[320 * 240 * 3];
|
||||
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
if (buf) {
|
||||
memcpy(g_last, buf, (size_t)w * h * ch);
|
||||
if (g_saved < 8) {
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "official_ir_%02d.rgb", g_saved);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(buf, 1, (size_t)w * h * ch, f); fclose(f); }
|
||||
g_saved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void CALLBACK onDist(float d) { (void)d; }
|
||||
static void CALLBACK onDistErr() {}
|
||||
|
||||
int main(void) {
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE h = LoadLibraryA("ThermalSDK.dll");
|
||||
printf("load: %p err=%lu\n", (void*)h, GetLastError());
|
||||
if (!h) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(h, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(h, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(h, "SetNewIRFrameDelegate");
|
||||
fn_SetNewDistanceDelegate_t T_Dist = (fn_SetNewDistanceDelegate_t)GetProcAddress(h, "SetNewDistanceDelegate");
|
||||
fn_SetDistanceError_t T_Err = (fn_SetDistanceError_t)GetProcAddress(h, "SetDistanceError");
|
||||
fn_ReadTemperatureAtPoint_t T_Temp = (fn_ReadTemperatureAtPoint_t)GetProcAddress(h, "ReadTemperatureAtPoint");
|
||||
printf("Start=%p Temp=%p\n", (void*)T_Start, (void*)T_Temp);
|
||||
T_Dist(onDist);
|
||||
T_Err(onDistErr);
|
||||
T_Set(onIR);
|
||||
printf("Start()...\n"); fflush(stdout);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
|
||||
DWORD t0 = GetTickCount();
|
||||
int n = 0;
|
||||
while (GetTickCount() - t0 < 10000) {
|
||||
Sleep(250);
|
||||
if (n < 5) {
|
||||
int x = 80 + (n % 2) * 40, y = 60 + (n / 2) * 30;
|
||||
unsigned char res[64] = {0};
|
||||
T_Temp(x, y, res);
|
||||
printf("temp(%d,%d): ", x, y);
|
||||
for (int i = 0; i < 24; ++i) printf("%02x ", res[i]);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
printf("saved %d frames\n", g_saved);
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/* ThermalSDK capture v3: save frames + read temperatures. */
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef void (CALLBACK *fn_ReadTemperatureAtPoint_t)(int, int, unsigned char *);
|
||||
typedef void (CALLBACK *fn_SetTempBoundary_t)(double, double, double);
|
||||
typedef void (CALLBACK *fn_SetUnitMode_t)(int);
|
||||
|
||||
static volatile int g_frames;
|
||||
static unsigned char g_last[320 * 240 * 3];
|
||||
static int g_saved;
|
||||
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
if (buf && w > 0 && h > 0 && ch == 3) {
|
||||
memcpy(g_last, buf, (size_t)w * h * ch);
|
||||
if (g_saved < 10) {
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "official_ir_%02d.rgb", g_saved);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(buf, 1, (size_t)w * h * ch, f); fclose(f); }
|
||||
g_saved++;
|
||||
}
|
||||
}
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE h = LoadLibraryA("ThermalSDK.dll");
|
||||
printf("load: %p err=%lu\n", (void*)h, GetLastError());
|
||||
if (!h) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(h, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(h, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(h, "SetNewIRFrameDelegate");
|
||||
fn_ReadTemperatureAtPoint_t T_Temp = (fn_ReadTemperatureAtPoint_t)GetProcAddress(h, "ReadTemperatureAtPoint");
|
||||
fn_SetTempBoundary_t T_Bound = (fn_SetTempBoundary_t)GetProcAddress(h, "SetTempBoundary");
|
||||
fn_SetUnitMode_t T_Unit = (fn_SetUnitMode_t)GetProcAddress(h, "SetUnitMode");
|
||||
printf("exports: Start=%p Temp=%p\n", (void*)T_Start, (void*)T_Temp);
|
||||
if (!T_Start || !T_Set || !T_Temp) { printf("missing\n"); return 2; }
|
||||
|
||||
T_Unit(0);
|
||||
T_Bound(30.0, 44.0, 37.0);
|
||||
T_Set(onIR);
|
||||
printf("Start()...\n"); fflush(stdout);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
|
||||
DWORD t0 = GetTickCount();
|
||||
while (g_frames < 12 && GetTickCount() - t0 < 12000) Sleep(100);
|
||||
|
||||
int pts[][2] = {{80, 60}, {40, 40}, {120, 80}, {80, 30}, {30, 90}, {130, 100}};
|
||||
for (int n = 0; n < 6; ++n) {
|
||||
unsigned char res[64] = {0};
|
||||
T_Temp(pts[n][0], pts[n][1], res);
|
||||
printf("temp(%d,%d): ", pts[n][0], pts[n][1]);
|
||||
for (int i = 0; i < 32; ++i) printf("%02x ", res[i]);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
Sleep(100);
|
||||
}
|
||||
printf("frames: %d saved: %d\n", g_frames, g_saved);
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* Official CoreSDKLib harness v2: use ThermalSDK high-level (which works),
|
||||
* but ALSO read raw temperature via CoreSDKLib MAG_GetTemperatureData_Raw
|
||||
* using the channel that ThermalSDK created. To find the channel, we
|
||||
* brute-force 0..4 after ThermalSDK Start().
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef int (CALLBACK *MAG_GetTemperatureDataRaw_t)(int, int *, int, int);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdataRGB24_t)(int, unsigned char *, int, int);
|
||||
typedef int (CALLBACK *MAG_GetFilteredRaw_t)(int, unsigned short *, int);
|
||||
typedef int (CALLBACK *MAG_GetFrameStatisticalData_t)(int, void *);
|
||||
|
||||
static volatile int g_frames;
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
(void)buf; (void)w; (void)h; (void)ch;
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const char *dir = "C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app";
|
||||
SetDllDirectoryA(dir);
|
||||
HMODULE t = LoadLibraryA("ThermalSDK.dll");
|
||||
HMODULE c = LoadLibraryA("CoreSDKLib.dll");
|
||||
printf("ThermalSDK=%p CoreSDKLib=%p\n", (void*)t, (void*)c);
|
||||
if (!t || !c) return 1;
|
||||
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(t, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(t, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(t, "SetNewIRFrameDelegate");
|
||||
MAG_GetTemperatureDataRaw_t G_TempRaw = (MAG_GetTemperatureDataRaw_t)GetProcAddress(c, "MAG_GetTemperatureData_Raw");
|
||||
MAG_GetOutputBMPdataRGB24_t G_BMP = (MAG_GetOutputBMPdataRGB24_t)GetProcAddress(c, "MAG_GetOutputBMPdataRGB24");
|
||||
MAG_GetFilteredRaw_t G_FRaw = (MAG_GetFilteredRaw_t)GetProcAddress(c, "MAG_GetFilteredRaw");
|
||||
printf("Start=%p TempRaw=%p BMP24=%p FilteredRaw=%p\n",
|
||||
(void*)T_Start, (void*)G_TempRaw, (void*)G_BMP, (void*)G_FRaw);
|
||||
|
||||
T_Set(onIR);
|
||||
printf("Start()...\n"); fflush(stdout);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
Sleep(3000);
|
||||
printf("frames=%d\n", g_frames);
|
||||
|
||||
/* try channels 0..4 for TempRaw */
|
||||
static int tempdata[20000];
|
||||
static unsigned short fraw[20000];
|
||||
for (int chan = 0; chan < 5; ++chan) {
|
||||
memset(tempdata, 0, sizeof(tempdata));
|
||||
int rc = G_TempRaw(chan, tempdata, 1, 1);
|
||||
printf("chan %d: TempRaw rc=%d t[0]=%d t[500]=%d t[9600]=%d\n",
|
||||
chan, rc, tempdata[0], tempdata[500], tempdata[9600]);
|
||||
if (rc && tempdata[0] != 0) {
|
||||
/* save */
|
||||
FILE *f = fopen("official_tempraw.bin", "wb");
|
||||
if (f) { fwrite(tempdata, 4, 19200, f); fclose(f); }
|
||||
printf(" saved official_tempraw.bin\n");
|
||||
break;
|
||||
}
|
||||
memset(fraw, 0, sizeof(fraw));
|
||||
int rc2 = G_FRaw(chan, fraw, 19200);
|
||||
printf(" FilteredRaw rc=%d f[0]=%d f[500]=%d\n", rc2, fraw[0], fraw[500]);
|
||||
if (rc2 && fraw[0] != 0) {
|
||||
FILE *f = fopen("official_fraw.bin", "wb");
|
||||
if (f) { fwrite(fraw, 2, 19200, f); fclose(f); }
|
||||
printf(" saved official_fraw.bin\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Debug ThermalSDK harness: verbose loading, check every step. */
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef int (CALLBACK *fn_GetSDKVersion_t)(void);
|
||||
|
||||
static volatile int g_frames;
|
||||
static unsigned char g_last[320 * 240 * 3];
|
||||
static int g_saved;
|
||||
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
if (buf) {
|
||||
memcpy(g_last, buf, (size_t)w * h * ch);
|
||||
if (g_saved < 8) {
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "official_ir_%02d.rgb", g_saved);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(buf, 1, (size_t)w * h * ch, f); fclose(f); }
|
||||
g_saved++;
|
||||
}
|
||||
if (g_frames < 5) {
|
||||
printf(" IR frame: w=%d h=%d ch=%d first RGB=%d,%d,%d\n",
|
||||
w, h, ch, buf[0], buf[1], buf[2]);
|
||||
}
|
||||
}
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const char *dir = "C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app";
|
||||
printf("SetDllDirectory: %d\n", SetDllDirectoryA(dir));
|
||||
HMODULE h = LoadLibraryA("ThermalSDK.dll");
|
||||
printf("LoadLibrary ThermalSDK: %p err=%lu\n", (void*)h, GetLastError());
|
||||
if (!h) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(h, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(h, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(h, "SetNewIRFrameDelegate");
|
||||
fn_GetSDKVersion_t T_Ver = (fn_GetSDKVersion_t)GetProcAddress(h, "GetSDKVersion");
|
||||
printf("exports: Start=%p Set=%p Ver=%p\n", (void*)T_Start, (void*)T_Set, (void*)T_Ver);
|
||||
|
||||
/* dependencies */
|
||||
HMODULE core = LoadLibraryA("CoreSDKLib.dll");
|
||||
printf("CoreSDKLib: %p err=%lu\n", (void*)core, GetLastError());
|
||||
HMODULE cam = LoadLibraryA("CameraSDK.dll");
|
||||
printf("CameraSDK: %p err=%lu\n", (void*)cam, GetLastError());
|
||||
|
||||
if (T_Set) T_Set(onIR);
|
||||
printf("delegate set, calling Start()...\n");
|
||||
fflush(stdout);
|
||||
BOOL ok = T_Start ? T_Start() : FALSE;
|
||||
printf("Start returned: %d\n", ok);
|
||||
fflush(stdout);
|
||||
Sleep(8000);
|
||||
printf("frames received: %d\n", g_frames);
|
||||
if (T_Stop) T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/* Capture official GRAYSCALE output via ThermalSDK + CoreSDKLib.
|
||||
* ThermalSDK creates the channel; we find it and read the grayscale
|
||||
* buffer through MAG_GetOutputBMPdata.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdata_t)(int, int *, unsigned char **);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdataRGB24_t)(int, unsigned char *, int, int);
|
||||
typedef int (CALLBACK *MAG_GetOutputColorBardata_t)(int, void *, int);
|
||||
|
||||
static volatile int g_frames;
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
(void)buf; (void)w; (void)h; (void)ch;
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE t = LoadLibraryA("ThermalSDK.dll");
|
||||
HMODULE c = LoadLibraryA("CoreSDKLib.dll");
|
||||
printf("T=%p C=%p\n", (void*)t, (void*)c);
|
||||
if (!t || !c) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(t, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(t, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(t, "SetNewIRFrameDelegate");
|
||||
MAG_GetOutputBMPdata_t G_Gray = (MAG_GetOutputBMPdata_t)GetProcAddress(c, "MAG_GetOutputBMPdata");
|
||||
MAG_GetOutputBMPdataRGB24_t G_RGB = (MAG_GetOutputBMPdataRGB24_t)GetProcAddress(c, "MAG_GetOutputBMPdataRGB24");
|
||||
MAG_GetOutputColorBardata_t G_Bar = (MAG_GetOutputColorBardata_t)GetProcAddress(c, "MAG_GetOutputColorBardata");
|
||||
printf("Gray=%p RGB=%p Bar=%p\n", (void*)G_Gray, (void*)G_RGB, (void*)G_Bar);
|
||||
|
||||
T_Set(onIR);
|
||||
printf("Start()...\n"); fflush(stdout);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
Sleep(3000);
|
||||
printf("frames=%d\n", g_frames);
|
||||
|
||||
/* try channels 0..4 for the grayscale buffer */
|
||||
for (int chan = 0; chan < 5; ++chan) {
|
||||
int w = 0;
|
||||
unsigned char *buf = NULL;
|
||||
int rc = G_Gray(chan, &w, &buf);
|
||||
printf("chan %d: GetOutputBMPdata rc=%d w=%d buf=%p\n", chan, rc, w, (void*)buf);
|
||||
if (rc && buf) {
|
||||
/* grayscale is w*h bytes; save it */
|
||||
int n = w > 0 && w <= 40000 ? w : 19200;
|
||||
FILE *f = fopen("official_gray.bin", "wb");
|
||||
if (f) { fwrite(buf, 1, n, f); fclose(f); }
|
||||
printf(" saved official_gray.bin (%d bytes)\n", n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Capture grayscale AND RGB24 at the same moment via CoreSDKLib, plus
|
||||
* verify the palette mapping. */
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdata_t)(int, int *, unsigned char **);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdataRGB24_t)(int, unsigned char *, int, int);
|
||||
|
||||
static volatile int g_frames;
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
(void)buf; (void)w; (void)h; (void)ch;
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE t = LoadLibraryA("ThermalSDK.dll");
|
||||
HMODULE c = LoadLibraryA("CoreSDKLib.dll");
|
||||
if (!t || !c) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(t, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(t, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(t, "SetNewIRFrameDelegate");
|
||||
MAG_GetOutputBMPdata_t G_Gray = (MAG_GetOutputBMPdata_t)GetProcAddress(c, "MAG_GetOutputBMPdata");
|
||||
MAG_GetOutputBMPdataRGB24_t G_RGB = (MAG_GetOutputBMPdataRGB24_t)GetProcAddress(c, "MAG_GetOutputBMPdataRGB24");
|
||||
|
||||
T_Set(onIR);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
Sleep(3000);
|
||||
|
||||
for (int chan = 0; chan < 5; ++chan) {
|
||||
int w = 0;
|
||||
unsigned char *graybuf = NULL;
|
||||
int rc = G_Gray(chan, &w, &graybuf);
|
||||
printf("chan %d gray rc=%d buf=%p\n", chan, rc, (void*)graybuf);
|
||||
if (rc && graybuf) {
|
||||
/* read true w/h from dev+0xadc/0xae0 = graybuf - 0xaf0 + 0xadc */
|
||||
unsigned char *dev = graybuf - 0xaf0;
|
||||
int w2 = *(int *)(dev + 0xadc);
|
||||
int h2 = *(int *)(dev + 0xae0);
|
||||
printf(" dev w=%d h=%d\n", w2, h2);
|
||||
if (w2 > 0 && w2 < 1000 && h2 > 0 && h2 < 1000) {
|
||||
unsigned char *rgb = malloc((size_t)w2 * h2 * 3);
|
||||
int rc2 = G_RGB(chan, rgb, w2 * h2 * 3, 1);
|
||||
printf(" RGB24 rc=%d\n", rc2);
|
||||
if (rc2) {
|
||||
FILE *f = fopen("official_gray3.bin", "wb");
|
||||
if (f) { fwrite(graybuf, 1, (size_t)w2 * h2, f); fclose(f); }
|
||||
f = fopen("official_rgb3.bin", "wb");
|
||||
if (f) { fwrite(rgb, 1, (size_t)w2 * h2 * 3, f); fclose(f); }
|
||||
printf(" saved gray=%dx%d rgb\n", w2, h2);
|
||||
}
|
||||
free(rgb);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
/* tsdk_pair2: same-frame capture of official pipeline outputs.
|
||||
* Per frame (synced to ThermalSDK callback):
|
||||
* - gray : 8-bit display gray buffer dev+0x220 (160x120)
|
||||
* - rgb : RGB24 render 160x120 (MAG_GetOutputBMPdataRGB24, order=1)
|
||||
* - raw : u16 counts frame dev+0x270 (post-NUC processed frame)
|
||||
* - palette: 256x4 BGRx dev+0xb18
|
||||
* - temps : u32 per-pixel temps MAG_GetTemperatureData_Raw
|
||||
* - cbRGB : ThermalSDK 320x240 callback frame
|
||||
* - points : ReadTemperatureAtPoint at several points
|
||||
* Also dumps device-struct bytes around the display header (dev+0xaf0..) and
|
||||
* the window fields found in the struct, for calibration.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef void (CALLBACK *fn_SetTempBoundary_t)(double, double, double);
|
||||
typedef void (CALLBACK *fn_SetUnitMode_t)(int);
|
||||
typedef void (CALLBACK *fn_ReadTemperatureAtPoint_t)(int, int, unsigned char *);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdata_t)(int, unsigned char **, void **);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdataRGB24_t)(int, unsigned char *, int, int);
|
||||
typedef int (CALLBACK *MAG_GetTemperatureData_Raw_t)(int, unsigned int *, int, int);
|
||||
typedef int (CALLBACK *MAG_GetOutputColorBardata_t)(int, void **, void **);
|
||||
typedef int (CALLBACK *MAG_TriggerFFC_t)(int, int);
|
||||
|
||||
static volatile int g_frames;
|
||||
static unsigned char g_cb[320 * 240 * 3];
|
||||
static int g_cbw, g_cbh;
|
||||
static const char *g_dir = ".";
|
||||
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
if (buf && w > 0 && h > 0 && ch == 3) {
|
||||
memcpy(g_cb, buf, (size_t)w * h * ch);
|
||||
g_cbw = w; g_cbh = h;
|
||||
}
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
static void save(const char *name, const void *buf, size_t n) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s\\%s", g_dir, name);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) { fwrite(buf, 1, n, f); fclose(f); }
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int npairs = 24;
|
||||
int start_delay = 6000;
|
||||
double tb[3] = {30.0, 44.0, 37.0};
|
||||
if (argc > 1) g_dir = argv[1];
|
||||
if (argc > 2) npairs = atoi(argv[2]);
|
||||
if (argc > 3) start_delay = atoi(argv[3]);
|
||||
if (argc > 4) tb[0] = atof(argv[4]);
|
||||
if (argc > 5) tb[1] = atof(argv[5]);
|
||||
if (argc > 6) tb[2] = atof(argv[6]);
|
||||
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE t = LoadLibraryA("ThermalSDK.dll");
|
||||
HMODULE c = LoadLibraryA("CoreSDKLib.dll");
|
||||
printf("T=%p C=%p\n", (void*)t, (void*)c);
|
||||
if (!t || !c) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(t, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(t, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(t, "SetNewIRFrameDelegate");
|
||||
fn_SetTempBoundary_t T_Bound = (fn_SetTempBoundary_t)GetProcAddress(t, "SetTempBoundary");
|
||||
fn_SetUnitMode_t T_Unit = (fn_SetUnitMode_t)GetProcAddress(t, "SetUnitMode");
|
||||
fn_ReadTemperatureAtPoint_t T_Temp = (fn_ReadTemperatureAtPoint_t)GetProcAddress(t, "ReadTemperatureAtPoint");
|
||||
MAG_GetOutputBMPdata_t G_Gray = (MAG_GetOutputBMPdata_t)GetProcAddress(c, "MAG_GetOutputBMPdata");
|
||||
MAG_GetOutputBMPdataRGB24_t G_RGB = (MAG_GetOutputBMPdataRGB24_t)GetProcAddress(c, "MAG_GetOutputBMPdataRGB24");
|
||||
MAG_GetTemperatureData_Raw_t G_TempRaw = (MAG_GetTemperatureData_Raw_t)GetProcAddress(c, "MAG_GetTemperatureData_Raw");
|
||||
MAG_GetOutputColorBardata_t G_Bar = (MAG_GetOutputColorBardata_t)GetProcAddress(c, "MAG_GetOutputColorBardata");
|
||||
printf("Start=%p TempPoint=%p Gray=%p RGB=%p TempRaw=%p Bar=%p\n",
|
||||
(void*)T_Start, (void*)T_Temp, (void*)G_Gray, (void*)G_RGB,
|
||||
(void*)G_TempRaw, (void*)G_Bar);
|
||||
if (!T_Start || !T_Set || !G_Gray || !G_RGB) { printf("missing export\n"); return 2; }
|
||||
|
||||
if (T_Unit) T_Unit(0);
|
||||
if (T_Bound) T_Bound(tb[0], tb[1], tb[2]);
|
||||
printf("SetTempBoundary(%.1f, %.1f, %.1f)\n", tb[0], tb[1], tb[2]); fflush(stdout);
|
||||
T_Set(onIR);
|
||||
printf("Start()...\n"); fflush(stdout);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
|
||||
printf("warming %d ms...\n", start_delay); fflush(stdout);
|
||||
Sleep(start_delay);
|
||||
|
||||
/* locate channel device */
|
||||
unsigned char *gray = NULL;
|
||||
void *hdr = NULL;
|
||||
int chan = -1;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int rc = G_Gray(i, &gray, &hdr);
|
||||
printf("chan %d: rc=%d gray=%p hdr=%p\n", i, rc, (void*)gray, (void*)hdr);
|
||||
if (rc && gray && hdr) { chan = i; break; }
|
||||
}
|
||||
if (chan < 0) { printf("no channel\n"); T_Stop(); return 3; }
|
||||
|
||||
unsigned char *dev = (unsigned char *)hdr - 0xaf0;
|
||||
int w = *(int *)(dev + 0xaf4);
|
||||
int h = *(int *)(dev + 0xaf8);
|
||||
printf("dev=%p w=%d h=%d graybuf=%p rawp=%p\n", (void*)dev, w, h,
|
||||
(void*)gray, (void*)*(void **)(dev + 0x270));
|
||||
/* probe pointer fields near 0x250..0x2b0 (buffer pointers) */
|
||||
for (int off = 0x240; off <= 0x2b0; off += 8) {
|
||||
void *p = *(void **)(dev + off);
|
||||
printf(" dev+0x%03x = %p\n", off, p);
|
||||
}
|
||||
if (w <= 0 || w > 1000 || h <= 0 || h > 1000) { w = 160; h = 120; }
|
||||
int n = w * h;
|
||||
int nw = w / 2, nh = h / 2; /* sensor frame 160x120 if 2x mode */
|
||||
int nn = nw * nh;
|
||||
printf("capturing %d pairs (disp %dx%d, sensor %dx%d)...\n", npairs, w, h, nw, nh); fflush(stdout);
|
||||
|
||||
unsigned char *rgb = malloc((size_t)n * 3);
|
||||
unsigned short *raw = malloc((size_t)nn * 2);
|
||||
unsigned char *pal = malloc(256 * 4);
|
||||
unsigned int *t32 = malloc((size_t)n * 4);
|
||||
unsigned char *g2 = malloc((size_t)n);
|
||||
|
||||
int pts[][2] = {{80, 60}, {40, 40}, {120, 80}, {80, 30}, {30, 90}, {130, 100},
|
||||
{10, 10}, {150, 110}, {80, 10}, {5, 115}, {155, 5}, {20, 60}};
|
||||
int np = sizeof(pts) / sizeof(pts[0]);
|
||||
|
||||
MAG_TriggerFFC_t G_FFC = (MAG_TriggerFFC_t)GetProcAddress(c, "MAG_TriggerFFC");
|
||||
printf("TriggerFFC=%p\n", (void*)G_FFC); fflush(stdout);
|
||||
|
||||
int prev = g_frames;
|
||||
for (int k = 0; k < npairs; ++k) {
|
||||
/* optional FFC triggers at specific pairs */
|
||||
if (G_FFC && k == npairs / 3) {
|
||||
printf(">>> TriggerFFC(1)\n"); fflush(stdout);
|
||||
G_FFC(chan, 1);
|
||||
Sleep(1500);
|
||||
prev = g_frames;
|
||||
}
|
||||
if (G_FFC && k == 2 * npairs / 3) {
|
||||
printf(">>> TriggerFFC(0)\n"); fflush(stdout);
|
||||
G_FFC(chan, 0);
|
||||
Sleep(1500);
|
||||
prev = g_frames;
|
||||
}
|
||||
/* wait for a new callback frame, then a small settle */
|
||||
int t0 = GetTickCount();
|
||||
while (g_frames == prev && GetTickCount() - t0 < 3000) Sleep(5);
|
||||
prev = g_frames;
|
||||
Sleep(2);
|
||||
|
||||
/* same-frame snapshot */
|
||||
memcpy(g2, gray, (size_t)n); /* dev+0x220 gray (320x240) */
|
||||
int rcr = G_RGB(chan, rgb, n * 3, 1); /* RGB24 320x240 */
|
||||
unsigned short *rawp = *(unsigned short **)(dev + 0x270);
|
||||
memcpy(raw, rawp, (size_t)nn * 2); /* u16 sensor frame 160x120 */
|
||||
memcpy(pal, dev + 0xb18, 256 * 4); /* palette */
|
||||
int rct = G_TempRaw ? G_TempRaw(chan, t32, nn * 4, 1) : 0;
|
||||
|
||||
/* frame counter + dev struct fields */
|
||||
unsigned int fcnt = *(unsigned int *)(dev + 0x8);
|
||||
unsigned int win_hi = *(unsigned int *)(dev + 0x4202c);
|
||||
unsigned int win_lo = *(unsigned int *)(dev + 0x42030);
|
||||
unsigned int win_span = win_hi > win_lo ? win_hi - win_lo : 0;
|
||||
unsigned int pcount = *(unsigned int *)(dev + 0x47938);
|
||||
unsigned int comp_off = *(unsigned int *)(dev + 0x47948);
|
||||
unsigned int comp_shift = *(unsigned int *)(dev + 0x4794c);
|
||||
unsigned int gain = *(unsigned int *)0x18006ea60; /* global */
|
||||
unsigned int smooth_mode = *(unsigned int *)(dev + 0x41fdc);
|
||||
unsigned int has_ref = *(unsigned int *)(dev + 0x41f0c);
|
||||
unsigned short *refp = *(unsigned short **)(dev + 0x41ef0);
|
||||
unsigned int m47944 = *(unsigned int *)(dev + 0x47944);
|
||||
unsigned int m47950 = *(unsigned int *)(dev + 0x47950);
|
||||
unsigned int m47954 = *(unsigned int *)(dev + 0x47954);
|
||||
unsigned int t_base = *(unsigned int *)0x1800990c0;
|
||||
unsigned int t_now = *(unsigned int *)0x1800990c4;
|
||||
unsigned int m41848 = *(unsigned int *)(dev + 0x41848);
|
||||
unsigned int m41858 = *(unsigned int *)(dev + 0x41858);
|
||||
unsigned int m41340 = *(unsigned int *)(dev + 0x41340);
|
||||
unsigned int m41348 = *(unsigned int *)(dev + 0x41348);
|
||||
unsigned int m41fe0 = *(unsigned int *)(dev + 0x41fe0);
|
||||
unsigned int m41f3c = *(unsigned int *)(dev + 0x41f3c);
|
||||
unsigned int m41840 = *(unsigned int *)(dev + 0x41840);
|
||||
unsigned int m41554 = *(unsigned int *)(dev + 0x41554);
|
||||
unsigned int m41558 = *(unsigned int *)(dev + 0x41558);
|
||||
unsigned int m4155c = *(unsigned int *)(dev + 0x4155c);
|
||||
unsigned int m41560 = *(unsigned int *)(dev + 0x41560);
|
||||
unsigned int m41564_0 = *(unsigned int *)(dev + 0x41564);
|
||||
unsigned int m41568_0 = *(unsigned int *)(dev + 0x41568);
|
||||
unsigned int m54 = *(unsigned int *)(dev + 0x54);
|
||||
|
||||
char base[64];
|
||||
snprintf(base, sizeof(base), "pair_%03d", k);
|
||||
{
|
||||
char nm[128];
|
||||
snprintf(nm, sizeof(nm), "%s.win", base);
|
||||
FILE *f = fopen(nm, "wb");
|
||||
if (f) {
|
||||
fwrite(&win_hi, 4, 1, f); fwrite(&win_lo, 4, 1, f);
|
||||
fwrite(&pcount, 4, 1, f);
|
||||
fwrite(&comp_off, 4, 1, f); fwrite(&comp_shift, 4, 1, f);
|
||||
fwrite(&gain, 4, 1, f); fwrite(&smooth_mode, 4, 1, f);
|
||||
fwrite(&has_ref, 4, 1, f); fclose(f);
|
||||
}
|
||||
/* extra fields for NUC-path analysis */
|
||||
snprintf(nm, sizeof(nm), "%s.win2", base);
|
||||
{
|
||||
FILE *f2 = fopen(nm, "wb");
|
||||
if (f2) {
|
||||
unsigned int v[14] = {m47944, m47950, m47954, t_base, t_now,
|
||||
m41848, m41858, m41340, m41348, m41fe0,
|
||||
comp_off, comp_shift, gain, smooth_mode};
|
||||
fwrite(v, 4, 14, f2); fclose(f2);
|
||||
}
|
||||
}
|
||||
/* NUC lookup tables: thresholds (0x41858) + gain/offset (0x41870) heads */
|
||||
{
|
||||
unsigned short th[16], g0[16], g1[16];
|
||||
memcpy(th, dev + 0x41858, 32);
|
||||
memcpy(g0, dev + 0x41870, 32);
|
||||
memcpy(g1, dev + 0x41870 + 2 * 19200 * 2, 32);
|
||||
snprintf(nm, sizeof(nm), "%s.nuctab", base);
|
||||
{
|
||||
FILE *f3 = fopen(nm, "wb");
|
||||
if (f3) { fwrite(th, 2, 16, f3); fwrite(g0, 2, 16, f3); fwrite(g1, 2, 16, f3); fclose(f3); }
|
||||
}
|
||||
printf(" nuctab th[0:4]=%d,%d,%d,%d g0[0:4]=%d,%d,%d,%d g1[0:4]=%d,%d,%d,%d\n",
|
||||
th[0], th[1], th[2], th[3], g0[0], g0[1], g0[2], g0[3], g1[0], g1[1], g1[2], g1[3]);
|
||||
}
|
||||
/* NUC lookup tables: pointers at 0x41858 (thresholds) and 0x41870 (gain/off) */
|
||||
{
|
||||
unsigned int nsegs = *(unsigned int *)(dev + 0x41554);
|
||||
void *thrp = *(void **)(dev + 0x41858);
|
||||
void *gainp = *(void **)(dev + 0x41870);
|
||||
printf(" nsegs=%u thrp=%p gainp=%p\n", nsegs, thrp, gainp);
|
||||
if (thrp) {
|
||||
snprintf(nm, sizeof(nm), "%s.thr", base);
|
||||
save(nm, thrp, 19200 * 2 * nsegs);
|
||||
}
|
||||
if (gainp && nsegs <= 64) {
|
||||
snprintf(nm, sizeof(nm), "%s.gain", base);
|
||||
save(nm, gainp, 19200 * 4 * nsegs);
|
||||
}
|
||||
}
|
||||
/* LUT1024 at dev+0x4284c */
|
||||
{
|
||||
unsigned int nsegs = *(unsigned int *)(dev + 0x41554);
|
||||
unsigned int segsz = nsegs * 19200u * 4u;
|
||||
printf(" nsegs=%u table_bytes=%u\n", nsegs, segsz);
|
||||
snprintf(nm, sizeof(nm), "%s.thr_inline", base);
|
||||
save(nm, dev + 0x41848, (nsegs * 2) > 4096 ? 4096 : (nsegs * 2));
|
||||
if (segsz <= 24u * 1024u * 1024u && nsegs <= 512) {
|
||||
snprintf(nm, sizeof(nm), "%s.gain_inline", base);
|
||||
save(nm, dev + 0x41870, segsz);
|
||||
}
|
||||
}
|
||||
/* LUT1024 at dev+0x4284c */
|
||||
snprintf(nm, sizeof(nm), "%s.lut", base);
|
||||
save(nm, dev + 0x4284c, 1024);
|
||||
/* histogram 256 bins at dev+0x4204c */
|
||||
snprintf(nm, sizeof(nm), "%s.hist", base);
|
||||
save(nm, dev + 0x4204c, 256 * 4);
|
||||
/* reference frame if present */
|
||||
if (refp) {
|
||||
snprintf(nm, sizeof(nm), "%s.ref", base);
|
||||
save(nm, refp, (size_t)nn * 2);
|
||||
}
|
||||
/* NUC input candidates: buffers pointed by 0x41848..0x41878 */
|
||||
{
|
||||
void *p[8];
|
||||
p[0] = *(void **)(dev + 0x41840);
|
||||
p[1] = *(void **)(dev + 0x41848);
|
||||
p[2] = *(void **)(dev + 0x41850);
|
||||
p[3] = *(void **)(dev + 0x41858);
|
||||
p[4] = *(void **)(dev + 0x41860);
|
||||
p[5] = *(void **)(dev + 0x41868);
|
||||
p[6] = *(void **)(dev + 0x41870);
|
||||
p[7] = *(void **)(dev + 0x41878);
|
||||
printf(" ptrs418x: ");
|
||||
for (int bi = 0; bi < 8; ++bi) printf("%d=%p ", bi, p[bi]);
|
||||
printf("\n");
|
||||
for (int bi = 0; bi < 8; ++bi) {
|
||||
if (!p[bi]) continue;
|
||||
snprintf(nm, sizeof(nm), "%s.b%02d", base, bi);
|
||||
save(nm, p[bi], (size_t)nn * 2);
|
||||
}
|
||||
}
|
||||
/* smoother output buffer (0x41f10+0x10) and 0x41f20 target */
|
||||
{
|
||||
void *smo = *(void **)(dev + 0x41f10);
|
||||
if (smo) {
|
||||
void *sout = *(void **)((unsigned char *)smo + 0x10);
|
||||
if (sout) {
|
||||
snprintf(nm, sizeof(nm), "%s.smo", base);
|
||||
save(nm, sout, (size_t)nn * 2);
|
||||
}
|
||||
void *acc = *(void **)((unsigned char *)smo + 0x18);
|
||||
if (acc) {
|
||||
snprintf(nm, sizeof(nm), "%s.smoacc", base);
|
||||
save(nm, acc, (size_t)nn * 4); /* u32 accumulator */
|
||||
}
|
||||
}
|
||||
void *f20 = *(void **)(dev + 0x41f20);
|
||||
if (f20) {
|
||||
snprintf(nm, sizeof(nm), "%s.f20", base);
|
||||
save(nm, f20, (size_t)nn * 2);
|
||||
}
|
||||
}
|
||||
/* 0x41ee0 smoother accumulator + frame counters */
|
||||
{
|
||||
void *e0 = *(void **)(dev + 0x41ee0);
|
||||
void *e0acc = *(void **)((unsigned char *)dev + 0x41ef8);
|
||||
if (e0acc) {
|
||||
snprintf(nm, sizeof(nm), "%s.e0acc", base);
|
||||
save(nm, e0acc, (size_t)nn * 4);
|
||||
}
|
||||
void *e0buf = *(void **)((unsigned char *)dev + 0x41ee8);
|
||||
if (e0buf) {
|
||||
snprintf(nm, sizeof(nm), "%s.e0buf", base);
|
||||
save(nm, e0buf, (size_t)nn * 2);
|
||||
}
|
||||
unsigned int cnt1 = *(unsigned int *)(dev + 0x41ee0 + 0x20);
|
||||
unsigned int cnt2 = *(unsigned int *)(dev + 0x41f10 + 0x20);
|
||||
unsigned int gfc = *(unsigned int *)(0x180073bec);
|
||||
unsigned int ffcst = *(unsigned int *)(0x180073bf4);
|
||||
snprintf(nm, sizeof(nm), "%s.cnt", base);
|
||||
{
|
||||
unsigned int v[4] = {cnt1, cnt2, gfc, ffcst};
|
||||
save(nm, v, 16);
|
||||
}
|
||||
printf(" cnt: e0=%u f10=%u globframe=%u ffcstate=%u\n", cnt1, cnt2, gfc, ffcst);
|
||||
}
|
||||
/* dump smoother object internals + mode fields */
|
||||
{
|
||||
unsigned char s1[0x40], s2[0x40];
|
||||
unsigned int modes[6];
|
||||
memcpy(s1, dev + 0x41ee0, 0x40);
|
||||
memcpy(s2, dev + 0x41f10, 0x40);
|
||||
modes[0] = *(unsigned int *)(dev + 0x41fd8);
|
||||
modes[1] = *(unsigned int *)(dev + 0x41fdc);
|
||||
modes[2] = *(unsigned int *)(dev + 0x41fe0);
|
||||
modes[3] = *(unsigned int *)(dev + 0x41fe4);
|
||||
modes[4] = *(unsigned int *)(dev + 0x41fdc);
|
||||
modes[5] = *(unsigned int *)(dev + 0x11c);
|
||||
snprintf(nm, sizeof(nm), "%s.smo1", base);
|
||||
save(nm, s1, 0x40);
|
||||
snprintf(nm, sizeof(nm), "%s.smo2", base);
|
||||
save(nm, s2, 0x40);
|
||||
snprintf(nm, sizeof(nm), "%s.modes", base);
|
||||
save(nm, modes, 24);
|
||||
printf(" modes: fd8=%u fdc=%u fe0=%u fe4=%u 11c=%u refp=%p f20p=%p\n",
|
||||
modes[0], modes[1], modes[2], modes[3], modes[5],
|
||||
(void*)refp, (void*)*(void **)(dev + 0x41f20));
|
||||
}
|
||||
/* stream parameters: transport object = channel_table[chan].slot1 */
|
||||
{
|
||||
unsigned char *tobj = *(unsigned char **)(0x180073750 + (size_t)chan * 0x4a8 + 8);
|
||||
if (tobj) {
|
||||
unsigned int v[6];
|
||||
v[0] = *(unsigned int *)(tobj + 0x2d78);
|
||||
v[1] = *(unsigned int *)(tobj + 0x2d88);
|
||||
v[2] = *(unsigned int *)(tobj + 0x2d8c);
|
||||
v[3] = *(unsigned int *)(tobj + 0x2d90);
|
||||
v[4] = *(unsigned int *)(tobj + 0x2d94);
|
||||
v[5] = *(unsigned int *)(tobj + 0x2d98);
|
||||
snprintf(nm, sizeof(nm), "%s.stream", base);
|
||||
save(nm, v, 24);
|
||||
printf(" stream: 2d78=%u 2d88=%u 2d8c=%u 2d90=%u 2d94=%u 2d98=%u\n",
|
||||
v[0], v[1], v[2], v[3], v[4], v[5]);
|
||||
}
|
||||
}
|
||||
}
|
||||
printf(" window hi=%u lo=%u span=%u pixels=%u comp_off=%u shift=%u gain=%u sm=%u ref=%u\n",
|
||||
win_hi, win_lo, win_span, pcount, comp_off, comp_shift, gain, smooth_mode, has_ref);
|
||||
printf(" p47944=%u p47950=%u p47954=%u t_base=%u t_now=%u 41848=%u 41858=%u 41340=%u 41348=%u 41fe0=%u\n",
|
||||
m47944, m47950, m47954, t_base, t_now, m41848, m41858, m41340, m41348, m41fe0);
|
||||
printf(" f3c=%u 41840=%u 41554=%u 41558=%u 4155c=%u 41560=%u b64_0=%u b68_0=%u dev54=%u\n",
|
||||
m41f3c, m41840, m41554, m41558, m4155c, m41560, m41564_0, m41568_0, m54);
|
||||
fflush(stdout);
|
||||
{
|
||||
char nm[128];
|
||||
snprintf(nm, sizeof(nm), "%s.gray", base); save(nm, g2, (size_t)n);
|
||||
snprintf(nm, sizeof(nm), "%s.rgb", base); save(nm, rgb, (size_t)n * 3);
|
||||
snprintf(nm, sizeof(nm), "%s.raw", base); save(nm, raw, (size_t)nn * 2);
|
||||
snprintf(nm, sizeof(nm), "%s.pal", base); save(nm, pal, 256 * 4);
|
||||
snprintf(nm, sizeof(nm), "%s.cbrgb", base); save(nm, g_cb, (size_t)g_cbw * g_cbh * 3);
|
||||
if (rct) { snprintf(nm, sizeof(nm), "%s.t32", base); save(nm, t32, (size_t)nn * 4); }
|
||||
}
|
||||
|
||||
/* header block dump: 0xaf0..0xaf0+0x60 (hdr + w/h + extra) */
|
||||
unsigned char hdrblk[0x60];
|
||||
memcpy(hdrblk, hdr, sizeof(hdrblk));
|
||||
{
|
||||
char nm[128];
|
||||
snprintf(nm, sizeof(nm), "%s.hdr", base);
|
||||
save(nm, hdrblk, sizeof(hdrblk));
|
||||
}
|
||||
|
||||
/* color bar data */
|
||||
if (G_Bar) {
|
||||
void *bdata = NULL, *binfo = NULL;
|
||||
int rcb = G_Bar(chan, &bdata, &binfo);
|
||||
printf(" bar rc=%d data=%p info=%p\n", rcb, bdata, binfo);
|
||||
if (rcb && bdata) {
|
||||
char nm[128];
|
||||
snprintf(nm, sizeof(nm), "%s.bar", base);
|
||||
save(nm, bdata, 4096);
|
||||
snprintf(nm, sizeof(nm), "%s.barinfo", base);
|
||||
save(nm, binfo ? binfo : bdata, 64);
|
||||
}
|
||||
}
|
||||
|
||||
/* temperature points */
|
||||
printf("[%d] frames=%d fcnt=%u rc_rgb=%d rc_temp=%d cb=%dx%d\n",
|
||||
k, g_frames, fcnt, rcr, rct, g_cbw, g_cbh);
|
||||
for (int p = 0; p < np; ++p) {
|
||||
unsigned char res[64] = {0};
|
||||
if (T_Temp) T_Temp(pts[p][0], pts[p][1], res);
|
||||
double tr = *(double *)(res + 16);
|
||||
double ta = *(double *)(res + 24);
|
||||
int rx = pts[p][0] / 2, ry = pts[p][1] / 2;
|
||||
unsigned int rawv = raw[ry * nw + rx];
|
||||
unsigned char gv = g2[pts[p][1] * w + pts[p][0]];
|
||||
unsigned int tv = rct ? t32[pts[p][1] * w + pts[p][0]] : 0;
|
||||
printf(" pt(%3d,%3d) raw@(%3d,%3d)=%5u gray=%3u temp32=%10u tempRaw=%.2f tempArm=%.2f\n",
|
||||
pts[p][0], pts[p][1], rx, ry, rawv, gv, tv, tr, ta);
|
||||
}
|
||||
fflush(stdout);
|
||||
Sleep(100);
|
||||
}
|
||||
|
||||
/* summary stats of raw/gray from last frame */
|
||||
{
|
||||
unsigned long long sr = 0, sg = 0;
|
||||
unsigned int mnr = 0xffff, mxr = 0, mng = 0xff, mxg = 0;
|
||||
unsigned int hist[256] = {0};
|
||||
for (int i = 0; i < nn; ++i) {
|
||||
unsigned int r = raw[i];
|
||||
sr += r;
|
||||
if (r < mnr) mnr = r; if (r > mxr) mxr = r;
|
||||
}
|
||||
for (int i = 0; i < n; ++i) {
|
||||
unsigned int g = g2[i];
|
||||
sg += g; hist[g]++;
|
||||
if (g < mng) mng = g; if (g > mxg) mxg = g;
|
||||
}
|
||||
printf("raw160: min=%u max=%u mean=%.1f gray320: min=%u max=%u mean=%.1f\n",
|
||||
mnr, mxr, (double)sr / nn, mng, mxg, (double)sg / n);
|
||||
printf("gray histogram (nonzero bins):\n");
|
||||
for (int i = 0; i < 256; ++i)
|
||||
if (hist[i]) printf(" %3d:%6u\n", i, hist[i]);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* scan device struct for interesting constant values */
|
||||
{
|
||||
printf("struct scan:\n");
|
||||
/* 1. look for window values as u32/u16 in wide range */
|
||||
int tw32[] = {22500, 23700, 25167, 31707, 5797, 1200};
|
||||
for (int off = 0x100; off < 0x10000; off += 4) {
|
||||
int v = *(int *)(dev + off);
|
||||
for (int t = 0; t < 6; ++t)
|
||||
if (v == tw32[t]) printf(" dev+0x%04x u32 = %d (t%d)\n", off, v, t);
|
||||
}
|
||||
for (int off = 0x100; off < 0x10000; off += 2) {
|
||||
short v = *(short *)(dev + off);
|
||||
for (int t = 0; t < 6; ++t)
|
||||
if (v == tw32[t]) printf(" dev+0x%04x u16 = %d (t%d)\n", off, v, t);
|
||||
}
|
||||
/* 2. look for 1024-byte monotone 0..255 table (LUT) */
|
||||
for (int off = 0x100; off < 0x20000 - 1024; off += 16) {
|
||||
const unsigned char *p = dev + off;
|
||||
int ok = 1, last = -1;
|
||||
for (int i = 0; i < 1024; i += 8) {
|
||||
int v = p[i];
|
||||
if (v < last || v > 255) { ok = 0; break; }
|
||||
if (p[i] != p[i+1] && p[i+1] != p[i]) { /* non-strict ok */ }
|
||||
last = v;
|
||||
}
|
||||
if (!ok) continue;
|
||||
/* require at least 32 distinct values and spans > 100 */
|
||||
int mn = 255, mx = 0;
|
||||
for (int i = 0; i < 1024; ++i) { if (p[i] < mn) mn = p[i]; if (p[i] > mx) mx = p[i]; }
|
||||
if (mx - mn > 100) {
|
||||
printf(" LUT candidate dev+0x%04x: min=%d max=%d first=%d last=%d\n",
|
||||
off, mn, mx, p[0], p[1023]);
|
||||
}
|
||||
}
|
||||
/* 3. look for window as double */
|
||||
for (int off = 0x100; off < 0x10000; off += 8) {
|
||||
double v = *(double *)(dev + off);
|
||||
if (v > 20000.0 && v < 40000.0 && fabs(v - 25167.0) < 10.0)
|
||||
printf(" dev+0x%04x double ~= %.1f\n", off, v);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Read the LIVE official palette: after ThermalSDK Start(), the channel
|
||||
* device object holds the 256-entry palette at dev+0xb00 (4 bytes each,
|
||||
* BGR order). We find the device pointer via MAG_GetOutputBMPdata which
|
||||
* returns the grayscale buffer at dev+0xaf0 - the palette is 0x10 before it.
|
||||
*/
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef void (CALLBACK *INewIRFrame)(const unsigned char *, int, int, int);
|
||||
typedef bool (CALLBACK *fn_Start_t)(void);
|
||||
typedef void (CALLBACK *fn_Stop_t)(void);
|
||||
typedef void (CALLBACK *fn_SetNewIRFrameDelegate_t)(INewIRFrame);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdata_t)(int, int *, unsigned char **);
|
||||
typedef int (CALLBACK *MAG_GetOutputBMPdataRGB24_t)(int, unsigned char *, int, int);
|
||||
typedef int (CALLBACK *MAG_GetOutputColorBardata_t)(int, unsigned char *, int);
|
||||
|
||||
static volatile int g_frames;
|
||||
static void CALLBACK onIR(const unsigned char *buf, int w, int h, int ch) {
|
||||
(void)buf; (void)w; (void)h; (void)ch;
|
||||
g_frames++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
SetDllDirectoryA("C:\\Project\\MAG160C\\IR_Camera_SDK-1.0.1\\windows\\windows\\app");
|
||||
HMODULE t = LoadLibraryA("ThermalSDK.dll");
|
||||
HMODULE c = LoadLibraryA("CoreSDKLib.dll");
|
||||
if (!t || !c) return 1;
|
||||
fn_Start_t T_Start = (fn_Start_t)GetProcAddress(t, "Start");
|
||||
fn_Stop_t T_Stop = (fn_Stop_t)GetProcAddress(t, "Stop");
|
||||
fn_SetNewIRFrameDelegate_t T_Set = (fn_SetNewIRFrameDelegate_t)GetProcAddress(t, "SetNewIRFrameDelegate");
|
||||
MAG_GetOutputBMPdata_t G_Gray = (MAG_GetOutputBMPdata_t)GetProcAddress(c, "MAG_GetOutputBMPdata");
|
||||
MAG_GetOutputBMPdataRGB24_t G_RGB = (MAG_GetOutputBMPdataRGB24_t)GetProcAddress(c, "MAG_GetOutputBMPdataRGB24");
|
||||
MAG_GetOutputColorBardata_t G_Bar = (MAG_GetOutputColorBardata_t)GetProcAddress(c, "MAG_GetOutputColorBardata");
|
||||
|
||||
T_Set(onIR);
|
||||
BOOL ok = T_Start();
|
||||
printf("Start=%d\n", ok); fflush(stdout);
|
||||
Sleep(3000);
|
||||
|
||||
for (int chan = 0; chan < 5; ++chan) {
|
||||
int w = 0;
|
||||
unsigned char *graybuf = NULL;
|
||||
int rc = G_Gray(chan, &w, &graybuf);
|
||||
printf("chan %d gray rc=%d buf=%p\n", chan, rc, (void*)graybuf);
|
||||
if (rc && graybuf) {
|
||||
/* palette at graybuf - 0x10 (dev+0xaf0 - 0x10 = dev+0xae0?)
|
||||
* Actually palette is dev+0xb00 = graybuf + 0x10 */
|
||||
unsigned char *pal = graybuf + 0x10;
|
||||
FILE *f = fopen("official_palette_live.bin", "wb");
|
||||
if (f) { fwrite(pal, 1, 256 * 4, f); fclose(f); }
|
||||
printf("saved official_palette_live.bin (first entries):\n");
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
printf(" %2d: %3d %3d %3d %3d\n", i, pal[i*4], pal[i*4+1], pal[i*4+2], pal[i*4+3]);
|
||||
}
|
||||
/* also dump a few RGB24 render pixels to correlate */
|
||||
unsigned char rgb[160*120*3];
|
||||
int rc2 = G_RGB(chan, rgb, 160*120*3, 1);
|
||||
printf("RGB24 rc=%d\n", rc2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
T_Stop();
|
||||
printf("done\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user