diff --git a/include/libdbc/message.hpp b/include/libdbc/message.hpp index 90a1dc1..a6613b2 100644 --- a/include/libdbc/message.hpp +++ b/include/libdbc/message.hpp @@ -29,8 +29,9 @@ struct Message { ParseSignalsStatus parseSignals(const std::vector& data, std::vector& values) const; void appendSignal(const Signal& signal); - const std::vector signals() const; + const std::vector getSignals() const; uint32_t id() const; + void addValueDescription(const std::string& signal_name, const std::vector&); virtual bool operator==(const Message& rhs) const; diff --git a/include/libdbc/signal.hpp b/include/libdbc/signal.hpp index 30178de..f41cef6 100644 --- a/include/libdbc/signal.hpp +++ b/include/libdbc/signal.hpp @@ -8,6 +8,11 @@ namespace libdbc { struct Signal { + struct SignalValueDescriptions { + uint32_t value; + std::string description; + }; + std::string name; bool is_multiplexed; uint32_t start_bit; @@ -20,6 +25,7 @@ struct Signal { double max; std::string unit; std::vector receivers; + std::vector svDescriptions; Signal() = delete; explicit Signal(std::string name, diff --git a/src/dbc.cpp b/src/dbc.cpp index c091408..63043d3 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -25,6 +25,120 @@ const auto unitPattern = "\"(.*)\""; // Random string const auto receiverPattern = "([\\w\\,]+|Vector__XXX)*"; const auto whiteSpace = "\\s"; +enum VALToken { Identifier = 0, CANId, SignalName, Value, Description }; + +struct VALObject { + uint32_t can_id; + std::string signal_name; + std::vector vd; +}; + +bool parseVal(const std::string& str, VALObject& obj) { + obj.signal_name = ""; + obj.vd.clear(); + auto state = Identifier; + const char* a = str.data(); + libdbc::Signal::SignalValueDescriptions vd; + for (;;) { + switch (state) { + case Identifier: { + if (*a != 'V') + return false; + a++; + if (*a != 'A') + return false; + a++; + if (*a != 'L') + return false; + a++; + if (*a != '_') + return false; + a++; + if (*a != ' ') + return false; + a++; // skip whitespace + state = CANId; + break; + } + case CANId: { + std::string can_id_str; + while (*a >= '0' && *a <= '9') { + can_id_str += *a; + a++; + } + if (can_id_str.empty()) + return false; + obj.can_id = std::stoul(can_id_str); + if (*a != ' ') + return false; + a++; // skip whitespace + state = SignalName; + break; + } + case SignalName: { + if ((*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || *a == '_') + obj.signal_name += *a; + else + return false; + a++; + while ((*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || *a == '_' || (*a >= '0' && *a <= '9')) { + obj.signal_name += *a; + a++; + } + if (*a != ' ') + return false; + a++; // skip whitespace + state = Value; + break; + } + case Value: { + std::string value_str; + while (*a >= '0' && *a <= '9') { + value_str += *a; + a++; + } + if (*a == ';') { + if (value_str.empty()) + return true; + return false; + } + if (value_str.empty()) + return false; + + if (*a != ' ') + return false; + a++; // skip whitespace + vd.value = (uint32_t)std::stoul(value_str); + state = Description; + break; + } + case Description: { + std::string desc; + if (*a != '"') + return false; + a++; + while (*a != '"' && *a != 0) { + desc += *a; + a++; + } + if (*a == 0) + return false; + a++; + if (*a != ' ') + return false; + a++; // skip whitespace + + vd.description = desc; + obj.vd.push_back(vd); + + state = Value; + break; + } + } + } + return false; +} + } // anonymous namespace namespace libdbc { @@ -123,6 +237,9 @@ void DbcParser::parse_dbc_nodes(std::istream& file_stream) { void DbcParser::parse_dbc_messages(const std::vector& lines) { std::smatch match; + std::vector sv; + + VALObject obj; for (const auto& line : lines) { if (std::regex_search(line, match, message_re)) { uint32_t id = std::stoul(match.str(2)); @@ -133,6 +250,7 @@ void DbcParser::parse_dbc_messages(const std::vector& lines) { Message msg(id, name, size, node); messages.push_back(msg); + continue; } if (std::regex_search(line, match, signal_re)) { @@ -158,6 +276,21 @@ void DbcParser::parse_dbc_messages(const std::vector& lines) { Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers); messages.back().appendSignal(sig); + continue; + } + + if (parseVal(line, obj)) { + sv.push_back(obj); + continue; + } + } + + for (const auto& obj : sv) { + for (auto& msg : messages) { + if (msg.id() == obj.can_id) { + msg.addValueDescription(obj.signal_name, obj.vd); + break; + } } } } diff --git a/src/message.cpp b/src/message.cpp index 7a2331b..1739b92 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -67,7 +67,7 @@ void Message::appendSignal(const Signal& signal) { m_signals.push_back(signal); } -const std::vector Message::signals() const { +const std::vector Message::getSignals() const { return m_signals; } @@ -75,6 +75,15 @@ uint32_t Message::id() const { return m_id; } +void Message::addValueDescription(const std::string& signal_name, const std::vector& vd) { + for (auto& s : m_signals) { + if (s.name.compare(signal_name) == 0) { + s.svDescriptions = vd; + return; + } + } +} + std::ostream& operator<<(std::ostream& out, const Message& msg) { out << "Message: {id: " << msg.id() << ", "; out << "name: " << msg.m_name << ", "; diff --git a/test/test_dbc.cpp b/test/test_dbc.cpp index 1c218aa..9c49888 100644 --- a/test/test_dbc.cpp +++ b/test/test_dbc.cpp @@ -57,7 +57,7 @@ TEST_CASE("Testing dbc file loading", "[fileio]") { REQUIRE(parser->get_messages() == msgs); - REQUIRE(parser->get_messages().front().signals() == msg.signals()); + REQUIRE(parser->get_messages().front().getSignals() == msg.getSignals()); } } @@ -80,13 +80,13 @@ TEST_CASE("Testing big endian, little endian") { parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals().size() == 2); + REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); { - const auto signal = parser.get_messages().at(0).signals().at(0); + const auto signal = parser.get_messages().at(0).getSignals().at(0); REQUIRE(signal.is_bigendian == true); } { - const auto signal = parser.get_messages().at(0).signals().at(1); + const auto signal = parser.get_messages().at(0).getSignals().at(1); REQUIRE(signal.is_bigendian == false); } } @@ -104,31 +104,31 @@ TEST_CASE("Testing negative values") { parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals().size() == 4); + REQUIRE(parser.get_messages().at(0).getSignals().size() == 4); SECTION("Evaluating first message") { - const auto signal = parser.get_messages().at(0).signals().at(0); + const auto signal = parser.get_messages().at(0).getSignals().at(0); REQUIRE(signal.factor == 0.1); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating second message") { - const auto signal = parser.get_messages().at(0).signals().at(1); + const auto signal = parser.get_messages().at(0).getSignals().at(1); REQUIRE(signal.factor == 0.1); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating third message") { - const auto signal = parser.get_messages().at(0).signals().at(2); + const auto signal = parser.get_messages().at(0).getSignals().at(2); REQUIRE(signal.factor == 10); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating fourth message") { - const auto signal = parser.get_messages().at(0).signals().at(3); + const auto signal = parser.get_messages().at(0).getSignals().at(3); REQUIRE(signal.factor == 1); REQUIRE(signal.offset == -10); REQUIRE(signal.min == 0); @@ -146,9 +146,103 @@ TEST_CASE("Special characters in unit") { parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals().size() == 1); + REQUIRE(parser.get_messages().at(0).getSignals().size() == 1); SECTION("Checking that signal with special characters as unit is parsed correctly") { - const auto signal = parser.get_messages().at(0).signals().at(0); + const auto signal = parser.get_messages().at(0).getSignals().at(0); REQUIRE(signal.unit.compare("Km/h") == 0); } } + +TEST_CASE("Signal Value Description") { + const auto* filename = std::tmpnam(NULL); + + create_tmp_dbc_with(filename, R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!" ;)"); + + auto parser = libdbc::DbcParser(); + parser.parse_file(filename); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); + + REQUIRE(parser.get_messages().at(0).getSignals().at(0).svDescriptions.size() == 3); + REQUIRE(parser.get_messages().at(0).getSignals().at(1).svDescriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).getSignals().at(0); + REQUIRE(signal.svDescriptions.at(0).value == 123); + REQUIRE(signal.svDescriptions.at(0).description == "Description 1"); + REQUIRE(signal.svDescriptions.at(1).value == 0); + REQUIRE(signal.svDescriptions.at(1).description == "Description 2"); + REQUIRE(signal.svDescriptions.at(2).value == 90903489); + REQUIRE(signal.svDescriptions.at(2).description == "Big value and special characters &$§())!"); +} + +TEST_CASE("Signal Value Description Extended CAN id") { + /* + * It should not crash, even extended CAN id is used + */ + const auto* filename = std::tmpnam(NULL); + + create_tmp_dbc_with(filename, R"(BO_ 3221225472 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" 4000000000 "Big value and special characters &$§())!" ;)"); + + auto parser = libdbc::DbcParser(); + parser.parse_file(filename); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); + + REQUIRE(parser.get_messages().at(0).getSignals().at(0).svDescriptions.size() == 3); + REQUIRE(parser.get_messages().at(0).getSignals().at(1).svDescriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).getSignals().at(0); + REQUIRE(signal.svDescriptions.at(0).value == 123); + REQUIRE(signal.svDescriptions.at(0).description == "Description 1"); + REQUIRE(signal.svDescriptions.at(1).value == 0); + REQUIRE(signal.svDescriptions.at(1).description == "Description 2"); + REQUIRE(signal.svDescriptions.at(2).value == 4000000000); + REQUIRE(signal.svDescriptions.at(2).description == "Big value and special characters &$§())!"); +} + +TEST_CASE("Signal Value Multiple VAL_") { + /* + * It should not crash, even extended CAN id is used + */ + const auto* filename = std::tmpnam(NULL); + + create_tmp_dbc_with(filename, R"(BO_ 3221225472 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3" +BO_ 123 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" ; +VAL_ 123 State1 123 "Description 3" 0 "Description 4" ;)"); + + auto parser = libdbc::DbcParser(); + parser.parse_file(filename); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages().at(0).getSignals().size() == 2); + + REQUIRE(parser.get_messages().at(0).getSignals().at(0).svDescriptions.size() == 2); + REQUIRE(parser.get_messages().at(0).getSignals().at(1).svDescriptions.size() == 0); + REQUIRE(parser.get_messages().at(1).getSignals().at(0).svDescriptions.size() == 2); + REQUIRE(parser.get_messages().at(1).getSignals().at(1).svDescriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).getSignals().at(0); + REQUIRE(signal.svDescriptions.at(0).value == 123); + REQUIRE(signal.svDescriptions.at(0).description == "Description 1"); + REQUIRE(signal.svDescriptions.at(1).value == 0); + REQUIRE(signal.svDescriptions.at(1).description == "Description 2"); + + const auto signal2 = parser.get_messages().at(1).getSignals().at(0); + REQUIRE(signal2.svDescriptions.at(0).value == 123); + REQUIRE(signal2.svDescriptions.at(0).description == "Description 3"); + REQUIRE(signal2.svDescriptions.at(1).value == 0); + REQUIRE(signal2.svDescriptions.at(1).description == "Description 4"); +}