openpilot v0.9.9 release (#35334)

* openpilot v0.9.9 release

date: 2025-06-05T19:54:08
master commit: 8aadf02b2fd91f4e1285e18c2c7feb32d93b66f5

* AGNOS 12.4 (#35558)

agnos12.4

---------

Co-authored-by: Vehicle Researcher <user@comma.ai>
Co-authored-by: Maxime Desroches <desroches.maxime@gmail.com>
This commit is contained in:
Adeeb Shihadeh
2025-06-17 16:32:08 -07:00
committed by GitHub
parent dd778596b7
commit 100f89a161
1240 changed files with 310104 additions and 30549 deletions
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env expect
spawn adb shell
expect "#"
send "cd data/openpilot\r"
send "export TERM=xterm-256color\r"
send "su comma\r"
send "clear\r"
interact
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
import sys
from openpilot.tools.lib.logreader import LogReader
def main():
if len(sys.argv) != 2:
print("Usage: python auto_source.py <log_path>")
sys.exit(1)
log_path = sys.argv[1]
lr = LogReader(log_path, sort_by_time=True)
print("\n".join(lr.logreader_identifiers))
if __name__ == "__main__":
main()
+2
View File
@@ -12,6 +12,8 @@ Options:
-h, --help Displays help on commandline options.
--help-all Displays help including Qt specific options.
--demo use a demo route instead of providing your own
--auto Auto load the route from the best available source (no video):
internal, openpilotci, comma_api, car_segments, testing_closet
--qcam load qcamera
--ecam load wide road camera
--msgq read can messages from msgq
+1 -1
View File
@@ -29,7 +29,7 @@ cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcans
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'utils/export.cc', 'utils/util.cc',
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
if GetOption('extras'):
+3 -1
View File
@@ -23,6 +23,7 @@ int main(int argc, char *argv[]) {
cmd_parser.addHelpOption();
cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai");
cmd_parser.addOption({"demo", "use a demo route instead of providing your own"});
cmd_parser.addOption({"auto", "Auto load the route from the best available source (no video): internal, openpilotci, comma_api, car_segments, testing_closet"});
cmd_parser.addOption({"qcam", "load qcamera"});
cmd_parser.addOption({"ecam", "load wide road camera"});
cmd_parser.addOption({"dcam", "load driver camera"});
@@ -69,7 +70,8 @@ int main(int argc, char *argv[]) {
}
if (!route.isEmpty()) {
auto replay_stream = std::make_unique<ReplayStream>(&app);
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
bool auto_source = cmd_parser.isSet("auto");
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags, auto_source)) {
return 0;
}
stream = replay_stream.release();
+26 -15
View File
@@ -180,25 +180,36 @@ bool cabana::Signal::operator==(const cabana::Signal &other) const {
// helper functions
double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig) {
int64_t val = 0;
const int msb_byte = sig.msb / 8;
if (msb_byte >= (int)data_size) return 0;
int i = sig.msb / 8;
int bits = sig.size;
while (i >= 0 && i < data_size && bits > 0) {
int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8;
int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1;
int size = msb - lsb + 1;
const int lsb_byte = sig.lsb / 8;
uint64_t val = 0;
uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1);
val |= d << (bits - size);
bits -= size;
i = sig.is_little_endian ? i - 1 : i + 1;
// Fast path: signal fits in a single byte
if (msb_byte == lsb_byte) {
val = (data[msb_byte] >> (sig.lsb & 7)) & ((1ULL << sig.size) - 1);
} else {
// Multi-byte case: signal spans across multiple bytes
int bits = sig.size;
int i = msb_byte;
const int step = sig.is_little_endian ? -1 : 1;
while (i >= 0 && i < (int)data_size && bits > 0) {
const int msb = (i == msb_byte) ? sig.msb & 7 : 7;
const int lsb = (i == lsb_byte) ? sig.lsb & 7 : 0;
const int nbits = msb - lsb + 1;
val = (val << nbits) | ((data[i] >> lsb) & ((1ULL << nbits) - 1));
bits -= nbits;
i += step;
}
}
if (sig.is_signed) {
val -= ((val >> (sig.size - 1)) & 0x1) ? (1ULL << sig.size) : 0;
// Sign extension (if needed)
if (sig.is_signed && (val & (1ULL << (sig.size - 1)))) {
val |= ~((1ULL << sig.size) - 1);
}
return val * sig.factor + sig.offset;
return static_cast<int64_t>(val) * sig.factor + sig.offset;
}
void updateMsbLsb(cabana::Signal &s) {
+3 -2
View File
@@ -7,6 +7,7 @@
#include <QPushButton>
#include "common/timing.h"
#include "common/util.h"
#include "tools/cabana/streams/routes.h"
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
@@ -45,9 +46,9 @@ void ReplayStream::mergeSegments() {
}
}
bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) {
bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags, bool auto_source) {
replay.reset(new Replay(route.toStdString(), {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"},
{}, nullptr, replay_flags, data_dir.toStdString()));
{}, nullptr, replay_flags, data_dir.toStdString(), auto_source));
replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter([this](const Event *event) { return eventFilter(event); });
+1 -1
View File
@@ -18,7 +18,7 @@ class ReplayStream : public AbstractStream {
public:
ReplayStream(QObject *parent);
void start() override { replay->start(); }
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false);
bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
bool liveStreaming() const override { return false; }
+40
View File
@@ -0,0 +1,40 @@
#include "tools/cabana/tools/routeinfo.h"
#include <QHeaderView>
#include <QScrollBar>
#include <QTableWidget>
#include <QVBoxLayout>
#include "tools/cabana/streams/replaystream.h"
RouteInfoDlg::RouteInfoDlg(QWidget *parent) : QDialog(parent) {
auto *replay = qobject_cast<ReplayStream *>(can)->getReplay();
setWindowTitle(tr("Route: %1").arg(QString::fromStdString(replay->route().name())));
auto *table = new QTableWidget(replay->route().segments().size(), 7, this);
table->setToolTip(tr("Click on a row to seek to the corresponding segment."));
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setSelectionMode(QAbstractItemView::SingleSelection);
table->setHorizontalHeaderLabels({"", "rlog", "fcam", "ecam", "dcam", "qlog", "qcam"});
table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
table->verticalHeader()->setVisible(false);
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
int row = 0;
for (const auto &[seg_num, seg] : replay->route().segments()) {
table->setItem(row, 0, new QTableWidgetItem(QString::number(seg_num)));
table->setItem(row, 1, new QTableWidgetItem(seg.rlog.empty() ? "--" : "Yes"));
table->setItem(row, 2, new QTableWidgetItem(seg.road_cam.empty() ? "--" : "Yes"));
table->setItem(row, 3, new QTableWidgetItem(seg.wide_road_cam.empty() ? "--" : "Yes"));
table->setItem(row, 4, new QTableWidgetItem(seg.driver_cam.empty() ? "--" : "Yes"));
table->setItem(row, 5, new QTableWidgetItem(seg.qlog.empty() ? "--" : "Yes"));
table->setItem(row, 6, new QTableWidgetItem(seg.qcamera.empty() ? "--" : "Yes"));
++row;
}
table->setMinimumWidth(table->horizontalHeader()->length() + table->verticalScrollBar()->sizeHint().width());
table->setMinimumHeight(table->rowHeight(0) * std::min(table->rowCount(), 13) + table->horizontalHeader()->height() + table->frameWidth() * 2);
connect(table, &QTableWidget::itemClicked, [](QTableWidgetItem *item) { can->seekTo(item->row() * 60.0); });
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(table);
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <QDialog>
class RouteInfoDlg : public QDialog {
Q_OBJECT
public:
RouteInfoDlg(QWidget *parent = nullptr);
};
+13 -2
View File
@@ -11,6 +11,8 @@
#include <QVBoxLayout>
#include <QtConcurrent>
#include "tools/cabana/tools/routeinfo.h"
const int MIN_VIDEO_HEIGHT = 100;
const int THUMBNAIL_MARGIN = 3;
@@ -100,9 +102,12 @@ void VideoWidget::createPlaybackController() {
if (!can->liveStreaming()) {
toolbar->addAction(utils::icon("repeat"), tr("Loop playback"), this, &VideoWidget::loopPlaybackClicked);
createSpeedDropdown(toolbar);
toolbar->addSeparator();
toolbar->addAction(utils::icon("info-circle"), tr("View route details"), this, &VideoWidget::showRouteInfo);
} else {
createSpeedDropdown(toolbar);
}
createSpeedDropdown(toolbar);
}
void VideoWidget::createSpeedDropdown(QToolBar *toolbar) {
@@ -230,6 +235,12 @@ void VideoWidget::showThumbnail(double seconds) {
slider->update();
}
void VideoWidget::showRouteInfo() {
RouteInfoDlg *route_info = new RouteInfoDlg(this);
route_info->setAttribute(Qt::WA_DeleteOnClose);
route_info->show();
}
bool VideoWidget::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds()));
+1
View File
@@ -71,6 +71,7 @@ protected:
void createSpeedDropdown(QToolBar *toolbar);
void loopPlaybackClicked();
void vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams);
void showRouteInfo();
StreamCameraView *cam_widget;
QAction *time_display_action = nullptr;
+320
View File
@@ -0,0 +1,320 @@
#!/usr/bin/env python3
import atexit
import logging
import os
import platform
import shutil
import sys
import time
from argparse import ArgumentParser, ArgumentTypeError
from collections.abc import Sequence
from pathlib import Path
from random import randint
from subprocess import Popen, PIPE
from typing import Literal
from cereal.messaging import SubMaster
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params, UnknownKeyName
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.tools.lib.route import Route
from openpilot.tools.lib.logreader import LogReader
DEFAULT_OUTPUT = 'output.mp4'
DEMO_START = 90
DEMO_END = 105
DEMO_ROUTE = 'a2a0ccea32023010/2023-07-27--13-01-19'
FRAMERATE = 20
PIXEL_DEPTH = '24'
RESOLUTION = '2160x1080'
SECONDS_TO_WARM = 2
PROC_WAIT_SECONDS = 30
OPENPILOT_FONT = str(Path(BASEDIR, 'selfdrive/assets/fonts/Inter-Regular.ttf').resolve())
REPLAY = str(Path(BASEDIR, 'tools/replay/replay').resolve())
UI = str(Path(BASEDIR, 'selfdrive/ui/ui').resolve())
logger = logging.getLogger('clip.py')
def check_for_failure(proc: Popen):
exit_code = proc.poll()
if exit_code is not None and exit_code != 0:
cmd = str(proc.args)
if isinstance(proc.args, str):
cmd = proc.args
elif isinstance(proc.args, Sequence):
cmd = str(proc.args[0])
msg = f'{cmd} failed, exit code {exit_code}'
logger.error(msg)
stdout, stderr = proc.communicate()
if stdout:
logger.error(stdout.decode())
if stderr:
logger.error(stderr.decode())
raise ChildProcessError(msg)
def escape_ffmpeg_text(value: str):
special_chars = {',': '\\,', ':': '\\:', '=': '\\=', '[': '\\[', ']': '\\]'}
value = value.replace('\\', '\\\\\\\\\\\\\\\\')
for char, escaped in special_chars.items():
value = value.replace(char, escaped)
return value
def get_logreader(route: Route):
return LogReader(route.qlog_paths()[0] if len(route.qlog_paths()) else route.name.canonical_name)
def get_meta_text(lr: LogReader, route: Route):
init_data = lr.first('initData')
car_params = lr.first('carParams')
origin_parts = init_data.gitRemote.split('/')
origin = origin_parts[3] if len(origin_parts) > 3 else 'unknown'
return ', '.join([
f"openpilot v{init_data.version}",
f"route: {route.name.canonical_name}",
f"car: {car_params.carFingerprint}",
f"origin: {origin}",
f"branch: {init_data.gitBranch}",
f"commit: {init_data.gitCommit[:7]}",
f"modified: {str(init_data.dirty).lower()}",
])
def parse_args(parser: ArgumentParser):
args = parser.parse_args()
if args.demo:
args.route = DEMO_ROUTE
if args.start is None or args.end is None:
args.start = DEMO_START
args.end = DEMO_END
elif args.route.count('/') == 1:
if args.start is None or args.end is None:
parser.error('must provide both start and end if timing is not in the route ID')
elif args.route.count('/') == 3:
if args.start is not None or args.end is not None:
parser.error('don\'t provide timing when including it in the route ID')
parts = args.route.split('/')
args.route = '/'.join(parts[:2])
args.start = int(parts[2])
args.end = int(parts[3])
if args.end <= args.start:
parser.error(f'end ({args.end}) must be greater than start ({args.start})')
if args.start < SECONDS_TO_WARM:
parser.error(f'start must be greater than {SECONDS_TO_WARM}s to allow the UI time to warm up')
try:
args.route = Route(args.route, data_dir=args.data_dir)
except Exception as e:
parser.error(f'failed to get route: {e}')
# FIXME: length isn't exactly max segment seconds, simplify to replay exiting at end of data
length = round(args.route.max_seg_number * 60)
if args.start >= length:
parser.error(f'start ({args.start}s) cannot be after end of route ({length}s)')
if args.end > length:
parser.error(f'end ({args.end}s) cannot be after end of route ({length}s)')
return args
def populate_car_params(lr: LogReader):
init_data = lr.first('initData')
assert init_data is not None
params = Params()
entries = init_data.params.entries
for cp in entries:
key, value = cp.key, cp.value
try:
params.put(key, value)
except UnknownKeyName:
# forks of openpilot may have other Params keys configured. ignore these
logger.warning(f"unknown Params key '{key}', skipping")
logger.debug('persisted CarParams')
def start_proc(args: list[str], env: dict[str, str]):
return Popen(args, env=env, stdout=PIPE, stderr=PIPE)
def validate_env(parser: ArgumentParser):
if platform.system() not in ['Linux']:
parser.exit(1, f'clip.py: error: {platform.system()} is not a supported operating system\n')
for proc in ['Xvfb', 'ffmpeg']:
if shutil.which(proc) is None:
parser.exit(1, f'clip.py: error: missing {proc} command, is it installed?\n')
for proc in [REPLAY, UI]:
if shutil.which(proc) is None:
parser.exit(1, f'clip.py: error: missing {proc} command, did you build openpilot yet?\n')
def validate_output_file(output_file: str):
if not output_file.endswith('.mp4'):
raise ArgumentTypeError('output must be an mp4')
return output_file
def validate_route(route: str):
if route.count('/') not in (1, 3):
raise ArgumentTypeError(f'route must include or exclude timing, example: {DEMO_ROUTE}')
return route
def validate_title(title: str):
if len(title) > 80:
raise ArgumentTypeError('title must be no longer than 80 chars')
return title
def wait_for_frames(procs: list[Popen]):
sm = SubMaster(['uiDebug'])
no_frames_drawn = True
while no_frames_drawn:
sm.update()
no_frames_drawn = sm['uiDebug'].drawTimeMillis == 0.
for proc in procs:
check_for_failure(proc)
def clip(
data_dir: str | None,
quality: Literal['low', 'high'],
prefix: str,
route: Route,
out: str,
start: int,
end: int,
target_mb: int,
title: str | None,
):
logger.info(f'clipping route {route.name.canonical_name}, start={start} end={end} quality={quality} target_filesize={target_mb}MB')
lr = get_logreader(route)
begin_at = max(start - SECONDS_TO_WARM, 0)
duration = end - start
bit_rate_kbps = int(round(target_mb * 8 * 1024 * 1024 / duration / 1000))
# TODO: evaluate creating fn that inspects /tmp/.X11-unix and creates unused display to avoid possibility of collision
display = f':{randint(99, 999)}'
box_style = 'box=1:boxcolor=black@0.33:boxborderw=7'
meta_text = get_meta_text(lr, route)
overlays = [
# metadata overlay
f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=15:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'",
# route time overlay
f"drawtext=text='%{{eif\\:floor(({start}+t)/60)\\:d\\:2}}\\:%{{eif\\:mod({start}+t\\,60)\\:d\\:2}}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=24:{box_style}:x=w-text_w-38:y=38"
]
if title:
overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53")
ffmpeg_cmd = [
'ffmpeg', '-y',
'-video_size', RESOLUTION,
'-framerate', str(FRAMERATE),
'-f', 'x11grab',
'-rtbufsize', '100M',
'-draw_mouse', '0',
'-i', display,
'-c:v', 'libx264',
'-maxrate', f'{bit_rate_kbps}k',
'-bufsize', f'{bit_rate_kbps*2}k',
'-crf', '23',
'-filter:v', ','.join(overlays),
'-preset', 'ultrafast',
'-tune', 'zerolatency',
'-pix_fmt', 'yuv420p',
'-movflags', '+faststart',
'-f', 'mp4',
'-t', str(duration),
out,
]
replay_cmd = [REPLAY, '--ecam', '-c', '1', '-s', str(begin_at), '--prefix', prefix]
if data_dir:
replay_cmd.extend(['--data_dir', data_dir])
if quality == 'low':
replay_cmd.append('--qcam')
replay_cmd.append(route.name.canonical_name)
ui_cmd = [UI, '-platform', 'xcb']
xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}']
with OpenpilotPrefix(prefix, shared_download_cache=True):
populate_car_params(lr)
env = os.environ.copy()
env['DISPLAY'] = display
xvfb_proc = start_proc(xvfb_cmd, env)
atexit.register(lambda: xvfb_proc.terminate())
ui_proc = start_proc(ui_cmd, env)
atexit.register(lambda: ui_proc.terminate())
replay_proc = start_proc(replay_cmd, env)
atexit.register(lambda: replay_proc.terminate())
procs = [replay_proc, ui_proc, xvfb_proc]
logger.info('waiting for replay to begin (loading segments, may take a while)...')
wait_for_frames(procs)
logger.debug(f'letting UI warm up ({SECONDS_TO_WARM}s)...')
time.sleep(SECONDS_TO_WARM)
for proc in procs:
check_for_failure(proc)
ffmpeg_proc = start_proc(ffmpeg_cmd, env)
procs.append(ffmpeg_proc)
atexit.register(lambda: ffmpeg_proc.terminate())
logger.info(f'recording in progress ({duration}s)...')
ffmpeg_proc.wait(duration + PROC_WAIT_SECONDS)
for proc in procs:
check_for_failure(proc)
logger.info(f'recording complete: {Path(out).resolve()}')
def main():
p = ArgumentParser(prog='clip.py', description='clip your openpilot route.', epilog='comma.ai')
validate_env(p)
route_group = p.add_mutually_exclusive_group(required=True)
route_group.add_argument('route', nargs='?', type=validate_route, help=f'The route (e.g. {DEMO_ROUTE} or {DEMO_ROUTE}/{DEMO_START}/{DEMO_END})')
route_group.add_argument('--demo', help='use the demo route', action='store_true')
p.add_argument('-d', '--data-dir', help='local directory where route data is stored')
p.add_argument('-e', '--end', help='stop clipping at <end> seconds', type=int)
p.add_argument('-f', '--file-size', help='target file size (Discord/GitHub support max 10MB, default is 9MB)', type=float, default=9.)
p.add_argument('-o', '--output', help='output clip to (.mp4)', type=validate_output_file, default=DEFAULT_OUTPUT)
p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}')
p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high')
p.add_argument('-s', '--start', help='start clipping at <start> seconds', type=int)
p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title)
args = parse_args(p)
exit_code = 1
try:
clip(
data_dir=args.data_dir,
quality=args.quality,
prefix=args.prefix,
route=args.route,
out=args.output,
start=args.start,
end=args.end,
target_mb=args.file_size,
title=args.title,
)
exit_code = 0
except KeyboardInterrupt as e:
logger.exception('interrupted by user', exc_info=e)
except Exception as e:
logger.exception('encountered error', exc_info=e)
finally:
atexit._run_exitfuncs()
sys.exit(exit_code)
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s %(levelname)s\t%(message)s')
main()
+2 -1
View File
@@ -63,7 +63,8 @@ function install_ubuntu_common_requirements() {
libqt5svg5-dev \
libqt5serialbus5-dev \
libqt5x11extras5-dev \
libqt5opengl5-dev
libqt5opengl5-dev \
xvfb
}
# Install Ubuntu 24.04 LTS packages
+2 -2
View File
@@ -28,7 +28,7 @@ class Keyboard:
key = self.kb.getch().lower()
self.cancel = False
if key == 'r':
self.axes_values = {ax: 0. for ax in self.axes_values}
self.axes_values = dict.fromkeys(self.axes_values, 0.)
elif key == 'c':
self.cancel = True
elif key in self.axes_map:
@@ -65,7 +65,7 @@ class Joystick:
try:
joystick_event = get_gamepad()[0]
except (OSError, UnpluggedError):
self.axes_values = {ax: 0. for ax in self.axes_values}
self.axes_values = dict.fromkeys(self.axes_values, 0.)
return False
event = (joystick_event.code, joystick_event.state)
+1 -1
View File
@@ -10,7 +10,7 @@ from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
LongCtrlState = car.CarControl.Actuators.LongControlState
MAX_LAT_ACCEL = 2.5
MAX_LAT_ACCEL = 3.0
def joystickd_thread():
+4 -26
View File
@@ -209,7 +209,7 @@ class BaseFrameReader:
def close(self):
pass
def get(self, num, count=1, pix_fmt="yuv420p"):
def get(self, num, count=1, pix_fmt="rgb24"):
raise NotImplementedError
@@ -497,7 +497,7 @@ class GOPFrameReader(BaseFrameReader):
return self.frame_cache[(num, pix_fmt)]
def get(self, num, count=1, pix_fmt="yuv420p"):
def get(self, num, count=1, pix_fmt="rgb24"):
assert self.frame_count is not None
if num + count > self.frame_count:
@@ -523,37 +523,15 @@ class StreamFrameReader(StreamGOPReader, GOPFrameReader):
GOPFrameReader.__init__(self, readahead, readbehind)
def GOPFrameIterator(gop_reader, pix_fmt):
def GOPFrameIterator(gop_reader, pix_fmt='rgb24'):
dec = VideoStreamDecompressor(gop_reader.fn, gop_reader.vid_fmt, gop_reader.w, gop_reader.h, pix_fmt)
yield from dec.read()
def FrameIterator(fn, pix_fmt, **kwargs):
def FrameIterator(fn, pix_fmt='rgb24', **kwargs):
fr = FrameReader(fn, **kwargs)
if isinstance(fr, GOPReader):
yield from GOPFrameIterator(fr, pix_fmt)
else:
for i in range(fr.frame_count):
yield fr.get(i, pix_fmt=pix_fmt)[0]
class NumpyFrameReader:
def __init__(self, name, w, h, cache_size):
self.name = name
self.pos = -1
self.frames = None
self.w = w
self.h = h
self.cache_size = cache_size
def close(self):
pass
def get(self, num, count=1, pix_fmt="nv12"):
num -= 1
q = num // self.cache_size
if q != self.pos:
del self.frames
self.pos = q
self.frames = np.load(f'{self.name}_{self.pos}.npy')
return [self.frames[num % self.cache_size]]
+1 -2
View File
@@ -87,8 +87,8 @@ class GithubUtils:
def comment_on_pr(self, comment, pr_branch, commenter="", overwrite=False):
pr_number = self.get_pr_number(pr_branch)
data = f'{{"body": "{comment}"}}'
github_path = f'issues/{pr_number}/comments'
if overwrite:
github_path = f'issues/{pr_number}/comments'
r = self.api_call(github_path)
comments = [x['id'] for x in r.json() if x['user']['login'] == commenter]
if comments:
@@ -96,7 +96,6 @@ class GithubUtils:
self.api_call(github_path, data=data, method=HTTPMethod.PATCH)
return
github_path=f'issues/{pr_number}/comments'
self.api_call(github_path, data=data, method=HTTPMethod.POST)
# upload files to github and comment them on the pr
+2 -2
View File
@@ -300,11 +300,11 @@ class LogReader:
def _run_on_segment(self, func, i):
return func(self._get_lr(i))
def run_across_segments(self, num_processes, func, desc=None):
def run_across_segments(self, num_processes, func, disable_tqdm=False, desc=None):
with multiprocessing.Pool(num_processes) as pool:
ret = []
num_segs = len(self.logreader_identifiers)
for p in tqdm.tqdm(pool.imap(partial(self._run_on_segment, func), range(num_segs)), total=num_segs, desc=desc):
for p in tqdm.tqdm(pool.imap(partial(self._run_on_segment, func), range(num_segs)), total=num_segs, disable=disable_tqdm, desc=desc):
ret.extend(p)
return ret
+27 -3
View File
@@ -1,12 +1,13 @@
import os
import re
import requests
from functools import cache
from urllib.parse import urlparse
from collections import defaultdict
from itertools import chain
from openpilot.tools.lib.auth_config import get_token
from openpilot.tools.lib.api import CommaApi
from openpilot.tools.lib.api import APIError, CommaApi
from openpilot.tools.lib.helpers import RE
QLOG_FILENAMES = ['qlog', 'qlog.bz2', 'qlog.zst']
@@ -19,6 +20,7 @@ ECAMERA_FILENAMES = ['ecamera.hevc']
class Route:
def __init__(self, name, data_dir=None):
self._metadata = None
self._name = RouteName(name)
self.files = None
if data_dir is not None:
@@ -27,6 +29,13 @@ class Route:
self._segments = self._get_segments_remote()
self.max_seg_number = self._segments[-1].name.segment_num
@property
def metadata(self):
if not self._metadata:
api = CommaApi(get_token())
self._metadata = api.get('v1/route/' + self.name.canonical_name)
return self._metadata
@property
def name(self):
return self._name
@@ -78,6 +87,7 @@ class Route:
url if fn in DCAMERA_FILENAMES else segments[segment_name].dcamera_path,
url if fn in ECAMERA_FILENAMES else segments[segment_name].ecamera_path,
url if fn in QCAMERA_FILENAMES else segments[segment_name].qcamera_path,
self.metadata['url'],
)
else:
segments[segment_name] = Segment(
@@ -88,6 +98,7 @@ class Route:
url if fn in DCAMERA_FILENAMES else None,
url if fn in ECAMERA_FILENAMES else None,
url if fn in QCAMERA_FILENAMES else None,
self.metadata['url'],
)
return sorted(segments.values(), key=lambda seg: seg.name.segment_num)
@@ -153,7 +164,7 @@ class Route:
except StopIteration:
qcamera_path = None
segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path))
segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, self.metadata['url']))
if len(segments) == 0:
raise ValueError(f'Could not find segments for route {self.name.canonical_name} in data directory {data_dir}')
@@ -161,8 +172,10 @@ class Route:
class Segment:
def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path):
def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, url):
self._events = None
self._name = SegmentName(name)
self.url = f'{url}/{self._name.segment_num}'
self.log_path = log_path
self.qlog_path = qlog_path
self.camera_path = camera_path
@@ -174,6 +187,17 @@ class Segment:
def name(self):
return self._name
@property
def events(self):
if not self._events:
try:
resp = requests.get(f'{self.url}/events.json')
resp.raise_for_status()
self._events = resp.json()
except Exception as e:
raise APIError(f'error getting events for segment {self._name}') from e
return self._events
class RouteName:
def __init__(self, name_str: str):
+1 -2
View File
@@ -16,8 +16,7 @@ CHUNK_SIZE = 1000 * K
logging.getLogger("urllib3").setLevel(logging.WARNING)
def hash_256(link: str) -> str:
hsh = str(sha256((link.split("?")[0]).encode('utf-8')).hexdigest())
return hsh
return sha256((link.split("?")[0]).encode('utf-8')).hexdigest()
class URLFileException(Exception):
+2
View File
@@ -28,6 +28,8 @@ if [[ $(command -v brew) == "" ]]; then
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> $RC_FILE
eval "$(/opt/homebrew/bin/brew shellenv)"
fi
else
brew up
fi
brew bundle --file=- <<-EOS
+25 -7
View File
@@ -52,6 +52,7 @@ function op_run_command() {
# be default, assume openpilot dir is in current directory
OPENPILOT_ROOT=$(pwd)
function op_get_openpilot_dir() {
# First try traversing up the directory tree
while [[ "$OPENPILOT_ROOT" != '/' ]];
do
if find "$OPENPILOT_ROOT/launch_openpilot.sh" -maxdepth 1 -mindepth 1 &> /dev/null; then
@@ -59,6 +60,14 @@ function op_get_openpilot_dir() {
fi
OPENPILOT_ROOT="$(readlink -f "$OPENPILOT_ROOT/"..)"
done
# Fallback to hardcoded directories if not found
for dir in "$HOME/openpilot" "/data/openpilot"; do
if [[ -f "$dir/launch_openpilot.sh" ]]; then
OPENPILOT_ROOT="$dir"
return 0
fi
done
}
function op_install_post_commit() {
@@ -245,7 +254,7 @@ function op_setup() {
function op_auth() {
op_before_cmd
op_run_command tools/lib/auth.py
op_run_command tools/lib/auth.py "$@"
}
function op_activate_venv() {
@@ -275,7 +284,7 @@ function op_venv() {
function op_adb() {
op_before_cmd
op_run_command tools/adb_shell.sh
op_run_command tools/scripts/adb_ssh.sh
}
function op_check() {
@@ -284,6 +293,11 @@ function op_check() {
unset VERBOSE
}
function op_esim() {
op_before_cmd
op_run_command system/hardware/esim.py "$@"
}
function op_build() {
CDIR=$(pwd)
op_before_cmd
@@ -328,6 +342,11 @@ function op_sim() {
op_run_command exec tools/sim/launch_openpilot.sh
}
function op_clip() {
op_before_cmd
op_run_command tools/clip/run.py $@
}
function op_switch() {
REMOTE="origin"
if [ "$#" -gt 1 ]; then
@@ -373,16 +392,12 @@ function op_default() {
echo " op is only a wrapper for existing scripts, tools, and commands."
echo " op will always show you what it will run on your system."
echo ""
echo " op will try to find your openpilot directory in the following order:"
echo " 1: use the directory specified with the --dir option"
echo " 2: use the current working directory"
echo " 3: go up the file tree non-recursively"
echo ""
echo -e "${BOLD}${UNDERLINE}Usage:${NC} op [OPTIONS] <COMMAND>"
echo ""
echo -e "${BOLD}${UNDERLINE}Commands [System]:${NC}"
echo -e " ${BOLD}auth${NC} Authenticate yourself for API use"
echo -e " ${BOLD}check${NC} Check the development environment (git, os, python) to start using openpilot"
echo -e " ${BOLD}esim${NC} Manage eSIM profiles on your comma device"
echo -e " ${BOLD}venv${NC} Activate the python virtual environment"
echo -e " ${BOLD}setup${NC} Install openpilot dependencies"
echo -e " ${BOLD}build${NC} Run the openpilot build system in the current working directory"
@@ -395,6 +410,7 @@ function op_default() {
echo -e " ${BOLD}juggle${NC} Run PlotJuggler"
echo -e " ${BOLD}replay${NC} Run Replay"
echo -e " ${BOLD}cabana${NC} Run Cabana"
echo -e " ${BOLD}clip${NC} Run clip (linux only)"
echo -e " ${BOLD}adb${NC} Run adb shell"
echo ""
echo -e "${BOLD}${UNDERLINE}Commands [Testing]:${NC}"
@@ -438,6 +454,7 @@ function _op() {
auth ) shift 1; op_auth "$@" ;;
venv ) shift 1; op_venv "$@" ;;
check ) shift 1; op_check "$@" ;;
esim ) shift 1; op_esim "$@" ;;
setup ) shift 1; op_setup "$@" ;;
build ) shift 1; op_build "$@" ;;
juggle ) shift 1; op_juggle "$@" ;;
@@ -445,6 +462,7 @@ function _op() {
lint ) shift 1; op_lint "$@" ;;
test ) shift 1; op_test "$@" ;;
replay ) shift 1; op_replay "$@" ;;
clip ) shift 1; op_clip "$@" ;;
sim ) shift 1; op_sim "$@" ;;
install ) shift 1; op_install "$@" ;;
switch ) shift 1; op_switch "$@" ;;
+4 -2
View File
@@ -61,13 +61,15 @@ def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None, pl
env["BASEDIR"] = BASEDIR
env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}"
if dbc:
if os.path.exists(dbc):
dbc = os.path.abspath(dbc)
env["DBC_NAME"] = dbc
extra_args = ""
if fn is not None:
extra_args += f" -d {fn}"
extra_args += f" -d {os.path.abspath(fn)}"
if layout is not None:
extra_args += f" -l {layout}"
extra_args += f" -l {os.path.abspath(layout)}"
if route_or_segment_name is not None:
extra_args += f" --window_title \"{route_or_segment_name}{f' ({platform})' if platform is not None else ''}\""
+62
View File
@@ -0,0 +1,62 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab tab_name="tab1" containers="1">
<Container>
<DockSplitter orientation="-" sizes="0.24977;0.250689;0.24977;0.24977" count="4">
<DockArea name="...">
<plot flip_y="false" flip_x="false" style="Lines" mode="TimeSeries">
<range left="0.000000" right="1678.753571" bottom="-0.025000" top="1.025000"/>
<limitY/>
<curve name="/gpsLocationExternal/hasFix" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_y="false" flip_x="false" style="Lines" mode="TimeSeries">
<range left="0.000000" right="1678.753571" bottom="-0.425000" top="17.425000"/>
<limitY/>
<curve name="/gpsLocationExternal/satelliteCount" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_y="false" flip_x="false" style="Lines" mode="TimeSeries">
<range left="0.000000" right="1678.753571" bottom="0.000000" top="3.000000"/>
<limitY max="3" min="0"/>
<curve name="/gpsLocationExternal/horizontalAccuracy" color="#1ac938"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_y="false" flip_x="false" style="Lines" mode="TimeSeries">
<range left="0.000000" right="1678.753571" bottom="-17.262000" top="766.374004"/>
<limitY/>
<curve name="/gpsLocationExternal/horizontalAccuracy" color="#1ac938"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="0"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<customMathEquations/>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -150,44 +150,44 @@
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
<limitY/>
<curve color="#d62728" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel"/>
<curve color="#1f77b4" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel"/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
</plot>
</DockArea>
<DockArea name="desired vs actual lateral acceleration (desired shifted by +0.1s)">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
<limitY/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
<transform alias="/controlsState/lateralControlState/torqueState/desiredLateralAccel[Scale/Offset]" name="Scale/Offset">
<options value_scale="1.0" time_offset="0.1" value_offset="0"/>
</transform>
</curve>
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
</plot>
</DockArea>
<DockArea name="desired vs actual lateral acceleration (desired shifted by +0.2s)">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
<limitY/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
<transform alias="/controlsState/lateralControlState/torqueState/desiredLateralAccel[Scale/Offset]" name="Scale/Offset">
<options value_scale="1.0" time_offset="0.2" value_offset="0"/>
</transform>
</curve>
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
</plot>
</DockArea>
<DockArea name="desired vs actual lateral acceleration (desired shifted by +0.3s)">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range top="3.586831" right="269.943117" left="0.134774" bottom="-2.354077"/>
<limitY/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/desiredLateralAccel">
<transform alias="/controlsState/lateralControlState/torqueState/desiredLateralAccel[Scale/Offset]" name="Scale/Offset">
<options value_scale="1.0" time_offset="0.3" value_offset="0"/>
</transform>
</curve>
<curve color="#ff7f0e" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
<curve color="#1ac938" name="/controlsState/lateralControlState/torqueState/actualLateralAccel"/>
</plot>
</DockArea>
</DockSplitter>
+3 -1
View File
@@ -64,6 +64,8 @@ Options:
-s, --start <seconds> start from <seconds>
-x <speed> playback <speed>. between 0.2 - 3
--demo use a demo route instead of providing your own
--auto Auto load the route from the best available source (no video):
internal, openpilotci, comma_api, car_segments, testing_closet
--data_dir <data_dir> local directory with routes
--prefix <prefix> set OPENPILOT_PREFIX
--dcam load driver camera
@@ -81,7 +83,7 @@ Arguments:
connect.comma.ai
```
## Visualize the Replay in the Openpilot UI
## Visualize the Replay in the openpilot UI
To visualize the replay within the openpilot UI, run the following commands:
```bash
+1 -1
View File
@@ -12,7 +12,7 @@ public:
ConsoleUI(Replay *replay);
~ConsoleUI();
int exec();
inline static const std::array speed_array = {0.2f, 0.5f, 1.0f, 2.0f, 3.0f};
inline static const std::array speed_array = {0.2f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f};
private:
void initWindows();
+8 -6
View File
@@ -19,6 +19,8 @@ Options:
-s, --start Start from <seconds>
-x, --playback Playback <speed>
--demo Use a demo route instead of providing your own
--auto Auto load the route from the best available source (no video):
internal, openpilotci, comma_api, car_segments, testing_closet
-d, --data_dir Local directory with routes
-p, --prefix Set OPENPILOT_PREFIX
--dcam Load driver camera
@@ -39,6 +41,7 @@ struct ReplayConfig {
std::string data_dir;
std::string prefix;
uint32_t flags = REPLAY_FLAG_NONE;
bool auto_source = false;
int start_seconds = 0;
int cache_segments = -1;
float playback_speed = -1;
@@ -52,6 +55,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) {
{"start", required_argument, nullptr, 's'},
{"playback", required_argument, nullptr, 'x'},
{"demo", no_argument, nullptr, 0},
{"auto", no_argument, nullptr, 0},
{"data_dir", required_argument, nullptr, 'd'},
{"prefix", required_argument, nullptr, 'p'},
{"dcam", no_argument, nullptr, 0},
@@ -94,11 +98,9 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) {
case 'p': config.prefix = optarg; break;
case 0: {
std::string name = cli_options[option_index].name;
if (name == "demo") {
config.route = DEMO_ROUTE;
} else {
config.flags |= flag_map.at(name);
}
if (name == "demo") config.route = DEMO_ROUTE;
else if (name == "auto") config.auto_source = true;
else config.flags |= flag_map.at(name);
break;
}
case 'h': std::cout << helpText; return false;
@@ -136,7 +138,7 @@ int main(int argc, char *argv[]) {
op_prefix = std::make_unique<OpenpilotPrefix>(config.prefix);
}
Replay replay(config.route, config.allow, config.block, nullptr, config.flags, config.data_dir);
Replay replay(config.route, config.allow, config.block, nullptr, config.flags, config.data_dir, config.auto_source);
if (config.cache_segments > 0) {
replay.setSegmentCacheLimit(config.cache_segments);
}
+2 -2
View File
@@ -15,8 +15,8 @@ void notifyEvent(Callback &callback, Args &&...args) {
}
Replay::Replay(const std::string &route, std::vector<std::string> allow, std::vector<std::string> block,
SubMaster *sm, uint32_t flags, const std::string &data_dir)
: sm_(sm), flags_(flags), seg_mgr_(std::make_unique<SegmentManager>(route, flags, data_dir)) {
SubMaster *sm, uint32_t flags, const std::string &data_dir, bool auto_source)
: sm_(sm), flags_(flags), seg_mgr_(std::make_unique<SegmentManager>(route, flags, data_dir, auto_source)) {
std::signal(SIGUSR1, interrupt_sleep_handler);
if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) {
+1 -1
View File
@@ -29,7 +29,7 @@ enum REPLAY_FLAGS {
class Replay {
public:
Replay(const std::string &route, std::vector<std::string> allow, std::vector<std::string> block, SubMaster *sm = nullptr,
uint32_t flags = REPLAY_FLAG_NONE, const std::string &data_dir = "");
uint32_t flags = REPLAY_FLAG_NONE, const std::string &data_dir = "", bool auto_source = false);
~Replay();
bool load();
RouteLoadError lastRouteError() const { return route().lastError(); }
+50 -16
View File
@@ -10,9 +10,8 @@
#include "tools/replay/replay.h"
#include "tools/replay/util.h"
Route::Route(const std::string &route, const std::string &data_dir) : data_dir_(data_dir) {
route_ = parseRoute(route);
}
Route::Route(const std::string &route, const std::string &data_dir, bool auto_source)
: route_string_(route), data_dir_(data_dir), auto_source_(auto_source) {}
RouteIdentifier Route::parseRoute(const std::string &str) {
RouteIdentifier identifier = {};
@@ -44,27 +43,62 @@ RouteIdentifier Route::parseRoute(const std::string &str) {
}
bool Route::load() {
err_ = RouteLoadError::None;
route_ = parseRoute(route_string_);
if (route_.str.empty() || (data_dir_.empty() && route_.dongle_id.empty())) {
rInfo("invalid route format");
return false;
}
// Parse the timestamp from the route identifier (only applicable for old route formats).
struct tm tm_time = {0};
strptime(route_.timestamp.c_str(), "%Y-%m-%d--%H-%M-%S", &tm_time);
date_time_ = mktime(&tm_time);
if (strptime(route_.timestamp.c_str(), "%Y-%m-%d--%H-%M-%S", &tm_time)) {
date_time_ = mktime(&tm_time);
}
bool ret = data_dir_.empty() ? loadFromServer() : loadFromLocal();
if (ret) {
if (route_.begin_segment == -1) route_.begin_segment = segments_.rbegin()->first;
if (route_.end_segment == -1) route_.end_segment = segments_.rbegin()->first;
for (auto it = segments_.begin(); it != segments_.end(); /**/) {
if (it->first < route_.begin_segment || it->first > route_.end_segment) {
it = segments_.erase(it);
} else {
++it;
if (!loadSegments()) {
rInfo("Failed to load segments");
return false;
}
return true;
}
bool Route::loadSegments() {
if (!auto_source_) {
bool ret = data_dir_.empty() ? loadFromServer() : loadFromLocal();
if (ret) {
// Trim segments
if (route_.begin_segment > 0) {
segments_.erase(segments_.begin(), segments_.lower_bound(route_.begin_segment));
}
if (route_.end_segment >= 0) {
segments_.erase(segments_.upper_bound(route_.end_segment), segments_.end());
}
}
return !segments_.empty();
}
return loadFromAutoSource();
}
bool Route::loadFromAutoSource() {
auto origin_prefix = getenv("OPENPILOT_PREFIX");
if (origin_prefix) {
setenv("OPENPILOT_PREFIX", "", 1);
}
auto cmd = util::string_format("../auto_source.py \"%s\"", route_string_.c_str());
auto log_files = split(util::check_output(cmd), '\n');
if (origin_prefix) {
setenv("OPENPILOT_PREFIX", origin_prefix, 1);
}
const static std::regex rx(R"(\/(\d+)\/)");
for (int i = 0; i < log_files.size(); ++i) {
int seg_num = i;
std::smatch match;
if (std::regex_search(log_files[i], match, rx)) {
seg_num = std::stoi(match[1]);
}
addFileToSegment(seg_num, log_files[i]);
}
return !segments_.empty();
}
@@ -121,7 +155,7 @@ bool Route::loadFromJson(const std::string &json) {
bool Route::loadFromLocal() {
std::string pattern = route_.timestamp + "--";
for (const auto &entry : std::filesystem::directory_iterator(data_dir_)) {
if (entry.is_directory() && entry.path().filename().string().find(pattern) == 0) {
if (entry.is_directory() && entry.path().filename().string().find(pattern) != std::string::npos) {
std::string segment = entry.path().string();
int seg_num = std::atoi(segment.substr(segment.rfind("--") + 2).c_str());
+6 -2
View File
@@ -40,7 +40,7 @@ struct SegmentFile {
class Route {
public:
Route(const std::string &route, const std::string &data_dir = {});
Route(const std::string &route, const std::string &data_dir = {}, bool auto_source = false);
bool load();
RouteLoadError lastError() const { return err_; }
inline const std::string &name() const { return route_.str; }
@@ -52,6 +52,8 @@ public:
static RouteIdentifier parseRoute(const std::string &str);
protected:
bool loadSegments();
bool loadFromAutoSource();
bool loadFromLocal();
bool loadFromServer(int retries = 3);
bool loadFromJson(const std::string &json);
@@ -59,8 +61,10 @@ protected:
RouteIdentifier route_ = {};
std::string data_dir_;
std::map<int, SegmentFile> segments_;
std::time_t date_time_;
std::time_t date_time_ = 0;
RouteLoadError err_ = RouteLoadError::None;
bool auto_source_ = false;
std::string route_string_;
};
class Segment {
+2 -2
View File
@@ -20,8 +20,8 @@ public:
bool isSegmentLoaded(int n) const { return segments.find(n) != segments.end(); }
};
SegmentManager(const std::string &route_name, uint32_t flags, const std::string &data_dir = "")
: flags_(flags), route_(route_name, data_dir), event_data_(std::make_shared<EventData>()) {}
SegmentManager(const std::string &route_name, uint32_t flags, const std::string &data_dir = "", bool auto_source = false)
: flags_(flags), route_(route_name, data_dir, auto_source), event_data_(std::make_shared<EventData>()) {}
~SegmentManager();
bool load();
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
# this is a little nicer than "adb shell" since
# "adb shell" doesn't do full terminal emulation
adb forward tcp:2222 tcp:22
ssh comma@localhost -p 2222
+1 -1
View File
@@ -18,6 +18,6 @@ if __name__ == "__main__":
params.put_bool("SshEnabled", True)
params.put("GithubSshKeys", keys.text)
params.put("GithubUsername", username)
print("Setup ssh keys successfully")
print("Set up ssh keys successfully")
else:
print("Error getting public keys from github")
+1 -1
View File
@@ -40,7 +40,7 @@ class SimulatorBridge(ABC):
def __init__(self, dual_camera, high_quality):
set_params_enabled()
self.params = Params()
self.params.put_bool("ExperimentalLongitudinalEnabled", True)
self.params.put_bool("AlphaLongitudinalEnabled", True)
self.rk = Ratekeeper(100, None)
+10 -10
View File
@@ -1,22 +1,22 @@
# Run openpilot with webcam on PC
What's needed:
- Ubuntu 24.04 ([WSL2 is not supported](https://github.com/commaai/openpilot/issues/34216))
- Ubuntu 24.04 ([WSL2 is not supported](https://github.com/commaai/openpilot/issues/34216)) or macOS
- GPU (recommended)
- Two USB webcams, at least 720p and 78 degrees FOV (e.g. Logitech C920/C615)
- [Car harness](https://comma.ai/shop/products/comma-car-harness) with black panda to connect to your car
- [Panda paw](https://comma.ai/shop/products/panda-paw) or USB-A to USB-A cable to connect panda to your computer
That's it!
- One USB webcam, at least 720p and 78 degrees FOV (e.g. Logitech C920/C615, NexiGo N60)
- [Car harness](https://comma.ai/shop/products/comma-car-harness)
- [panda](https://comma.ai/shop/panda)
- USB-A to USB-A cable to connect panda to your computer
## Setup openpilot
- Follow [this readme](../README.md) to install and build the requirements
- Install OpenCL Driver
- Install OpenCL Driver (Ubuntu)
```
sudo apt install pocl-opencl-icd
```
## Connect the hardware
- Connect the road facing camera first, then the driver facing camera
- Connect the camera first
- Connect your computer to panda
## GO
@@ -24,12 +24,12 @@ sudo apt install pocl-opencl-icd
USE_WEBCAM=1 system/manager/manager.py
```
- Start the car, then the UI should show the road webcam's view
- Adjust and secure the webcams.
- Adjust and secure the webcam
- Finish calibration and engage!
## Specify Cameras
Use the `ROAD_CAM`, `DRIVER_CAM`, and optional `WIDE_CAM` environment variables to specify which camera is which (ie. `DRIVER_CAM=2` uses `/dev/video2` for the driver-facing camera):
Use the `ROAD_CAM` (default 0) and optional `DRIVER_CAM`, `WIDE_CAM` environment variables to specify which camera is which (ie. `ROAD_CAM=1` uses `/dev/video1`, on Ubuntu, for the road camera):
```
USE_WEBCAM=1 ROAD_CAM=4 WIDE_CAM=6 system/manager/manager.py
USE_WEBCAM=1 ROAD_CAM=1 system/manager/manager.py
```
+7 -2
View File
@@ -9,14 +9,19 @@ from cereal import messaging
from openpilot.tools.webcam.camera import Camera
from openpilot.common.realtime import Ratekeeper
ROAD_CAM = os.getenv("ROAD_CAM", "0")
WIDE_CAM = os.getenv("WIDE_CAM")
DRIVER_CAM = os.getenv("DRIVER_CAM")
CameraType = namedtuple("CameraType", ["msg_name", "stream_type", "cam_id"])
CAMERAS = [
CameraType("roadCameraState", VisionStreamType.VISION_STREAM_ROAD, os.getenv("ROAD_CAM", "0")),
CameraType("driverCameraState", VisionStreamType.VISION_STREAM_DRIVER, os.getenv("DRIVER_CAM", "2")),
CameraType("roadCameraState", VisionStreamType.VISION_STREAM_ROAD, ROAD_CAM)
]
if WIDE_CAM:
CAMERAS.append(CameraType("wideRoadCameraState", VisionStreamType.VISION_STREAM_WIDE_ROAD, WIDE_CAM))
if DRIVER_CAM:
CAMERAS.append(CameraType("driverCameraState", VisionStreamType.VISION_STREAM_DRIVER, DRIVER_CAM))
class Camerad:
def __init__(self):