From 043a309d67da9c4d4671303b370e04aa29cd115d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 3 May 2022 16:51:44 -0700 Subject: [PATCH 01/13] ui: new notcar battery design (#24412) * ui: new notcar battery design * if charging * cleanup * remove that * slower * real colors * relative from left * fix that --- selfdrive/ui/qt/body.cc | 50 +++++++++++++++++++++++++++----------- selfdrive/ui/qt/body.h | 4 ++- selfdrive/ui/tests/body.py | 22 +++++++++++++++++ 3 files changed, 61 insertions(+), 15 deletions(-) create mode 100755 selfdrive/ui/tests/body.py diff --git a/selfdrive/ui/qt/body.cc b/selfdrive/ui/qt/body.cc index a83d749577..da87a7485a 100644 --- a/selfdrive/ui/qt/body.cc +++ b/selfdrive/ui/qt/body.cc @@ -1,10 +1,11 @@ #include "selfdrive/ui/qt/body.h" #include +#include #include -BodyWindow::BodyWindow(QWidget *parent) : QLabel(parent) { +BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QLabel(parent) { awake = new QMovie("../assets/body/awake.gif"); awake->setCacheMode(QMovie::CacheAll); sleep = new QMovie("../assets/body/sleep.gif"); @@ -26,20 +27,40 @@ void BodyWindow::paintEvent(QPaintEvent *event) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing); + + p.translate(width() - 136, 16); + + // battery outline + detail + const QColor gray = QColor("#737373"); + p.setBrush(Qt::NoBrush); + p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + p.drawRoundedRect(2, 2, 78, 36, 8, 8); + p.setPen(Qt::NoPen); + p.setBrush(gray); + p.drawRoundedRect(84, 12, 6, 16, 4, 4); + p.drawRect(84, 12, 3, 16); - // draw battery level - const int offset = 90; - const int radius = 60 / 2; - const int levels = 5; - const float interval = 1. / levels; - for (int i = 0; i < levels; i++) { - float level = 1.0 - (i+1)*interval; - float perc = (fuel >= level) ? 1.0 : 0.35; - - p.setBrush(QColor(255, 255, 255, 255 * perc)); - QPoint pt(width() - (i*offset + offset / 2), offset / 2); - p.drawEllipse(pt, radius, radius); + // battery level + double fuel = std::clamp(fuel_filter.x(), 0.2f, 1.0f); + const int m = 5; // manual margin since we can't do an inner border + p.setPen(Qt::NoPen); + p.setBrush(fuel > 0.25 ? QColor("#32D74B") : QColor("#FF453A")); + p.drawRoundedRect(2 + m, 2 + m, (78 - 2*m)*fuel, 36 - 2*m, 4, 4); + + // charging status + if (charging) { + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + const QPolygonF charger({ + QPointF(12.31, 0), + QPointF(12.31, 16.92), + QPointF(18.46, 16.92), + QPointF(6.15, 40), + QPointF(6.15, 23.08), + QPointF(0, 23.08), + }); + p.drawPolygon(charger.translated(98, 0)); } } @@ -52,7 +73,8 @@ void BodyWindow::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); auto cs = sm["carState"].getCarState(); - fuel = cs.getFuelGauge(); + charging = cs.getCharging(); + fuel_filter.update(cs.getFuelGauge()); // TODO: use carState.standstill when that's fixed const bool standstill = std::abs(cs.getVEgo()) < 0.01; diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index c1138b8703..cf1ffa191f 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -3,6 +3,7 @@ #include #include +#include "selfdrive/common/util.h" #include "selfdrive/ui/ui.h" class BodyWindow : public QLabel { @@ -12,7 +13,8 @@ public: BodyWindow(QWidget* parent = 0); private: - float fuel = 1.0; + bool charging = false; + FirstOrderFilter fuel_filter; QMovie *awake, *sleep; void paintEvent(QPaintEvent*) override; diff --git a/selfdrive/ui/tests/body.py b/selfdrive/ui/tests/body.py new file mode 100755 index 0000000000..c34e717eaf --- /dev/null +++ b/selfdrive/ui/tests/body.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import time +import cereal.messaging as messaging + +if __name__ == "__main__": + while True: + pm = messaging.PubMaster(['carParams', 'carState']) + batt = 1. + while True: + msg = messaging.new_message('carParams') + msg.carParams.carName = "COMMA BODY" + msg.carParams.notCar = True + pm.send('carParams', msg) + + for b in range(100, 0, -1): + msg = messaging.new_message('carState') + msg.carState.charging = True + msg.carState.fuelGauge = b / 100. + pm.send('carState', msg) + time.sleep(0.1) + + time.sleep(1) From 0771addd144a6822fca8209be53994c0db009bbb Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Tue, 3 May 2022 19:03:17 -0500 Subject: [PATCH 02/13] update joystick README for webjoystick (#24413) --- tools/joystick/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/joystick/README.md b/tools/joystick/README.md index 5a45785db6..c74e51146c 100644 --- a/tools/joystick/README.md +++ b/tools/joystick/README.md @@ -45,6 +45,17 @@ In order to use a joystick over the network, we need to run joystickd locally fr tools/joystick/joystickd.py ``` +### Web joystick on your mobile device + +A browser-based virtual joystick designed for touch screens. Starts automatically when installed on comma body (non-car robotics platform). +For cars, start the web joystick service manually via SSH before starting the car. + +```shell +tools/joystick/web.py +``` + +After starting the car/body, open the web joystick app at this URL: `http://[comma three IP address]:5000` + --- Now start your car and openpilot should go into joystick mode with an alert on startup! The status of the axes will display on the alert, while button statuses print in the shell. From cdf6338388c4f2e054de0e76eb1401a2993360ce Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 3 May 2022 23:30:35 -0700 Subject: [PATCH 03/13] manager: start process by callback (#24417) --- selfdrive/manager/manager.py | 7 +++---- selfdrive/manager/process.py | 22 ++++++++++------------ selfdrive/manager/process_config.py | 19 ++++++++++++++----- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 1934da6701..cd7817fa97 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -128,17 +128,16 @@ def manager_thread() -> None: ignore.append("pandad") ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0] - ensure_running(managed_processes.values(), started=False, not_run=ignore) - sm = messaging.SubMaster(['deviceState', 'carParams'], poll=['deviceState']) pm = messaging.PubMaster(['managerState']) + ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore) + while True: sm.update() started = sm['deviceState'].started - driverview = params.get_bool("IsDriverViewEnabled") - ensure_running(managed_processes.values(), started=started, driverview=driverview, notcar=sm['carParams'].notCar, not_run=ignore) + ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=ignore) running = ' '.join("%s%s\u001b[0m" % ("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) for p in managed_processes.values() if p.proc) diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index 8bb160961f..e2bb41c217 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -4,7 +4,7 @@ import signal import struct import time import subprocess -from typing import Optional, List, ValuesView +from typing import Optional, Callable, List, ValuesView from abc import ABC, abstractmethod from multiprocessing import Process @@ -12,6 +12,7 @@ from setproctitle import setproctitle # pylint: disable=no-name-in-module import cereal.messaging as messaging import selfdrive.sentry as sentry +from cereal import car from common.basedir import BASEDIR from common.params import Params from common.realtime import sec_since_boot @@ -71,8 +72,7 @@ class ManagerProcess(ABC): sigkill = False onroad = True offroad = False - driverview = False - notcar = False + callback: Optional[Callable[[bool, Params, car.CarParams], bool]] = None proc: Optional[Process] = None enabled = True name = "" @@ -184,15 +184,14 @@ class ManagerProcess(ABC): class NativeProcess(ManagerProcess): - def __init__(self, name, cwd, cmdline, enabled=True, onroad=True, offroad=False, driverview=False, notcar=False, unkillable=False, sigkill=False, watchdog_max_dt=None): + def __init__(self, name, cwd, cmdline, enabled=True, onroad=True, offroad=False, callback=None, unkillable=False, sigkill=False, watchdog_max_dt=None): self.name = name self.cwd = cwd self.cmdline = cmdline self.enabled = enabled self.onroad = onroad self.offroad = offroad - self.driverview = driverview - self.notcar = notcar + self.callback = callback self.unkillable = unkillable self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt @@ -217,14 +216,13 @@ class NativeProcess(ManagerProcess): class PythonProcess(ManagerProcess): - def __init__(self, name, module, enabled=True, onroad=True, offroad=False, driverview=False, notcar=False, unkillable=False, sigkill=False, watchdog_max_dt=None): + def __init__(self, name, module, enabled=True, onroad=True, offroad=False, callback=None, unkillable=False, sigkill=False, watchdog_max_dt=None): self.name = name self.module = module self.enabled = enabled self.onroad = onroad self.offroad = offroad - self.driverview = driverview - self.notcar = notcar + self.callback = callback self.unkillable = unkillable self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt @@ -291,7 +289,7 @@ class DaemonProcess(ManagerProcess): pass -def ensure_running(procs: ValuesView[ManagerProcess], started: bool, driverview: bool=False, notcar: bool=False, +def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None, CP: car.CarParams=None, not_run: Optional[List[str]]=None) -> None: if not_run is None: not_run = [] @@ -301,9 +299,9 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, driverview: run = any(( p.offroad and not started, p.onroad and started, - p.driverview and driverview, - p.notcar and notcar, )) + if p.callback is not None and None not in (params, CP): + run = run or p.callback(started, params, CP) # Conditions that block a process from starting run = run and not any(( diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 2e2747fecc..0979c67d70 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -1,16 +1,25 @@ import os +from cereal import car +from common.params import Params from selfdrive.hardware import TICI, PC from selfdrive.manager.process import PythonProcess, NativeProcess, DaemonProcess WEBCAM = os.getenv("USE_WEBCAM") is not None +def driverview(started: bool, params: Params, CP: car.CarParams) -> bool: + return params.get_bool("IsDriverViewEnabled") + +def notcar(started: bool, params: Params, CP: car.CarParams) -> bool: + return CP.notCar + + procs = [ DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), # due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption - NativeProcess("camerad", "selfdrive/camerad", ["./camerad"], unkillable=True, driverview=True), + NativeProcess("camerad", "selfdrive/camerad", ["./camerad"], unkillable=True, callback=driverview), NativeProcess("clocksd", "selfdrive/clocksd", ["./clocksd"]), - NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), driverview=True), + NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview), NativeProcess("logcatd", "selfdrive/logcatd", ["./logcatd"]), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"]), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), @@ -25,7 +34,7 @@ procs = [ PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"), PythonProcess("controlsd", "selfdrive.controls.controlsd"), PythonProcess("deleter", "selfdrive.loggerd.deleter", offroad=True), - PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", enabled=(not PC or WEBCAM), driverview=True), + PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", enabled=(not PC or WEBCAM), callback=driverview), PythonProcess("logmessaged", "selfdrive.logmessaged", offroad=True), PythonProcess("pandad", "selfdrive.boardd.pandad", offroad=True), PythonProcess("paramsd", "selfdrive.locationd.paramsd"), @@ -38,8 +47,8 @@ procs = [ PythonProcess("uploader", "selfdrive.loggerd.uploader", offroad=True), PythonProcess("statsd", "selfdrive.statsd", offroad=True), - NativeProcess("bridge", "cereal/messaging", ["./bridge"], onroad=False, notcar=True), - PythonProcess("webjoystick", "tools.joystick.web", onroad=False, notcar=True), + NativeProcess("bridge", "cereal/messaging", ["./bridge"], onroad=False, callback=notcar), + PythonProcess("webjoystick", "tools.joystick.web", onroad=False, callback=notcar), # Experimental PythonProcess("rawgpsd", "selfdrive.sensord.rawgps.rawgpsd", enabled=os.path.isfile("/persist/comma/use-quectel-rawgps")), From 670126cbf15ceecb03279e6fa9779798d905cb99 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 3 May 2022 23:46:59 -0700 Subject: [PATCH 04/13] body: opt-in logging (#24416) --- selfdrive/common/params.cc | 1 + selfdrive/manager/process_config.py | 5 +- selfdrive/ui/qt/body.cc | 74 ++++++++++++++++++++++++----- selfdrive/ui/qt/body.h | 15 +++++- 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index bea12aca11..9cc7a113cb 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -103,6 +103,7 @@ std::unordered_map keys = { {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, + {"EnableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"EnableWideCamera", CLEAR_ON_MANAGER_START}, {"EndToEndToggle", PERSISTENT}, {"ForcePowerDown", CLEAR_ON_MANAGER_START}, diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 0979c67d70..34b120efaf 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -13,6 +13,9 @@ def driverview(started: bool, params: Params, CP: car.CarParams) -> bool: def notcar(started: bool, params: Params, CP: car.CarParams) -> bool: return CP.notCar +def logging(started, params, CP: car.CarParams) -> bool: + run = (not CP.notCar) or params.get_bool("EnableLogging") + return started and run procs = [ DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), @@ -21,7 +24,7 @@ procs = [ NativeProcess("clocksd", "selfdrive/clocksd", ["./clocksd"]), NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview), NativeProcess("logcatd", "selfdrive/logcatd", ["./logcatd"]), - NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"]), + NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), NativeProcess("navd", "selfdrive/ui/navd", ["./navd"], offroad=True), NativeProcess("proclogd", "selfdrive/proclogd", ["./proclogd"]), diff --git a/selfdrive/ui/qt/body.cc b/selfdrive/ui/qt/body.cc index da87a7485a..01b91571a4 100644 --- a/selfdrive/ui/qt/body.cc +++ b/selfdrive/ui/qt/body.cc @@ -4,33 +4,81 @@ #include #include +#include -BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QLabel(parent) { +#include "selfdrive/common/params.h" + +RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) { + setCheckable(true); + setChecked(false); + setFixedSize(148, 148); +} + +void RecordButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QPoint center(width() / 2, height() / 2); + + QColor bg(isChecked() ? "#FFFFFF" : "#404040"); + QColor accent(isChecked() ? "#FF0000" : "#FFFFFF"); + if (isDown()) { + accent.setAlphaF(0.7); + } + + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(center, 74, 74); + + p.setPen(QPen(accent, 6)); + p.setBrush(Qt::NoBrush); + p.drawEllipse(center, 42, 42); + + p.setPen(Qt::NoPen); + p.setBrush(accent); + p.drawEllipse(center, 22, 22); +} + + +BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QWidget(parent) { + QStackedLayout *layout = new QStackedLayout(this); + layout->setStackingMode(QStackedLayout::StackAll); + + QWidget *w = new QWidget; + QVBoxLayout *vlayout = new QVBoxLayout(w); + vlayout->setMargin(45); + layout->addWidget(w); + + // face + face = new QLabel(); + face->setAlignment(Qt::AlignCenter); + layout->addWidget(face); awake = new QMovie("../assets/body/awake.gif"); awake->setCacheMode(QMovie::CacheAll); sleep = new QMovie("../assets/body/sleep.gif"); sleep->setCacheMode(QMovie::CacheAll); - QPalette p(Qt::black); - setPalette(p); - setAutoFillBackground(true); - - setAlignment(Qt::AlignCenter); - - setAttribute(Qt::WA_TransparentForMouseEvents, true); + // record button + btn = new RecordButton(this); + vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight); + QObject::connect(btn, &QPushButton::clicked, [=](bool checked) { + Params().putBool("EnableLogging", checked); + }); + w->raise(); QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); } void BodyWindow::paintEvent(QPaintEvent *event) { - QLabel::paintEvent(event); + //QLabel::paintEvent(event); QPainter p(this); p.setRenderHint(QPainter::Antialiasing); - p.translate(width() - 136, 16); + p.fillRect(rect(), QColor(0, 0, 0)); // battery outline + detail + p.translate(width() - 136, 16); const QColor gray = QColor("#737373"); p.setBrush(Qt::NoBrush); p.setPen(QPen(gray, 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); @@ -79,9 +127,9 @@ void BodyWindow::updateState(const UIState &s) { // TODO: use carState.standstill when that's fixed const bool standstill = std::abs(cs.getVEgo()) < 0.01; QMovie *m = standstill ? sleep : awake; - if (m != movie()) { - setMovie(m); - movie()->start(); + if (m != face->movie()) { + face->setMovie(m); + face->movie()->start(); } update(); diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index cf1ffa191f..b49192a2bf 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -2,11 +2,22 @@ #include #include +#include #include "selfdrive/common/util.h" #include "selfdrive/ui/ui.h" -class BodyWindow : public QLabel { +class RecordButton : public QPushButton { + Q_OBJECT + +public: + RecordButton(QWidget* parent = 0); + +private: + void paintEvent(QPaintEvent*) override; +}; + +class BodyWindow : public QWidget { Q_OBJECT public: @@ -15,7 +26,9 @@ public: private: bool charging = false; FirstOrderFilter fuel_filter; + QLabel *face; QMovie *awake, *sleep; + RecordButton *btn; void paintEvent(QPaintEvent*) override; private slots: From 9307fe434cf7e9adb68a12d0b9073ebcf75e1f3e Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 4 May 2022 11:37:31 +0200 Subject: [PATCH 05/13] map.cc: fix crash on older route with missing liveLocationKalman values --- selfdrive/ui/qt/maps/map.cc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 3b8668d0b7..055f93b291 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -117,17 +117,15 @@ void MapWindow::timerUpdate() { auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); auto pos = location.getPositionGeodetic(); auto orientation = location.getCalibratedOrientationNED(); + auto velocity = location.getVelocityCalibrated(); - localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && pos.getValid(); + localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && + pos.getValid() && orientation.getValid() && velocity.getValid(); if (localizer_valid) { - float velocity = location.getVelocityCalibrated().getValue()[0]; - float bearing = RAD2DEG(orientation.getValue()[2]); - auto coordinate = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); - - last_position = coordinate; - last_bearing = bearing; - velocity_filter.update(velocity); + last_position = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); + last_bearing = RAD2DEG(orientation.getValue()[2]); + velocity_filter.update(velocity.getValue()[0]); } } From 3fc01ec15acc5a4f8fdd427240dfc178362760dd Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 4 May 2022 11:44:16 +0200 Subject: [PATCH 06/13] cameraview.cc: set alignment to 1 when copying texture (#24418) --- selfdrive/ui/qt/widgets/cameraview.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index cb978182db..9d2d301ca8 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -216,6 +216,7 @@ void CameraViewWidget::paintGL() { if (latest_frame == nullptr) return; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glViewport(0, 0, width(), height()); glBindVertexArray(frame_vao); @@ -238,6 +239,7 @@ void CameraViewWidget::paintGL() { glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) { From e8892481abc7e3c529be35dd093614c50e6bfbbd Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 4 May 2022 12:12:13 +0200 Subject: [PATCH 07/13] ui: fix HUD drawing order (#24419) --- selfdrive/ui/qt/onroad.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index cee347daa9..098747946f 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -294,6 +294,8 @@ void NvgWindow::updateFrameMat(int w, int h) { } void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) { + painter.save(); + const UIScene &scene = s->scene; // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { @@ -329,9 +331,13 @@ void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) { } painter.setBrush(bg); painter.drawPolygon(scene.track_vertices.v, scene.track_vertices.cnt); + + painter.restore(); } void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd) { + painter.save(); + const float speedBuff = 10.; const float leadBuff = 40.; const float d_rel = lead_data.getX()[0]; @@ -361,6 +367,8 @@ void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; painter.setBrush(redColor(fillAlpha)); painter.drawPolygon(chevron, std::size(chevron)); + + painter.restore(); } void NvgWindow::paintGL() { @@ -370,8 +378,6 @@ void NvgWindow::paintGL() { painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); - drawHud(painter); - UIState *s = uiState(); if (s->worldObjectsVisible()) { @@ -388,6 +394,8 @@ void NvgWindow::paintGL() { } } + drawHud(painter); + double cur_draw_t = millis_since_boot(); double dt = cur_draw_t - prev_draw_t; double fps = fps_filter.update(1. / dt * 1000); From 43d2a3c187368a7c6ddf40c03766f7a3a95dfb24 Mon Sep 17 00:00:00 2001 From: Lukas Petersson Date: Wed, 4 May 2022 14:05:41 -0700 Subject: [PATCH 08/13] warning instead assert (#24424) * warning instead assert * warning description --- tools/latencylogger/latency_logger.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index b03dc84f61..319e73a769 100644 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -83,8 +83,10 @@ def read_logs(lr): if msg_obj.frameIdExtra != frame_id: frame_mismatches.append(frame_id) - assert frame_id_fails < 20, "Too many frameId fetch fails" - assert len(frame_mismatches) < 20, "Too many frame mismatches" + if frame_id_fails > 20: + print("Warning, many frameId fetch fails", frame_id_fails) + if len(frame_mismatches) > 20: + print("Warning, many frame mismatches", len(frame_mismatches)) return (data, frame_mismatches) def find_frame_id(time, service, start_times, end_times): @@ -98,12 +100,11 @@ def find_t0(start_times, frame_id=-1): m = max(start_times.keys()) while frame_id <= m: for service in SERVICES: - if service in start_times[frame_id]: + if start_times[frame_id][service]: return start_times[frame_id][service] frame_id += 1 raise Exception('No start time has been set') - ## ASSUMES THAT AT LEAST ONE CLOUDLOG IS MADE IN CONTROLSD def insert_cloudlogs(lr, timestamps, start_times, end_times): t0 = find_t0(start_times) @@ -138,8 +139,10 @@ def insert_cloudlogs(lr, timestamps, start_times, end_times): timestamps[frame_id][service].append((event, time)) else: failed_inserts += 1 - assert latest_controls_frameid > 0, "No timestamp in controlsd" - assert failed_inserts < len(timestamps), "Too many failed cloudlog inserts" + if latest_controls_frameid == 0: + print("Warning, no timestamp in controlsd. Implementation assumes that a timestamp is made in controlsd to bind boardd logs to frame ID. Please add such a timestamp.") + elif failed_inserts > len(timestamps): + print("Warning, many cloudlog inserts failed", failed_inserts) def print_timestamps(timestamps, durations, start_times, relative): t0 = find_t0(start_times) @@ -159,17 +162,15 @@ def print_timestamps(timestamps, durations, start_times, relative): def graph_timestamps(timestamps, start_times, end_times, relative): t0 = find_t0(start_times) - y0 = min(start_times.keys()) fig, ax = plt.subplots() ax.set_xlim(0, 150 if relative else 750) - ax.set_ylim(y0, y0+15) + ax.set_ylim(0, 15) ax.set_xlabel('milliseconds') - ax.set_ylabel('Frame ID') colors = ['blue', 'green', 'red', 'yellow', 'purple'] assert len(colors) == len(SERVICES), 'Each service needs a color' points = {"x": [], "y": [], "labels": []} - for frame_id, services in timestamps.items(): + for i, (frame_id, services) in enumerate(timestamps.items()): if relative: t0 = find_t0(start_times, frame_id) service_bars = [] @@ -180,9 +181,9 @@ def graph_timestamps(timestamps, start_times, end_times, relative): service_bars.append(((start-t0)/1e6,(end-start)/1e6)) for event in events: points['x'].append((event[1]-t0)/1e6) - points['y'].append(frame_id) + points['y'].append(i) points['labels'].append(event[0]) - ax.broken_barh(service_bars, (frame_id-0.45, 0.9), facecolors=(colors), alpha=0.5) + ax.broken_barh(service_bars, (i-0.45, 0.9), facecolors=(colors), alpha=0.5) scatter = ax.scatter(points['x'], points['y'], marker='d', edgecolor='black') tooltip = mpld3.plugins.PointLabelTooltip(scatter, labels=points['labels']) @@ -212,6 +213,7 @@ if __name__ == "__main__": r = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() lr = logreader_from_route_or_segment(r, sort_by_time=True) + data, _ = get_timestamps(lr) print_timestamps(data['timestamp'], data['duration'], data['start'], args.relative) if args.plot: From 930d0c01a90ba6a7b321516a276cc0e6e6862514 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 May 2022 14:16:27 -0700 Subject: [PATCH 09/13] latency logger: better errors and minor cleanup --- tools/latencylogger/latency_logger.py | 32 ++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index 319e73a769..11ec4850ec 100644 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -7,7 +7,7 @@ import sys from collections import defaultdict from tools.lib.logreader import logreader_from_route_or_segment - + DEMO_ROUTE = "9f583b1d93915c31|2022-04-26--18-49-49" SERVICES = ['camerad', 'modeld', 'plannerd', 'controlsd', 'boardd'] @@ -25,7 +25,7 @@ MSGQ_TO_SERVICE = { SERVICE_TO_DURATIONS = { 'camerad': ['processingTime'], 'modeld': ['modelExecutionTime', 'gpuExecutionTime'], - 'plannerd': ["solverExecutionTime"], + 'plannerd': ['solverExecutionTime'], } def read_logs(lr): @@ -38,7 +38,7 @@ def read_logs(lr): if msg.which() == 'sendcan': latest_sendcan_monotime = msg.logMonoTime continue - + if msg.which() in MSGQ_TO_SERVICE: service = MSGQ_TO_SERVICE[msg.which()] msg_obj = getattr(msg, msg.which()) @@ -105,9 +105,10 @@ def find_t0(start_times, frame_id=-1): frame_id += 1 raise Exception('No start time has been set') -## ASSUMES THAT AT LEAST ONE CLOUDLOG IS MADE IN CONTROLSD def insert_cloudlogs(lr, timestamps, start_times, end_times): - t0 = find_t0(start_times) + # at least one cloudlog must be made in controlsd + + t0 = find_t0(start_times) failed_inserts = 0 latest_controls_frameid = 0 for msg in lr: @@ -118,7 +119,7 @@ def insert_cloudlogs(lr, timestamps, start_times, end_times): service = jmsg['ctx']['daemon'] event = jmsg['msg']['timestamp']['event'] if time < t0: - # Filter out controlsd messages which arrive before the camera loop + # Filter out controlsd messages which arrive before the camera loop continue if "frame_id" in jmsg['msg']['timestamp']: @@ -139,29 +140,30 @@ def insert_cloudlogs(lr, timestamps, start_times, end_times): timestamps[frame_id][service].append((event, time)) else: failed_inserts += 1 + if latest_controls_frameid == 0: - print("Warning, no timestamp in controlsd. Implementation assumes that a timestamp is made in controlsd to bind boardd logs to frame ID. Please add such a timestamp.") + print("Warning: failed to bind boardd logs to a frame ID. Add a timestamp cloudlog in controlsd.") elif failed_inserts > len(timestamps): - print("Warning, many cloudlog inserts failed", failed_inserts) + print(f"Warning: failed to bind {failed_inserts} cloudlog timestamps to a frame ID") def print_timestamps(timestamps, durations, start_times, relative): - t0 = find_t0(start_times) + t0 = find_t0(start_times) for frame_id in timestamps.keys(): print('='*80) print("Frame ID:", frame_id) if relative: - t0 = find_t0(start_times, frame_id) + t0 = find_t0(start_times, frame_id) for service in SERVICES: - print(" "+service) + print(" "+service) events = timestamps[frame_id][service] for event, time in sorted(events, key = lambda x: x[1]): - print(" "+'%-53s%-53s' %(event, str((time-t0)/1e6))) + print(" "+'%-53s%-53s' %(event, str((time-t0)/1e6))) for event, time in durations[frame_id][service]: - print(" "+'%-53s%-53s' %(event, str(time*1000))) + print(" "+'%-53s%-53s' %(event, str(time*1000))) def graph_timestamps(timestamps, start_times, end_times, relative): - t0 = find_t0(start_times) + t0 = find_t0(start_times) fig, ax = plt.subplots() ax.set_xlim(0, 150 if relative else 750) ax.set_ylim(0, 15) @@ -172,7 +174,7 @@ def graph_timestamps(timestamps, start_times, end_times, relative): points = {"x": [], "y": [], "labels": []} for i, (frame_id, services) in enumerate(timestamps.items()): if relative: - t0 = find_t0(start_times, frame_id) + t0 = find_t0(start_times, frame_id) service_bars = [] for service, events in services.items(): if start_times[frame_id][service] and end_times[frame_id][service]: From 2466233b1cf5195bc4005c647d00408c838fe1a8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 May 2022 14:57:39 -0700 Subject: [PATCH 10/13] body: opt-out logging (#24423) * body: opt-out logging * wait a bit * little better * fix that too Co-authored-by: Comma Device --- selfdrive/common/params.cc | 2 +- selfdrive/manager/process_config.py | 2 +- selfdrive/ui/qt/body.cc | 33 +++++++++++++++++++++++++---- selfdrive/ui/qt/body.h | 2 ++ selfdrive/ui/qt/home.cc | 2 +- selfdrive/ui/ui.cc | 2 +- 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index 9cc7a113cb..c294c48dfe 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -94,6 +94,7 @@ std::unordered_map keys = { {"CompletedTrainingVersion", PERSISTENT}, {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, {"DisableRadar_Allow", PERSISTENT}, {"DisableRadar", PERSISTENT}, // WARNING: THIS DISABLES AEB @@ -103,7 +104,6 @@ std::unordered_map keys = { {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, - {"EnableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"EnableWideCamera", CLEAR_ON_MANAGER_START}, {"EndToEndToggle", PERSISTENT}, {"ForcePowerDown", CLEAR_ON_MANAGER_START}, diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 34b120efaf..8fa0454011 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -14,7 +14,7 @@ def notcar(started: bool, params: Params, CP: car.CarParams) -> bool: return CP.notCar def logging(started, params, CP: car.CarParams) -> bool: - run = (not CP.notCar) or params.get_bool("EnableLogging") + run = (not CP.notCar) or not params.get_bool("DisableLogging") return started and run procs = [ diff --git a/selfdrive/ui/qt/body.cc b/selfdrive/ui/qt/body.cc index 01b91571a4..7ca064a6cf 100644 --- a/selfdrive/ui/qt/body.cc +++ b/selfdrive/ui/qt/body.cc @@ -7,11 +7,16 @@ #include #include "selfdrive/common/params.h" +#include "selfdrive/common/timing.h" RecordButton::RecordButton(QWidget *parent) : QPushButton(parent) { setCheckable(true); setChecked(false); setFixedSize(148, 148); + + QObject::connect(this, &QPushButton::toggled, [=]() { + setEnabled(false); + }); } void RecordButton::paintEvent(QPaintEvent *event) { @@ -20,8 +25,13 @@ void RecordButton::paintEvent(QPaintEvent *event) { QPoint center(width() / 2, height() / 2); - QColor bg(isChecked() ? "#FFFFFF" : "#404040"); + QColor bg(isChecked() ? "#FFFFFF" : "#737373"); QColor accent(isChecked() ? "#FF0000" : "#FFFFFF"); + if (!isEnabled()) { + bg = QColor("#404040"); + accent = QColor("#FFFFFF"); + } + if (isDown()) { accent.setAlphaF(0.7); } @@ -62,7 +72,9 @@ BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QW btn = new RecordButton(this); vlayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight); QObject::connect(btn, &QPushButton::clicked, [=](bool checked) { - Params().putBool("EnableLogging", checked); + btn->setEnabled(false); + Params().putBool("DisableLogging", !checked); + last_button = nanos_since_boot(); }); w->raise(); @@ -70,8 +82,6 @@ BodyWindow::BodyWindow(QWidget *parent) : fuel_filter(1.0, 5., 1. / UI_FREQ), QW } void BodyWindow::paintEvent(QPaintEvent *event) { - //QLabel::paintEvent(event); - QPainter p(this); p.setRenderHint(QPainter::Antialiasing); @@ -112,6 +122,11 @@ void BodyWindow::paintEvent(QPaintEvent *event) { } } +void BodyWindow::offroadTransition(bool offroad) { + btn->setChecked(true); + btn->setEnabled(true); + fuel_filter.reset(1.0); +} void BodyWindow::updateState(const UIState &s) { if (!isVisible()) { @@ -132,5 +147,15 @@ void BodyWindow::updateState(const UIState &s) { face->movie()->start(); } + // update record button state + if (sm.updated("managerState") && (sm.rcv_time("managerState") - last_button)*1e-9 > 0.5) { + for (auto proc : sm["managerState"].getManagerState().getProcesses()) { + if (proc.getName() == "loggerd") { + btn->setEnabled(true); + btn->setChecked(proc.getRunning()); + } + } + } + update(); } diff --git a/selfdrive/ui/qt/body.h b/selfdrive/ui/qt/body.h index b49192a2bf..6555487bf1 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -25,6 +25,7 @@ public: private: bool charging = false; + uint64_t last_button = 0; FirstOrderFilter fuel_filter; QLabel *face; QMovie *awake, *sleep; @@ -33,4 +34,5 @@ private: private slots: void updateState(const UIState &s); + void offroadTransition(bool onroad); }; diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 0b64900770..fbd458cc02 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -32,7 +32,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { body = new BodyWindow(this); slayout->addWidget(body); - body->setEnabled(false); driver_view = new DriverViewWindow(this); connect(driver_view, &DriverViewWindow::done, [=] { @@ -59,6 +58,7 @@ void HomeWindow::updateState(const UIState &s) { } void HomeWindow::offroadTransition(bool offroad) { + body->setEnabled(false); sidebar->setVisible(offroad); if (offroad) { slayout->setCurrentWidget(home); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 0debb5ba04..ab99b8fc24 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -233,7 +233,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman", - "wideRoadCameraState", + "wideRoadCameraState", "managerState", }); Params params; From e3fd6a7c5074d5f06edf4738f46347844a5cef4f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 May 2022 15:00:47 -0700 Subject: [PATCH 11/13] Add azure-storage-blob to dev packages (#24426) * add azure-storage-blob * same --- Pipfile | 1 + Pipfile.lock | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 6d30179b9d..473b873077 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +azure-storage-blob = "~=2.1" control = "*" coverage = "*" dictdiffer = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 2c62e13dfa..4398c37d29 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "41e285b10b9b5a353b3eb9c202886e52d22403c369784877aaa35e099aa203a8" + "sha256": "918c8cba5c6a0242dc0f6ea74246176a1c54f0a9395feddf35af2189cc813378" }, "pipfile-spec": 6, "requires": { @@ -1111,6 +1111,28 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==21.4.0" }, + "azure-common": { + "hashes": [ + "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3", + "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad" + ], + "version": "==1.1.28" + }, + "azure-storage-blob": { + "hashes": [ + "sha256:a8e91a51d4f62d11127c7fd8ba0077385c5b11022f0269f8a2a71b9fc36bef31", + "sha256:b90323aad60f207f9f90a0c4cf94c10acc313c20b39403398dfba51f25f7b454" + ], + "index": "pypi", + "version": "==2.1.0" + }, + "azure-storage-common": { + "hashes": [ + "sha256:b01a491a18839b9d05a4fe3421458a0ddb5ab9443c14e487f40d16f9a1dc2fbe", + "sha256:ccedef5c67227bc4d6670ffd37cec18fb529a1b7c3a5e53e4096eb0cf23dc73f" + ], + "version": "==2.1.0" + }, "babel": { "hashes": [ "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", From 674a2061fd9f0213b14851d82832cff6d684d12f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 May 2022 15:08:08 -0700 Subject: [PATCH 12/13] CI: allow triggering prebuilt workflow manually --- .github/workflows/prebuilt.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 80942b3bc5..807ac06b94 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -3,6 +3,8 @@ on: schedule: - cron: '0 * * * *' + workflow_dispatch: + env: BASE_IMAGE: openpilot-base DOCKER_REGISTRY: ghcr.io/commaai From 1bc6f2fa7db49ebd3d29063e2a93234488b88db0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 May 2022 15:58:08 -0700 Subject: [PATCH 13/13] increase cruiseMismatch threshold (#24428) --- selfdrive/controls/controlsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 6dff3bb2e1..498218ab46 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -343,7 +343,7 @@ class Controls: # Check for mismatch between openpilot and car's PCM cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0 - if self.cruise_mismatch_counter > int(3. / DT_CTRL): + if self.cruise_mismatch_counter > int(6. / DT_CTRL): self.events.add(EventName.cruiseMismatch) # Check for FCW