/* Real-time thermal viewer: Win32 window showing live 160x120 frames with pseudo-color. Esc or close window to stop. */ #define _WIN32_WINNT 0x0601 #include #include #include static libusb_device_handle *g_h; static unsigned char g_frame[40000]; static CRITICAL_SECTION g_lock; static volatile int g_new_frame; static volatile int g_running; static unsigned char g_bmp[54 + 160 * 120 * 3]; static char g_title[128]; static int sendcmd(libusb_device_handle *h, 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; int rc = libusb_bulk_transfer(h, 0x03, cmd, len, &xfer, 2000); if (rc) return rc; unsigned char resp[0x1000]; rc = libusb_bulk_transfer(h, 0x82, resp, sizeof(resp), &xfer, 2000); return rc; } static void render(const unsigned char *data, unsigned mn, unsigned mx) { unsigned char *px = g_bmp + 54; for (int y = 0; y < 120; ++y) { for (int x = 0; x < 160; ++x) { int o = (y * 160 + x) * 2 + 28; unsigned v = (unsigned)data[o] | ((unsigned)data[o+1] << 8); unsigned idx = (v - mn) * 255 / (mx - mn + 1); if (idx > 255) idx = 255; unsigned char r, g, b; 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 = (119 - y) * 160 * 3 + x * 3; px[dst+0] = b; px[dst+1] = g; px[dst+2] = r; } } } static DWORD WINAPI reader_thread(LPVOID p) { (void)p; unsigned char hdr[64]; unsigned prev = 0xffffffff; int frames = 0; int ffc_done = 0; while (g_running) { int xfer = 0; if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 2000) || 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 == prev) continue; prev = c; if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 2000) || xfer < 38400) continue; frames++; /* switch to type=0 after the 3rd complete frame (device is streaming) */ if (!ffc_done && frames == 3) { sendcmd(g_h, 0x6bb6b672, 1, 8); ffc_done = 1; Sleep(400); continue; } unsigned mn = 0xffff, mx = 0; for (int k = 0; k < 38400; k += 2) { unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8); if (v < mn) mn = v; if (v > mx) mx = v; } EnterCriticalSection(&g_lock); render(g_frame, mn, mx); snprintf(g_title, sizeof(g_title), "MAG160C thermal live - frame %u type=%u range [%u..%u]", c, (unsigned)hdr[12], mn, mx); g_new_frame = 1; LeaveCriticalSection(&g_lock); } return 0; } 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, 160, 120); HGDIOBJ old = SelectObject(mem, bm); SetDIBitsToDevice(mem, 0, 0, 160, 120, 0, 0, 0, 120, g_bmp + 54, (BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS); StretchBlt(dc, 0, 0, 640, 480, mem, 0, 0, 160, 120, SRCCOPY); SelectObject(mem, old); DeleteObject(bm); DeleteDC(mem); EndPaint(hw, &ps); break; } case WM_ERASEBKGND: return 1; case WM_KEYDOWN: if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; } break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hw, msg, wp, lp); } int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { (void)prev; (void)cmd; (void)show; libusb_context *ctx = NULL; libusb_init(&ctx); libusb_device **list = NULL; ssize_t cnt = libusb_get_device_list(ctx, &list); for (ssize_t i = 0; i < cnt && !g_h; ++i) { struct libusb_device_descriptor d; libusb_get_device_descriptor(list[i], &d); if (d.idVendor == 0x833c) libusb_open(list[i], &g_h); } if (!g_h) { MessageBoxA(NULL, "no MAG160C device found", "MAG160C", MB_ICONERROR); return 1; } libusb_set_configuration(g_h, 2); libusb_set_configuration(g_h, 1); libusb_claim_interface(g_h, 0); sendcmd(g_h, 0x6bb6b66b, 0, 4); sendcmd(g_h, 0x6bb6b66c, 0, 4); sendcmd(g_h, 0x6bb6b66f, 0, 4); sendcmd(g_h, 0x6bb6b672, 0, 8); sendcmd(g_h, 0x6bb6b672, 0, 8); Sleep(300); sendcmd(g_h, 0x6bb6b673, 0, 4); Sleep(500); /* FFC(1) is issued from the reader thread after the 3rd frame */ WNDCLASSA wc = {0}; wc.lpfnWndProc = wndproc; wc.hInstance = inst; wc.lpszClassName = "ThermalView"; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClassA(&wc); HWND hw = CreateWindowA("ThermalView", "MAG160C thermal live", WS_OVERLAPPEDWINDOW, 100, 100, 656, 520, NULL, NULL, inst, NULL); /* BMP header */ unsigned sz = 54 + 160*120*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]=160; g_bmp[19]=0; g_bmp[22]=120; g_bmp[23]=0; g_bmp[26]=1; g_bmp[28]=24; InitializeCriticalSection(&g_lock); g_running = 1; ShowWindow(hw, SW_SHOW); /* single-threaded: read one frame, render, pump messages */ int nread = 0; unsigned prev2 = 0xffffffff; int ffc2 = 0; for (;;) { MSG msg; 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 = 0; if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) continue; 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 == prev2) continue; prev2 = c; if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) || xfer < 38400) continue; nread++; if (!ffc2 && nread == 3) { sendcmd(g_h, 0x6bb6b672, 1, 8); ffc2 = 1; Sleep(400); continue; } unsigned hist[65536] = {0}; unsigned nz = 0; for (int k = 0; k < 38400; k += 2) { unsigned v = (unsigned)g_frame[k] | ((unsigned)g_frame[k+1] << 8); if (v == 0) continue; hist[v]++; nz++; } unsigned mn = 0, mx = 0; unsigned acc = 0; unsigned lo = (unsigned)(nz * 2 / 100); unsigned hi = (unsigned)(nz * 98 / 100); for (unsigned k = 0; k < 65536; ++k) { acc += hist[k]; if (acc >= lo && mn == 0) mn = k; if (acc >= hi && mx == 0) { mx = k; break; } } if (mx <= mn + 50) { mn = 0; mx = 65535; } EnterCriticalSection(&g_lock); render(g_frame, mn, mx); snprintf(g_title, sizeof(g_title), "MAG160C thermal live - frame %u type=%u range [%u..%u]", c, (unsigned)hdr[12], mn, mx); LeaveCriticalSection(&g_lock); SetWindowTextA(hw, g_title); InvalidateRect(hw, NULL, FALSE); UpdateWindow(hw); } } done:; g_running = 0; g_running = 0; Sleep(300); libusb_clear_halt(g_h, 0x03); libusb_clear_halt(g_h, 0x82); sendcmd(g_h, 0x6bb6b674, 0, 4); libusb_close(g_h); libusb_exit(ctx); return 0; }