Overview

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.

Messages Browse & filterDecode Raw β†’ signalsEncode Signals β†’ rawProgram Notebook + closed-loopPlot Live monitorConvert candump ↔ cansendChangelog Version history

Getting Started

1. Load a Config File

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.

2. Browse Messages

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.

3. Use Decode / Encode / Program / Plot

Navigate to any tool page using the nav bar. The loaded configs are shared across all pages in the same session.

Page Reference

Browse all loaded message definitions.

  • Search by name or description
  • Filter by device using the dropdown
  • Toggle messages on/off β€” disabled messages are skipped during decoding
  • Expand a message row to see its signals and configure filters:

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.

  • Enter the CAN ID in hex (e.g. 0x481 or 481)
  • Paste the payload bytes space-separated (e.g. FF 7F 66 26 66 06 00 08)
  • Click Decode β€” matching message name and all signal values are shown
  • Multi-node messages show the resolved node ID alongside the message name
  • MAVLink frames: check MAVLink mode to parse 29-bit extended IDs and MAVLink v2 framing

Build a raw CAN frame from signal values.

  • Select a message from the dropdown (loaded from configs)
  • For multi-node messages, set the target node ID
  • Fill in signal values β€” enum signals show a dropdown of named options
  • Click Encode to generate the frame
  • Output is shown as hex bytes and in cansend format (can0 481##1FF7F…)
  • Click Copy to copy the cansend command to clipboard
  • If you're connected to a bus, click Send to bus to transmit. Click + Sequence to push the current message into the Program page as a Send block.

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

  • Shares the same bus connection as the Plot and Encode pages. Connect once on any page and all three see live frames.
  • Use the Download server script button if you haven't set up the bridge yet β€” see Live Monitor Setup.

Statement types

  • Send β€” encode and transmit a CAN frame. Pick a message, fill values, set node id (or expression for multi-node). For messages with a broadcast id, tick broadcast to address all nodes in one frame β€” node tabs appear so you can edit each node's values independently.
  • Wait β€” sleep for N ms.
  • Repeat β€” run a body N times.
  • Every β€” fire a body on a periodic timer. Unset duration runs in the background while the rest of the sequence continues; set duration blocks for that many ms.
  • Sweep β€” auto-generate a series of Sends stepping one signal from from to to by step, one frame every period ms.
  • Group β€” labeled container for organisation. Top-level groups also act as notebook cells (see below).
  • Set β€” assign name = expr. Expressions support + - * / %, parentheses, hex/decimal/binary literals, and references to other variables.
  • Bind β€” continuous closed loop: 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.
  • Read β€” one-shot blocking variant of Bind: waits for the next matching frame (with a timeout), writes the value, then advances.

Variables & expressions

  • Any Send field can take an expression by prefixing with =: 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.
  • Forgetting the = in front of a variable name raises a friendly error (no silent NaN sends).
  • Variables persist across cell runs and Run All β€” only Reset vars clears them.

Notebook-style cells

  • Each top-level Group block gets its own β–Ά Run button (Jupyter-cell style).
  • Click β–Ά to run just that cell. Variables set in one cell are visible to the next.
  • Run All runs the whole sequence top to bottom; Stop cancels in-flight wait/every/read.
  • Reset vars wipes the vars map and drops all active bindings (use to start fresh).

Drag & rearrange

  • Click and drag any block (anywhere on the row) to a new position β€” before, after, or inside a container.
  • Shift+click a second block (same parent) to select a contiguous range; drag any of them to move the whole range together.
  • Esc clears the selection. ⎘ duplicates a block; Γ— removes it.

Live bindings panel

  • Appears under the run controls whenever a Bind has been executed.
  • Each row shows varName ← Msg.signal[@node] : lastValue Ns ago, updated on every matching frame.
  • Bindings re-resolve their nodeId expression once, when the Bind statement executes β€” re-run the cell to chase a moving target.

TX/RX in the Plot view

  • Frames the Program page transmits also appear in Plot, tagged TX in the status bar, raw log and chart tooltips so you can correlate command and response on one timeline.

Import / Export

  • Export saves the current sequence as JSON for sharing or version control.
  • Import loads a JSON file back into the page.

Signal plotter and live CAN monitor.

Paste mode

  • Paste candump -ta output directly into the text area
  • All decodable frames are parsed and signals are plotted over time
  • A raw frame log shows the original candump lines

Live mode

  • Download the server script (Download can_ws_server.py), run it on the machine with the CAN interface
  • Enter the WebSocket URL (default ws://localhost:8765) and click Connect
  • Frames stream in real time; use Pause to freeze collection without disconnecting

Chart controls

  • Signal selector β€” check/uncheck series; drag to group into panels
  • Views β€” Signals (line chart), Timeline (per-message event timing), Interval (per-signal delta-time)
  • Zoom & pan β€” scroll wheel zooms time; Shift+scroll zooms value; Ctrl/⌘+scroll zooms both. Left-drag pans, Shift+drag box-zooms, double-click fits to data. Pinch on touch zooms both axes.
  • Fit X / Fit Y / Fit β€” toolbar buttons. Fit resets both axes, Fit X auto-scales only the time axis (preserves Y zoom), Fit Y only the value axis.
  • Follow live (live mode only) β€” auto-scrolls the X axis with a rolling window. Adjust the window seconds inline. Manually zooming or panning pauses follow so you can investigate without the stream snapping you back; click Follow live again to resume.
  • Click a data point β€” copies the candump line (id + bytes + timestamp) to the clipboard
  • Timeline markers A/B β€” click Measure Ξ”t then pick two frames on the timeline to measure time delta
  • t=0 origin β€” click Set t=0 then pick a frame on the timeline to make it the time origin
  • Export Layout / Import Layout β€” save/restore panel arrangement as YAML
  • Save PNG β€” export all charts as a PNG image
  • Record to file β€” save raw frames to a .log file during live capture
  • Clear β€” reset all collected data

Tip: 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.

  • Paste candump output on the left
  • The right panel shows equivalent cansend commands
  • Useful for replaying captures or scripting test sequences

Live Monitor Setup

The 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.

Step 1 β€” Download the server script

On the Plot page, expand "Server setup guide" and click "Download can_ws_server.py". Copy it to the machine with the CAN interface.

Step 2 β€” Install requirements

# 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]"

Step 3 β€” USB adapter permissions (Linux, USB SLCAN/SLCANFD devices only)

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.py
  • serial.serialutil.SerialException: could not open port
  • ModemManager grabs the port for ~30 s on plug-in and replies to AT commands, corrupting the SLCAN protocol

1. 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
Troubleshooting:
  • Still "Permission denied" after logging out/in? Run id β€” if dialout isn't listed, the group change didn't apply (try a full reboot).
  • Adapter disconnects randomly mid-stream? ModemManager is still probing β€” confirm 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.
  • Multiple adapters plugged in? Use the SYMLINK+="usb2can" alias (or extend the rule with ATTRS{serial}==... to disambiguate per-serial-number).

Step 4 β€” Run the server

# 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

Step 5 β€” Connect in the browser

Go to the Plot page, enter ws://localhost:8765 in the URL field, and click Connect.

Browser security note: Some browsers block connections to 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.

Config File Reference

Device configs are YAML files that define CAN message layouts. Place them in the configs/ directory or load them directly in the browser.

Minimal example

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"

Signal fields

FieldRequiredDefaultDescription
nameyesβ€”Signal name used in encode/decode
start_bityesβ€”Bit offset in the payload
bit_lengthyesβ€”Number of bits
byte_ordernolittle_endianlittle_endian or big_endian
value_typenounsignedunsigned Β· signed Β· float32 Β· float64
scaleno1.0physical = raw Γ— scale + offset
offsetno0.0See scale
min / maxnoβ€”Physical range (used with linear_map)
linear_mapnofalseAuto-calculate scale/offset from min/max
unitno""Display unit string
defaultnoβ€”Default value when encoding (omitted signals)
constantnofalseAlways use default; user cannot override
enumnoβ€”Map of int β†’ name for named values
bitfieldnoβ€”Map of bit_position β†’ flag_name

Multi-node messages

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

MAVLink XML

Load standard or custom MAVLink XML files the same way as YAML. The codec maps MAVLink message IDs to CAN frames automatically.

CLI Reference

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