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 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", diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index bea12aca11..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 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 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..8fa0454011 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -1,18 +1,30 @@ 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 + +def logging(started, params, CP: car.CarParams) -> bool: + run = (not CP.notCar) or not params.get_bool("DisableLogging") + return started and run + 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("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"]), @@ -25,7 +37,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 +50,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")), diff --git a/selfdrive/ui/qt/body.cc b/selfdrive/ui/qt/body.cc index a83d749577..7ca064a6cf 100644 --- a/selfdrive/ui/qt/body.cc +++ b/selfdrive/ui/qt/body.cc @@ -1,48 +1,132 @@ #include "selfdrive/ui/qt/body.h" #include +#include #include +#include -BodyWindow::BodyWindow(QWidget *parent) : QLabel(parent) { +#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) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QPoint center(width() / 2, height() / 2); + + QColor bg(isChecked() ? "#FFFFFF" : "#737373"); + QColor accent(isChecked() ? "#FF0000" : "#FFFFFF"); + if (!isEnabled()) { + bg = QColor("#404040"); + accent = QColor("#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) { + btn->setEnabled(false); + Params().putBool("DisableLogging", !checked); + last_button = nanos_since_boot(); + }); + w->raise(); QObject::connect(uiState(), &UIState::uiUpdate, this, &BodyWindow::updateState); } void BodyWindow::paintEvent(QPaintEvent *event) { - QLabel::paintEvent(event); - QPainter p(this); p.setRenderHint(QPainter::Antialiasing); + + 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)); + 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)); } } +void BodyWindow::offroadTransition(bool offroad) { + btn->setChecked(true); + btn->setEnabled(true); + fuel_filter.reset(1.0); +} void BodyWindow::updateState(const UIState &s) { if (!isVisible()) { @@ -52,14 +136,25 @@ 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; QMovie *m = standstill ? sleep : awake; - if (m != movie()) { - setMovie(m); - movie()->start(); + if (m != face->movie()) { + face->setMovie(m); + 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 c1138b8703..6555487bf1 100644 --- a/selfdrive/ui/qt/body.h +++ b/selfdrive/ui/qt/body.h @@ -2,20 +2,37 @@ #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: BodyWindow(QWidget* parent = 0); private: - float fuel = 1.0; + bool charging = false; + uint64_t last_button = 0; + FirstOrderFilter fuel_filter; + QLabel *face; QMovie *awake, *sleep; + RecordButton *btn; void paintEvent(QPaintEvent*) override; 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/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]); } } 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); 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) { 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) 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; 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. diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index b03dc84f61..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()) @@ -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,15 +100,15 @@ 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) + # 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: @@ -117,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']: @@ -138,40 +140,41 @@ 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: failed to bind boardd logs to a frame ID. Add a timestamp cloudlog in controlsd.") + elif failed_inserts > len(timestamps): + 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) - y0 = min(start_times.keys()) + t0 = find_t0(start_times) 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) + 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]: @@ -180,9 +183,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 +215,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: