346 lines
9.8 KiB
C++
346 lines
9.8 KiB
C++
#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;
|
|
}
|