/* MAG160C Windows Demo (libusb0 = libusb-win32 backend, same as the official * EloThermal demo). Uses the exact transfer layer the vendor demo ships * with, so the type=0 (temperature) stream stays alive with FFC switching. * * Build: * gcc -std=c11 -mwindows mag160c_demo_usb0.c libusb0.a -o mag160c_demo_usb0.exe * copy libusb0.dll next to the exe. */ #define _WIN32_WINNT 0x0601 #include #include #include /* ---- libusb-win32 (libusb0) API ---------------------------------------- */ typedef struct usb_bus { struct usb_bus *next, *prev; char dirname[512]; struct usb_device *devices; unsigned long location; struct usb_device *root_dev; } usb_bus; typedef struct usb_device_descriptor { uint8_t bLength, bDescriptorType; uint16_t bcdUSB; uint8_t bDeviceClass, bDeviceSubClass, bDeviceProtocol, bMaxPacketSize0; uint16_t idVendor, idProduct, bcdDevice; uint8_t iManufacturer, iProduct, iSerialNumber, bNumConfigurations; } usb_device_descriptor; typedef struct usb_device { struct usb_device *next, *prev; char filename[512]; struct usb_bus *bus; usb_device_descriptor descriptor; void *config; void *dev; uint8_t devnum; unsigned char num_children; struct usb_device **children; } usb_device; typedef struct usb_dev_handle usb_dev_handle; typedef int (*fn_usb_init)(void); typedef int (*fn_usb_find_busses)(void); typedef int (*fn_usb_find_devices)(void); typedef usb_bus *(*fn_usb_get_busses)(void); typedef usb_dev_handle *(*fn_usb_open)(usb_device *); typedef int (*fn_usb_set_configuration)(usb_dev_handle *, int); typedef int (*fn_usb_claim_interface)(usb_dev_handle *, int); typedef int (*fn_usb_bulk_write)(usb_dev_handle *, int, const char *, int, int); typedef int (*fn_usb_bulk_read)(usb_dev_handle *, int, char *, int, int); typedef int (*fn_usb_close)(usb_dev_handle *); static fn_usb_init p_usb_init; static fn_usb_find_busses p_usb_find_busses; static fn_usb_find_devices p_usb_find_devices; static fn_usb_get_busses p_usb_get_busses; static fn_usb_open p_usb_open; static fn_usb_set_configuration p_usb_set_configuration; static fn_usb_claim_interface p_usb_claim_interface; static fn_usb_bulk_write p_usb_bulk_write; static fn_usb_bulk_read p_usb_bulk_read; static fn_usb_close p_usb_close; static usb_dev_handle *g_dev; #define MAG_CMD_PREPARE1 0x6bb6b66b #define MAG_CMD_PREPARE2 0x6bb6b66c #define MAG_CMD_GET_INFO 0x6bb6b66f #define MAG_CMD_FFC 0x6bb6b672 #define MAG_CMD_START 0x6bb6b673 #define MAG_CMD_STOP 0x6bb6b674 #define IR_W 160 #define IR_H 120 #define FRAME_LEN (IR_W * IR_H * 2) static unsigned char g_frame[FRAME_LEN]; static unsigned char g_bmp[54 + IR_W * IR_H * 3]; static char g_status[512]; static volatile unsigned g_frame_count; static volatile unsigned g_frame_type; static int g_diff_mode = 1; static unsigned short g_reference[IR_W * IR_H]; static int g_has_reference; static double g_fps; static int g_probe_x = -1, g_probe_y = -1; static int g_max_x = -1, g_max_y = -1; static double g_max_temp = -100.0; static double g_center_temp = -100.0; static FILE *g_log; static int sendcmd(unsigned magic, unsigned param, int len) { unsigned char cmd[8] = {0}; cmd[0] = (unsigned char)(magic); cmd[1] = (unsigned char)(magic >> 8); cmd[2] = (unsigned char)(magic >> 16); cmd[3] = (unsigned char)(magic >> 24); if (len >= 8) { cmd[4] = (unsigned char)(param); cmd[5] = (unsigned char)(param >> 8); cmd[6] = (unsigned char)(param >> 16); cmd[7] = (unsigned char)(param >> 24); } int xfer = 0; if (p_usb_bulk_write(g_dev, 0x03, (char *)cmd, len, 2000) != len) return -1; unsigned char resp[0x1000]; xfer = p_usb_bulk_read(g_dev, 0x82, (char *)resp, sizeof(resp), 2000); if (xfer < 4) return -1; return 0; } static void render(const unsigned char *frame) { int hot = 0; unsigned hotv = 0; unsigned center_sum = 0; unsigned char *px = g_bmp + 54; for (int y = 0; y < IR_H; ++y) { for (int x = 0; x < IR_W; ++x) { int o = (y * IR_W + x) * 2; unsigned v = (unsigned)frame[o] | ((unsigned)frame[o + 1] << 8); int diffv = 0; if (g_diff_mode && g_has_reference) diffv = (int)v - (int)g_reference[y * IR_W + x]; if (x >= 70 && x < 90 && y >= 50 && y < 70) center_sum += v; if (v > hotv && v != 0) { hotv = v; hot = y * IR_W + x; } unsigned char r, g, b; if (g_diff_mode && g_has_reference) { int span = 2000; if (diffv > 0) { unsigned idx = (unsigned)(diffv * 255 / span); if (idx > 255) idx = 255; r = (unsigned char)(128 + idx / 2); g = (unsigned char)(128 - idx / 2); b = (unsigned char)(128 - idx / 2); } else { unsigned idx = (unsigned)(-diffv * 255 / span); if (idx > 255) idx = 255; r = (unsigned char)(128 - idx / 2); g = (unsigned char)(128 - idx / 2); b = (unsigned char)(128 + idx / 2); } } else { /* absolute with fixed range (type=0 data ~ 8000..30000) */ unsigned idx = (v - 8000) * 255 / 22000; if (idx > 255) idx = 255; if (idx < 64) { r = 0; g = (unsigned char)(idx * 4); b = 255; } else if (idx < 128) { r = 0; g = 255; b = (unsigned char)(255 - (idx - 64) * 4); } else if (idx < 192) { r = (unsigned char)((idx - 128) * 4); g = 255; b = 0; } else { r = 255; g = (unsigned char)(255 - (idx - 192) * 4); b = 0; } } int dst = (IR_H - 1 - y) * IR_W * 3 + x * 3; px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r; } } if (hot >= 0) { g_max_x = hot % IR_W; g_max_y = hot / IR_W; g_max_temp = 25.0 + ((int)hotv - (int)g_reference[hot]) / 147.0; int mx2 = g_max_x, my2 = IR_H - 1 - g_max_y; for (int k = -2; k <= 2; ++k) { if (mx2 + k >= 0 && mx2 + k < IR_W) { int d2 = my2 * IR_W * 3 + (mx2 + k) * 3; px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255; } if (my2 + k >= 0 && my2 + k < IR_H) { int d2 = (my2 + k) * IR_W * 3 + mx2 * 3; px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255; } } } g_center_temp = 25.0 + ((int)(center_sum / 400) - (int)g_reference[50 * IR_W + 70]) / 147.0; } static HWND g_hwnd; static HFONT g_font; static void set_status(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(g_status, sizeof(g_status), fmt, ap); va_end(ap); InvalidateRect(g_hwnd, NULL, TRUE); } static void save_bmp(void) { char path[MAX_PATH]; SYSTEMTIME st; GetLocalTime(&st); snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); FILE *f = fopen(path, "wb"); if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); set_status("saved %s", path); } } static void do_ffc(void) { sendcmd(MAG_CMD_FFC, 1, 8); Sleep(300); sendcmd(MAG_CMD_FFC, 0, 8); set_status("FFC triggered"); } static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC dc = BeginPaint(hw, &ps); HDC mem = CreateCompatibleDC(dc); HBITMAP bm = CreateCompatibleBitmap(dc, IR_W, IR_H); HGDIOBJ old = SelectObject(mem, bm); SetDIBitsToDevice(mem, 0, 0, IR_W, IR_H, 0, 0, 0, IR_H, g_bmp + 54, (BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS); StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, IR_W, IR_H, SRCCOPY); SelectObject(mem, old); DeleteObject(bm); DeleteDC(mem); SelectObject(dc, g_font); SetBkMode(dc, TRANSPARENT); SetTextColor(dc, RGB(220, 220, 220)); int y = 20; char line[256]; snprintf(line, sizeof(line), "frame : %u (type %u)", g_frame_count, g_frame_type); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; snprintf(line, sizeof(line), "fps : %.1f", g_fps); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; if (g_probe_x >= 0) { double t = 25.0 + ((int)(g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2] | (g_frame[g_probe_y * IR_W * 2 + g_probe_x * 2 + 1] << 8)) - (int)g_reference[g_probe_y * IR_W + g_probe_x]) / 147.0; snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; } if (g_max_x >= 0) { snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; } snprintf(line, sizeof(line), "center: %.2f C", g_center_temp); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; snprintf(line, sizeof(line), "mode : %s", g_diff_mode ? "DIFF" : "absolute"); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; SetTextColor(dc, RGB(120, 220, 120)); TextOutA(dc, 10, 500, g_status, (int)strlen(g_status)); EndPaint(hw, &ps); break; } case WM_LBUTTONDOWN: { int x = LOWORD(lp), y = HIWORD(lp); if (x >= 10 && x < 650 && y >= 10 && y < 490) { g_probe_x = (x - 10) * IR_W / 640; g_probe_y = IR_H - 1 - (y - 10) * IR_H / 480; InvalidateRect(hw, NULL, TRUE); } break; } case WM_COMMAND: switch (LOWORD(wp)) { case 1001: do_ffc(); break; case 1002: save_bmp(); break; case 1003: g_diff_mode = !g_diff_mode; break; case 1004: for (int i = 0; i < FRAME_LEN; i += 2) g_reference[i / 2] = (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8)); g_has_reference = 1; g_diff_mode = 1; set_status("reference captured"); break; case 1005: g_probe_x = -1; InvalidateRect(hw, NULL, TRUE); break; } break; case WM_ERASEBKGND: return 1; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hw, msg, wp, lp); } static int open_camera(void) { p_usb_init(); p_usb_find_busses(); p_usb_find_devices(); for (usb_bus *bus = p_usb_get_busses(); bus; bus = bus->next) { for (usb_device *dev = bus->devices; dev; dev = dev->next) { if (dev->descriptor.idVendor == 0x833c) { g_dev = p_usb_open(dev); if (g_dev) { p_usb_set_configuration(g_dev, 1); if (p_usb_claim_interface(g_dev, 0) == 0) return 0; p_usb_close(g_dev); g_dev = NULL; } } } } return -1; } static int init_camera(void) { for (int attempt = 0; attempt < 4; ++attempt) { if (g_log) { fprintf(g_log, "init attempt %d\n", attempt + 1); fflush(g_log); } if (open_camera() != 0) { Sleep(2000); continue; } if (sendcmd(MAG_CMD_PREPARE1, 0, 4)) { Sleep(2000); continue; } if (sendcmd(MAG_CMD_PREPARE2, 0, 4)) { Sleep(2000); continue; } if (sendcmd(MAG_CMD_GET_INFO, 0, 4)) { Sleep(2000); continue; } if (sendcmd(MAG_CMD_FFC, 0, 8)) { Sleep(2000); continue; } Sleep(100); if (sendcmd(MAG_CMD_FFC, 0, 8)) { Sleep(2000); continue; } Sleep(300); if (sendcmd(MAG_CMD_START, 0, 4)) { Sleep(2000); continue; } Sleep(700); if (g_log) { fprintf(g_log, "init ok (attempt %d)\n", attempt + 1); fflush(g_log); } return 0; } return -1; } int WINAPI WinMain(HINSTANCE inst, HINSTANCE hprev, LPSTR cmd, int show) { (void)hprev; (void)cmd; (void)show; g_log = fopen("C:/Users/ZXC/AppData/Local/Temp/opencode/demo_usb0_log.txt", "w"); if (g_log) { fprintf(g_log, "demo start\n"); fflush(g_log); } HMODULE l0 = LoadLibraryA("libusb0.dll"); if (!l0) { MessageBoxA(NULL, "libusb0.dll not found", "MAG160C Demo", MB_ICONERROR); return 1; } p_usb_init = (fn_usb_init)GetProcAddress(l0, "usb_init"); p_usb_find_busses = (fn_usb_find_busses)GetProcAddress(l0, "usb_find_busses"); p_usb_find_devices = (fn_usb_find_devices)GetProcAddress(l0, "usb_find_devices"); p_usb_get_busses = (fn_usb_get_busses)GetProcAddress(l0, "usb_get_busses"); p_usb_open = (fn_usb_open)GetProcAddress(l0, "usb_open"); p_usb_set_configuration = (fn_usb_set_configuration)GetProcAddress(l0, "usb_set_configuration"); p_usb_claim_interface = (fn_usb_claim_interface)GetProcAddress(l0, "usb_claim_interface"); p_usb_bulk_write = (fn_usb_bulk_write)GetProcAddress(l0, "usb_bulk_write"); p_usb_bulk_read = (fn_usb_bulk_read)GetProcAddress(l0, "usb_bulk_read"); p_usb_close = (fn_usb_close)GetProcAddress(l0, "usb_close"); if (init_camera() != 0) { MessageBoxA(NULL, "camera init failed", "MAG160C Demo", MB_ICONERROR); return 1; } WNDCLASSA wc = {0}; wc.lpfnWndProc = wndproc; wc.hInstance = inst; wc.lpszClassName = "Mag160cDemoUsb0"; wc.hCursor = LoadCursor(NULL, IDC_CROSS); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); RegisterClassA(&wc); g_hwnd = CreateWindowA("Mag160cDemoUsb0", "MAG160C Thermal Demo (libusb0)", WS_OVERLAPPEDWINDOW, 60, 40, 1000, 600, NULL, NULL, inst, NULL); g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, "Consolas"); CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL); CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL); CreateWindowA("BUTTON", "Diff mode", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 670, 200, 140, 28, g_hwnd, (HMENU)1003, inst, NULL); CreateWindowA("BUTTON", "Set reference", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 670, 240, 140, 32, g_hwnd, (HMENU)1004, inst, NULL); CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 820, 240, 100, 32, g_hwnd, (HMENU)1005, inst, NULL); unsigned sz = 54 + IR_W * IR_H * 3; g_bmp[0] = 'B'; g_bmp[1] = 'M'; g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8); g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24); g_bmp[10] = 54; g_bmp[14] = 40; g_bmp[18] = IR_W; g_bmp[19] = 0; g_bmp[22] = IR_H; g_bmp[23] = 0; g_bmp[26] = 1; g_bmp[28] = 24; ShowWindow(g_hwnd, SW_SHOW); set_status("starting..."); unsigned prevcnt = 0xffffffff; int frames = 0, ffc_done = 0; unsigned long long ref_sum[IR_W * IR_H] = {0}; int ref_n = 0; DWORD t0 = GetTickCount(); DWORD last_fps_t = t0; unsigned last_fps_cnt = 0; MSG msg; for (;;) { while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) goto done; TranslateMessage(&msg); DispatchMessage(&msg); } unsigned char hdr[64]; int xfer = p_usb_bulk_read(g_dev, 0x81, (char *)hdr, sizeof(hdr), 100); if (xfer < 28) continue; unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) | ((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24); if (m != 0x1bb1b11b) continue; unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) | ((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24); if (c == prevcnt) continue; prevcnt = c; xfer = p_usb_bulk_read(g_dev, 0x81, (char *)g_frame, sizeof(g_frame), 100); if (xfer < FRAME_LEN) continue; frames++; if (!ffc_done && frames == 3) { sendcmd(MAG_CMD_FFC, 1, 8); /* switch to type=0 (temperature) */ ffc_done = 1; Sleep(400); continue; } g_frame_count++; g_frame_type = (unsigned)hdr[12]; if (!g_has_reference && frames >= 150 && ref_n < 30) { for (int i = 0; i < FRAME_LEN; i += 2) ref_sum[i / 2] += (unsigned short)(g_frame[i] | (g_frame[i + 1] << 8)); ref_n++; if (ref_n == 30) { for (int i = 0; i < IR_W * IR_H; ++i) g_reference[i] = (unsigned short)(ref_sum[i] / 30); g_has_reference = 1; set_status("reference captured - hand shows red"); } } else if (g_has_reference) { for (int i = 0; i < FRAME_LEN; i += 2) { unsigned v = (unsigned)g_frame[i] | ((unsigned)g_frame[i + 1] << 8); unsigned r = g_reference[i / 2]; g_reference[i / 2] = (unsigned short)((r * 199 + v) / 200); } } render(g_frame); DWORD now = GetTickCount(); if (now - last_fps_t >= 1000) { g_fps = (g_frame_count - last_fps_cnt) * 1000.0 / (now - last_fps_t); last_fps_t = now; last_fps_cnt = g_frame_count; } InvalidateRect(g_hwnd, NULL, FALSE); UpdateWindow(g_hwnd); } done: sendcmd(MAG_CMD_STOP, 0, 4); p_usb_close(g_dev); return 0; }