commit
7455dde790
136 changed files with 1779 additions and 3565 deletions
@ -1 +1 @@ |
||||
#define COMMA_VERSION "0.10.0" |
||||
#define COMMA_VERSION "0.10.1" |
||||
|
@ -1 +1 @@ |
||||
Subproject commit 22b8df68fb6d8aa197fb9b432a428da6dd96c98c |
||||
Subproject commit 43006b9a41e233325cb7cbcb6ff40de0234217a0 |
@ -1 +1 @@ |
||||
Subproject commit c2723b2f6bcce7e2d97f685db1ec2472a8dd28f5 |
||||
Subproject commit 3dc21386239e3073a623156b75901aa302340d6c |
@ -0,0 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:a69514fe68dd1b4c73f28771640dcba10fc40989d1c7e771cb48bfd830fef206 |
||||
size 5713 |
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:22aec22a10ce09384d4a4af2a0bbff08d54af7e0c888503508f356fae4ff0e29 |
||||
size 15583374 |
||||
oid sha256:04b763fb71efe57a8a4c4168a8043ecd58939015026ded0dc755ded6905ac251 |
||||
size 12343523 |
||||
|
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1 |
||||
oid sha256:c824f68646a3b94f117f01c70dc8316fb466e05fbd42ccdba440b8a8dc86914b |
||||
size 46265993 |
||||
oid sha256:e66bb8d53eced3786ed71a59b55ffc6810944cb217f0518621cc76303260a1ef |
||||
size 46271991 |
||||
|
@ -0,0 +1,54 @@ |
||||
import math |
||||
from enum import StrEnum, auto |
||||
|
||||
from cereal import car, messaging |
||||
from openpilot.common.realtime import DT_CTRL |
||||
from openpilot.selfdrive.locationd.helpers import Pose |
||||
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY |
||||
from opendbc.car.lateral import ISO_LATERAL_ACCEL |
||||
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX |
||||
|
||||
MIN_EXCESSIVE_ACTUATION_COUNT = int(0.25 / DT_CTRL) |
||||
MIN_LATERAL_ENGAGE_BUFFER = int(1 / DT_CTRL) |
||||
|
||||
|
||||
class ExcessiveActuationType(StrEnum): |
||||
LONGITUDINAL = auto() |
||||
LATERAL = auto() |
||||
|
||||
|
||||
class ExcessiveActuationCheck: |
||||
def __init__(self): |
||||
self._excessive_counter = 0 |
||||
self._engaged_counter = 0 |
||||
|
||||
def update(self, sm: messaging.SubMaster, CS: car.CarState, calibrated_pose: Pose) -> ExcessiveActuationType | None: |
||||
# CS.aEgo can be noisy to bumps in the road, transitioning from standstill, losing traction, etc. |
||||
# longitudinal |
||||
accel_calibrated = calibrated_pose.acceleration.x |
||||
excessive_long_actuation = sm['carControl'].longActive and (accel_calibrated > ACCEL_MAX * 2 or accel_calibrated < ACCEL_MIN * 2) |
||||
|
||||
# lateral |
||||
yaw_rate = calibrated_pose.angular_velocity.yaw |
||||
roll = sm['liveParameters'].roll |
||||
roll_compensated_lateral_accel = (CS.vEgo * yaw_rate) - (math.sin(roll) * ACCELERATION_DUE_TO_GRAVITY) |
||||
|
||||
# Prevent false positives after overriding |
||||
excessive_lat_actuation = False |
||||
self._engaged_counter = self._engaged_counter + 1 if sm['carControl'].latActive and not CS.steeringPressed else 0 |
||||
if self._engaged_counter > MIN_LATERAL_ENGAGE_BUFFER: |
||||
if abs(roll_compensated_lateral_accel) > ISO_LATERAL_ACCEL * 2: |
||||
excessive_lat_actuation = True |
||||
|
||||
# livePose acceleration can be noisy due to bad mounting or aliased livePose measurements |
||||
livepose_valid = abs(CS.aEgo - accel_calibrated) < 2 |
||||
self._excessive_counter = self._excessive_counter + 1 if livepose_valid and (excessive_long_actuation or excessive_lat_actuation) else 0 |
||||
|
||||
excessive_type = None |
||||
if self._excessive_counter > MIN_EXCESSIVE_ACTUATION_COUNT: |
||||
if excessive_long_actuation: |
||||
excessive_type = ExcessiveActuationType.LONGITUDINAL |
||||
else: |
||||
excessive_type = ExcessiveActuationType.LATERAL |
||||
|
||||
return excessive_type |
@ -1 +1 @@ |
||||
8ff1b4c9c7a34589142a07579b0051acddfe7699 |
||||
6d3219bca9f66a229b38a5382d301a92b0147edb |
@ -0,0 +1,71 @@ |
||||
#!/usr/bin/env python3 |
||||
import cereal.messaging as messaging |
||||
from openpilot.common.params import Params |
||||
from openpilot.common.swaglog import cloudlog |
||||
from cereal import car |
||||
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER |
||||
|
||||
FEEDBACK_MAX_DURATION = 10.0 |
||||
ButtonType = car.CarState.ButtonEvent.Type |
||||
|
||||
|
||||
def main(): |
||||
params = Params() |
||||
pm = messaging.PubMaster(['userBookmark', 'audioFeedback']) |
||||
sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState']) |
||||
should_record_audio = False |
||||
block_num = 0 |
||||
waiting_for_release = False |
||||
early_stop_triggered = False |
||||
|
||||
while True: |
||||
sm.update() |
||||
should_send_bookmark = False |
||||
|
||||
# TODO: https://github.com/commaai/openpilot/issues/36015 |
||||
if False and sm.updated['carState'] and sm['carState'].canValid: |
||||
for be in sm['carState'].buttonEvents: |
||||
if be.type == ButtonType.lkas: |
||||
if be.pressed: |
||||
if not should_record_audio: |
||||
if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set |
||||
should_record_audio = True |
||||
block_num = 0 |
||||
waiting_for_release = False |
||||
early_stop_triggered = False |
||||
cloudlog.info("LKAS button pressed - starting 10-second audio feedback") |
||||
else: |
||||
should_send_bookmark = True # immediately send bookmark if toggle false |
||||
cloudlog.info("LKAS button pressed - bookmarking") |
||||
elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early |
||||
waiting_for_release = True |
||||
elif waiting_for_release: # Second press released |
||||
waiting_for_release = False |
||||
early_stop_triggered = True |
||||
cloudlog.info("LKAS button released - ending recording early") |
||||
|
||||
if should_record_audio and sm.updated['rawAudioData']: |
||||
raw_audio = sm['rawAudioData'] |
||||
msg = messaging.new_message('audioFeedback', valid=True) |
||||
msg.audioFeedback.audio.data = raw_audio.data |
||||
msg.audioFeedback.audio.sampleRate = raw_audio.sampleRate |
||||
msg.audioFeedback.blockNum = block_num |
||||
block_num += 1 |
||||
if (block_num * SAMPLE_BUFFER / SAMPLE_RATE) >= FEEDBACK_MAX_DURATION or early_stop_triggered: # Check for timeout or early stop |
||||
should_send_bookmark = True # send bookmark at end of audio segment |
||||
should_record_audio = False |
||||
early_stop_triggered = False |
||||
cloudlog.info("10-second recording completed or second button press - stopping audio feedback") |
||||
pm.send('audioFeedback', msg) |
||||
|
||||
if sm.updated['bookmarkButton']: |
||||
cloudlog.info("Bookmark button pressed!") |
||||
should_send_bookmark = True |
||||
|
||||
if should_send_bookmark: |
||||
msg = messaging.new_message('userBookmark', valid=True) |
||||
pm.send('userBookmark', msg) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -1,23 +0,0 @@ |
||||
import os |
||||
import platform |
||||
from cffi import FFI |
||||
|
||||
import sip |
||||
|
||||
from openpilot.common.basedir import BASEDIR |
||||
|
||||
def suffix(): |
||||
return ".dylib" if platform.system() == "Darwin" else ".so" |
||||
|
||||
|
||||
def get_ffi(): |
||||
lib = os.path.join(BASEDIR, "selfdrive", "ui", "qt", "libpython_helpers" + suffix()) |
||||
|
||||
ffi = FFI() |
||||
ffi.cdef("void set_main_window(void *w);") |
||||
return ffi, ffi.dlopen(lib) |
||||
|
||||
|
||||
def set_main_window(widget): |
||||
ffi, lib = get_ffi() |
||||
lib.set_main_window(ffi.cast('void*', sip.unwrapinstance(widget))) |
@ -1,541 +0,0 @@ |
||||
#include "selfdrive/ui/qt/setup/setup.h" |
||||
|
||||
#include <cstdio> |
||||
#include <cstdlib> |
||||
#include <sstream> |
||||
#include <string> |
||||
|
||||
#include <QApplication> |
||||
#include <QLabel> |
||||
#include <QVBoxLayout> |
||||
|
||||
#include <curl/curl.h> |
||||
|
||||
#include "common/util.h" |
||||
#include "system/hardware/hw.h" |
||||
#include "selfdrive/ui/qt/api.h" |
||||
#include "selfdrive/ui/qt/qt_window.h" |
||||
#include "selfdrive/ui/qt/network/networking.h" |
||||
#include "selfdrive/ui/qt/util.h" |
||||
#include "selfdrive/ui/qt/widgets/input.h" |
||||
|
||||
const std::string USER_AGENT = "AGNOSSetup-"; |
||||
const QString OPENPILOT_URL = "https://openpilot.comma.ai"; |
||||
|
||||
bool is_elf(char *fname) { |
||||
FILE *fp = fopen(fname, "rb"); |
||||
if (fp == NULL) { |
||||
return false; |
||||
} |
||||
char buf[4]; |
||||
size_t n = fread(buf, 1, 4, fp); |
||||
fclose(fp); |
||||
return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F'; |
||||
} |
||||
|
||||
void Setup::download(QString url) { |
||||
// autocomplete incomplete urls
|
||||
if (QRegularExpression("^([^/.]+)/([^/]+)$").match(url).hasMatch()) { |
||||
url.prepend("https://installer.comma.ai/"); |
||||
} |
||||
|
||||
CURL *curl = curl_easy_init(); |
||||
if (!curl) { |
||||
emit finished(url, tr("Something went wrong. Reboot the device.")); |
||||
return; |
||||
} |
||||
|
||||
auto version = util::read_file("/VERSION"); |
||||
|
||||
struct curl_slist *list = NULL; |
||||
std::string header = "X-openpilot-serial: " + Hardware::get_serial(); |
||||
list = curl_slist_append(list, header.c_str()); |
||||
|
||||
char tmpfile[] = "/tmp/installer_XXXXXX"; |
||||
FILE *fp = fdopen(mkstemp(tmpfile), "wb"); |
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str()); |
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); |
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); |
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); |
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str()); |
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); |
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); |
||||
|
||||
int ret = curl_easy_perform(curl); |
||||
long res_status = 0; |
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status); |
||||
|
||||
if (ret != CURLE_OK || res_status != 200) { |
||||
emit finished(url, tr("Ensure the entered URL is valid, and the device’s internet connection is good.")); |
||||
} else if (!is_elf(tmpfile)) { |
||||
emit finished(url, tr("No custom software found at this URL.")); |
||||
} else { |
||||
rename(tmpfile, "/tmp/installer"); |
||||
|
||||
FILE *fp_url = fopen("/tmp/installer_url", "w"); |
||||
fprintf(fp_url, "%s", url.toStdString().c_str()); |
||||
fclose(fp_url); |
||||
|
||||
emit finished(url); |
||||
} |
||||
|
||||
curl_slist_free_all(list); |
||||
curl_easy_cleanup(curl); |
||||
fclose(fp); |
||||
} |
||||
|
||||
QWidget * Setup::low_voltage() { |
||||
QWidget *widget = new QWidget(); |
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget); |
||||
main_layout->setContentsMargins(55, 0, 55, 55); |
||||
main_layout->setSpacing(0); |
||||
|
||||
// inner text layout: warning icon, title, and body
|
||||
QVBoxLayout *inner_layout = new QVBoxLayout(); |
||||
inner_layout->setContentsMargins(110, 144, 365, 0); |
||||
main_layout->addLayout(inner_layout); |
||||
|
||||
QLabel *triangle = new QLabel(); |
||||
triangle->setPixmap(QPixmap(ASSET_PATH + "icons/warning.png")); |
||||
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft); |
||||
inner_layout->addSpacing(80); |
||||
|
||||
QLabel *title = new QLabel(tr("WARNING: Low Voltage")); |
||||
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;"); |
||||
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); |
||||
|
||||
inner_layout->addSpacing(25); |
||||
|
||||
QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk.")); |
||||
body->setWordWrap(true); |
||||
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); |
||||
body->setStyleSheet("font-size: 80px; font-weight: 300;"); |
||||
inner_layout->addWidget(body); |
||||
|
||||
inner_layout->addStretch(); |
||||
|
||||
// power off + continue buttons
|
||||
QHBoxLayout *blayout = new QHBoxLayout(); |
||||
blayout->setSpacing(50); |
||||
main_layout->addLayout(blayout, 0); |
||||
|
||||
QPushButton *poweroff = new QPushButton(tr("Power off")); |
||||
poweroff->setObjectName("navBtn"); |
||||
blayout->addWidget(poweroff); |
||||
QObject::connect(poweroff, &QPushButton::clicked, this, [=]() { |
||||
Hardware::poweroff(); |
||||
}); |
||||
|
||||
QPushButton *cont = new QPushButton(tr("Continue")); |
||||
cont->setObjectName("navBtn"); |
||||
blayout->addWidget(cont); |
||||
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); |
||||
|
||||
return widget; |
||||
} |
||||
|
||||
QWidget * Setup::custom_software_warning() { |
||||
QWidget *widget = new QWidget(); |
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget); |
||||
main_layout->setContentsMargins(55, 0, 55, 55); |
||||
main_layout->setSpacing(0); |
||||
|
||||
QVBoxLayout *inner_layout = new QVBoxLayout(); |
||||
inner_layout->setContentsMargins(110, 110, 300, 0); |
||||
main_layout->addLayout(inner_layout); |
||||
|
||||
QLabel *title = new QLabel(tr("WARNING: Custom Software")); |
||||
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;"); |
||||
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); |
||||
|
||||
inner_layout->addSpacing(25); |
||||
|
||||
QLabel *body = new QLabel(tr("Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.\n\nIf you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later.")); |
||||
body->setWordWrap(true); |
||||
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); |
||||
body->setStyleSheet("font-size: 65px; font-weight: 300;"); |
||||
inner_layout->addWidget(body); |
||||
|
||||
inner_layout->addStretch(); |
||||
|
||||
QHBoxLayout *blayout = new QHBoxLayout(); |
||||
blayout->setSpacing(50); |
||||
main_layout->addLayout(blayout, 0); |
||||
|
||||
QPushButton *back = new QPushButton(tr("Back")); |
||||
back->setObjectName("navBtn"); |
||||
blayout->addWidget(back); |
||||
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); |
||||
|
||||
QPushButton *cont = new QPushButton(tr("Continue")); |
||||
cont->setObjectName("navBtn"); |
||||
blayout->addWidget(cont); |
||||
QObject::connect(cont, &QPushButton::clicked, this, [=]() { |
||||
QTimer::singleShot(0, [=]() { |
||||
setCurrentWidget(downloading_widget); |
||||
}); |
||||
QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); |
||||
if (!url.isEmpty()) { |
||||
QTimer::singleShot(1000, this, [=]() { |
||||
download(url); |
||||
}); |
||||
} else { |
||||
setCurrentWidget(software_selection_widget); |
||||
} |
||||
}); |
||||
|
||||
return widget; |
||||
} |
||||
|
||||
QWidget * Setup::getting_started() { |
||||
QWidget *widget = new QWidget(); |
||||
|
||||
QHBoxLayout *main_layout = new QHBoxLayout(widget); |
||||
main_layout->setMargin(0); |
||||
|
||||
QVBoxLayout *vlayout = new QVBoxLayout(); |
||||
vlayout->setContentsMargins(165, 280, 100, 0); |
||||
main_layout->addLayout(vlayout); |
||||
|
||||
QLabel *title = new QLabel(tr("Getting Started")); |
||||
title->setStyleSheet("font-size: 90px; font-weight: 500;"); |
||||
vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); |
||||
|
||||
vlayout->addSpacing(90); |
||||
QLabel *desc = new QLabel(tr("Before we get on the road, let’s finish installation and cover some details.")); |
||||
desc->setWordWrap(true); |
||||
desc->setStyleSheet("font-size: 80px; font-weight: 300;"); |
||||
vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft); |
||||
|
||||
vlayout->addStretch(); |
||||
|
||||
QPushButton *btn = new QPushButton(); |
||||
btn->setIcon(QIcon(":/images/button_continue_triangle.svg")); |
||||
btn->setIconSize(QSize(54, 106)); |
||||
btn->setFixedSize(310, 1080); |
||||
btn->setProperty("primary", true); |
||||
btn->setStyleSheet("border: none;"); |
||||
main_layout->addWidget(btn, 0, Qt::AlignRight); |
||||
QObject::connect(btn, &QPushButton::clicked, this, &Setup::nextPage); |
||||
|
||||
return widget; |
||||
} |
||||
|
||||
QWidget * Setup::network_setup() { |
||||
QWidget *widget = new QWidget(); |
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget); |
||||
main_layout->setContentsMargins(55, 50, 55, 50); |
||||
|
||||
// title
|
||||
QLabel *title = new QLabel(tr("Connect to Wi-Fi")); |
||||
title->setStyleSheet("font-size: 90px; font-weight: 500;"); |
||||
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); |
||||
|
||||
main_layout->addSpacing(25); |
||||
|
||||
// wifi widget
|
||||
Networking *networking = new Networking(this, false); |
||||
networking->setStyleSheet("Networking {background-color: #292929; border-radius: 13px;}"); |
||||
main_layout->addWidget(networking, 1); |
||||
|
||||
main_layout->addSpacing(35); |
||||
|
||||
// back + continue buttons
|
||||
QHBoxLayout *blayout = new QHBoxLayout; |
||||
main_layout->addLayout(blayout); |
||||
blayout->setSpacing(50); |
||||
|
||||
QPushButton *back = new QPushButton(tr("Back")); |
||||
back->setObjectName("navBtn"); |
||||
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); |
||||
blayout->addWidget(back); |
||||
|
||||
QPushButton *cont = new QPushButton(); |
||||
cont->setObjectName("navBtn"); |
||||
cont->setProperty("primary", true); |
||||
cont->setEnabled(false); |
||||
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); |
||||
blayout->addWidget(cont); |
||||
|
||||
// setup timer for testing internet connection
|
||||
HttpRequest *request = new HttpRequest(this, false, 2500); |
||||
QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) { |
||||
cont->setEnabled(success); |
||||
if (success) { |
||||
const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI; |
||||
cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi")); |
||||
} else { |
||||
cont->setText(tr("Waiting for internet")); |
||||
} |
||||
repaint(); |
||||
}); |
||||
request->sendRequest(OPENPILOT_URL); |
||||
QTimer *timer = new QTimer(this); |
||||
QObject::connect(timer, &QTimer::timeout, [=]() { |
||||
if (!request->active() && cont->isVisible()) { |
||||
request->sendRequest(OPENPILOT_URL); |
||||
} |
||||
}); |
||||
timer->start(1000); |
||||
|
||||
return widget; |
||||
} |
||||
|
||||
QWidget * radio_button(QString title, QButtonGroup *group) { |
||||
QPushButton *btn = new QPushButton(title); |
||||
btn->setCheckable(true); |
||||
group->addButton(btn); |
||||
btn->setStyleSheet(R"( |
||||
QPushButton { |
||||
height: 230; |
||||
padding-left: 100px; |
||||
padding-right: 100px; |
||||
text-align: left; |
||||
font-size: 80px; |
||||
font-weight: 400; |
||||
border-radius: 10px; |
||||
background-color: #4F4F4F; |
||||
} |
||||
QPushButton:checked { |
||||
background-color: #465BEA; |
||||
} |
||||
)"); |
||||
|
||||
// checkmark icon
|
||||
QPixmap pix(":/icons/circled_check.svg"); |
||||
btn->setIcon(pix); |
||||
btn->setIconSize(QSize(0, 0)); |
||||
btn->setLayoutDirection(Qt::RightToLeft); |
||||
QObject::connect(btn, &QPushButton::toggled, [=](bool checked) { |
||||
btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0)); |
||||
}); |
||||
return btn; |
||||
} |
||||
|
||||
QWidget * Setup::software_selection() { |
||||
QWidget *widget = new QWidget(); |
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget); |
||||
main_layout->setContentsMargins(55, 50, 55, 50); |
||||
main_layout->setSpacing(0); |
||||
|
||||
// title
|
||||
QLabel *title = new QLabel(tr("Choose Software to Install")); |
||||
title->setStyleSheet("font-size: 90px; font-weight: 500;"); |
||||
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); |
||||
|
||||
main_layout->addSpacing(50); |
||||
|
||||
// openpilot + custom radio buttons
|
||||
QButtonGroup *group = new QButtonGroup(widget); |
||||
group->setExclusive(true); |
||||
|
||||
QWidget *openpilot = radio_button(tr("openpilot"), group); |
||||
main_layout->addWidget(openpilot); |
||||
|
||||
main_layout->addSpacing(30); |
||||
|
||||
QWidget *custom = radio_button(tr("Custom Software"), group); |
||||
main_layout->addWidget(custom); |
||||
|
||||
main_layout->addStretch(); |
||||
|
||||
// back + continue buttons
|
||||
QHBoxLayout *blayout = new QHBoxLayout; |
||||
main_layout->addLayout(blayout); |
||||
blayout->setSpacing(50); |
||||
|
||||
QPushButton *back = new QPushButton(tr("Back")); |
||||
back->setObjectName("navBtn"); |
||||
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); |
||||
blayout->addWidget(back); |
||||
|
||||
QPushButton *cont = new QPushButton(tr("Continue")); |
||||
cont->setObjectName("navBtn"); |
||||
cont->setEnabled(false); |
||||
cont->setProperty("primary", true); |
||||
blayout->addWidget(cont); |
||||
|
||||
QObject::connect(cont, &QPushButton::clicked, [=]() { |
||||
if (group->checkedButton() != openpilot) { |
||||
QTimer::singleShot(0, [=]() { |
||||
setCurrentWidget(custom_software_warning_widget); |
||||
}); |
||||
} else { |
||||
QTimer::singleShot(0, [=]() { |
||||
setCurrentWidget(downloading_widget); |
||||
}); |
||||
QTimer::singleShot(1000, this, [=]() { |
||||
download(OPENPILOT_URL); |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
connect(group, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) { |
||||
btn->setChecked(true); |
||||
cont->setEnabled(true); |
||||
}); |
||||
|
||||
return widget; |
||||
} |
||||
|
||||
QWidget * Setup::downloading() { |
||||
QWidget *widget = new QWidget(); |
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget); |
||||
QLabel *txt = new QLabel(tr("Downloading...")); |
||||
txt->setStyleSheet("font-size: 90px; font-weight: 500;"); |
||||
main_layout->addWidget(txt, 0, Qt::AlignCenter); |
||||
return widget; |
||||
} |
||||
|
||||
QWidget * Setup::download_failed(QLabel *url, QLabel *body) { |
||||
QWidget *widget = new QWidget(); |
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget); |
||||
main_layout->setContentsMargins(55, 185, 55, 55); |
||||
main_layout->setSpacing(0); |
||||
|
||||
QLabel *title = new QLabel(tr("Download Failed")); |
||||
title->setStyleSheet("font-size: 90px; font-weight: 500;"); |
||||
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); |
||||
|
||||
main_layout->addSpacing(67); |
||||
|
||||
url->setWordWrap(true); |
||||
url->setAlignment(Qt::AlignTop | Qt::AlignLeft); |
||||
url->setStyleSheet("font-family: \"JetBrains Mono\"; font-size: 64px; font-weight: 400; margin-right: 100px;"); |
||||
main_layout->addWidget(url); |
||||
|
||||
main_layout->addSpacing(48); |
||||
|
||||
body->setWordWrap(true); |
||||
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); |
||||
body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;"); |
||||
main_layout->addWidget(body); |
||||
|
||||
main_layout->addStretch(); |
||||
|
||||
// reboot + start over buttons
|
||||
QHBoxLayout *blayout = new QHBoxLayout(); |
||||
blayout->setSpacing(50); |
||||
main_layout->addLayout(blayout, 0); |
||||
|
||||
QPushButton *reboot = new QPushButton(tr("Reboot device")); |
||||
reboot->setObjectName("navBtn"); |
||||
blayout->addWidget(reboot); |
||||
QObject::connect(reboot, &QPushButton::clicked, this, [=]() { |
||||
Hardware::reboot(); |
||||
}); |
||||
|
||||
QPushButton *restart = new QPushButton(tr("Start over")); |
||||
restart->setObjectName("navBtn"); |
||||
restart->setProperty("primary", true); |
||||
blayout->addWidget(restart); |
||||
QObject::connect(restart, &QPushButton::clicked, this, [=]() { |
||||
setCurrentIndex(1); |
||||
}); |
||||
|
||||
widget->setStyleSheet(R"( |
||||
QLabel { |
||||
margin-left: 117; |
||||
} |
||||
)"); |
||||
return widget; |
||||
} |
||||
|
||||
void Setup::prevPage() { |
||||
setCurrentIndex(currentIndex() - 1); |
||||
} |
||||
|
||||
void Setup::nextPage() { |
||||
setCurrentIndex(currentIndex() + 1); |
||||
} |
||||
|
||||
Setup::Setup(QWidget *parent) : QStackedWidget(parent) { |
||||
if (std::getenv("MULTILANG")) { |
||||
selectLanguage(); |
||||
} |
||||
|
||||
std::stringstream buffer; |
||||
buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf(); |
||||
float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.; |
||||
if (voltage < 7) { |
||||
addWidget(low_voltage()); |
||||
} |
||||
|
||||
addWidget(getting_started()); |
||||
addWidget(network_setup()); |
||||
software_selection_widget = software_selection(); |
||||
addWidget(software_selection_widget); |
||||
custom_software_warning_widget = custom_software_warning(); |
||||
addWidget(custom_software_warning_widget); |
||||
downloading_widget = downloading(); |
||||
addWidget(downloading_widget); |
||||
|
||||
QLabel *url_label = new QLabel(); |
||||
QLabel *body_label = new QLabel(); |
||||
failed_widget = download_failed(url_label, body_label); |
||||
addWidget(failed_widget); |
||||
|
||||
QObject::connect(this, &Setup::finished, [=](const QString &url, const QString &error) { |
||||
qDebug() << "finished" << url << error; |
||||
if (error.isEmpty()) { |
||||
// hide setup on success
|
||||
QTimer::singleShot(3000, this, &QWidget::hide); |
||||
} else { |
||||
url_label->setText(url); |
||||
body_label->setText(error); |
||||
setCurrentWidget(failed_widget); |
||||
} |
||||
}); |
||||
|
||||
// TODO: revisit pressed bg color
|
||||
setStyleSheet(R"( |
||||
* { |
||||
color: white; |
||||
font-family: Inter; |
||||
} |
||||
Setup { |
||||
background-color: black; |
||||
} |
||||
QPushButton#navBtn { |
||||
height: 160; |
||||
font-size: 55px; |
||||
font-weight: 400; |
||||
border-radius: 10px; |
||||
background-color: #333333; |
||||
} |
||||
QPushButton#navBtn:disabled, QPushButton[primary='true']:disabled { |
||||
color: #808080; |
||||
background-color: #333333; |
||||
} |
||||
QPushButton#navBtn:pressed { |
||||
background-color: #444444; |
||||
} |
||||
QPushButton[primary='true'], #navBtn[primary='true'] { |
||||
background-color: #465BEA; |
||||
} |
||||
QPushButton[primary='true']:pressed, #navBtn:pressed[primary='true'] { |
||||
background-color: #3049F4; |
||||
} |
||||
)"); |
||||
} |
||||
|
||||
void Setup::selectLanguage() { |
||||
QMap<QString, QString> langs = getSupportedLanguages(); |
||||
QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), "", this); |
||||
if (!selection.isEmpty()) { |
||||
QString selectedLang = langs[selection]; |
||||
Params().put("LanguageSetting", selectedLang.toStdString()); |
||||
if (translator.load(":/" + selectedLang)) { |
||||
qApp->installTranslator(&translator); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
QApplication a(argc, argv); |
||||
Setup setup; |
||||
setMainWindow(&setup); |
||||
return a.exec(); |
||||
} |
@ -1,38 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <QLabel> |
||||
#include <QStackedWidget> |
||||
#include <QString> |
||||
#include <QTranslator> |
||||
#include <QWidget> |
||||
|
||||
class Setup : public QStackedWidget { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit Setup(QWidget *parent = 0); |
||||
|
||||
private: |
||||
void selectLanguage(); |
||||
QWidget *low_voltage(); |
||||
QWidget *custom_software_warning(); |
||||
QWidget *getting_started(); |
||||
QWidget *network_setup(); |
||||
QWidget *software_selection(); |
||||
QWidget *downloading(); |
||||
QWidget *download_failed(QLabel *url, QLabel *body); |
||||
|
||||
QWidget *failed_widget; |
||||
QWidget *downloading_widget; |
||||
QWidget *custom_software_warning_widget; |
||||
QWidget *software_selection_widget; |
||||
QTranslator translator; |
||||
|
||||
signals: |
||||
void finished(const QString &url, const QString &error = ""); |
||||
|
||||
public slots: |
||||
void nextPage(); |
||||
void prevPage(); |
||||
void download(QString url); |
||||
}; |
@ -0,0 +1,53 @@ |
||||
import pytest |
||||
import cereal.messaging as messaging |
||||
from cereal import car |
||||
from openpilot.common.params import Params |
||||
from openpilot.system.manager.process_config import managed_processes |
||||
|
||||
|
||||
@pytest.mark.skip("tmp disabled") |
||||
class TestFeedbackd: |
||||
def setup_method(self): |
||||
self.pm = messaging.PubMaster(['carState', 'rawAudioData']) |
||||
self.sm = messaging.SubMaster(['audioFeedback']) |
||||
|
||||
def _send_lkas_button(self, pressed: bool): |
||||
msg = messaging.new_message('carState') |
||||
msg.carState.canValid = True |
||||
msg.carState.buttonEvents = [{'type': car.CarState.ButtonEvent.Type.lkas, 'pressed': pressed}] |
||||
self.pm.send('carState', msg) |
||||
|
||||
def _send_audio_data(self, count: int = 5): |
||||
for _ in range(count): |
||||
audio_msg = messaging.new_message('rawAudioData') |
||||
audio_msg.rawAudioData.data = bytes(1600) # 800 samples of int16 |
||||
audio_msg.rawAudioData.sampleRate = 16000 |
||||
self.pm.send('rawAudioData', audio_msg) |
||||
self.sm.update(timeout=100) |
||||
|
||||
@pytest.mark.parametrize("record_feedback", [False, True]) |
||||
def test_audio_feedback(self, record_feedback): |
||||
Params().put_bool("RecordAudioFeedback", record_feedback) |
||||
|
||||
managed_processes["feedbackd"].start() |
||||
assert self.pm.wait_for_readers_to_update('carState', timeout=5) |
||||
assert self.pm.wait_for_readers_to_update('rawAudioData', timeout=5) |
||||
|
||||
self._send_lkas_button(pressed=True) |
||||
self._send_audio_data() |
||||
self._send_lkas_button(pressed=False) |
||||
self._send_audio_data() |
||||
|
||||
if record_feedback: |
||||
assert self.sm.updated['audioFeedback'], "audioFeedback should be published when enabled" |
||||
else: |
||||
assert not self.sm.updated['audioFeedback'], "audioFeedback should not be published when disabled" |
||||
|
||||
self._send_lkas_button(pressed=True) |
||||
self._send_audio_data() |
||||
self._send_lkas_button(pressed=False) |
||||
self._send_audio_data() |
||||
|
||||
assert not self.sm.updated['audioFeedback'], "audioFeedback should not be published after second press" |
||||
|
||||
managed_processes["feedbackd"].stop() |
@ -0,0 +1,8 @@ |
||||
import time |
||||
from openpilot.selfdrive.test.helpers import with_processes |
||||
|
||||
|
||||
@with_processes(["raylib_ui"]) |
||||
def test_raylib_ui(): |
||||
"""Test initialization of the UI widgets is successful.""" |
||||
time.sleep(1) |
@ -1,33 +0,0 @@ |
||||
#include <QApplication> |
||||
#include <QtWidgets> |
||||
|
||||
#include "selfdrive/ui/qt/qt_window.h" |
||||
#include "selfdrive/ui/qt/util.h" |
||||
#include "selfdrive/ui/qt/widgets/cameraview.h" |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
initApp(argc, argv); |
||||
|
||||
QApplication a(argc, argv); |
||||
QWidget w; |
||||
setMainWindow(&w); |
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(&w); |
||||
layout->setMargin(0); |
||||
layout->setSpacing(0); |
||||
|
||||
{ |
||||
QHBoxLayout *hlayout = new QHBoxLayout(); |
||||
layout->addLayout(hlayout); |
||||
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_ROAD)); |
||||
} |
||||
|
||||
{ |
||||
QHBoxLayout *hlayout = new QHBoxLayout(); |
||||
layout->addLayout(hlayout); |
||||
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_DRIVER)); |
||||
hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_WIDE_ROAD)); |
||||
} |
||||
|
||||
return a.exec(); |
||||
} |
@ -0,0 +1,17 @@ |
||||
#!/usr/bin/env python3 |
||||
import pyray as rl |
||||
|
||||
from msgq.visionipc import VisionStreamType |
||||
from openpilot.system.ui.lib.application import gui_app |
||||
from openpilot.selfdrive.ui.onroad.cameraview import CameraView |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
gui_app.init_window("watch3") |
||||
road = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) |
||||
driver = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) |
||||
wide = CameraView("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD) |
||||
for _ in gui_app.render(): |
||||
road.render(rl.Rectangle(gui_app.width // 4, 0, gui_app.width // 2, gui_app.height // 2)) |
||||
driver.render(rl.Rectangle(0, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2)) |
||||
wide.render(rl.Rectangle(gui_app.width // 2, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2)) |
@ -1,34 +0,0 @@ |
||||
#if SENSOR_ID == 1 |
||||
|
||||
#define VIGNETTE_PROFILE_8DT0MM |
||||
|
||||
#define BIT_DEPTH 12 |
||||
#define PV_MAX 4096 |
||||
#define BLACK_LVL 168 |
||||
|
||||
float4 normalize_pv(int4 parsed, float vignette_factor) { |
||||
float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX - BLACK_LVL); |
||||
return clamp(pv*vignette_factor, 0.0, 1.0); |
||||
} |
||||
|
||||
float3 color_correct(float3 rgb) { |
||||
float3 corrected = rgb.x * (float3)(1.82717181, -0.31231438, 0.07307673); |
||||
corrected += rgb.y * (float3)(-0.5743977, 1.36858544, -0.53183455); |
||||
corrected += rgb.z * (float3)(-0.25277411, -0.05627105, 1.45875782); |
||||
return corrected; |
||||
} |
||||
|
||||
float3 apply_gamma(float3 rgb, int expo_time) { |
||||
// tone mapping params
|
||||
const float gamma_k = 0.75; |
||||
const float gamma_b = 0.125; |
||||
const float mp = 0.01; // ideally midpoint should be adaptive
|
||||
const float rk = 9 - 100*mp; |
||||
|
||||
// poly approximation for s curve
|
||||
return (rgb > mp) ? |
||||
((rk * (rgb-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(rgb-mp))) + gamma_k*mp + gamma_b) : |
||||
((rk * (rgb-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(rgb-mp))) + gamma_k*mp + gamma_b); |
||||
} |
||||
|
||||
#endif |
@ -1,58 +0,0 @@ |
||||
#if SENSOR_ID == 3 |
||||
|
||||
#define BGGR |
||||
#define VIGNETTE_PROFILE_4DT6MM |
||||
|
||||
#define BIT_DEPTH 12 |
||||
#define PV_MAX10 1023 |
||||
#define PV_MAX12 4095 |
||||
#define PV_MAX16 65536 // gamma curve is calibrated to 16bit
|
||||
#define BLACK_LVL 48 |
||||
|
||||
float combine_dual_pvs(float lv, float sv, int expo_time) { |
||||
float svc = fmax(sv * expo_time, (float)(64 * (PV_MAX10 - BLACK_LVL))); |
||||
float svd = sv * fmin(expo_time, 8.0) / 8; |
||||
|
||||
if (expo_time > 64) { |
||||
if (lv < PV_MAX10 - BLACK_LVL) { |
||||
return lv / (PV_MAX16 - BLACK_LVL); |
||||
} else { |
||||
return (svc / 64) / (PV_MAX16 - BLACK_LVL); |
||||
} |
||||
} else { |
||||
if (lv > 32) { |
||||
return (lv * 64 / fmax(expo_time, 8.0)) / (PV_MAX16 - BLACK_LVL); |
||||
} else { |
||||
return svd / (PV_MAX16 - BLACK_LVL); |
||||
} |
||||
} |
||||
} |
||||
|
||||
float4 normalize_pv_hdr(int4 parsed, int4 short_parsed, float vignette_factor, int expo_time) { |
||||
float4 pl = convert_float4(parsed - BLACK_LVL); |
||||
float4 ps = convert_float4(short_parsed - BLACK_LVL); |
||||
float4 pv; |
||||
pv.s0 = combine_dual_pvs(pl.s0, ps.s0, expo_time); |
||||
pv.s1 = combine_dual_pvs(pl.s1, ps.s1, expo_time); |
||||
pv.s2 = combine_dual_pvs(pl.s2, ps.s2, expo_time); |
||||
pv.s3 = combine_dual_pvs(pl.s3, ps.s3, expo_time); |
||||
return clamp(pv*vignette_factor, 0.0, 1.0); |
||||
} |
||||
|
||||
float4 normalize_pv(int4 parsed, float vignette_factor) { |
||||
float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX12 - BLACK_LVL); |
||||
return clamp(pv*vignette_factor, 0.0, 1.0); |
||||
} |
||||
|
||||
float3 color_correct(float3 rgb) { |
||||
float3 corrected = rgb.x * (float3)(1.55361989, -0.268894615, -0.000593219); |
||||
corrected += rgb.y * (float3)(-0.421217301, 1.51883144, -0.69760146); |
||||
corrected += rgb.z * (float3)(-0.132402589, -0.249936825, 1.69819468); |
||||
return corrected; |
||||
} |
||||
|
||||
float3 apply_gamma(float3 rgb, int expo_time) { |
||||
return (10 * rgb) / (1 + 9 * rgb); |
||||
} |
||||
|
||||
#endif |
@ -1,47 +0,0 @@ |
||||
#if SENSOR_ID == 2 |
||||
|
||||
#define VIGNETTE_PROFILE_8DT0MM |
||||
|
||||
#define BIT_DEPTH 12 |
||||
#define BLACK_LVL 64 |
||||
|
||||
float ox_lut_func(int x) { |
||||
if (x < 512) { |
||||
return x * 5.94873e-8; |
||||
} else if (512 <= x && x < 768) { |
||||
return 3.0458e-05 + (x-512) * 1.19913e-7; |
||||
} else if (768 <= x && x < 1536) { |
||||
return 6.1154e-05 + (x-768) * 2.38493e-7; |
||||
} else if (1536 <= x && x < 1792) { |
||||
return 0.0002448 + (x-1536) * 9.56930e-7; |
||||
} else if (1792 <= x && x < 2048) { |
||||
return 0.00048977 + (x-1792) * 1.91441e-6; |
||||
} else if (2048 <= x && x < 2304) { |
||||
return 0.00097984 + (x-2048) * 3.82937e-6; |
||||
} else if (2304 <= x && x < 2560) { |
||||
return 0.0019601 + (x-2304) * 7.659055e-6; |
||||
} else if (2560 <= x && x < 2816) { |
||||
return 0.0039207 + (x-2560) * 1.525e-5; |
||||
} else { |
||||
return 0.0078421 + (exp((x-2816)/273.0) - 1) * 0.0092421; |
||||
} |
||||
} |
||||
|
||||
float4 normalize_pv(int4 parsed, float vignette_factor) { |
||||
// PWL
|
||||
float4 pv = {ox_lut_func(parsed.s0), ox_lut_func(parsed.s1), ox_lut_func(parsed.s2), ox_lut_func(parsed.s3)}; |
||||
return clamp(pv*vignette_factor*256.0, 0.0, 1.0); |
||||
} |
||||
|
||||
float3 color_correct(float3 rgb) { |
||||
float3 corrected = rgb.x * (float3)(1.5664815, -0.29808738, -0.03973474); |
||||
corrected += rgb.y * (float3)(-0.48672447, 1.41914433, -0.40295248); |
||||
corrected += rgb.z * (float3)(-0.07975703, -0.12105695, 1.44268722); |
||||
return corrected; |
||||
} |
||||
|
||||
float3 apply_gamma(float3 rgb, int expo_time) { |
||||
return -0.507089*exp(-12.54124638*rgb) + 0.9655*powr(rgb, 0.5) - 0.472597*rgb + 0.507089; |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,47 @@ |
||||
import io |
||||
import re |
||||
|
||||
from PIL import Image, ImageDraw, ImageFont |
||||
import pyray as rl |
||||
|
||||
_cache: dict[str, rl.Texture] = {} |
||||
|
||||
EMOJI_REGEX = re.compile( |
||||
"""[\U0001F600-\U0001F64F |
||||
\U0001F300-\U0001F5FF |
||||
\U0001F680-\U0001F6FF |
||||
\U0001F1E0-\U0001F1FF |
||||
\U00002700-\U000027BF |
||||
\U0001F900-\U0001F9FF |
||||
\U00002600-\U000026FF |
||||
\U00002300-\U000023FF |
||||
\U00002B00-\U00002BFF |
||||
\U0001FA70-\U0001FAFF |
||||
\U0001F700-\U0001F77F |
||||
\u2640-\u2642 |
||||
\u2600-\u2B55 |
||||
\u200d |
||||
\u23cf |
||||
\u23e9 |
||||
\u231a |
||||
\ufe0f |
||||
\u3030 |
||||
]+""", |
||||
flags=re.UNICODE |
||||
) |
||||
|
||||
def find_emoji(text): |
||||
return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)] |
||||
|
||||
def emoji_tex(emoji): |
||||
if emoji not in _cache: |
||||
img = Image.new("RGBA", (128, 128), (0, 0, 0, 0)) |
||||
draw = ImageDraw.Draw(img) |
||||
font = ImageFont.truetype("NotoColorEmoji", 109) |
||||
draw.text((0, 0), emoji, font=font, embedded_color=True) |
||||
buffer = io.BytesIO() |
||||
img.save(buffer, format="PNG") |
||||
l = buffer.tell() |
||||
buffer.seek(0) |
||||
_cache[emoji] = rl.load_texture_from_image(rl.load_image_from_memory(".png", buffer.getvalue(), l)) |
||||
return _cache[emoji] |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue