5#include <QTemporaryFile>
11 const std::string fixture = R
"DBC(BO_ 291 Standard: 2 SenderA
12 SG_ Mode M : 0|8@1+ (1,0) [0|255] "" ReceiverA
13 SG_ State m3 : 8|8@1+ (2,1) [1|511] "km/h" ReceiverA,ReceiverB
14BO_ 2147483939 Extended: 1 SenderB
15 SG_ State : 7|8@0- (0.5,-3) [-67|60.5] "deg C" ReceiverB
16BO_ 2565866562 J1939: 0 SenderC
17CM_ BO_ 291 "Standard frame comment";
18CM_ SG_ 291 Mode "Selector comment";
19CM_ SG_ 291 State "Standard signal comment";
20CM_ BO_ 2147483939 "Extended frame comment";
21CM_ SG_ 2147483939 State "Extended signal comment";
22VAL_ 291 Mode 3 "Selected";
23VAL_ 291 State 4 "Standard four";
24VAL_ 2147483939 State -2 "Extended minus two";
29 if (!file.open())
throw std::runtime_error(
"Cannot create DBC fixture");
30 if (file.write(text.data(),
static_cast<qint64
>(text.size())) !=
static_cast<qint64
>(text.size()) || !file.flush())
31 throw std::runtime_error(
"Cannot write DBC fixture");
39 standard.
name =
"Standard";
40 standard.
canId = 291U;
43 standard.
comment =
"Standard frame comment";
50 mode.
comment =
"Selector comment";
63 state.
receivers = {
"ReceiverA",
"ReceiverB"};
64 state.
comment =
"Standard signal comment";
66 standard.signals = {mode, state};
69 extended.
name =
"Extended";
70 extended.
canId = 291U;
75 extended.
comment =
"Extended frame comment";
86 state.
comment =
"Extended signal comment";
88 extended.signals = {state};
92 j1939.
canId = 0x18F00442U;
100 template<
class Signal>
101 void CheckSignalMetadata(
const Signal &actual,
const SignalInfo &expected) {
102 QCOMPARE(actual.name, expected.
name);
103 QCOMPARE(actual.startBit, expected.
startBit);
104 QCOMPARE(actual.length, expected.
length);
105 QCOMPARE(actual.factor, expected.
factor);
106 QCOMPARE(actual.offset, expected.
offset);
107 QCOMPARE(actual.minimum, expected.
minimum);
108 QCOMPARE(actual.maximum, expected.
maximum);
109 QCOMPARE(actual.unit, expected.
unit);
110 QVERIFY(actual.receivers == expected.
receivers);
111 QCOMPARE(actual.comment, expected.
comment);
113 QCOMPARE(actual.multiplexing.role, expected.
multiplexing.role);
118 CheckSignalMetadata(actual, expected);
124 CheckSignalMetadata(actual, expected);
129 template<
class Frame>
130 void CheckFrame(
const Frame &actual,
const FrameInfo &expected) {
131 QCOMPARE(actual.name, expected.
name);
132 QCOMPARE(actual.canId, expected.
canId);
133 QCOMPARE(actual.isExtended, expected.
isExtended);
134 QCOMPARE(actual.dlc, expected.
dlc);
135 QCOMPARE(actual.pgn, expected.
pgn);
136 QCOMPARE(actual.hasPgn, expected.
hasPgn);
137 QCOMPARE(actual.transmitter, expected.
transmitter);
138 QCOMPARE(actual.comment, expected.
comment);
139 QCOMPARE(actual.signals.size(), expected.signals.size());
140 for (std::size_t i = 0; i < expected.signals.size(); ++i)
141 CheckSignal(actual.signals[i], expected.signals[i]);
145 QCOMPARE(root.
GetType(), NodeType::Root);
147 QVERIFY(root.
GetFrame() ==
nullptr);
150 for (std::size_t i = 0; i < expected.
frames.size(); ++i) {
152 QVERIFY(frame !=
nullptr);
153 QCOMPARE(frame->
GetType(), NodeType::Frame);
155 QCOMPARE(frame->
GetRow(), i);
157 QVERIFY(frame->
GetFrame() !=
nullptr);
163 QVERIFY(signal !=
nullptr);
164 QCOMPARE(signal->
GetType(), NodeType::Signal);
166 QCOMPARE(signal->
GetRow(), j);
167 QCOMPARE(signal->
GetName(), expected.
frames[i].signals[j].name);
169 QVERIFY(signal->
GetFrame() ==
nullptr);
179 QVERIFY(runtime.
FindFrame(key) ==
nullptr);
181 QVERIFY(
DbcDecoder().FindFrame(runtime, key.canId, key.isExtended) ==
nullptr);
183 raw.
canId = key.canId;
187 QVERIFY(!decoded.
valid);
188 QCOMPARE(decoded.
status, FrameDecodeStatus::UnknownFrame);
190 QVERIFY(decoded.signals.empty());
191 QVERIFY(decoded.
key == key);
195class PipelineTest :
public QObject {
198 void metadataLookupDecodeAndTree();
199 void emptyDatabase_data();
200 void emptyDatabase();
201 void databaseReplacement();
202 void unsupportedSignalTypes_data();
203 void unsupportedSignalTypes();
206void PipelineTest::metadataLookupDecodeAndTree() {
215 QVERIFY(tree !=
nullptr);
216 CheckTree(*tree, expected);
217 for (std::size_t i = 0; i < expected.
frames.size(); ++i) {
221 const std::shared_ptr<const DecodeFrame> found = runtime.
FindFrame(key);
222 QVERIFY(found !=
nullptr);
223 QCOMPARE(found.get(), &runtime.
GetFrames()[i]);
224 QCOMPARE(
DbcDecoder().FindFrame(runtime, key.canId, key.isExtended).get(), found.get());
225 for (std::size_t j = 0; j < found->signals.size(); ++j) {
226 const std::shared_ptr<const DecodeSignal> signal = runtime.
FindSignal(
SignalKey(key, found->signals[j].name));
227 QVERIFY(signal !=
nullptr);
228 QCOMPARE(signal.get(), &found->signals[j]);
238 QVERIFY(standard.
valid);
239 QCOMPARE(standard.
status, FrameDecodeStatus::Valid);
242 QCOMPARE(standard.signals.size(), std::size_t(2));
243 for (std::size_t i = 0; i < standard.signals.size(); ++i) {
245 QVERIFY(signal.
valid);
246 QCOMPARE(signal.
status, SignalDecodeStatus::Valid);
247 QVERIFY(signal.
key ==
SignalKey(standard.
key, i == 0 ?
"Mode" :
"State"));
249 QCOMPARE(std::get<std::uint64_t>(signal.
rawValue), i == 0 ? 3U : 4U);
252 QCOMPARE(*signal.
GetValueDescription(), std::string(i == 0 ?
"Selected" :
"Standard four"));
257 QVERIFY(extended.
valid);
258 QCOMPARE(extended.
status, FrameDecodeStatus::Valid);
261 QCOMPARE(extended.signals.size(), std::size_t(1));
263 QVERIFY(signal.
valid);
264 QCOMPARE(signal.
status, SignalDecodeStatus::Valid);
267 QCOMPARE(std::get<std::int64_t>(signal.
rawValue), std::int64_t(-2));
272 raw.
canId = 0x18F00442U;
275 QVERIFY(j1939.
valid);
276 QCOMPARE(j1939.
status, FrameDecodeStatus::Valid);
279 QVERIFY(j1939.signals.empty());
282void PipelineTest::unsupportedSignalTypes_data() {
283 QTest::addColumn<unsigned>(
"bits");
284 QTest::addColumn<unsigned>(
"type");
285 QTest::addColumn<std::vector<std::uint8_t>>(
"payload");
286 QTest::addColumn<std::uint64_t>(
"integerValue");
287 QTest::newRow(
"float32-one") << 32U << 1U << std::vector<std::uint8_t>{0U, 0U, 128U, 63U}
288 << std::uint64_t(0x3F800000U);
289 QTest::newRow(
"float64-one") << 64U << 2U << std::vector<std::uint8_t>{0U, 0U, 0U, 0U, 0U, 0U, 240U, 63U}
290 << std::uint64_t(0x3FF0000000000000ULL);
293void PipelineTest::unsupportedSignalTypes() {
294 QFETCH(
unsigned, bits);
295 QFETCH(
unsigned, type);
296 QFETCH(std::vector<std::uint8_t>, payload);
297 QFETCH(std::uint64_t, integerValue);
298 const std::string definitions =
"BO_ 1 Frame: " + std::to_string(bits / 8U) +
" ECU\n"
299 "SG_ Value : 0|" + std::to_string(bits) +
"@1+ (1,0) [0|100] \"\" ECU\n";
310 QCOMPARE(integerFrame.
status, FrameDecodeStatus::Valid);
311 QCOMPARE(integerFrame.signals.size(), std::size_t(1));
312 QVERIFY(integerFrame.signals[0].
valid);
313 QCOMPARE(std::get<std::uint64_t>(integerFrame.signals[0].rawValue), integerValue);
316 "SIG_VALTYPE_ 1 Value : " + std::to_string(type) +
";\n");
318 QCOMPARE(floatingPoint.
diagnostics.size(), std::size_t(1));
319 QCOMPARE(floatingPoint.
diagnostics[0].code, DbcDiagnosticCode::UnsupportedSignalType);
320 QCOMPARE(floatingPoint.
diagnostics[0].lineNumber, std::size_t(3));
324 QCOMPARE(rejected.
status, FrameDecodeStatus::UnknownFrame);
325 QVERIFY(!rejected.
valid);
327 QVERIFY(rejected.signals.empty());
330void PipelineTest::emptyDatabase_data() {
331 QTest::addColumn<QString>(
"text");
332 QTest::newRow(
"empty-file") << QString();
333 QTest::newRow(
"headers-only") << QString::fromUtf8(
"VERSION \"empty\"\nNS_ :\nBS_:\nBU_: Sender\n");
336void PipelineTest::emptyDatabase() {
337 QFETCH(QString, text);
344 CheckUnknown(runtime,
false);
345 CheckUnknown(runtime,
true);
347 QVERIFY(tree !=
nullptr);
351void PipelineTest::databaseReplacement() {
360 QVERIFY(saved.
valid);
361 QCOMPARE(saved.signals.size(), std::size_t(2));
362 QVERIFY(tree !=
nullptr);
363 std::unique_ptr<TreeNode> savedTree = std::move(tree);
365 parsed = ParseFixture(R
"DBC(BO_ 291 Replacement: 1 NewSender
366 SG_ NewState : 0|8@1+ (3,-1) [-1|764] "V" NewReceiver
367CM_ BO_ 291 "Replacement frame";
368CM_ SG_ 291 NewState "Replacement signal";
369VAL_ 291 NewState 4 "New four";
374 replacement.name = "Replacement";
375 replacement.
canId = 291U;
376 replacement.
dlc = 1U;
378 replacement.
comment =
"Replacement frame";
380 state.
name =
"NewState";
388 state.
comment =
"Replacement signal";
390 replacement.signals = {state};
396 QVERIFY(tree !=
nullptr);
397 QCOMPARE(runtime.
GetFrames().size(), std::size_t(1));
398 CheckFrame(runtime.
GetFrames()[0], replacement);
399 CheckTree(*tree, expected);
400 CheckUnknown(runtime,
true);
406 QVERIFY(current.
valid);
407 QCOMPARE(current.
status, FrameDecodeStatus::Valid);
408 QCOMPARE(current.signals.size(), std::size_t(1));
409 QVERIFY(current.signals[0].
valid);
412 QCOMPARE(std::get<std::uint64_t>(current.signals[0].rawValue), std::uint64_t(4));
413 QCOMPARE(current.signals[0].physicalValue, 11.0);
414 QVERIFY(current.signals[0].GetValueDescription() !=
nullptr);
415 QCOMPARE(*current.signals[0].GetValueDescription(), std::string(
"New four"));
417 parsed = ParseFixture(
"");
422 QVERIFY(tree !=
nullptr);
424 CheckUnknown(runtime,
false);
425 CheckUnknown(runtime,
true);
427 QVERIFY(runtime.
FindSignal(current.signals[0].
key) ==
nullptr);
429 CheckFrame(*saved.
definition, ExpectedMetadata().frames[0]);
430 CheckTree(*savedTree, ExpectedMetadata());
431 QCOMPARE(saved.signals[1].physicalValue, 9.0);
432 QVERIFY(saved.signals[1].GetValueDescription() !=
nullptr);
433 QCOMPARE(*saved.signals[1].GetValueDescription(), std::string(
"Standard four"));
436int RunPipelineTests(
int argc,
char **argv) {
438 return QTest::qExec(&test, argc, argv);
441#include "dbc_pipeline_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
std::shared_ptr< const DecodeSignal > FindSignal(const SignalKey &key) const
Return an owning signal handle, or an empty handle for a missing key.
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
const std::vector< DecodeFrame > & GetFrames() const
Tree node for later use in model/view or other hierarchy consumers.
const SignalInfo * GetSignal() const
Get signal payload if node is signal.
const TreeNode * GetChild(std::size_t index) const
Get child by index.
const TreeNode * GetParent() const
const std::string & GetName() const
Get display name.
std::size_t GetChildCount() const
Get child count.
std::size_t GetRow() const
NodeType GetType() const
Get node type.
const FrameInfo * GetFrame() const
Get frame payload if node is frame.
Runtime decode database builder.
CAN frame decoding API and result types.
DBC file parser and interpretation options.
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Parsed database and diagnostics; the database is empty on error.
std::vector< DbcDiagnostic > diagnostics
bool HasErrors() const
Return whether any diagnostic has error severity.
Runtime-ready signal definition.
std::shared_ptr< const DecodeFrame > definition
One decoded signal value.
std::shared_ptr< const DecodeSignal > definition
SignalDecodeStatus status
const std::string * GetValueDescription() const
Describes one CAN frame from a DBC file.
Stable frame identifier: normalized CAN ID and frame type.
Basic markers and optional explicit extended multiplexing condition.
Raw CAN frame used for runtime or trace decoding.
std::vector< std::uint8_t > data
Describes one signal inside a DBC frame.
std::vector< std::string > receivers
ValueDescriptions valueDescriptions
Multiplexing multiplexing