dbc: Vector DBC (CAN database) files
| (require dbc) | package: dbc |
The "dbc" package reads and writes DBC files — the de-facto-standard CAN database format invented by Vector Informatik and consumed by virtually every CAN tool in the automotive industry. A DBC file describes the symbolic layer of a CAN bus: message and signal names, bit layouts, scaling, value tables (choices), comments and attributes.
Everything is implemented in pure Racket: no FFI, no system dependency. The implementation is validated bit-for-bit against the Python cantools library on its own test corpus (Intel and Motorola byte order, signed/unsigned and IEEE float signals, multiplexing, extended multiplexing).
1 Quick start
> (define db (parse-dbc "VERSION \"1.0\"\n\nBO_ 37 ExampleMessage: 8 Vector__XXX\n SG_ VehicleSpeed : 39|16@1+ (0.01,0) [-100|100] \"kph\" Vector__XXX\n SG_ Temperature : 0|12@0- (0.01,250) [-40|215] \"degC\" Vector__XXX\n\nVAL_ 37 VehicleSpeed 0 \"standstill\" 1 \"moving\" ;"))
> (define m (for/first ([e (in-list (dbc-entries db))] #:when (entry-bo? e)) (entry-bo-message e))) > (message-name m) 'ExampleMessage
> (message-decode m (bytes 1 176 0 0 0 0 0 0)) '#hasheq((Temperature . 243.6) (VehicleSpeed . 0.0))
> (message-decode m (bytes 1 176 0 0 0 0 0 0) #:decode-choices? #t) '#hasheq((Temperature . 243.6) (VehicleSpeed . 0.0))
> (message-encode m (hash 'Temperature 250.47 'VehicleSpeed 0.5)) #"\0\5\340\0\0\31\0\0"
A file is parsed with parse-dbc, which returns a dbc? value. Two views live side by side:
the entry stream — (dbc-entries db) keeps every grammar element of the file (BO_, CM_, BA_DEF_, VAL_, …) in source order, which is what makes lossless serialization possible; and
the codec view — each entry-bo wraps a message? whose signal?s carry the decoded choices/comments/float kinds, ready for message-decode and message-encode.
2 Bit numbering conventions
A payload bit n lives in byte (quotient n 8) at bit position (remainder n 8) counting from the LSB.
Intel @1 (little-endian):: the value’s LSB sits at signal-start-bit; the value occupies bits start … start+len-1, accumulated LSB-first.
Motorola @0 (big-endian): the value’s MSB sits at signal-start-bit; the remaining bits walk the sawtooth — from a byte’s LSB the walk jumps to the next byte’s MSB (+15), otherwise it descends one position (-1) — accumulated MSB-first. This is the same placement cantools produces (verified bit by bit).
3 Parser and serializer
procedure
(serialize-dbc db) → string?
db : dbc?
struct
(struct message (id extended? name dlc transmitter signals))
id : exact-integer? extended? : boolean? name : symbol? dlc : exact-nonnegative-integer? transmitter : (or/c symbol? #f) signals : (listof signal?)
struct
(struct signal ( name mux mux-ranges start-bit bit-count little-endian? signed? scale offset minimum maximum unit receivers float-kind choices comment)) name : symbol? mux : (or/c #f (integer-in 0 65535) 'multiplexor) mux-ranges : list? start-bit : (integer-in 0 511) bit-count : (integer-in 1 64) little-endian? : boolean? signed? : boolean? scale : rational? offset : rational? minimum : (or/c rational? #f) maximum : (or/c rational? #f) unit : (or/c string? #f) receivers : (listof symbol?) float-kind : (or/c #f 'ieee) choices : (or/c hash? #f) comment : (or/c string? #f)
4 Message codec
procedure
(message-decode msg payload [ #:decode-choices? decode-choices?]) → (hash/c symbol? (or/c exact-integer? rational? real? string?)) msg : message? payload : bytes? decode-choices? : boolean? = #f
procedure
(message-encode msg values [ #:base base #:dlc dlc]) → bytes? msg : message? values : (hash/c symbol? real?) base : (or/c bytes? #f) = #f dlc : (or/c #f exact-nonnegative-integer?) = #f
procedure
(signal-decode sig payload)
→ (or/c exact-integer? rational? real?) sig : signal? payload : bytes?
procedure
(signal-encode sig payload value) → bytes?
sig : signal? payload : bytes? value : real?
procedure
(message-signal msg name) → (or/c signal? #f)
msg : message? name : symbol?
procedure
(signal-active? sig switch) → boolean?
sig : signal? switch : (or/c exact-integer? #f)
5 Tokenizer
6 Limitations
Attribute definitions and values (BA_DEF_, BA_) are parsed and round-tripped, but attribute semantics are not interpreted.
The EV_ (environment variable), SIG_GROUP_ and SGTYPE_ families are kept as opaque entries (parsed structurally where the grammar is regular, entry-unparsed otherwise).
J1939-specific conventions (PGN packing inside the 29-bit ID) are out of scope; the extended-ID bit is exposed via message-extended?.
7 License
MIT. The test corpus includes sample files from the cantools project (MIT).