cabana: support multiplexed signals (#28309)

* support muxed signals

* write multiplexor in generateDBC

* edit multiplex_switch_value in signalView

* no overlapping warning for mux signals

* group signals by multiplexer indicator

* display freq for each multiplexed signals

* remove all multiplexed signals after switch deleted

* disable switch value

* cleanup

* historyView: use getValue

* sort by switch value

* check address

* rename variables

* rename signale type

* parse multiplexed signals in dbcmanater

* cache signal color in member variable

* cleanup num_decimals

* remove sources from dbcmanager and cleanup code

* fix sort

* check mltiplex in operator==

* fix sizehint

* convert multipledxed to normal after changing multiplxor to normal

* throw error on multiple 'M' signals

* add comment

* parse multipled signals in test case

* cleanup

* change order

* cleanup open

* display multiplexed/overlapping signals in binaryview

* sort overlapped signals by size

* refactor dbcmanager

* trimmed

* parse multiplexed signals in test case

* cleanup

* merge master

* space

* use pointer for sigs

* alldbcFiles

* cleanup

* cleanup sparkline

* use std::vector

* skip draw sparkline if isnull

* bigger capacity
pull/28537/head
Dean Lee 2 years ago committed by GitHub
parent 7128daebba
commit e08569b0f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      tools/cabana/binaryview.cc
  2. 16
      tools/cabana/chart/chart.cc
  3. 21
      tools/cabana/chart/sparkline.cc
  4. 3
      tools/cabana/chart/sparkline.h
  5. 37
      tools/cabana/commands.cc
  6. 5
      tools/cabana/commands.h
  7. 44
      tools/cabana/dbc/dbc.cc
  8. 20
      tools/cabana/dbc/dbc.h
  9. 100
      tools/cabana/dbc/dbcfile.cc
  10. 4
      tools/cabana/dbc/dbcfile.h
  11. 2
      tools/cabana/dbc/dbcmanager.h
  12. 3
      tools/cabana/detailwidget.cc
  13. 2
      tools/cabana/historylog.cc
  14. 2
      tools/cabana/mainwin.cc
  15. 5
      tools/cabana/messageswidget.cc
  16. 96
      tools/cabana/signalview.cc
  17. 8
      tools/cabana/signalview.h
  18. 2
      tools/cabana/streams/abstractstream.cc
  19. 8
      tools/cabana/streams/abstractstream.h
  20. 11
      tools/cabana/streams/replaystream.cc
  21. 2
      tools/cabana/streams/replaystream.h
  22. 20
      tools/cabana/tests/test_cabana.cc
  23. 2
      tools/cabana/tools/findsimilarbits.cc

@ -219,9 +219,12 @@ void BinaryView::refresh() {
QSet<const cabana::Signal *> BinaryView::getOverlappingSignals() const { QSet<const cabana::Signal *> BinaryView::getOverlappingSignals() const {
QSet<const cabana::Signal *> overlapping; QSet<const cabana::Signal *> overlapping;
for (auto &item : model->items) { for (const auto &item : model->items) {
if (item.sigs.size() > 1) if (item.sigs.size() > 1) {
for (auto s : item.sigs) overlapping += s; for (auto s : item.sigs) {
if (s->type == cabana::Signal::Type::Normal) overlapping += s;
}
}
} }
return overlapping; return overlapping;
} }

@ -289,14 +289,16 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
const double route_start_time = can->routeStartTime(); const double route_start_time = can->routeStartTime();
for (auto end = msgs.cend(); first != end; ++first) { for (auto end = msgs.cend(); first != end; ++first) {
const CanEvent *e = *first; const CanEvent *e = *first;
double value = get_raw_value(e->dat, e->size, *s.sig); double value = 0;
double ts = e->mono_time / 1e9 - route_start_time; // seconds if (s.sig->getValue(e->dat, e->size, &value)) {
s.vals.append({ts, value}); double ts = e->mono_time / 1e9 - route_start_time; // seconds
if (!s.step_vals.empty()) { s.vals.append({ts, value});
s.step_vals.append({ts, s.step_vals.back().y()}); if (!s.step_vals.empty()) {
s.step_vals.append({ts, s.step_vals.back().y()});
}
s.step_vals.append({ts, value});
s.last_value_mono_time = e->mono_time;
} }
s.step_vals.append({ts, value});
s.last_value_mono_time = e->mono_time;
} }
if (!can->liveStreaming()) { if (!can->liveStreaming()) {
s.segment_tree.build(s.vals); s.segment_tree.build(s.vals);

@ -22,21 +22,30 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl
if (first != last) { if (first != last) {
if (update_values) { if (update_values) {
values.clear(); values.clear();
values.reserve(std::distance(first, last)); if (values.capacity() < std::distance(first, last)) {
values.reserve(std::distance(first, last) * 2);
}
min_val = std::numeric_limits<double>::max(); min_val = std::numeric_limits<double>::max();
max_val = std::numeric_limits<double>::lowest(); max_val = std::numeric_limits<double>::lowest();
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
const CanEvent *e = *it; const CanEvent *e = *it;
double value = get_raw_value(e->dat, e->size, *sig); double value = 0;
values.emplace_back((e->mono_time - (*first)->mono_time) / 1e9, value); if (sig->getValue(e->dat, e->size, &value)) {
if (min_val > value) min_val = value; values.emplace_back((e->mono_time - (*first)->mono_time) / 1e9, value);
if (max_val < value) max_val = value; if (min_val > value) min_val = value;
if (max_val < value) max_val = value;
}
} }
if (min_val == max_val) { if (min_val == max_val) {
min_val -= 1; min_val -= 1;
max_val += 1; max_val += 1;
} }
} }
} else {
values.clear();
}
if (!values.empty()) {
render(sig->color, size); render(sig->color, size);
} else { } else {
pixmap = QPixmap(); pixmap = QPixmap();
@ -49,7 +58,7 @@ void Sparkline::render(const QColor &color, QSize size) {
const double xscale = (size.width() - 1) / (double)time_range; const double xscale = (size.width() - 1) / (double)time_range;
const double yscale = (size.height() - 3) / (max_val - min_val); const double yscale = (size.height() - 3) / (max_val - min_val);
points.clear(); points.clear();
points.reserve(values.size()); points.reserve(values.capacity());
for (auto &v : values) { for (auto &v : values) {
points.emplace_back(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale); points.emplace_back(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale);
} }

@ -10,6 +10,9 @@ class Sparkline {
public: public:
void update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size); void update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size);
const QSize size() const { return pixmap.size() / pixmap.devicePixelRatio(); } const QSize size() const { return pixmap.size() / pixmap.devicePixelRatio(); }
inline double freq() const {
return values.empty() ? 0 : values.size() / std::max(values.back().x() - values.front().x(), 1.0);
}
QPixmap pixmap; QPixmap pixmap;
double min_val = 0; double min_val = 0;

@ -62,22 +62,43 @@ void AddSigCommand::redo() { dbc()->addSignal(id, signal); }
// RemoveSigCommand // RemoveSigCommand
RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *sig, QUndoCommand *parent) RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *sig, QUndoCommand *parent)
: id(id), signal(*sig), QUndoCommand(parent) { : id(id), QUndoCommand(parent) {
setText(QObject::tr("remove signal %1 from %2:%3").arg(signal.name).arg(msgName(id)).arg(id.address)); sigs.push_back(*sig);
if (sig->type == cabana::Signal::Type::Multiplexor) {
for (const auto &s : dbc()->msg(id)->sigs) {
if (s->type == cabana::Signal::Type::Multiplexed) {
sigs.push_back(*s);
}
}
}
setText(QObject::tr("remove signal %1 from %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address));
} }
void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); } void RemoveSigCommand::undo() { for (const auto &s : sigs) dbc()->addSignal(id, s); }
void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name); } void RemoveSigCommand::redo() { for (const auto &s : sigs) dbc()->removeSignal(id, s.name); }
// EditSignalCommand // EditSignalCommand
EditSignalCommand::EditSignalCommand(const MessageId &id, const cabana::Signal *sig, const cabana::Signal &new_sig, QUndoCommand *parent) EditSignalCommand::EditSignalCommand(const MessageId &id, const cabana::Signal *sig, const cabana::Signal &new_sig, QUndoCommand *parent)
: id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) { : id(id), QUndoCommand(parent) {
setText(QObject::tr("edit signal %1 in %2:%3").arg(old_signal.name).arg(msgName(id)).arg(id.address)); sigs.push_back({*sig, new_sig});
if (sig->type == cabana::Signal::Type::Multiplexor && new_sig.type == cabana::Signal::Type::Normal) {
// convert all multiplexed signals to normal signals
auto msg = dbc()->msg(id);
assert(msg);
for (const auto &s : msg->sigs) {
if (s->type == cabana::Signal::Type::Multiplexed) {
auto new_s = *s;
new_s.type = cabana::Signal::Type::Normal;
sigs.push_back({*s, new_s});
}
}
}
setText(QObject::tr("edit signal %1 in %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address));
} }
void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name, old_signal); } void EditSignalCommand::undo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.second.name, s.first); }
void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name, new_signal); } void EditSignalCommand::redo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.first.name, s.second); }
namespace UndoStack { namespace UndoStack {

@ -48,7 +48,7 @@ public:
private: private:
const MessageId id; const MessageId id;
cabana::Signal signal = {}; QList<cabana::Signal> sigs;
}; };
class EditSignalCommand : public QUndoCommand { class EditSignalCommand : public QUndoCommand {
@ -59,8 +59,7 @@ public:
private: private:
const MessageId id; const MessageId id;
cabana::Signal old_signal = {}; QList<std::pair<cabana::Signal, cabana::Signal>> sigs; // QList<{old_sig, new_sig}>
cabana::Signal new_signal = {};
}; };
namespace UndoStack { namespace UndoStack {

@ -77,12 +77,18 @@ QString cabana::Msg::newSignalName() {
void cabana::Msg::update() { void cabana::Msg::update() {
mask = QVector<uint8_t>(size, 0x00).toList(); mask = QVector<uint8_t>(size, 0x00).toList();
multiplexor = nullptr;
// sort signals // sort signals
std::sort(sigs.begin(), sigs.end(), [](auto l, auto r) { std::sort(sigs.begin(), sigs.end(), [](auto l, auto r) {
return std::tie(l->start_bit, l->name) < std::tie(r->start_bit, r->name); return std::tie(r->type, l->multiplex_value, l->start_bit, l->name) <
std::tie(l->type, r->multiplex_value, r->start_bit, r->name);
}); });
for (auto &sig : sigs) { for (auto sig : sigs) {
if (sig->type == cabana::Signal::Type::Multiplexor) {
multiplexor = sig;
}
sig->update(); sig->update();
// update mask // update mask
@ -101,6 +107,13 @@ void cabana::Msg::update() {
i = sig->is_little_endian ? i - 1 : i + 1; i = sig->is_little_endian ? i - 1 : i + 1;
} }
} }
for (auto sig : sigs) {
sig->multiplexor = sig->type == cabana::Signal::Type::Multiplexed ? multiplexor : nullptr;
if (!sig->multiplexor) {
sig->multiplex_value = 0;
}
}
} }
// cabana::Signal // cabana::Signal
@ -131,6 +144,24 @@ QString cabana::Signal::formatValue(double value) const {
return val_str; return val_str;
} }
bool cabana::Signal::getValue(const uint8_t *data, size_t data_size, double *val) const {
if (multiplexor && get_raw_value(data, data_size, *multiplexor) != multiplex_value) {
return false;
}
*val = get_raw_value(data, data_size, *this);
return true;
}
bool cabana::Signal::operator==(const cabana::Signal &other) const {
return name == other.name && size == other.size &&
start_bit == other.start_bit &&
msb == other.msb && lsb == other.lsb &&
is_signed == other.is_signed && is_little_endian == other.is_little_endian &&
factor == other.factor && offset == other.offset &&
min == other.min && max == other.max && comment == other.comment && unit == other.unit && val_desc == other.val_desc &&
multiplex_value == other.multiplex_value && type == other.type;
}
// helper functions // helper functions
static QVector<int> BIG_ENDIAN_START_BITS = []() { static QVector<int> BIG_ENDIAN_START_BITS = []() {
@ -163,15 +194,6 @@ double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal
return val * sig.factor + sig.offset; return val * sig.factor + sig.offset;
} }
bool cabana::operator==(const cabana::Signal &l, const cabana::Signal &r) {
return l.name == r.name && l.size == r.size &&
l.start_bit == r.start_bit &&
l.msb == r.msb && l.lsb == r.lsb &&
l.is_signed == r.is_signed && l.is_little_endian == r.is_little_endian &&
l.factor == r.factor && l.offset == r.offset &&
l.min == r.min && l.max == r.max && l.comment == r.comment && l.unit == r.unit && l.val_desc == r.val_desc;
}
int bigEndianStartBitsIndex(int start_bit) { return BIG_ENDIAN_START_BITS[start_bit]; } int bigEndianStartBitsIndex(int start_bit) { return BIG_ENDIAN_START_BITS[start_bit]; }
int bigEndianBitIndex(int index) { return BIG_ENDIAN_START_BITS.indexOf(index); } int bigEndianBitIndex(int index) { return BIG_ENDIAN_START_BITS.indexOf(index); }

@ -52,8 +52,18 @@ public:
Signal() = default; Signal() = default;
Signal(const Signal &other) = default; Signal(const Signal &other) = default;
void update(); void update();
bool getValue(const uint8_t *data, size_t data_size, double *val) const;
QString formatValue(double value) const; QString formatValue(double value) const;
bool operator==(const cabana::Signal &other) const;
inline bool operator!=(const cabana::Signal &other) const { return !(*this == other); }
enum class Type {
Normal = 0,
Multiplexed,
Multiplexor
};
Type type = Type::Normal;
QString name; QString name;
int start_bit, msb, lsb, size; int start_bit, msb, lsb, size;
double factor, offset; double factor, offset;
@ -65,6 +75,10 @@ public:
ValueDescription val_desc; ValueDescription val_desc;
int precision = 0; int precision = 0;
QColor color; QColor color;
// Multiplexed
int multiplex_value = 0;
Signal *multiplexor = nullptr;
}; };
class Msg { class Msg {
@ -79,6 +93,7 @@ public:
int indexOf(const cabana::Signal *sig) const; int indexOf(const cabana::Signal *sig) const;
cabana::Signal *sig(const QString &sig_name) const; cabana::Signal *sig(const QString &sig_name) const;
QString newSignalName(); QString newSignalName();
void update();
inline const std::vector<cabana::Signal *> &getSignals() const { return sigs; } inline const std::vector<cabana::Signal *> &getSignals() const { return sigs; }
uint32_t address; uint32_t address;
@ -88,12 +103,9 @@ public:
std::vector<cabana::Signal *> sigs; std::vector<cabana::Signal *> sigs;
QList<uint8_t> mask; QList<uint8_t> mask;
void update(); cabana::Signal *multiplexor = nullptr;
}; };
bool operator==(const cabana::Signal &l, const cabana::Signal &r);
inline bool operator!=(const cabana::Signal &l, const cabana::Signal &r) { return !(l == r); }
} // namespace cabana } // namespace cabana
// Helper functions // Helper functions

