diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index ed93a07e40..cc5259136a 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -219,9 +219,12 @@ void BinaryView::refresh() { QSet BinaryView::getOverlappingSignals() const { QSet overlapping; - for (auto &item : model->items) { - if (item.sigs.size() > 1) - for (auto s : item.sigs) overlapping += s; + for (const auto &item : model->items) { + if (item.sigs.size() > 1) { + for (auto s : item.sigs) { + if (s->type == cabana::Signal::Type::Normal) overlapping += s; + } + } } return overlapping; } diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 622b79d806..da38054cb0 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -289,14 +289,16 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) { const double route_start_time = can->routeStartTime(); for (auto end = msgs.cend(); first != end; ++first) { const CanEvent *e = *first; - double value = get_raw_value(e->dat, e->size, *s.sig); - double ts = e->mono_time / 1e9 - route_start_time; // seconds - s.vals.append({ts, value}); - if (!s.step_vals.empty()) { - s.step_vals.append({ts, s.step_vals.back().y()}); + double value = 0; + if (s.sig->getValue(e->dat, e->size, &value)) { + double ts = e->mono_time / 1e9 - route_start_time; // seconds + s.vals.append({ts, value}); + 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()) { s.segment_tree.build(s.vals); diff --git a/tools/cabana/chart/sparkline.cc b/tools/cabana/chart/sparkline.cc index bac867bd20..baedee8bb2 100644 --- a/tools/cabana/chart/sparkline.cc +++ b/tools/cabana/chart/sparkline.cc @@ -22,21 +22,30 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl if (first != last) { if (update_values) { 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::max(); max_val = std::numeric_limits::lowest(); for (auto it = first; it != last; ++it) { const CanEvent *e = *it; - double value = get_raw_value(e->dat, e->size, *sig); - values.emplace_back((e->mono_time - (*first)->mono_time) / 1e9, value); - if (min_val > value) min_val = value; - if (max_val < value) max_val = value; + double value = 0; + if (sig->getValue(e->dat, e->size, &value)) { + values.emplace_back((e->mono_time - (*first)->mono_time) / 1e9, value); + if (min_val > value) min_val = value; + if (max_val < value) max_val = value; + } } if (min_val == max_val) { min_val -= 1; max_val += 1; } } + } else { + values.clear(); + } + + if (!values.empty()) { render(sig->color, size); } else { 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 yscale = (size.height() - 3) / (max_val - min_val); points.clear(); - points.reserve(values.size()); + points.reserve(values.capacity()); for (auto &v : values) { points.emplace_back(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale); } diff --git a/tools/cabana/chart/sparkline.h b/tools/cabana/chart/sparkline.h index 69d4f4bc55..2061966ebb 100644 --- a/tools/cabana/chart/sparkline.h +++ b/tools/cabana/chart/sparkline.h @@ -10,6 +10,9 @@ class Sparkline { public: 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(); } + inline double freq() const { + return values.empty() ? 0 : values.size() / std::max(values.back().x() - values.front().x(), 1.0); + } QPixmap pixmap; double min_val = 0; diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index d64e173338..1ddec0ea93 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -62,22 +62,43 @@ void AddSigCommand::redo() { dbc()->addSignal(id, signal); } // RemoveSigCommand RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *sig, QUndoCommand *parent) - : id(id), signal(*sig), QUndoCommand(parent) { - setText(QObject::tr("remove signal %1 from %2:%3").arg(signal.name).arg(msgName(id)).arg(id.address)); + : id(id), QUndoCommand(parent) { + 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::redo() { dbc()->removeSignal(id, signal.name); } +void RemoveSigCommand::undo() { for (const auto &s : sigs) dbc()->addSignal(id, s); } +void RemoveSigCommand::redo() { for (const auto &s : sigs) dbc()->removeSignal(id, s.name); } // EditSignalCommand 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) { - setText(QObject::tr("edit signal %1 in %2:%3").arg(old_signal.name).arg(msgName(id)).arg(id.address)); + : id(id), QUndoCommand(parent) { + 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::redo() { dbc()->updateSignal(id, old_signal.name, new_signal); } +void EditSignalCommand::undo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.second.name, s.first); } +void EditSignalCommand::redo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.first.name, s.second); } namespace UndoStack { diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index 2552115229..4e2e2eac4b 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -48,7 +48,7 @@ public: private: const MessageId id; - cabana::Signal signal = {}; + QList sigs; }; class EditSignalCommand : public QUndoCommand { @@ -59,8 +59,7 @@ public: private: const MessageId id; - cabana::Signal old_signal = {}; - cabana::Signal new_signal = {}; + QList> sigs; // QList<{old_sig, new_sig}> }; namespace UndoStack { diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index 1ac99ad464..269aeb4247 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -77,12 +77,18 @@ QString cabana::Msg::newSignalName() { void cabana::Msg::update() { mask = QVector(size, 0x00).toList(); + multiplexor = nullptr; + // sort signals 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(); // update mask @@ -101,6 +107,13 @@ void cabana::Msg::update() { 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 @@ -131,6 +144,24 @@ QString cabana::Signal::formatValue(double value) const { 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 static QVector 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; } -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 bigEndianBitIndex(int index) { return BIG_ENDIAN_START_BITS.indexOf(index); } diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index 56a3ea9a65..d7439620a7 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -52,8 +52,18 @@ public: Signal() = default; Signal(const Signal &other) = default; void update(); + bool getValue(const uint8_t *data, size_t data_size, double *val) 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; int start_bit, msb, lsb, size; double factor, offset; @@ -65,6 +75,10 @@ public: ValueDescription val_desc; int precision = 0; QColor color; + + // Multiplexed + int multiplex_value = 0; + Signal *multiplexor = nullptr; }; class Msg { @@ -79,6 +93,7 @@ public: int indexOf(const cabana::Signal *sig) const; cabana::Signal *sig(const QString &sig_name) const; QString newSignalName(); + void update(); inline const std::vector &getSignals() const { return sigs; } uint32_t address; @@ -88,12 +103,9 @@ public: std::vector sigs; QList 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 // Helper functions diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 6de00fe77d..c7df7553ef 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -16,7 +16,7 @@ DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) { filename.chop(AUTO_SAVE_EXTENSION.length()); } - open(file.readAll()); + parse(file.readAll()); } else { 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("") { // Open from clipboard - open(content); -} - -void DBCFile::open(const QString &content) { - std::istringstream stream(content.toStdString()); - auto dbc = const_cast(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; + parse(content); } 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(); }); } -void DBCFile::parseExtraInfo(const QString &content) { +void DBCFile::parse(const QString &content) { 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 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; QString line; - auto dbc_assert = [&line_num, &line, this](bool condition) { - if (!condition) throw std::runtime_error(QString("[%1:%2]: %3").arg(filename).arg(line_num).arg(line).toStdString()); + auto dbc_assert = [&line_num, &line, this](bool condition, const QString &msg = "") { + 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 m = (cabana::Msg *)msg(address); return m ? (cabana::Signal *)m->sig(name) : nullptr; }; + msgs.clear(); QTextStream stream((QString *)&content); - uint32_t address = 0; + cabana::Msg *current_msg = nullptr; + int multiplexor_cnt = 0; while (!stream.atEnd()) { ++line_num; line = stream.readLine().trimmed(); if (line.startsWith("BO_ ")) { + multiplexor_cnt = 0; auto match = bo_regexp.match(line); 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_ ")) { int offset = 0; auto match = sg_regexp.match(line); @@ -145,11 +125,40 @@ void DBCFile::parseExtraInfo(const QString &content) { offset = 1; } dbc_assert(match.hasMatch()); - if (auto s = get_sig(address, match.captured(1))) { - s->min = match.captured(8 + offset).toDouble(); - s->max = match.captured(9 + offset).toDouble(); - s->unit = match.captured(10 + offset); + dbc_assert(current_msg, "No Message"); + auto name = match.captured(1); + dbc_assert(current_msg->sig(name) == nullptr, "Duplicate signal name"); + 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_ ")) { auto match = val_regexp.match(line); dbc_assert(match.hasMatch()); @@ -185,6 +194,10 @@ void DBCFile::parseExtraInfo(const QString &content) { } } } + + for (auto &[_, m] : msgs) { + m.update(); + } } QString DBCFile::generateDBC() { @@ -195,8 +208,15 @@ QString DBCFile::generateDBC() { message_comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(m.comment); } 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(multiplexer_indicator) .arg(sig->start_bit) .arg(sig->size) .arg(sig->is_little_endian ? '1' : '0') diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index 76e71e189e..78a73d58e4 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -15,8 +15,6 @@ public: DBCFile(const QString &name, const QString &content, QObject *parent=nullptr); ~DBCFile() {} - void open(const QString &content); - bool save(); bool saveAs(const QString &new_filename); bool autoSave(); @@ -40,7 +38,7 @@ public: QString filename; private: - void parseExtraInfo(const QString &content); + void parse(const QString &content); std::map msgs; QString name_; }; diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 9af4e0c1cc..659253b82a 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -15,7 +15,7 @@ class DBCManager : public QObject { Q_OBJECT public: - DBCManager(QObject *parent) {} + DBCManager(QObject *parent) : QObject(parent) {} ~DBCManager() {} 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); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 92851761f9..47c5e4a62e 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -167,7 +167,8 @@ void DetailWidget::editMsg() { int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); 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())); } } diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index eb8bada69e..f61a89b73c 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -125,7 +125,7 @@ std::deque HistoryLogModel::fetchData(InputIt first, I for (; first != last && (*first)->mono_time > min_time; ++first) { const CanEvent *e = *first; 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)) { auto &m = msgs.emplace_back(); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index f424a3eecf..2961d888ff 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -482,7 +482,7 @@ void MainWindow::updateLoadSaveMenus() { manage_dbcs_menu->clear(); manage_dbcs_menu->setEnabled(dynamic_cast(can) == nullptr); - for (uint8_t source : can->sources) { + for (int source : can->sources) { if (source >= 64) continue; // Sent and blocked buses are handled implicitly SourceSet ss = {source, uint8_t(source + 128), uint8_t(source + 192)}; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 2e2087dece..5982689bda 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -32,6 +32,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes); view->setItemDelegate(delegate); + view->setHeader(header); view->setModel(model); view->setHeader(header); view->setSortingEnabled(true); @@ -130,7 +131,7 @@ void MessagesWidget::dbcModified() { void MessagesWidget::selectMessage(const MessageId &msg_id) { auto it = std::find(model->msgs.cbegin(), model->msgs.cend(), msg_id); 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 &filters) { void MessageListModel::dbcModified() { dbc_address.clear(); - for (const auto &[_, m] : dbc()->getMessages(0)) { + for (const auto &[_, m] : dbc()->getMessages(-1)) { dbc_address.insert(m.address); } fetchData(); diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 71186bbdf1..a557aa57fb 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -18,6 +18,12 @@ // 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) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &SignalModel::refresh); 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) { Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig}; 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) { 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) { 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; } @@ -124,6 +134,8 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { case Item::Sig: return item->sig_val; case Item::Name: return item->sig->name; 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::Factor: return doubleToString(item->sig->factor); 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) { case Item::Name: s.name = value.toString(); 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::Signed: s.is_signed = value.toBool(); 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) { if (int row = signalRow(sig); row != -1) { 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 { int width = option.widget->size().width() / 2; 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); 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) + spacing); + it = width_cache.insert(text, option.fontMetrics.width(text)); } - width = std::min(option.widget->size().width() / 3.0, it.value()); + width = std::min(option.widget->size().width() / 3.0, it.value() + spacing); } 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)); 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()); painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); painter->setFont(option.font); painter->drawText(r, option.displayAlignment, text); - } else if (index.column() == 1) { + } else if (index.column() == 1 && !item->sparkline.pixmap.isNull()) { // sparkline QSize sparkline_size = item->sparkline.pixmap.size() / item->sparkline.pixmap.devicePixelRatio(); 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); QFontMetrics fm(minmax_font); 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); rect.adjust(value_adjust, 0, -button_size.width(), 0); 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 { auto item = (SignalModel::Item *)index.internalPointer(); 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); 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) { QCompleter *completer = new QCompleter(dbc()->signalNames()); @@ -389,6 +437,15 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie spin->setFrame(false); spin->setRange(1, 64); 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) { ValueDescriptionDlg dlg(item->sig->val_desc, parent); dlg.setWindowTitle(item->sig->name); @@ -400,6 +457,15 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie 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(ChartsWidget *charts, QWidget *parent) : charts(charts), QFrame(parent) { @@ -438,6 +504,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), tree->setHeaderHidden(true); tree->setMouseTracking(true); tree->setExpandsOnDoubleClick(false); + tree->setEditTriggers(QAbstractItemView::AllEditTriggers); tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); tree->header()->setStretchLastSection(true); tree->setMinimumHeight(300); @@ -579,8 +646,10 @@ void SignalView::updateState(const QHash *msgs) { if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id)) || last_msg.dat.size() == 0) return; for (auto item : model->root->children) { - double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig); - item->sig_val = item->sig->formatValue(value); + double value = 0; + 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)); } @@ -620,11 +689,6 @@ void SignalView::resizeEvent(QResizeEvent* event) { QFrame::resizeEvent(event); } -void SignalView::leaveEvent(QEvent *event) { - emit highlight(nullptr); - QWidget::leaveEvent(event); -} - // ValueDescriptionDlg ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent) : QDialog(parent) { diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index f5924c1f5d..eb268e517c 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -15,7 +15,7 @@ class SignalModel : public QAbstractItemModel { Q_OBJECT public: 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); } inline int row() { return parent->children.indexOf(this); } @@ -86,6 +86,7 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; 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 setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; QValidator *name_validator, *double_validator; QFont label_font, minmax_font; @@ -112,7 +113,6 @@ signals: private: void rowsChanged(); - void leaveEvent(QEvent *event) override; void resizeEvent(QResizeEvent* event) override; void updateToolBar(); void setSparklineRange(int value); @@ -130,6 +130,10 @@ private: // Bypass the slow call to QTreeView::dataChanged. QAbstractItemView::dataChanged(topLeft, bottomRight, roles); } + void leaveEvent(QEvent *event) override { + emit ((SignalView *)parentWidget())->highlight(nullptr); + QTreeView::leaveEvent(event); + } }; int max_value_width = 0; TreeView *tree; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index dc23421a8c..059215b364 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -180,7 +180,7 @@ void CanData::compute(const char *can_data, const int size, double current_sec, dat.resize(size); bit_change_counts.resize(size); colors = QVector(size, QColor(0, 0, 0, 0)); - last_change_t = QVector(size, ts); + last_change_t.assign(size, ts); last_delta.resize(size); same_delta_counter.resize(size); } else { diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index cdb939b948..78610fc657 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -20,10 +20,10 @@ struct CanData { double freq = 0; QByteArray dat; QVector colors; - QVector last_change_t; - QVector> bit_change_counts; - QVector last_delta; - QVector same_delta_counter; + std::vector last_change_t; + std::vector> bit_change_counts; + std::vector last_delta; + std::vector same_delta_counter; }; struct CanEvent { diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index c7c79c8388..2e580c0f0d 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -55,11 +55,12 @@ bool ReplayStream::eventFilter(const Event *event) { const auto dat = c.getDat(); 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()) { - prev_update_ts = ts; - } + } + + double ts = millis_since_boot(); + if ((ts - prev_update_ts) > (1000.0 / settings.fps)) { + if (postEvents()) { + prev_update_ts = ts; } } return true; diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index a5705a14a6..5087de0329 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -11,7 +11,7 @@ public: void start() override; bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); 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 carFingerprint() const override { return replay->carFingerprint().c_str(); } double totalSeconds() const override { return replay->totalSeconds(); } diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index 01b8dd7081..8b2a81e4b1 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -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"; 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_from_generated("", dbc_origin.generateDBC()); REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount()); - auto msgs = dbc_origin.getMessages(); - auto new_msgs = dbc_from_generated.getMessages(); + auto &msgs = dbc_origin.getMessages(); + auto &new_msgs = dbc_from_generated.getMessages(); for (auto &[id, m] : msgs) { auto &new_m = new_msgs.at(id); 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_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"; CM_ BO_ 160 "message comment" ; @@ -109,4 +113,14 @@ CM_ SG_ 160 signal_2 "multiple line comment auto &sig_2 = msg->sigs[1]; 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); } diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 5eb4d7fb86..72429104a5 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -28,7 +28,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi msg_cb = new QComboBox(this); // 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->model()->sort(0);