1// Phase 0 sniffer: open an MX Creative Console half by product-name match and
2// log every HID input report. Operate the control you want to map and watch the
3// report id + bytes. Logitech HID++ events arrive as report id 0x10 (short, 7B)
4// or 0x11 (long, 20B). Default mouse/consumer reports use other ids.
5//
6// node examples/hid-sniff.ts [name-substring] [seconds]
7// node examples/hid-sniff.ts dialpad 25
8const { devices, HID } = await import("node-hid");
9
10const match = (process.argv[2] ?? "dialpad").toLowerCase();
11const durationSec = Number(process.argv[3] ?? 0);
12
13const hex = (n: number, w = 2) => n.toString(16).padStart(w, "0");
14
15const dev = devices().find(
16 (d) => (d.product ?? "").toLowerCase().includes(match) && d.path,
17);
18if (!dev?.path) {
19 console.error(
20 `No HID device matching "${match}". Run examples/enumerate-hid.ts to list.`,
21 );
22 process.exit(1);
23}
24
25console.info(
26 `Opening ${dev.product} 0x${hex(dev.vendorId, 4)}:0x${hex(dev.productId, 4)}\n path=${dev.path}`,
27);
28
29let device: import("node-hid").HID;
30try {
31 device = new HID(dev.path);
32} catch (error) {
33 console.error(
34 "Failed to open device. On macOS, grant the terminal/node Input Monitoring\n"
35 + "(System Settings > Privacy & Security > Input Monitoring), then retry.\n",
36 error,
37 );
38 process.exit(1);
39}
40
41const t0 = Date.now();
42let count = 0;
43device.on("data", (buf: Buffer) => {
44 const bytes = [...buf];
45 const id = bytes[0];
46 const kind = id === 0x11
47 ? "hid++ long "
48 : id === 0x10
49 ? "hid++ short"
50 : "report ";
51 const ms = String(Date.now() - t0).padStart(6);
52 console.info(
53 `+${ms}ms ${kind} id=0x${hex(id)} ${bytes.map((b) => hex(b)).join(" ")}`,
54 );
55 count += 1;
56});
57device.on("error", (error) => console.error("device error:", error));
58
59console.info("Listening — operate the dial / knob / buttons. Ctrl-C to stop.\n");
60
61if (durationSec > 0) {
62 setTimeout(() => {
63 console.info(`\nCaptured ${count} reports in ${durationSec}s. Closing.`);
64 device.close();
65 process.exit(0);
66 }, durationSec * 1000);
67}