@ -16,7 +16,7 @@ DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent
if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) { if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) {
filename.chop(AUTO_SAVE_EXTENSION.length()); filename.chop(AUTO_SAVE_EXTENSION.length());
} }
open(file.readAll()); parse(file.readAll());
} else { } else {
throw std::runtime_error("Failed to open file."); throw std::runtime_error("Failed to open file.");
} }
@ -24,35 +24,7 @@ DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent
DBCFile::DBCFile(const QString &name, const QString &content, QObject *parent) : QObject(parent), name_(name), filename("") { DBCFile::DBCFile(const QString &name, const QString &content, QObject *parent) : QObject(parent), name_(name), filename("") {
// Open from clipboard // Open from clipboard
open(content); parse(content);
}
void DBCFile::open(const QString &content) {
std::istringstream stream(content.toStdString());
auto dbc = const_cast<DBC *>(dbc_parse_from_stream(name_.toStdString(), stream));
msgs.clear();
for (auto &msg : dbc->msgs) {
auto &m = msgs[msg.address];
m.address = msg.address;
m.name = msg.name.c_str();
m.size = msg.size;
for (auto &s : msg.sigs) {
auto sig = m.sigs.emplace_back(new cabana::Signal);
sig->name = s.name.c_str();
sig->start_bit = s.start_bit;
sig->msb = s.msb;
sig->lsb = s.lsb;
sig->size = s.size;
sig->is_signed = s.is_signed;
sig->factor = s.factor;
sig->offset = s.offset;
sig->is_little_endian = s.is_little_endian;
}
m.update();
}
parseExtraInfo(content);
delete dbc;
} }
bool DBCFile::save() { bool DBCFile::save() {
@ -110,7 +82,7 @@ int DBCFile::signalCount() {
return std::accumulate(msgs.cbegin(), msgs.cend(), 0, [](int &n, const auto &m) { return n + m.second.sigs.size(); }); return std::accumulate(msgs.cbegin(), msgs.cend(), 0, [](int &n, const auto &m) { return n + m.second.sigs.size(); });
} }
void DBCFile::parseExtraInfo(const QString &content) { void DBCFile::parse(const QString &content) {
static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))"); static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))");
static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))");
@ -120,23 +92,31 @@ void DBCFile::parseExtraInfo(const QString &content) {
int line_num = 0; int line_num = 0;
QString line; QString line;
auto dbc_assert = [&line_num, &line, this](bool condition) { auto dbc_assert = [&line_num, &line, this](bool condition, const QString &msg = "") {
if (!condition) throw std::runtime_error(QString("[%1:%2]: %3").arg(filename).arg(line_num).arg(line).toStdString()); if (!condition) throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(msg).arg(line).toStdString());
}; };
auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * { auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * {
auto m = (cabana::Msg *)msg(address); auto m = (cabana::Msg *)msg(address);
return m ? (cabana::Signal *)m->sig(name) : nullptr; return m ? (cabana::Signal *)m->sig(name) : nullptr;
}; };
msgs.clear();
QTextStream stream((QString *)&content); QTextStream stream((QString *)&content);
uint32_t address = 0; cabana::Msg *current_msg = nullptr;
int multiplexor_cnt = 0;
while (!stream.atEnd()) { while (!stream.atEnd()) {
++line_num; ++line_num;
line = stream.readLine().trimmed(); line = stream.readLine().trimmed();
if (line.startsWith("BO_ ")) { if (line.startsWith("BO_ ")) {
multiplexor_cnt = 0;
auto match = bo_regexp.match(line); auto match = bo_regexp.match(line);
dbc_assert(match.hasMatch()); dbc_assert(match.hasMatch());
address = match.captured(1).toUInt(); auto address = match.captured(1).toUInt();
dbc_assert(msgs.count(address) == 0, QString("Duplicate message address: %1").arg(address));
current_msg = &msgs[address];
current_msg->address = address;
current_msg->name = match.captured(2);
current_msg->size = match.captured(3).toULong();
} else if (line.startsWith("SG_ ")) { } else if (line.startsWith("SG_ ")) {
int offset = 0; int offset = 0;
auto match = sg_regexp.match(line); auto match = sg_regexp.match(line);
@ -145,11 +125,40 @@ void DBCFile::parseExtraInfo(const QString &content) {
offset = 1; offset = 1;
} }
dbc_assert(match.hasMatch()); dbc_assert(match.hasMatch());
if (auto s = get_sig(address, match.captured(1))) { dbc_assert(current_msg, "No Message");
s->min = match.captured(8 + offset).toDouble(); auto name = match.captured(1);
s->max = match.captured(9 + offset).toDouble(); dbc_assert(current_msg->sig(name) == nullptr, "Duplicate signal name");
s->unit = match.captured(10 + offset); cabana::Signal s{};
if (offset == 1) {
auto indicator = match.captured(2);
if (indicator == "M") {
// Only one signal within a single message can be the multiplexer switch.
dbc_assert(++multiplexor_cnt < 2, "Multiple multiplexor");
s.type = cabana::Signal::Type::Multiplexor;
} else {
dbc_assert(multiplexor_cnt == 1, "No multiplexor");
s.type = cabana::Signal::Type::Multiplexed;
s.multiplex_value = indicator.mid(1).toInt();
}
} }
s.name = name;
s.start_bit = match.captured(offset + 2).toInt();
s.size = match.captured(offset + 3).toInt();
s.is_little_endian = match.captured(offset + 4).toInt() == 1;
s.is_signed = match.captured(offset + 5) == "-";
s.factor = match.captured(offset + 6).toDouble();
s.offset = match.captured(offset + 7).toDouble();
if (s.is_little_endian) {
s.lsb = s.start_bit;
s.msb = s.start_bit + s.size - 1;
} else {
s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1);
s.msb = s.start_bit;
}
s.min = match.captured(8 + offset).toDouble();
s.max = match.captured(9 + offset).toDouble();
s.unit = match.captured(10 + offset);
current_msg->sigs.push_back(new cabana::Signal(s));
} else if (line.startsWith("VAL_ ")) { } else if (line.startsWith("VAL_ ")) {
auto match = val_regexp.match(line); auto match = val_regexp.match(line);
dbc_assert(match.hasMatch()); dbc_assert(match.hasMatch());
@ -185,6 +194,10 @@ void DBCFile::parseExtraInfo(const QString &content) {
} }
} }
} }
for (auto &[_, m] : msgs) {
m.update();
}
} }
QString DBCFile::generateDBC() { QString DBCFile::generateDBC() {
@ -195,8 +208,15 @@ QString DBCFile::generateDBC() {
message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment); message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment);
} }
for (auto sig : m.getSignals()) { for (auto sig : m.getSignals()) {
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [%8|%9] \"%10\" XXX\n") QString multiplexer_indicator;
if (sig->type == cabana::Signal::Type::Multiplexor) {
multiplexer_indicator = "M ";
} else if (sig->type == cabana::Signal::Type::Multiplexed) {
multiplexer_indicator = QString("m%1 ").arg(sig->multiplex_value);
}
dbc_string += QString(" SG_ %1 %2: %3|%4@%5%6 (%7,%8) [%9|%10] \"%11\" XXX\n")
.arg(sig->name) .arg(sig->name)
.arg(multiplexer_indicator)
.arg(sig->start_bit) .arg(sig->start_bit)
.arg(sig->size) .arg(sig->size)
.arg(sig->is_little_endian ? '1' : '0') .arg(sig->is_little_endian ? '1' : '0')

@ -15,8 +15,6 @@ public:
DBCFile(const QString &name, const QString &content, QObject *parent=nullptr); DBCFile(const QString &name, const QString &content, QObject *parent=nullptr);
~DBCFile() {} ~DBCFile() {}
void open(const QString &content);
bool save(); bool save();
bool saveAs(const QString &new_filename); bool saveAs(const QString &new_filename);
bool autoSave(); bool autoSave();
@ -40,7 +38,7 @@ public:
QString filename; QString filename;
private: private:
void parseExtraInfo(const QString &content); void parse(const QString &content);
std::map<uint32_t, cabana::Msg> msgs; std::map<uint32_t, cabana::Msg> msgs;
QString name_; QString name_;
}; };

