PSA-RE: the networks of Peugeot, Citroën and DS cars
PSA-RE documents the in-car networks of the PSA group cars (Peugeot, Citroën, DS, and the Opel/Vauxhall models built on PSA platforms): which electronic architecture each car uses, the buses of each architecture, the ECUs connected to them, the frames they exchange, down to the meaning of every bit, and how to diagnose each ECU (UDS / KWP2000 identifiers, routines, actuator tests and fault codes).
Everything is plain YAML in the DBMUXEv format, checked by a JSON schema, and every text is available in English and French (and most of it in Spanish, German, Italian, Polish, Russian, Chinese, Hungarian and Portuguese). The goal is to let anybody understand their own car and build tools, dashboards, retrofits or DIY ECUs on top of it.
Browse it online: explorer.openleo.org: search frames, signals and diagnostic data, see which ECUs and buses a car has, decode a capture log, export DBC files, all in the browser.
What's inside
| Architecture | Cars | Networks | Frame files |
|---|---|---|---|
| AEE2001 | VAN + CAN cars, late 90s to 2008 (206, early 307, Xsara Picasso...) | 1 CAN bus, VAN comfort/body buses | 132 |
| AEE2004 | first "full CAN" cars, 2004 to 2018 (207, 308 I, C4 I, C5 II...) | HS and LS CAN, LIN | 849 |
| AEE2010 | second generation, 2010 to the early 2020s (208 I/II, 308 II, 3008 II, C4 Picasso II, DS 7...) | up to 11 HS CAN buses, LS CAN, LIN | 1962 |
| NEA2020 | 2021+ cars | not documented yet | - |
The diagnostics cover 486 ECU models (on these architectures and on the cars built with Mitsubishi, Fiat and Toyota): 11938 UDS data identifiers, 4215 KWP2000 local identifiers, 1472 routines, 2888 input/output controls (actuator tests) and the fault codes of 363 ECU models.
Each architecture has variants ():
full: the reference variantev: evolution, a later revision of the architecture (not "electric vehicle");AEE2004.evis used by cars
eco,ev_eco: the economy variant (low cost cars, fewer and merged buses)hybrid: HYbrid4 diesel hybrids (3008 HYbrid4, 508 RXH, DS5 HYbrid4)electric: plug-in hybrids and electric cars
parent (see architectures.yml) and only contains
what is different or new.
The bus names follow the car's own naming: HS is high speed CAN (500 kbit/s), LS low speed CAN (125 kbit/s),
IS is the inter-systems bus (powertrain and chassis), CONF the comfort bus, CAR the body bus, INFO-DIV the
infotainment bus, LAS the steering/chassis sensors bus, HYB the hybrid/electric drivetrain bus, and so on.
Every bus is described in architectures.yml.
Which architecture does my car use?
Look for your car in cars/: files are named after the PSA project code (T9 is the 308 II, P2 the
208 II / 2008 II, T7 the 308 I...), and list the commercial names. Each car has one or more versions
(phases, engines, hybrid or electric versions): every version gives its architecture variant, the ECUs on each
network, the optional ones, and the buses actually fitted. The explorer has a car search that does the same.
Repository layout
architectures.yml architectures, variants, networks and buses
cars/{project}.yml car codes, names, versions, architecture and ECUs of each version
nodes/{architecture}.{variant}.yml ECUs (nodes): name, buses, network addresses, diagnostic addressing
buses/{architecture}.{variant}/{network}.{bus}/{ID}.yml
one frame per file, ID in uppercase hex
buses/.../{ID}_{NAME}.yml when several different frames use the same ID on a bus
diag/protocols/ diagnostic protocols (KWP2000, KWP-PSA2000, UDS, EOBD)
diag/{architecture}.{variant}/{node}/{model}/
diagnostics of one ECU model:
ecu.yml addressing (request / response CAN IDs), protocol, sessions, security access,
identification and software variants
dids/{DID}.yml UDS data identifiers (read 0x22, write 0x2E)
lids/{LID}.yml KWP2000 local identifiers (read 0x21, write 0x3B)
routines/{ID}.yml routines (0x31, or KWP2000 0x31/0x32/0x33 with a 1 byte identifier: {ID}_LID.yml)
ioctls/{ID}.yml input/output controls, actuator tests (0x2F, KWP2000 0x30: {ID}_LID.yml)
dtcs.yml fault codes
schemas/dbmuxev.schema.json JSON schema of every document
dbmuxev/ Python library (load, decode, encode) and the format documentation (dbmuxev/doc)
tools/ validator and DBC export
To get all the frames of a variant, start from its own directory and walk up its parent chain: a file in a child
variant replaces the file with the same path in its parents (buses/AEE2004.ev/HS.IS/305.yml replaces
buses/AEE2004.full/HS.IS/305.yml on the cars using AEE2004.ev).
Reading a frame
Here is the steering wheel angle frame of AEE2004 (buses/AEE2004.full/HS.IS/305.yml), shortened:
id: 0x305 # CAN ID
name: 'STEERING_WHEEL_ANGLE' # unique name in the architecture
alt_names: ['ANGLE_VOLANT', 'WHEEL_SENSOR'] # other names this frame is known by
type: 'can' # can, can-tp (ISO-TP transfer), van or lin
length: 7 # payload length in bytes
comment:
en: 'Steering-wheel angle, rotation speed and direction, sensor calibration/trim and fault status.'
fr: 'Angle volant, vitesse et sens de rotation, calibration/trim du capteur et état de défaut.'
periodicity: ['10ms'] # or 'trigger' (sent on events), 'request', or both
senders: ['ABS', 'CAV', 'DAE'] # nodes defined in nodes/AEE2004.full.yml
receivers: ['CMM', 'BSI', 'BVA', ...]
signals:
STEERING_WHEEL_ANGLE:
bits: '1.7-2.0' # byte 1 bit 7 (MSB) to byte 2 bit 0 (LSB): 16 bits
type: 'sint' # two's complement
factor: 0.1
min: -780
max: 780
units: '°'
invalid: 0x7FFF # raw value meaning "not available"
default: 0x00
STEERING_WHEEL_ROTATION_SPEED:
bits: '3.7-3.0'
type: 'uint'
factor: 4
units: '°/s'
invalid: 0xFF
STEERING_WHEEL_ROTATION_DIRECTION:
bits: '4.7' # a single bit
type: 'bool'
values:
0x00:
en: 'Positive angle direction, wheel angle increasing (counter-clockwise)'
0x01:
en: 'Negative angle direction, steering wheel angle decreasing: clockwise'
...
Bit notation
Bits are written : bytes are numbered from 1 (the first byte on the bus), bits from 7 (most significant)
to 0. A range is written in transmission order, so multi-byte values are big endian
("Motorola"): 1.7-2.0 is byte 1 (most significant) followed by byte 2.
byte 1 byte 2
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
[1.7 ][ 2.0] bits: '1.7-2.0' (16 bits)
[1.5-1.3] bits: '1.5-1.3' (3 bits)
A few values are little endian: they have byte_order: 'little_endian'. Variable length payloads (CAN-TP transfers,
diagnostics) use n as the end: 3.7-n is everything from byte 3. For a DBC file, the Motorola start bit of
is (byte - 1) * 8 + bit.
From raw bytes to values
The physical value is raw * factor + offset (factor 1 and offset 0 when they are not given). With the payload
FF 9C 1C 00 A0 F0 00 on 0x305:
STEERING_WHEEL_ANGLE, bits1.7-2.0: raw0xFF9C, signed on 16 bits = -100, x 0.1 = -10.0 °STEERING_WHEEL_ROTATION_SPEED, bits3.7-3.0: raw0x1C= 28, x 4 = 112 °/sSTEERING_WHEEL_ROTATION_DIRECTION, bit4.7: 0 = counter-clockwise
Interpreting the fields
| Field | Meaning |
|---|---|
| type | uint, sint, bool (1 bit), enum (a value table, possibly with a scaling for the other values), bcd, str (ASCII), bytes (raw), float |
| values | labels of raw values (keys are hex). unused: true in a value table marks a reserved value |
| invalid | raw value sent when the data is not available or not valid: do not scale it |
| default | value sent before the data is known (start-up, sender absent) |
| min / max | physical range |
| unused: true | bits with no known meaning |
| mux_selector / mux | multiplexed frames: the selector signal tells which signals are present, each multiplexed signal lists the selector values it belongs to |
| alternatives | conflicting observations of the same frame or signal (another length, period, layout or scaling, seen on some cars or software versions): the main description is the most common one, each alternative has a note |
| observations | how and when the frame is sent, differences between car generations |
| senders: [] | the sender is not known yet (marked # TODO: find the sender(s)) |
| alt_names | other names of the frame or signal, to help searching |
| isotp (can-tp) | ISO 15765-2 parameters: the signals describe the reassembled payload, not the CAN frames |
| van (van) | VAN access mode, acknowledge, reply (in-frame response) |
Node names (BSI, CMM, ABS, BSM...) are the usual PSA ECU names; their description is in
nodes/{architecture}.{variant}.yml: BSI is the body computer and main gateway, CMM the engine ECU, BVA the
automatic gearbox, CMB the instrument cluster, BSM the under-hood fuse box, and so on.
Reading the diagnostics
Each ECU model has its own directory, because ECUs with the same role (an engine ECU, a BSI...) come from different
suppliers and generations, each with its own identifiers. ecu.yml tells how to reach it (the diagnostic CAN IDs or
K-line address, the protocol), which sessions and services it supports, and how to tell its software variants apart.
Data identifiers use the same params format as frame signals, with byte 1 being the first data byte after the
identifier. For a UDS DID, read 22 F1 8B, the ECU answers 62 F1 8B followed by the data that params describes:
did: 0xF18B
name: 'MANUFACTURING_DATE'
kind: 'identification' # live | config | identification | counter | other
access: 'read'
length: 3 # data bytes after the DID
params:
PRODUCTION_DATE:
bits: '1.7-3.0'
type: 'uint'
variants: the software variants (listed inecu.yml) an identifier was seen on; none means all of themalternatives: another layout of the same identifier on some variants, with anotemapped: values without a fixed position (the ECU sends a table of parameters), given byindexandlength- routines and I/O controls give their parameters,
results,controls(control option values) and the actuator
tests that use them
dtcs.yml: fault codes with their description, and the meaning of the failure type byte sent after 2 byte codes:
failure_types applies to every code that does not have its own (failure_types: {}: none)
Using the data
In the browser
explorer.openleo.org loads this repository directly: browse the architectures, cars, ECUs, frames, signals and diagnostic data, decode a capture log, estimate the bus load, export DBC or JSON files. It can also load a local copy or a fork, to check your own changes.
With Python
The dbmuxev package loads a repository, resolves the variant inheritance and decodes/encodes frames:
from dbmuxev import Database
db = Database('.').load_all()
bus = db['AEE2004.full']['HS.IS']
decoded = bus.decode(0x305, bytes.fromhex('FF9C1C00A0F000'))
print(decoded) # one line per signal, with units and value labels
decoded['STEERING_WHEEL_ANGLE'].value # -10.0 (physical value); .raw, .label, .valid
From the command line: python3 -m dbmuxev decode AEE2004.full HS.IS 305 FF9C1C00A0F000, and
python3 -m dbmuxev --help for the other commands (frames, show, find, car...). See
tools/README.md for the whole API.
DBC files (one per CAN bus, inherited frames included):
pip install -r tools/requirements.txt
python3 tools/export_dbc.py --arch AEE2004.full -o dbc/
With your own parser
Any YAML parser works, but keep in mind:
- keys of value tables are hex numbers (
0x1A:), read as integers by YAML 1.1 parsers (PyYAML, js-yaml); a YAML 1.2
- resolve the
parentchain of the variant (see above), and accept symlinks between architectures (identical
- text fields are maps of language codes: fall back to
enwhen a language is missing - validate your understanding with
schemas/dbmuxev.schema.json, which describes
dbmuxev/doc
Contributing
Corrections and new frames are welcome. Run the validator before opening a pull request:
pip install -r tools/requirements.txt
python3 tools/validate.py
It checks the schema, bit ranges, overlaps, value tables, node and bus references and the usual YAML traps.
The rules are in dbmuxev/doc/README.md: names in English UPPER_SNAKE_CASE, every text
with at least en (and fr if you can), numbers in hex, and when two observations disagree, keep both (the most
common one as the main description, the other one in alternatives with a note).
Come and discuss on the Discord server, and see the wiki.
Warning
This is an unofficial project: the data may be incomplete or wrong. Any change you make to your car, even based on this documentation, is your sole responsibility.
Thanks
- Wouter Bokslag for his awesome work on the reverse engineering of the immobilizer
- Alexandre Blin for his tools, work on his 207 and for being a huge inspiration for this
- Peter Pinter for his huge work on his own FullCAN to VAN bridge
- Lanchon for his reverse engineering of AEE2004's 0x60F
- All the people who leaked parts of PSA's designs all over the internet :)