Files
MAG160C/csdk/tools/mag160c_cli.c
T

439 lines
14 KiB
C

/* 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;
}