Skip to content

Commit 12afc73

Browse files
committed
feat: Support multiplexed signals
Parse the optional multiplexer indicator on SG_ lines (M, mN and mNM). Previously these lines did not match the signal regex and were silently dropped. - Signal gains is_multiplexor and multiplexer_value; is_multiplexed is now set from the parsed indicator. - Decoding reads the multiplexor's raw value and only emits the non multiplexed signals plus those matching that value. - New parse_signals / parse_message overloads return values keyed by signal name, since positional output is ambiguous for multiplexed messages. Extended multiplexing (SG_MUL_VAL_) is not supported yet: mNM signals are parsed, but all multiplexed signals are switched by the top level multiplexor.
1 parent 2c8acdb commit 12afc73

7 files changed

Lines changed: 255 additions & 60 deletions

File tree

‎include/libdbc/dbc.hpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <libdbc/message.hpp>
77
#include <regex>
88
#include <string>
9+
#include <unordered_map>
910
#include <vector>
1011

1112
namespace Libdbc {
@@ -32,6 +33,7 @@ class DbcParser : public Parser {
3233
std::vector<Libdbc::Message> get_messages() const;
3334

3435
Message::ParseSignalsStatus parse_message(uint32_t message_id, const std::vector<uint8_t>& data, std::vector<double>& out_values);
36+
Message::ParseSignalsStatus parse_message(uint32_t message_id, const std::vector<uint8_t>& data, std::unordered_map<std::string, double>& out_values);
3537

3638
std::vector<std::string> unused_lines() const;
3739

‎include/libdbc/message.hpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <iostream>
66
#include <libdbc/signal.hpp>
77
#include <string>
8+
#include <unordered_map>
89
#include <vector>
910

1011
namespace Libdbc {
@@ -21,7 +22,11 @@ struct Message {
2122
ErrorInvalidConversion,
2223
};
2324

25+
// Decodes the signals present in data, in signal order. For multiplexed messages only the multiplexor,
26+
// the non multiplexed signals and the signals matching the current multiplexer value are present.
2427
ParseSignalsStatus parse_signals(const std::vector<uint8_t>& data, std::vector<double>& values) const;
28+
// Same as above but keyed by signal name, so multiplexed messages can be decoded without tracking which signals are present.
29+
ParseSignalsStatus parse_signals(const std::vector<uint8_t>& data, std::unordered_map<std::string, double>& values) const;
2530

2631
void append_signal(const Signal& signal);
2732
std::vector<Signal> get_signals() const;

‎include/libdbc/signal.hpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ struct Signal {
1515
};
1616

1717
std::string name;
18-
bool is_multiplexed;
18+
bool is_multiplexed; // Signal is only present for a specific multiplexer value (mN)
19+
bool is_multiplexor = false; // Signal is the multiplexer switch of its message (M)
20+
uint32_t multiplexer_value = 0; // Multiplexer value this signal is present for. Only valid if is_multiplexed
1921
uint32_t start_bit;
2022
uint32_t size;
2123
bool is_bigendian;

‎src/dbc.cpp‎

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <libdbc/utils/utils.hpp>
1010
#include <regex>
1111
#include <string>
12+
#include <unordered_map>
1213
#include <vector>
1314

1415
namespace Libdbc {
@@ -17,6 +18,7 @@ const auto floatPattern = "(-?\\d+\\.?(\\d+)?)"; // Can be negative
1718

1819
const auto signalIdentifierPattern = "(SG_)";
1920
const auto namePattern = "(\\w+)";
21+
const auto multiplexerPattern = "(?:\\s(M|m\\d+M?))?"; // Optional: M = multiplexor, mN = multiplexed by value N, mNM = both
2022
const auto bitStartPattern = "(\\d+)"; // Cannot be negative
2123
const auto lengthPattern = "(\\d+)"; // Cannot be negative
2224
const auto byteOrderPattern = "([0-1])";
@@ -34,16 +36,17 @@ const auto receiverPattern = "([\\w\\,]+|Vector__XXX)*";
3436
const auto whiteSpace = "\\s";
3537

3638
constexpr unsigned SIGNAL_NAME_GROUP = 2;
37-
constexpr unsigned SIGNAL_START_BIT_GROUP = 3;
38-
constexpr unsigned SIGNAL_SIZE_GROUP = 4;
39-
constexpr unsigned SIGNAL_ENDIAN_GROUP = 5;
40-
constexpr unsigned SIGNAL_SIGNED_GROUP = 6;
41-
constexpr unsigned SIGNAL_FACTOR_GROUP = 7;
42-
constexpr unsigned SIGNAL_OFFSET_GROUP = 9;
43-
constexpr unsigned SIGNAL_MIN_GROUP = 11;
44-
constexpr unsigned SIGNAL_MAX_GROUP = 13;
45-
constexpr unsigned SIGNAL_UNIT_GROUP = 15;
46-
constexpr unsigned SIGNAL_RECIEVER_GROUP = 16;
39+
constexpr unsigned SIGNAL_MULTIPLEXER_GROUP = 3;
40+
constexpr unsigned SIGNAL_START_BIT_GROUP = 4;
41+
constexpr unsigned SIGNAL_SIZE_GROUP = 5;
42+
constexpr unsigned SIGNAL_ENDIAN_GROUP = 6;
43+
constexpr unsigned SIGNAL_SIGNED_GROUP = 7;
44+
constexpr unsigned SIGNAL_FACTOR_GROUP = 8;
45+
constexpr unsigned SIGNAL_OFFSET_GROUP = 10;
46+
constexpr unsigned SIGNAL_MIN_GROUP = 12;
47+
constexpr unsigned SIGNAL_MAX_GROUP = 14;
48+
constexpr unsigned SIGNAL_UNIT_GROUP = 16;
49+
constexpr unsigned SIGNAL_RECIEVER_GROUP = 17;
4750

4851
constexpr unsigned MESSAGE_ID_GROUP = 2;
4952
constexpr unsigned MESSAGE_NAME_GROUP = 3;
@@ -63,11 +66,9 @@ DbcParser::DbcParser()
6366
, node_re("^(BU_:)\\s?((?:[\\w]+?\\s?)*)?")
6467
, message_re("^(BO_)\\s(\\d+)\\s(\\w+)\\:\\s(\\d+)\\s(\\w+|Vector__XXX)")
6568
, value_re("^(VAL_)\\s(\\d+)\\s(\\w+)((?:\\s(\\d+)\\s\"([^\"]*)\")+)\\s;$")
66-
,
67-
// NOTE: No multiplex support yet
68-
signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + whiteSpace + "\\:" + whiteSpace + bitStartPattern + "\\|"
69-
+ lengthPattern + "\\@" + byteOrderPattern + signPattern + whiteSpace + offsetScalePattern + whiteSpace + minMaxPattern + whiteSpace + unitPattern
70-
+ whiteSpace + receiverPattern) {
69+
, signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + multiplexerPattern + whiteSpace + "\\:" + whiteSpace
70+
+ bitStartPattern + "\\|" + lengthPattern + "\\@" + byteOrderPattern + signPattern + whiteSpace + offsetScalePattern + whiteSpace
71+
+ minMaxPattern + whiteSpace + unitPattern + whiteSpace + receiverPattern) {
7172
}
7273

7374
void DbcParser::parse_file(std::istream& stream) {
@@ -128,6 +129,16 @@ Message::ParseSignalsStatus DbcParser::parse_message(const uint32_t message_id,
128129
return Message::ParseSignalsStatus::ErrorUnknownID;
129130
}
130131

132+
Message::ParseSignalsStatus
133+
DbcParser::parse_message(const uint32_t message_id, const std::vector<uint8_t>& data, std::unordered_map<std::string, double>& out_values) {
134+
for (const auto& message : messages) {
135+
if (message.id() == message_id) {
136+
return message.parse_signals(data, out_values);
137+
}
138+
}
139+
return Message::ParseSignalsStatus::ErrorUnknownID;
140+
}
141+
131142
void DbcParser::parse_dbc_header(std::istream& file_stream) {
132143
std::string line;
133144
std::smatch match;
@@ -183,7 +194,11 @@ void DbcParser::parse_dbc_messages(const std::vector<std::string>& lines) {
183194

184195
if (std::regex_search(line, match, signal_re) && !messages.empty()) {
185196
std::string name = match.str(SIGNAL_NAME_GROUP);
186-
bool is_multiplexed = false; // No support yet
197+
// Multiplexer indicator: "M", "mN" or "mNM" (extended multiplexing)
198+
std::string multiplexer = match.str(SIGNAL_MULTIPLEXER_GROUP);
199+
bool is_multiplexed = !multiplexer.empty() && multiplexer.front() == 'm';
200+
bool is_multiplexor = !multiplexer.empty() && multiplexer.back() == 'M';
201+
uint32_t multiplexer_value = is_multiplexed ? static_cast<uint32_t>(std::stoul(multiplexer.substr(1))) : 0;
187202
uint32_t start_bit = static_cast<uint32_t>(std::stoul(match.str(SIGNAL_START_BIT_GROUP)));
188203
uint32_t size = static_cast<uint32_t>(std::stoul(match.str(SIGNAL_SIZE_GROUP)));
189204
bool is_bigendian = (std::stoul(match.str(SIGNAL_ENDIAN_GROUP)) == 0);
@@ -200,6 +215,8 @@ void DbcParser::parse_dbc_messages(const std::vector<std::string>& lines) {
200215
Utils::String::split(match.str(SIGNAL_RECIEVER_GROUP), receivers, ',');
201216

202217
Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers);
218+
sig.is_multiplexor = is_multiplexor;
219+
sig.multiplexer_value = multiplexer_value;
203220
messages.back().append_signal(sig);
204221
continue;
205222
}

‎src/message.cpp‎

Lines changed: 79 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <libdbc/signal.hpp>
55
#include <ostream>
66
#include <string>
7+
#include <unordered_map>
78
#include <vector>
89

910
namespace Libdbc {
@@ -26,10 +27,55 @@ bool Message::operator==(const Message& rhs) const {
2627
return (m_id == rhs.id()) && (m_name == rhs.m_name) && (m_size == rhs.m_size) && (m_node == rhs.m_node);
2728
}
2829

29-
Message::ParseSignalsStatus Message::parse_signals(const std::vector<uint8_t>& data, std::vector<double>& values) const {
30+
namespace {
31+
32+
// Extracts the raw (unscaled) bits of a signal. Bits above signal.size are not masked off.
33+
uint64_t extract_raw(const Signal& signal, uint64_t data_little_endian, uint64_t data_big_endian, std::size_t len) {
34+
if (signal.is_bigendian) {
35+
uint32_t start_bit = ONE_BYTE * (signal.start_bit / ONE_BYTE) + (SEVEN_BITS - (signal.start_bit % ONE_BYTE)); // Calculation taken from python CAN
36+
uint64_t value = data_big_endian << start_bit;
37+
return value >> (len - signal.size);
38+
}
39+
return data_little_endian >> signal.start_bit;
40+
}
41+
42+
double to_physical(const Signal& signal, uint64_t value) {
43+
if (signal.is_signed && signal.size > 1) {
44+
switch (signal.size) {
45+
case ONE_BYTE:
46+
return static_cast<int8_t>(value) * signal.factor + signal.offset;
47+
case TWO_BYTES:
48+
return static_cast<int16_t>(value) * signal.factor + signal.offset;
49+
case FOUR_BYTES:
50+
return static_cast<int32_t>(value) * signal.factor + signal.offset;
51+
case EIGHT_BYTES:
52+
return static_cast<double>(value) * signal.factor + signal.offset;
53+
default: {
54+
// 2 complement -> decimal
55+
const bool is_negative = (value & (1ULL << (signal.size - 1))) != 0;
56+
int64_t nativeInt = 0;
57+
if (is_negative) {
58+
nativeInt = static_cast<int64_t>(value | ~((1ULL << signal.size) - 1)); // invert all bits above signal.size
59+
} else {
60+
nativeInt = static_cast<int64_t>(value & ((1ULL << signal.size) - 1)); // masking
61+
}
62+
return static_cast<double>(nativeInt) * signal.factor + signal.offset;
63+
}
64+
}
65+
}
66+
// use only the relevant bits
67+
value = value & ((1 << signal.size) - 1); // masking
68+
return static_cast<double>(value) * signal.factor + signal.offset;
69+
}
70+
71+
// Decodes every signal that is present in the frame and hands it to on_value(signal, value).
72+
// Multiplexed signals are only present when the multiplexor's raw value matches their multiplexer value.
73+
// NOTE: Extended multiplexing (SG_MUL_VAL_) is not supported. All multiplexed signals are switched by the top level multiplexor.
74+
template<typename OnValue>
75+
Message::ParseSignalsStatus decode_signals(const std::vector<Signal>& signals, const std::vector<uint8_t>& data, OnValue on_value) {
3076
auto size = data.size();
3177
if (size > ONE_BYTE) {
32-
return ParseSignalsStatus::ErrorMessageToLong; // not supported yet
78+
return Message::ParseSignalsStatus::ErrorMessageToLong; // not supported yet
3379
}
3480

3581
uint64_t data_little_endian = 0;
@@ -42,50 +88,41 @@ Message::ParseSignalsStatus Message::parse_signals(const std::vector<uint8_t>& d
4288
// TODO: does this also work on a big endian machine?
4389

4490
const auto len = size * 8;
45-
uint64_t value = 0;
46-
for (const auto& signal : m_signals) {
47-
if (signal.is_bigendian) {
48-
uint32_t start_bit = ONE_BYTE * (signal.start_bit / ONE_BYTE) + (SEVEN_BITS - (signal.start_bit % ONE_BYTE)); // Calculation taken from python CAN
49-
value = data_big_endian << start_bit;
50-
value = value >> (len - signal.size);
51-
} else {
52-
value = data_little_endian >> signal.start_bit;
53-
}
5491

55-
if (signal.is_signed && signal.size > 1) {
56-
switch (signal.size) {
57-
case ONE_BYTE:
58-
values.push_back(static_cast<int8_t>(value) * signal.factor + signal.offset);
59-
break;
60-
case TWO_BYTES:
61-
values.push_back(static_cast<int16_t>(value) * signal.factor + signal.offset);
62-
break;
63-
case FOUR_BYTES:
64-
values.push_back(static_cast<int32_t>(value) * signal.factor + signal.offset);
65-
break;
66-
case EIGHT_BYTES:
67-
values.push_back(static_cast<double>(value) * signal.factor + signal.offset);
68-
break;
69-
default: {
70-
// 2 complement -> decimal
71-
const bool is_negative = (value & (1ULL << (signal.size - 1))) != 0;
72-
int64_t nativeInt = 0;
73-
if (is_negative) {
74-
nativeInt = static_cast<int64_t>(value | ~((1ULL << signal.size) - 1)); // invert all bits above signal.size
75-
} else {
76-
nativeInt = static_cast<int64_t>(value & ((1ULL << signal.size) - 1)); // masking
77-
}
78-
values.push_back(static_cast<double>(nativeInt) * signal.factor + signal.offset);
79-
break;
92+
bool has_multiplexor = false;
93+
uint64_t multiplexer_value = 0;
94+
for (const auto& signal : signals) {
95+
if (signal.is_multiplexor && !signal.is_multiplexed) {
96+
multiplexer_value = extract_raw(signal, data_little_endian, data_big_endian, len);
97+
if (signal.size < EIGHT_BYTES) {
98+
multiplexer_value &= (1ULL << signal.size) - 1;
8099
}
81-
}
82-
} else {
83-
// use only the relevant bits
84-
value = value & ((1 << signal.size) - 1); // masking
85-
values.push_back(static_cast<double>(value) * signal.factor + signal.offset);
100+
has_multiplexor = true;
101+
break;
102+
}
103+
}
104+
105+
for (const auto& signal : signals) {
106+
if (signal.is_multiplexed && (!has_multiplexor || signal.multiplexer_value != multiplexer_value)) {
107+
continue;
86108
}
109+
on_value(signal, to_physical(signal, extract_raw(signal, data_little_endian, data_big_endian, len)));
87110
}
88-
return ParseSignalsStatus::Success;
111+
return Message::ParseSignalsStatus::Success;
112+
}
113+
114+
}
115+
116+
Message::ParseSignalsStatus Message::parse_signals(const std::vector<uint8_t>& data, std::vector<double>& values) const {
117+
return decode_signals(m_signals, data, [&values](const Signal&, double value) {
118+
values.push_back(value);
119+
});
120+
}
121+
122+
Message::ParseSignalsStatus Message::parse_signals(const std::vector<uint8_t>& data, std::unordered_map<std::string, double>& values) const {
123+
return decode_signals(m_signals, data, [&values](const Signal& signal, double value) {
124+
values[signal.name] = value;
125+
});
89126
}
90127

91128
void Message::append_signal(const Signal& signal) {

‎src/signal.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ Signal::Signal(std::string name,
3232
}
3333

3434
bool Signal::operator==(const Signal& rhs) const {
35-
return (this->name == rhs.name) && (this->is_multiplexed == rhs.is_multiplexed) && (this->start_bit == rhs.start_bit) && (this->size == rhs.size)
35+
return (this->name == rhs.name) && (this->is_multiplexed == rhs.is_multiplexed) && (this->is_multiplexor == rhs.is_multiplexor)
36+
&& (this->multiplexer_value == rhs.multiplexer_value) && (this->start_bit == rhs.start_bit) && (this->size == rhs.size)
3637
&& (this->is_bigendian == rhs.is_bigendian) && (this->is_signed == rhs.is_signed) && (this->offset == rhs.offset) && (this->min == rhs.min)
3738
&& (this->max == rhs.max) && (this->unit == rhs.unit) && (this->receivers == rhs.receivers);
3839
}
@@ -44,6 +45,10 @@ bool Signal::operator<(const Signal& rhs) const {
4445
std::ostream& operator<<(std::ostream& out, const Signal& sig) {
4546
out << "Signal {name: " << sig.name << ", ";
4647
out << "Multiplexed: " << (sig.is_multiplexed ? "True" : "False") << ", ";
48+
if (sig.is_multiplexed) {
49+
out << "Multiplexer value: " << sig.multiplexer_value << ", ";
50+
}
51+
out << "Multiplexor: " << (sig.is_multiplexor ? "True" : "False") << ", ";
4752
out << "Start bit: " << sig.start_bit << ", ";
4853
out << "Size: " << sig.size << ", ";
4954
out << "Endianness: " << (sig.is_bigendian ? "Big endian" : "Little endian") << ", ";

0 commit comments

Comments
 (0)