CAN signal maps as code.
One YAML file → documentation, DBC, C firmware code and Python test helpers.
Quick start · Installation · Outputs · Web editor · Commands · Map format · Development
mapcan creates and edits CAN signal maps for any device or system. The map lives in a single YAML file, which is the single source of truth: documents, firmware code and files for bus analyzers are all generated from it, so they never drift apart.
- Protocols — CANopen (IDs from the function code and
node_id), SAE J1939 (IDs from PGN, priority and address), CAN 2.0A/B with explicit 11/29-bit IDs, CAN FD with DLC up to 64. - Signals — signed and unsigned integers of any length and position, little- and big-endian;
enumerations, flags and bit fields; physical quantities with
factor,offset, unit and range; float32/float64; byte arrays; multiplexing; default and "not available" values. - Frames — cycle time, direction, sender and receivers, CANopen SDO exchanges. Each frame is described once, at its sender; what every device receives is derived for the editor, the documents (including an Excel communication matrix) and DBC.
- Validation — every error points to a file, line and column, before anything is generated.
mapcan: 1
document:
title: Electric vehicle example map
version: 0.1.0
language: en
buses:
- name: powertrain
bitrate: 500000
protocol: can
devices:
- name: bms
frames:
- name: bms_status
id: 0x100
length: 8
cycle_ms: 100
receivers: [vcu]
signals:
- {name: pack_voltage, byte: 0, encoding: float, unit: V, min: 0, max: 1000}
- {name: pack_current, byte: 4, encoding: float, unit: A, initial: 0}
- name: vcu
Quick start
mapcan app
The editor opens in its own window. Click New map… and pick a template — CANopen, J1939, CAN with explicit IDs or an empty map — or start from one of the examples on the start screen. Add devices, frames and signals in the tree, and draw signals with the mouse on the frame's bit layout. Errors show up as you type; Ctrl+S saves the file.
Prefer a text editor? Write the YAML by hand with completion and hints from the JSON Schema in VS Code — see docs/format.md.
Installation
| Platform | How |
|---|---|
| Windows | Download mapcan-X.Y.Z-setup.exe from Releases and run it. No Python or administrator rights needed. |
| Linux x64 | Download mapcan-X.Y.Z-linux-x64.tar.gz, extract it and run mapcan/mapcan-app (editor in the browser) or mapcan/mapcan (CLI). |
| Any OS with Python 3.12 or newer | uv tool install "mapcan[desktop]" or pipx install "mapcan[desktop]" (PyPI) |
| Docker | ghcr.io/ordu1453/mapcan:<version> — everything needed for CI |
The package is split into parts: pip install mapcan is the core (validation, DBC, EDS, Markdown,
HTML, C and Python code: enough for CI and firmware builds). Extras add the rest: web (the editor
in a browser), pdf, xlsx, lsp (the language server for VS Code), desktop (the app with all
its exports) and all. A command that needs a missing part says which one to install.
No external programs are required: PDFs are built by the bundled Typst compiler, and the PT Sans and PT Serif fonts (OFL) ship with the package.
Windows installer details
The installer adds mapcan to the Start menu and the desktop, to "Open with" for .yaml files and,
optionally, the mapcan command to PATH. Launching it again or double-clicking a map opens the
map in the window that is already running. A banner at the top of the editor announces new
versions; the update installs over the current one. A portable build is also available:
mapcan-X.Y.Z-windows-x64.zip, run mapcan-app.exe.
The installer is not code-signed, so SmartScreen may warn about an unknown publisher — click More info → Run anyway.
Offline install (corporate network without PyPI)
Build a wheelhouse on a machine with access and copy the folder over:
pip wheel . -w wheelhouse
pip install --no-index --find-links wheelhouse mapcan
Moving from canmap
The tool used to be called canmap. The Windows installer upgrades the old version by itself; with
uv/pipx run uv tool uninstall canmap, then install again. Settings move from ~/.canmap to
~/.mapcan, and the canmap PDF template is read as mapcan. Maps need the mapcan: 1 key
(see docs/format.md).
Outputs
| Output | Command |
|---|---|
| 🖥️ Web editor: tree, forms, bit layout, live validation | mapcan app / mapcan serve |
| ✅ Validation with file and line numbers | mapcan validate my-map.yaml |
| 📊 Excel: addressing, contents, bit layouts, changes | mapcan export xlsx my-map.yaml |
| 📄 PDF: title page, contents, bit diagrams, tables | mapcan export pdf my-map.yaml |
| 📝 Markdown for a README or wiki, HTML page with search | mapcan export md / mapcan export html |
| 🔌 DBC for CANalyzer, SavvyCAN, PCAN-View, Wireshark | mapcan export dbc my-map.yaml -o dbc/ |
| 🧩 EDS of each CANopen device: mapped objects and PDO parameters | mapcan export eds my-map.yaml -o eds/ |
⚙️ C: ID #defines, enums, masks, structs, pack/unpack |
mapcan gen c my-map.yaml -o firmware/ |
| 🐍 Python module for tests and test benches | mapcan gen py my-map.yaml -o tools/ |
| 📦 Everything at once, for CI | mapcan build my-map.yaml -o build/ |
| 🔀 Comparison of two map versions, changelog | mapcan diff old.yaml my-map.yaml |
| 📥 Start from an existing DBC | mapcan import dbc vehicle.dbc -o my-map.yaml |
Documents can be produced in English or Russian. The default comes from the map's
document.language; override it with --lang or the language switch in the editor. The ESKD
template (gost-2.105) is always produced in Russian.
mapcan import dbc turns DBC nodes into devices (the sender of a frame owns it), and brings over
frames with their IDs, length, cycle or send type and receivers, and signals with their layout,
scaling, units, ranges, value tables, initial values and multiplexing. A bus whose frames are
marked J1939 (VFrameFormat) becomes a j1939 bus with PGNs and device addresses; any other
becomes a can bus with explicit IDs. Value names become identifiers (Not available →
NOT_AVAILABLE, the original text goes to the description), and whatever DBC cannot express is
reported as a warning.
Generated C code
Each bus gets <bus>.h and <bus>.c with no dynamic memory and no libc beyond stdint.h,
stdbool.h and stddef.h. The code compiles cleanly with -Wall -Wextra -Wconversion for x86
and Cortex-M. For examples/vehicle-can.yaml:
#include "powertrain.h"
vcu_command_t cmd = {0};
cmd.contactor_request = VCU_COMMAND_CONTACTOR_REQUEST_CLOSE;
cmd.torque_request = vcu_command_torque_request_from_phys(120.5f); /* N·m → raw */
uint8_t buf[8];
int len = vcu_command_pack(buf, sizeof buf, &cmd); /* → VCU_COMMAND_ID */
bms_status_t st;
if (bms_status_unpack(&st, rx_data, rx_len) == 0 && st.pack_voltage > 350.0f) { ... }
Next to <FRAME>_ID and <FRAME>_LENGTH, the header tells the CAN driver how to send the frame:
<FRAME>_IS_EXTENDED for a 29-bit ID, and on a CAN FD bus <FRAME>_IS_FD and <FRAME>_BRS
(bit rate switch, 0 or 1). The classes of the Python module carry the same as IS_EXTENDED,
IS_FD and BITRATE_SWITCH, named after the arguments of python-can's can.Message:
msg = can.Message(arbitration_id=VcuCommand.ID, data=cmd.pack(), is_extended_id=VcuCommand.IS_EXTENDED,
is_fd=VcuCommand.IS_FD, bitrate_switch=VcuCommand.BITRATE_SWITCH)
Validation
Export and generation validate the map first and stop on errors (--force generates anyway).
validate exits with code 1 on errors, and with --strict on warnings too.
$ mapcan validate tests/data/invalid_map.yaml
tests/data/invalid_map.yaml:21:25: error[number-comma]: value '0,1': decimal comma used, should be 0.1
tests/data/invalid_map.yaml:24:17: error[number-cyrillic-x]: value '0х196': the 0x prefix uses a Cyrillic “х”
tests/data/invalid_map.yaml:33:22: error[invalid-identifier]: value name 'NO DATA': only Latin letters, digits and _ are allowed
tests/data/invalid_map.yaml:62:36: error[duplicate-signal-name]: flag 'dc_status' already exists in frame 'pcu_state' (signal)
...
FAIL: errors: 12, warnings: 2, notes: 4
mapcan rules lists every rule; any of them can be turned off with --disable CODE.
Web editor
mapcan app # own window; start screen: new map, open, recent files, examples
mapcan app my-map.yaml # open a map right away
mapcan serve my-map.yaml # the same in the browser
- Map tree with search by name, ID, PGN and description.
- Property forms that adapt to the bus protocol.
- Bit layout editor: drag across bits to add a signal, drag a signal to move it, drag its edge to resize; overlaps are highlighted.
- Value and flag tables, live validation and a YAML tab.
- Preview and download of every format.
- Comparison with any git revision, with the changelog written into the revision history.
Files are saved with their comments and formatting intact, so git diff shows only the lines you
changed. More in docs/web.md.
Examples
| File | What it shows |
|---|---|
| minimal.yaml | Every kind of signal in one CANopen frame: enum, bit field, quantities, big-endian |
| j1939.yaml | SAE J1939: engine (EEC1, ET1), instrument cluster, addressed PDU1 request, "not available" 0xFF/0xFFFF |
| canopen-node.yaml | CANopen drive: PDO signals mapped to CiA 402 and manufacturer objects, EDS export |
| tractor/ | A map in two files: include adds a J1939 powertrain bus, a device on the main file's bus and shared value tables |
| vehicle-can.yaml | Explicit 11/29-bit IDs: float32, multiplexed cell voltages, receivers, CAN FD with DLC 64 and float64 |
The same examples are on the editor's start screen: pick one, choose where to save a copy, and it opens.
Commands
mapcan app [FILE|DIR] [--browser] [--readonly]
mapcan serve [FILE|DIR] [--host 127.0.0.1] [--port 8765] [--readonly] [--no-browser] [--token T | --no-auth]
mapcan validate FILE [--strict] [--info] [--format json] [--disable CODE]
mapcan export xlsx FILE [-o OUT] [--style style.yaml] [--lang en]
mapcan export pdf FILE [-o OUT] [--template mapcan|gost-2.105|my.typ] [--font-path DIR] [--lang en]
mapcan export md FILE [-o OUT] [--template my.md.j2] [--lang en]
mapcan export html FILE [-o OUT] [--template my.html.j2] [--lang en]
mapcan export dbc FILE [-o DIR|file.dbc] [--bus NAME]
mapcan export eds FILE [-o DIR] [--bus NAME]
mapcan gen c FILE [-o DIR] [--prefix my_] [--bus NAME]
mapcan gen py FILE [-o DIR] [--bus NAME]
mapcan build FILE [-o DIR] [--formats xlsx,pdf,md,html,dbc,eds,c,py] [--pdf-template NAME]
mapcan diff OLD NEW [--format text|md|json]
mapcan import dbc FILE.dbc [-o FILE.yaml] [--title TEXT] [--lang en] [--force]
mapcan migrate FILE… [--check] # former field names → current ones
mapcan fmt FILE… [--check] # keys in one order, comments and layout kept
mapcan schema [-o schema.json] [--part] # --part: schema of an included file
mapcan template --list | pdf [--name gost-2.105] -o FILE | md|html|xlsx-style -o FILE
mapcan rules
Custom styling
| Format | How to customize |
|---|---|
Two built-in templates: mapcan (standard layout) and ESKD gost-2.105 (frame and title block). Copy one with mapcan template pdf --name gost-2.105 -o corp.typ, edit it, then export with --template corp.typ. Document details (designation, letter, signatures, approval blocks) and the company logo are set in document.print. See docs/templates.md. |
|
| Excel | mapcan template xlsx-style -o style.yaml, adjust fonts, colors and columns, then mapcan export xlsx map.yaml --style style.yaml. |
| Markdown / HTML | Jinja2 templates over the same data: mapcan template md -o my.md.j2. |
CI
docs/ci-example.md shows a pipeline for map repositories: when the YAML
changes, the map is validated and the PDF, Excel, DBC and C headers are rebuilt and kept as
artifacts. The Docker image ghcr.io/ordu1453/mapcan:<version> contains everything
needed.
Development
CONTRIBUTING.md covers the setup, tests and conventions; ARCHITECTURE.md explains how the code is organized and what to change to add a field to the format.
uv sync # environment from uv.lock (with every extra)
uv run pre-commit install # run ruff before every commit
uv run mapcan --help
uv run pytest
UPDATE_GOLDEN=1 uv run pytest # refresh reference files after intentional changes
The tests compile the generated C code with gcc (and arm-none-eabi-gcc if installed) and
compare pack/unpack byte for byte with the Python module and cantools for every example.
The UI lives in web/ (Vue 3 + TypeScript + Vite); npm run build there writes the bundle
to src/mapcan/web/static/.
Without uv
python -m venv .venv
.venv/Scripts/activate # Linux/macOS: source .venv/bin/activate
pip install -e ".[all]"
Releasing a version
- Bump the version in
pyproject.toml,src/mapcan/__init__.py,web/package.jsonandCITATION.cff(also itsdate-released) (uv lock,npm run buildinweb/), commit. - Create and push a tag:
git tag -a vX.Y.Z -m "mapcan X.Y.Z: …" && git push origin main vX.Y.Z.
release.yml runs the tests, checks that the tag matches the version, builds the Windows installer and zip (PyInstaller + Inno Setup), the wheel and the Docker image, and publishes a GitHub Release (the tag annotation becomes the release notes) and then the package on PyPI. Running the workflow by hand (Actions → release → Run workflow) makes a trial build without publishing; with Publish to TestPyPI checked, the package goes to TestPyPI to check the upload and the project page.
Building the Windows version locally:
uv sync --extra desktop --group package
uv run pyinstaller packaging/mapcan.spec --noconfirm # → dist/mapcan/
iscc /DAppVersion=X.Y.Z packaging/mapcan.iss # → dist/mapcan-X.Y.Z-setup.exe
Releasing the VS Code extension
- If the extension needs a newer language server, release mapcan first and raise
MIN_SERVERinvscode/src/version.ts. - In
vscode/:npm version X.Y.Z --no-git-tag-version; invscode/CHANGELOG.mdrename## Unreleasedto## X.Y.Z; commit. - Create and push a tag:
git tag -a extension-vX.Y.Z -m "mapcan for VS Code X.Y.Z" && git push origin main extension-vX.Y.Z. - Upload the
.vsixof the GitHub Release to the Visual Studio Marketplace: the first time New extension → Visual Studio Code, later ⋯ → Update at the extension.
extension-release.yml checks that the tag matches
vscode/package.json and that the changelog has the section, runs the typecheck and the integration
tests, packages the .vsix, publishes a GitHub Release with it and the changelog section, and then
the extension on Open VSX (secret OVSX_PAT,
namespace ordu1453). The Marketplace is published by hand (step 4): a token for it needs an Azure
DevOps organization, which needs an Azure subscription. With a secret VSCE_PAT (a token of the
publisher ordu1453 with the scope Marketplace → Manage) the workflow publishes there too; without
it that step only warns. Running the workflow by hand makes a trial .vsix (an artifact) without
releasing.
Repository layout
examples/ example maps (CANopen, J1939, CAN/CAN FD)
schema/mapcan.schema.json JSON Schema (mapcan schema -o …)
docs/ format, PDF templates, web editor, CI, logo
src/mapcan/
model.py data model (pydantic)
loader.py YAML loading with node positions
protocols.py, canopen.py CAN ID computation: CANopen, J1939, explicit IDs
resolve.py references, SDO layout
validate.py validation rules
bits.py bit layout, pack/unpack
docmodel.py shared view for documents
export/ xlsx, pdf (Typst), md/html (Jinja2), dbc
codegen/ C and Python
diff.py map version comparison, changelog
launcher.py mapcan app: window, single instance, port selection
updates.py new version check on GitHub Releases
web/ web editor: FastAPI, YAML operations, built UI (static/)
templates/ document templates (pdf/ — Typst)
fonts/ PT Sans, PT Serif (OFL)
web/ UI sources (Vue 3 + TypeScript + Vite)
src/form/ node forms shared with the VS Code form: fields, conditions, help from the schema
vscode/ VS Code extension (language server client, map tree, node form in webview/)
packaging/ Windows build: PyInstaller, Inno Setup installer, icon and logo
tests/ tests; data/ — fixtures, golden/ — reference files
Roadmap
- Import existing maps from DBC and Excel
- Live bus: log decoding, monitor, simulator
License
mapcan is free and open source, licensed under the Apache License 2.0. Copyright 2026 Ali Ordu — see NOTICE.
- Your outputs are yours. Files generated from your map — C and Python code, DBC, PDF, Excel, Markdown and HTML — belong to you and may be used under any terms, including in closed-source firmware. The "Generated by mapcan" header is a courtesy, not a requirement.
- The map format is open. The format specification (docs/format.md) is available under CC BY 4.0; the JSON Schema (schema/mapcan.schema.json) and the example maps (examples/) are dedicated to the public domain under CC0 1.0. Anyone may implement readers, writers and tools for the format.
- Third-party components bundled with mapcan are listed in THIRD_PARTY_NOTICES.md.
If mapcan helps your project, a star on GitHub or a citation (CITATION.cff) is much appreciated.
Metadata
Release files for mapcan 0.21.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mapcan-0.21.0.tar.gz | 2.0 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mapcan-0.21.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.0 MB
Release files / mapcan-0.21.0.tar.gz
| Download URL | mapcan-0.21.0.tar.gz |
|---|---|
| Size | 2.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f2050ee3af1bb28fe7c585bc929835fddd4823383ad50e6967a4003eef01131
|
|
BLAKE2b-256 checksum How to use checksums |
7fab0b97d3344f5475ca5a9ca6ddd38cf5f702f1ea8d5d0229e89609c6e36b6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / mapcan-0.21.0-py3-none-any.whl
| Download URL | mapcan-0.21.0-py3-none-any.whl |
|---|---|
| Size | 2.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0ceaf4d74fbd7361e043d3ba90040a1aece3c8c936aceeb3d3f5b6f831bfb6a0
|
|
BLAKE2b-256 checksum How to use checksums |
b6a5537bbc8c08632afa538004f9d12ddcc8821a9542293cbb49bf7cb645aa6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log