1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qcandbcfileparser.h" |
5 | #include "qcandbcfileparser_p.h" |
6 | #include "qcanmessagedescription.h" |
7 | #include "qcansignaldescription.h" |
8 | #include "qcanuniqueiddescription.h" |
9 | #include "private/qcanmessagedescription_p.h" |
10 | #include "private/qcansignaldescription_p.h" |
11 | |
12 | #include <QtCore/QFile> |
13 | #include <QtCore/QRegularExpression> |
14 | |
15 | #include <optional> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | /*! |
20 | \class QCanDbcFileParser |
21 | \inmodule QtSerialBus |
22 | \since 6.5 |
23 | \preliminary |
24 | |
25 | \brief The QCanDbcFileParser class can be used to parse DBC files. |
26 | |
27 | A CAN database or CAN DBC file is an ASCII text file that contains |
28 | information on how to decode and interpret raw CAN bus data. Some more |
29 | details about the format can be found \l {CSSElectronics DBC Intro}{here} |
30 | or \l {OpenVehicles DBC Intro}{here}. |
31 | |
32 | The QCanDbcFileParser class takes the input DBC file, parses it, and |
33 | provides a list of \l {QCanMessageDescription}s as an output. These message |
34 | descriptions can be forwarded to \l QCanFrameProcessor, and later used |
35 | as rules to encode or decode \l {QCanBusFrame}s. |
36 | |
37 | Use one of \l parse() overloads to specify a file or a list of files that |
38 | will be processed. Both overloads return \c true if the parsing completes |
39 | successfully and \c false otherwise. |
40 | |
41 | Call the \l error() method to get the error which occurred during the |
42 | parsing. If the parsing completes successfully, this method will return |
43 | \l {QCanDbcFileParser::}{None}. Otherwise, you can use an |
44 | \l errorString() method to get the string representation of an error. |
45 | |
46 | During the parsing some non-critical problems may occur as well. Such |
47 | problems will be logged, but the parsing process will not be aborted. You |
48 | can use the \l warnings() method to get the full list of such problems |
49 | after the parsing is completed. |
50 | |
51 | If the parsing completes successfully, call \l messageDescriptions() to get |
52 | a list of the message descriptions that were extracted during the last |
53 | \l parse() call. Call \l messageValueDescriptions() to get the textual |
54 | descriptions of signal raw values, if they are available. |
55 | |
56 | Use the static \l uniqueIdDescription() function to get a |
57 | \l QCanUniqueIdDescription for the DBC format. |
58 | |
59 | \code |
60 | QCanDbcFileParser fileParser; |
61 | const bool result = fileParser.parse(u"path/to/file.dbc"_s); |
62 | // Check result, call error() and warnings() if needed |
63 | |
64 | // Prepare a QCanFrameProcessor to decode or encode DBC frames |
65 | QCanFrameProcessor frameProcessor; |
66 | frameProcessor.setUniqueIdDescription(QCanDbcFileParser::uniqueIdDescription()); |
67 | frameProcessor.setMessageDescriptions(fileParser.messageDescriptions()); |
68 | \endcode |
69 | |
70 | \note The parser is stateful, which means that all the results (like |
71 | extracted message descriptions, error code, or warnings) are reset once the |
72 | next parsing starts. |
73 | |
74 | \section2 Supported Keywords |
75 | |
76 | The current implementation supports only a subset of keywords that you can |
77 | find in a DBC file: |
78 | |
79 | \list |
80 | \li \c {BO_} - message description. |
81 | \li \c {SG_} - signal description. |
82 | \li \c {SIG_VALTYPE_} - signal type description. |
83 | \li \c {SG_MUL_VAL_} - extended multiplexing description. |
84 | \li \c {CM_} - comments (only for message and signal descriptions). |
85 | \li \c {VAL_} - textual descriptions for raw signal values. |
86 | \endlist |
87 | |
88 | Lines starting from other keywords are simply ignored. |
89 | |
90 | \sa QCanMessageDescription, QCanFrameProcessor |
91 | */ |
92 | |
93 | /*! |
94 | \typealias QCanDbcFileParser::ValueDescriptions |
95 | |
96 | This is a type alias for \c {QHash<quint32, QString>}. |
97 | |
98 | The keys of the hash represent raw signal values, and the values of the |
99 | hash represent corresponding string descriptions. |
100 | */ |
101 | |
102 | /*! |
103 | \typealias QCanDbcFileParser::SignalValueDescriptions |
104 | |
105 | This is a type alias for \c {QHash<QString, ValueDescriptions>}. |
106 | |
107 | The keys of the hash represent signal names, and the values of the |
108 | hash contain the corresponding \l QCanDbcFileParser::ValueDescriptions |
109 | entries. |
110 | |
111 | \sa QCanDbcFileParser::ValueDescriptions |
112 | */ |
113 | |
114 | /*! |
115 | \typealias QCanDbcFileParser::MessageValueDescriptions |
116 | |
117 | This is a type alias for |
118 | \c {QHash<QtCanBus::UniqueId, SignalValueDescriptions>}. |
119 | |
120 | The keys of the hash represent message unique ids, and the values of the |
121 | hash contain the corresponding \l QCanDbcFileParser::SignalValueDescriptions |
122 | entries. |
123 | |
124 | \sa QCanDbcFileParser::SignalValueDescriptions |
125 | */ |
126 | |
127 | /*! |
128 | \enum QCanDbcFileParser::Error |
129 | |
130 | This enum represents the possible errors that can happen during the parsing |
131 | of a DBC file. |
132 | |
133 | \value None No error occurred. |
134 | \value FileReading An error occurred while opening or reading the file. |
135 | \value Parsing An error occurred while parsing the content of the file. |
136 | */ |
137 | |
138 | /*! |
139 | Constructs a DBC file parser. |
140 | */ |
141 | QCanDbcFileParser::QCanDbcFileParser() |
142 | : d(std::make_unique<QCanDbcFileParserPrivate>()) |
143 | { |
144 | } |
145 | |
146 | /*! |
147 | Destroys this DBC file parser. |
148 | */ |
149 | QCanDbcFileParser::~QCanDbcFileParser() = default; |
150 | |
151 | /*! |
152 | Parses the file \a fileName. Returns \c true if the parsing completed |
153 | successfully or \c false otherwise. |
154 | |
155 | If the parsing completed successfully, call the \l messageDescriptions() |
156 | method to get the list of all extracted message descriptions. |
157 | |
158 | If the parsing failed, call the \l error() and \l errorString() methods |
159 | to get the information about the error. |
160 | |
161 | Call the \l warnings() method to get the list of warnings that were |
162 | logged during the parsing. |
163 | |
164 | \note This method expects the file contents to be encoded in UTF-8. If the |
165 | file has a different encoding, decode it first, and use \l parseData() |
166 | to extract the DBC information. |
167 | |
168 | \sa messageDescriptions(), error(), warnings(), parseData() |
169 | */ |
170 | bool QCanDbcFileParser::parse(const QString &fileName) |
171 | { |
172 | d->reset(); |
173 | return d->parseFile(fileName); |
174 | } |
175 | |
176 | /*! |
177 | \overload |
178 | |
179 | Parses a list of files \a fileNames. Returns \c true if the parsing |
180 | completed successfully or \c false otherwise. |
181 | |
182 | If the parsing completed successfully, call the \l messageDescriptions() |
183 | method to get the list of all extracted message descriptions. |
184 | |
185 | The parsing stops at the first error. Call the \l error() and |
186 | \l errorString() methods to get the information about the error. |
187 | |
188 | Call the \l warnings() method to get the list of warnings that were |
189 | logged during the parsing. |
190 | |
191 | \note This method expects the file contents to be encoded in UTF-8. If the |
192 | file has a different encoding, decode it first, and use \l parseData() |
193 | to extract the DBC information. |
194 | |
195 | \sa messageDescriptions(), error(), warnings(), parseData() |
196 | */ |
197 | bool QCanDbcFileParser::parse(const QStringList &fileNames) |
198 | { |
199 | d->reset(); |
200 | for (const auto &fileName : fileNames) { |
201 | if (!d->parseFile(fileName)) |
202 | return false; |
203 | } |
204 | return true; |
205 | } |
206 | |
207 | /*! |
208 | \since 6.7 |
209 | |
210 | Parses the input data \a data and returns \c true if the parsing completed |
211 | successfully or \c false otherwise. |
212 | |
213 | If the parsing completed successfully, call the \l messageDescriptions() |
214 | method to get the list of all extracted message descriptions. |
215 | |
216 | If the parsing failed, call the \l error() and \l errorString() methods |
217 | to get the information about the error. |
218 | |
219 | Call the \l warnings() method to get the list of warnings that were |
220 | logged during the parsing. |
221 | |
222 | The method expects that \a data is the content of a valid DBC file, |
223 | properly converted to QStringView. |
224 | |
225 | Use this method when the input file has an encoding different from UTF-8. |
226 | |
227 | \code |
228 | // Read the data from a DBC file with custom encoding |
229 | const QByteArray initialData = ...; |
230 | // Convert to UTF-16 using QStringDecoder or some other way |
231 | const QString decodedData = ...; |
232 | QCanDbcFileParser parser; |
233 | const bool result = parser.parseData(decodedData); |
234 | \endcode |
235 | |
236 | \sa messageDescriptions(), error(), warnings(), parse() |
237 | */ |
238 | bool QCanDbcFileParser::parseData(QStringView data) |
239 | { |
240 | d->reset(); |
241 | return d->parseData(data); |
242 | } |
243 | |
244 | /*! |
245 | Returns the list of message descriptions that were extracted during the |
246 | last \l parse() call. |
247 | |
248 | \sa parse(), error() |
249 | */ |
250 | QList<QCanMessageDescription> QCanDbcFileParser::messageDescriptions() const |
251 | { |
252 | return d->getMessages(); |
253 | } |
254 | |
255 | /*! |
256 | Returns the textual descriptions for signal raw values. |
257 | |
258 | DBC supports the possibility to provide textual descriptions to signal raw |
259 | values. If such data exists in the parsed DBC file(s), it can be accessed |
260 | using this function. |
261 | |
262 | The textual descriptions are unique for a certain signal within a specific |
263 | message, so the returned structure contains the information about the |
264 | message unique id and the signal name, as well as the actual value |
265 | descriptions. |
266 | |
267 | \sa QCanDbcFileParser::MessageValueDescriptions, |
268 | QCanDbcFileParser::SignalValueDescriptions, |
269 | QCanDbcFileParser::ValueDescriptions |
270 | */ |
271 | QCanDbcFileParser::MessageValueDescriptions QCanDbcFileParser::messageValueDescriptions() const |
272 | { |
273 | return d->m_valueDescriptions; |
274 | } |
275 | |
276 | /*! |
277 | Returns the last error which occurred during the parsing. |
278 | |
279 | \sa errorString(), parse() |
280 | */ |
281 | QCanDbcFileParser::Error QCanDbcFileParser::error() const |
282 | { |
283 | return d->m_error; |
284 | } |
285 | |
286 | /*! |
287 | Returns the text representation of the last error which occurred during the |
288 | parsing or an empty string if there was no error. |
289 | |
290 | \sa error() |
291 | */ |
292 | QString QCanDbcFileParser::errorString() const |
293 | { |
294 | return d->m_errorString; |
295 | } |
296 | |
297 | /*! |
298 | Returns the list of non-critical problems which occurred during the parsing. |
299 | |
300 | A typical problem can be a malformed message or signal description. In such |
301 | cases the malformed message or signal is skipped, but the rest of the file |
302 | can be processed as usual. |
303 | |
304 | \sa error(), parse() |
305 | */ |
306 | QStringList QCanDbcFileParser::warnings() const |
307 | { |
308 | return d->m_warnings; |
309 | } |
310 | |
311 | /*! |
312 | Returns a unique identifier description. DBC protocol always uses the |
313 | Frame Id as an identifier, and therefore the unique identifier description |
314 | is always the same. |
315 | |
316 | Use this method to get an instance of \l QCanUniqueIdDescription and pass |
317 | it to \l QCanFrameProcessor. |
318 | |
319 | \sa QCanFrameProcessor::setUniqueIdDescription() |
320 | */ |
321 | QCanUniqueIdDescription QCanDbcFileParser::uniqueIdDescription() |
322 | { |
323 | QCanUniqueIdDescription desc; |
324 | desc.setSource(QtCanBus::DataSource::FrameId); |
325 | desc.setEndian(QSysInfo::Endian::LittleEndian); |
326 | desc.setStartBit(0); |
327 | desc.setBitLength(29); // for both extended and normal frame id |
328 | return desc; |
329 | } |
330 | |
331 | /* QCanDbcFileParserPrivate implementation */ |
332 | |
333 | using namespace Qt::StringLiterals; |
334 | |
335 | // signal name with whitespaces is invalid in DBC, so we can safely use it |
336 | // for internal purposes |
337 | static const auto kQtDummySignal = u"Qt Dummy Signal"_s ; |
338 | |
339 | static constexpr auto kMessageDef = "BO_ "_L1 ; |
340 | static constexpr auto kSignalDef = "SG_ "_L1 ; |
341 | static constexpr auto kSigValTypeDef = "SIG_VALTYPE_ "_L1 ; |
342 | static constexpr auto = "CM_ "_L1 ; |
343 | static constexpr auto kExtendedMuxDef = "SG_MUL_VAL_ "_L1 ; |
344 | static constexpr auto kValDef = "VAL_ "_L1 ; |
345 | |
346 | static constexpr auto kUnsignedIntRegExp = "\\d+"_L1 ; |
347 | static constexpr auto kDoubleRegExp = "[+-]?\\d+((.\\d*)?([eE][+-]?\\d+)?)?"_L1 ; |
348 | static constexpr auto kDbcIdentRegExp = "[_[:alpha:]][_[:alnum:]]+"_L1 ; |
349 | static constexpr auto kOneOrMoreSpaceRegExp = "[ ]+"_L1 ; |
350 | static constexpr auto kMaybeSpaceRegExp = "[ ]*"_L1 ; |
351 | static constexpr auto kMuxIndicatorRegExp = "M|m\\d+M?"_L1 ; |
352 | static constexpr auto kByteOrderRegExp = "0|1"_L1 ; |
353 | static constexpr auto kValueTypeRegExp = "\\+|\\-"_L1 ; |
354 | // The pattern matches all printable characters, except double-quote (") and backslash (\). |
355 | static constexpr auto kCharStrRegExp = "((?![\\\"\\\\])\\P{Cc})*"_L1 ; |
356 | |
357 | void QCanDbcFileParserPrivate::reset() |
358 | { |
359 | m_fileName.clear(); |
360 | m_error = QCanDbcFileParser::Error::None; |
361 | m_errorString.clear(); |
362 | m_warnings.clear(); |
363 | m_lineOffset = 0; |
364 | m_isProcessingMessage = false; |
365 | m_seenExtraData = false; |
366 | m_currentMessage = {}; |
367 | m_messageDescriptions.clear(); |
368 | m_valueDescriptions.clear(); |
369 | } |
370 | |
371 | /*! |
372 | \internal |
373 | Returns \c false only in case of hard error. Returns \c true even if some |
374 | warnings occurred during parsing. |
375 | */ |
376 | bool QCanDbcFileParserPrivate::parseFile(const QString &fileName) |
377 | { |
378 | QFile f(fileName); |
379 | if (!f.open(flags: QIODevice::ReadOnly)) { |
380 | m_error = QCanDbcFileParser::Error::FileReading; |
381 | m_errorString = f.errorString(); |
382 | return false; |
383 | } |
384 | m_fileName = fileName; |
385 | m_seenExtraData = false; |
386 | |
387 | while (!f.atEnd()) { |
388 | const QString str = QString::fromUtf8(ba: f.readLine().trimmed()); |
389 | if (!processLine(line: {str.constData(), str.size()})) // also sets the error properly |
390 | return false; |
391 | } |
392 | addCurrentMessage(); // check if we need to add the message |
393 | // now when we parsed the whole file, we can verify the signal multiplexing |
394 | postProcessSignalMultiplexing(); |
395 | |
396 | return true; |
397 | } |
398 | |
399 | struct ReadData |
400 | { |
401 | qsizetype index; |
402 | QStringView result; |
403 | }; |
404 | |
405 | static ReadData readUntilNewline(QStringView in, qsizetype from) |
406 | { |
407 | const qsizetype idx = in.indexOf(c: '\n'_L1, from); |
408 | |
409 | return (idx == -1) ? ReadData{.index: idx, .result: in.sliced(pos: from).trimmed()} |
410 | : ReadData{.index: idx, .result: in.sliced(pos: from, n: idx - from).trimmed()}; |
411 | } |
412 | |
413 | /*! |
414 | \internal |
415 | The implementation basically copies parseFile(), including all |
416 | post-processing. The only difference is in extracting the data. |
417 | */ |
418 | bool QCanDbcFileParserPrivate::parseData(QStringView data) |
419 | { |
420 | if (data.isEmpty()) { |
421 | m_error = QCanDbcFileParser::Error::Parsing; |
422 | m_errorString = QObject::tr(s: "Empty input data." ); |
423 | return false; |
424 | } |
425 | m_seenExtraData = false; |
426 | qsizetype from = 0; |
427 | while (true) { |
428 | const auto [idx, sv] = readUntilNewline(in: data, from); |
429 | if (!processLine(line: sv)) |
430 | return false; |
431 | if (idx == -1) // reached the end of the string |
432 | break; |
433 | from = idx + 1; |
434 | } |
435 | addCurrentMessage(); |
436 | postProcessSignalMultiplexing(); |
437 | return true; |
438 | } |
439 | |
440 | /*! |
441 | \internal |
442 | Returns \c false only in case of hard error. Returns \c true even if some |
443 | warnings occurred during parsing. |
444 | */ |
445 | bool QCanDbcFileParserPrivate::processLine(const QStringView line) |
446 | { |
447 | QStringView data = line; |
448 | m_lineOffset = 0; |
449 | |
450 | auto handleParsingError = [this](QLatin1StringView section) { |
451 | m_error = QCanDbcFileParser::Error::Parsing; |
452 | m_errorString = !m_fileName.isEmpty() |
453 | ? QObject::tr(s: "Failed to parse file %1. Unexpected position " |
454 | "of %2 section." ).arg(args&: m_fileName, args&: section) |
455 | : QObject::tr(s: "Failed to parse input data. Unexpected position " |
456 | "of %1 section." ).arg(a: section); |
457 | }; |
458 | |
459 | if (data.startsWith(s: kMessageDef)) { |
460 | if (m_seenExtraData) { |
461 | // Unexpected position of message description |
462 | handleParsingError(kMessageDef); |
463 | return false; |
464 | } |
465 | addCurrentMessage(); |
466 | if (!parseMessage(data)) |
467 | return false; |
468 | } |
469 | // signal definitions can be on the same line as message definition, |
470 | // or on a separate line |
471 | data = data.sliced(pos: m_lineOffset).trimmed(); |
472 | while (data.startsWith(s: kSignalDef)) { |
473 | if (!m_isProcessingMessage || m_seenExtraData) { |
474 | // Unexpected position of signal description |
475 | handleParsingError(kSignalDef); |
476 | return false; |
477 | } |
478 | if (!parseSignal(data)) |
479 | return false; |
480 | data = data.sliced(pos: m_lineOffset).trimmed(); |
481 | } |
482 | // If we detect one of the following lines, then message description is |
483 | // finished. We also assume that we can have only one key at each line. |
484 | if (data.startsWith(s: kSigValTypeDef)) { |
485 | m_seenExtraData = true; |
486 | addCurrentMessage(); |
487 | parseSignalType(data); |
488 | } else if (data.startsWith(s: kCommentDef)) { |
489 | m_seenExtraData = true; |
490 | addCurrentMessage(); |
491 | parseComment(data); |
492 | } else if (data.startsWith(s: kExtendedMuxDef)) { |
493 | m_seenExtraData = true; |
494 | addCurrentMessage(); |
495 | parseExtendedMux(data); |
496 | } else if (data.startsWith(s: kValDef)) { |
497 | m_seenExtraData = true; |
498 | addCurrentMessage(); |
499 | parseValueDescriptions(data); |
500 | } |
501 | return true; |
502 | } |
503 | |
504 | static std::optional<QtCanBus::UniqueId> (QStringView view) |
505 | { |
506 | bool ok = false; |
507 | const uint value = view.toUInt(ok: &ok); |
508 | if (ok) |
509 | return QtCanBus::UniqueId{value & 0x1FFFFFFF}; |
510 | return std::nullopt; |
511 | } |
512 | |
513 | /*! |
514 | \internal |
515 | Returns \c false only in case of hard error. Returns \c true even if some |
516 | warnings occurred during parsing. |
517 | */ |
518 | bool QCanDbcFileParserPrivate::parseMessage(const QStringView data) |
519 | { |
520 | // The regexp matches the following definition: |
521 | // BO_ message_id message_name ':' message_size transmitter |
522 | // also considering the fact that spaces around ':' seem to be optional, and |
523 | // allowing more than one space between parts. |
524 | |
525 | // %1 - messageDef |
526 | // %2 - maybeSpace |
527 | // %3 - unsignedInt |
528 | // %4 - oneOrMoreSpace |
529 | // %5 - DbcIdentifier |
530 | static const QString regExStr = |
531 | "%1%2(?<messageId>%3)%4(?<name>%5)%2:%2(?<size>%3)%4(?<transmitter>%5)"_L1 . |
532 | arg(args: kMessageDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, args: kOneOrMoreSpaceRegExp, |
533 | args: kDbcIdentRegExp); |
534 | static const QRegularExpression messageRegExp(regExStr); |
535 | |
536 | m_isProcessingMessage = false; |
537 | const auto match = messageRegExp.matchView(subjectView: data); |
538 | if (match.hasMatch()) { |
539 | m_currentMessage = extractMessage(match); |
540 | // can't check for isValid() here, because demands signal descriptions |
541 | if (!m_currentMessage.name().isEmpty()) { |
542 | m_isProcessingMessage = true; |
543 | } else { |
544 | addWarning(warning: QObject::tr(s: "Failed to parse message description from " |
545 | "string %1" ).arg(a: data)); |
546 | } |
547 | m_lineOffset = match.capturedEnd(nth: 0); |
548 | } else { |
549 | addWarning(warning: QObject::tr(s: "Failed to find message description in string %1" ).arg(a: data)); |
550 | m_lineOffset = data.size(); // skip this string |
551 | } |
552 | return true; |
553 | } |
554 | |
555 | QCanMessageDescription |
556 | QCanDbcFileParserPrivate::extractMessage(const QRegularExpressionMatch &match) |
557 | { |
558 | Q_ASSERT(match.hasMatch()); |
559 | QCanMessageDescription desc; |
560 | desc.setName(match.captured(name: u"name"_s )); |
561 | |
562 | const auto id = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
563 | if (id.has_value()) { |
564 | desc.setUniqueId(id.value()); |
565 | } else { |
566 | addWarning(warning: QObject::tr(s: "Failed to parse frame id for message %1" ).arg(a: desc.name())); |
567 | return {}; |
568 | } |
569 | |
570 | bool ok = false; |
571 | const auto size = match.capturedView(name: u"size"_s ).toUInt(ok: &ok); |
572 | if (ok) { |
573 | desc.setSize(size); |
574 | } else { |
575 | addWarning(warning: QObject::tr(s: "Failed to parse size for message %1" ).arg(a: desc.name())); |
576 | return {}; |
577 | } |
578 | |
579 | desc.setTransmitter(match.captured(name: u"transmitter"_s )); |
580 | |
581 | return desc; |
582 | } |
583 | |
584 | /*! |
585 | \internal |
586 | Returns \c false only in case of hard error. Returns \c true even if some |
587 | warnings occurred during parsing. |
588 | */ |
589 | bool QCanDbcFileParserPrivate::parseSignal(const QStringView data) |
590 | { |
591 | // The regexp should match the following pattern: |
592 | // SG_ signal_name multiplexer_indicator : start_bit | |
593 | // signal_size @ byte_order value_type ( factor , offset ) |
594 | // [ minimum | maximum ] unit receiver {, receiver} |
595 | // We also need to consider the fact that some of the spaces might be |
596 | // optional, and we can potentially allow more spaces between parts. |
597 | // Note that the end of the signal description can contain multiple |
598 | // receivers. The regexp is supposed to extract all of them, but we use |
599 | // only the first one for now. |
600 | |
601 | // %1 - SignalDef |
602 | // %2 - MaybeSpace |
603 | // %3 - DbcIdentifier |
604 | // %4 - OneOrMoreSpace |
605 | // %5 - MuxIndicator |
606 | // %6 - unsignedInt |
607 | // %7 - byteOrder |
608 | // %8 - valueType |
609 | // %9 - double |
610 | // %10 - charStr |
611 | static const QString regExStr = |
612 | "%1%2(?<name>%3)(%4(?<mux>%5))?%2:%2(?<startBit>%6)%2\\|%2(?<sigSize>%6)%2@%2" |
613 | "(?<byteOrder>%7)%2(?<valueType>%8)%4\\(%2(?<factor>%9)%2,%2(?<offset>%9)%2\\)" |
614 | "%4\\[%2(?<min>%9)%2\\|%2(?<max>%9)%2\\]%4\"(?<unit>%10)\"" |
615 | "%4(?<receiver>%3)(%2,%2%3)*"_L1 . |
616 | arg(args: kSignalDef, args: kMaybeSpaceRegExp, args: kDbcIdentRegExp, args: kOneOrMoreSpaceRegExp, |
617 | args: kMuxIndicatorRegExp, args: kUnsignedIntRegExp, args: kByteOrderRegExp, args: kValueTypeRegExp, |
618 | args: kDoubleRegExp, args: kCharStrRegExp); |
619 | static const QRegularExpression signalRegExp(regExStr); |
620 | |
621 | const auto match = signalRegExp.matchView(subjectView: data); |
622 | if (match.hasMatch()) { |
623 | QCanSignalDescription desc = extractSignal(match); |
624 | |
625 | if (desc.isValid()) |
626 | m_currentMessage.addSignalDescription(description: desc); |
627 | else |
628 | addWarning(warning: QObject::tr(s: "Failed to parse signal description from string %1" ).arg(a: data)); |
629 | |
630 | m_lineOffset = match.capturedEnd(nth: 0); |
631 | } else { |
632 | addWarning(warning: QObject::tr(s: "Failed to find signal description in string %1" ).arg(a: data)); |
633 | m_lineOffset = data.size(); // skip this string |
634 | } |
635 | return true; |
636 | } |
637 | |
638 | QCanSignalDescription QCanDbcFileParserPrivate::extractSignal(const QRegularExpressionMatch &match) |
639 | { |
640 | Q_ASSERT(match.hasMatch()); |
641 | QCanSignalDescription desc; |
642 | desc.setName(match.captured(name: u"name"_s )); |
643 | |
644 | bool ok = false; |
645 | |
646 | if (match.hasCaptured(name: u"mux"_s )) { |
647 | const auto muxStr = match.capturedView(name: u"mux"_s ); |
648 | if (muxStr == u"M"_s ) { |
649 | desc.setMultiplexState(QtCanBus::MultiplexState::MultiplexorSwitch); |
650 | } else if (muxStr.endsWith(s: u"M"_s , cs: Qt::CaseSensitive)) { |
651 | desc.setMultiplexState(QtCanBus::MultiplexState::SwitchAndSignal); |
652 | const auto val = muxStr.sliced(pos: 1, n: muxStr.size() - 2).toUInt(ok: &ok); |
653 | if (!ok) { |
654 | addWarning(warning: QObject::tr(s: "Failed to parse multiplexor value for signal %1" ). |
655 | arg(a: desc.name())); |
656 | return {}; |
657 | } |
658 | // We have the value, but we do not really know the multiplexor |
659 | // switch name. To know it, we potentially need to parse all signals |
660 | // for the message. So for now we just create a dummy entry, and |
661 | // the actual signal name will be updated later; |
662 | desc.addMultiplexSignal(name: kQtDummySignal, value: val); |
663 | } else { |
664 | desc.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal); |
665 | const auto val = muxStr.sliced(pos: 1).toUInt(ok: &ok); |
666 | if (!ok) { |
667 | addWarning(warning: QObject::tr(s: "Failed to parse multiplexor value for signal %1" ). |
668 | arg(a: desc.name())); |
669 | return {}; |
670 | } |
671 | // Same as above |
672 | desc.addMultiplexSignal(name: kQtDummySignal, value: val); |
673 | } |
674 | } |
675 | |
676 | const uint startBit = match.capturedView(name: u"startBit"_s ).toUInt(ok: &ok); |
677 | if (ok) { |
678 | desc.setStartBit(startBit); |
679 | } else { |
680 | addWarning(warning: QObject::tr(s: "Failed to parse start bit for signal %1" ).arg(a: desc.name())); |
681 | return {}; |
682 | } |
683 | |
684 | const uint bitLength = match.capturedView(name: u"sigSize"_s ).toUInt(ok: &ok); |
685 | if (ok) { |
686 | desc.setBitLength(bitLength); |
687 | } else { |
688 | addWarning(warning: QObject::tr(s: "Failed to parse bit length for signal %1" ).arg(a: desc.name())); |
689 | return {}; |
690 | } |
691 | |
692 | // 0 = BE; 1 = LE |
693 | const auto endian = match.capturedView(name: u"byteOrder"_s ) == u"0"_s |
694 | ? QSysInfo::Endian::BigEndian : QSysInfo::Endian::LittleEndian; |
695 | desc.setDataEndian(endian); |
696 | |
697 | // + = unsigned; - = signed |
698 | const auto dataFormat = match.capturedView(name: u"valueType"_s ) == u"+"_s |
699 | ? QtCanBus::DataFormat::UnsignedInteger : QtCanBus::DataFormat::SignedInteger; |
700 | desc.setDataFormat(dataFormat); |
701 | |
702 | const double factor = match.capturedView(name: u"factor"_s ).toDouble(ok: &ok); |
703 | if (ok) { |
704 | desc.setFactor(factor); |
705 | } else { |
706 | addWarning(warning: QObject::tr(s: "Failed to parse factor for signal %1" ).arg(a: desc.name())); |
707 | return {}; |
708 | } |
709 | |
710 | const double offset = match.capturedView(name: u"offset"_s ).toDouble(ok: &ok); |
711 | if (ok) { |
712 | desc.setOffset(offset); |
713 | } else { |
714 | addWarning(warning: QObject::tr(s: "Failed to parse offset for signal %1" ).arg(a: desc.name())); |
715 | return {}; |
716 | } |
717 | |
718 | const double min = match.capturedView(name: u"min"_s ).toDouble(ok: &ok); |
719 | if (ok) { |
720 | const double max = match.capturedView(name: u"max"_s ).toDouble(ok: &ok); |
721 | if (ok) |
722 | desc.setRange(minimum: min, maximum: max); |
723 | } |
724 | if (!ok) { |
725 | addWarning(warning: QObject::tr(s: "Failed to parse value range from signal %1" ).arg(a: desc.name())); |
726 | return {}; |
727 | } |
728 | |
729 | desc.setPhysicalUnit(match.captured(name: u"unit"_s )); |
730 | desc.setReceiver(match.captured(name: u"receiver"_s )); |
731 | |
732 | return desc; |
733 | } |
734 | |
735 | void QCanDbcFileParserPrivate::parseSignalType(const QStringView data) |
736 | { |
737 | // The regexp should match the following pattern: |
738 | // SIG_VALTYPE_ message_id signal_name signal_extended_value_type ; |
739 | // We also need to consider the fact that we can potentially allow more |
740 | // spaces between parts. |
741 | |
742 | // %1 sigValTypeDef |
743 | // %2 maybeSpace |
744 | // %3 unsignedInt |
745 | // %4 oneOrMoreSpace |
746 | // %5 DbcIdentifier |
747 | const QString regExStr = |
748 | "%1%2(?<messageId>%3)%4(?<sigName>%5)%2:%2(?<type>%3)%2;"_L1 . |
749 | arg(args: kSigValTypeDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, |
750 | args: kOneOrMoreSpaceRegExp, args: kDbcIdentRegExp); |
751 | const QRegularExpression sigValTypeRegEx(regExStr); |
752 | |
753 | const auto match = sigValTypeRegEx.matchView(subjectView: data); |
754 | if (!match.hasMatch()) { |
755 | m_lineOffset = data.size(); |
756 | addWarning(warning: QObject::tr(s: "Failed to find signal value type description in string %1" ). |
757 | arg(a: data)); |
758 | return; |
759 | } |
760 | |
761 | m_lineOffset = match.capturedEnd(nth: 0); |
762 | |
763 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
764 | if (!uidOptional) { |
765 | addWarning(warning: QObject::tr(s: "Failed to parse frame id from string %1" ).arg(a: data)); |
766 | return; |
767 | } |
768 | |
769 | const QtCanBus::UniqueId uid = uidOptional.value(); |
770 | auto msgDesc = m_messageDescriptions.value(key: uid); |
771 | if (msgDesc.isValid()) { |
772 | const QString sigName = match.captured(name: u"sigName"_s ); |
773 | auto sigDesc = msgDesc.signalDescriptionForName(name: sigName); |
774 | if (sigDesc.isValid()) { |
775 | bool ok = false; |
776 | const auto type = match.capturedView(name: u"type" ).toUInt(ok: &ok); |
777 | if (ok) { |
778 | bool sigDescChanged = false; |
779 | switch (type) { |
780 | case 0: /* signed or unsigned integer */ |
781 | // do nothing, as we already have signed/unsinged integer |
782 | // based on "SG_ " string |
783 | break; |
784 | case 1: /* 32-bit IEEE-float */ |
785 | sigDesc.setDataFormat(QtCanBus::DataFormat::Float); |
786 | sigDesc.setBitLength(32); |
787 | sigDescChanged = true; |
788 | break; |
789 | case 2: /* 64-bit IEEE-double */ |
790 | sigDesc.setDataFormat(QtCanBus::DataFormat::Double); |
791 | sigDesc.setBitLength(64); |
792 | sigDescChanged = true; |
793 | break; |
794 | default: |
795 | // invalid value |
796 | break; |
797 | } |
798 | if (sigDescChanged) { |
799 | msgDesc.addSignalDescription(description: sigDesc); |
800 | m_messageDescriptions.insert(key: msgDesc.uniqueId(), value: msgDesc); |
801 | } |
802 | } else { |
803 | addWarning(warning: QObject::tr(s: "Failed to parse data type from string %1" ).arg(a: data)); |
804 | } |
805 | } else { |
806 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
807 | "Skipping string %2" ).arg(args: sigName, args: data)); |
808 | } |
809 | } else { |
810 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
811 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
812 | } |
813 | } |
814 | |
815 | void QCanDbcFileParserPrivate::parseComment(const QStringView data) |
816 | { |
817 | // The comment for message or signal description is represented by the |
818 | // following pattern: |
819 | // CM_ (BO_ message_id char_string | SG_ message_id signal_name char_string); |
820 | |
821 | // %1 commentDef |
822 | // %2 maybeSpace |
823 | // %3 messageDef |
824 | // %4 signalDef |
825 | // %5 oneOrMoreSpace |
826 | // %6 unsignedInt |
827 | // %7 DbcIdentifier |
828 | // %8 charStr |
829 | const QString regExStr = |
830 | "%1%2(?<type>(%3|%4))%2(?<messageId>%6)%5((?<sigName>%7)%5)?\"(?<comment>%8)\"%2;"_L1 . |
831 | arg(args: kCommentDef, args: kMaybeSpaceRegExp, args: kMessageDef, args: kSignalDef, args: kOneOrMoreSpaceRegExp, |
832 | args: kUnsignedIntRegExp, args: kDbcIdentRegExp, args: kCharStrRegExp); |
833 | const QRegularExpression (regExStr); |
834 | |
835 | const auto match = commentRegExp.matchView(subjectView: data); |
836 | if (!match.hasMatch()) { |
837 | // no warning here, as we ignore some "general" comments, and parse only |
838 | // comments related to messages and signals |
839 | m_lineOffset = data.size(); |
840 | return; |
841 | } |
842 | |
843 | m_lineOffset = match.capturedEnd(nth: 0); |
844 | |
845 | const auto type = match.capturedView(name: u"type"_s ); |
846 | |
847 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
848 | if (!uidOptional) { |
849 | addWarning(warning: QObject::tr(s: "Failed to parse frame id from string %1" ).arg(a: data)); |
850 | return; |
851 | } |
852 | |
853 | const QtCanBus::UniqueId uid = uidOptional.value(); |
854 | auto messageDesc = m_messageDescriptions.value(key: uid); |
855 | if (!messageDesc.isValid()) { |
856 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
857 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
858 | return; |
859 | } |
860 | |
861 | if (type == kMessageDef) { |
862 | const QString = match.captured(name: u"comment"_s ); |
863 | messageDesc.setComment(comment); |
864 | m_messageDescriptions.insert(key: uid, value: messageDesc); |
865 | } else if (type == kSignalDef) { |
866 | const QString sigName = match.captured(name: u"sigName"_s ); |
867 | auto signalDesc = messageDesc.signalDescriptionForName(name: sigName); |
868 | if (signalDesc.isValid()) { |
869 | const QString = match.captured(name: u"comment"_s ); |
870 | signalDesc.setComment(comment); |
871 | messageDesc.addSignalDescription(description: signalDesc); |
872 | m_messageDescriptions.insert(key: uid, value: messageDesc); |
873 | } else { |
874 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
875 | "Skipping string %2" ).arg(args: sigName, args: data)); |
876 | } |
877 | } |
878 | } |
879 | |
880 | void QCanDbcFileParserPrivate::parseExtendedMux(const QStringView data) |
881 | { |
882 | // The extended multiplexing is defined by the following pattern: |
883 | // SG_MUL_VAL_ message_id multiplexed_signal_name |
884 | // multiplexor_switch_name multiplexor_value_ranges ; |
885 | // Here multiplexor_value_ranges consists of multiple ranges, separated |
886 | // by a whitespace, and one range is defined as follows: |
887 | // multiplexor_value_range = unsigned_integer - unsigned_integer |
888 | |
889 | // %1 extendedMuxDef |
890 | // %2 maybeSpace |
891 | // %3 unsignedInt |
892 | // %4 oneOrMoreSpace |
893 | // %5 DbcIdentifier |
894 | const QString regExStr = |
895 | "%1%2(?<messageId>%3)%4(?<multiplexedSignal>%5)%4(?<multiplexorSwitch>%5)%4" |
896 | "(?<firstRange>%3%2-%2%3)(%2,%2%3%2-%2%3)*%2;"_L1 . |
897 | arg(args: kExtendedMuxDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, args: kOneOrMoreSpaceRegExp, |
898 | args: kDbcIdentRegExp); |
899 | const QRegularExpression extendedMuxRegExp(regExStr); |
900 | |
901 | const auto match = extendedMuxRegExp.matchView(subjectView: data); |
902 | if (!match.hasMatch()) { |
903 | m_lineOffset = data.size(); |
904 | addWarning(warning: QObject::tr(s: "Failed to find extended multiplexing description in string %1" ). |
905 | arg(a: data)); |
906 | return; |
907 | } |
908 | |
909 | m_lineOffset = match.capturedEnd(nth: 0); |
910 | |
911 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
912 | if (!uidOptional) { |
913 | addWarning(warning: QObject::tr(s: "Failed to parse frame id from string %1" ).arg(a: data)); |
914 | return; |
915 | } |
916 | |
917 | const QtCanBus::UniqueId uid = uidOptional.value(); |
918 | auto messageDesc = m_messageDescriptions.value(key: uid); |
919 | if (!messageDesc.isValid()) { |
920 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
921 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
922 | return; |
923 | } |
924 | |
925 | const QString multiplexedSignalName = match.captured(name: u"multiplexedSignal"_s ); |
926 | const QString multiplexorSwitchName = match.captured(name: u"multiplexorSwitch"_s ); |
927 | |
928 | auto multiplexedSignal = messageDesc.signalDescriptionForName(name: multiplexedSignalName); |
929 | auto multiplexorSwitch = messageDesc.signalDescriptionForName(name: multiplexorSwitchName); |
930 | |
931 | if (!multiplexedSignal.isValid() || !multiplexorSwitch.isValid()) { |
932 | const QString invalidName = multiplexedSignal.isValid() ? multiplexorSwitchName |
933 | : multiplexedSignalName; |
934 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
935 | "Skipping string %2" ).arg(args: invalidName, args: data)); |
936 | return; |
937 | } |
938 | |
939 | auto signalRanges = multiplexedSignal.multiplexSignals(); |
940 | signalRanges.remove(key: kQtDummySignal); // dummy signal not needed anymore |
941 | |
942 | QCanSignalDescription::MultiplexValues rangeValues; |
943 | auto rangeView = match.capturedView(name: u"firstRange"_s ); |
944 | const auto sepIdx = rangeView.indexOf(c: u'-'); |
945 | if (sepIdx != -1) { |
946 | const auto min = rangeView.first(n: sepIdx).trimmed().toUInt(); |
947 | const auto max = rangeView.sliced(pos: sepIdx + 1).trimmed().toUInt(); |
948 | rangeValues.push_back(t: {.minimum: min, .maximum: max}); |
949 | } |
950 | |
951 | // We can have an arbitrary amount of ranges, so we can't use capture groups |
952 | // to capture them. But we know that they follow a specific pattern (because |
953 | // the full string matched the regexp). So we need to parse the rest of the |
954 | // matched string manually |
955 | const auto totalEnd = match.capturedEnd(nth: 0); // including the ';' |
956 | const auto firstRangeEnd = match.capturedEnd(name: u"firstRange"_s ); |
957 | const auto len = totalEnd - firstRangeEnd - 1; |
958 | if (len > 0) { |
959 | const auto otherRangesView = data.sliced(pos: firstRangeEnd, n: len).trimmed(); |
960 | const QStringTokenizer parts = otherRangesView.tokenize(needle: u',', flags: Qt::SkipEmptyParts); |
961 | for (const QStringView range : parts) { |
962 | const auto sepIdx = range.indexOf(c: u'-'); |
963 | if (sepIdx != -1) { |
964 | const auto min = range.first(n: sepIdx).trimmed().toUInt(); |
965 | const auto max = range.sliced(pos: sepIdx + 1).trimmed().toUInt(); |
966 | rangeValues.push_back(t: {.minimum: min, .maximum: max}); |
967 | } |
968 | } |
969 | } |
970 | |
971 | if (!rangeValues.isEmpty()) |
972 | signalRanges.insert(key: multiplexorSwitchName, value: rangeValues); |
973 | else |
974 | signalRanges.remove(key: multiplexorSwitchName); |
975 | |
976 | // update the value |
977 | multiplexedSignal.setMultiplexSignals(signalRanges); |
978 | messageDesc.addSignalDescription(description: multiplexedSignal); |
979 | m_messageDescriptions.insert(key: uid, value: messageDesc); |
980 | } |
981 | |
982 | void QCanDbcFileParserPrivate::parseValueDescriptions(const QStringView data) |
983 | { |
984 | // The regexp should match the following pattern: |
985 | // VAL_ message_id signal_name { value_description }; |
986 | // Here the value_description is defined as follows |
987 | // value_description = unsigned_int char_string |
988 | |
989 | // %1 valDef |
990 | // %2 maybeSpace |
991 | // %3 unsignedInt |
992 | // %4 oneOrMoreSpace |
993 | // %5 DbcIdentifier |
994 | // %6 charStr |
995 | const QString regExStr = |
996 | "%1%2(?<messageId>%3)%4(?<signalName>%5)(%4%3%4\"(%6)\")+%2;"_L1 . |
997 | arg(args: kValDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, args: kOneOrMoreSpaceRegExp, |
998 | args: kDbcIdentRegExp, args: kCharStrRegExp); |
999 | |
1000 | const QRegularExpression valueDescRegExp(regExStr); |
1001 | |
1002 | const auto match = valueDescRegExp.matchView(subjectView: data); |
1003 | if (!match.hasMatch()) { |
1004 | m_lineOffset = data.size(); |
1005 | addWarning(warning: QObject::tr(s: "Failed to parse value description from string %1" ).arg(a: data)); |
1006 | return; |
1007 | } |
1008 | |
1009 | m_lineOffset = match.capturedEnd(nth: 0); |
1010 | |
1011 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
1012 | if (!uidOptional) { |
1013 | addWarning(warning: QObject::tr(s: "Failed to parse value description from string %1" ).arg(a: data)); |
1014 | return; |
1015 | } |
1016 | |
1017 | const QtCanBus::UniqueId uid = uidOptional.value(); |
1018 | // Check if the message exists |
1019 | const auto messageDesc = m_messageDescriptions.value(key: uid); |
1020 | if (!messageDesc.isValid()) { |
1021 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
1022 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
1023 | return; |
1024 | } |
1025 | |
1026 | // Check if the signal exists within the message |
1027 | const QString signalName = match.captured(name: u"signalName"_s ); |
1028 | if (!messageDesc.signalDescriptionForName(name: signalName).isValid()) { |
1029 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
1030 | "Skipping string %2" ).arg(args: signalName, args: data)); |
1031 | return; |
1032 | } |
1033 | |
1034 | // We can have an arbitrary amount of value descriptions, so we can't use |
1035 | // capture groups to capture them. But we know that they follow a specific |
1036 | // pattern (because the full string matched the regexp). So we need to parse |
1037 | // the rest of the matched string manually |
1038 | const auto totalEnd = match.capturedEnd(nth: 0); // including the ';' |
1039 | const auto signalNameEnd = match.capturedEnd(name: u"signalName"_s ); |
1040 | const auto len = totalEnd - signalNameEnd - 1; |
1041 | if (len > 0) { |
1042 | auto signalDescriptionsView = data.sliced(pos: signalNameEnd, n: len).trimmed(); |
1043 | while (signalDescriptionsView.size()) { |
1044 | const auto spacePos = signalDescriptionsView.indexOf(c: u' '); |
1045 | if (spacePos == -1) |
1046 | break; |
1047 | bool ok = false; |
1048 | const auto value = signalDescriptionsView.sliced(pos: 0, n: spacePos).toUInt(ok: &ok); |
1049 | if (!ok) |
1050 | break; |
1051 | const auto firstQuotePos = signalDescriptionsView.indexOf(c: u'"', from: spacePos + 1); |
1052 | if (firstQuotePos == -1) |
1053 | break; |
1054 | const auto nextQuotePos = signalDescriptionsView.indexOf(c: u'"', from: firstQuotePos + 1); |
1055 | if (nextQuotePos == -1) |
1056 | break; |
1057 | const auto description = signalDescriptionsView.sliced( |
1058 | pos: firstQuotePos + 1, n: nextQuotePos - firstQuotePos - 1); |
1059 | |
1060 | m_valueDescriptions[uid][signalName].insert(key: value, value: description.toString()); |
1061 | signalDescriptionsView = signalDescriptionsView.sliced(pos: nextQuotePos + 1).trimmed(); |
1062 | } |
1063 | } |
1064 | } |
1065 | |
1066 | void QCanDbcFileParserPrivate::postProcessSignalMultiplexing() |
1067 | { |
1068 | // For the case of simple multiplexing we need to do the following for |
1069 | // every message description: |
1070 | // 1. Find the multiplexor signal |
1071 | // 2. Replace all kQtDummySignal entries with the name of the multiplexor |
1072 | // 3. While doing so, check if we have any signals with type |
1073 | // SwitchAndSignal. This will mean that extended multiplexing is used |
1074 | // 4. Also detect conditions when we have more than one multiplexor signal. |
1075 | // This is an error as well, and message description should be discarded. |
1076 | |
1077 | QList<QtCanBus::UniqueId> uidsToRemove; |
1078 | |
1079 | for (auto &messageDesc : m_messageDescriptions) { |
1080 | bool useExtendedMux = false; |
1081 | QString multiplexorSignal; |
1082 | auto &signalDescriptions = QCanMessageDescriptionPrivate::get(desc: messageDesc)->messageSignals; |
1083 | for (const auto &signalDesc : std::as_const(t&: signalDescriptions)) { |
1084 | if (signalDesc.multiplexState() == QtCanBus::MultiplexState::MultiplexorSwitch) { |
1085 | if (multiplexorSignal.isEmpty()) { |
1086 | multiplexorSignal = signalDesc.name(); |
1087 | } else { |
1088 | // invalid config |
1089 | multiplexorSignal.clear(); |
1090 | uidsToRemove.push_back(t: messageDesc.uniqueId()); |
1091 | break; |
1092 | } |
1093 | } else if (signalDesc.multiplexState() == QtCanBus::MultiplexState::SwitchAndSignal) { |
1094 | // extended multiplexing |
1095 | useExtendedMux = true; |
1096 | } |
1097 | } |
1098 | if (!useExtendedMux && !multiplexorSignal.isEmpty()) { |
1099 | // iterate through all signal descriptions and update kQtDummySignal |
1100 | for (auto &signalDesc : signalDescriptions) { |
1101 | if (signalDesc.multiplexState() == QtCanBus::MultiplexState::MultiplexedSignal) { |
1102 | auto &muxValues = QCanSignalDescriptionPrivate::get(desc: signalDesc)->muxSignals; |
1103 | auto val = muxValues.value(key: kQtDummySignal); |
1104 | muxValues.remove(key: kQtDummySignal); |
1105 | muxValues.insert(key: multiplexorSignal, value: val); |
1106 | } |
1107 | } |
1108 | } else if (useExtendedMux) { |
1109 | // Iterate through all signal descriptions and check that we do |
1110 | // not have any kQtDummySignal entries. It such entry exists, this |
1111 | // means that there were errors while parsing extended multiplexing |
1112 | // table, and this message description is invalid |
1113 | for (const auto &signalDesc : std::as_const(t&: signalDescriptions)) { |
1114 | const auto muxSignals = signalDesc.multiplexSignals(); |
1115 | if (muxSignals.contains(key: kQtDummySignal)) { |
1116 | uidsToRemove.push_back(t: messageDesc.uniqueId()); |
1117 | break; |
1118 | } |
1119 | } |
1120 | } |
1121 | } |
1122 | |
1123 | for (const auto &uid : std::as_const(t&: uidsToRemove)) { |
1124 | m_messageDescriptions.remove(key: uid); |
1125 | addWarning(warning: QObject::tr(s: "Message description with unique id %1 is skipped because " |
1126 | "it has invalid multiplexing description." ). |
1127 | arg(a: qToUnderlying(e: uid))); |
1128 | } |
1129 | } |
1130 | |
1131 | void QCanDbcFileParserPrivate::addWarning(QString &&warning) |
1132 | { |
1133 | m_warnings.emplace_back(args&: warning); |
1134 | } |
1135 | |
1136 | void QCanDbcFileParserPrivate::addCurrentMessage() |
1137 | { |
1138 | if (m_isProcessingMessage) { |
1139 | auto uid = m_currentMessage.uniqueId(); |
1140 | if (!m_currentMessage.isValid()) { |
1141 | addWarning(warning: QObject::tr(s: "Message description with unique id %1 is skipped " |
1142 | "because it's not valid." ).arg(a: qToUnderlying(e: uid))); |
1143 | } else if (m_messageDescriptions.contains(key: uid)) { |
1144 | addWarning(warning: QObject::tr(s: "Message description with unique id %1 is skipped " |
1145 | "because such unique id is already used." ). |
1146 | arg(a: qToUnderlying(e: uid))); |
1147 | } else { |
1148 | m_messageDescriptions.insert(key: uid, value: m_currentMessage); |
1149 | } |
1150 | m_currentMessage = {}; |
1151 | m_isProcessingMessage = false; |
1152 | } |
1153 | } |
1154 | |
1155 | QList<QCanMessageDescription> QCanDbcFileParserPrivate::getMessages() const |
1156 | { |
1157 | return QList<QCanMessageDescription>(m_messageDescriptions.cbegin(), |
1158 | m_messageDescriptions.cend()); |
1159 | } |
1160 | |
1161 | QT_END_NAMESPACE |
1162 | |