@ -15,7 +15,7 @@ class DBCManager : public QObject {
Q_OBJECT Q_OBJECT
public: public:
DBCManager(QObject *parent) {} DBCManager(QObject *parent) : QObject(parent) {}
~DBCManager() {} ~DBCManager() {}
bool open(const SourceSet &sources, const QString &dbc_file_name, QString *error = nullptr); bool open(const SourceSet &sources, const QString &dbc_file_name, QString *error = nullptr);
bool open(const SourceSet &sources, const QString &name, const QString &content, QString *error = nullptr); bool open(const SourceSet &sources, const QString &name, const QString &content, QString *error = nullptr);

@ -167,7 +167,8 @@ void DetailWidget::editMsg() {
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); int size = msg ? msg->size : can->lastMessage(msg_id).dat.size();
EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); EditMessageDialog dlg(msg_id, msgName(msg_id), size, this);
if (dlg.exec()) { if (dlg.exec()) {
UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(), dlg.comment_edit->toPlainText().trimmed())); UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(),
dlg.size_spin->value(), dlg.comment_edit->toPlainText().trimmed()));
} }
} }

@ -125,7 +125,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
for (; first != last && (*first)->mono_time > min_time; ++first) { for (; first != last && (*first)->mono_time > min_time; ++first) {
const CanEvent *e = *first; const CanEvent *e = *first;
for (int i = 0; i < sigs.size(); ++i) { for (int i = 0; i < sigs.size(); ++i) {
values[i] = get_raw_value(e->dat, e->size, *sigs[i]); sigs[i]->getValue(e->dat, e->size, &values[i]);
} }
if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) {
auto &m = msgs.emplace_back(); auto &m = msgs.emplace_back();

@ -482,7 +482,7 @@ void MainWindow::updateLoadSaveMenus() {
manage_dbcs_menu->clear(); manage_dbcs_menu->clear();
manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr); manage_dbcs_menu->setEnabled(dynamic_cast<DummyStream *>(can) == nullptr);
for (uint8_t source : can->sources) { for (int source : can->sources) {
if (source >= 64) continue; // Sent and blocked buses are handled implicitly if (source >= 64) continue; // Sent and blocked buses are handled implicitly
SourceSet ss = {source, uint8_t(source + 128), uint8_t(source + 192)}; SourceSet ss = {source, uint8_t(source + 128), uint8_t(source + 192)};

@ -32,6 +32,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes); auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes);
view->setItemDelegate(delegate); view->setItemDelegate(delegate);
view->setHeader(header);
view->setModel(model); view->setModel(model);
view->setHeader(header); view->setHeader(header);
view->setSortingEnabled(true); view->setSortingEnabled(true);
@ -130,7 +131,7 @@ void MessagesWidget::dbcModified() {
void MessagesWidget::selectMessage(const MessageId &msg_id) { void MessagesWidget::selectMessage(const MessageId &msg_id) {
auto it = std::find(model->msgs.cbegin(), model->msgs.cend(), msg_id); auto it = std::find(model->msgs.cbegin(), model->msgs.cend(), msg_id);
if (it != model->msgs.cend()) { if (it != model->msgs.cend()) {
view->selectionModel()->setCurrentIndex(model->index(std::distance(model->msgs.cbegin(), it), 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); view->setCurrentIndex(model->index(std::distance(model->msgs.cbegin(), it), 0));
} }
} }
@ -209,7 +210,7 @@ void MessageListModel::setFilterStrings(const QMap<int, QString> &filters) {
void MessageListModel::dbcModified() { void MessageListModel::dbcModified() {
dbc_address.clear(); dbc_address.clear();
for (const auto &[_, m] : dbc()->getMessages(0)) { for (const auto &[_, m] : dbc()->getMessages(-1)) {
dbc_address.insert(m.address); dbc_address.insert(m.address);
} }
fetchData(); fetchData();

@ -18,6 +18,12 @@
// SignalModel // SignalModel
static QString signalTypeToString(cabana::Signal::Type type) {
if (type == cabana::Signal::Type::Multiplexor) return "Multiplexor Signal";
else if (type == cabana::Signal::Type::Multiplexed) return "Multiplexed Signal";
else return "Normal Signal";
}
SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(parent) { SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(parent) {
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &SignalModel::refresh); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &SignalModel::refresh);
QObject::connect(dbc(), &DBCManager::msgUpdated, this, &SignalModel::handleMsgChanged); QObject::connect(dbc(), &DBCManager::msgUpdated, this, &SignalModel::handleMsgChanged);
@ -30,7 +36,8 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p
void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) { void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) {
Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig}; Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig};
parent_item->children.insert(pos, item); parent_item->children.insert(pos, item);
QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"}; QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info",
"Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"};
for (int i = 0; i < std::size(titles); ++i) { for (int i = 0; i < std::size(titles); ++i) {
item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}); item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)});
} }
@ -87,6 +94,9 @@ Qt::ItemFlags SignalModel::flags(const QModelIndex &index) const {
if (index.column() == 1 && item->type != Item::Sig && item->type != Item::ExtraInfo) { if (index.column() == 1 && item->type != Item::Sig && item->type != Item::ExtraInfo) {
flags |= (item->type == Item::Endian || item->type == Item::Signed) ? Qt::ItemIsUserCheckable : Qt::ItemIsEditable; flags |= (item->type == Item::Endian || item->type == Item::Signed) ? Qt::ItemIsUserCheckable : Qt::ItemIsEditable;
} }
if (item->type == Item::MultiplexValue && item->sig->type != cabana::Signal::Type::Multiplexed) {
flags &= ~Qt::ItemIsEnabled;
}
return flags; return flags;
} }
@ -124,6 +134,8 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
case Item::Sig: return item->sig_val; case Item::Sig: return item->sig_val;
case Item::Name: return item->sig->name; case Item::Name: return item->sig->name;
case Item::Size: return item->sig->size; case Item::Size: return item->sig->size;
case Item::SignalType: return signalTypeToString(item->sig->type);
case Item::MultiplexValue: return item->sig->multiplex_value;
case Item::Offset: return doubleToString(item->sig->offset); case Item::Offset: return doubleToString(item->sig->offset);
case Item::Factor: return doubleToString(item->sig->factor); case Item::Factor: return doubleToString(item->sig->factor);
case Item::Unit: return item->sig->unit; case Item::Unit: return item->sig->unit;
@ -160,6 +172,8 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r
switch (item->type) { switch (item->type) {
case Item::Name: s.name = value.toString(); break; case Item::Name: s.name = value.toString(); break;
case Item::Size: s.size = value.toInt(); break; case Item::Size: s.size = value.toInt(); break;
case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break;
case Item::MultiplexValue: s.multiplex_value = value.toInt(); break;
case Item::Endian: s.is_little_endian = value.toBool(); break; case Item::Endian: s.is_little_endian = value.toBool(); break;
case Item::Signed: s.is_signed = value.toBool(); break; case Item::Signed: s.is_signed = value.toBool(); break;
case Item::Offset: s.offset = value.toDouble(); break; case Item::Offset: s.offset = value.toDouble(); break;
@ -265,6 +279,14 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) {
void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { void SignalModel::handleSignalUpdated(const cabana::Signal *sig) {
if (int row = signalRow(sig); row != -1) { if (int row = signalRow(sig); row != -1) {
emit dataChanged(index(row, 0), index(row, 1), {Qt::DisplayRole, Qt::EditRole, Qt::CheckStateRole}); emit dataChanged(index(row, 0), index(row, 1), {Qt::DisplayRole, Qt::EditRole, Qt::CheckStateRole});
// move row when the order changes.
int to = dbc()->msg(msg_id)->indexOf(sig);
if (to != row) {
beginMoveRows({}, row, row, {}, to > row ? to + 1 : to);
root->children.move(row, to);
endMoveRows();
}
} }
} }
@ -289,13 +311,18 @@ SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(pa
QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
int width = option.widget->size().width() / 2; int width = option.widget->size().width() / 2;
if (index.column() == 0) { if (index.column() == 0) {
auto text = index.data(Qt::DisplayRole).toString(); int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8;
auto text = index.data(Qt::DisplayRole).toString();;
auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Sig && item->sig->type != cabana::Signal::Type::Normal) {
text += item->sig->type == cabana::Signal::Type::Multiplexor ? QString(" M ") : QString(" m%1 ").arg(item->sig->multiplex_value);
spacing += (option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1) * 2;
}
auto it = width_cache.find(text); auto it = width_cache.find(text);
if (it == width_cache.end()) { if (it == width_cache.end()) {
int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8; it = width_cache.insert(text, option.fontMetrics.width(text));
it = width_cache.insert(text, option.fontMetrics.width(text) + spacing);
} }
width = std::min<int>(option.widget->size().width() / 3.0, it.value()); width = std::min<int>(option.widget->size().width() / 3.0, it.value() + spacing);
} }
return {width, QApplication::fontMetrics().height()}; return {width, QApplication::fontMetrics().height()};
} }
@ -334,11 +361,24 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->drawText(icon_rect, Qt::AlignCenter, QString::number(item->row() + 1)); painter->drawText(icon_rect, Qt::AlignCenter, QString::number(item->row() + 1));
r.setLeft(icon_rect.right() + h_margin * 2); r.setLeft(icon_rect.right() + h_margin * 2);
// multiplexer indicator
if (item->sig->type != cabana::Signal::Type::Normal) {
QString indicator = item->sig->type == cabana::Signal::Type::Multiplexor ? QString(" M ") : QString(" m%1 ").arg(item->sig->multiplex_value);
QRect indicator_rect{r.x(), r.y(), option.fontMetrics.width(indicator), r.height()};
painter->setBrush(Qt::gray);
painter->setPen(Qt::NoPen);
painter->drawRoundedRect(indicator_rect, 3, 3);
painter->setPen(Qt::white);
painter->drawText(indicator_rect, Qt::AlignCenter, indicator);
r.setLeft(indicator_rect.right() + h_margin * 2);
}
// name
auto text = option.fontMetrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, r.width()); auto text = option.fontMetrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, r.width());
painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text));
painter->setFont(option.font); painter->setFont(option.font);
painter->drawText(r, option.displayAlignment, text); painter->drawText(r, option.displayAlignment, text);
} else if (index.column() == 1) { } else if (index.column() == 1 && !item->sparkline.pixmap.isNull()) {
// sparkline // sparkline
QSize sparkline_size = item->sparkline.pixmap.size() / item->sparkline.pixmap.devicePixelRatio(); QSize sparkline_size = item->sparkline.pixmap.size() / item->sparkline.pixmap.devicePixelRatio();
painter->drawPixmap(QRect(r.topLeft(), sparkline_size), item->sparkline.pixmap); painter->drawPixmap(QRect(r.topLeft(), sparkline_size), item->sparkline.pixmap);
@ -356,8 +396,15 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->drawText(rect, Qt::AlignLeft | Qt::AlignBottom, min); painter->drawText(rect, Qt::AlignLeft | Qt::AlignBottom, min);
QFontMetrics fm(minmax_font); QFontMetrics fm(minmax_font);
value_adjust = std::max(fm.width(min), fm.width(max)) + 5; value_adjust = std::max(fm.width(min), fm.width(max)) + 5;
} else if (item->sig->type == cabana::Signal::Type::Multiplexed) {
// display freq of multiplexed signal
painter->setFont(label_font);
QString freq = QString("%1 hz").arg(item->sparkline.freq(), 0, 'g', 2);
painter->drawText(rect.adjusted(5, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, freq);
QFontMetrics fm(label_font);
value_adjust = fm.width(freq) + 10;
} }
// value // signal value
painter->setFont(option.font); painter->setFont(option.font);
rect.adjust(value_adjust, 0, -button_size.width(), 0); rect.adjust(value_adjust, 0, -button_size.width(), 0);
auto text = option.fontMetrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rect.width()); auto text = option.fontMetrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rect.width());
@ -371,10 +418,11 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
auto item = (SignalModel::Item *)index.internalPointer(); auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset ||
item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) { item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::MultiplexValue ||
item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) {
QLineEdit *e = new QLineEdit(parent); QLineEdit *e = new QLineEdit(parent);
e->setFrame(false); e->setFrame(false);
e->setValidator(index.row() == 0 ? name_validator : double_validator); e->setValidator(item->type == SignalModel::Item::Name ? name_validator : double_validator);
if (item->type == SignalModel::Item::Name) { if (item->type == SignalModel::Item::Name) {
QCompleter *completer = new QCompleter(dbc()->signalNames()); QCompleter *completer = new QCompleter(dbc()->signalNames());
@ -389,6 +437,15 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie
spin->setFrame(false); spin->setFrame(false);
spin->setRange(1, 64); spin->setRange(1, 64);
return spin; return spin;
} else if (item->type == SignalModel::Item::SignalType) {
QComboBox *c = new QComboBox(parent);
c->addItem(signalTypeToString(cabana::Signal::Type::Normal), (int)cabana::Signal::Type::Normal);
if (!dbc()->msg(((SignalModel *)index.model())->msg_id)->multiplexor) {
c->addItem(signalTypeToString(cabana::Signal::Type::Multiplexor), (int)cabana::Signal::Type::Multiplexor);
} else if (item->sig->type != cabana::Signal::Type::Multiplexor) {
c->addItem(signalTypeToString(cabana::Signal::Type::Multiplexed), (int)cabana::Signal::Type::Multiplexed);
}
return c;
} else if (item->type == SignalModel::Item::Desc) { } else if (item->type == SignalModel::Item::Desc) {
ValueDescriptionDlg dlg(item->sig->val_desc, parent); ValueDescriptionDlg dlg(item->sig->val_desc, parent);
dlg.setWindowTitle(item->sig->name); dlg.setWindowTitle(item->sig->name);
@ -400,6 +457,15 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie
return QStyledItemDelegate::createEditor(parent, option, index); return QStyledItemDelegate::createEditor(parent, option, index);
} }
void SignalItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::SignalType) {
model->setData(index, ((QComboBox*)editor)->currentData().toInt());
return;
}
QStyledItemDelegate::setModelData(editor, model, index);
}
// SignalView // SignalView
SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QFrame(parent) { SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QFrame(parent) {
@ -438,6 +504,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
tree->setHeaderHidden(true); tree->setHeaderHidden(true);
tree->setMouseTracking(true); tree->setMouseTracking(true);
tree->setExpandsOnDoubleClick(false); tree->setExpandsOnDoubleClick(false);
tree->setEditTriggers(QAbstractItemView::AllEditTriggers);
tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
tree->header()->setStretchLastSection(true); tree->header()->setStretchLastSection(true);
tree->setMinimumHeight(300); tree->setMinimumHeight(300);
@ -579,8 +646,10 @@ void SignalView::updateState(const QHash<MessageId, CanData> *msgs) {
if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id)) || last_msg.dat.size() == 0) return; if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id)) || last_msg.dat.size() == 0) return;
for (auto item : model->root->children) { for (auto item : model->root->children) {
double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig); double value = 0;
item->sig_val = item->sig->formatValue(value); if (item->sig->getValue((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), &value)) {
item->sig_val = item->sig->formatValue(value);
}
max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val)); max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val));
} }
@ -620,11 +689,6 @@ void SignalView::resizeEvent(QResizeEvent* event) {
QFrame::resizeEvent(event); QFrame::resizeEvent(event);
} }
void SignalView::leaveEvent(QEvent *event) {
emit highlight(nullptr);
QWidget::leaveEvent(event);
}
// ValueDescriptionDlg // ValueDescriptionDlg
ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent) : QDialog(parent) { ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent) : QDialog(parent) {

@ -15,7 +15,7 @@ class SignalModel : public QAbstractItemModel {
Q_OBJECT Q_OBJECT
public: public:
struct Item { struct Item {
enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, ExtraInfo, Unit, Comment, Min, Max, Desc }; enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc };
~Item() { qDeleteAll(children); } ~Item() { qDeleteAll(children); }
inline int row() { return parent->children.indexOf(this); } inline int row() { return parent->children.indexOf(this); }
@ -86,6 +86,7 @@ public:
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
QValidator *name_validator, *double_validator; QValidator *name_validator, *double_validator;
QFont label_font, minmax_font; QFont label_font, minmax_font;
@ -112,7 +113,6 @@ signals:
private: private:
void rowsChanged(); void rowsChanged();
void leaveEvent(QEvent *event) override;
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
void updateToolBar(); void updateToolBar();
void setSparklineRange(int value); void setSparklineRange(int value);
@ -130,6 +130,10 @@ private:
// Bypass the slow call to QTreeView::dataChanged. // Bypass the slow call to QTreeView::dataChanged.
QAbstractItemView::dataChanged(topLeft, bottomRight, roles); QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
} }
void leaveEvent(QEvent *event) override {
emit ((SignalView *)parentWidget())->highlight(nullptr);
QTreeView::leaveEvent(event);
}
}; };
int max_value_width = 0; int max_value_width = 0;
TreeView *tree; TreeView *tree;

@ -180,7 +180,7 @@ void CanData::compute(const char *can_data, const int size, double current_sec,
dat.resize(size); dat.resize(size);
bit_change_counts.resize(size); bit_change_counts.resize(size);
colors = QVector(size, QColor(0, 0, 0, 0)); colors = QVector(size, QColor(0, 0, 0, 0));
last_change_t = QVector(size, ts); last_change_t.assign(size, ts);
last_delta.resize(size); last_delta.resize(size);
same_delta_counter.resize(size); same_delta_counter.resize(size);
} else { } else {

@ -20,10 +20,10 @@ struct CanData {
double freq = 0; double freq = 0;
QByteArray dat; QByteArray dat;
QVector<QColor> colors; QVector<QColor> colors;
QVector<double> last_change_t; std::vector<double> last_change_t;
QVector<std::array<uint32_t, 8>> bit_change_counts; std::vector<std::array<uint32_t, 8>> bit_change_counts;
QVector<int> last_delta; std::vector<int> last_delta;
QVector<int> same_delta_counter; std::vector<int> same_delta_counter;
}; };
struct CanEvent { struct CanEvent {

@ -55,11 +55,12 @@ bool ReplayStream::eventFilter(const Event *event) {
const auto dat = c.getDat(); const auto dat = c.getDat();
updateEvent(id, current_sec, (const uint8_t*)dat.begin(), dat.size()); updateEvent(id, current_sec, (const uint8_t*)dat.begin(), dat.size());
} }
double ts = millis_since_boot(); }
if ((ts - prev_update_ts) > (1000.0 / settings.fps)) {
if (postEvents()) { double ts = millis_since_boot();
prev_update_ts = ts; if ((ts - prev_update_ts) > (1000.0 / settings.fps)) {
} if (postEvents()) {
prev_update_ts = ts;
} }
} }
return true; return true;

@ -11,7 +11,7 @@ public:
void start() override; void start() override;
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }; void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
inline QString routeName() const override { return replay->route()->name(); } inline QString routeName() const override { return replay->route()->name(); }
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
double totalSeconds() const override { return replay->totalSeconds(); } double totalSeconds() const override { return replay->totalSeconds(); }

@ -10,13 +10,13 @@
const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2"; const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2";
TEST_CASE("DBCFile::generateDBC") { TEST_CASE("DBCFile::generateDBC") {
QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "toyota_new_mc_pt_generated"); QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can");
DBCFile dbc_origin(fn); DBCFile dbc_origin(fn);
DBCFile dbc_from_generated("", dbc_origin.generateDBC()); DBCFile dbc_from_generated("", dbc_origin.generateDBC());
REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount()); REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount());
auto msgs = dbc_origin.getMessages(); auto &msgs = dbc_origin.getMessages();
auto new_msgs = dbc_from_generated.getMessages(); auto &new_msgs = dbc_from_generated.getMessages();
for (auto &[id, m] : msgs) { for (auto &[id, m] : msgs) {
auto &new_m = new_msgs.at(id); auto &new_m = new_msgs.at(id);
REQUIRE(m.name == new_m.name); REQUIRE(m.name == new_m.name);
@ -76,6 +76,10 @@ BO_ 160 message_1: 8 XXX
SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX
SG_ signal_2 : 12|1@1+ (1.0,0.0) [0.0|1] "" XXX SG_ signal_2 : 12|1@1+ (1.0,0.0) [0.0|1] "" XXX
BO_ 162 message_1: 8 XXX
SG_ signal_1 M : 0|12@1+ (1,0) [0|4095] "unit" XXX
SG_ signal_2 M4 : 12|1@1+ (1.0,0.0) [0.0|1] "" XXX
VAL_ 160 signal_1 0 "disabled" 1.2 "initializing" 2 "fault"; VAL_ 160 signal_1 0 "disabled" 1.2 "initializing" 2 "fault";
CM_ BO_ 160 "message comment" ; CM_ BO_ 160 "message comment" ;
@ -109,4 +113,14 @@ CM_ SG_ 160 signal_2 "multiple line comment
auto &sig_2 = msg->sigs[1]; auto &sig_2 = msg->sigs[1];
REQUIRE(sig_2->comment == "multiple line comment\n1\n2"); REQUIRE(sig_2->comment == "multiple line comment\n1\n2");
// multiplexed signals
msg = file.msg(162);
REQUIRE(msg != nullptr);
REQUIRE(msg->sigs.size() == 2);
REQUIRE(msg->sigs[0]->type == cabana::Signal::Type::Multiplexor);
REQUIRE(msg->sigs[1]->type == cabana::Signal::Type::Multiplexed);
REQUIRE(msg->sigs[1]->multiplex_value == 4);
REQUIRE(msg->sigs[1]->start_bit == 12);
REQUIRE(msg->sigs[1]->size == 1);
} }

@ -28,7 +28,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
msg_cb = new QComboBox(this); msg_cb = new QComboBox(this);
// TODO: update when src_bus_combo changes // TODO: update when src_bus_combo changes
for (auto &[address, msg] : dbc()->getMessages(0)) { for (auto &[address, msg] : dbc()->getMessages(-1)) {
msg_cb->addItem(msg.name, address); msg_cb->addItem(msg.name, address);
} }
msg_cb->model()->sort(0); msg_cb->model()->sort(0);

Loading…
Cancel
Save