Compare commits
6 Commits
4270459973
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f0e706542b | |||
| a53a5dfed3 | |||
| cb20f5fe55 | |||
| f2aa10cee4 | |||
| efc57d658d | |||
| 2a90b2d79d |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.user
|
||||||
842
README.md
842
README.md
@@ -1,3 +1,841 @@
|
|||||||
# dbc
|
# DBC Parser and Runtime Decode Module Documentation
|
||||||
|
|
||||||
some experiments with parsing dbc files
|
## Overview
|
||||||
|
|
||||||
|
This module provides a minimal but extensible DBC parser and runtime decode foundation for the **FrameTap** project.
|
||||||
|
|
||||||
|
Its purpose is to:
|
||||||
|
|
||||||
|
- parse DBC files
|
||||||
|
- store parsed frame and signal metadata
|
||||||
|
- build a tree for future UI integration
|
||||||
|
- build a runtime-ready decode database
|
||||||
|
- decode live CAN frames
|
||||||
|
- decode CAN trace records using the same decoder
|
||||||
|
|
||||||
|
The implementation follows a simple and practical design:
|
||||||
|
|
||||||
|
- parser logic is separated from UI logic
|
||||||
|
- runtime decode structures are separated from tree structures
|
||||||
|
- Qt is not required at the parser or decoder level
|
||||||
|
- the same decode engine can be reused for live traffic and trace replay
|
||||||
|
|
||||||
|
This is **not a full production-grade DBC implementation yet**, but it is a strong architectural base.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High-Level Architecture
|
||||||
|
|
||||||
|
The module is divided into two main paths.
|
||||||
|
|
||||||
|
### 1. Parse and UI path
|
||||||
|
|
||||||
|
Used for:
|
||||||
|
|
||||||
|
- browsing frames and signals
|
||||||
|
- later integration with Qt `Model/View`
|
||||||
|
- displaying metadata
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DBC file
|
||||||
|
↓
|
||||||
|
DbcParser
|
||||||
|
↓
|
||||||
|
DbcDatabase
|
||||||
|
↓
|
||||||
|
DbcTreeBuilder
|
||||||
|
↓
|
||||||
|
TreeNode hierarchy
|
||||||
|
↓
|
||||||
|
future Qt UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Runtime decode path
|
||||||
|
|
||||||
|
Used for:
|
||||||
|
|
||||||
|
- live CAN decoding
|
||||||
|
- CAN trace decoding
|
||||||
|
- fast lookup by CAN ID
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DBC file
|
||||||
|
↓
|
||||||
|
DbcParser
|
||||||
|
↓
|
||||||
|
DbcDatabase
|
||||||
|
↓
|
||||||
|
DbcDecodeBuilder
|
||||||
|
↓
|
||||||
|
DecodeDatabase
|
||||||
|
↓
|
||||||
|
DbcDecoder
|
||||||
|
↓
|
||||||
|
Decoded values
|
||||||
|
```
|
||||||
|
|
||||||
|
This separation is intentional.
|
||||||
|
|
||||||
|
The tree is useful for UI, but it is **not** the main data structure for runtime decoding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why the Runtime Decode Layer Exists
|
||||||
|
|
||||||
|
A tree structure is convenient for browsing, but a runtime decoder needs something different:
|
||||||
|
|
||||||
|
- fast lookup by CAN ID
|
||||||
|
- direct access to signal decode definitions
|
||||||
|
- minimal overhead during repeated decoding
|
||||||
|
- the same logic for live frames and trace frames
|
||||||
|
|
||||||
|
Because of that, the design uses a dedicated runtime-ready structure:
|
||||||
|
|
||||||
|
- `DecodeDatabase`
|
||||||
|
- `DecodeFrame`
|
||||||
|
- `DecodeSignal`
|
||||||
|
|
||||||
|
This avoids forcing UI-oriented structures into a decode role they were not meant for.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module Layout
|
||||||
|
|
||||||
|
### Parsed DBC structures
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
- `signal_info.h`
|
||||||
|
- `frame_info.h`
|
||||||
|
- `dbc_database.h`
|
||||||
|
|
||||||
|
These store a readable representation of the parsed DBC file.
|
||||||
|
|
||||||
|
### UI tree structures
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
- `tree_node.h`
|
||||||
|
- `tree_node.cpp`
|
||||||
|
- `dbc_tree_builder.h`
|
||||||
|
- `dbc_tree_builder.cpp`
|
||||||
|
|
||||||
|
These convert parsed DBC content into a tree hierarchy suitable for UI and model/view usage later.
|
||||||
|
|
||||||
|
### Runtime decode structures
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
- `decode_database.h`
|
||||||
|
- `dbc_decode_builder.h`
|
||||||
|
- `dbc_decode_builder.cpp`
|
||||||
|
|
||||||
|
These convert parsed DBC content into a structure optimized for decoding.
|
||||||
|
|
||||||
|
### Runtime decoder
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
- `dbc_decoder.h`
|
||||||
|
- `dbc_decoder.cpp`
|
||||||
|
|
||||||
|
These perform actual decoding of raw CAN frames using `DecodeDatabase`.
|
||||||
|
|
||||||
|
### Parser
|
||||||
|
|
||||||
|
Files:
|
||||||
|
|
||||||
|
- `dbc_parser.h`
|
||||||
|
- `dbc_parser.cpp`
|
||||||
|
|
||||||
|
These parse the DBC file itself.
|
||||||
|
|
||||||
|
### Demo
|
||||||
|
|
||||||
|
File:
|
||||||
|
|
||||||
|
- `main.cpp`
|
||||||
|
|
||||||
|
Used as a small integration example.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parsed Data Structures
|
||||||
|
|
||||||
|
## `SignalInfo`
|
||||||
|
|
||||||
|
Represents one signal as parsed from the DBC file.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `startBit`
|
||||||
|
- `length`
|
||||||
|
- `isLittleEndian`
|
||||||
|
- `isSigned`
|
||||||
|
- `factor`
|
||||||
|
- `offset`
|
||||||
|
- `minimum`
|
||||||
|
- `maximum`
|
||||||
|
- `unit`
|
||||||
|
- `receivers`
|
||||||
|
- `comment`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `receivers` is a list because a signal may have more than one receiver ECU
|
||||||
|
- `factor` and `offset` define physical conversion
|
||||||
|
- this structure is close to DBC content and easy to inspect
|
||||||
|
|
||||||
|
Physical value rule:
|
||||||
|
|
||||||
|
```text
|
||||||
|
physical = raw * factor + offset
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `FrameInfo`
|
||||||
|
|
||||||
|
Represents one frame as parsed from the DBC file.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `canId`
|
||||||
|
- `isExtended`
|
||||||
|
- `pgn`
|
||||||
|
- `hasPgn`
|
||||||
|
- `dlc`
|
||||||
|
- `transmitter`
|
||||||
|
- `comment`
|
||||||
|
- `signals`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `signals` is a list of `SignalInfo`
|
||||||
|
- `isExtended` is determined during CAN ID normalization
|
||||||
|
- `pgn` is derived using simplified J1939 logic when applicable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DbcDatabase`
|
||||||
|
|
||||||
|
Top-level parsed DBC container.
|
||||||
|
|
||||||
|
Conceptually:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DbcDatabase
|
||||||
|
└── vector<FrameInfo>
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the central structure produced by `DbcParser`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Tree Layer
|
||||||
|
|
||||||
|
## `TreeNode`
|
||||||
|
|
||||||
|
The UI tree contains three node types:
|
||||||
|
|
||||||
|
- `Root`
|
||||||
|
- `Frame`
|
||||||
|
- `Signal`
|
||||||
|
|
||||||
|
Example hierarchy:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dbc
|
||||||
|
├── EngineData
|
||||||
|
│ ├── EngineSpeed
|
||||||
|
│ ├── OilTemp
|
||||||
|
│ └── CoolantTemp
|
||||||
|
└── VehicleData
|
||||||
|
├── VehicleSpeed
|
||||||
|
└── Odometer
|
||||||
|
```
|
||||||
|
|
||||||
|
Each node stores either:
|
||||||
|
|
||||||
|
- `FrameInfo`
|
||||||
|
- `SignalInfo`
|
||||||
|
|
||||||
|
The tree is intended for browsing and later Qt model integration.
|
||||||
|
|
||||||
|
It is **not** the primary runtime decode structure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Runtime Decode Layer
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The decode layer exists so that decoding can be fast and independent from UI concerns.
|
||||||
|
|
||||||
|
Instead of searching a tree, the decoder uses a prepared database with direct lookup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `ByteOrder`
|
||||||
|
|
||||||
|
Runtime byte order enum:
|
||||||
|
|
||||||
|
- `Intel`
|
||||||
|
- `Motorola`
|
||||||
|
|
||||||
|
This is better for decode code than passing around raw DBC characters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `ValueType`
|
||||||
|
|
||||||
|
Numeric type enum:
|
||||||
|
|
||||||
|
- `Unsigned`
|
||||||
|
- `Signed`
|
||||||
|
|
||||||
|
This is clearer than combining multiple boolean flags during runtime logic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DecodeSignal`
|
||||||
|
|
||||||
|
Represents one runtime-ready signal definition.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `startBit`
|
||||||
|
- `length`
|
||||||
|
- `byteOrder`
|
||||||
|
- `valueType`
|
||||||
|
- `factor`
|
||||||
|
- `offset`
|
||||||
|
- `minimum`
|
||||||
|
- `maximum`
|
||||||
|
- `unit`
|
||||||
|
- `receivers`
|
||||||
|
- `comment`
|
||||||
|
|
||||||
|
This structure contains all information required for extracting and converting a signal value from raw frame data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DecodeFrame`
|
||||||
|
|
||||||
|
Represents one runtime-ready frame definition.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `canId`
|
||||||
|
- `isExtended`
|
||||||
|
- `dlc`
|
||||||
|
- `pgn`
|
||||||
|
- `hasPgn`
|
||||||
|
- `transmitter`
|
||||||
|
- `comment`
|
||||||
|
- `signals`
|
||||||
|
|
||||||
|
This structure is used directly by the decoder.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `FrameKey`
|
||||||
|
|
||||||
|
Fast lookup key for runtime frame matching.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `canId`
|
||||||
|
- `isExtended`
|
||||||
|
|
||||||
|
This matters because the same numeric identifier must not be confused between standard and extended frames.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DecodeDatabase`
|
||||||
|
|
||||||
|
Top-level runtime decode container.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `frames`
|
||||||
|
- `frameIndexByKey`
|
||||||
|
|
||||||
|
Conceptually:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DecodeDatabase
|
||||||
|
├── vector<DecodeFrame>
|
||||||
|
└── unordered_map<FrameKey, index>
|
||||||
|
```
|
||||||
|
|
||||||
|
This gives the decoder fast access to a frame definition using CAN ID and frame type.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decoder Layer
|
||||||
|
|
||||||
|
## `RawCanFrame`
|
||||||
|
|
||||||
|
Represents a raw CAN frame to decode.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `canId`
|
||||||
|
- `isExtended`
|
||||||
|
- `data`
|
||||||
|
|
||||||
|
This same structure can be used for:
|
||||||
|
|
||||||
|
- live CAN bus input
|
||||||
|
- replayed trace records
|
||||||
|
- unit tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DecodedSignalValue`
|
||||||
|
|
||||||
|
Represents one decoded signal result.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `definition`
|
||||||
|
- `rawValue`
|
||||||
|
- `physicalValue`
|
||||||
|
- `valid`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DecodedFrameValue`
|
||||||
|
|
||||||
|
Represents one decoded frame result.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `definition`
|
||||||
|
- `signals`
|
||||||
|
- `valid`
|
||||||
|
|
||||||
|
This is the decoder output for one raw frame.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `DbcDecoder`
|
||||||
|
|
||||||
|
Main runtime decoder class.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- find a frame definition by CAN ID
|
||||||
|
- decode all signals in a frame
|
||||||
|
- extract raw values
|
||||||
|
- sign-extend signed values
|
||||||
|
- convert raw values into physical values
|
||||||
|
|
||||||
|
Main methods:
|
||||||
|
|
||||||
|
- `FindFrame(...)`
|
||||||
|
- `Decode(...)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parser Support
|
||||||
|
|
||||||
|
The current parser supports the following DBC elements:
|
||||||
|
|
||||||
|
- `BO_`
|
||||||
|
- `SG_`
|
||||||
|
- `CM_ BO_`
|
||||||
|
- `CM_ SG_`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Supported DBC Syntax
|
||||||
|
|
||||||
|
## Frame definition
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BO_ 256 EngineData: 8 EEC1
|
||||||
|
```
|
||||||
|
|
||||||
|
Parsed fields:
|
||||||
|
|
||||||
|
- frame CAN ID
|
||||||
|
- frame name
|
||||||
|
- DLC
|
||||||
|
- transmitter ECU
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Signal definition
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
|
||||||
|
```
|
||||||
|
|
||||||
|
Parsed fields:
|
||||||
|
|
||||||
|
- signal name
|
||||||
|
- start bit
|
||||||
|
- signal length
|
||||||
|
- byte order
|
||||||
|
- signedness
|
||||||
|
- factor
|
||||||
|
- offset
|
||||||
|
- minimum
|
||||||
|
- maximum
|
||||||
|
- unit
|
||||||
|
- receivers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
Frame comment example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CM_ BO_ 256 "Engine data frame";
|
||||||
|
```
|
||||||
|
|
||||||
|
Signal comment example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CM_ SG_ 256 EngineSpeed "Actual engine speed";
|
||||||
|
```
|
||||||
|
|
||||||
|
Stored in:
|
||||||
|
|
||||||
|
- `FrameInfo::comment`
|
||||||
|
- `SignalInfo::comment`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CAN ID Normalization
|
||||||
|
|
||||||
|
The parser normalizes frame identifiers.
|
||||||
|
|
||||||
|
Common DBC behavior:
|
||||||
|
|
||||||
|
- extended identifiers may be stored with bit 31 set
|
||||||
|
- the actual 29-bit identifier must be extracted from that value
|
||||||
|
|
||||||
|
The parser therefore stores:
|
||||||
|
|
||||||
|
- normalized `canId`
|
||||||
|
- separate `isExtended` flag
|
||||||
|
|
||||||
|
This is important both for correct lookup and for future interoperability with live CAN APIs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PGN Extraction
|
||||||
|
|
||||||
|
PGN is derived only when the frame is treated as extended.
|
||||||
|
|
||||||
|
The current logic is simplified J1939 extraction:
|
||||||
|
|
||||||
|
- `pf`
|
||||||
|
- `ps`
|
||||||
|
- `dp`
|
||||||
|
|
||||||
|
This is enough for a practical start but should not be treated as full J1939 validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decode Flow
|
||||||
|
|
||||||
|
Typical runtime decode flow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawCanFrame
|
||||||
|
↓
|
||||||
|
Find frame in DecodeDatabase
|
||||||
|
↓
|
||||||
|
For each signal:
|
||||||
|
extract raw bits
|
||||||
|
apply sign extension if needed
|
||||||
|
convert to physical value
|
||||||
|
↓
|
||||||
|
DecodedFrameValue
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intel and Motorola Extraction
|
||||||
|
|
||||||
|
The decoder currently has separate extraction paths:
|
||||||
|
|
||||||
|
- `ExtractIntel(...)`
|
||||||
|
- `ExtractMotorola(...)`
|
||||||
|
|
||||||
|
This is important because byte order is not just metadata once decoding starts.
|
||||||
|
|
||||||
|
Intel and Motorola require different bit extraction logic.
|
||||||
|
|
||||||
|
This is one of the main reasons why the runtime decode layer should be explicit and prepared in advance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
## Parse DBC
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DbcParser parser;
|
||||||
|
DbcDatabase database = parser.ParseFile("example.dbc");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build UI tree
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DbcTreeBuilder treeBuilder;
|
||||||
|
std::unique_ptr<TreeNode> root = treeBuilder.Build(database);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build runtime decode database
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DbcDecodeBuilder decodeBuilder;
|
||||||
|
DecodeDatabase decodeDatabase = decodeBuilder.Build(database);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decode a raw frame
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
RawCanFrame rawFrame;
|
||||||
|
rawFrame.canId = 0x123;
|
||||||
|
rawFrame.isExtended = false;
|
||||||
|
rawFrame.data = {0x01, 0x02, 0x03, 0x04};
|
||||||
|
|
||||||
|
DbcDecoder decoder;
|
||||||
|
DecodedFrameValue decoded = decoder.Decode(decodeDatabase, rawFrame);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Unified Decode Strategy
|
||||||
|
|
||||||
|
A key design goal is that the same decoder should work for both:
|
||||||
|
|
||||||
|
- live CAN frames
|
||||||
|
- trace replay frames
|
||||||
|
|
||||||
|
That means this architecture supports:
|
||||||
|
|
||||||
|
### live path
|
||||||
|
|
||||||
|
```text
|
||||||
|
live CAN input
|
||||||
|
↓
|
||||||
|
RawCanFrame
|
||||||
|
↓
|
||||||
|
DbcDecoder
|
||||||
|
↓
|
||||||
|
decoded signal values
|
||||||
|
```
|
||||||
|
|
||||||
|
### trace path
|
||||||
|
|
||||||
|
```text
|
||||||
|
trace reader
|
||||||
|
↓
|
||||||
|
RawCanFrame
|
||||||
|
↓
|
||||||
|
DbcDecoder
|
||||||
|
↓
|
||||||
|
decoded signal values
|
||||||
|
```
|
||||||
|
|
||||||
|
This avoids duplicating decode logic in two separate parts of the application.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intended Use in FrameTap
|
||||||
|
|
||||||
|
This module is meant to support at least the following FrameTap workflows:
|
||||||
|
|
||||||
|
- load a DBC file
|
||||||
|
- browse frames and signals
|
||||||
|
- search signals
|
||||||
|
- drag a signal into a plot later
|
||||||
|
- decode live CAN traffic
|
||||||
|
- decode recorded traces
|
||||||
|
- convert raw values into physical values
|
||||||
|
- show metadata like units, comments, transmitter, receivers, and PGN
|
||||||
|
|
||||||
|
Example combined workflow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Load DBC
|
||||||
|
↓
|
||||||
|
Parse into DbcDatabase
|
||||||
|
↓
|
||||||
|
Build UI tree
|
||||||
|
↓
|
||||||
|
Build DecodeDatabase
|
||||||
|
↓
|
||||||
|
Use same decode engine for:
|
||||||
|
- live frames
|
||||||
|
- trace replay
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why the Tree Is Not Enough
|
||||||
|
|
||||||
|
The tree exists for browsing.
|
||||||
|
|
||||||
|
However, runtime decode should not rely on tree traversal because that would introduce unnecessary coupling and inefficiency.
|
||||||
|
|
||||||
|
A runtime decoder needs:
|
||||||
|
|
||||||
|
- fast key-based access
|
||||||
|
- minimal interpretation at decode time
|
||||||
|
- direct signal definitions already prepared
|
||||||
|
|
||||||
|
That is why `DecodeDatabase` is a separate layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why No Abstract Factory Is Used
|
||||||
|
|
||||||
|
At the current stage, abstract factory is intentionally avoided.
|
||||||
|
|
||||||
|
The current design is already clean:
|
||||||
|
|
||||||
|
```text
|
||||||
|
parser → parsed database → decode database
|
||||||
|
↘ tree builder → UI tree
|
||||||
|
```
|
||||||
|
|
||||||
|
Introducing factory layers now would increase complexity without solving an immediate problem.
|
||||||
|
|
||||||
|
If later the project requires multiple output representations or multiple build strategies, that can be added then.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Limitations
|
||||||
|
|
||||||
|
This is still a minimal implementation.
|
||||||
|
|
||||||
|
Not supported yet:
|
||||||
|
|
||||||
|
- multiplexed signals
|
||||||
|
- `VAL_` tables
|
||||||
|
- `BA_` attributes
|
||||||
|
- `BA_DEF_` definitions
|
||||||
|
- advanced comment handling
|
||||||
|
- full DBC grammar coverage
|
||||||
|
- full J1939 validation
|
||||||
|
- extensive edge-case handling for unusual DBC formatting
|
||||||
|
|
||||||
|
Motorola extraction is implemented, but it should still be verified carefully against real-world DBC files and expected values.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Next Steps
|
||||||
|
|
||||||
|
A practical development order would be:
|
||||||
|
|
||||||
|
### Stage 1 - already implemented
|
||||||
|
|
||||||
|
- `BO_`
|
||||||
|
- `SG_`
|
||||||
|
- `CM_`
|
||||||
|
- normalized CAN ID
|
||||||
|
- `isExtended`
|
||||||
|
- transmitter
|
||||||
|
- receivers
|
||||||
|
- comments
|
||||||
|
- tree representation
|
||||||
|
- runtime decode database
|
||||||
|
- runtime decoder
|
||||||
|
|
||||||
|
### Stage 2
|
||||||
|
|
||||||
|
Recommended additions:
|
||||||
|
|
||||||
|
- parent pointer in `TreeNode`
|
||||||
|
- Qt model adapter
|
||||||
|
- `VAL_` support for enum-style signals
|
||||||
|
- better display strings for UI
|
||||||
|
- selective decoding of only chosen signals
|
||||||
|
|
||||||
|
### Stage 3
|
||||||
|
|
||||||
|
Recommended additions:
|
||||||
|
|
||||||
|
- multiplexing support
|
||||||
|
- attribute parsing
|
||||||
|
- richer J1939 support
|
||||||
|
- CSV export of decoded traces
|
||||||
|
- optimized filtering and signal selection
|
||||||
|
|
||||||
|
### Stage 4
|
||||||
|
|
||||||
|
Advanced functionality:
|
||||||
|
|
||||||
|
- live plot integration
|
||||||
|
- signal subscriptions
|
||||||
|
- per-signal trace decode pipelines
|
||||||
|
- decoder-assisted export formats
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Integration
|
||||||
|
|
||||||
|
The module does not depend on any specific build system.
|
||||||
|
|
||||||
|
It can be integrated with:
|
||||||
|
|
||||||
|
- CMake
|
||||||
|
- qmake
|
||||||
|
- Makefile
|
||||||
|
|
||||||
|
Just add the source files to the project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This module is now split into two intentionally separate layers:
|
||||||
|
|
||||||
|
### Parsed representation
|
||||||
|
|
||||||
|
Used for:
|
||||||
|
|
||||||
|
- storing parsed DBC content
|
||||||
|
- browsing
|
||||||
|
- UI tree generation
|
||||||
|
|
||||||
|
### Runtime decode representation
|
||||||
|
|
||||||
|
Used for:
|
||||||
|
|
||||||
|
- fast frame lookup
|
||||||
|
- live CAN decode
|
||||||
|
- trace decode
|
||||||
|
- physical value conversion
|
||||||
|
|
||||||
|
That separation is the main architectural improvement.
|
||||||
|
|
||||||
|
In short, the system now looks like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DBC parser → DbcDatabase → DecodeDatabase → DbcDecoder
|
||||||
|
↘ TreeNode → future UI
|
||||||
|
```
|
||||||
|
|
||||||
|
This gives FrameTap a much better foundation for real use, because both browsing and decoding are supported without forcing one representation to do the other's job.
|
||||||
|
|||||||
23
dbc.pro
Normal file
23
dbc.pro
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += console c++17
|
||||||
|
CONFIG -= app_bundle
|
||||||
|
CONFIG -= qt
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
dbc_decode_builder.cpp \
|
||||||
|
dbc_decoder.cpp \
|
||||||
|
dbc_parser.cpp \
|
||||||
|
dbc_tree_builder.cpp \
|
||||||
|
main.cpp \
|
||||||
|
tree_node.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
dbc_database.h \
|
||||||
|
dbc_decode_builder.h \
|
||||||
|
dbc_decoder.h \
|
||||||
|
dbc_parser.h \
|
||||||
|
dbc_tree_builder.h \
|
||||||
|
decode_database.h \
|
||||||
|
frame_info.h \
|
||||||
|
signal_info.h \
|
||||||
|
tree_node.h
|
||||||
15
dbc_database.h
Normal file
15
dbc_database.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef DBC_DATABASE_H
|
||||||
|
#define DBC_DATABASE_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "frame_info.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parsed DBC content stored in a simple internal form.
|
||||||
|
*/
|
||||||
|
struct DbcDatabase {
|
||||||
|
std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DBC_DATABASE_H */
|
||||||
45
dbc_decode_builder.cpp
Normal file
45
dbc_decode_builder.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "dbc_decode_builder.h"
|
||||||
|
|
||||||
|
DecodeDatabase DbcDecodeBuilder::Build (const DbcDatabase &source) const {
|
||||||
|
DecodeDatabase result;
|
||||||
|
|
||||||
|
for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) {
|
||||||
|
const FrameInfo &sourceFrame = source.frames[frameIndex];
|
||||||
|
DecodeFrame targetFrame;
|
||||||
|
|
||||||
|
targetFrame.name = sourceFrame.name;
|
||||||
|
targetFrame.canId = sourceFrame.canId;
|
||||||
|
targetFrame.isExtended = sourceFrame.isExtended;
|
||||||
|
targetFrame.dlc = sourceFrame.dlc;
|
||||||
|
targetFrame.pgn = sourceFrame.pgn;
|
||||||
|
targetFrame.hasPgn = sourceFrame.hasPgn;
|
||||||
|
targetFrame.transmitter = sourceFrame.transmitter;
|
||||||
|
targetFrame.comment = sourceFrame.comment;
|
||||||
|
|
||||||
|
for (std::size_t signalIndex = 0U; signalIndex < sourceFrame.signals.size(); ++signalIndex) {
|
||||||
|
const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex];
|
||||||
|
DecodeSignal targetSignal;
|
||||||
|
|
||||||
|
targetSignal.name = sourceSignal.name;
|
||||||
|
targetSignal.startBit = sourceSignal.startBit;
|
||||||
|
targetSignal.length = sourceSignal.length;
|
||||||
|
targetSignal.byteOrder = sourceSignal.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola;
|
||||||
|
targetSignal.valueType = sourceSignal.isSigned ? ValueType::Signed : ValueType::Unsigned;
|
||||||
|
targetSignal.factor = sourceSignal.factor;
|
||||||
|
targetSignal.offset = sourceSignal.offset;
|
||||||
|
targetSignal.minimum = sourceSignal.minimum;
|
||||||
|
targetSignal.maximum = sourceSignal.maximum;
|
||||||
|
targetSignal.unit = sourceSignal.unit;
|
||||||
|
targetSignal.receivers = sourceSignal.receivers;
|
||||||
|
targetSignal.comment = sourceSignal.comment;
|
||||||
|
|
||||||
|
targetFrame.signals.push_back (targetSignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.frames.push_back (targetFrame);
|
||||||
|
result.frameIndexByKey[FrameKey (targetFrame.canId, targetFrame.isExtended)] =
|
||||||
|
result.frames.size() - 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
20
dbc_decode_builder.h
Normal file
20
dbc_decode_builder.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef DBC_DECODE_BUILDER_H
|
||||||
|
#define DBC_DECODE_BUILDER_H
|
||||||
|
|
||||||
|
#include "dbc_database.h"
|
||||||
|
#include "decode_database.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts parsed DBC data into runtime decode database.
|
||||||
|
*/
|
||||||
|
class DbcDecodeBuilder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Build runtime decode database.
|
||||||
|
* @param source Parsed DBC database.
|
||||||
|
* @return Runtime-ready decode database.
|
||||||
|
*/
|
||||||
|
DecodeDatabase Build (const DbcDatabase &source) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DBC_DECODE_BUILDER_H */
|
||||||
146
dbc_decoder.cpp
Normal file
146
dbc_decoder.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#include "dbc_decoder.h"
|
||||||
|
|
||||||
|
const DecodeFrame *DbcDecoder::FindFrame (const DecodeDatabase &database,
|
||||||
|
std::uint32_t canId,
|
||||||
|
bool isExtended) const {
|
||||||
|
const FrameKey key (canId, isExtended);
|
||||||
|
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
|
||||||
|
database.frameIndexByKey.find (key);
|
||||||
|
|
||||||
|
if (it == database.frameIndexByKey.end())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const std::size_t index = it->second;
|
||||||
|
if (index >= database.frames.size())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &database.frames[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedFrameValue DbcDecoder::Decode (const DecodeDatabase &database,
|
||||||
|
const RawCanFrame &frame) const {
|
||||||
|
DecodedFrameValue result;
|
||||||
|
const DecodeFrame *definition = FindFrame (database, frame.canId, frame.isExtended);
|
||||||
|
|
||||||
|
if (definition == nullptr)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.definition = definition;
|
||||||
|
result.valid = true;
|
||||||
|
|
||||||
|
for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) {
|
||||||
|
const DecodeSignal &signal = definition->signals[signalIndex];
|
||||||
|
DecodedSignalValue decoded;
|
||||||
|
std::uint64_t unsignedValue = 0U;
|
||||||
|
|
||||||
|
decoded.definition = &signal;
|
||||||
|
|
||||||
|
if (!ExtractUnsigned (frame.data, signal, unsignedValue)) {
|
||||||
|
decoded.valid = false;
|
||||||
|
result.signals.push_back (decoded);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal.valueType == ValueType::Signed)
|
||||||
|
decoded.rawValue = SignExtend (unsignedValue, signal.length);
|
||||||
|
else
|
||||||
|
decoded.rawValue = static_cast<std::int64_t> (unsignedValue);
|
||||||
|
|
||||||
|
decoded.physicalValue =
|
||||||
|
(static_cast<double> (decoded.rawValue) * signal.factor) + signal.offset;
|
||||||
|
decoded.valid = true;
|
||||||
|
|
||||||
|
result.signals.push_back (decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DbcDecoder::ExtractUnsigned (const std::vector<std::uint8_t> &data,
|
||||||
|
const DecodeSignal &signal,
|
||||||
|
std::uint64_t &value) {
|
||||||
|
if ((signal.length == 0U) || (signal.length > 64U))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (signal.byteOrder == ByteOrder::Intel)
|
||||||
|
return ExtractIntel (data, signal.startBit, signal.length, value);
|
||||||
|
|
||||||
|
return ExtractMotorola (data, signal.startBit, signal.length, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DbcDecoder::ExtractIntel (const std::vector<std::uint8_t> &data,
|
||||||
|
std::uint32_t startBit,
|
||||||
|
std::uint32_t length,
|
||||||
|
std::uint64_t &value) {
|
||||||
|
value = 0U;
|
||||||
|
|
||||||
|
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
|
||||||
|
const std::uint32_t absoluteBit = startBit + bitIndex;
|
||||||
|
const std::uint32_t byteIndex = absoluteBit / 8U;
|
||||||
|
const std::uint32_t bitInByte = absoluteBit % 8U;
|
||||||
|
|
||||||
|
if (byteIndex >= data.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const std::uint64_t bitValue =
|
||||||
|
(static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
|
||||||
|
|
||||||
|
value |= bitValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DbcDecoder::ExtractMotorola (const std::vector<std::uint8_t> &data,
|
||||||
|
std::uint32_t startBit,
|
||||||
|
std::uint32_t length,
|
||||||
|
std::uint64_t &value) {
|
||||||
|
/*
|
||||||
|
* DBC Motorola bit numbering:
|
||||||
|
* - startBit points to the most significant bit of the signal
|
||||||
|
* - inside a byte, bit numbering goes 7..0
|
||||||
|
* - crossing byte boundary moves to the next byte, bit 7
|
||||||
|
*/
|
||||||
|
|
||||||
|
value = 0U;
|
||||||
|
|
||||||
|
std::int32_t currentBit = static_cast<std::int32_t> (startBit);
|
||||||
|
|
||||||
|
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
|
||||||
|
if (currentBit < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const std::uint32_t absoluteBit = static_cast<std::uint32_t> (currentBit);
|
||||||
|
const std::uint32_t byteIndex = absoluteBit / 8U;
|
||||||
|
const std::uint32_t bitFromMsb = absoluteBit % 8U;
|
||||||
|
const std::uint32_t bitInByte = 7U - bitFromMsb;
|
||||||
|
|
||||||
|
if (byteIndex >= data.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value <<= 1U;
|
||||||
|
value |= static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U);
|
||||||
|
|
||||||
|
if ((absoluteBit % 8U) == 7U)
|
||||||
|
currentBit = static_cast<std::int32_t> ((byteIndex + 1U) * 8U);
|
||||||
|
else
|
||||||
|
--currentBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t DbcDecoder::SignExtend (std::uint64_t value, std::uint32_t bitLength) {
|
||||||
|
if ((bitLength == 0U) || (bitLength >= 64U))
|
||||||
|
return static_cast<std::int64_t> (value);
|
||||||
|
|
||||||
|
const std::uint64_t signMask = (static_cast<std::uint64_t> (1U) << (bitLength - 1U));
|
||||||
|
const std::uint64_t valueMask = (static_cast<std::uint64_t> (1U) << bitLength) - 1U;
|
||||||
|
|
||||||
|
value &= valueMask;
|
||||||
|
|
||||||
|
if ((value & signMask) == 0U)
|
||||||
|
return static_cast<std::int64_t> (value);
|
||||||
|
|
||||||
|
return static_cast<std::int64_t> (value | (~valueMask));
|
||||||
|
}
|
||||||
99
dbc_decoder.h
Normal file
99
dbc_decoder.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#ifndef DBC_DECODER_H
|
||||||
|
#define DBC_DECODER_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "decode_database.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Raw CAN frame used for runtime or trace decoding.
|
||||||
|
*/
|
||||||
|
struct RawCanFrame {
|
||||||
|
std::uint32_t canId; /**< Normalized CAN ID. */
|
||||||
|
bool isExtended; /**< true for extended frame. */
|
||||||
|
std::vector<std::uint8_t> data; /**< Payload bytes. */
|
||||||
|
|
||||||
|
RawCanFrame()
|
||||||
|
: canId (0U)
|
||||||
|
, isExtended (false)
|
||||||
|
, data() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief One decoded signal value.
|
||||||
|
*/
|
||||||
|
struct DecodedSignalValue {
|
||||||
|
const DecodeSignal *definition; /**< Signal definition. */
|
||||||
|
std::int64_t rawValue; /**< Extracted raw integer value. */
|
||||||
|
double physicalValue; /**< Converted physical value. */
|
||||||
|
bool valid; /**< true if decoding succeeded. */
|
||||||
|
|
||||||
|
DecodedSignalValue()
|
||||||
|
: definition (nullptr)
|
||||||
|
, rawValue (0)
|
||||||
|
, physicalValue (0.0)
|
||||||
|
, valid (false) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fully decoded frame.
|
||||||
|
*/
|
||||||
|
struct DecodedFrameValue {
|
||||||
|
const DecodeFrame *definition; /**< Frame definition. */
|
||||||
|
std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */
|
||||||
|
bool valid; /**< true if frame was matched. */
|
||||||
|
|
||||||
|
DecodedFrameValue()
|
||||||
|
: definition (nullptr)
|
||||||
|
, signals()
|
||||||
|
, valid (false) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runtime CAN decoder using prebuilt decode database.
|
||||||
|
*/
|
||||||
|
class DbcDecoder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Find frame definition by CAN ID.
|
||||||
|
* @param database Runtime decode database.
|
||||||
|
* @param canId Normalized CAN ID.
|
||||||
|
* @param isExtended true for extended frame.
|
||||||
|
* @return Pointer to frame definition or nullptr.
|
||||||
|
*/
|
||||||
|
const DecodeFrame *FindFrame (const DecodeDatabase &database,
|
||||||
|
std::uint32_t canId,
|
||||||
|
bool isExtended) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decode one raw CAN frame.
|
||||||
|
* @param database Runtime decode database.
|
||||||
|
* @param frame Raw CAN frame.
|
||||||
|
* @return Decoded frame value.
|
||||||
|
*/
|
||||||
|
DecodedFrameValue Decode (const DecodeDatabase &database,
|
||||||
|
const RawCanFrame &frame) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool ExtractUnsigned (const std::vector<std::uint8_t> &data,
|
||||||
|
const DecodeSignal &signal,
|
||||||
|
std::uint64_t &value);
|
||||||
|
|
||||||
|
static bool ExtractIntel (const std::vector<std::uint8_t> &data,
|
||||||
|
std::uint32_t startBit,
|
||||||
|
std::uint32_t length,
|
||||||
|
std::uint64_t &value);
|
||||||
|
|
||||||
|
static bool ExtractMotorola (const std::vector<std::uint8_t> &data,
|
||||||
|
std::uint32_t startBit,
|
||||||
|
std::uint32_t length,
|
||||||
|
std::uint64_t &value);
|
||||||
|
|
||||||
|
static std::int64_t SignExtend (std::uint64_t value, std::uint32_t bitLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DBC_DECODER_H */
|
||||||
345
dbc_parser.cpp
Normal file
345
dbc_parser.cpp
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
#include "dbc_parser.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/**
|
||||||
|
* @brief Remove leading and trailing spaces.
|
||||||
|
* @param text Input text.
|
||||||
|
* @return Trimmed text.
|
||||||
|
*/
|
||||||
|
std::string TrimText (const std::string &text) {
|
||||||
|
std::string::size_type begin = 0U;
|
||||||
|
while ((begin < text.size()) &&
|
||||||
|
std::isspace (static_cast<unsigned char> (text[begin])))
|
||||||
|
++begin;
|
||||||
|
|
||||||
|
std::string::size_type end = text.size();
|
||||||
|
while ((end > begin) &&
|
||||||
|
std::isspace (static_cast<unsigned char> (text[end - 1U])))
|
||||||
|
--end;
|
||||||
|
|
||||||
|
return text.substr (begin, end - begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DbcDatabase DbcParser::ParseFile (const std::string &filePath) const {
|
||||||
|
std::ifstream input (filePath.c_str());
|
||||||
|
if (!input.is_open())
|
||||||
|
throw std::runtime_error ("Failed to open DBC file: " + filePath);
|
||||||
|
|
||||||
|
DbcDatabase database;
|
||||||
|
FrameInfo *currentFrame = nullptr;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline (input, line)) {
|
||||||
|
line = Trim (line);
|
||||||
|
if (line.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (IsFrameLine (line)) {
|
||||||
|
FrameInfo frame = ParseFrameLine (line);
|
||||||
|
database.frames.push_back (frame);
|
||||||
|
currentFrame = &database.frames.back();
|
||||||
|
} else if (IsSignalLine (line)) {
|
||||||
|
if (currentFrame == nullptr)
|
||||||
|
throw std::runtime_error ("Signal found before any frame definition.");
|
||||||
|
|
||||||
|
SignalInfo signal = ParseSignalLine (line);
|
||||||
|
currentFrame->signals.push_back (signal);
|
||||||
|
} else if (IsCommentLine (line))
|
||||||
|
ParseCommentLine (line, database);
|
||||||
|
}
|
||||||
|
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DbcParser::IsFrameLine (const std::string &line) {
|
||||||
|
return (line.size() >= 4U) && (line.compare (0U, 4U, "BO_ ") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DbcParser::IsSignalLine (const std::string &line) {
|
||||||
|
return (line.size() >= 4U) && (line.compare (0U, 4U, "SG_ ") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DbcParser::IsCommentLine (const std::string &line) {
|
||||||
|
return (line.size() >= 4U) && (line.compare (0U, 4U, "CM_ ") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DbcParser::Trim (const std::string &text) {
|
||||||
|
return TrimText (text);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> DbcParser::SplitReceivers (const std::string &text) {
|
||||||
|
std::vector<std::string> receivers;
|
||||||
|
std::string token;
|
||||||
|
std::istringstream stream (text);
|
||||||
|
|
||||||
|
while (std::getline (stream, token, ',')) {
|
||||||
|
token = TrimText (token);
|
||||||
|
if (!token.empty())
|
||||||
|
receivers.push_back (token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return receivers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbcParser::NormalizeCanId (std::uint32_t rawCanId,
|
||||||
|
std::uint32_t &normalizedCanId,
|
||||||
|
bool &isExtended) {
|
||||||
|
/*
|
||||||
|
* DBC commonly stores extended identifiers with bit 31 set.
|
||||||
|
* Example:
|
||||||
|
* raw id = 0x80000000 | actual_29_bit_id
|
||||||
|
*/
|
||||||
|
if ((rawCanId & 0x80000000U) != 0U) {
|
||||||
|
isExtended = true;
|
||||||
|
normalizedCanId = (rawCanId & 0x1FFFFFFFU);
|
||||||
|
} else {
|
||||||
|
isExtended = (rawCanId > 0x7FFU);
|
||||||
|
normalizedCanId = rawCanId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t DbcParser::TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn) {
|
||||||
|
hasPgn = false;
|
||||||
|
|
||||||
|
if (!isExtended)
|
||||||
|
return 0U;
|
||||||
|
|
||||||
|
if ((canId & 0x1FFFFFFFU) != canId)
|
||||||
|
return 0U;
|
||||||
|
|
||||||
|
const std::uint32_t pf = (canId >> 16U) & 0xFFU;
|
||||||
|
const std::uint32_t ps = (canId >> 8U) & 0xFFU;
|
||||||
|
const std::uint32_t dp = (canId >> 24U) & 0x01U;
|
||||||
|
|
||||||
|
std::uint32_t pgn = 0U;
|
||||||
|
|
||||||
|
if (pf < 240U)
|
||||||
|
pgn = (dp << 16U) | (pf << 8U);
|
||||||
|
else
|
||||||
|
pgn = (dp << 16U) | (pf << 8U) | ps;
|
||||||
|
|
||||||
|
hasPgn = true;
|
||||||
|
return pgn;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameInfo DbcParser::ParseFrameLine (const std::string &line) {
|
||||||
|
/*
|
||||||
|
* Example:
|
||||||
|
* BO_ 256 EngineData: 8 EEC1
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::istringstream stream (line);
|
||||||
|
std::string token;
|
||||||
|
FrameInfo frame;
|
||||||
|
std::uint32_t rawCanId = 0U;
|
||||||
|
|
||||||
|
stream >> token;
|
||||||
|
if (token != "BO_")
|
||||||
|
throw std::runtime_error ("Invalid frame line: " + line);
|
||||||
|
|
||||||
|
stream >> rawCanId;
|
||||||
|
NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
|
||||||
|
|
||||||
|
stream >> token;
|
||||||
|
if (token.empty())
|
||||||
|
throw std::runtime_error ("Missing frame name: " + line);
|
||||||
|
|
||||||
|
if (token[token.size() - 1U] == ':')
|
||||||
|
token.erase (token.size() - 1U, 1U);
|
||||||
|
|
||||||
|
frame.name = token;
|
||||||
|
|
||||||
|
{
|
||||||
|
unsigned int dlcValue = 0U;
|
||||||
|
stream >> dlcValue;
|
||||||
|
frame.dlc = static_cast<std::uint8_t> (dlcValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream >> frame.transmitter;
|
||||||
|
frame.pgn = TryExtractPgn (frame.canId, frame.isExtended, frame.hasPgn);
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalInfo DbcParser::ParseSignalLine (const std::string &line) {
|
||||||
|
/*
|
||||||
|
* Example:
|
||||||
|
* SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
|
||||||
|
*/
|
||||||
|
|
||||||
|
SignalInfo signal;
|
||||||
|
|
||||||
|
std::string work = TrimText (line);
|
||||||
|
if (work.compare (0U, 4U, "SG_ ") != 0)
|
||||||
|
throw std::runtime_error ("Invalid signal line: " + line);
|
||||||
|
|
||||||
|
work.erase (0U, 4U);
|
||||||
|
|
||||||
|
const std::string::size_type colonPos = work.find (':');
|
||||||
|
if (colonPos == std::string::npos)
|
||||||
|
throw std::runtime_error ("Signal line missing ':' : " + line);
|
||||||
|
|
||||||
|
signal.name = TrimText (work.substr (0U, colonPos));
|
||||||
|
std::string rest = TrimText (work.substr (colonPos + 1U));
|
||||||
|
|
||||||
|
const std::string::size_type pipePos = rest.find ('|');
|
||||||
|
const std::string::size_type atPos = rest.find ('@');
|
||||||
|
const std::string::size_type signPos = rest.find_first_of ("+-", atPos);
|
||||||
|
const std::string::size_type factorBegin = rest.find ('(');
|
||||||
|
const std::string::size_type factorComma = rest.find (',', factorBegin);
|
||||||
|
const std::string::size_type factorEnd = rest.find (')', factorComma);
|
||||||
|
const std::string::size_type rangeBegin = rest.find ('[');
|
||||||
|
const std::string::size_type rangeSep = rest.find ('|', rangeBegin);
|
||||||
|
const std::string::size_type rangeEnd = rest.find (']', rangeSep);
|
||||||
|
const std::string::size_type unitBegin = rest.find ('"', rangeEnd);
|
||||||
|
const std::string::size_type unitEnd = rest.find ('"', unitBegin + 1U);
|
||||||
|
|
||||||
|
if ((pipePos == std::string::npos) ||
|
||||||
|
(atPos == std::string::npos) ||
|
||||||
|
(signPos == std::string::npos) ||
|
||||||
|
(factorBegin == std::string::npos) ||
|
||||||
|
(factorComma == std::string::npos) ||
|
||||||
|
(factorEnd == std::string::npos) ||
|
||||||
|
(rangeBegin == std::string::npos) ||
|
||||||
|
(rangeSep == std::string::npos) ||
|
||||||
|
(rangeEnd == std::string::npos) ||
|
||||||
|
(unitBegin == std::string::npos) ||
|
||||||
|
(unitEnd == std::string::npos))
|
||||||
|
throw std::runtime_error ("Unsupported signal syntax: " + line);
|
||||||
|
|
||||||
|
signal.startBit = static_cast<std::uint32_t> (
|
||||||
|
std::stoul (TrimText (rest.substr (0U, pipePos)))
|
||||||
|
);
|
||||||
|
|
||||||
|
signal.length = static_cast<std::uint32_t> (
|
||||||
|
std::stoul (TrimText (rest.substr (pipePos + 1U, atPos - pipePos - 1U)))
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
if ((atPos + 1U) >= rest.size())
|
||||||
|
throw std::runtime_error ("Invalid endianness in signal: " + line);
|
||||||
|
|
||||||
|
const char endianChar = rest[atPos + 1U];
|
||||||
|
signal.isLittleEndian = (endianChar == '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char signChar = rest[signPos];
|
||||||
|
signal.isSigned = (signChar == '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
signal.factor = std::stod (
|
||||||
|
TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U))
|
||||||
|
);
|
||||||
|
|
||||||
|
signal.offset = std::stod (
|
||||||
|
TrimText (rest.substr (factorComma + 1U, factorEnd - factorComma - 1U))
|
||||||
|
);
|
||||||
|
|
||||||
|
signal.minimum = std::stod (
|
||||||
|
TrimText (rest.substr (rangeBegin + 1U, rangeSep - rangeBegin - 1U))
|
||||||
|
);
|
||||||
|
|
||||||
|
signal.maximum = std::stod (
|
||||||
|
TrimText (rest.substr (rangeSep + 1U, rangeEnd - rangeSep - 1U))
|
||||||
|
);
|
||||||
|
|
||||||
|
signal.unit = rest.substr (unitBegin + 1U, unitEnd - unitBegin - 1U);
|
||||||
|
|
||||||
|
{
|
||||||
|
const std::string receiversText = TrimText (rest.substr (unitEnd + 1U));
|
||||||
|
signal.receivers = SplitReceivers (receiversText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbcParser::ParseCommentLine (const std::string &line, DbcDatabase &database) {
|
||||||
|
/*
|
||||||
|
* Examples:
|
||||||
|
* CM_ BO_ 256 "Frame comment";
|
||||||
|
* CM_ SG_ 256 EngineSpeed "Signal comment";
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::istringstream stream (line);
|
||||||
|
std::string token;
|
||||||
|
stream >> token;
|
||||||
|
|
||||||
|
if (token != "CM_")
|
||||||
|
return;
|
||||||
|
|
||||||
|
stream >> token;
|
||||||
|
|
||||||
|
if (token == "BO_") {
|
||||||
|
std::uint32_t rawCanId = 0U;
|
||||||
|
std::uint32_t canId = 0U;
|
||||||
|
bool isExtended = false;
|
||||||
|
|
||||||
|
stream >> rawCanId;
|
||||||
|
NormalizeCanId (rawCanId, canId, isExtended);
|
||||||
|
|
||||||
|
const std::string::size_type quoteBegin = line.find ('"');
|
||||||
|
const std::string::size_type quoteEnd = line.rfind ('"');
|
||||||
|
|
||||||
|
if ((quoteBegin == std::string::npos) ||
|
||||||
|
(quoteEnd == std::string::npos) ||
|
||||||
|
(quoteEnd <= quoteBegin))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FrameInfo *frame = FindFrameById (database, canId, isExtended);
|
||||||
|
if (frame != nullptr)
|
||||||
|
frame->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
|
||||||
|
} else if (token == "SG_") {
|
||||||
|
std::uint32_t rawCanId = 0U;
|
||||||
|
std::uint32_t canId = 0U;
|
||||||
|
bool isExtended = false;
|
||||||
|
std::string signalName;
|
||||||
|
|
||||||
|
stream >> rawCanId;
|
||||||
|
stream >> signalName;
|
||||||
|
|
||||||
|
NormalizeCanId (rawCanId, canId, isExtended);
|
||||||
|
|
||||||
|
const std::string::size_type quoteBegin = line.find ('"');
|
||||||
|
const std::string::size_type quoteEnd = line.rfind ('"');
|
||||||
|
|
||||||
|
if ((quoteBegin == std::string::npos) ||
|
||||||
|
(quoteEnd == std::string::npos) ||
|
||||||
|
(quoteEnd <= quoteBegin))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FrameInfo *frame = FindFrameById (database, canId, isExtended);
|
||||||
|
if (frame != nullptr) {
|
||||||
|
SignalInfo *signal = FindSignalByName (*frame, signalName);
|
||||||
|
if (signal != nullptr)
|
||||||
|
signal->comment = line.substr (quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameInfo *DbcParser::FindFrameById (DbcDatabase &database,
|
||||||
|
std::uint32_t canId,
|
||||||
|
bool isExtended) {
|
||||||
|
for (std::size_t index = 0U; index < database.frames.size(); ++index) {
|
||||||
|
if ((database.frames[index].canId == canId) &&
|
||||||
|
(database.frames[index].isExtended == isExtended))
|
||||||
|
return &database.frames[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalInfo *DbcParser::FindSignalByName (FrameInfo &frame, const std::string &signalName) {
|
||||||
|
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
|
||||||
|
if (frame.signals[index].name == signalName)
|
||||||
|
return &frame.signals[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
58
dbc_parser.h
Normal file
58
dbc_parser.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef DBC_PARSER_H
|
||||||
|
#define DBC_PARSER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "dbc_database.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Minimal DBC parser.
|
||||||
|
*
|
||||||
|
* Supports:
|
||||||
|
* - BO_
|
||||||
|
* - SG_
|
||||||
|
* - CM_ BO_
|
||||||
|
* - CM_ SG_
|
||||||
|
*
|
||||||
|
* Ignores:
|
||||||
|
* - attributes
|
||||||
|
* - multiplexing
|
||||||
|
* - value tables
|
||||||
|
*/
|
||||||
|
class DbcParser {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Parse DBC file.
|
||||||
|
* @param filePath Path to input file.
|
||||||
|
* @return Parsed database.
|
||||||
|
* @throws std::runtime_error on file or parse errors.
|
||||||
|
*/
|
||||||
|
DbcDatabase ParseFile (const std::string &filePath) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool IsFrameLine (const std::string &line);
|
||||||
|
static bool IsSignalLine (const std::string &line);
|
||||||
|
static bool IsCommentLine (const std::string &line);
|
||||||
|
static std::string Trim (const std::string &text);
|
||||||
|
static std::vector<std::string> SplitReceivers (const std::string &text);
|
||||||
|
static std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn);
|
||||||
|
|
||||||
|
static void NormalizeCanId (std::uint32_t rawCanId,
|
||||||
|
std::uint32_t &normalizedCanId,
|
||||||
|
bool &isExtended);
|
||||||
|
|
||||||
|
static FrameInfo ParseFrameLine (const std::string &line);
|
||||||
|
static SignalInfo ParseSignalLine (const std::string &line);
|
||||||
|
|
||||||
|
static void ParseCommentLine (const std::string &line, DbcDatabase &database);
|
||||||
|
|
||||||
|
static FrameInfo *FindFrameById (DbcDatabase &database,
|
||||||
|
std::uint32_t canId,
|
||||||
|
bool isExtended);
|
||||||
|
|
||||||
|
static SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DBC_PARSER_H */
|
||||||
20
dbc_tree_builder.cpp
Normal file
20
dbc_tree_builder.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "dbc_tree_builder.h"
|
||||||
|
|
||||||
|
std::unique_ptr<TreeNode> DbcTreeBuilder::Build (const DbcDatabase &database) const {
|
||||||
|
std::unique_ptr<TreeNode> root (new TreeNode());
|
||||||
|
|
||||||
|
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
|
||||||
|
const FrameInfo &frame = database.frames[frameIndex];
|
||||||
|
std::unique_ptr<TreeNode> frameNode (new TreeNode (frame));
|
||||||
|
|
||||||
|
for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) {
|
||||||
|
const SignalInfo &signal = frame.signals[signalIndex];
|
||||||
|
std::unique_ptr<TreeNode> signalNode (new TreeNode (signal));
|
||||||
|
frameNode->AddChild (std::move (signalNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
root->AddChild (std::move (frameNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
22
dbc_tree_builder.h
Normal file
22
dbc_tree_builder.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef DBC_TREE_BUILDER_H
|
||||||
|
#define DBC_TREE_BUILDER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "dbc_database.h"
|
||||||
|
#include "tree_node.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Builds a simple tree from parsed DBC database.
|
||||||
|
*/
|
||||||
|
class DbcTreeBuilder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Build tree representation of parsed DBC data.
|
||||||
|
* @param database Parsed database.
|
||||||
|
* @return Root node of the tree.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<TreeNode> Build (const DbcDatabase &database) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DBC_TREE_BUILDER_H */
|
||||||
126
decode_database.h
Normal file
126
decode_database.h
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#ifndef DECODE_DATABASE_H
|
||||||
|
#define DECODE_DATABASE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Signal byte order used for runtime decoding.
|
||||||
|
*/
|
||||||
|
enum class ByteOrder {
|
||||||
|
Intel,
|
||||||
|
Motorola
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Signal numeric type.
|
||||||
|
*/
|
||||||
|
enum class ValueType {
|
||||||
|
Unsigned,
|
||||||
|
Signed
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runtime-ready signal definition.
|
||||||
|
*/
|
||||||
|
struct DecodeSignal {
|
||||||
|
std::string name; /**< Signal name. */
|
||||||
|
std::uint32_t startBit; /**< DBC start bit. */
|
||||||
|
std::uint32_t length; /**< Signal length in bits. */
|
||||||
|
ByteOrder byteOrder; /**< Intel or Motorola. */
|
||||||
|
ValueType valueType; /**< Signed or unsigned. */
|
||||||
|
double factor; /**< Scaling factor. */
|
||||||
|
double offset; /**< Physical offset. */
|
||||||
|
double minimum; /**< Minimum physical value. */
|
||||||
|
double maximum; /**< Maximum physical value. */
|
||||||
|
std::string unit; /**< Physical unit. */
|
||||||
|
std::vector<std::string> receivers; /**< Receivers. */
|
||||||
|
std::string comment; /**< Comment. */
|
||||||
|
|
||||||
|
DecodeSignal()
|
||||||
|
: name()
|
||||||
|
, startBit (0U)
|
||||||
|
, length (0U)
|
||||||
|
, byteOrder (ByteOrder::Intel)
|
||||||
|
, valueType (ValueType::Unsigned)
|
||||||
|
, factor (1.0)
|
||||||
|
, offset (0.0)
|
||||||
|
, minimum (0.0)
|
||||||
|
, maximum (0.0)
|
||||||
|
, unit()
|
||||||
|
, receivers()
|
||||||
|
, comment() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runtime-ready frame definition.
|
||||||
|
*/
|
||||||
|
struct DecodeFrame {
|
||||||
|
std::string name; /**< Frame name. */
|
||||||
|
std::uint32_t canId; /**< Normalized CAN ID. */
|
||||||
|
bool isExtended; /**< true for extended frame. */
|
||||||
|
std::uint8_t dlc; /**< Payload length. */
|
||||||
|
std::uint32_t pgn; /**< PGN if available. */
|
||||||
|
bool hasPgn; /**< true if PGN is valid. */
|
||||||
|
std::string transmitter; /**< Transmitter ECU. */
|
||||||
|
std::string comment; /**< Frame comment. */
|
||||||
|
std::vector<DecodeSignal> signals; /**< Signal definitions. */
|
||||||
|
|
||||||
|
DecodeFrame()
|
||||||
|
: name()
|
||||||
|
, canId (0U)
|
||||||
|
, isExtended (false)
|
||||||
|
, dlc (0U)
|
||||||
|
, pgn (0U)
|
||||||
|
, hasPgn (false)
|
||||||
|
, transmitter()
|
||||||
|
, comment()
|
||||||
|
, signals() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Key for fast frame lookup.
|
||||||
|
*/
|
||||||
|
struct FrameKey {
|
||||||
|
std::uint32_t canId;
|
||||||
|
bool isExtended;
|
||||||
|
|
||||||
|
FrameKey()
|
||||||
|
: canId (0U)
|
||||||
|
, isExtended (false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameKey (std::uint32_t id, bool extended)
|
||||||
|
: canId (id)
|
||||||
|
, isExtended (extended) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator== (const FrameKey &other) const {
|
||||||
|
return (canId == other.canId) && (isExtended == other.isExtended);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hasher for frame key.
|
||||||
|
*/
|
||||||
|
struct FrameKeyHasher {
|
||||||
|
std::size_t operator() (const FrameKey &key) const {
|
||||||
|
const std::size_t a = static_cast<std::size_t> (key.canId);
|
||||||
|
const std::size_t b = key.isExtended ? 1U : 0U;
|
||||||
|
return (a * 1315423911U) ^ b;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runtime decode database with fast lookup by CAN ID.
|
||||||
|
*/
|
||||||
|
struct DecodeDatabase {
|
||||||
|
std::vector<DecodeFrame> frames;
|
||||||
|
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DECODE_DATABASE_H */
|
||||||
37
frame_info.h
Normal file
37
frame_info.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef FRAME_INFO_H
|
||||||
|
#define FRAME_INFO_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "signal_info.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes one CAN frame from a DBC file.
|
||||||
|
*/
|
||||||
|
struct FrameInfo {
|
||||||
|
std::string name; /**< Frame name. */
|
||||||
|
std::uint32_t canId; /**< Normalized CAN identifier. */
|
||||||
|
bool isExtended; /**< true for extended frame. */
|
||||||
|
std::uint32_t pgn; /**< J1939 PGN if applicable. */
|
||||||
|
bool hasPgn; /**< true if PGN was derived. */
|
||||||
|
std::uint8_t dlc; /**< Frame payload length. */
|
||||||
|
std::string transmitter; /**< Transmitter ECU name. */
|
||||||
|
std::string comment; /**< Optional frame comment. */
|
||||||
|
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
|
||||||
|
|
||||||
|
FrameInfo()
|
||||||
|
: name()
|
||||||
|
, canId (0U)
|
||||||
|
, isExtended (false)
|
||||||
|
, pgn (0U)
|
||||||
|
, hasPgn (false)
|
||||||
|
, dlc (0U)
|
||||||
|
, transmitter()
|
||||||
|
, comment()
|
||||||
|
, signals() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* FRAME_INFO_H */
|
||||||
125
main.cpp
Normal file
125
main.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "dbc_parser.h"
|
||||||
|
#include "dbc_tree_builder.h"
|
||||||
|
#include "dbc_decode_builder.h"
|
||||||
|
#include "dbc_decoder.h"
|
||||||
|
|
||||||
|
static void PrintTree (const TreeNode *node, int indent) {
|
||||||
|
if (node == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < indent; ++i)
|
||||||
|
std::cout << " ";
|
||||||
|
|
||||||
|
switch (node->GetType()) {
|
||||||
|
case NodeType::Root:
|
||||||
|
std::cout << "[root] " << node->GetName() << "\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NodeType::Frame: {
|
||||||
|
const FrameInfo *frame = node->GetFrame();
|
||||||
|
std::cout << "[frame] " << node->GetName();
|
||||||
|
|
||||||
|
if (frame != nullptr) {
|
||||||
|
std::cout << " id=0x" << std::hex << frame->canId << std::dec
|
||||||
|
<< " ext=" << (frame->isExtended ? "yes" : "no")
|
||||||
|
<< " dlc=" << static_cast<unsigned int> (frame->dlc);
|
||||||
|
|
||||||
|
if (frame->hasPgn)
|
||||||
|
std::cout << " pgn=" << frame->pgn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NodeType::Signal: {
|
||||||
|
const SignalInfo *signal = node->GetSignal();
|
||||||
|
std::cout << "[signal] " << node->GetName();
|
||||||
|
|
||||||
|
if (signal != nullptr) {
|
||||||
|
std::cout << " start=" << signal->startBit
|
||||||
|
<< " len=" << signal->length
|
||||||
|
<< " unit=" << signal->unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::cout << "[unknown]\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0U; i < node->GetChildCount(); ++i)
|
||||||
|
PrintTree (node->GetChild (i), indent + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintDecodedFrame (const DecodedFrameValue &decoded) {
|
||||||
|
if (!decoded.valid || (decoded.definition == nullptr)) {
|
||||||
|
std::cout << "No frame definition found.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Decoded frame: " << decoded.definition->name << "\n";
|
||||||
|
|
||||||
|
for (std::size_t index = 0U; index < decoded.signals.size(); ++index) {
|
||||||
|
const DecodedSignalValue &signal = decoded.signals[index];
|
||||||
|
|
||||||
|
if ((signal.definition == nullptr) || !signal.valid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::cout << " " << signal.definition->name
|
||||||
|
<< " raw=" << signal.rawValue
|
||||||
|
<< " physical=" << signal.physicalValue
|
||||||
|
<< " " << signal.definition->unit
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char *argv[]) {
|
||||||
|
if (argc < 2) {
|
||||||
|
std::cerr << "Usage: dbc_demo <file.dbc>\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DbcParser parser;
|
||||||
|
DbcDatabase database = parser.ParseFile (argv[1]);
|
||||||
|
|
||||||
|
DbcTreeBuilder treeBuilder;
|
||||||
|
std::unique_ptr<TreeNode> root = treeBuilder.Build (database);
|
||||||
|
|
||||||
|
std::cout << "=== Parsed tree ===\n";
|
||||||
|
PrintTree (root.get(), 0);
|
||||||
|
|
||||||
|
DbcDecodeBuilder decodeBuilder;
|
||||||
|
DecodeDatabase decodeDatabase = decodeBuilder.Build (database);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Example raw frame.
|
||||||
|
* Replace with live CAN frame or trace record later.
|
||||||
|
*/
|
||||||
|
RawCanFrame rawFrame;
|
||||||
|
rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId;
|
||||||
|
rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended;
|
||||||
|
rawFrame.data.resize (8U, 0U);
|
||||||
|
|
||||||
|
DbcDecoder decoder;
|
||||||
|
DecodedFrameValue decoded = decoder.Decode (decodeDatabase, rawFrame);
|
||||||
|
|
||||||
|
std::cout << "\n=== Decoded frame ===\n";
|
||||||
|
PrintDecodedFrame (decoded);
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
std::cerr << "Error: " << ex.what() << "\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
41
signal_info.h
Normal file
41
signal_info.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef SIGNAL_INFO_H
|
||||||
|
#define SIGNAL_INFO_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Describes one signal inside a DBC frame.
|
||||||
|
*/
|
||||||
|
struct SignalInfo {
|
||||||
|
std::string name; /**< Signal name. */
|
||||||
|
std::uint32_t startBit; /**< Start bit in DBC notation. */
|
||||||
|
std::uint32_t length; /**< Signal length in bits. */
|
||||||
|
bool isLittleEndian; /**< true for Intel, false for Motorola. */
|
||||||
|
bool isSigned; /**< true if signal is signed. */
|
||||||
|
double factor; /**< Scaling factor. */
|
||||||
|
double offset; /**< Physical offset. */
|
||||||
|
double minimum; /**< Minimum physical value. */
|
||||||
|
double maximum; /**< Maximum physical value. */
|
||||||
|
std::string unit; /**< Physical unit. */
|
||||||
|
std::vector<std::string> receivers; /**< Signal receivers. */
|
||||||
|
std::string comment; /**< Optional signal comment. */
|
||||||
|
|
||||||
|
SignalInfo()
|
||||||
|
: name()
|
||||||
|
, startBit (0U)
|
||||||
|
, length (0U)
|
||||||
|
, isLittleEndian (true)
|
||||||
|
, isSigned (false)
|
||||||
|
, factor (1.0)
|
||||||
|
, offset (0.0)
|
||||||
|
, minimum (0.0)
|
||||||
|
, maximum (0.0)
|
||||||
|
, unit()
|
||||||
|
, receivers()
|
||||||
|
, comment() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* SIGNAL_INFO_H */
|
||||||
64
tree_node.cpp
Normal file
64
tree_node.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include "tree_node.h"
|
||||||
|
|
||||||
|
TreeNode::TreeNode()
|
||||||
|
: m_type (NodeType::Root)
|
||||||
|
, m_name ("dbc")
|
||||||
|
, m_children()
|
||||||
|
, m_frame()
|
||||||
|
, m_signal() {
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNode::TreeNode (const FrameInfo &frame)
|
||||||
|
: m_type (NodeType::Frame)
|
||||||
|
, m_name (frame.name)
|
||||||
|
, m_children()
|
||||||
|
, m_frame (new FrameInfo (frame))
|
||||||
|
, m_signal() {
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNode::TreeNode (const SignalInfo &signal)
|
||||||
|
: m_type (NodeType::Signal)
|
||||||
|
, m_name (signal.name)
|
||||||
|
, m_children()
|
||||||
|
, m_frame()
|
||||||
|
, m_signal (new SignalInfo (signal)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeNode::AddChild (std::unique_ptr<TreeNode> child) {
|
||||||
|
if (child)
|
||||||
|
m_children.push_back (std::move (child));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t TreeNode::GetChildCount() const {
|
||||||
|
return m_children.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TreeNode *TreeNode::GetChild (std::size_t index) const {
|
||||||
|
if (index >= m_children.size())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return m_children[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNode *TreeNode::GetChild (std::size_t index) {
|
||||||
|
if (index >= m_children.size())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return m_children[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeType TreeNode::GetType() const {
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &TreeNode::GetName() const {
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FrameInfo *TreeNode::GetFrame() const {
|
||||||
|
return m_frame.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignalInfo *TreeNode::GetSignal() const {
|
||||||
|
return m_signal.get();
|
||||||
|
}
|
||||||
109
tree_node.h
Normal file
109
tree_node.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#ifndef TREE_NODE_H
|
||||||
|
#define TREE_NODE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "frame_info.h"
|
||||||
|
#include "signal_info.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type of a tree node.
|
||||||
|
*/
|
||||||
|
enum class NodeType {
|
||||||
|
Root,
|
||||||
|
Frame,
|
||||||
|
Signal
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tree node for later use in model/view or other hierarchy consumers.
|
||||||
|
*/
|
||||||
|
class TreeNode {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create root node.
|
||||||
|
*/
|
||||||
|
TreeNode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create frame node.
|
||||||
|
* @param frame Frame payload.
|
||||||
|
*/
|
||||||
|
explicit TreeNode (const FrameInfo &frame);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create signal node.
|
||||||
|
* @param signal Signal payload.
|
||||||
|
*/
|
||||||
|
explicit TreeNode (const SignalInfo &signal);
|
||||||
|
|
||||||
|
~TreeNode() = default;
|
||||||
|
|
||||||
|
TreeNode (const TreeNode &) = delete;
|
||||||
|
TreeNode &operator= (const TreeNode &) = delete;
|
||||||
|
|
||||||
|
TreeNode (TreeNode &&) = default;
|
||||||
|
TreeNode &operator= (TreeNode &&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add child node.
|
||||||
|
* @param child Child node to add.
|
||||||
|
*/
|
||||||
|
void AddChild (std::unique_ptr<TreeNode> child);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get child count.
|
||||||
|
* @return Number of children.
|
||||||
|
*/
|
||||||
|
std::size_t GetChildCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get child by index.
|
||||||
|
* @param index Child index.
|
||||||
|
* @return Child pointer or nullptr if index is invalid.
|
||||||
|
*/
|
||||||
|
const TreeNode *GetChild (std::size_t index) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get mutable child by index.
|
||||||
|
* @param index Child index.
|
||||||
|
* @return Child pointer or nullptr if index is invalid.
|
||||||
|
*/
|
||||||
|
TreeNode *GetChild (std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get node type.
|
||||||
|
* @return Node type.
|
||||||
|
*/
|
||||||
|
NodeType GetType() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get display name.
|
||||||
|
* @return Node name.
|
||||||
|
*/
|
||||||
|
const std::string &GetName() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get frame payload if node is frame.
|
||||||
|
* @return Pointer to frame info or nullptr.
|
||||||
|
*/
|
||||||
|
const FrameInfo *GetFrame() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get signal payload if node is signal.
|
||||||
|
* @return Pointer to signal info or nullptr.
|
||||||
|
*/
|
||||||
|
const SignalInfo *GetSignal() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NodeType m_type;
|
||||||
|
std::string m_name;
|
||||||
|
std::vector<std::unique_ptr<TreeNode> > m_children;
|
||||||
|
std::unique_ptr<FrameInfo> m_frame;
|
||||||
|
std::unique_ptr<SignalInfo> m_signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* TREE_NODE_H */
|
||||||
Reference in New Issue
Block a user