730 lines
21 KiB
Markdown
730 lines
21 KiB
Markdown
# MAG160C Linux SDK Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
|
|
|
|
**Goal:** Build an original Linux SDK with a C++ core, stable C ABI, CLI diagnostics, and Python-callable wrapper for the MAG160C thermal camera kit.
|
|
|
|
**Architecture:** Implement a C++17 core around libusb-1.0 and expose a C ABI from include/mag160c/mag160c.h. The CLI and Python wrapper both call the C ABI. TCM commands are implemented from recovered frame evidence; IR private streaming paths expose safe probe behavior and explicit protocol-unknown errors until USB traces provide the missing command packets.
|
|
|
|
**Tech Stack:** CMake, C++17, C ABI, libusb-1.0, Python ctypes, pytest, CTest.
|
|
|
|
## Global Constraints
|
|
|
|
- Use only original source code; do not link proprietary SDK binaries.
|
|
- Current workspace is not a git repository; replace commit steps with verification plus updates to progress.md.
|
|
- Keep public C ABI free of C++ types.
|
|
- Use C++17.
|
|
- libusb-1.0 is the only native runtime dependency for the core library.
|
|
- Firmware update commands are not exposed as an easy accidental CLI action.
|
|
- IR protocol gaps return MAG160C_ERR_PROTOCOL_UNKNOWN or MAG160C_ERR_UNSUPPORTED.
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
Create:
|
|
|
|
- CMakeLists.txt: root build, options, targets, CTest.
|
|
- include/mag160c/mag160c.h: stable C ABI.
|
|
- src/core/error.hpp and src/core/error.cpp: thread-local error messages and error names.
|
|
- src/core/context.hpp and src/core/context.cpp: opaque context and libusb lifetime.
|
|
- src/core/device.hpp and src/core/device.cpp: USB descriptor models and endpoint discovery.
|
|
- src/core/tcm_frame.hpp and src/core/tcm_frame.cpp: TCM frame encode/decode/checksum.
|
|
- src/core/tcm_device.hpp and src/core/tcm_device.cpp: TCM command payload builders.
|
|
- src/core/ir_device.hpp and src/core/ir_device.cpp: IR camera probe/skeleton operations.
|
|
- src/c_api.cpp: C ABI implementation over the C++ core.
|
|
- tools/mag160c_cli.cpp: CLI entry point.
|
|
- tests/cpp/test_c_api.cpp, tests/cpp/test_tcm_frame.cpp, tests/cpp/test_tcm_device.cpp, tests/cpp/test_device_model.cpp, tests/cpp/test_ir_skeleton.cpp.
|
|
- python/mag160c/__init__.py, python/mag160c/core.py, python/mag160c/tcm.py, python/mag160c/ir.py.
|
|
- python/tests/test_python_wrapper.py.
|
|
- docs/linux-hardware-test.md and README.md.
|
|
|
|
---
|
|
|
|
### Task 1: Build scaffold and public C ABI lifecycle
|
|
|
|
**Files:**
|
|
- Create: CMakeLists.txt
|
|
- Create: include/mag160c/mag160c.h
|
|
- Create: src/core/error.hpp
|
|
- Create: src/core/error.cpp
|
|
- Create: src/core/context.hpp
|
|
- Create: src/core/context.cpp
|
|
- Create: src/c_api.cpp
|
|
- Create: tests/cpp/test_c_api.cpp
|
|
|
|
**Interfaces:**
|
|
- Produces: mag160c_error_t, mag160c_context_t, mag160c_init, mag160c_shutdown, mag160c_last_error, mag160c_error_name.
|
|
|
|
- [ ] Step 1: Create failing lifecycle test in tests/cpp/test_c_api.cpp.
|
|
|
|
Use these assertions:
|
|
|
|
~~~cpp
|
|
assert(std::strcmp(mag160c_error_name(MAG160C_OK), "MAG160C_OK") == 0);
|
|
assert(std::strcmp(mag160c_error_name(MAG160C_ERR_PROTOCOL_UNKNOWN), "MAG160C_ERR_PROTOCOL_UNKNOWN") == 0);
|
|
assert(mag160c_init(nullptr) == MAG160C_ERR_INVALID_ARGUMENT);
|
|
mag160c_context_t* ctx = nullptr;
|
|
assert(mag160c_init(&ctx) == MAG160C_OK);
|
|
assert(ctx != nullptr);
|
|
mag160c_shutdown(ctx);
|
|
~~~
|
|
|
|
- [ ] Step 2: Create CMakeLists.txt.
|
|
|
|
Required target behavior:
|
|
|
|
~~~cmake
|
|
cmake_minimum_required(VERSION 3.16)
|
|
project(mag160c VERSION 0.1.0 LANGUAGES C CXX)
|
|
option(MAG160C_BUILD_TESTS "Build MAG160C tests" ON)
|
|
option(MAG160C_BUILD_CLI "Build MAG160C CLI" ON)
|
|
set(CMAKE_CXX_STANDARD 17)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
find_package(PkgConfig QUIET)
|
|
if(PkgConfig_FOUND)
|
|
pkg_check_modules(LIBUSB QUIET libusb-1.0)
|
|
endif()
|
|
add_library(mag160c_core SHARED src/core/error.cpp src/core/context.cpp src/c_api.cpp)
|
|
target_include_directories(mag160c_core PUBLIC include PRIVATE src)
|
|
if(LIBUSB_FOUND)
|
|
target_include_directories(mag160c_core PRIVATE ${LIBUSB_INCLUDE_DIRS})
|
|
target_link_libraries(mag160c_core PRIVATE ${LIBUSB_LIBRARIES})
|
|
target_compile_definitions(mag160c_core PRIVATE MAG160C_HAS_LIBUSB=1)
|
|
else()
|
|
target_compile_definitions(mag160c_core PRIVATE MAG160C_HAS_LIBUSB=0)
|
|
endif()
|
|
if(MAG160C_BUILD_TESTS)
|
|
enable_testing()
|
|
add_executable(test_c_api tests/cpp/test_c_api.cpp)
|
|
target_link_libraries(test_c_api PRIVATE mag160c_core)
|
|
add_test(NAME test_c_api COMMAND test_c_api)
|
|
endif()
|
|
~~~
|
|
|
|
- [ ] Step 3: Create include/mag160c/mag160c.h with opaque handles and error enum.
|
|
|
|
Required declarations:
|
|
|
|
~~~c
|
|
typedef enum mag160c_error_t {
|
|
MAG160C_OK = 0,
|
|
MAG160C_ERR_INVALID_ARGUMENT = 1,
|
|
MAG160C_ERR_NO_DEVICE = 2,
|
|
MAG160C_ERR_PERMISSION = 3,
|
|
MAG160C_ERR_USB = 4,
|
|
MAG160C_ERR_TIMEOUT = 5,
|
|
MAG160C_ERR_CHECKSUM = 6,
|
|
MAG160C_ERR_PROTOCOL_UNKNOWN = 7,
|
|
MAG160C_ERR_UNSUPPORTED = 8,
|
|
MAG160C_ERR_INTERNAL = 9
|
|
} mag160c_error_t;
|
|
|
|
typedef struct mag160c_context_t mag160c_context_t;
|
|
typedef struct mag160c_ir_device_t mag160c_ir_device_t;
|
|
typedef struct mag160c_tcm_device_t mag160c_tcm_device_t;
|
|
|
|
mag160c_error_t mag160c_init(mag160c_context_t** out_ctx);
|
|
void mag160c_shutdown(mag160c_context_t* ctx);
|
|
const char* mag160c_last_error(void);
|
|
const char* mag160c_error_name(mag160c_error_t code);
|
|
~~~
|
|
|
|
- [ ] Step 4: Implement src/core/error.* and src/core/context.*.
|
|
|
|
Required behavior:
|
|
|
|
- last error is thread-local;
|
|
- mag160c_error_name returns stable string names;
|
|
- Context construction succeeds without hardware;
|
|
- when libusb is absent at build time, Context records has_libusb as false but still constructs.
|
|
|
|
- [ ] Step 5: Implement src/c_api.cpp lifecycle.
|
|
|
|
Required behavior:
|
|
|
|
- mag160c_init rejects null output pointer;
|
|
- mag160c_init allocates a context;
|
|
- mag160c_shutdown accepts null through delete behavior;
|
|
- mag160c_last_error returns an empty string after successful init.
|
|
|
|
- [ ] Step 6: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake -S . -B build -DMAG160C_BUILD_TESTS=ON
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
~~~
|
|
|
|
Expected: test_c_api passes.
|
|
|
|
- [ ] Step 7: Record checkpoint in progress.md.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 1 scaffold: CMake, public C ABI lifecycle, error names, and lifecycle tests.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 2: TCM frame codec
|
|
|
|
**Files:**
|
|
- Create: src/core/tcm_frame.hpp
|
|
- Create: src/core/tcm_frame.cpp
|
|
- Create: tests/cpp/test_tcm_frame.cpp
|
|
- Modify: CMakeLists.txt
|
|
- Modify: include/mag160c/mag160c.h
|
|
- Modify: src/c_api.cpp
|
|
|
|
**Interfaces:**
|
|
- Produces: mag160c::core::TcmFrame, encode_tcm_frame, decode_tcm_frame, mag160c_tcm_encode_frame, mag160c_tcm_decode_header.
|
|
|
|
- [ ] Step 1: Create byte-exact codec tests.
|
|
|
|
Required exact frame for rotate +5 frame id 1:
|
|
|
|
~~~text
|
|
7e 00 07 85 02 77 00 01 00 05 7f
|
|
~~~
|
|
|
|
Test cases:
|
|
|
|
- encode_tcm_frame(0x02, 0x77, 0x0001, {0x00, 0x05}) equals the exact bytes above;
|
|
- corrupting byte 3 returns MAG160C_ERR_CHECKSUM;
|
|
- decoding a valid frame returns main 0x02, sub 0x77, frame id 1, payload {0x00, 0x05}.
|
|
|
|
- [ ] Step 2: Implement src/core/tcm_frame.hpp.
|
|
|
|
Required declarations:
|
|
|
|
~~~cpp
|
|
struct TcmFrame {
|
|
uint8_t main_cmd = 0;
|
|
uint8_t sub_cmd = 0;
|
|
uint16_t frame_id = 0;
|
|
std::vector<uint8_t> payload;
|
|
};
|
|
|
|
uint8_t checksum(const uint8_t* data, size_t begin, size_t end);
|
|
std::vector<uint8_t> encode_tcm_frame(uint8_t main_cmd, uint8_t sub_cmd, uint16_t frame_id, const std::vector<uint8_t>& payload);
|
|
mag160c_error_t decode_tcm_frame(const uint8_t* data, size_t size, TcmFrame* out);
|
|
~~~
|
|
|
|
- [ ] Step 3: Implement src/core/tcm_frame.cpp.
|
|
|
|
Rules:
|
|
|
|
- header byte 0 is 0x7e;
|
|
- bytes 1..2 are big-endian body length;
|
|
- byte 3 is additive checksum over bytes 0..2;
|
|
- body is main, sub, frame high, frame low, payload, body checksum;
|
|
- body checksum is additive checksum over body bytes before the final checksum byte.
|
|
|
|
- [ ] Step 4: Add C ABI frame utilities.
|
|
|
|
Required declarations:
|
|
|
|
~~~c
|
|
mag160c_error_t mag160c_tcm_encode_frame(uint8_t main_cmd, uint8_t sub_cmd, uint16_t frame_id, const uint8_t* payload, size_t payload_size, uint8_t* out_bytes, size_t out_capacity, size_t* out_size);
|
|
mag160c_error_t mag160c_tcm_decode_header(const uint8_t* data, size_t size, uint8_t* out_main_cmd, uint8_t* out_sub_cmd, uint16_t* out_frame_id, size_t* out_payload_size);
|
|
~~~
|
|
|
|
- [ ] Step 5: Update build.
|
|
|
|
Add src/core/tcm_frame.cpp to mag160c_core and add test_tcm_frame to CTest.
|
|
|
|
- [ ] Step 6: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
~~~
|
|
|
|
Expected: test_c_api and test_tcm_frame pass.
|
|
|
|
- [ ] Step 7: Record checkpoint.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 2 TCM frame codec with byte-exact encode/decode tests and C ABI utility functions.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 3: TCM command builders
|
|
|
|
**Files:**
|
|
- Create: src/core/transport.hpp
|
|
- Create: src/core/tcm_device.hpp
|
|
- Create: src/core/tcm_device.cpp
|
|
- Create: tests/cpp/test_tcm_device.cpp
|
|
- Modify: CMakeLists.txt
|
|
- Modify: include/mag160c/mag160c.h
|
|
- Modify: src/c_api.cpp
|
|
|
|
**Interfaces:**
|
|
- Produces: Transport, TcmCommandBuilder, mag160c_tcm_build_rotate_frame, mag160c_tcm_build_light_frame.
|
|
|
|
- [ ] Step 1: Create tests for known commands.
|
|
|
|
Required expected outputs:
|
|
|
|
- rotate +5: 7e 00 07 85 02 77 00 01 00 05 7f
|
|
- after one command, rotate -3 with frame id 2: 7e 00 07 85 02 77 00 02 01 03 7f
|
|
- green blink: 7e 00 09 87 02 32 00 01 01 00 ff 00 35
|
|
|
|
- [ ] Step 2: Create src/core/transport.hpp.
|
|
|
|
Required interface:
|
|
|
|
~~~cpp
|
|
class Transport {
|
|
public:
|
|
virtual ~Transport() = default;
|
|
virtual mag160c_error_t write(const std::vector<uint8_t>& bytes) = 0;
|
|
virtual mag160c_error_t read(std::vector<uint8_t>* out, int timeout_ms) = 0;
|
|
};
|
|
~~~
|
|
|
|
- [ ] Step 3: Create TcmCommandBuilder.
|
|
|
|
Required behavior:
|
|
|
|
- frame id starts at 1;
|
|
- increments after each encoded frame;
|
|
- wraps to 1 before reaching 0x8000;
|
|
- rotate clamps input to [-128, 128];
|
|
- rotate payload is direction and magnitude, direction 0 for positive, 1 for negative;
|
|
- light payload is [on, red, green, blue];
|
|
- red/green/blue/yellow channel value is 0xff;
|
|
- off payload is [0,0,0,0];
|
|
- steady sub 0x31, blink sub 0x32, breath sub 0x33.
|
|
|
|
- [ ] Step 4: Add C ABI dry-run builders.
|
|
|
|
Required declarations:
|
|
|
|
~~~c
|
|
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;
|
|
|
|
mag160c_error_t mag160c_tcm_build_rotate_frame(int angle, uint8_t* out_bytes, size_t out_capacity, size_t* out_size);
|
|
mag160c_error_t mag160c_tcm_build_light_frame(mag160c_tcm_light_color_t color, mag160c_tcm_light_mode_t mode, uint8_t* out_bytes, size_t out_capacity, size_t* out_size);
|
|
~~~
|
|
|
|
- [ ] Step 5: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
~~~
|
|
|
|
Expected: test_tcm_device and previous tests pass.
|
|
|
|
- [ ] Step 6: Record checkpoint.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 3 TCM command builder, dry-run C ABI helpers, and command payload tests.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 4: USB discovery and endpoint model
|
|
|
|
**Files:**
|
|
- Create: src/core/device.hpp
|
|
- Create: src/core/device.cpp
|
|
- Create: tests/cpp/test_device_model.cpp
|
|
- Modify: CMakeLists.txt
|
|
- Modify: include/mag160c/mag160c.h
|
|
- Modify: src/c_api.cpp
|
|
|
|
**Interfaces:**
|
|
- Produces: EndpointPair, DeviceInfo, find_bulk_pair, mag160c_list_devices, mag160c_free_device_list.
|
|
|
|
- [ ] Step 1: Create endpoint-selection tests.
|
|
|
|
Cases:
|
|
|
|
- endpoints {0x01 interrupt, 0x82 bulk, 0x03 bulk} returns bulk_in 0x82 and bulk_out 0x03;
|
|
- endpoints {0x82 bulk} returns false because bulk OUT is absent.
|
|
|
|
- [ ] Step 2: Implement device model.
|
|
|
|
Required constants and types:
|
|
|
|
~~~cpp
|
|
constexpr uint16_t MAG_IR_VENDOR_ID = 0x833c;
|
|
constexpr uint16_t MAG_IR_PRODUCT_ID = 0x0001;
|
|
|
|
enum class EndpointType { Other, Bulk, Interrupt, Isochronous };
|
|
|
|
struct EndpointDescriptor {
|
|
uint8_t address = 0;
|
|
EndpointType type = EndpointType::Other;
|
|
};
|
|
|
|
struct EndpointPair {
|
|
uint8_t bulk_in = 0;
|
|
uint8_t bulk_out = 0;
|
|
};
|
|
|
|
struct DeviceInfo {
|
|
mag160c_device_info_t c_info{};
|
|
};
|
|
~~~
|
|
|
|
- [ ] Step 3: Implement libusb enumeration.
|
|
|
|
Required behavior:
|
|
|
|
- if libusb is unavailable at build time, list_devices returns MAG160C_ERR_UNSUPPORTED;
|
|
- if libusb is available and no matching device is present, list_devices returns MAG160C_OK with count 0;
|
|
- only VID 0x833C and PID 0x0001 are matched initially;
|
|
- first interface with both bulk IN and bulk OUT is reported.
|
|
|
|
- [ ] Step 4: Add C ABI device list.
|
|
|
|
Required declarations:
|
|
|
|
~~~c
|
|
typedef struct mag160c_device_info_t {
|
|
uint16_t vendor_id;
|
|
uint16_t product_id;
|
|
uint8_t bus;
|
|
uint8_t address;
|
|
uint8_t interface_number;
|
|
uint8_t bulk_in_endpoint;
|
|
uint8_t bulk_out_endpoint;
|
|
char product[128];
|
|
char manufacturer[128];
|
|
char serial[128];
|
|
} mag160c_device_info_t;
|
|
|
|
mag160c_error_t mag160c_list_devices(mag160c_context_t* ctx, mag160c_device_info_t** out_devices, size_t* out_count);
|
|
void mag160c_free_device_list(mag160c_device_info_t* devices);
|
|
~~~
|
|
|
|
- [ ] Step 5: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
~~~
|
|
|
|
Expected: device model tests pass without hardware.
|
|
|
|
- [ ] Step 6: Record checkpoint.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 4 USB device model, endpoint-pair selection, and C ABI device listing/freeing.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 5: IR camera skeleton with explicit protocol gaps
|
|
|
|
**Files:**
|
|
- Create: src/core/ir_device.hpp
|
|
- Create: src/core/ir_device.cpp
|
|
- Create: tests/cpp/test_ir_skeleton.cpp
|
|
- Modify: CMakeLists.txt
|
|
- Modify: include/mag160c/mag160c.h
|
|
- Modify: src/c_api.cpp
|
|
|
|
**Interfaces:**
|
|
- Produces: mag160c_ir_open_first, mag160c_ir_close, mag160c_ir_get_info, mag160c_ir_trigger_ffc, mag160c_ir_read_raw_once.
|
|
|
|
- [x] Step 1: Create no-hardware-safe IR tests.
|
|
|
|
Cases:
|
|
|
|
- mag160c_ir_open_first(nullptr, nullptr) returns MAG160C_ERR_INVALID_ARGUMENT;
|
|
- mag160c_ir_close(nullptr) is safe;
|
|
- mag160c_ir_get_info(nullptr, nullptr) returns MAG160C_ERR_INVALID_ARGUMENT;
|
|
- trigger_ffc on an opened skeleton device returns MAG160C_ERR_PROTOCOL_UNKNOWN.
|
|
|
|
- [x] Step 2: Add public IR info struct and declarations.
|
|
|
|
Required struct fields:
|
|
|
|
~~~c
|
|
typedef struct mag160c_ir_info_t {
|
|
uint16_t width;
|
|
uint16_t height;
|
|
uint16_t output_width;
|
|
uint16_t output_height;
|
|
uint8_t max_fps;
|
|
uint8_t current_fps;
|
|
char name[64];
|
|
char type[64];
|
|
} mag160c_ir_info_t;
|
|
~~~
|
|
|
|
Required functions:
|
|
|
|
~~~c
|
|
mag160c_error_t mag160c_ir_open_first(mag160c_context_t* ctx, mag160c_ir_device_t** out_device);
|
|
void mag160c_ir_close(mag160c_ir_device_t* device);
|
|
mag160c_error_t mag160c_ir_get_info(mag160c_ir_device_t* device, mag160c_ir_info_t* out_info);
|
|
mag160c_error_t mag160c_ir_trigger_ffc(mag160c_ir_device_t* device);
|
|
mag160c_error_t mag160c_ir_read_raw_once(mag160c_ir_device_t* device, uint8_t* out_bytes, size_t out_capacity, size_t* out_size, int timeout_ms);
|
|
~~~
|
|
|
|
- [x] Step 3: Implement IR skeleton.
|
|
|
|
Required behavior:
|
|
|
|
- info returns width 160, height 120, output dimensions 160x120, name MAG160C, type vendor-bulk-ir;
|
|
- trigger_ffc returns MAG160C_ERR_PROTOCOL_UNKNOWN with an explanatory last error;
|
|
- read_raw_once sets out_size to 0 and returns MAG160C_ERR_PROTOCOL_UNKNOWN.
|
|
|
|
- [x] Step 4: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
~~~
|
|
|
|
Expected: IR skeleton tests and previous tests pass.
|
|
|
|
- [x] Step 5: Record checkpoint.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 5 IR API skeleton with explicit protocol-unknown errors for unrecovered private IR commands.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 6: CLI diagnostics and dry-run commands
|
|
|
|
**Files:**
|
|
- Create: tools/mag160c_cli.cpp
|
|
- Modify: CMakeLists.txt
|
|
|
|
**Interfaces:**
|
|
- Produces: mag160c-cli probe, mag160c-cli tcm-rotate --dry-run ANGLE, mag160c-cli tcm-light --dry-run COLOR MODE, mag160c-cli ir-info.
|
|
|
|
- [x] Step 1: Implement CLI parser.
|
|
|
|
Required command behavior:
|
|
|
|
- no args prints usage and exits 1;
|
|
- probe prints devices count and per-device VID/PID/bus/address/interface/endpoints;
|
|
- tcm-rotate --dry-run 5 prints 7e 00 07 85 02 77 00 01 00 05 7f;
|
|
- tcm-light --dry-run green blink prints 7e 00 09 87 02 32 00 01 01 00 ff 00 35;
|
|
- ir-info prints MAG160C skeleton info.
|
|
|
|
- [x] Step 2: Add CLI target.
|
|
|
|
Add executable target mag160c-cli linked against mag160c_core when MAG160C_BUILD_CLI is ON.
|
|
|
|
- [x] Step 3: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake --build build
|
|
.uildmag160c-cli.exe tcm-rotate --dry-run 5
|
|
.uildmag160c-cli.exe tcm-light --dry-run green blink
|
|
.uildmag160c-cli.exe ir-info
|
|
~~~
|
|
|
|
If using a multi-config generator, use .\build\Debug\mag160c-cli.exe.
|
|
|
|
Expected dry-run outputs match Step 1.
|
|
|
|
- [x] Step 4: Record checkpoint.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 6 mag160c-cli with probe, TCM dry-run commands, and IR info diagnostics.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 7: Python ctypes wrapper
|
|
|
|
**Files:**
|
|
- Create: python/mag160c/__init__.py
|
|
- Create: python/mag160c/core.py
|
|
- Create: python/mag160c/tcm.py
|
|
- Create: python/mag160c/ir.py
|
|
- Create: python/tests/test_python_wrapper.py
|
|
|
|
**Interfaces:**
|
|
- Produces: mag160c.tcm.build_rotate_frame(angle), mag160c.tcm.build_light_frame(color, mode), mag160c.ir.info().
|
|
|
|
- [ ] Step 1: Implement core.py shared-library loading.
|
|
|
|
Required behavior:
|
|
|
|
- load MAG160C_LIBRARY env var first when set;
|
|
- otherwise search build, build/Debug, build/Release, and current directory;
|
|
- configure ctypes signatures for mag160c_error_name, mag160c_last_error, mag160c_tcm_build_rotate_frame, mag160c_tcm_build_light_frame;
|
|
- raise Mag160CError with C error name and last error.
|
|
|
|
- [ ] Step 2: Implement tcm.py.
|
|
|
|
Required behavior:
|
|
|
|
~~~python
|
|
assert build_rotate_frame(5) == bytes.fromhex("7e0007850277000100057f")
|
|
assert build_light_frame("green", "blink") == bytes.fromhex("7e000987023200010100ff0035")
|
|
~~~
|
|
|
|
Invalid color or mode raises ValueError.
|
|
|
|
- [ ] Step 3: Implement ir.py.
|
|
|
|
Required behavior:
|
|
|
|
- info() returns dict with name MAG160C, type vendor-bulk-ir, width 160, height 120, and status string describing that private streaming packets are not recovered.
|
|
|
|
- [ ] Step 4: Add pytest coverage.
|
|
|
|
Test:
|
|
|
|
- rotate bytes exact;
|
|
- green blink bytes exact;
|
|
- invalid color raises ValueError.
|
|
|
|
- [ ] Step 5: Verify.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
$env:PYTHONPATH = "C:\Project\MAG160C\python"
|
|
python -m pytest python\tests -q
|
|
~~~
|
|
|
|
Expected: Python tests pass when shared library is in a searched build directory or MAG160C_LIBRARY points to it.
|
|
|
|
- [ ] Step 6: Record checkpoint.
|
|
|
|
Append:
|
|
|
|
~~~markdown
|
|
- Implemented Task 7 Python ctypes wrapper for TCM dry-run helpers and initial IR status helper.
|
|
~~~
|
|
|
|
---
|
|
|
|
### Task 8: Hardware-test docs and final no-hardware verification
|
|
|
|
**Files:**
|
|
- Create: docs/linux-hardware-test.md
|
|
- Create or modify: README.md
|
|
- Modify: progress.md
|
|
- Modify: task_plan.md
|
|
|
|
**Interfaces:**
|
|
- Consumes: all previous tasks.
|
|
- Produces: reproducible no-hardware verification and hardware checklist.
|
|
|
|
- [ ] Step 1: Create docs/linux-hardware-test.md.
|
|
|
|
Required sections:
|
|
|
|
- no-hardware build and test commands;
|
|
- udev rule for VID 833c PID 0001;
|
|
- probe command and expected outputs;
|
|
- safe TCM checks limited to dry-run commands in the first release;
|
|
- IR checks that state full streaming requires recovered private command packets;
|
|
- evidence to preserve from future hardware captures: lsusb -v, mag160c-cli probe, Windows USBPcap traces, raw Linux bulk bytes.
|
|
|
|
- [ ] Step 2: Create README.md if absent.
|
|
|
|
Required sections:
|
|
|
|
- current scope;
|
|
- build commands;
|
|
- CLI examples;
|
|
- Python examples;
|
|
- link to docs/linux-hardware-test.md.
|
|
|
|
- [ ] Step 3: Run full verification.
|
|
|
|
Run:
|
|
|
|
~~~powershell
|
|
cmake -S . -B build -DMAG160C_BUILD_TESTS=ON -DMAG160C_BUILD_CLI=ON
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
$env:PYTHONPATH = "C:\Project\MAG160C\python"
|
|
python -m pytest python\tests -q
|
|
~~~
|
|
|
|
Expected:
|
|
|
|
- all CTest tests pass;
|
|
- Python tests pass;
|
|
- CLI dry-run examples print exact bytes from Task 6.
|
|
|
|
- [ ] Step 4: Update planning files.
|
|
|
|
In task_plan.md:
|
|
|
|
- mark Phase 6 complete after implementation builds and no-hardware tests pass;
|
|
- mark Phase 7 complete after docs/linux-hardware-test.md exists and verification commands are recorded.
|
|
|
|
Append to progress.md:
|
|
|
|
~~~markdown
|
|
- Implemented Task 8 documentation and ran full no-hardware verification for C++ tests, CLI dry-run examples, and Python wrapper tests.
|
|
~~~
|
|
|
|
---
|
|
|
|
## Self-Review Checklist
|
|
|
|
- Spec coverage:
|
|
- C++ core library: Tasks 1 through 5.
|
|
- Stable C ABI: Tasks 1 through 5.
|
|
- CLI: Task 6.
|
|
- Python-callable layer: Task 7.
|
|
- No proprietary SDK linkage: Global Constraints and CMake design.
|
|
- TCM known commands: Tasks 2 and 3.
|
|
- IR protocol gaps: Task 5.
|
|
- Tests without hardware: Tasks 1 through 8.
|
|
- Hardware docs: Task 8.
|
|
- Type consistency:
|
|
- Public C handles use mag160c_context_t, mag160c_ir_device_t, and mag160c_tcm_device_t.
|
|
- Error codes match mag160c_error_t.
|
|
- TCM dry-run helpers use uint8_t* out_bytes, size_t out_capacity, and size_t* out_size.
|
|
- Scope check:
|
|
- Firmware update packet construction is not surfaced in the first easy CLI path.
|
|
- IR streaming parity is not claimed without recovered packet evidence.
|