#include "dbc_parser.h" #include #include #include #include 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 (text[begin]))) ++begin; std::string::size_type end = text.size(); while ((end > begin) && std::isspace (static_cast (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 DbcParser::SplitReceivers (const std::string &text) { std::vector 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 (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::stoul (TrimText (rest.substr (0U, pipePos))) ); signal.length = static_cast ( 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; }