This commit is contained in:
deeaitch committed 2026-10-20 13:12:30 -04:00
1 parent fc97682b37
commit e555ebb571
16 files changed
+337 -52

No files matched your search

+1
View File
@@ -8,6 +8,7 @@ SOURCES += \
$$PWD/tree_node.cpp
HEADERS += \
$$PWD/j1939.h \
$$PWD/signal_layout.h \
$$PWD/definition_key.h \
$$PWD/dbc_diagnostic.h \
+19 -3
View File
@@ -10,6 +10,7 @@
*/
#include "dbc_decoder.h"
#include "j1939.h"
#include "signal_layout.h"
#include <cmath>
#include <stdexcept>
@@ -19,7 +20,17 @@
std::shared_ptr<const DecodeFrame> DbcDecoder::FindFrame(const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const {
return database.FindFrame(FrameKey(canId, isExtended));
return ResolveFrame(database, canId, isExtended).definition;
}
PgnLookupResult DbcDecoder::ResolveFrame(const DecodeDatabase &database,
std::uint32_t canId, bool isExtended) {
const std::shared_ptr<const DecodeFrame> exact = database.FindFrame(FrameKey(canId, isExtended));
if (exact)
return {exact, false};
if (!isExtended || canId > 0x1FFFFFFFU)
return {};
return database.FindFrameByPgn(ExtractJ1939Pgn(canId));
}
DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database,
@@ -38,10 +49,15 @@ DecodedFrameValue DbcDecoder::DecodeImpl(const DecodeDatabase &database,
const std::vector<SignalKey> *selection) const {
DecodedFrameValue result;
result.key = FrameKey(frame.canId, frame.isExtended);
const std::shared_ptr<const DecodeFrame> definition = FindFrame(database, frame.canId, frame.isExtended);
const PgnLookupResult match = ResolveFrame(database, frame.canId, frame.isExtended);
if (match.ambiguous) {
result.status = FrameDecodeStatus::AmbiguousFrame;
return result;
}
const std::shared_ptr<const DecodeFrame> definition = match.definition;
if (definition == nullptr)
return result;
result.key = definition->GetKey();
std::vector<const DecodeSignal *> selected;
if (selection != nullptr) {
+10 -5
View File
@@ -43,7 +43,8 @@ enum class FrameDecodeStatus {
UnknownFrame,
InsufficientPayload,
SignalErrors,
Valid
Valid,
AmbiguousFrame
};
/**
@@ -70,7 +71,7 @@ struct DecodedSignalValue {
*/
struct DecodedFrameValue {
std::shared_ptr<const DecodeFrame> definition; /**< Owning immutable definition. */
FrameKey key; /**< Stable application identifier. */
FrameKey key; /**< Matched definition key; received key if unmatched. */
std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */
bool valid = false; /**< true if frame was matched. */
FrameDecodeStatus status = FrameDecodeStatus::UnknownFrame; /**< Inspect signals for individual failures. */
@@ -84,11 +85,11 @@ struct DecodedFrameValue {
class DbcDecoder {
public:
/**
* @brief Find frame definition by CAN ID.
* @brief Find exact CAN ID first, then unique PGN among explicitly J1939 definitions.
* @param database Runtime decode database.
* @param canId Normalized CAN ID.
* @param isExtended true for extended frame.
* @return Owning immutable frame handle or nullptr. Safe across reload.
* @return Owning immutable handle, or nullptr for missing/ambiguous matches. Safe across reload.
*/
std::shared_ptr<const DecodeFrame> FindFrame(const DecodeDatabase &database,
std::uint32_t canId,
@@ -108,7 +109,8 @@ public:
* @brief Decode selected signals in selection order using stable keys.
* An empty selection returns no signals. Multiplex selectors are read internally.
* Frame status summarizes selected signals; short payload still takes precedence.
* Unknown frames return UnknownFrame before checking the selection.
* Unknown/ambiguous frames return before checking the selection.
* For PGN matches, selection keys must belong to the matched DBC definition.
* @throws std::invalid_argument for a foreign frame key, unknown signal, or duplicate key.
*/
DecodedFrameValue Decode(const DecodeDatabase &database,
@@ -116,6 +118,9 @@ public:
const std::vector<SignalKey> &selection) const;
private:
static PgnLookupResult ResolveFrame(const DecodeDatabase &database,
std::uint32_t canId, bool isExtended);
DecodedFrameValue DecodeImpl(const DecodeDatabase &database,
const RawCanFrame &frame,
const std::vector<SignalKey> *selection) const;
+1
View File
@@ -83,6 +83,7 @@ DbcParseResult DbcParser::ParseFileWithDiagnostics(const std::string &filePath)
DbcDatabase database;
DbcParseContext context{database, nullptr};
context.enableJ1939 = options.enableJ1939;
const DbcStatementDispatcher dispatcher;
bool inNamespace = false;
std::size_t lineNumber = 0U;
+9
View File
@@ -16,6 +16,11 @@
#include "dbc_diagnostic.h"
/** Explicit interpretation applied to extended frames in this file. */
struct DbcParserOptions {
bool enableJ1939 = false;
};
/**
* @brief Minimal DBC parser.
*
@@ -34,6 +39,8 @@
*/
class DbcParser {
public:
explicit DbcParser(DbcParserOptions options = {}) : options(options) {}
/**
* @brief Parse DBC file.
* @param filePath Path to input file.
@@ -53,6 +60,8 @@ public:
*/
DbcParseResult ParseFileWithDiagnostics(const std::string &filePath) const;
private:
DbcParserOptions options;
};
#endif /* DBC_PARSER_H */
+5 -26
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include "j1939.h"
#include "text_utils.h"
#include "signal_layout.h"
#include <charconv>
@@ -167,31 +168,6 @@ namespace {
normalizedCanId = rawCanId;
}
}
std::uint32_t 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;
}
} // namespace
FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
@@ -228,7 +204,6 @@ FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn);
return frame;
}
@@ -307,6 +282,10 @@ namespace {
void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
FrameInfo frame = ParseFrameLine(line);
if (context.enableJ1939 && frame.isExtended) {
frame.hasPgn = true;
frame.pgn = ExtractJ1939Pgn(frame.canId);
}
if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
" frame CAN ID: " + std::to_string(frame.canId));
+1
View File
@@ -41,6 +41,7 @@ public:
struct DbcParseContext {
DbcDatabase &database;
FrameInfo *currentFrame = nullptr;
bool enableJ1939 = false;
std::size_t lineNumber = 0U;
std::vector<PendingValueDescriptions> pendingValues = {};
+22
View File
@@ -2,6 +2,7 @@
// Copyright (c) 2026 Dim Himro
#include "decode_database.h"
#include "j1939.h"
#include "signal_layout.h"
#include <bitset>
#include <cmath>
@@ -55,6 +56,9 @@ namespace {
if (frame.canId > maximumId)
throw std::invalid_argument("Invalid normalized CAN ID: " + std::to_string(frame.canId));
if (frame.hasPgn && (!frame.isExtended || frame.pgn != ExtractJ1939Pgn(frame.canId)))
throw std::invalid_argument("Invalid J1939 PGN metadata: " + frame.name);
std::unordered_set<std::string> names;
const DecodeSignal *selector = nullptr;
for (const DecodeSignal &signal: frame.signals) {
@@ -99,6 +103,8 @@ DecodeDatabase::Storage::Storage(const std::vector<DecodeFrame> &definitions)
frameIndexByKey.reserve(frames.size());
for (std::size_t index = 0U; index < frames.size(); ++index) {
frameIndexByKey.emplace(frames[index].GetKey(), index);
if (frames[index].hasPgn)
frameIndicesByPgn[frames[index].pgn].push_back(index);
}
}
@@ -121,6 +127,22 @@ std::shared_ptr<const DecodeFrame> DecodeDatabase::FindFrame(const FrameKey &key
return std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second]);
}
PgnLookupResult DecodeDatabase::FindFrameByPgn(std::uint32_t pgn) const {
PgnLookupResult result;
if (!storage)
return result;
const std::unordered_map<std::uint32_t, std::vector<std::size_t>>::const_iterator it =
storage->frameIndicesByPgn.find(pgn);
if (it == storage->frameIndicesByPgn.end())
return result;
if (it->second.size() != 1U) {
result.ambiguous = true;
return result;
}
result.definition = std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second[0]]);
return result;
}
std::shared_ptr<const DecodeSignal> DecodeDatabase::FindSignal(const SignalKey &key) const {
const std::shared_ptr<const DecodeFrame> frame = FindFrame(key.frame);
if (!frame)
+11 -1
View File
@@ -68,7 +68,7 @@ struct DecodeFrame {
bool isExtended = false; /**< true for extended frame. */
std::uint8_t dlc = 0U; /**< Payload length. */
std::uint32_t pgn = 0U; /**< PGN if available. */
bool hasPgn = false; /**< true if PGN is valid. */
bool hasPgn = false; /**< Explicit J1939 opt-in; pgn must match canId. */
std::string transmitter; /**< Transmitter ECU. */
std::string comment; /**< Frame comment. */
std::vector<DecodeSignal> signals; /**< Signal definitions. */
@@ -80,6 +80,12 @@ struct DecodeFrame {
}
};
/** A PGN lookup rejects multiple definitions rather than choosing by order. */
struct PgnLookupResult {
std::shared_ptr<const DecodeFrame> definition;
bool ambiguous = false;
};
/**
* @brief Immutable runtime snapshot. Copies share storage; reload replaces a snapshot.
* Owning lookup handles and decoded results keep old definitions alive.
@@ -98,6 +104,8 @@ public:
const std::vector<DecodeFrame> &GetFrames() const;
/** Owning handles retain the entire snapshot across reload or destruction. */
std::shared_ptr<const DecodeFrame> FindFrame(const FrameKey &key) const;
/** Only explicitly marked hasPgn frames participate; ambiguous results have no definition. */
PgnLookupResult FindFrameByPgn(std::uint32_t pgn) const;
std::shared_ptr<const DecodeSignal> FindSignal(const SignalKey &key) const;
private:
@@ -105,6 +113,8 @@ private:
std::vector<DecodeFrame> frames;
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
std::unordered_map<std::uint32_t, std::vector<std::size_t>> frameIndicesByPgn;
explicit Storage(const std::vector<DecodeFrame> &definitions);
};
+1 -1
View File
@@ -26,7 +26,7 @@ struct FrameInfo {
std::uint32_t canId = 0U; /**< Normalized CAN identifier. */
bool isExtended = false; /**< true for extended frame. */
std::uint32_t pgn = 0U; /**< J1939 PGN if applicable. */
bool hasPgn = false; /**< true if PGN was derived. */
bool hasPgn = false; /**< Explicit J1939 opt-in; pgn is derived from canId. */
std::uint8_t dlc = 0U; /**< Frame payload length. */
std::string transmitter; /**< Transmitter ECU name. */
std::string comment; /**< Optional frame comment. */
+17
View File
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
#ifndef J1939_H
#define J1939_H
#include <cstdint>
/** Extract an 18-bit PGN from a normalized 29-bit ID explicitly interpreted as J1939.
* Priority and source address are excluded. PDU1 also excludes destination address.
* Both data-page bits are retained; this does not validate the J1939 protocol.
*/
inline std::uint32_t ExtractJ1939Pgn(std::uint32_t canId) {
const std::uint32_t pf = (canId >> 16U) & 0xFFU;
const std::uint32_t pgn = (canId >> 8U) & 0x3FFFFU;
return pf < 240U ? pgn & 0x3FF00U : pgn;
}
#endif /* J1939_H */