/* mag160c-cli: diagnostics and dry-run tool for the recovered protocol. */ #include "mag160c/mag160c.h" #include "mag160c/mag160c_display.h" #include #include #include #if defined(_WIN32) #include #define MAG_SLEEP_MS(ms) Sleep(ms) #define MAG_TIME_NOW() ((double)GetTickCount()) #define MAG_ELAPSED(t0) ((double)GetTickCount() - (t0)) #else #include #include #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 [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 build a TCM rotate frame (dry run, prints bytes)\n" " tcm-light 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 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 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; }