Compare commits

...

6 Commits

Author SHA1 Message Date
f0e706542b Update documentation 2026-03-13 13:34:07 -04:00
a53a5dfed3 Added decoding support to runtime decode. 2026-03-13 13:30:38 -04:00
cb20f5fe55 update readme 2026-03-13 13:14:20 -04:00
f2aa10cee4 Update readme 2026-03-13 13:11:23 -04:00
efc57d658d readme 2026-03-13 13:03:29 -04:00
2a90b2d79d DBC parser initial commit. 2026-03-13 13:00:01 -04:00
18 changed files with 2136 additions and 2 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.user

842
README.md
View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 */