A static web application to browse the PSA-RE DBMUXEv (v0.2) YAML database of the Peugeot / Citroën / DS electronic architectures (AEE2001 VAN+CAN, AEE2004, AEE2010…), written for people who build things that talk to these cars: custom ECUs, diagnostic tools, CAN / VAN / LIN sniffers, dashboards, retrofits.
No build step, no server code: index.html + ES modules + one CSS file. The only external library is
js-yaml, loaded from jsDelivr with a vendored copy (vendor/js-yaml.mjs) as fallback.
Unofficial, reverse engineered data: use it at your own risk. The HS / IS bus is safety critical.
- Architectures: variants (comment, years, parent), buses (protocol, bitrate), SVG topology (buses as lines, ECUs attached, gateways such as the BSI connected to several buses), ECU codes, diff with the parent variant (added / changed / inherited frames), bus notes.
- Cars: searchable table (codes, names, brand, years, architecture); car page with versions, fitted ECUs (optional ones marked) and the frames you should see on the car.
- Frames (main view): filters (architecture, buses, ECU as sender/receiver, text, type, timing, alternatives,
data warnings, source), virtualized list, and per frame:
- Grid: SavvyCAN / CANdb++ style bit layout (8 columns, one row per byte, MSB/LSB markers, hatched undocumented bits, mux selector, big-endian and little-endian multi-byte signals, long can-tp/VAN payloads collapsed), hover details and legend; click a signal (grid, legend, simple view, tables) to keep it selected until it is clicked again or another one is clicked (bottom sheet on phones);
- Simple view: bit string with letter groups and staggered callouts, plus a prose listing ("Byte 3, bits 7-6: … 0 = closed, 1 = open");
- Signals table (PSA bits, start, length, DBC Motorola start bit, type, scaling, range, invalid/default, values);
- Emitters / receivers SVG diagram with links to the ECUs and the cars fitted with them;
- Decode a payload (hex or a candump line) with flags (invalid, out of range, unknown value…);
- Encode from a form (physical values, enums, raw) with
cansend,cangen, candump, SavvyCAN CSV, python-can, Arduino MCP2515 and ESP32 TWAI snippets (VAN and LIN specifics: LIN PID/checksum); - Code: C header (struct + pack/unpack with shifts and masks, sign extension, byte order), Python decoder/encoder, DBC snippet;
- Alternatives: conflicting observations side by side, applicable to every other tab;
- Other architectures: same ID elsewhere with a signal-level diff.
- ID pairs: diagnostic request ⇄ response (from
diag/*/*/ecu.ymladdressing, the nodes'diagblocks, ISO 15765-4 OBD IDs, or guessed from the frame names on the bus) and ISO-TP data ⇄ flow control frames, with links to the partner frame, the ECU diag page and the request builder; - Common frames (version, supervision, fault log / event, wake-up, diagnostics, flow control, VIN): generic layouts sent by many ECUs are badged in the list, filterable, and list the other frames of the same kind;
- Variant inheritance (inherited frames, "changed in this variant" + diff), symbolic links ("same as AEE2004.full LS.CONF 036"), multi-source switch, "never released" badges.
- Signals: searchable exact-name cross-reference across effective architecture variants, independent of frame ID. Each signal page groups occurrences by architecture and shows the containing frame, bus, bit range, encoding, timing, source/inheritance status, and links back to the frame's Signals tab.
- ECUs: list per variant (inherited nodes marked, pseudo nodes such as
DIAG_TOOL), ECU page with the frames sent/received, diag addressing, a "build a custom ECU / emulate it" checklist (kept in the browser), cars fitted with it, DBC export limited to its frames. - Diagnostics: protocol reference (from
diag/protocols/*.yml, or built-in ISO 14229/14230/J1979 tables), addressing table (nodes'diagblocks +diag/*/*/ecu.yml), ECU diag pages (sessions, services, security access, DIDs/LIDs/routines/IO controls with a payload decoder), DTC lookup (B1003↔0x9003), ISO-TP request builder (SF/FF/FC/CF explained, can-utils / isotp / python-can-isotp snippets), and the PSA seed/key calculator (port ofPSA-RE/sandbox/uds_auth_algorithm.py). Shows "no data yet" whendiag/is absent. - Tools: log decoder (candump
-Land default formats, SavvyCAN/GVRET CSV, Vector ASC, plainID#DATA) with per-ID statistics (count, measured vs documented period, unknown IDs, changing bits heat map, ISO-TP reassembly), bit position converter (PSA ↔ DBC Motorola ↔ Motorola LSB ↔ Intel), DBC / JSON export (whole bus or filtered), bus load estimator, data issues panel. - Global search (
/): frame ID (036,0x036), frame/signal names, ECUs, car codes, DTCs, DIDs. - Update check: the explorer compares the copy of its own files kept by the browser cache with the server copies (at start-up, when the tab is shown again, every 30 minutes, or from About) and offers to update when a newer version was deployed (it downloads the changed files past the cache, then reloads).
- Hash routing (shareable URLs such as
#/frames/AEE2004.full/LS.CONF/036?tab=decode), content language selector (en, fr, es, de, it, pl, ru, zh, hu with fallback en → fr → any), light/dark theme.
cd /path/to/BusDoc # folder containing openleo-explorer/ (and optionally PSA-RE*/)
python3 -m http.server 8000
# open http://localhost:8000/openleo-explorer/ES modules need HTTP (opening index.html with file:// does not work).
Serve the explorer and the repositories from the same server (same host and port): a page loaded from
localhost:8080 cannot read data from localhost:8081, because python3 -m http.server does not send CORS
headers and the browser blocks the requests. If you really want separate servers, serve the data with
python3 openleo-explorer/tools/serve.py 8081 (directory listings + CORS headers).
By default the data is fetched from GitHub (prototux/PSA-RE@master). To use local repositories served by the same
server, open the settings (⚙) and add HTTP sources, or pass them in the URL:
http://localhost:8000/openleo-explorer/?url=http://localhost:8000/PSA-RE/&only
(url= and github=owner/repo@branch[:subpath] can be repeated; &only ignores the saved sources.)
Copy the openleo-explorer/ folder to any static host (GitHub Pages works as is: publish the folder or a branch
containing it). Nothing else is needed; the data is fetched by the browser.
Sources are configured in the settings page and stored in localStorage. Several sources can be enabled at once:
they are merged in the configured order (the first one wins by default), every object is tagged with its source
(colored badge), and frames defined in several sources get a "sources" switch. Nodes marked released: false
in the data show a red "never released" badge.
-
GitHub (owner, repo, branch, optional sub-path): one call to the tree API (
/git/trees/<branch>?recursive=1), then the data bundle of the repository when there is one (see below), and the other files fromraw.githubusercontent.com(32 parallel requests). Git symlinks (mode120000) are resolved relative to the link and reuse the target's parsed data. When the API is rate limited (60 calls/hour unauthenticated) the loader falls back to jsDelivr (data.jsdelivr.comlisting,cdn.jsdelivr.net/gh/…files). An optional token (settings) raises the limit; it is only sent toapi.github.com. -
HTTP URL of a folder with the same layout: the loader reads
manifest.json(orindex.json), and otherwise walks the HTML directory listings produced bypython3 -m http.server. Generate a manifest (faster, and keeps symlinks as links) with:python3 openleo-explorer/tools/make_manifest.py PSA-RE # writes PSA-RE/manifest.json python3 openleo-explorer/tools/make_manifest.py PSA-RE --output /tmp/m.json --revision v1Format:
{"revision": "...", "files": ["architectures.yml", ...], "symlinks": {"link path": "target path"}}(a plain JSON array of paths is accepted too). -
Local folder: picked with the File System Access API when available, or
<input webkitdirectory>(files are read locally, nothing is uploaded). The folder must be picked again after a page reload.
Loading is kept short:
- Diagnostic data on demand: the DIDs, LIDs, routines, I/O controls and fault codes (most of
diag/) are not loaded at start-up. An ECU page loads the files of its ECU, the DTC lookup loads the fault code tables, and the counts come from the file listing in the meantime. With a bundle, the rest ofdiag/is loaded in the background (status bar: "loading diagnostics…"), so the global search finds diagnostic identifiers too. - Data bundles: the parsed files of a repository in two gzipped JSON files (
coreat start-up,diagwhen needed), published by thedata bundleworkflow of this repository on itsdatabranch and read fromraw.githubusercontent.com/OpenLeo/explorer/data/bundles/<owner>/<repo>/<branch>/. The workflow runs on each push to PSA-RE (PSA-RE'sexplorer bundleworkflow sends apsa-re-pushrepository dispatch, with a token stored in itsEXPLORER_DISPATCH_TOKENsecret), and every hour as a fallback; it rebuilds a bundle when its repository changed. Every file of a bundle keeps its git blob hash: the explorer only takes a file from the bundle when the hash matches the GitHub listing, so a bundle older than the repository is still used and only the files changed since are downloaded.?bundles=<url>uses another bundles directory (self-hosted copy),?bundles=nonedisables them. Build one by hand withnode tools/make_bundle.mjs <repository> <output dir>. - Caches (IndexedDB): the parsed files of the source revision (reloading is instant until the repository changes), and every parsed file by content hash (git blob SHA, or jsDelivr hash), so after an update of the repository only the files that changed are downloaded. Entries no source lists any more are pruned. The ⟳ button reloads without the revision cache; Settings › clear cache empties both.
- Parsing of the downloaded YAML files runs in web workers (main thread fallback).
Note: GitHub disables scheduled workflows of a repository without activity for 60 days; run the workflow by hand (Actions › data bundle › Run workflow) to re-enable it. The explorer keeps working without bundle, only slower.
The loader is tolerant: YAML errors are collected per file (Tools › Data issues) and legacy constructs are
normalized with a warning badge (periodicity: 10 → ['10ms'], free-text periodicities, type: 'VAN',
resolution → factor, unit → units, signed: true → sint, legacy types uint8/int16/float…,
legacy bit notations '3' / '1-2', legacy bus directories such as buses/AEE2001/CONF, VAN method,
duplicate keys, non UTF-8 files…).
index.html shell (top bar, tabs, search, settings buttons)
css/app.css all styles (CSS variables, light/dark theme, container queries)
vendor/js-yaml.mjs js-yaml 4.1.0 (fallback when jsDelivr is unreachable)
js/app.js bootstrap, source loading + progress, hash router, global search, theme/lang
js/core/ UI independent logic (runs in node, unit tested)
util.js escaping, multilang pick, hex helpers, bounded concurrency, identifiers
bits.js PSA bit notation, abs indexes, Motorola/Intel/DBC conversions, bit extraction/insertion,
little-endian chunks
normalize.js tolerant normalization of frames, signals, alternatives, architectures, nodes, cars
codec.js decode / encode payloads (uint, sint, bool, enum, bcd, str, bytes, float, mux, byte order)
paths.js repository path conventions, symlink resolution
yamlparse.js js-yaml loading (CDN / vendored), tolerant parsing, encoding fallback
sources.js GitHub / jsDelivr / HTTP (manifest or listing) / local folder loaders, data bundles,
on-demand loading session (bundle, per-file cache, network)
parsepool.js web workers pool parsing the YAML files (parseworker.js)
cache.js IndexedDB caches (source revision, files by content hash) + safe localStorage
repo.js database build: merge sources, variants inheritance, nodes, cars, frames, diag,
overrides, search index, diffs; diagnostic files loaded on demand (ensureEcuDiag, ensureDtcs)
signalref.js exact-name signal catalog across effective variant frames and differing frame IDs
dbc.js DBC and JSON export
codegen.js C header, Python, transmit snippets, LIN PID/checksum
logparse.js candump / SavvyCAN / ASC / plain log parsing, per-ID statistics
isotp.js ISO-TP segmentation, flow control, reassembly
seedkey.js PSA SecurityAccess seed/key algorithm
busload.js CAN / VAN / LIN bus load estimation
appupdate.js fingerprints of the app files (running copy vs server copy)
diagref.js built-in UDS / KWP2000 / OBD service and NRC tables, request description
relations.js diagnostic / ISO-TP ID pairs, common frame families
js/ui/ DOM code
dom.js html`` tagged template (auto-escaping), delegated events, copy/download, toasts
state.js settings and shared app state
widgets.js badges, links, code blocks, small fragments
bitgrid.js bit layout grid, legend, signal details
simpleview.js simple (byte by byte) view
diagrams.js SVG topology and emitters/receivers diagrams
vlist.js virtualized list
update.js "new version available" check and bar
views/ one module per tab (frames, frame detail, signals, archs, cars, ecus, diag, tools, about,
settings)
tools/make_manifest.py manifest generator for HTTP sources
tools/make_bundle.mjs data bundle builder (used by .github/workflows/data-bundle.yml)
tests/ node --test unit tests + fixtures (small repositories with symlinks, a second source)
Every data-derived string is escaped by the html template (or esc() for the few string-built rows).
cd openleo-explorer
node --test tests/The tests cover the bit notation and DBC conversions, normalization of legacy data, decoding/encoding
(including little endian, mux, strings, floats), the loaders (mocked GitHub API + raw + jsDelivr fallback,
manifest, a real python3 -m http.server crawl of ../PSA-RE), symlinks, variant inheritance, multi-source
merging, search, DTC codes, DBC export, generated C (compiled with gcc -Wall -Werror and compared with the JS
codec on random payloads of every PSA-RE frame), generated Python (run with python3), log parsing, ISO-TP,
the seed/key algorithm (vectors from the Python reference), bus load, the dbmuxev/doc examples, and the
on-demand diagnostics, per-file cache and data bundles (stale bundle included) against a mocked GitHub.
Tests needing ../PSA-RE, gcc or python3 are skipped when absent.
- Local folder sources must be picked again after each reload (browsers do not persist the permission).
- jsDelivr fallback may lag behind GitHub by a few hours and cannot tell which files are symlinks (link files stored as plain text are still detected).
- DBC cannot express VAN/LIN frames, fields wider than 64 bits, or non byte-aligned little-endian fields: they are exported with a warning or skipped. Frames sharing an ID on one bus: only the first one is exported.
- Bus load is an estimate (worst-case CAN bit stuffing, approximate VAN time slots, LIN nominal × 1.4).
- The "custom ECU" checklist uses heuristics for wake-up / supervision frames (ID ranges and names).