cabana: de-Qt, part 1 (#38357)

* cabana: de-Qt, part 1

* lil more
This commit is contained in:
Adeeb Shihadeh
2026-07-16 13:45:57 -07:00
committed by GitHub
parent 60716edc37
commit 06a73f538e
41 changed files with 723 additions and 471 deletions
+1
View File
@@ -7,3 +7,4 @@ assets.cc
_cabana
dbc/car_fingerprint_to_dbc.json
tests/test_cabana
tests/test_dbc_core
+14 -1
View File
@@ -97,7 +97,7 @@ cabana_env.Depends(assets_src, str(bootstrap_icons.SVG_PATH))
cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"]))
cabana_srcs = ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'dbc/dbcqt.cc',
'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc',
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc',
@@ -110,6 +110,19 @@ cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_lib
if GetOption('extras'):
cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs])
# This target deliberately uses the base environment and links no Qt libraries.
# It prevents Qt dependencies from creeping back into the DBC core.
dbc_core_test_env = env.Clone()
dbc_core_test_env['CXXFLAGS'] += [opendbc_path]
dbc_core_test_objects = [
dbc_core_test_env.Object('tests/dbc_core_test_runner', 'tests/test_runner.cc'),
dbc_core_test_env.Object('tests/dbc_core_tests', 'tests/test_cabana.cc'),
dbc_core_test_env.Object('tests/dbc_core_model', 'dbc/dbc.cc'),
dbc_core_test_env.Object('tests/dbc_core_file', 'dbc/dbcfile.cc'),
dbc_core_test_env.Object('tests/dbc_core_manager', 'dbc/dbcmanager.cc'),
]
dbc_core_test_env.Program('tests/test_dbc_core', dbc_core_test_objects)
output_json_file = 'openpilot/tools/cabana/dbc/car_fingerprint_to_dbc.json'
generate_dbc = cabana_env.Command('#' + output_json_file,
['dbc/generate_dbc_json.py'],
+7 -6
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/binaryview.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
@@ -34,7 +35,7 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
setMouseTracking(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &BinaryView::refresh);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &BinaryView::refresh);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &BinaryView::refresh);
addShortcuts();
@@ -334,7 +335,7 @@ void BinaryViewModel::updateState() {
color.setAlpha(alpha);
updateItem(i, j, bit_val, color);
}
updateItem(i, 8, binary[i], last_msg.colors[i]);
updateItem(i, 8, binary[i], toQColor(last_msg.colors[i]));
}
}
@@ -421,14 +422,14 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->fillRect(option.rect, item->bg_color);
}
} else if (option.state & QStyle::State_Selected) {
auto color = bin_view->resize_sig ? bin_view->resize_sig->color : option.palette.color(QPalette::Active, QPalette::Highlight);
auto color = bin_view->resize_sig ? toQColor(bin_view->resize_sig->color) : option.palette.color(QPalette::Active, QPalette::Highlight);
painter->fillRect(option.rect, color);
painter->setPen(option.palette.color(QPalette::BrightText));
} else if (!bin_view->selectionModel()->hasSelection() || std::find(item->sigs.begin(), item->sigs.end(), bin_view->resize_sig) == item->sigs.end()) { // not resizing
if (item->sigs.size() > 0) {
for (auto &s : item->sigs) {
if (s == bin_view->hovered_sig) {
painter->fillRect(option.rect, s->color.darker(125)); // 4/5x brightness
painter->fillRect(option.rect, toQColor(s->color.darker(125))); // 4/5x brightness
} else {
drawSignalCell(painter, option, index, s);
}
@@ -483,14 +484,14 @@ void BinaryItemDelegate::drawSignalCell(QPainter *painter, const QStyleOptionVie
painter->setClipRegion(QRegion(rc).subtracted(subtract));
auto item = (const BinaryViewModel::Item *)index.internalPointer();
QColor color = sig->color;
QColor color = toQColor(sig->color);
color.setAlpha(item->bg_color.alpha());
// Mixing the signal color with the Base background color to fade it
painter->fillRect(rc, option.palette.color(QPalette::Base));
painter->fillRect(rc, color);
// Draw edges
color = sig->color.darker(125);
color = toQColor(sig->color.darker(125));
painter->setPen(QPen(color, 1));
if (draw_left) painter->drawLine(rc.topLeft(), rc.bottomLeft());
if (draw_right) painter->drawLine(rc.topRight(), rc.bottomRight());
+10 -9
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/chart/chart.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <limits>
@@ -52,10 +53,10 @@ ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *par
QObject::connect(axis_y, &QAbstractAxis::titleTextChanged, this, &ChartView::resetChartCache);
QObject::connect(window()->windowHandle(), &QWindow::screenChanged, this, &ChartView::resetChartCache);
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated);
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved);
QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalRemoved, this, &ChartView::signalRemoved);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalUpdated, this, &ChartView::signalUpdated);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgRemoved, this, &ChartView::msgRemoved);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgUpdated, this, &ChartView::msgUpdated);
}
void ChartView::createToolButtons() {
@@ -115,14 +116,14 @@ void ChartView::setTheme(QChart::ChartTheme theme) {
axis_x->setLineVisible(false);
axis_y->setLineVisible(false);
for (auto &s : sigs) {
s.series->setColor(s.sig->color);
s.series->setColor(toQColor(s.sig->color));
}
}
void ChartView::addSignal(const MessageId &msg_id, const cabana::Signal *sig) {
if (hasSignal(msg_id, sig)) return;
QXYSeries *series = createSeries(series_type, sig->color);
QXYSeries *series = createSeries(series_type, toQColor(sig->color));
sigs.push_back({.msg_id = msg_id, .sig = sig, .series = series});
updateSeries(sig);
updateSeriesPoints();
@@ -157,8 +158,8 @@ void ChartView::removeIf(std::function<bool(const SigItem &s)> predicate) {
void ChartView::signalUpdated(const cabana::Signal *sig) {
auto it = std::find_if(sigs.begin(), sigs.end(), [sig](auto &s) { return s.sig == sig; });
if (it != sigs.end()) {
if (it->series->color() != sig->color) {
setSeriesColor(it->series, sig->color);
if (it->series->color() != toQColor(sig->color)) {
setSeriesColor(it->series, toQColor(sig->color));
}
updateTitle();
updateSeries(sig);
@@ -838,7 +839,7 @@ void ChartView::setSeriesType(SeriesType type) {
s.series->deleteLater();
}
for (auto &s : sigs) {
s.series = createSeries(series_type, s.sig->color);
s.series = createSeries(series_type, toQColor(s.sig->color));
const auto &points = series_type == SeriesType::StepLine ? s.step_vals : s.vals;
s.series->replace(QVector<QPointF>(points.cbegin(), points.cend()));
}
+3 -2
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/chart/chartswidget.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <future>
@@ -115,7 +116,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) {
align_timer->setSingleShot(true);
QObject::connect(align_timer, &QTimer::timeout, this, &ChartsWidget::alignCharts);
QObject::connect(auto_scroll_timer, &QTimer::timeout, this, &ChartsWidget::doAutoScroll);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged);
QObject::connect(can, &AbstractStream::msgsReceived, this, &ChartsWidget::updateState);
QObject::connect(can, &AbstractStream::seeking, this, &ChartsWidget::updateState);
@@ -309,7 +310,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
src_chart->chart()->removeSeries(it->series);
// Restore to the original color
it->series->setColor(it->sig->color);
it->series->setColor(toQColor(it->sig->color));
c->addSeries(it->series);
c->sigs.emplace_back(std::move(*it));
@@ -1,4 +1,5 @@
#include "tools/cabana/chart/signalselector.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <QCompleter>
#include <QDialogButtonBox>
@@ -92,7 +93,7 @@ void SignalSelector::updateAvailableList(int index) {
}
void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, const cabana::Signal *sig, bool show_msg_name) {
QString text = QString("<span style=\"color:%0;\">■ </span> %1").arg(sig->color.name(), QString::fromStdString(sig->name));
QString text = QString("<span style=\"color:%0;\">■ </span> %1").arg(toQColor(sig->color).name(), QString::fromStdString(sig->name));
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(QString::fromStdString(msgName(id)), QString::fromStdString(id.toString()));
QLabel *label = new QLabel(text);
+1 -1
View File
@@ -31,7 +31,7 @@ void Sparkline::update(const cabana::Signal *sig, CanEventIter first, CanEventIt
}
freq_ = points_.size() / std::max(points_.back().x() - points_.front().x(), 1.0);
render(sig->color, range, size);
render(toQColor(sig->color), range, size);
}
void Sparkline::render(const QColor &color, int range, QSize size) {
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <array>
#include <cstdint>
#include <unordered_map>
#include <vector>
#include "tools/cabana/core/color.h"
#include "tools/cabana/core/message_id.h"
struct CanData {
void compute(const MessageId &msg_id, const uint8_t *data, int size, double current_sec,
double playback_speed, const std::vector<uint8_t> &mask, double frequency = 0);
double ts = 0.;
uint32_t count = 0;
double freq = 0;
std::vector<uint8_t> dat;
std::vector<CabanaColor> colors;
struct ByteLastChange {
double ts = 0;
int delta = 0;
int same_delta_counter = 0;
bool suppressed = false;
};
std::vector<ByteLastChange> last_changes;
std::vector<std::array<uint32_t, 8>> bit_flip_counts;
double last_freq_update_ts = 0;
};
struct CanEvent {
uint8_t src;
uint32_t address;
uint64_t mono_time;
uint8_t size;
uint8_t dat[];
};
struct CompareCanEvent {
constexpr bool operator()(const CanEvent *const event, uint64_t ts) const { return event->mono_time < ts; }
constexpr bool operator()(uint64_t ts, const CanEvent *const event) const { return ts < event->mono_time; }
};
using MessageEventsMap = std::unordered_map<MessageId, std::vector<const CanEvent *>>;
using CanEventIter = std::vector<const CanEvent *>::const_iterator;
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
struct CabanaColor {
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
uint8_t a = 255;
constexpr CabanaColor() = default;
constexpr CabanaColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255)
: r(red), g(green), b(blue), a(alpha) {}
static CabanaColor fromHsv(float hue, float saturation, float value, float alpha = 1.0f) {
const float h = hue - std::floor(hue);
const float c = value * saturation;
const float x = c * (1.0f - std::fabs(std::fmod(h * 6.0f, 2.0f) - 1.0f));
const float m = value - c;
float red = 0, green = 0, blue = 0;
switch (static_cast<int>(h * 6.0f) % 6) {
case 0: red = c; green = x; break;
case 1: red = x; green = c; break;
case 2: green = c; blue = x; break;
case 3: green = x; blue = c; break;
case 4: red = x; blue = c; break;
default: red = c; blue = x; break;
}
auto channel = [m](float v) { return static_cast<uint8_t>(std::clamp((v + m) * 255.0f, 0.0f, 255.0f) + 0.5f); };
return {channel(red), channel(green), channel(blue),
static_cast<uint8_t>(std::clamp(alpha * 255.0f, 0.0f, 255.0f) + 0.5f)};
}
CabanaColor darker(int factor = 200) const {
if (factor <= 0) return *this;
if (factor < 100) return lighter(10000 / factor);
auto [hue, saturation, value] = hsv();
return fromHsv(hue, saturation, value * 100.0f / factor, a / 255.0f);
}
CabanaColor lighter(int factor = 150) const {
if (factor <= 0) return *this;
if (factor < 100) return darker(10000 / factor);
auto [hue, saturation, value] = hsv();
const float scaled_value = value * factor / 100.0f;
if (scaled_value > 1.0f) saturation = std::max(0.0f, saturation - (scaled_value - 1.0f));
return fromHsv(hue, saturation, std::min(1.0f, scaled_value), a / 255.0f);
}
constexpr int red() const { return r; }
constexpr int green() const { return g; }
constexpr int blue() const { return b; }
constexpr int alpha() const { return a; }
float alphaF() const { return a / 255.0f; }
void setAlphaF(float alpha) { a = static_cast<uint8_t>(std::clamp(alpha * 255.0f, 0.0f, 255.0f) + 0.5f); }
constexpr bool operator==(const CabanaColor &other) const {
return r == other.r && g == other.g && b == other.b && a == other.a;
}
private:
struct Hsv { float hue; float saturation; float value; };
Hsv hsv() const {
const float red = r / 255.0f, green = g / 255.0f, blue = b / 255.0f;
const float maximum = std::max({red, green, blue});
const float minimum = std::min({red, green, blue});
const float delta = maximum - minimum;
float hue = 0;
if (delta > 0) {
if (maximum == red) hue = std::fmod((green - blue) / delta, 6.0f) / 6.0f;
else if (maximum == green) hue = ((blue - red) / delta + 2.0f) / 6.0f;
else hue = ((red - green) / delta + 4.0f) / 6.0f;
if (hue < 0) hue += 1.0f;
}
return {hue, maximum == 0 ? 0 : delta / maximum, maximum};
}
};
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
#include <cstdio>
#include <functional>
#include <string>
#include <tuple>
constexpr int INVALID_SOURCE = 0xff;
struct MessageId {
uint8_t source = 0;
uint32_t address = 0;
std::string toString() const { char b[64]; snprintf(b, sizeof(b), "%u:%X", source, address); return b; }
static MessageId fromString(const std::string &s) {
const auto p = s.find(':');
if (p == std::string::npos) return {};
return {.source = static_cast<uint8_t>(std::stoul(s.substr(0, p))), .address = static_cast<uint32_t>(std::stoul(s.substr(p + 1), nullptr, 16))};
}
bool operator==(const MessageId &o) const { return source == o.source && address == o.address; }
bool operator!=(const MessageId &o) const { return !(*this == o); }
bool operator<(const MessageId &o) const { return std::tie(source, address) < std::tie(o.source, o.address); }
bool operator>(const MessageId &o) const { return o < *this; }
};
template <> struct std::hash<MessageId> {
size_t operator()(const MessageId &id) const noexcept { return std::hash<uint8_t>{}(id.source) ^ (std::hash<uint32_t>{}(id.address) << 1); }
};
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <vector>
constexpr int LIGHT_THEME = 1;
constexpr int DARK_THEME = 2;
struct CabanaSettingsState {
enum DragDirection { MsbFirst, LsbFirst, AlwaysLE, AlwaysBE };
bool absolute_time = false;
int fps = 10;
int max_cached_minutes = 30;
int chart_height = 200;
int chart_column_count = 1;
int chart_range = 3 * 60;
int chart_series_type = 0;
int theme = 0;
int sparkline_range = 15;
bool multiple_lines_hex = false;
bool log_livestream = true;
bool suppress_defined_signals = false;
std::string log_path;
std::string last_dir;
std::string last_route_dir;
std::vector<std::string> recent_files;
DragDirection drag_direction = MsbFirst;
std::string recent_dbc_file;
std::string active_msg_id;
std::vector<std::string> selected_msg_ids;
std::vector<std::string> active_charts;
};
+13 -3
View File
@@ -1,8 +1,18 @@
#include "tools/cabana/dbc/dbc.h"
#include <algorithm>
#include <cmath>
#include "tools/cabana/utils/util.h"
namespace {
int numDecimals(double value) {
int decimals = 0;
while (decimals < 6 && std::fabs(value - std::round(value)) > 1e-9) {
value *= 10.0;
++decimals;
}
return decimals;
}
}
// cabana::Msg
@@ -135,8 +145,8 @@ void cabana::Signal::update() {
float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0;
float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0;
color = QColor::fromHsvF(h, s, v);
precision = std::max(num_decimals(factor), num_decimals(offset));
color = CabanaColor::fromHsv(h, s, v);
precision = std::max(numDecimals(factor), numDecimals(offset));
}
std::string cabana::Signal::formatValue(double value, bool with_unit) const {
+3 -47
View File
@@ -8,58 +8,14 @@
#include <utility>
#include <vector>
#include <QColor>
#include <QMetaType>
#include "tools/cabana/core/color.h"
#include "tools/cabana/core/message_id.h"
const std::string UNTITLED = "untitled";
const std::string DEFAULT_NODE_NAME = "XXX";
constexpr int CAN_MAX_DATA_BYTES = 64;
struct MessageId {
uint8_t source = 0;
uint32_t address = 0;
std::string toString() const {
char buf[64];
snprintf(buf, sizeof(buf), "%u:%X", source, address);
return buf;
}
inline static MessageId fromString(const std::string &str) {
auto pos = str.find(':');
if (pos == std::string::npos) return {};
return MessageId{.source = uint8_t(std::stoul(str.substr(0, pos))),
.address = uint32_t(std::stoul(str.substr(pos + 1), nullptr, 16))};
}
bool operator==(const MessageId &other) const {
return source == other.source && address == other.address;
}
bool operator!=(const MessageId &other) const {
return !(*this == other);
}
bool operator<(const MessageId &other) const {
return std::tie(source, address) < std::tie(other.source, other.address);
}
bool operator>(const MessageId &other) const {
return std::tie(source, address) > std::tie(other.source, other.address);
}
};
Q_DECLARE_METATYPE(MessageId);
template <>
struct std::hash<MessageId> {
std::size_t operator()(const MessageId &k) const noexcept {
return std::hash<uint8_t>{}(k.source) ^ (std::hash<uint32_t>{}(k.address) << 1);
}
};
typedef std::vector<std::pair<double, std::string>> ValueDescription;
Q_DECLARE_METATYPE(ValueDescription);
namespace cabana {
@@ -92,7 +48,7 @@ public:
std::string receiver_name;
ValueDescription val_desc;
int precision = 0;
QColor color;
CabanaColor color;
// Multiplexed
int multiplex_value = 0;
+170 -180
View File
@@ -1,23 +1,60 @@
#include "tools/cabana/dbc/dbcfile.h"
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QString>
#include <algorithm>
#include <cassert>
#include <filesystem>
#include <fstream>
#include <regex>
#include <sstream>
#include <stdexcept>
DBCFile::DBCFile(const std::string &dbc_file_name) {
QFile file(QString::fromStdString(dbc_file_name));
if (file.open(QIODevice::ReadOnly)) {
name_ = QFileInfo(QString::fromStdString(dbc_file_name)).baseName().toStdString();
filename = dbc_file_name;
parse(file.readAll());
} else {
throw std::runtime_error("Failed to open file.");
}
namespace {
std::string trim(const std::string &value) {
const auto first = value.find_first_not_of(" \t\r\n");
if (first == std::string::npos) return {};
return value.substr(first, value.find_last_not_of(" \t\r\n") - first + 1);
}
DBCFile::DBCFile(const std::string &name, const std::string &content) : name_(name), filename("") {
parse(QString::fromStdString(content));
bool startsWith(const std::string &value, const char *prefix) {
return value.rfind(prefix, 0) == 0;
}
std::string unescapeComment(std::string value) {
for (size_t pos = 0; (pos = value.find("\\\"", pos)) != std::string::npos; ++pos) {
value.replace(pos, 2, "\"");
}
return trim(value);
}
bool commentComplete(const std::string &line) {
bool escaped = false;
for (size_t i = 0; i < line.size(); ++i) {
if (line[i] == '\\' && !escaped) {
escaped = true;
continue;
}
if (line[i] == '"' && !escaped) {
size_t next = line.find_first_not_of(" \t\r\n", i + 1);
if (next != std::string::npos && line[next] == ';') return true;
}
escaped = false;
}
return false;
}
} // namespace
DBCFile::DBCFile(const std::string &dbc_file_name) {
std::ifstream file(dbc_file_name, std::ios::binary);
if (!file) throw std::runtime_error("Failed to open file.");
filename = dbc_file_name;
name_ = std::filesystem::path(dbc_file_name).stem().string();
parse(std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()));
}
DBCFile::DBCFile(const std::string &name, const std::string &content) : name_(name) {
parse(content);
}
bool DBCFile::save() {
@@ -31,15 +68,14 @@ bool DBCFile::saveAs(const std::string &new_filename) {
}
bool DBCFile::writeContents(const std::string &fn) {
QFile file(QString::fromStdString(fn));
if (file.open(QIODevice::WriteOnly)) {
std::string content = generateDBC();
return file.write(content.c_str(), content.size()) >= 0;
}
return false;
std::ofstream file(fn, std::ios::binary | std::ios::trunc);
if (!file) return false;
file << generateDBC();
return file.good();
}
void DBCFile::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) {
void DBCFile::updateMsg(const MessageId &id, const std::string &name, uint32_t size,
const std::string &node, const std::string &comment) {
auto &m = msgs[id.address];
m.address = id.address;
m.name = name;
@@ -55,178 +91,143 @@ cabana::Msg *DBCFile::msg(uint32_t address) {
cabana::Msg *DBCFile::msg(const std::string &name) {
auto it = std::find_if(msgs.begin(), msgs.end(), [&name](auto &m) { return m.second.name == name; });
return it != msgs.end() ? &(it->second) : nullptr;
return it != msgs.end() ? &it->second : nullptr;
}
cabana::Signal *DBCFile::signal(uint32_t address, const std::string &name) {
auto m = msg(address);
return m ? (cabana::Signal *)m->sig(name) : nullptr;
return m ? m->sig(name) : nullptr;
}
void DBCFile::parse(const QString &content) {
void DBCFile::parse(const std::string &content) {
msgs.clear();
int line_num = 0;
QString line;
header.clear();
std::istringstream input(content);
std::string raw_line;
cabana::Msg *current_msg = nullptr;
int multiplexor_cnt = 0;
int line_num = 0;
bool seen_first = false;
QTextStream stream((QString *)&content);
while (!stream.atEnd()) {
while (std::getline(input, raw_line)) {
++line_num;
QString raw_line = stream.readLine();
line = raw_line.trimmed();
const size_t first_nonspace = raw_line.find_first_not_of(" \t\r");
std::string line = first_nonspace == std::string::npos ? std::string() : raw_line.substr(first_nonspace);
const int statement_line = line_num;
if ((startsWith(line, "CM_ BO_") || startsWith(line, "CM_ SG_ ")) && !commentComplete(line)) {
std::string continuation;
while (std::getline(input, continuation)) {
++line_num;
line += "\n" + continuation;
if (commentComplete(line)) break;
}
}
bool seen = true;
try {
if (line.startsWith("BO_ ")) {
if (startsWith(line, "BO_ ")) {
multiplexor_cnt = 0;
current_msg = parseBO(line);
} else if (line.startsWith("SG_ ")) {
} else if (startsWith(line, "SG_ ")) {
parseSG(line, current_msg, multiplexor_cnt);
} else if (line.startsWith("VAL_ ")) {
} else if (startsWith(line, "VAL_ ")) {
parseVAL(line);
} else if (line.startsWith("CM_ BO_")) {
parseCM_BO(line, content, raw_line, stream);
} else if (line.startsWith("CM_ SG_ ")) {
parseCM_SG(line, content, raw_line, stream);
} else if (startsWith(line, "CM_ BO_")) {
parseCM_BO(line);
} else if (startsWith(line, "CM_ SG_ ")) {
parseCM_SG(line);
} else {
seen = false;
}
} catch (std::exception &e) {
throw std::runtime_error(QString("[%1:%2]%3: %4").arg(QString::fromStdString(filename)).arg(line_num).arg(e.what()).arg(line).toStdString());
}
if (seen) {
seen_first = true;
} else if (!seen_first) {
header += raw_line.toStdString() + "\n";
} catch (const std::exception &e) {
throw std::runtime_error("[" + filename + ":" + std::to_string(statement_line) + "]" + e.what() + ": " + line);
}
if (seen) seen_first = true;
else if (!seen_first) header += raw_line + "\n";
}
for (auto &[_, m] : msgs) {
m.update();
}
for (auto &[_, message] : msgs) message.update();
}
cabana::Msg *DBCFile::parseBO(const QString &line) {
static QRegularExpression bo_regexp(R"(^BO_ (?<address>\w+) (?<name>\w+) *: (?<size>\w+) (?<transmitter>\w+))");
QRegularExpressionMatch match = bo_regexp.match(line);
if (!match.hasMatch())
throw std::runtime_error("Invalid BO_ line format");
uint32_t address = match.captured("address").toUInt();
if (msgs.count(address) > 0)
throw std::runtime_error(QString("Duplicate message address: %1").arg(address).toStdString());
// Create a new message object
cabana::Msg *msg = &msgs[address];
msg->address = address;
msg->name = match.captured("name").toStdString();
msg->size = match.captured("size").toULong();
msg->transmitter = match.captured("transmitter").trimmed().toStdString();
return msg;
cabana::Msg *DBCFile::parseBO(const std::string &line) {
static const std::regex pattern(R"(^BO_ ([[:alnum:]_]+) ([[:alnum:]_]+) *: ([[:alnum:]_]+) ([[:alnum:]_]+))");
std::smatch match;
if (!std::regex_search(line, match, pattern)) throw std::runtime_error("Invalid BO_ line format");
const uint32_t address = std::stoul(match[1].str());
if (msgs.count(address)) throw std::runtime_error("Duplicate message address: " + std::to_string(address));
auto &message = msgs[address];
message.address = address;
message.name = match[2].str();
message.size = std::stoul(match[3].str());
message.transmitter = trim(match[4].str());
return &message;
}
void DBCFile::parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) {
static QRegularExpression msg_comment_regexp(R"(^CM_ BO_ *(?<address>\w+) *\"(?<comment>(?:[^"\\]|\\.)*)\"\s*;)");
void DBCFile::parseSG(const std::string &line, cabana::Msg *current_msg, int &multiplexor_cnt) {
static const std::regex pattern(R"dbc(^SG_ ([[:alnum:]_]+)(?: +([[:alnum:]_]+))? *: ([0-9]+)\|([0-9]+)@([0-9]+)([+-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] "(.*)" (.*))dbc");
if (!current_msg) throw std::runtime_error("No Message");
std::smatch match;
if (!std::regex_search(line, match, pattern)) throw std::runtime_error("Invalid SG_ line format");
if (current_msg->sig(match[1].str())) throw std::runtime_error("Duplicate signal name");
QString parse_line = line;
if (!parse_line.endsWith("\";")) {
int pos = stream.pos() - raw_line.length() - 1;
parse_line = content.mid(pos, content.indexOf("\";", pos));
}
auto match = msg_comment_regexp.match(parse_line);
if (!match.hasMatch())
throw std::runtime_error("Invalid message comment format");
if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt()))
m->comment = match.captured("comment").trimmed().replace("\\\"", "\"").toStdString();
}
void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) {
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]+)\] \"(.*)\" (.*))");
if (!current_msg)
throw std::runtime_error("No Message");
int offset = 0;
auto match = sg_regexp.match(line);
if (!match.hasMatch()) {
match = sgm_regexp.match(line);
offset = 1;
}
if (!match.hasMatch())
throw std::runtime_error("Invalid SG_ line format");
std::string name = match.captured(1).toStdString();
if (current_msg->sig(name) != nullptr)
throw std::runtime_error("Duplicate signal name");
cabana::Signal s{};
if (offset == 1) {
auto indicator = match.captured(2);
cabana::Signal signal{};
const std::string indicator = match[2].str();
if (!indicator.empty()) {
if (indicator == "M") {
++multiplexor_cnt;
// Only one signal within a single message can be the multiplexer switch.
if (multiplexor_cnt >= 2)
throw std::runtime_error("Multiple multiplexor");
s.type = cabana::Signal::Type::Multiplexor;
if (++multiplexor_cnt >= 2) throw std::runtime_error("Multiple multiplexor");
signal.type = cabana::Signal::Type::Multiplexor;
} else {
s.type = cabana::Signal::Type::Multiplexed;
s.multiplex_value = indicator.mid(1).toInt();
signal.type = cabana::Signal::Type::Multiplexed;
signal.multiplex_value = indicator.size() > 1 ? std::stoi(indicator.substr(1)) : 0;
}
}
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();
s.min = match.captured(8 + offset).toDouble();
s.max = match.captured(9 + offset).toDouble();
s.unit = match.captured(10 + offset).toStdString();
s.receiver_name = match.captured(11 + offset).trimmed().toStdString();
current_msg->sigs.push_back(new cabana::Signal(s));
signal.name = match[1].str();
signal.start_bit = std::stoi(match[3].str());
signal.size = std::stoi(match[4].str());
signal.is_little_endian = match[5].str() == "1";
signal.is_signed = match[6].str() == "-";
signal.factor = std::stod(match[7].str());
signal.offset = std::stod(match[8].str());
signal.min = std::stod(match[9].str());
signal.max = std::stod(match[10].str());
signal.unit = match[11].str();
signal.receiver_name = trim(match[12].str());
current_msg->sigs.push_back(new cabana::Signal(signal));
}
void DBCFile::parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream) {
static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"((?:[^"\\]|\\.)*)\"\s*;)");
QString parse_line = line;
if (!parse_line.endsWith("\";")) {
int pos = stream.pos() - raw_line.length() - 1;
parse_line = content.mid(pos, content.indexOf("\";", pos));
void DBCFile::parseCM_BO(const std::string &line) {
std::istringstream prefix(line.substr(7));
uint32_t address = 0;
prefix >> address;
const size_t first_quote = line.find('"');
const size_t last_quote = line.rfind('"');
if (!prefix || first_quote == std::string::npos || last_quote <= first_quote) {
throw std::runtime_error("Invalid message comment format");
}
auto match = sg_comment_regexp.match(parse_line);
if (!match.hasMatch())
if (auto message = msg(address)) message->comment = unescapeComment(line.substr(first_quote + 1, last_quote - first_quote - 1));
}
void DBCFile::parseCM_SG(const std::string &line) {
std::istringstream prefix(line.substr(7));
uint32_t address = 0;
std::string name;
prefix >> address >> name;
const size_t first_quote = line.find('"');
const size_t last_quote = line.rfind('"');
if (!prefix || name.empty() || first_quote == std::string::npos || last_quote <= first_quote) {
throw std::runtime_error("Invalid CM_ SG_ line format");
if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) {
s->comment = match.captured(3).trimmed().replace("\\\"", "\"").toStdString();
}
if (auto sig = signal(address, name)) sig->comment = unescapeComment(line.substr(first_quote + 1, last_quote - first_quote - 1));
}
void DBCFile::parseVAL(const QString &line) {
static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (\s*[-+]?[0-9]+\s+\".+?\"[^;]*))");
auto match = val_regexp.match(line);
if (!match.hasMatch())
throw std::runtime_error("invalid VAL_ line format");
if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) {
QStringList desc_list = match.captured(3).trimmed().split('"');
for (int i = 0; i < desc_list.size(); i += 2) {
auto val = desc_list[i].trimmed();
if (!val.isEmpty() && (i + 1) < desc_list.size()) {
auto desc = desc_list[i + 1].trimmed();
s->val_desc.push_back({val.toDouble(), desc.toStdString()});
}
void DBCFile::parseVAL(const std::string &line) {
static const std::regex header_pattern(R"(^VAL_ ([[:alnum:]_]+) ([[:alnum:]_]+) (.*))");
static const std::regex entry_pattern(R"dbc(([+-]?[0-9]+(?:\.[0-9]+)?)\s+"([^"]*)")dbc");
std::smatch match;
if (!std::regex_search(line, match, header_pattern)) throw std::runtime_error("invalid VAL_ line format");
if (auto sig = signal(std::stoul(match[1].str()), match[2].str())) {
const std::string entries = match[3].str();
for (std::sregex_iterator it(entries.begin(), entries.end(), entry_pattern), end; it != end; ++it) {
sig->val_desc.emplace_back(std::stod((*it)[1].str()), trim((*it)[2].str()));
}
}
}
@@ -237,40 +238,29 @@ std::string DBCFile::generateDBC() {
const std::string &transmitter = m.transmitter.empty() ? DEFAULT_NODE_NAME : m.transmitter;
dbc_string += "BO_ " + std::to_string(address) + " " + m.name + ": " + std::to_string(m.size) + " " + transmitter + "\n";
if (!m.comment.empty()) {
std::string escaped_comment = m.comment;
// Replace " with \"
for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2)
escaped_comment.replace(pos, 1, "\\\"");
comment += "CM_ BO_ " + std::to_string(address) + " \"" + escaped_comment + "\";\n";
std::string escaped = m.comment;
for (size_t pos = 0; (pos = escaped.find('"', pos)) != std::string::npos; pos += 2) escaped.replace(pos, 1, "\\\"");
comment += "CM_ BO_ " + std::to_string(address) + " \"" + escaped + "\";\n";
}
for (auto sig : m.getSignals()) {
std::string multiplexer_indicator;
if (sig->type == cabana::Signal::Type::Multiplexor) {
multiplexer_indicator = "M ";
} else if (sig->type == cabana::Signal::Type::Multiplexed) {
multiplexer_indicator = "m" + std::to_string(sig->multiplex_value) + " ";
}
const std::string &recv = sig->receiver_name.empty() ? DEFAULT_NODE_NAME : sig->receiver_name;
dbc_string += " SG_ " + sig->name + " " + multiplexer_indicator + ": " +
std::to_string(sig->start_bit) + "|" + std::to_string(sig->size) + "@" +
std::string(1, sig->is_little_endian ? '1' : '0') +
std::string(1, sig->is_signed ? '-' : '+') +
std::string mux;
if (sig->type == cabana::Signal::Type::Multiplexor) mux = "M ";
else if (sig->type == cabana::Signal::Type::Multiplexed) mux = "m" + std::to_string(sig->multiplex_value) + " ";
const std::string &receiver = sig->receiver_name.empty() ? DEFAULT_NODE_NAME : sig->receiver_name;
dbc_string += " SG_ " + sig->name + " " + mux + ": " + std::to_string(sig->start_bit) + "|" + std::to_string(sig->size) + "@" +
(sig->is_little_endian ? "1" : "0") + (sig->is_signed ? "-" : "+") +
" (" + doubleToString(sig->factor) + "," + doubleToString(sig->offset) + ")" +
" [" + doubleToString(sig->min) + "|" + doubleToString(sig->max) + "]" +
" \"" + sig->unit + "\" " + recv + "\n";
" [" + doubleToString(sig->min) + "|" + doubleToString(sig->max) + "] \"" + sig->unit + "\" " + receiver + "\n";
if (!sig->comment.empty()) {
std::string escaped_comment = sig->comment;
for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2)
escaped_comment.replace(pos, 1, "\\\"");
comment += "CM_ SG_ " + std::to_string(address) + " " + sig->name + " \"" + escaped_comment + "\";\n";
std::string escaped = sig->comment;
for (size_t pos = 0; (pos = escaped.find('"', pos)) != std::string::npos; pos += 2) escaped.replace(pos, 1, "\\\"");
comment += "CM_ SG_ " + std::to_string(address) + " " + sig->name + " \"" + escaped + "\";\n";
}
if (!sig->val_desc.empty()) {
std::string text;
for (auto &[val, desc] : sig->val_desc) {
for (const auto &[value, description] : sig->val_desc) {
if (!text.empty()) text += " ";
char val_buf[64];
snprintf(val_buf, sizeof(val_buf), "%g", val);
text += std::string(val_buf) + " \"" + desc + "\"";
text += doubleToString(value) + " \"" + description + "\"";
}
val_desc += "VAL_ " + std::to_string(address) + " " + sig->name + " " + text + ";\n";
}
+6 -7
View File
@@ -2,7 +2,6 @@
#include <map>
#include <string>
#include <QTextStream>
#include "tools/cabana/dbc/dbc.h"
@@ -32,12 +31,12 @@ public:
std::string filename;
private:
void parse(const QString &content);
cabana::Msg *parseBO(const QString &line);
void parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt);
void parseCM_BO(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream);
void parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream);
void parseVAL(const QString &line);
void parse(const std::string &content);
cabana::Msg *parseBO(const std::string &line);
void parseSG(const std::string &line, cabana::Msg *current_msg, int &multiplexor_cnt);
void parseCM_BO(const std::string &line);
void parseCM_SG(const std::string &line);
void parseVAL(const std::string &line);
std::string header;
std::map<uint32_t, cabana::Msg> msgs;
+18 -17
View File
@@ -1,9 +1,10 @@
#include "tools/cabana/dbc/dbcmanager.h"
#include <algorithm>
#include <cassert>
#include <set>
bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name, QString *error) {
bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name, std::string *error) {
try {
auto it = std::find_if(dbc_files.begin(), dbc_files.end(),
[&](auto &f) { return f.second && f.second->filename == dbc_file_name; });
@@ -16,11 +17,11 @@ bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name
return false;
}
emit DBCFileChanged();
if (callbacks_.file_changed) callbacks_.file_changed();
return true;
}
bool DBCManager::open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error) {
bool DBCManager::open(const SourceSet &sources, const std::string &name, const std::string &content, std::string *error) {
try {
auto file = std::make_shared<DBCFile>(name, content);
for (auto s : sources) {
@@ -31,7 +32,7 @@ bool DBCManager::open(const SourceSet &sources, const std::string &name, const s
return false;
}
emit DBCFileChanged();
if (callbacks_.file_changed) callbacks_.file_changed();
return true;
}
@@ -39,26 +40,26 @@ void DBCManager::close(const SourceSet &sources) {
for (auto s : sources) {
dbc_files[s] = nullptr;
}
emit DBCFileChanged();
if (callbacks_.file_changed) callbacks_.file_changed();
}
void DBCManager::close(DBCFile *dbc_file) {
for (auto &[_, f] : dbc_files) {
if (f.get() == dbc_file) f = nullptr;
}
emit DBCFileChanged();
if (callbacks_.file_changed) callbacks_.file_changed();
}
void DBCManager::closeAll() {
dbc_files.clear();
emit DBCFileChanged();
if (callbacks_.file_changed) callbacks_.file_changed();
}
void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
if (auto m = msg(id)) {
if (auto s = m->addSignal(sig)) {
emit signalAdded(id, s);
emit maskUpdated();
if (callbacks_.signal_added) callbacks_.signal_added(id, s);
if (callbacks_.mask_updated) callbacks_.mask_updated();
}
}
}
@@ -66,8 +67,8 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) {
void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig) {
if (auto m = msg(id)) {
if (auto s = m->updateSignal(sig_name, sig)) {
emit signalUpdated(s);
emit maskUpdated();
if (callbacks_.signal_updated) callbacks_.signal_updated(s);
if (callbacks_.mask_updated) callbacks_.mask_updated();
}
}
}
@@ -75,9 +76,9 @@ void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name,
void DBCManager::removeSignal(const MessageId &id, const std::string &sig_name) {
if (auto m = msg(id)) {
if (auto s = m->sig(sig_name)) {
emit signalRemoved(s);
if (callbacks_.signal_removed) callbacks_.signal_removed(s);
m->removeSignal(sig_name);
emit maskUpdated();
if (callbacks_.mask_updated) callbacks_.mask_updated();
}
}
}
@@ -86,15 +87,15 @@ void DBCManager::updateMsg(const MessageId &id, const std::string &name, uint32_
auto dbc_file = findDBCFile(id);
assert(dbc_file); // This should be impossible
dbc_file->updateMsg(id, name, size, node, comment);
emit msgUpdated(id);
if (callbacks_.msg_updated) callbacks_.msg_updated(id);
}
void DBCManager::removeMsg(const MessageId &id) {
auto dbc_file = findDBCFile(id);
assert(dbc_file); // This should be impossible
dbc_file->removeMsg(id);
emit msgRemoved(id);
emit maskUpdated();
if (callbacks_.msg_removed) callbacks_.msg_removed(id);
if (callbacks_.mask_updated) callbacks_.mask_updated();
}
std::string DBCManager::newMsgName(const MessageId &id) {
@@ -176,6 +177,6 @@ std::string toString(const SourceSet &ss) {
}
DBCManager *dbc() {
static DBCManager dbc_manager(nullptr);
static DBCManager dbc_manager;
return &dbc_manager;
}
+17 -18
View File
@@ -1,6 +1,6 @@
#pragma once
#include <QObject>
#include <functional>
#include <memory>
#include <map>
#include <set>
@@ -11,17 +11,23 @@
typedef std::set<int> SourceSet;
const SourceSet SOURCE_ALL = {-1};
const int INVALID_SOURCE = 0xff;
inline bool operator<(const std::shared_ptr<DBCFile> &l, const std::shared_ptr<DBCFile> &r) { return l.get() < r.get(); }
class DBCManager : public QObject {
Q_OBJECT
class DBCManager {
public:
DBCManager(QObject *parent) : QObject(parent) {}
~DBCManager() {}
bool open(const SourceSet &sources, const std::string &dbc_file_name, QString *error = nullptr);
bool open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error = nullptr);
struct Callbacks {
std::function<void(MessageId, const cabana::Signal *)> signal_added;
std::function<void(const cabana::Signal *)> signal_removed;
std::function<void(const cabana::Signal *)> signal_updated;
std::function<void(MessageId)> msg_updated;
std::function<void(MessageId)> msg_removed;
std::function<void()> file_changed;
std::function<void()> mask_updated;
};
DBCManager() = default;
bool open(const SourceSet &sources, const std::string &dbc_file_name, std::string *error = nullptr);
bool open(const SourceSet &sources, const std::string &name, const std::string &content, std::string *error = nullptr);
void close(const SourceSet &sources);
void close(DBCFile *dbc_file);
void closeAll();
@@ -48,18 +54,11 @@ public:
DBCFile *findDBCFile(const uint8_t source);
inline DBCFile *findDBCFile(const MessageId &id) { return findDBCFile(id.source); }
std::set<DBCFile *> allDBCFiles();
signals:
void signalAdded(MessageId id, const cabana::Signal *sig);
void signalRemoved(const cabana::Signal *sig);
void signalUpdated(const cabana::Signal *sig);
void msgUpdated(MessageId id);
void msgRemoved(MessageId id);
void DBCFileChanged();
void maskUpdated();
void setCallbacks(Callbacks callbacks) { callbacks_ = std::move(callbacks); }
private:
std::map<int, std::shared_ptr<DBCFile>> dbc_files;
Callbacks callbacks_;
};
DBCManager *dbc();
+18
View File
@@ -0,0 +1,18 @@
#include "tools/cabana/dbc/dbcqt.h"
QtDBCNotifier::QtDBCNotifier(QObject *parent) : QObject(parent) {
dbc()->setCallbacks({
.signal_added = [this](MessageId id, const cabana::Signal *sig) { emit signalAdded(id, sig); },
.signal_removed = [this](const cabana::Signal *sig) { emit signalRemoved(sig); },
.signal_updated = [this](const cabana::Signal *sig) { emit signalUpdated(sig); },
.msg_updated = [this](MessageId id) { emit msgUpdated(id); },
.msg_removed = [this](MessageId id) { emit msgRemoved(id); },
.file_changed = [this]() { emit DBCFileChanged(); },
.mask_updated = [this]() { emit maskUpdated(); },
});
}
QtDBCNotifier *dbcNotifier() {
static QtDBCNotifier notifier;
return &notifier;
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <QMetaType>
#include <QObject>
#include "tools/cabana/dbc/dbcmanager.h"
Q_DECLARE_METATYPE(MessageId)
Q_DECLARE_METATYPE(ValueDescription)
class QtDBCNotifier : public QObject {
Q_OBJECT
public:
explicit QtDBCNotifier(QObject *parent = nullptr);
signals:
void signalAdded(MessageId id, const cabana::Signal *sig);
void signalRemoved(const cabana::Signal *sig);
void signalUpdated(const cabana::Signal *sig);
void msgUpdated(MessageId id);
void msgRemoved(MessageId id);
void DBCFileChanged();
void maskUpdated();
};
QtDBCNotifier *dbcNotifier();
+2 -1
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/detailwidget.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <QFormLayout>
#include <QMenu>
@@ -56,7 +57,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
QObject::connect(signal_view, &SignalView::highlight, binary_view, &BinaryView::highlight);
QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &DetailWidget::updateState);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::refresh);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &DetailWidget::refresh);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &DetailWidget::refresh);
QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
+6 -5
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/historylog.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <functional>
@@ -54,7 +55,7 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
return unit.isEmpty() ? name : QString("%1 (%2)").arg(name, unit);
} else if (role == Qt::BackgroundRole && section > 0 && !isHexMode()) {
// Alpha-blend the signal color with the background to ensure contrast
QColor sigColor = sigs[section - 1]->color;
QColor sigColor = toQColor(sigs[section - 1]->color);
sigColor.setAlpha(128);
return QBrush(sigColor);
}
@@ -207,7 +208,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) {
QObject::connect(value_edit, &QLineEdit::textEdited, this, &LogsWidget::filterChanged);
QObject::connect(export_btn, &QToolButton::clicked, this, &LogsWidget::exportToCSV);
QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::reset);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &HistoryLogModel::reset);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, model, &HistoryLogModel::reset);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, model, &HistoryLogModel::reset);
QObject::connect(model, &HistoryLogModel::modelReset, this, &LogsWidget::modelReset);
QObject::connect(model, &HistoryLogModel::rowsInserted, [this]() { export_btn->setEnabled(true); });
@@ -238,11 +239,11 @@ void LogsWidget::filterChanged() {
}
void LogsWidget::exportToCSV() {
QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())).arg(QString::fromStdString(msgName(model->msg_id)));
QString dir = QString("%1/%2_%3.csv").arg(QString::fromStdString(settings.last_dir)).arg(QString::fromStdString(can->routeName())).arg(QString::fromStdString(msgName(model->msg_id)));
QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(QString::fromStdString(msgName(model->msg_id))),
dir, tr("csv (*.csv)"));
if (!fn.isEmpty()) {
model->isHexMode() ? utils::exportToCSV(fn, model->msg_id)
: utils::exportSignalsToCSV(fn, model->msg_id);
model->isHexMode() ? utils::exportToCSV(fn.toStdString(), model->msg_id)
: utils::exportSignalsToCSV(fn.toStdString(), model->msg_id);
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ public:
uint64_t mono_time = 0;
std::vector<double> sig_values;
std::vector<uint8_t> data;
std::vector<QColor> colors;
std::vector<CabanaColor> colors;
};
void fetchData(std::deque<Message>::iterator insert_pos, uint64_t from_time, uint64_t min_time);
+38 -27
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/mainwin.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <iostream>
@@ -65,7 +66,7 @@ MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainW
QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage);
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &MainWindow::DBCFileChanged);
QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged);
QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus);
@@ -253,16 +254,16 @@ void MainWindow::selectAndOpenStream() {
void MainWindow::closeStream() {
openStream(new DummyStream(this));
if (dbc()->nonEmptyDBCCount() > 0) {
emit dbc()->DBCFileChanged();
emit dbcNotifier()->DBCFileChanged();
}
statusBar()->showMessage(tr("stream closed"));
}
void MainWindow::exportToCSV() {
QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName()));
QString dir = QString("%1/%2.csv").arg(QString::fromStdString(settings.last_dir)).arg(QString::fromStdString(can->routeName()));
QString fn = QFileDialog::getSaveFileName(this, "Export stream to CSV file", dir, tr("csv (*.csv)"));
if (!fn.isEmpty()) {
utils::exportToCSV(fn);
utils::exportToCSV(fn.toStdString());
}
}
@@ -273,7 +274,7 @@ void MainWindow::newFile(SourceSet s) {
void MainWindow::openFile(SourceSet s) {
remindSaveChanges();
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), QString::fromStdString(settings.last_dir), "DBC (*.dbc)");
if (!fn.isEmpty()) {
loadFile(fn, s);
}
@@ -283,13 +284,13 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) {
if (!fn.isEmpty()) {
closeFile(s);
QString error;
std::string error;
if (dbc()->open(s, fn.toStdString(), &error)) {
updateRecentFiles(fn);
statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000);
} else {
QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn));
msg_box.setDetailedText(error);
msg_box.setDetailedText(QString::fromStdString(error));
msg_box.exec();
}
}
@@ -303,13 +304,13 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) {
closeFile(s);
QString dbc_str = QGuiApplication::clipboard()->text();
QString error;
std::string error;
bool ret = dbc()->open(s, std::string(""), dbc_str.toStdString(), &error);
if (ret && dbc()->nonEmptyDBCCount() > 0) {
QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!"));
} else {
QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC from clipboard"), tr("Make sure that you paste the text with correct format."));
msg_box.setDetailedText(error);
msg_box.setDetailedText(QString::fromStdString(error));
msg_box.exec();
}
}
@@ -427,7 +428,7 @@ void MainWindow::saveFile(DBCFile *dbc_file) {
void MainWindow::saveFileAs(DBCFile *dbc_file) {
QString title = tr("Save File (bus: %1)").arg(QString::fromStdString(toString(dbc()->sources(dbc_file))));
QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(QString::fromStdString(settings.last_dir) + "/untitled.dbc"), tr("DBC (*.dbc)"));
if (!fn.isEmpty()) {
dbc_file->saveAs(fn.toStdString());
UndoStack::instance()->setClean();
@@ -481,12 +482,13 @@ void MainWindow::updateLoadSaveMenus() {
}
void MainWindow::updateRecentFiles(const QString &fn) {
settings.recent_files.removeAll(fn);
settings.recent_files.prepend(fn);
const std::string filename = fn.toStdString();
settings.recent_files.erase(std::remove(settings.recent_files.begin(), settings.recent_files.end(), filename), settings.recent_files.end());
settings.recent_files.insert(settings.recent_files.begin(), filename);
while (settings.recent_files.size() > MAX_RECENT_FILES) {
settings.recent_files.removeLast();
settings.recent_files.pop_back();
}
settings.last_dir = QFileInfo(fn).absolutePath();
settings.last_dir = QFileInfo(fn).absolutePath().toStdString();
}
void MainWindow::updateRecentFileMenu() {
@@ -499,8 +501,8 @@ void MainWindow::updateRecentFileMenu() {
}
for (int i = 0; i < num_recent_files; ++i) {
QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(settings.recent_files[i]).fileName());
open_recent_menu->addAction(text, this, [this, file = settings.recent_files[i]]() { loadFile(file); });
QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(QString::fromStdString(settings.recent_files[i])).fileName());
open_recent_menu->addAction(text, this, [this, file = settings.recent_files[i]]() { loadFile(QString::fromStdString(file)); });
}
}
@@ -627,30 +629,39 @@ void MainWindow::saveSessionState() {
settings.active_charts.clear();
for (auto &f : dbc()->allDBCFiles())
if (!f->isEmpty()) { settings.recent_dbc_file = QString::fromStdString(f->filename); break; }
if (!f->isEmpty()) { settings.recent_dbc_file = f->filename; break; }
if (auto *detail = center_widget->getDetailWidget()) {
auto [active_id, ids] = detail->serializeMessageIds();
settings.active_msg_id = active_id;
settings.selected_msg_ids = ids;
settings.active_msg_id = active_id.toStdString();
settings.selected_msg_ids.clear();
for (const auto &id : ids) settings.selected_msg_ids.push_back(id.toStdString());
}
if (charts_widget) {
settings.active_charts.clear();
for (const auto &id : charts_widget->serializeChartIds()) settings.active_charts.push_back(id.toStdString());
}
if (charts_widget)
settings.active_charts = charts_widget->serializeChartIds();
}
void MainWindow::restoreSessionState() {
if (settings.recent_dbc_file.isEmpty() || dbc()->nonEmptyDBCCount() == 0) return;
if (settings.recent_dbc_file.empty() || dbc()->nonEmptyDBCCount() == 0) return;
QString dbc_file;
for (auto& f : dbc()->allDBCFiles())
if (!f->isEmpty()) { dbc_file = QString::fromStdString(f->filename); break; }
if (dbc_file != settings.recent_dbc_file) return;
if (dbc_file.toStdString() != settings.recent_dbc_file) return;
if (!settings.selected_msg_ids.isEmpty())
center_widget->ensureDetailWidget()->restoreTabs(settings.active_msg_id, settings.selected_msg_ids);
if (!settings.selected_msg_ids.empty()) {
QStringList ids;
for (const auto &id : settings.selected_msg_ids) ids.push_back(QString::fromStdString(id));
center_widget->ensureDetailWidget()->restoreTabs(QString::fromStdString(settings.active_msg_id), ids);
}
if (charts_widget != nullptr && !settings.active_charts.empty())
charts_widget->restoreChartsFromIds(settings.active_charts);
if (charts_widget != nullptr && !settings.active_charts.empty()) {
QStringList ids;
for (const auto &id : settings.active_charts) ids.push_back(QString::fromStdString(id));
charts_widget->restoreChartsFromIds(ids);
}
}
// HelpOverlay
+2 -1
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/messageswidget.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <limits>
#include <utility>
@@ -43,7 +44,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget
QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent);
QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions);
QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::dbcModified);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, model, &MessageListModel::dbcModified);
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, model, &MessageListModel::dbcModified);
QObject::connect(model, &MessageListModel::modelReset, [this]() {
if (current_msg_id) {
+30 -10
View File
@@ -17,8 +17,31 @@ const int MAX_CACHE_MINIUTES = 120;
Settings settings;
template <typename T>
void readSetting(QSettings &settings_store, const char *key, T &value) {
if (auto stored = settings_store.value(key); stored.canConvert<T>()) value = stored.value<T>();
}
void readSetting(QSettings &settings_store, const char *key, std::string &value) {
value = settings_store.value(key, QString::fromStdString(value)).toString().toStdString();
}
void readSetting(QSettings &settings_store, const char *key, std::vector<std::string> &value) {
value.clear();
for (const auto &item : settings_store.value(key).toStringList()) value.push_back(item.toStdString());
}
template <typename T>
void writeSetting(QSettings &settings_store, const char *key, const T &value) { settings_store.setValue(key, value); }
void writeSetting(QSettings &settings_store, const char *key, const std::string &value) { settings_store.setValue(key, QString::fromStdString(value)); }
void writeSetting(QSettings &settings_store, const char *key, const std::vector<std::string> &value) {
QStringList items;
for (const auto &item : value) items.push_back(QString::fromStdString(item));
settings_store.setValue(key, items);
}
template <class SettingOperation>
void settings_op(SettingOperation op) {
void settingsOp(SettingOperation op) {
QSettings s("cabana");
op(s, "absolute_time", settings.absolute_time);
op(s, "fps", settings.fps);
@@ -48,16 +71,13 @@ void settings_op(SettingOperation op) {
}
Settings::Settings() {
last_dir = last_route_dir = QDir::homePath();
log_path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/cabana_live_stream/";
settings_op([](QSettings &s, const QString &key, auto &value) {
if (auto v = s.value(key); v.canConvert<std::decay_t<decltype(value)>>())
value = v.value<std::decay_t<decltype(value)>>();
});
last_dir = last_route_dir = QDir::homePath().toStdString();
log_path = (QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/cabana_live_stream/").toStdString();
settingsOp([](QSettings &s, const char *key, auto &value) { readSetting(s, key, value); });
}
Settings::~Settings() {
settings_op([](QSettings &s, const QString &key, auto &v) { s.setValue(key, v); });
settingsOp([](QSettings &s, const char *key, const auto &value) { writeSetting(s, key, value); });
}
// SettingsDlg
@@ -102,7 +122,7 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
log_livestream = new QGroupBox(tr("Enable live stream logging"), this);
log_livestream->setCheckable(true);
QHBoxLayout *path_layout = new QHBoxLayout(log_livestream);
path_layout->addWidget(log_path = new QLineEdit(settings.log_path, this));
path_layout->addWidget(log_path = new QLineEdit(QString::fromStdString(settings.log_path), this));
log_path->setReadOnly(true);
auto browse_btn = new QPushButton(tr("B&rowse..."));
path_layout->addWidget(browse_btn);
@@ -134,7 +154,7 @@ void SettingsDlg::save() {
settings.max_cached_minutes = cached_minutes->value();
settings.chart_height = chart_height->value();
settings.log_livestream = log_livestream->isChecked();
settings.log_path = log_path->text();
settings.log_path = log_path->text().toStdString();
settings.drag_direction = (Settings::DragDirection)drag_direction->currentIndex();
emit settings.changed();
QDialog::accept();
+3 -33
View File
@@ -7,50 +7,20 @@
#include <QLineEdit>
#include <QSpinBox>
#define LIGHT_THEME 1
#define DARK_THEME 2
#include "tools/cabana/core/settings.h"
class Settings : public QObject {
class Settings : public QObject, public CabanaSettingsState {
Q_OBJECT
public:
enum DragDirection {
MsbFirst,
LsbFirst,
AlwaysLE,
AlwaysBE,
};
Settings();
~Settings();
bool absolute_time = false;
int fps = 10;
int max_cached_minutes = 30;
int chart_height = 200;
int chart_column_count = 1;
int chart_range = 3 * 60; // 3 minutes
int chart_series_type = 0;
int theme = 0;
int sparkline_range = 15; // 15 seconds
bool multiple_lines_hex = false;
bool log_livestream = true;
bool suppress_defined_signals = false;
QString log_path;
QString last_dir;
QString last_route_dir;
// Qt frontend layout state. This intentionally stays outside CabanaSettingsState.
QByteArray geometry;
QByteArray video_splitter_state;
QByteArray window_state;
QStringList recent_files;
QByteArray message_header_state;
DragDirection drag_direction = MsbFirst;
// session data
QString recent_dbc_file;
QString active_msg_id;
QStringList selected_msg_ids;
QStringList active_charts;
signals:
void changed();
+10 -9
View File
@@ -1,4 +1,5 @@
#include "tools/cabana/signalview.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <algorithm>
#include <future>
@@ -25,12 +26,12 @@ static QString signalTypeToString(cabana::Signal::Type type) {
}
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);
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &SignalModel::handleMsgChanged);
QObject::connect(dbc(), &DBCManager::signalAdded, this, &SignalModel::handleSignalAdded);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalModel::handleSignalUpdated);
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &SignalModel::handleSignalRemoved);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &SignalModel::refresh);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgUpdated, this, &SignalModel::handleMsgChanged);
QObject::connect(dbcNotifier(), &QtDBCNotifier::msgRemoved, this, &SignalModel::handleMsgChanged);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalAdded, this, &SignalModel::handleSignalAdded);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalUpdated, this, &SignalModel::handleSignalUpdated);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalRemoved, this, &SignalModel::handleSignalRemoved);
}
void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) {
@@ -304,7 +305,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
path.addRoundedRect(icon_rect, 3, 3);
painter->setPen(item->highlight ? Qt::white : Qt::black);
painter->setFont(label_font);
painter->fillPath(path, item->sig->color.darker(item->highlight ? 125 : 0));
painter->fillPath(path, toQColor(item->sig->color.darker(item->highlight ? 125 : 0)));
painter->drawText(icon_rect, Qt::AlignCenter, QString::number(item->row() + 1));
rect.setLeft(icon_rect.right() + h_margin * 2);
@@ -481,8 +482,8 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
QObject::connect(tree, &QTreeView::entered, [this](const QModelIndex &index) { emit highlight(model->getItem(index)->sig); });
QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged);
QObject::connect(dbc(), &DBCManager::signalAdded, this, &SignalView::handleSignalAdded);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalView::handleSignalUpdated);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalAdded, this, &SignalView::handleSignalAdded);
QObject::connect(dbcNotifier(), &QtDBCNotifier::signalUpdated, this, &SignalView::handleSignalUpdated);
QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); });
QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); });
QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState);
@@ -1,4 +1,5 @@
#include "tools/cabana/streams/abstractstream.h"
#include "tools/cabana/dbc/dbcqt.h"
#include <limits>
#include <utility>
@@ -18,8 +19,8 @@ AbstractStream::AbstractStream(QObject *parent) : QObject(parent) {
QObject::connect(this, &AbstractStream::privateUpdateLastMsgsSignal, this, &AbstractStream::updateLastMessages, Qt::QueuedConnection);
QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo);
QObject::connect(this, &AbstractStream::seeking, this, [this](double sec) { current_sec_ = sec; });
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &AbstractStream::updateMasks);
QObject::connect(dbc(), &DBCManager::maskUpdated, this, &AbstractStream::updateMasks);
QObject::connect(dbcNotifier(), &QtDBCNotifier::DBCFileChanged, this, &AbstractStream::updateMasks);
QObject::connect(dbcNotifier(), &QtDBCNotifier::maskUpdated, this, &AbstractStream::updateMasks);
}
void AbstractStream::updateMasks() {
@@ -233,18 +234,18 @@ std::pair<CanEventIter, CanEventIter> AbstractStream::eventsInRange(const Messag
namespace {
enum Color { GREYISH_BLUE, CYAN, RED};
QColor getColor(int c) {
CabanaColor getColor(int c) {
constexpr int start_alpha = 128;
static const QColor colors[] = {
[GREYISH_BLUE] = QColor(102, 86, 169, start_alpha / 2),
[CYAN] = QColor(0, 187, 255, start_alpha),
[RED] = QColor(255, 0, 0, start_alpha),
static const CabanaColor colors[] = {
[GREYISH_BLUE] = CabanaColor(102, 86, 169, start_alpha / 2),
[CYAN] = CabanaColor(0, 187, 255, start_alpha),
[RED] = CabanaColor(255, 0, 0, start_alpha),
};
return settings.theme == LIGHT_THEME ? colors[c] : colors[c].lighter(135);
}
inline QColor blend(const QColor &a, const QColor &b) {
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
inline CabanaColor blend(const CabanaColor &a, const CabanaColor &b) {
return CabanaColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
}
// Calculate the frequency from the past one minute data
@@ -271,7 +272,7 @@ void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const in
if (dat.size() != size) {
dat.assign(can_data, can_data + size);
colors.assign(size, QColor(0, 0, 0, 0));
colors.assign(size, CabanaColor(0, 0, 0, 0));
last_changes.resize(size);
bit_flip_counts.resize(size);
std::for_each(last_changes.begin(), last_changes.end(), [current_sec](auto &c) { c.ts = current_sec; });
@@ -317,7 +318,7 @@ void CanData::compute(const MessageId &msg_id, const uint8_t *can_data, const in
last_change.delta = delta;
} else {
// Fade out
colors[i].setAlphaF(std::max(0.0, colors[i].alphaF() - alpha_delta));
colors[i].setAlphaF(std::max(0.0f, colors[i].alphaF() - alpha_delta));
}
}
}
@@ -3,6 +3,7 @@
#include <algorithm>
#include <array>
#include <condition_variable>
#include <chrono>
#include <memory>
#include <mutex>
#include <optional>
@@ -11,51 +12,12 @@
#include <utility>
#include <vector>
#include <QColor>
#include <QDateTime>
#include "openpilot/cereal/messaging/messaging.h"
#include "tools/cabana/core/can_data.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/utils/util.h"
#include "tools/replay/util.h"
struct CanData {
void compute(const MessageId &msg_id, const uint8_t *dat, const int size, double current_sec,
double playback_speed, const std::vector<uint8_t> &mask, double in_freq = 0);
double ts = 0.;
uint32_t count = 0;
double freq = 0;
std::vector<uint8_t> dat;
std::vector<QColor> colors;
struct ByteLastChange {
double ts = 0;
int delta = 0;
int same_delta_counter = 0;
bool suppressed = false;
};
std::vector<ByteLastChange> last_changes;
std::vector<std::array<uint32_t, 8>> bit_flip_counts;
double last_freq_update_ts = 0;
};
struct CanEvent {
uint8_t src;
uint32_t address;
uint64_t mono_time;
uint8_t size;
uint8_t dat[];
};
struct CompareCanEvent {
constexpr bool operator()(const CanEvent *const e, uint64_t ts) const { return e->mono_time < ts; }
constexpr bool operator()(uint64_t ts, const CanEvent *const e) const { return ts < e->mono_time; }
};
typedef std::unordered_map<MessageId, std::vector<const CanEvent *>> MessageEventsMap;
using CanEventIter = std::vector<const CanEvent *>::const_iterator;
class AbstractStream : public QObject {
Q_OBJECT
@@ -67,7 +29,7 @@ public:
virtual void seekTo(double ts) {}
virtual std::string routeName() const = 0;
virtual std::string carFingerprint() const { return ""; }
virtual QDateTime beginDateTime() const { return {}; }
virtual std::chrono::system_clock::time_point beginDateTime() const { return {}; }
virtual uint64_t beginMonoTime() const { return 0; }
virtual double minSeconds() const { return 0; }
virtual double maxSeconds() const { return 0; }
+10 -3
View File
@@ -3,7 +3,9 @@
#include <QThread>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <memory>
#include <sstream>
#include "common/timing.h"
#include "common/util.h"
@@ -14,9 +16,14 @@ struct LiveStream::Logger {
void write(kj::ArrayPtr<capnp::word> data) {
int n = (seconds_since_epoch() - start_ts) / 60.0;
if (std::exchange(segment_num, n) != segment_num) {
const time_t start_time = start_ts;
std::tm local_time = {};
localtime_r(&start_time, &local_time);
std::ostringstream date;
date << std::put_time(&local_time, "%Y-%m-%d--%H-%M-%S");
QString dir = QString("%1/%2--%3")
.arg(settings.log_path)
.arg(QDateTime::fromSecsSinceEpoch(start_ts).toString("yyyy-MM-dd--hh-mm-ss"))
.arg(QString::fromStdString(settings.log_path))
.arg(QString::fromStdString(date.str()))
.arg(n);
util::create_directories(dir.toStdString(), 0755);
fs.reset(new std::ofstream((dir + "/rlog").toStdString(), std::ios::binary | std::ios::out));
@@ -55,7 +62,7 @@ void LiveStream::startUpdateTimer() {
void LiveStream::start() {
stream_thread->start();
startUpdateTimer();
begin_date_time = QDateTime::currentDateTime();
begin_date_time = std::chrono::system_clock::now();
}
void LiveStream::stop() {
+2 -2
View File
@@ -16,7 +16,7 @@ public:
virtual ~LiveStream();
void start() override;
void stop();
inline QDateTime beginDateTime() const { return begin_date_time; }
inline std::chrono::system_clock::time_point beginDateTime() const override { return begin_date_time; }
inline uint64_t beginMonoTime() const override { return begin_event_ts; }
double maxSeconds() const override { return std::max(1.0, (lastest_event_ts - begin_event_ts) / 1e9); }
void setSpeed(float speed) override { speed_ = speed; }
@@ -41,7 +41,7 @@ private:
int timer_id;
QBasicTimer update_timer;
QDateTime begin_date_time;
std::chrono::system_clock::time_point begin_date_time;
uint64_t begin_event_ts = 0;
uint64_t lastest_event_ts = 0;
uint64_t current_event_ts = 0;
@@ -136,10 +136,10 @@ OpenReplayWidget::OpenReplayWidget(QWidget *parent) : AbstractOpenStreamWidget(p
setMinimumWidth(550);
QObject::connect(browse_local_btn, &QPushButton::clicked, [=]() {
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), QString::fromStdString(settings.last_route_dir));
if (!dir.isEmpty()) {
route_edit->setText(dir);
settings.last_route_dir = QFileInfo(dir).absolutePath();
settings.last_route_dir = QFileInfo(dir).absolutePath().toStdString();
}
});
QObject::connect(browse_remote_btn, &QPushButton::clicked, [this]() {
@@ -26,7 +26,9 @@ public:
inline std::string carFingerprint() const override { return replay->carFingerprint(); }
double minSeconds() const override { return replay->minSeconds(); }
double maxSeconds() const { return replay->maxSeconds(); }
inline QDateTime beginDateTime() const { return QDateTime::fromSecsSinceEpoch(replay->routeDateTime()); }
inline std::chrono::system_clock::time_point beginDateTime() const override {
return std::chrono::system_clock::from_time_t(replay->routeDateTime());
}
inline uint64_t beginMonoTime() const override { return replay->routeStartNanos(); }
inline void setSpeed(float speed) override { replay->setSpeed(speed); }
inline float getSpeed() const { return replay->getSpeed(); }
+2 -2
View File
@@ -52,10 +52,10 @@ StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) {
setEnabled(true);
});
QObject::connect(file_btn, &QPushButton::clicked, [this]() {
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), QString::fromStdString(settings.last_dir), "DBC (*.dbc)");
if (!fn.isEmpty()) {
dbc_file->setText(fn);
settings.last_dir = QFileInfo(fn).absolutePath();
settings.last_dir = QFileInfo(fn).absolutePath().toStdString();
}
});
}
+74 -6
View File
@@ -1,9 +1,16 @@
#undef INFO
#include <QDir>
#include <filesystem>
#include <sstream>
#include "catch2/catch.hpp"
#include "tools/cabana/dbc/dbcfile.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/core/settings.h"
#ifdef QT_CORE_LIB
#include <QColor>
#endif
const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2";
@@ -143,15 +150,76 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\"";
}
TEST_CASE("parse_opendbc") {
QDir dir(OPENDBC_FILE_PATH);
QStringList errors;
for (auto fn : dir.entryList({"*.dbc"}, QDir::Files, QDir::Name)) {
std::vector<std::string> errors;
for (const auto &entry : std::filesystem::directory_iterator(OPENDBC_FILE_PATH)) {
if (!entry.is_regular_file() || entry.path().extension() != ".dbc") continue;
try {
auto dbc = DBCFile(dir.filePath(fn).toStdString());
auto dbc = DBCFile(entry.path().string());
} catch (std::exception &e) {
errors.push_back(e.what());
}
}
INFO(errors.join("\n").toStdString());
std::ostringstream details;
for (const auto &error : errors) details << error << '\n';
INFO(details.str());
REQUIRE(errors.empty());
}
TEST_CASE("DBCManager core callbacks") {
DBCManager manager;
int files_changed = 0;
int signals_added = 0;
int masks_updated = 0;
manager.setCallbacks({
.signal_added = [&](MessageId, const cabana::Signal *) { ++signals_added; },
.file_changed = [&]() { ++files_changed; },
.mask_updated = [&]() { ++masks_updated; },
});
std::string error;
REQUIRE(manager.open(SOURCE_ALL, "test", "BO_ 160 message: 8 XXX\n", &error));
REQUIRE(error.empty());
REQUIRE(files_changed == 1);
cabana::Signal signal{};
signal.name = "speed";
signal.start_bit = 0;
signal.size = 8;
signal.is_little_endian = true;
manager.addSignal({.source = 0, .address = 160}, signal);
REQUIRE(signals_added == 1);
REQUIRE(masks_updated == 1);
REQUIRE(manager.msg({.source = 0, .address = 160})->sig("speed") != nullptr);
}
TEST_CASE("Cabana settings core defaults") {
CabanaSettingsState state;
REQUIRE(state.fps == 10);
REQUIRE(state.chart_range == 180);
REQUIRE(state.drag_direction == CabanaSettingsState::MsbFirst);
REQUIRE(state.recent_files.empty());
}
#ifdef QT_CORE_LIB
TEST_CASE("CabanaColor preserves QColor transformations") {
const std::vector<QColor> colors = {
QColor(102, 86, 169, 64), QColor(0, 187, 255, 128), QColor(255, 0, 0, 128), QColor(45, 120, 75, 255),
};
for (const auto &qt_color : colors) {
CabanaColor color(qt_color.red(), qt_color.green(), qt_color.blue(), qt_color.alpha());
for (int factor : {75, 100, 135, 150, 200}) {
const auto lighter = color.lighter(factor);
const auto qt_lighter = qt_color.lighter(factor);
CHECK(std::abs(lighter.red() - qt_lighter.red()) <= 1);
CHECK(std::abs(lighter.green() - qt_lighter.green()) <= 1);
CHECK(std::abs(lighter.blue() - qt_lighter.blue()) <= 1);
const auto darker = color.darker(factor);
const auto qt_darker = qt_color.darker(factor);
CHECK(std::abs(darker.red() - qt_darker.red()) <= 1);
CHECK(std::abs(darker.green() - qt_darker.green()) <= 1);
CHECK(std::abs(darker.blue() - qt_darker.blue()) <= 1);
}
}
}
#endif
@@ -1,10 +1,7 @@
#define CATCH_CONFIG_RUNNER
#include "catch2/catch.hpp"
#include <QCoreApplication>
int main(int argc, char **argv) {
// unit tests for Qt
QCoreApplication app(argc, argv);
const int res = Catch::Session().run(argc, argv);
return (res < 0xff ? res : 0xff);
}
+16 -16
View File
@@ -1,41 +1,41 @@
#include "tools/cabana/utils/export.h"
#include <QFile>
#include <QTextStream>
#include <fstream>
#include <iomanip>
#include "tools/cabana/streams/abstractstream.h"
namespace utils {
void exportToCSV(const QString &file_name, std::optional<MessageId> msg_id) {
QFile file(file_name);
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
QTextStream stream(&file);
void exportToCSV(const std::string &file_name, std::optional<MessageId> msg_id) {
std::ofstream stream(file_name, std::ios::trunc);
if (stream) {
stream << "time,addr,bus,data\n";
for (auto e : msg_id ? can->events(*msg_id) : can->allEvents()) {
stream << QString::number(can->toSeconds(e->mono_time), 'f', 3) << ","
<< "0x" << QString::number(e->address, 16) << "," << e->src << ","
<< "0x" << QByteArray::fromRawData((const char *)e->dat, e->size).toHex().toUpper() << "\n";
stream << std::fixed << std::setprecision(3) << can->toSeconds(e->mono_time) << ","
<< "0x" << std::hex << e->address << std::dec << "," << static_cast<int>(e->src) << ",0x"
<< std::uppercase << std::hex << std::setfill('0');
for (int i = 0; i < e->size; ++i) stream << std::setw(2) << static_cast<int>(e->dat[i]);
stream << std::nouppercase << std::dec << "\n";
}
}
}
void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) {
QFile file(file_name);
if (auto msg = dbc()->msg(msg_id); msg && msg->sigs.size() && file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
QTextStream stream(&file);
void exportSignalsToCSV(const std::string &file_name, const MessageId &msg_id) {
std::ofstream stream(file_name, std::ios::trunc);
if (auto msg = dbc()->msg(msg_id); msg && !msg->sigs.empty() && stream) {
stream << "time,addr,bus";
for (auto s : msg->sigs)
stream << "," << s->name.c_str();
stream << "\n";
for (auto e : can->events(msg_id)) {
stream << QString::number(can->toSeconds(e->mono_time), 'f', 3) << ","
<< "0x" << QString::number(e->address, 16) << "," << e->src;
stream << std::fixed << std::setprecision(3) << can->toSeconds(e->mono_time) << ","
<< "0x" << std::hex << e->address << std::dec << "," << static_cast<int>(e->src);
for (auto s : msg->sigs) {
double value = 0;
s->getValue(e->dat, e->size, &value);
stream << "," << QString::number(value, 'f', s->precision);
stream << "," << std::fixed << std::setprecision(s->precision) << value;
}
stream << "\n";
}
+3 -2
View File
@@ -1,10 +1,11 @@
#pragma once
#include <optional>
#include <string>
#include "tools/cabana/dbc/dbcmanager.h"
namespace utils {
void exportToCSV(const QString &file_name, std::optional<MessageId> msg_id = std::nullopt);
void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id);
void exportToCSV(const std::string &file_name, std::optional<MessageId> msg_id = std::nullopt);
void exportSignalsToCSV(const std::string &file_name, const MessageId &msg_id);
} // namespace utils
+2 -2
View File
@@ -100,7 +100,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
// Paint hex column
const auto &bytes = *static_cast<std::vector<uint8_t> *>(data.value<void *>());
const auto &colors = *static_cast<std::vector<QColor> *>(index.data(ColorsRole).value<void *>());
const auto &colors = *static_cast<std::vector<CabanaColor> *>(index.data(ColorsRole).value<void *>());
painter->setFont(fixed_font);
const QPen text_pen(option.state & QStyle::State_Selected ? highlighted_color : text_color);
@@ -115,7 +115,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
painter->setPen(option.palette.color(QPalette::Text));
painter->fillRect(r, option.palette.color(QPalette::Window));
}
painter->fillRect(r, colors[i]);
painter->fillRect(r, toQColor(colors[i]));
} else {
painter->setPen(text_pen);
}
+5
View File
@@ -7,6 +7,7 @@
#include <QApplication>
#include <QByteArray>
#include <QColor>
#include <QDoubleValidator>
#include <QFont>
#include <QFontMetrics>
@@ -21,6 +22,10 @@
#include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/settings.h"
inline QColor toQColor(const CabanaColor &color) {
return QColor(color.r, color.g, color.b, color.a);
}
class LogSlider : public QSlider {
Q_OBJECT
+1 -1
View File
@@ -203,7 +203,7 @@ void VideoWidget::timeRangeChanged() {
QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
if (settings.absolute_time)
sec = can->beginDateTime().addMSecs(sec * 1000).toMSecsSinceEpoch() / 1000.0;
sec += std::chrono::duration<double>(can->beginDateTime().time_since_epoch()).count();
return utils::formatSeconds(sec, include_milliseconds, settings.absolute_time);
}