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
This commit is contained in:
Dean Lee 2023-06-14 04:22:03 +08:00 committed by GitHub
parent 7128daebba
commit e08569b0f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 298 additions and 124 deletions

View File

@ -219,9 +219,12 @@ void BinaryView::refresh() {
QSet<const cabana::Signal *> BinaryView::getOverlappingSignals() const {
QSet<const cabana::Signal *> 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;
}

View File

@ -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);

View File

@ -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<double>::max();
max_val = std::numeric_limits<double>::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);
}

View File

@ -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;

View File

@ -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 {

View File

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

View File

@ -77,12 +77,18 @@ QString cabana::Msg::newSignalName() {
void cabana::Msg::update() {
mask = QVector<uint8_t>(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<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;
}
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); }

View File

@ -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<cabana::Signal *> &getSignals() const { return sigs; }
uint32_t address;
@ -88,12 +103,9 @@ public:
std::vector<cabana::Signal *> sigs;
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
// Helper functions

View File

@ -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 *>(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')

View File

@ -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<uint32_t, cabana::Msg> msgs;
QString name_;
};

View File

@ -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);

View File

@ -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()));
}
}

View File

@ -125,7 +125,7 @@ std::deque<HistoryLogModel::Message> 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();

View File

@ -482,7 +482,7 @@ void MainWindow::updateLoadSaveMenus() {
manage_dbcs_menu->clear();
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
SourceSet ss = {source, uint8_t(source + 128), uint8_t(source + 192)};

View File

@ -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<int, QString> &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();

View File

@ -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<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()};
}
@ -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<MessageId, CanData> *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) {

View File

@ -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;

View File

@ -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 {

View File

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

View File

@ -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;

View File

@ -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(); }

View File

@ -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);
}

View File

@ -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);