Complete guide to CAN Codec web interface
CAN Codec is a browser-based tool for encoding and decoding CAN/CAN-FD messages. Load your device's YAML or MAVLink XML configuration file, then use the web interface to browse message definitions, decode raw frames, encode signal values, and monitor live CAN data.
All processing runs in the browser β no data leaves your machine.
Click "+ Files" in the navbar to upload one or more .yaml / .yml / .xml config files. You can also click "+ Folder" to load an entire directory at once.
If you don't have a config file yet, use a built-in template on the Messages page. Templates include example motor controller definitions.
After loading, the Messages page lists all decoded message definitions grouped by device. Each row shows the CAN ID, message name, direction (TX/RX), DLC, and description.
Navigate to any tool page using the nav bar. The loaded configs are shared across all pages in the same session.
Browse all loaded message definitions.
Node filter β for multi-node messages, enable/disable individual node IDs
Enum value filter β for signals with named values (e.g. register_id), show only specific values
Signal filter β hide individual signals from decode output
A filtered badge appears on messages with active filters.
Decode a single raw CAN frame into human-readable signal values.
0x481 or 481)FF 7F 66 26 66 06 00 08)Build a raw CAN frame from signal values.
can0 481##1FF7Fβ¦)Build and run multi-step CAN command sequences (a small AST of statements). Mix open-loop sends with closed-loop signal bindings to react to live telemetry.
Connection
Statement types
from to to by step, one frame every period ms.name = expr. Expressions support + - * / %, parentheses, hex/decimal/binary literals, and references to other variables.varName β Msg.signal. Each matching RX frame decodes the signal's physical value and writes it to the variable. Persists across runs until the binding is replaced or Reset vars is clicked.Variables & expressions
=: e.g. position = =target_pos * 2.nodeId on Send, Bind and Read accepts the same syntax β handy for closed-loop control of one motor out of many: set motor_idx = 2; bind pos β Telemetry.position @=motor_idx.= in front of a variable name raises a friendly error (no silent NaN sends).Notebook-style cells
Drag & rearrange
Live bindings panel
varName β Msg.signal[@node] : lastValue Ns ago, updated on every matching frame.nodeId expression once, when the Bind statement executes β re-run the cell to chase a moving target.TX/RX in the Plot view
Import / Export
Signal plotter and live CAN monitor.
Paste mode
candump -ta output directly into the text areaLive mode
ws://localhost:8765) and click ConnectChart controls
.log file during live captureTip: the ? button next to Clear on the Plot page re-shows the gesture cheat-sheet if you've dismissed it.
Convert between candump -ta timestamped format and cansend replay format.
cansend commandsThe Plot page can receive real-time CAN frames via WebSocket. You need to run a small bridge server on the machine that has the CAN interface.
On the Plot page, expand "Server setup guide" and click "Download can_ws_server.py". Copy it to the machine with the CAN interface.
# SocketCAN (Linux built-in driver β most common setup)
sudo apt install can-utils # provides candump β no pip needed
# USB adapters (SLCAN, PCAN, gs_usb python-can backend)
pip install python-can>=4.0 pyserial>=3.5
# or via the package:
pip install "canfd-codec[serve]" If you're using a USB-to-CAN(FD) adapter such as CANable v2 / USB2SLCANFD, Linux by default exposes it as a /dev/ttyACM* device that only root can open. Skip this step if you only use built-in SocketCAN (can0, vcan0) β those don't go through /dev/tty*.
Symptoms of missing permission:
[Errno 13] Permission denied: '/dev/ttyACM0' when starting can_ws_server.pyserial.serialutil.SerialException: could not open port1. Identify your adapter's USB IDs. Plug it in and run:
lsusb | grep -i -E 'can|stm|cdc'
# Example output for CANable v2:
# Bus 001 Device 005: ID 16d0:117e MCS CANable2 Note the idVendor:idProduct pair (here 16d0:117e). Common USB2SLCANFD adapters: 16d0:117e (CANable v2 / canable.io), 1d50:606f (gs_usb / CANable v1).
2. Create a udev rule at /etc/udev/rules.d/99-canable2.rules:
# /etc/udev/rules.d/99-canable2.rules
# CANable2 / USB-to-CAN-FD adapter β set group + permissions, stop ModemManager hijack
SUBSYSTEM=="usb", ATTR{idVendor}=="16d0", ATTR{idProduct}=="117e", ENV{ID_MM_DEVICE_IGNORE}="1"
SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="117e", \
GROUP="dialout", MODE:="0660", TAG+="uaccess", SYMLINK+="usb2can", ENV{ID_MM_DEVICE_IGNORE}="1" If your lsusb showed different IDs, replace the four idVendor/idProduct values with yours. The rule does four things: (1) sets group dialout and rw for owner/group (0660), (2) tags uaccess so the currently logged-in graphical user can access the port without group setup, (3) creates a stable /dev/usb2can symlink so you can write --bus /dev/usb2can instead of guessing ttyACM0 / ttyACM1, (4) sets ID_MM_DEVICE_IGNORE=1 so ModemManager doesn't probe the adapter.
3. Reload udev and replug the adapter:
sudo udevadm control --reload-rules
sudo udevadm trigger
# then unplug and replug the USB cable 4. Make sure your user is in the dialout group (needed only if your distro doesn't honour TAG+="uaccess"):
sudo usermod -aG dialout $USER
# Then log out and log back in for the new group to take effect.
groups # verify "dialout" appears 5. Verify permissions:
ls -l /dev/usb2can /dev/ttyACM*
# Expected: crw-rw---- 1 root dialout ... /dev/ttyACM0
# lrwxrwxrwx 1 root root ... /dev/usb2can -> ttyACM0 id β if dialout isn't listed, the group change didn't apply (try a full reboot).ID_MM_DEVICE_IGNORE=1 applied with udevadm info -a /dev/ttyACM0 | grep ID_MM. As a last resort, disable it entirely: sudo systemctl disable --now ModemManager.SYMLINK+="usb2can" alias (or extend the rule with ATTRS{serial}==... to disambiguate per-serial-number).# SocketCAN interface
python3 can_ws_server.py --bus can0
# Virtual CAN (for testing)
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan && sudo ip link set up vcan0
python3 can_ws_server.py --bus vcan0
# USB SLCAN adapter (CAN FD) β use the /dev/usb2can symlink from the udev rule above
python3 can_ws_server.py --bus /dev/usb2can --interface slcan --bitrate 1000000 --data-bitrate 5000000
# (or --bus /dev/ttyACM0 if you didn't add the SYMLINK)
# LAN relay β run on remote CAN machine, re-serve on this PC
python3 can_ws_server.py --source ws://192.168.x.x:8765 Go to the Plot page, enter ws://localhost:8765 in the URL field, and click Connect.
localhost from HTTPS pages. If you are using the hosted web app, either run it locally (npm run dev) or use the relay mode from a local instance.Device configs are YAML files that define CAN message layouts. Place them in the configs/ directory or load them directly in the browser.
device:
name: "My Device"
bus: "can0"
messages:
- id: 0x100
name: "SpeedCommand"
dlc: 4
signals:
- name: "speed"
start_bit: 0
bit_length: 16
scale: 0.1
unit: "rpm"
- name: "direction"
start_bit: 16
bit_length: 8
enum:
0: "stop"
1: "forward"
2: "reverse" | Field | Required | Default | Description |
|---|---|---|---|
| name | yes | β | Signal name used in encode/decode |
| start_bit | yes | β | Bit offset in the payload |
| bit_length | yes | β | Number of bits |
| byte_order | no | little_endian | little_endian or big_endian |
| value_type | no | unsigned | unsigned Β· signed Β· float32 Β· float64 |
| scale | no | 1.0 | physical = raw Γ scale + offset |
| offset | no | 0.0 | See scale |
| min / max | no | β | Physical range (used with linear_map) |
| linear_map | no | false | Auto-calculate scale/offset from min/max |
| unit | no | "" | Display unit string |
| default | no | β | Default value when encoding (omitted signals) |
| constant | no | false | Always use default; user cannot override |
| enum | no | β | Map of int β name for named values |
| bitfield | no | β | Map of bit_position β flag_name |
For systems with multiple identical devices (e.g. 7 motors on IDs 0x481β0x487):
- id: 0x480 # base ID
name: "PositionControl"
node_count: 7 # 7 nodes
node_id_offset: 1 # ID step per node
node_id_start: 1 # first valid node_id (nodes 1β7)
# actual IDs: 0x481, 0x482, ..., 0x487 Load standard or custom MAVLink XML files the same way as YAML. The codec maps MAVLink message IDs to CAN frames automatically.
Install the Python package to use the CLI. The -c flag must come before the subcommand.
pip install canfd-codec
canfd-codec -c ./configs list
canfd-codec -c ./configs describe MITControl
canfd-codec -c ./configs decode 0x481 "FF 7F 66 26 66 06 00 08"
canfd-codec -c ./configs encode MITControl position=1.57 velocity=2.0 --node 1
canfd-codec -c ./configs encode MITControl position=1.57 --node 1 --cansend
canfd-codec -c ./configs monitor --bus can0
canfd-codec -c ./configs monitor --bus can0 --summary # live table
# Start WebSocket server for web UI live monitor
canfd-codec -c ./configs serve --bus can0
canfd-codec -c ./configs serve --bus /dev/ttyACM0 --interface slcan --bitrate 1000000