Closes #24
This commit is contained in:
1 parent
fc97682b37
commit
e555ebb571
16 files changed
+337
-52
No files matched your search
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
@@ -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));
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
struct DbcParseContext {
|
||||
DbcDatabase &database;
|
||||
FrameInfo *currentFrame = nullptr;
|
||||
bool enableJ1939 = false;
|
||||
std::size_t lineNumber = 0U;
|
||||
std::vector<PendingValueDescriptions> pendingValues = {};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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. */
|
||||
|
||||
@@ -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 */
|
||||
Reference in new issue
Block a user