CAN Bus Monitor
Built a real-time web dashboard for monitoring, debugging, and visualizing CAN bus networks with live frame logging, topology discovery, connection tracking, and hybrid node communication.

A CAN bus doesn't hand you logs — it hands you bytes, all of them on one shared wire, with nothing built in to show who's talking to whom. can-monitor turns a handful of Arduino + MCP2515 boards into that missing view: a browser tab that watches every node on the bus live, draws the network as it forms, and — because the same wire that carries diagnostics can carry anything — lets two nodes hold an actual text conversation over CAN frames.
Six microcontrollers, one browser tab
There's no backend and no driver to install. SerialManager opens each node through the browser's own Web Serial API — navigator.serial.requestPort() — and keeps every connection in a plain Map keyed by port id, reading and multiplexing several physical nodes at once from one client component. Each reader runs its own loop, buffers partial chunks across reads so a line split mid-frame still parses correctly, and forwards complete lines up to the page. The whole app is Next.js 15 (App Router) and React 19 in TypeScript, with D3 driving the topology view and Tailwind for the instrument-panel styling — but the part doing the actual work, reading physical hardware from a tab with no extension and no local server, is just what the browser already ships.
A protocol simple enough to read over someone’s shoulder
Every node runs the same sketch with a different mode compiled in — LOOPBACK, SENDER, RECEIVER, SNIFFER, OBD, or HYBRID — and all of them speak one line-based, pipe-delimited protocol over serial. A node announces itself once at boot, then narrates everything: each CAN frame it sends or receives, a heartbeat every two seconds, a frame count every five. Nothing here needs a binary parser or a spec sheet; it reads like a log because it's built to be one.
NODE|HYBRID|HYBRID
HB|SENT|HYBRID|148
CAN|TX|321|6|48 45 4c 4c 4f 00|HELLO
CAN_PEER|ID:321
STAT|FRAMES|12parser.tsx turns each of those lines into one typed CanFrame — node, direction, CAN id, data length, the raw hex bytes, and a best-effort ASCII decode — and that single shape is what every panel in the UI (the log view, the node inspector, the graph) reads from.
A graph that forgets who stopped talking
CanGraph runs a live D3 force simulation — forceLink, forceManyBody, forceCenter, forceCollide — over whatever nodes and edges have been discovered so far, so the topology settles into a real layout instead of a fixed grid. Edges only exist between HYBRID nodes that have actually exchanged a CAN_PEER frame, not just any two nodes that happen to be connected.
An edge is pruned three seconds after its last CAN_PEER frame. The graph shows who is talking right now, not a history of who ever talked — stop a conversation and the line between those two nodes disappears within seconds.
Chat, but every message is CAN traffic
HYBRID mode doubles as a demo of what the bus can actually carry. Select two hybrid nodes in the sidebar and a chat box unlocks; typed text is handed to sendStringOverCAN() on the sending node, which slices it into 8-byte payloads on CAN id 0x321 — the standard frame size limit — and the far side's HYBRID firmware reassembles it back into a message. It's a small proof, but a real one: the exact same 8 bytes per frame that carry a heartbeat or an OBD-II response can just as easily carry a sentence.
Select two hybrid nodes and the diagnostics tool becomes a walkie-talkie — the bus never knew the difference.
The same rig reads a real engine
Swap a node into OBD mode and the identical wire protocol carries something else entirely: obd.ino decodes standard OBD-II PIDs off the bus — RPM, vehicle speed, engine load, throttle position, coolant and intake air temperature, fuel level, short- and long-term fuel trim, control module voltage, runtime since start, distance since codes were cleared — into the same CAN| lines the browser already knows how to parse. Adding a real diagnostics source meant writing new firmware, not touching the frontend at all.
What’s rough
This started as a take-home assignment (for IGT), not a shipped product, and it shows in a couple of honest places. The Web Serial API is Chromium-only — no Firefox, no Safari — so it only runs where that API exists at all. Every node, edge, and buffered frame lives in React state with no persistence layer behind it: refresh the tab and the topology is gone, rebuilt from whatever the connected nodes announce next. Each port also only keeps its most recent 100 log lines and CAN frames, by design — this is a live instrument, not a black-box recorder.
Not a product — a working instrument for a bus that was never built to be watched.
