From 805f33b3d644aebba028e21b5d57bb32fcc75cbe Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 11 Jul 2026 16:54:18 -0700 Subject: [PATCH 1/5] Radial Gauges for the Vehicle page. --- src/app/pages/vehicle.cpp | 204 +++++++++++++++++++++++++++++++++++--- 1 file changed, 189 insertions(+), 15 deletions(-) diff --git a/src/app/pages/vehicle.cpp b/src/app/pages/vehicle.cpp index 1193568e..fa2d5217 100644 --- a/src/app/pages/vehicle.cpp +++ b/src/app/pages/vehicle.cpp @@ -1,6 +1,11 @@ #include +#include +#include #include +#include +#include + #include "app/config.hpp" #include "app/pages/vehicle.hpp" #include "app/window.hpp" @@ -8,6 +13,163 @@ #include "canbus/elm327.hpp" #include "plugins/vehicle_plugin.hpp" +namespace { + +class RadialGaugeLabel : public QLabel +{ +public: + explicit RadialGaugeLabel(bool large_gauge, QWidget *parent = nullptr) + : QLabel(parent) + , large_gauge(large_gauge) + { + this->setAlignment(Qt::AlignCenter); + this->setAutoFillBackground(false); + this->setAttribute(Qt::WA_TranslucentBackground); + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + } + + QSize sizeHint() const override + { + return this->large_gauge ? QSize(190, 190) : QSize(120, 120); + } + + QSize minimumSizeHint() const override + { + return this->large_gauge ? QSize(150, 150) : QSize(100, 100); + } + + void set_gauge_fonts(const QFont &value_font, const QFont &unit_font) + { + this->value_font = value_font; + this->unit_font = unit_font; + this->update(); + } + + void set_gauge_unit(const QString &unit) + { + this->unit = unit; + this->update(); + } + + void set_gauge_value(const QString &text, double value, bool valid) + { + this->value_text = text; + this->value = value; + this->valid = valid; + this->update(); + } + +protected: + void paintEvent(QPaintEvent *) override + { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + const qreal side = std::max(20.0, std::min(this->width(), this->height()) - 8.0); + const QPointF center(this->width() / 2.0, this->height() / 2.0); + const qreal ring_width = std::max(5.0, side * (this->large_gauge ? 0.045 : 0.055)); + const QRectF ring_rect(center.x() - side / 2.0 + ring_width, + center.y() - side / 2.0 + ring_width, + side - ring_width * 2.0, + side - ring_width * 2.0); + const QRectF face_rect(center.x() - side / 2.0 + ring_width / 2.0, + center.y() - side / 2.0 + ring_width / 2.0, + side - ring_width, + side - ring_width); + + const QColor face(16, 16, 16); + const QColor outer_ring(95, 95, 95); + const QColor green(0, 210, 32); + const QColor red(255, 35, 35); + + painter.setPen(Qt::NoPen); + painter.setBrush(face); + painter.drawEllipse(face_rect); + + constexpr qreal start_angle = 225.0; + constexpr qreal span_angle = -270.0; + + QPen outer_pen(outer_ring, ring_width, Qt::SolidLine, Qt::RoundCap); + painter.setPen(outer_pen); + painter.setBrush(Qt::NoBrush); + painter.drawArc(ring_rect, static_cast(start_angle * 16.0), static_cast(span_angle * 16.0)); + + // Draw the warning zone at the high end of the gauge. + QPen warning_pen(red, ring_width * 0.82, Qt::SolidLine, Qt::RoundCap); + painter.setPen(warning_pen); + painter.drawArc(ring_rect, + static_cast((start_angle + span_angle * 0.84) * 16.0), + static_cast((span_angle * 0.16) * 16.0)); + + const double max_value = this->max_for_unit(this->unit); + double fraction = this->valid ? (this->value / max_value) : 0.0; + fraction = std::max(0.0, std::min(1.0, fraction)); + const int segments = this->large_gauge ? 28 : 22; + const int lit_segments = static_cast(std::round(fraction * segments)); + const qreal segment_span = span_angle / segments; + + QPen progress_pen(green, ring_width * 0.82, Qt::SolidLine, Qt::FlatCap); + painter.setPen(progress_pen); + for (int i = 0; i < lit_segments; ++i) { + const qreal segment_start = start_angle + segment_span * i; + painter.drawArc(ring_rect, + static_cast(segment_start * 16.0), + static_cast((segment_span * 0.62) * 16.0)); + } + + QFont display_value_font = this->value_font; + if (display_value_font.family().isEmpty()) + display_value_font = this->font(); + + QFont display_unit_font = this->unit_font; + if (display_unit_font.family().isEmpty()) + display_unit_font = this->font(); + + painter.setPen(green); + painter.setFont(display_value_font); + QRectF value_rect(center.x() - side * 0.35, + center.y() - side * 0.18, + side * 0.70, + side * 0.28); + painter.drawText(value_rect, Qt::AlignCenter, this->value_text); + + painter.setFont(display_unit_font); + QRectF unit_rect(center.x() - side * 0.35, + center.y() + side * 0.08, + side * 0.70, + side * 0.18); + painter.drawText(unit_rect, Qt::AlignCenter, this->unit); + } + +private: + double max_for_unit(const QString &unit) const + { + if (unit == QStringLiteral("km/h")) + return 220.0; + if (unit == QStringLiteral("mph")) + return 140.0; + if (unit.contains(QStringLiteral("rpm"), Qt::CaseInsensitive)) + return 8.0; + if (unit == QStringLiteral("%")) + return 100.0; + if (unit == QStringLiteral("°C")) + return 120.0; + if (unit == QStringLiteral("°F")) + return 250.0; + return 100.0; + } + + bool large_gauge; + bool valid = false; + double value = 0.0; + QString value_text = QStringLiteral("--"); + QString unit; + QFont value_font; + QFont unit_font; +}; + +} // namespace + Gauge::Gauge(units_t units, QFont value_font, QFont unit_font, Gauge::Orientation orientation, int rate, std::vector cmds, int precision, obd_decoder_t decoder, QWidget *parent) : QWidget(parent) @@ -49,14 +211,14 @@ Gauge::Gauge(units_t units, QFont value_font, QFont unit_font, Gauge::Orientatio layout = new QVBoxLayout(this); else layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); - value_label = new QLabel(this->null_value(), this); - value_label->setFont(value_font); - value_label->setAlignment(Qt::AlignCenter); - - QLabel *unit_label = new QLabel(this->si ? units.second : units.first, this); - unit_label->setFont(unit_font); - unit_label->setAlignment(Qt::AlignCenter); + value_label = new RadialGaugeLabel(orientation == BOTTOM, this); + auto radial_gauge = static_cast(value_label); + radial_gauge->set_gauge_fonts(value_font, unit_font); + radial_gauge->set_gauge_unit(this->si ? units.second : units.first); + radial_gauge->set_gauge_value(this->null_value(), 0.0, false); this->timer = new QTimer(this); connect(this->timer, &QTimer::timeout, [this, bus, cmds]() { @@ -65,24 +227,24 @@ Gauge::Gauge(units_t units, QFont value_font, QFont unit_font, Gauge::Orientatio } }); - connect(config, &Config::si_units_changed, [this, units, unit_label](bool si) { + connect(config, &Config::si_units_changed, [this, units](bool si) { this->si = si; - unit_label->setText(this->si ? units.second : units.first); - value_label->setText(this->null_value()); + auto radial_gauge = static_cast(value_label); + radial_gauge->set_gauge_unit(this->si ? units.second : units.first); + radial_gauge->set_gauge_value(this->null_value(), 0.0, false); }); - layout->addStretch(6); - layout->addWidget(value_label); layout->addStretch(1); - layout->addWidget(unit_label); - layout->addStretch(4); + layout->addWidget(value_label, 0, Qt::AlignCenter); + layout->addStretch(1); } void Gauge::can_callback(QByteArray payload){ Response resp = Response(payload); for(auto cmd : cmds){ if(cmd.frame.payload().at(2) == resp.PID){ - value_label->setText(this->format_value(this->decoder(cmd.decoder(resp), this->si))); + double value = this->decoder(cmd.decoder(resp), this->si); + static_cast(value_label)->set_gauge_value(this->format_value(value), value, true); } } } @@ -368,6 +530,13 @@ QWidget *DataTab::speedo_tach_widget() Gauge::BOTTOM, 100, {cmds.SPEED}, 0, [](double x, bool si) { return si ? x : kph_to_mph(x); }, widget); layout->addWidget(speed); + + QFont driving_label_font(this->arbiter.forge().font(14)); + driving_label_font.setWeight(QFont::Light); + QLabel *speed_label = new QLabel("Speed", widget); + speed_label->setFont(driving_label_font); + speed_label->setAlignment(Qt::AlignHCenter); + layout->addWidget(speed_label); this->gauges.push_back(speed); layout->addStretch(2); @@ -382,6 +551,11 @@ QWidget *DataTab::speedo_tach_widget() tach_unit_font, Gauge::BOTTOM, 100, {cmds.RPM}, 1, [](double x, bool _) { return x / 1000.0; }, widget); layout->addWidget(rpm); + + QLabel *rpm_label = new QLabel("RPM", widget); + rpm_label->setFont(driving_label_font); + rpm_label->setAlignment(Qt::AlignHCenter); + layout->addWidget(rpm_label); this->gauges.push_back(rpm); layout->addStretch(1); From d9c31390962e50fc06d0bd7165483256ad7b3b95 Mon Sep 17 00:00:00 2001 From: kg6wxc Date: Sat, 11 Jul 2026 17:23:40 -0700 Subject: [PATCH 2/5] Vehicle page into 2x3 grid. Added missing MAF and Intake Temp gauges. All gauges are slightly larger, and use bold text labels. Removed vertical parting line so the grid lines up. --- include/app/pages/vehicle.hpp | 6 +- src/app/pages/vehicle.cpp | 280 +++++++++++++++++++++------------- 2 files changed, 180 insertions(+), 106 deletions(-) diff --git a/include/app/pages/vehicle.hpp b/include/app/pages/vehicle.hpp index 0401dcc3..2e88655e 100644 --- a/include/app/pages/vehicle.hpp +++ b/include/app/pages/vehicle.hpp @@ -81,11 +81,13 @@ class DataTab : public QWidget { private: Arbiter &arbiter; - QWidget *speedo_tach_widget(); + QWidget *speed_widget(); + QWidget *rpm_widget(); // QWidget *mileage_data_widget(); - QWidget *engine_data_widget(); QWidget *coolant_temp_widget(); QWidget *engine_load_widget(); + QWidget *intake_temp_widget(); + QWidget *maf_widget(); std::vector gauges; }; diff --git a/src/app/pages/vehicle.cpp b/src/app/pages/vehicle.cpp index fa2d5217..648e3594 100644 --- a/src/app/pages/vehicle.cpp +++ b/src/app/pages/vehicle.cpp @@ -18,24 +18,28 @@ namespace { class RadialGaugeLabel : public QLabel { public: - explicit RadialGaugeLabel(bool large_gauge, QWidget *parent = nullptr) + explicit RadialGaugeLabel(QWidget *parent = nullptr) : QLabel(parent) - , large_gauge(large_gauge) { + // The previous RPM gauge requested a 190x190 size. A fixed 200x200 + // canvas makes every gauge identical and is approximately 5% larger. + constexpr int gauge_size = 200; + this->setAlignment(Qt::AlignCenter); this->setAutoFillBackground(false); this->setAttribute(Qt::WA_TranslucentBackground); - this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + this->setFixedSize(gauge_size, gauge_size); } QSize sizeHint() const override { - return this->large_gauge ? QSize(190, 190) : QSize(120, 120); + return QSize(200, 200); } QSize minimumSizeHint() const override { - return this->large_gauge ? QSize(150, 150) : QSize(100, 100); + return QSize(200, 200); } void set_gauge_fonts(const QFont &value_font, const QFont &unit_font) @@ -67,7 +71,7 @@ class RadialGaugeLabel : public QLabel const qreal side = std::max(20.0, std::min(this->width(), this->height()) - 8.0); const QPointF center(this->width() / 2.0, this->height() / 2.0); - const qreal ring_width = std::max(5.0, side * (this->large_gauge ? 0.045 : 0.055)); + const qreal ring_width = std::max(5.0, side * 0.045); const QRectF ring_rect(center.x() - side / 2.0 + ring_width, center.y() - side / 2.0 + ring_width, side - ring_width * 2.0, @@ -104,7 +108,7 @@ class RadialGaugeLabel : public QLabel const double max_value = this->max_for_unit(this->unit); double fraction = this->valid ? (this->value / max_value) : 0.0; fraction = std::max(0.0, std::min(1.0, fraction)); - const int segments = this->large_gauge ? 28 : 22; + constexpr int segments = 28; const int lit_segments = static_cast(std::round(fraction * segments)); const qreal segment_span = span_angle / segments; @@ -120,24 +124,26 @@ class RadialGaugeLabel : public QLabel QFont display_value_font = this->value_font; if (display_value_font.family().isEmpty()) display_value_font = this->font(); + display_value_font.setBold(true); QFont display_unit_font = this->unit_font; if (display_unit_font.family().isEmpty()) display_unit_font = this->font(); + display_unit_font.setBold(true); painter.setPen(green); painter.setFont(display_value_font); - QRectF value_rect(center.x() - side * 0.35, - center.y() - side * 0.18, - side * 0.70, - side * 0.28); + QRectF value_rect(center.x() - side * 0.38, + center.y() - side * 0.20, + side * 0.76, + side * 0.30); painter.drawText(value_rect, Qt::AlignCenter, this->value_text); painter.setFont(display_unit_font); - QRectF unit_rect(center.x() - side * 0.35, + QRectF unit_rect(center.x() - side * 0.38, center.y() + side * 0.08, - side * 0.70, - side * 0.18); + side * 0.76, + side * 0.20); painter.drawText(unit_rect, Qt::AlignCenter, this->unit); } @@ -156,10 +162,11 @@ class RadialGaugeLabel : public QLabel return 120.0; if (unit == QStringLiteral("°F")) return 250.0; + if (unit == QStringLiteral("g/s")) + return 200.0; return 100.0; } - bool large_gauge; bool valid = false; double value = 0.0; QString value_text = QStringLiteral("--"); @@ -214,7 +221,7 @@ Gauge::Gauge(units_t units, QFont value_font, QFont unit_font, Gauge::Orientatio layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - value_label = new RadialGaugeLabel(orientation == BOTTOM, this); + value_label = new RadialGaugeLabel(this); auto radial_gauge = static_cast(value_label); radial_gauge->set_gauge_fonts(value_font, unit_font); radial_gauge->set_gauge_unit(this->si ? units.second : units.first); @@ -493,78 +500,94 @@ DataTab::DataTab(Arbiter &arbiter, QWidget *parent) : QWidget(parent) , arbiter(arbiter) { - QHBoxLayout *layout = new QHBoxLayout(this); - - QWidget *driving_data = this->speedo_tach_widget(); - layout->addWidget(driving_data); - layout->addWidget(Session::Forge::br(true)); + // Use one grid for the entire Vehicle page so every gauge shares the + // same row and column geometry. This also removes the old vertical + // divider between the driving and engine-data sections. + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(16, 8, 16, 8); + layout->setHorizontalSpacing(20); + layout->setVerticalSpacing(12); + + layout->addWidget(this->speed_widget(), 0, 0, Qt::AlignCenter); + layout->addWidget(this->coolant_temp_widget(), 0, 1, Qt::AlignCenter); + layout->addWidget(this->intake_temp_widget(), 0, 2, Qt::AlignCenter); + layout->addWidget(this->rpm_widget(), 1, 0, Qt::AlignCenter); + layout->addWidget(this->engine_load_widget(), 1, 1, Qt::AlignCenter); + layout->addWidget(this->maf_widget(), 1, 2, Qt::AlignCenter); + + for (int column = 0; column < 3; ++column) + layout->setColumnStretch(column, 1); + for (int row = 0; row < 2; ++row) + layout->setRowStretch(row, 1); - QWidget *engine_data = this->engine_data_widget(); - layout->addWidget(engine_data); - - QSizePolicy sp_left(QSizePolicy::Preferred, QSizePolicy::Preferred); - sp_left.setHorizontalStretch(5); - driving_data->setSizePolicy(sp_left); - QSizePolicy sp_right(QSizePolicy::Preferred, QSizePolicy::Preferred); - sp_right.setHorizontalStretch(2); - engine_data->setSizePolicy(sp_right); for (auto &gauge : this->gauges) gauge->start(); } -QWidget *DataTab::speedo_tach_widget() +QWidget *DataTab::speed_widget() { QWidget *widget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(widget); layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(4); - layout->addStretch(3); - - QFont speed_value_font(this->arbiter.forge().font(36, true)); + QFont value_font(this->arbiter.forge().font(38, true)); + value_font.setBold(true); - QFont speed_unit_font(this->arbiter.forge().font(16)); - speed_unit_font.setWeight(QFont::Light); - speed_unit_font.setItalic(true); + QFont unit_font(this->arbiter.forge().font(16)); + unit_font.setBold(true); + unit_font.setItalic(false); - Gauge *speed = new Gauge({"mph", "km/h"}, speed_value_font, speed_unit_font, + Gauge *speed = new Gauge({"mph", "km/h"}, value_font, unit_font, Gauge::BOTTOM, 100, {cmds.SPEED}, 0, [](double x, bool si) { return si ? x : kph_to_mph(x); }, widget); - layout->addWidget(speed); + layout->addWidget(speed, 0, Qt::AlignHCenter); + this->gauges.push_back(speed); + + QFont label_font(this->arbiter.forge().font(14)); + label_font.setBold(true); - QFont driving_label_font(this->arbiter.forge().font(14)); - driving_label_font.setWeight(QFont::Light); QLabel *speed_label = new QLabel("Speed", widget); - speed_label->setFont(driving_label_font); + speed_label->setFont(label_font); speed_label->setAlignment(Qt::AlignHCenter); layout->addWidget(speed_label); - this->gauges.push_back(speed); - layout->addStretch(2); + return widget; +} + +QWidget *DataTab::rpm_widget() +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(4); - QFont tach_value_font(this->arbiter.forge().font(24, true)); + QFont value_font(this->arbiter.forge().font(38, true)); + value_font.setBold(true); - QFont tach_unit_font(this->arbiter.forge().font(12)); - tach_unit_font.setWeight(QFont::Light); - tach_unit_font.setItalic(true); + QFont unit_font(this->arbiter.forge().font(16)); + unit_font.setBold(true); + unit_font.setItalic(false); - Gauge *rpm = new Gauge({"x1000rpm", "x1000rpm"}, tach_value_font, - tach_unit_font, Gauge::BOTTOM, 100, {cmds.RPM}, 1, + Gauge *rpm = new Gauge({"x1000rpm", "x1000rpm"}, value_font, unit_font, + Gauge::BOTTOM, 100, {cmds.RPM}, 1, [](double x, bool _) { return x / 1000.0; }, widget); - layout->addWidget(rpm); + layout->addWidget(rpm, 0, Qt::AlignHCenter); + this->gauges.push_back(rpm); + + QFont label_font(this->arbiter.forge().font(14)); + label_font.setBold(true); QLabel *rpm_label = new QLabel("RPM", widget); - rpm_label->setFont(driving_label_font); + rpm_label->setFont(label_font); rpm_label->setAlignment(Qt::AlignHCenter); layout->addWidget(rpm_label); - this->gauges.push_back(rpm); - - layout->addStretch(1); return widget; } /* socketcan/elm327 rewrite right now only has support for one PID per gauge, so we can't calculate milage at this point. - This is because gauges act more as event handlers now for each PID. + This is because gauges act more as event handlers now for each PID. Multi-PID gauges could feasibly be reimplemented if there was a helper method that stored received values, and only calls the gauge update once all values have been updated since last gauge update. @@ -572,15 +595,15 @@ QWidget *DataTab::speedo_tach_widget() // QWidget *DataTab::mileage_data_widget() // { -// QWidget *widget = new QWidget(this); -// QHBoxLayout *layout = new QHBoxLayout(widget); -// -// QFont value_font(Theme::font_36); -// value_font.setFamily("Titillium Web"); -// -// QFont unit_font(Theme::font_14); -// unit_font.setWeight(QFont::Light); -// unit_font.setItalic(true); +// QWidget *widget = new QWidget(this); +// QHBoxLayout *layout = new QHBoxLayout(widget); +// +// QFont value_font(Theme::font_36); +// value_font.setFamily("Titillium Web"); +// +// QFont unit_font(Theme::font_14); +// unit_font.setWeight(QFont::Light); +// unit_font.setItalic(true); // // Gauge *mileage = new Gauge({"mpg", "km/L"}, value_font, unit_font, // Gauge::BOTTOM, 100, {cmds.SPEED, cmds.MAF}, 1, @@ -590,51 +613,34 @@ QWidget *DataTab::speedo_tach_widget() // widget); // layout->addWidget(mileage); // this->gauges.push_back(mileage); - +// // return widget; // } -QWidget *DataTab::engine_data_widget() -{ - QWidget *widget = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(widget); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - layout->addStretch(); - layout->addWidget(this->coolant_temp_widget()); - layout->addStretch(); - layout->addWidget(Session::Forge::br()); - layout->addStretch(); - layout->addWidget(this->engine_load_widget()); - layout->addStretch(); - - return widget; -} - QWidget *DataTab::coolant_temp_widget() { QWidget *widget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(widget); layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); + layout->setSpacing(4); - QFont value_font(this->arbiter.forge().font(16, true)); + QFont value_font(this->arbiter.forge().font(38, true)); + value_font.setBold(true); - QFont unit_font(this->arbiter.forge().font(12)); - unit_font.setWeight(QFont::Light); - unit_font.setItalic(true); + QFont unit_font(this->arbiter.forge().font(16)); + unit_font.setBold(true); + unit_font.setItalic(false); Gauge *coolant_temp = new Gauge( - {"°F", "°C"}, value_font, unit_font, Gauge::RIGHT, 5000, + {"°F", "°C"}, value_font, unit_font, Gauge::BOTTOM, 5000, {cmds.COOLANT_TEMP}, 1, [](double x, bool si) { return si ? x : c_to_f(x); }, widget); - layout->addWidget(coolant_temp); + layout->addWidget(coolant_temp, 0, Qt::AlignHCenter); this->gauges.push_back(coolant_temp); - QFont label_font(this->arbiter.forge().font(10)); - label_font.setWeight(QFont::Light); + QFont label_font(this->arbiter.forge().font(14)); + label_font.setBold(true); - QLabel *coolant_temp_label = new QLabel("coolant", widget); + QLabel *coolant_temp_label = new QLabel("Coolant", widget); coolant_temp_label->setFont(label_font); coolant_temp_label->setAlignment(Qt::AlignHCenter); layout->addWidget(coolant_temp_label); @@ -647,26 +653,92 @@ QWidget *DataTab::engine_load_widget() QWidget *widget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(widget); layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); + layout->setSpacing(4); - QFont value_font(this->arbiter.forge().font(16, true)); + QFont value_font(this->arbiter.forge().font(38, true)); + value_font.setBold(true); - QFont unit_font(this->arbiter.forge().font(12)); - unit_font.setWeight(QFont::Light); - unit_font.setItalic(true); + QFont unit_font(this->arbiter.forge().font(16)); + unit_font.setBold(true); + unit_font.setItalic(false); Gauge *engine_load = - new Gauge({"%", "%"}, value_font, unit_font, Gauge::RIGHT, + new Gauge({"%", "%"}, value_font, unit_font, Gauge::BOTTOM, 500, {cmds.LOAD}, 1, [](double x, bool _) { return x; }, widget); - layout->addWidget(engine_load); + layout->addWidget(engine_load, 0, Qt::AlignHCenter); this->gauges.push_back(engine_load); - QFont label_font(this->arbiter.forge().font(10)); - label_font.setWeight(QFont::Light); + QFont label_font(this->arbiter.forge().font(14)); + label_font.setBold(true); - QLabel *engine_load_label = new QLabel("load", widget); + QLabel *engine_load_label = new QLabel("Load", widget); engine_load_label->setFont(label_font); engine_load_label->setAlignment(Qt::AlignHCenter); layout->addWidget(engine_load_label); + + return widget; +} + +QWidget *DataTab::intake_temp_widget() +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(4); + + QFont value_font(this->arbiter.forge().font(38, true)); + value_font.setBold(true); + + QFont unit_font(this->arbiter.forge().font(16)); + unit_font.setBold(true); + unit_font.setItalic(false); + + Gauge *intake_temp = new Gauge( + {"°F", "°C"}, value_font, unit_font, Gauge::BOTTOM, 5000, + {cmds.INTAKE_TEMP}, 1, [](double x, bool si) { return si ? x : c_to_f(x); }, widget); + layout->addWidget(intake_temp, 0, Qt::AlignHCenter); + this->gauges.push_back(intake_temp); + + QFont label_font(this->arbiter.forge().font(14)); + label_font.setBold(true); + + QLabel *intake_temp_label = new QLabel("Intake Air", widget); + intake_temp_label->setFont(label_font); + intake_temp_label->setAlignment(Qt::AlignHCenter); + layout->addWidget(intake_temp_label); + + return widget; +} + +QWidget *DataTab::maf_widget() +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(4); + + QFont value_font(this->arbiter.forge().font(38, true)); + value_font.setBold(true); + + QFont unit_font(this->arbiter.forge().font(16)); + unit_font.setBold(true); + unit_font.setItalic(false); + + // OBD-II PID 0x10 is conventionally reported in grams per second, + // including on many non-SI scan tools, so both unit modes use g/s. + Gauge *maf = new Gauge( + {"g/s", "g/s"}, value_font, unit_font, Gauge::BOTTOM, 500, + {cmds.MAF}, 1, [](double x, bool _) { return x; }, widget); + layout->addWidget(maf, 0, Qt::AlignHCenter); + this->gauges.push_back(maf); + + QFont label_font(this->arbiter.forge().font(14)); + label_font.setBold(true); + + QLabel *maf_label = new QLabel("MAF", widget); + maf_label->setFont(label_font); + maf_label->setAlignment(Qt::AlignHCenter); + layout->addWidget(maf_label); + return widget; } From 14a674f427ffafea8c72c717f31101b5e253b200 Mon Sep 17 00:00:00 2001 From: Truckputer Date: Tue, 21 Jul 2026 14:22:38 -0700 Subject: [PATCH 3/5] Use and ENV Var to have a speed correction (ie: multiply speed by 1.0737) In case of larger/smaller than stock tire sizes.o --- src/app/pages/vehicle.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/app/pages/vehicle.cpp b/src/app/pages/vehicle.cpp index 648e3594..c7b669f3 100644 --- a/src/app/pages/vehicle.cpp +++ b/src/app/pages/vehicle.cpp @@ -3,6 +3,10 @@ #include #include +//For speed Correction - Eric KG6WXC +#include +#include + #include #include @@ -15,6 +19,24 @@ namespace { +//For speed Correction - Eric KG6WXC +double configured_speed_correction() +{ + const QByteArray raw_value = qgetenv("DASH_SPEED_CORRECTION"); + + if (raw_value.isEmpty()) + return 1.0; + + bool valid = false; + const double correction = raw_value.toDouble(&valid); + + // Invalid, zero, or negative values fall back to no correction. + if (!valid || correction <= 0.0) + return 1.0; + + return correction; +} + class RadialGaugeLabel : public QLabel { public: @@ -538,9 +560,18 @@ QWidget *DataTab::speed_widget() unit_font.setBold(true); unit_font.setItalic(false); + //For speed Correction - Eric KG6WXC + const double speed_correction = configured_speed_correction(); + + //Change below decoder For speed Correction - Eric KG6WXC Gauge *speed = new Gauge({"mph", "km/h"}, value_font, unit_font, Gauge::BOTTOM, 100, {cmds.SPEED}, 0, - [](double x, bool si) { return si ? x : kph_to_mph(x); }, widget); + [speed_correction](double x, bool si) { + const double corrected_kph = x * speed_correction; + return si ? corrected_kph : kph_to_mph(corrected_kph); + }, + widget); + layout->addWidget(speed, 0, Qt::AlignHCenter); this->gauges.push_back(speed); From dc049a68615aa35b16caf9e49b8e8bef27d06931 Mon Sep 17 00:00:00 2001 From: kg6wxc Date: Tue, 21 Jul 2026 15:01:35 -0700 Subject: [PATCH 4/5] No signed `char` for sensor values. The values would go negative when certain thresholds were exceeded, this should fix that. Vibe Coded - ChatGPT - Eric KG6WXC - r1de --- include/obd/decoders.hpp | 72 +++++++++++++++++++++++++++++++++++++-- include/obd/message.hpp | 41 ++++++++++++++++++++++ src/app/pages/vehicle.cpp | 8 +++++ 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/include/obd/decoders.hpp b/include/obd/decoders.hpp index 4bbe8199..7fd5cc76 100644 --- a/include/obd/decoders.hpp +++ b/include/obd/decoders.hpp @@ -1,7 +1,75 @@ #pragma once - +#include #include +namespace { + + inline bool has_obd_data(const Response &resp, int required_bytes) + { + return resp.success && resp.data.size() >= required_bytes; + } + + inline int obd_byte(const Response &resp, int index) + { + return static_cast(resp.data.at(index)); + } + + inline double invalid_obd_value() + { + return std::numeric_limits::quiet_NaN(); + } + +} // namespace + +inline double percentage(const Response &resp) +{ + if (!has_obd_data(resp, 1)) + return invalid_obd_value(); + + const int a = obd_byte(resp, 0); + return (100.0 / 255.0) * a; +} + +inline double temp(const Response &resp) +{ + if (!has_obd_data(resp, 1)) + return invalid_obd_value(); + + const int a = obd_byte(resp, 0); + return a - 40.0; +} + +inline double rpm(const Response &resp) +{ + if (!has_obd_data(resp, 2)) + return invalid_obd_value(); + + const int a = obd_byte(resp, 0); + const int b = obd_byte(resp, 1); + + return ((256.0 * a) + b) / 4.0; +} + +inline double speed(const Response &resp) +{ + if (!has_obd_data(resp, 1)) + return invalid_obd_value(); + + return obd_byte(resp, 0); +} + +inline double flow(const Response &resp) +{ + if (!has_obd_data(resp, 2)) + return invalid_obd_value(); + + const int a = obd_byte(resp, 0); + const int b = obd_byte(resp, 1); + + return ((256.0 * a) + b) / 100.0; +} + +/* double percentage(Response resp) { return (100.0 / 255.0) * (int)resp.data.at(0); } double temp(Response resp) { return (int)resp.data.at(0) - 40; } @@ -11,4 +79,4 @@ double rpm(Response resp) { return ((256.0 * (int)resp.data.at(0)) + (int)resp.d double speed(Response resp) { return (int)resp.data.at(0); } double flow(Response resp) { return ((256.0 * (int)resp.data.at(0)) + (int)resp.data.at(1)) / 100.0; } - +*/ diff --git a/include/obd/message.hpp b/include/obd/message.hpp index 7f5d8730..39671c61 100644 --- a/include/obd/message.hpp +++ b/include/obd/message.hpp @@ -4,6 +4,7 @@ #include #include #include +#include struct Message { unsigned char length; @@ -27,6 +28,45 @@ struct Request : Message { } }; +struct Response : Message { + bool success = false; + QByteArray data; + + explicit Response(const QByteArray &payload) + : Message{0, 0, 0} + { + if (payload.size() < 3) + return; + + this->length = + static_cast(payload.at(0)); + + if (this->length < 2) + return; + + this->mode = + static_cast(payload.at(1)) - 0x40; + + this->PID = + static_cast(payload.at(2)); + + const int data_length = + std::min(this->length - 2, payload.size() - 3); + + if (data_length > 0) + this->data = payload.mid(3, data_length); + + this->success = true; + } + + Response() + : Message{0, 0, 0} + , success(false) + { + } +}; + +/* struct Response : Message { bool success = true; QByteArray data; @@ -42,3 +82,4 @@ struct Response : Message { Response() { this->success = false; } }; +*/ diff --git a/src/app/pages/vehicle.cpp b/src/app/pages/vehicle.cpp index c7b669f3..bd2b8d96 100644 --- a/src/app/pages/vehicle.cpp +++ b/src/app/pages/vehicle.cpp @@ -273,6 +273,14 @@ void Gauge::can_callback(QByteArray payload){ for(auto cmd : cmds){ if(cmd.frame.payload().at(2) == resp.PID){ double value = this->decoder(cmd.decoder(resp), this->si); + if (!std::isfinite(value)) { + DASH_LOG(warning) + << "[Gauges] Invalid response for PID " + << static_cast(resp.PID) + << ", payload " + << payload.toHex(); + return; + } static_cast(value_label)->set_gauge_value(this->format_value(value), value, true); } } From bcef679ac85496f42a33c3363aa417f2abb63f10 Mon Sep 17 00:00:00 2001 From: kg6wxc Date: Tue, 21 Jul 2026 15:40:04 -0700 Subject: [PATCH 5/5] Fix logging routine vehicle.cpp --- src/app/pages/vehicle.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/pages/vehicle.cpp b/src/app/pages/vehicle.cpp index bd2b8d96..d17a6b86 100644 --- a/src/app/pages/vehicle.cpp +++ b/src/app/pages/vehicle.cpp @@ -274,11 +274,12 @@ void Gauge::can_callback(QByteArray payload){ if(cmd.frame.payload().at(2) == resp.PID){ double value = this->decoder(cmd.decoder(resp), this->si); if (!std::isfinite(value)) { + const QByteArray payload_hex = payload.toHex(); DASH_LOG(warning) << "[Gauges] Invalid response for PID " << static_cast(resp.PID) << ", payload " - << payload.toHex(); + << payload_hex.constData(); return; } static_cast(value_label)->set_gauge_value(this->format_value(value), value, true);