mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-04 12:23:43 +08:00
cabana: string helpers in utils return std::string (#38720)
This commit is contained in:
@@ -100,7 +100,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, "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',
|
||||
'routesdialog.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
|
||||
'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc',
|
||||
'utils/export.cc', 'utils/util.cc', 'utils/strings.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',
|
||||
'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc']
|
||||
@@ -119,6 +119,7 @@ if GetOption('extras'):
|
||||
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.Object('tests/dbc_core_strings', 'utils/strings.cc'),
|
||||
]
|
||||
dbc_core_test_env.Program('tests/test_dbc_core', dbc_core_test_objects)
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, i
|
||||
|
||||
QVariant BinaryViewModel::data(const QModelIndex &index, int role) const {
|
||||
auto item = (const BinaryViewModel::Item *)index.internalPointer();
|
||||
return role == Qt::ToolTipRole && item && !item->sigs.empty() ? signalToolTip(item->sigs.back()) : QVariant();
|
||||
return role == Qt::ToolTipRole && item && !item->sigs.empty() ? QString::fromStdString(utils::signalToolTip(item->sigs.back())) : QVariant();
|
||||
}
|
||||
|
||||
// BinaryItemDelegate
|
||||
|
||||
@@ -245,7 +245,7 @@ void ChartsWidget::setIsDocked(bool docked) {
|
||||
void ChartsWidget::updateToolBar() {
|
||||
title_label->setText(tr("Charts: %1").arg(charts.size()));
|
||||
columns_action->setText(tr("Columns: %1").arg(column_count));
|
||||
range_lb->setText(utils::formatSeconds(max_chart_range));
|
||||
range_lb->setText(QString::fromStdString(utils::formatSeconds(max_chart_range)));
|
||||
|
||||
bool is_zoomed = can->timeRange().has_value();
|
||||
range_lb_action->setVisible(!is_zoomed);
|
||||
|
||||
@@ -195,7 +195,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
switch (index.column()) {
|
||||
case Column::NAME: return item.name;
|
||||
case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : NA;
|
||||
case Column::ADDRESS: return toHexString(item.id.address);
|
||||
case Column::ADDRESS: return QString::fromStdString(utils::toHexString(item.id.address));
|
||||
case Column::NODE: return item.node;
|
||||
case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(can->lastMessage(item.id).freq) : NA;
|
||||
case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(can->lastMessage(item.id).count) : NA;
|
||||
@@ -288,7 +288,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) {
|
||||
match = parseRange(txt, item.id.source);
|
||||
break;
|
||||
case Column::ADDRESS:
|
||||
match = toHexString(item.id.address).contains(txt, Qt::CaseInsensitive);
|
||||
match = QString::fromStdString(utils::toHexString(item.id.address)).contains(txt, Qt::CaseInsensitive);
|
||||
match = match || parseRange(txt, item.id.address, 16);
|
||||
break;
|
||||
case Column::NODE:
|
||||
@@ -301,7 +301,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) {
|
||||
match = parseRange(txt, data.count);
|
||||
break;
|
||||
case Column::DATA:
|
||||
match = utils::toHex(data.dat).contains(txt, Qt::CaseInsensitive);
|
||||
match = QString::fromStdString(utils::toHex(data.dat)).contains(txt, Qt::CaseInsensitive);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
|
||||
if (item->type == Item::Endian) return item->sig->is_little_endian ? Qt::Checked : Qt::Unchecked;
|
||||
if (item->type == Item::Signed) return item->sig->is_signed ? Qt::Checked : Qt::Unchecked;
|
||||
} else if (role == Qt::ToolTipRole && item->type == Item::Sig) {
|
||||
return (index.column() == 0) ? signalToolTip(item->sig) : QString();
|
||||
return (index.column() == 0) ? QString::fromStdString(utils::signalToolTip(item->sig)) : QString();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
@@ -570,7 +570,7 @@ void SignalView::signalHovered(const cabana::Signal *sig) {
|
||||
|
||||
void SignalView::updateToolBar() {
|
||||
signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount()));
|
||||
sparkline_label->setText(utils::formatSeconds(settings.sparkline_range));
|
||||
sparkline_label->setText(QString::fromStdString(utils::formatSeconds(settings.sparkline_range)));
|
||||
}
|
||||
|
||||
void SignalView::setSparklineRange(int value) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
|
||||
#include "common/tests/native_test.h"
|
||||
#include "tools/cabana/dbc/dbcfile.h"
|
||||
#include "tools/cabana/dbc/dbcmanager.h"
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
|
||||
const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2";
|
||||
|
||||
@@ -185,7 +188,62 @@ void test_dbc_manager() {
|
||||
REQUIRE(manager.msg({.source = 0, .address = 160})->sig("speed") != nullptr);
|
||||
}
|
||||
|
||||
void test_format_seconds() {
|
||||
REQUIRE(utils::formatSeconds(0) == "00:00");
|
||||
REQUIRE(utils::formatSeconds(59.4) == "00:59");
|
||||
REQUIRE(utils::formatSeconds(-1) == "00:00");
|
||||
REQUIRE(utils::formatSeconds(61.234, true) == "01:01.234");
|
||||
REQUIRE(utils::formatSeconds(3599.9) == "59:59");
|
||||
REQUIRE(utils::formatSeconds(3601) == "01:00:01");
|
||||
REQUIRE(utils::formatSeconds(3601.5, true) == "01:00:01.500");
|
||||
|
||||
const char *tz = getenv("TZ");
|
||||
const bool had_tz = tz != nullptr;
|
||||
const std::string saved_tz = had_tz ? tz : "";
|
||||
setenv("TZ", "UTC", 1);
|
||||
tzset();
|
||||
REQUIRE(utils::formatSeconds(0, false, true) == "1970-01-01 00:00:00");
|
||||
REQUIRE(utils::formatSeconds(1700000000.123, true, true) == "2023-11-14 22:13:20.123");
|
||||
if (had_tz) {
|
||||
setenv("TZ", saved_tz.c_str(), 1);
|
||||
} else {
|
||||
unsetenv("TZ");
|
||||
}
|
||||
tzset();
|
||||
}
|
||||
|
||||
void test_to_hex() {
|
||||
REQUIRE(utils::toHex({}) == "");
|
||||
REQUIRE(utils::toHex({0x00, 0x0f, 0xab, 0xff}) == "000FABFF");
|
||||
REQUIRE(utils::toHex({0x01, 0x02, 0x03}, ' ') == "01 02 03");
|
||||
|
||||
REQUIRE(utils::toHexString(0) == "0x00");
|
||||
REQUIRE(utils::toHexString(0xf) == "0x0F");
|
||||
REQUIRE(utils::toHexString(0x1ab) == "0x1AB");
|
||||
REQUIRE(utils::toHexString(0x1fffffff) == "0x1FFFFFFF");
|
||||
}
|
||||
|
||||
void test_signal_tooltip() {
|
||||
cabana::Signal sig{};
|
||||
sig.name = "speed";
|
||||
sig.start_bit = 3;
|
||||
sig.size = 12;
|
||||
sig.msb = 14;
|
||||
sig.lsb = 3;
|
||||
sig.is_little_endian = true;
|
||||
sig.is_signed = false;
|
||||
REQUIRE(utils::signalToolTip(&sig) == R"(
|
||||
speed<br /><span font-size:small">
|
||||
Start Bit: 3 Size: 12<br />
|
||||
MSB: 14 LSB: 3<br />
|
||||
Little Endian: Y Signed: N</span>
|
||||
)");
|
||||
}
|
||||
|
||||
void test_cabana_core() {
|
||||
test_format_seconds();
|
||||
test_to_hex();
|
||||
test_signal_tooltip();
|
||||
test_generate_dbc();
|
||||
test_comment_order();
|
||||
test_preserve_original_header();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
#include "tools/cabana/dbc/dbc.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
std::string formatSeconds(double sec, bool include_milliseconds, bool absolute_time) {
|
||||
char out[80] = {};
|
||||
if (absolute_time) {
|
||||
const auto ms_total = static_cast<int64_t>(std::llround(sec * 1000.0));
|
||||
const std::time_t secs = static_cast<std::time_t>(ms_total / 1000);
|
||||
int millis = static_cast<int>(ms_total % 1000);
|
||||
if (millis < 0) millis = -millis;
|
||||
std::tm tm{};
|
||||
localtime_r(&secs, &tm);
|
||||
char buf[64] = {};
|
||||
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
|
||||
if (!include_milliseconds) return buf;
|
||||
snprintf(out, sizeof(out), "%s.%03d", buf, millis);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Relative duration (not wall-clock).
|
||||
const bool show_hours = sec > 60 * 60;
|
||||
int total_ms = static_cast<int>(std::llround(std::max(0.0, sec) * 1000.0));
|
||||
const int hours = total_ms / (3600 * 1000);
|
||||
const int minutes = (total_ms / (60 * 1000)) % 60;
|
||||
const int seconds = (total_ms / 1000) % 60;
|
||||
const int millis = total_ms % 1000;
|
||||
if (show_hours && include_milliseconds) {
|
||||
snprintf(out, sizeof(out), "%02d:%02d:%02d.%03d", hours, minutes, seconds, millis);
|
||||
} else if (show_hours) {
|
||||
snprintf(out, sizeof(out), "%02d:%02d:%02d", hours, minutes, seconds);
|
||||
} else if (include_milliseconds) {
|
||||
snprintf(out, sizeof(out), "%02d:%02d.%03d", minutes, seconds, millis);
|
||||
} else {
|
||||
snprintf(out, sizeof(out), "%02d:%02d", minutes, seconds);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string signalToolTip(const cabana::Signal *sig) {
|
||||
std::ostringstream s;
|
||||
s << "\n " << sig->name << "<br /><span font-size:small\">\n"
|
||||
<< " Start Bit: " << sig->start_bit << " Size: " << sig->size << "<br />\n"
|
||||
<< " MSB: " << sig->msb << " LSB: " << sig->lsb << "<br />\n"
|
||||
<< " Little Endian: " << (sig->is_little_endian ? "Y" : "N")
|
||||
<< " Signed: " << (sig->is_signed ? "Y" : "N") << "</span>\n ";
|
||||
return s.str();
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cabana { class Signal; }
|
||||
|
||||
namespace utils {
|
||||
|
||||
std::string formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false);
|
||||
std::string signalToolTip(const cabana::Signal *sig);
|
||||
|
||||
inline std::string toHex(const std::vector<uint8_t> &dat, char separator = '\0') {
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
std::string hex;
|
||||
hex.reserve(dat.size() * (separator ? 3 : 2));
|
||||
for (size_t i = 0; i < dat.size(); ++i) {
|
||||
if (separator && i) hex += separator;
|
||||
hex += digits[dat[i] >> 4];
|
||||
hex += digits[dat[i] & 0xf];
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
inline std::string toHexString(int value) {
|
||||
char buf[16] = {};
|
||||
snprintf(buf, sizeof(buf), "0x%02X", value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <csignal>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
@@ -442,37 +441,6 @@ void setTheme(int theme) {
|
||||
}
|
||||
}
|
||||
|
||||
QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time) {
|
||||
if (absolute_time) {
|
||||
const auto ms_total = static_cast<int64_t>(std::llround(sec * 1000.0));
|
||||
const std::time_t secs = static_cast<std::time_t>(ms_total / 1000);
|
||||
int millis = static_cast<int>(ms_total % 1000);
|
||||
if (millis < 0) millis = -millis;
|
||||
std::tm tm{};
|
||||
localtime_r(&secs, &tm);
|
||||
char buf[64];
|
||||
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
|
||||
if (include_milliseconds) {
|
||||
return QString::asprintf("%s.%03d", buf, millis);
|
||||
}
|
||||
return QString::fromUtf8(buf);
|
||||
}
|
||||
|
||||
// Relative duration (not wall-clock).
|
||||
const bool show_hours = sec > 60 * 60;
|
||||
int total_ms = static_cast<int>(std::llround(std::max(0.0, sec) * 1000.0));
|
||||
const int hours = total_ms / (3600 * 1000);
|
||||
const int minutes = (total_ms / (60 * 1000)) % 60;
|
||||
const int seconds = (total_ms / 1000) % 60;
|
||||
const int millis = total_ms % 1000;
|
||||
if (show_hours) {
|
||||
return include_milliseconds ? QString::asprintf("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis)
|
||||
: QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
return include_milliseconds ? QString::asprintf("%02d:%02d.%03d", minutes, seconds, millis)
|
||||
: QString::asprintf("%02d:%02d", minutes, seconds);
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
||||
int num_decimals(double num) {
|
||||
@@ -481,16 +449,6 @@ int num_decimals(double num) {
|
||||
return dot_pos == -1 ? 0 : string.size() - dot_pos - 1;
|
||||
}
|
||||
|
||||
QString signalToolTip(const cabana::Signal *sig) {
|
||||
return QObject::tr(R"(
|
||||
%1<br /><span font-size:small">
|
||||
Start Bit: %2 Size: %3<br />
|
||||
MSB: %4 LSB: %5<br />
|
||||
Little Endian: %6 Signed: %7</span>
|
||||
)").arg(QString::fromStdString(sig->name)).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb)
|
||||
.arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N");
|
||||
}
|
||||
|
||||
void sigTermHandler(int s) {
|
||||
std::signal(s, SIG_DFL);
|
||||
qApp->quit();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "tools/cabana/core/observable.h"
|
||||
#include "tools/cabana/dbc/dbc.h"
|
||||
#include "tools/cabana/settings.h"
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
|
||||
// needed by QVariant::fromValue() in the Qt views; goes away with QVariant
|
||||
Q_DECLARE_METATYPE(MessageId)
|
||||
@@ -149,22 +150,10 @@ bool getClipboardText(std::string *text); // false if no clipboard tool is avai
|
||||
bool setClipboardText(const std::string &text);
|
||||
bool isDarkTheme();
|
||||
void setTheme(int theme);
|
||||
QString formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false);
|
||||
inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) {
|
||||
auto size = (r.size() - text.size()) / 2;
|
||||
p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text);
|
||||
}
|
||||
inline QString toHex(const std::vector<uint8_t> &dat, char separator = '\0') {
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
QString hex;
|
||||
hex.reserve(dat.size() * (separator ? 3 : 2));
|
||||
for (size_t i = 0; i < dat.size(); ++i) {
|
||||
if (separator && i) hex += QLatin1Char(separator);
|
||||
hex += QLatin1Char(digits[dat[i] >> 4]);
|
||||
hex += QLatin1Char(digits[dat[i] & 0xf]);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
// boundary conversions for the remaining Qt byte-array based state APIs
|
||||
template <typename T>
|
||||
@@ -225,7 +214,5 @@ private:
|
||||
};
|
||||
|
||||
int num_decimals(double num);
|
||||
QString signalToolTip(const cabana::Signal *sig);
|
||||
inline QString toHexString(int value) { return QString("0x%1").arg(QString::number(value, 16).toUpper(), 2, '0'); }
|
||||
void initApp(int argc, char *argv[], bool disable_hidpi = true);
|
||||
QPixmap bootstrapPixmap(const QString &id);
|
||||
|
||||
@@ -204,7 +204,7 @@ void VideoWidget::timeRangeChanged() {
|
||||
QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
|
||||
if (settings.absolute_time)
|
||||
sec += std::chrono::duration<double>(can->beginDateTime().time_since_epoch()).count();
|
||||
return utils::formatSeconds(sec, include_milliseconds, settings.absolute_time);
|
||||
return QString::fromStdString(utils::formatSeconds(sec, include_milliseconds, settings.absolute_time));
|
||||
}
|
||||
|
||||
void VideoWidget::updateState() {
|
||||
|
||||
Reference in New Issue
Block a user