# MAG160C Linux SDK Design Date: 2026-07-12 ## Status Draft for user review. Approved approach: C++ core library + stable C ABI + CLI + Python-callable layer. ## Goal Build an original Linux-usable SDK for the MAG160C USB thermal camera kit, based on interoperability evidence from the bundled Windows and Android SDKs. The deliverable should support: - a reusable C/C++ library; - a command-line sample/tool; - Python-callable access to the same library; - clear boundaries between confirmed behavior and unknown IR camera private protocol details. ## Key evidence driving the design - IR camera USB identity: VID 0x833C, PID 0x0001. - Android MagDevice passes an Android USB file descriptor into native LinkCamera(fd), then calls MAG-style native APIs. - Android libcoresdk.so exports MAG_* APIs and uses libusb_bulk_transfer. - Windows ThermalSDK.dll wraps CoreSDKLib.dll MAG_* APIs. - Windows CoreSDKLib.dll imports libusb0 bulk read/write APIs. - TCM control framing is recoverable from Java bytecode: - packet starts with 0x7e; - big-endian body length; - additive byte checksums for header and body; - main/sub/frame fields; - payload starts after frame id. - TCM commands for motor, proximity, light, audio, power, schedule task, and firmware update are known enough to implement. ## Architecture The project should be split into four layers. ### 1. Core C++ library Internal name: libmag160c_core. Responsibilities: - Own libusb context/device handles. - Enumerate USB devices. - Match known MAG IR camera VID/PID. - Discover bulk endpoints for IR/TCM-like devices. - Encode and decode TCM frames. - Provide IR camera lifecycle abstractions matching the SDK shape: - open/close; - start/stop; - trigger FFC; - camera info; - raw capture/probe hooks; - callback plumbing. - Return explicit unsupported/protocol-unknown errors where low-level IR packet details are not yet known. The C++ layer may use RAII and internal classes, but those classes are not the public ABI. ### 2. Stable C ABI Public header: include/mag160c/mag160c.h. Responsibilities: - Expose opaque handles. - Provide simple structs and error codes. - Avoid C++ types in public signatures. - Support CLI and Python bindings through the same ABI. Proposed API families: - Context: - mag160c_init - mag160c_shutdown - mag160c_last_error - Device discovery: - mag160c_list_devices - mag160c_free_device_list - IR camera: - mag160c_ir_open - mag160c_ir_close - mag160c_ir_get_info - mag160c_ir_start - mag160c_ir_stop - mag160c_ir_trigger_ffc - mag160c_ir_read_raw_once - TCM: - mag160c_tcm_open - mag160c_tcm_close - mag160c_tcm_rotate - mag160c_tcm_read_distance - mag160c_tcm_set_light - mag160c_tcm_set_audio - mag160c_tcm_get_version - Utilities: - mag160c_tcm_encode_frame - mag160c_tcm_decode_frame ### 3. CLI tool Binary name: mag160c-cli. Initial commands: - probe - list matching USB devices and endpoints; - print VID/PID, bus/address, interfaces, endpoints. - tcm-rotate - send known motor rotate command. - tcm-light [steady|blink|breath] - send known light command. - tcm-distance - read/parse proximity distance if the TCM device is available. - ir-info - open IR device if possible and show descriptors/endpoints. - ir-capture-raw --out - attempt one raw read using discovered endpoints. - if protocol command is unknown, fail with a clear MAG160C_ERR_PROTOCOL_UNKNOWN. The CLI is both a sample and a hardware diagnostic tool. ### 4. Python-callable layer Package name: mag160c. Recommended first implementation: ctypes wrapper over the C ABI. Why ctypes first: - no compiled Python extension needed; - easier to use in experiments; - shares the exact tested C ABI used by the CLI. Python modules: - mag160c.core - shared-library loading, error handling, ctypes structs. - mag160c.tcm - rotate, light, distance helpers. - mag160c.ir - probe, open, info, raw capture helper. - mag160c.cli - optional Python CLI wrapper for quick scripts. ## Data flow ### TCM command flow Caller -> C ABI -> C++ TCM codec -> libusb bulk OUT -> device -> libusb bulk IN -> C++ TCM parser -> caller. TCM frames are framed, checksummed, and correlated by frame id. The implementation should keep a 15-bit-ish monotonically increasing frame id compatible with the Android behavior: start at 1, increment, wrap before 0x8000. ### IR camera flow Caller -> C ABI -> C++ IR device -> libusb endpoint discovery -> protocol-specific start/read/stop path. Known today: - device is vendor-specific bulk USB; - proprietary SDK converts raw/response words into temperature data; - exact start/read packet protocol remains unknown. Therefore initial IR behavior should focus on descriptor probing, safe endpoint discovery, raw read experiments, and clearly marked unsupported operations until protocol evidence is recovered. ## Error handling Use explicit error codes: - MAG160C_OK - MAG160C_ERR_INVALID_ARGUMENT - MAG160C_ERR_NO_DEVICE - MAG160C_ERR_PERMISSION - MAG160C_ERR_USB - MAG160C_ERR_TIMEOUT - MAG160C_ERR_CHECKSUM - MAG160C_ERR_PROTOCOL_UNKNOWN - MAG160C_ERR_UNSUPPORTED - MAG160C_ERR_INTERNAL Every public function returns an error code or a documented sentinel. Detailed messages are available through mag160c_last_error. The CLI must print actionable messages, especially for Linux USB permission problems and unknown IR protocol paths. ## Build system Use CMake. Targets: - mag160c_core shared library - mag160c_cli executable - mag160c_tests test executable - optional install target Dependencies: - libusb-1.0 - standard C/C++ runtime - Python only for wrapper/tests, not for core build No vendored proprietary SDK binaries should be linked into the implementation. ## Testing strategy without hardware Unit tests: - TCM frame encode/decode round trips. - checksum validation. - command payload construction. - frame id wrapping. - error-code behavior for null/invalid arguments. Mock tests: - fake USB transport interface for TCM read/write. - fake device descriptor/endpoints for probe logic. CLI dry-run tests: - commands that support --dry-run print the bytes they would send. - probe gracefully reports no device when hardware is absent. Hardware tests to document: - verify udev permissions; - run mag160c-cli probe; - run tcm-light/tcm-rotate with small safe values; - run ir-info; - run ir-capture-raw and preserve captured bytes for protocol analysis. ## Scope boundaries In scope for first implementation: - project scaffolding; - C ABI and Python wrapper; - TCM frame codec and known commands; - USB enumeration and endpoint discovery; - CLI diagnostics; - IR API skeleton with honest protocol-unknown errors. Not in scope until more evidence exists: - exact proprietary IR start/stream command sequence; - full raw-to-temperature parity; - face/RGB camera logic beyond noting that RGB is ordinary UVC/V4L2 territory; - firmware update execution by default. Firmware update commands may be encoded but should be guarded and not exposed as an easy accidental CLI action. ## Open decisions - Whether to expose firmware update commands at all in the first public CLI. Recommendation: keep them internal or behind an explicit experimental flag. - Whether to use C++17 or C++20. Recommendation: C++17 for broader Linux compatibility. - Whether Python packaging should initially be source-tree-only or installable via pyproject.toml. Recommendation: include pyproject.toml once the shared library layout is settled. ## Acceptance criteria for first implementation - CMake configures without proprietary SDK dependencies. - Unit tests pass without hardware. - CLI probe works and reports no-device cleanly when absent. - TCM codec tests include byte-exact known examples generated from the recovered frame rules. - Python can import mag160c and call at least probe/dry-run TCM helpers. - IR unsupported/protocol-unknown operations are explicit, not silent stubs.