/* * 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 #include #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 */