mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-08-20 23:53:44 +08:00
dragonpilot mod for 0.8.5-4
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
moc_*
|
||||
*.moc
|
||||
|
||||
replay/replay
|
||||
qt/text
|
||||
qt/spinner
|
||||
qt/setup/setup
|
||||
qt/setup/reset
|
||||
qt/setup/wifi
|
||||
qt/setup/installer*
|
||||
@@ -1,83 +0,0 @@
|
||||
import os
|
||||
Import('qt_env', 'arch', 'common', 'messaging', 'gpucommon', 'visionipc',
|
||||
'cereal', 'transformations')
|
||||
|
||||
base_libs = [gpucommon, common, messaging, cereal, visionipc, transformations, 'zmq',
|
||||
'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"]
|
||||
|
||||
maps = arch in ['larch64', 'x86_64'] and \
|
||||
os.path.exists(File("qt/maps/map.cc").srcnode().abspath)
|
||||
|
||||
if arch == 'aarch64':
|
||||
base_libs += ['log', 'utils', 'gui', 'ui', 'CB', 'gsl', 'adreno_utils', 'cutils', 'uuid']
|
||||
|
||||
if maps and arch in ['x86_64']:
|
||||
rpath = [Dir(f"#phonelibs/mapbox-gl-native-qt/{arch}").srcnode().abspath]
|
||||
qt_env["RPATH"] += rpath
|
||||
|
||||
if arch == "Darwin":
|
||||
del base_libs[base_libs.index('OpenCL')]
|
||||
qt_env['FRAMEWORKS'] += ['OpenCL']
|
||||
|
||||
widgets_src = ["qt/widgets/input.cc", "qt/widgets/drive_stats.cc",
|
||||
"qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc",
|
||||
"qt/widgets/offroad_alerts.cc", "qt/widgets/setup.cc", "qt/widgets/keyboard.cc",
|
||||
"qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#phonelibs/qrcode/QrCode.cc", "qt/api.cc",
|
||||
"qt/request_repeater.cc"]
|
||||
|
||||
if arch != 'aarch64':
|
||||
widgets_src += ["qt/offroad/networking.cc", "qt/offroad/wifiManager.cc"]
|
||||
|
||||
if maps:
|
||||
base_libs += ['qmapboxgl']
|
||||
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map.cc"]
|
||||
qt_env['CPPDEFINES'] = ["ENABLE_MAPS"]
|
||||
|
||||
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)
|
||||
qt_libs = [widgets] + base_libs
|
||||
|
||||
# spinner and text window
|
||||
qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs)
|
||||
qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=base_libs)
|
||||
|
||||
# build main UI
|
||||
qt_src = ["main.cc", "ui.cc", "paint.cc", "qt/sidebar.cc", "qt/onroad.cc",
|
||||
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
|
||||
"qt/offroad/onboarding.cc", "qt/offroad/driverview.cc", "#phonelibs/nanovg/nanovg.c"]
|
||||
|
||||
qt_env.Program("_ui", qt_src, LIBS=qt_libs)
|
||||
|
||||
# setup, factory resetter, and installer
|
||||
if arch != 'aarch64' and "BUILD_SETUP" in os.environ:
|
||||
qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs)
|
||||
qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc"], LIBS=qt_libs + ['curl', 'common', 'json11'])
|
||||
qt_env.Program("qt/setup/wifi", ["qt/setup/wifi.cc"], LIBS=qt_libs + ['common', 'json11'])
|
||||
|
||||
installers = [
|
||||
("openpilot", "master"),
|
||||
("openpilot_test", "release3-staging"),
|
||||
("openpilot_internal", "master"),
|
||||
("dashcam", "dashcam3-staging"),
|
||||
("dashcam_test", "dashcam3-staging"),
|
||||
]
|
||||
for name, branch in installers:
|
||||
d = {'BRANCH': f"'\"{branch}\"'"}
|
||||
if "internal" in name:
|
||||
d['INTERNAL'] = "1"
|
||||
|
||||
import requests
|
||||
r = requests.get("https://github.com/commaci2.keys")
|
||||
r.raise_for_status()
|
||||
d['SSH_KEYS'] = f'\\"{r.text.strip()}\\"'
|
||||
obj = qt_env.Object(f"qt/setup/installer_{name}.o", ["qt/setup/installer.cc"], CPPDEFINES=d)
|
||||
qt_env.Program(f"qt/setup/installer_{name}", obj, LIBS=qt_libs, CPPDEFINES=d)
|
||||
|
||||
# build headless replay
|
||||
if arch == 'x86_64' and os.path.exists(Dir("#tools/").get_abspath()):
|
||||
qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
|
||||
|
||||
replay_lib_src = ["replay/replay.cc", "replay/filereader.cc", "replay/framereader.cc"]
|
||||
|
||||
replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs)
|
||||
replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'swscale', 'bz2'] + qt_libs
|
||||
qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs)
|
||||
Executable
BIN
Binary file not shown.
@@ -1,31 +0,0 @@
|
||||
#include <QApplication>
|
||||
#include <QSslConfiguration>
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/window.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QSurfaceFormat fmt;
|
||||
#ifdef __APPLE__
|
||||
fmt.setVersion(3, 2);
|
||||
fmt.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
fmt.setRenderableType(QSurfaceFormat::OpenGL);
|
||||
#else
|
||||
fmt.setRenderableType(QSurfaceFormat::OpenGLES);
|
||||
#endif
|
||||
QSurfaceFormat::setDefaultFormat(fmt);
|
||||
|
||||
if (Hardware::EON()) {
|
||||
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||
QSslConfiguration ssl = QSslConfiguration::defaultConfiguration();
|
||||
ssl.setCaCertificates(QSslCertificate::fromPath("/usr/etc/tls/cert.pem"));
|
||||
QSslConfiguration::setDefaultConfiguration(ssl);
|
||||
}
|
||||
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
setMainWindow(&w);
|
||||
a.installEventFilter(&w);
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
#include "paint.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl3.h>
|
||||
#define NANOVG_GL3_IMPLEMENTATION
|
||||
#define nvgCreate nvgCreateGL3
|
||||
#else
|
||||
#include <GLES3/gl3.h>
|
||||
#define NANOVG_GLES3_IMPLEMENTATION
|
||||
#define nvgCreate nvgCreateGLES3
|
||||
#endif
|
||||
|
||||
#define NANOVG_GLES3_IMPLEMENTATION
|
||||
#include <nanovg_gl.h>
|
||||
#include <nanovg_gl_utils.h>
|
||||
|
||||
#include "selfdrive/common/timing.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
static void ui_draw_text(const UIState *s, float x, float y, const char *string, float size, NVGcolor color, const char *font_name) {
|
||||
nvgFontFace(s->vg, font_name);
|
||||
nvgFontSize(s->vg, size);
|
||||
nvgFillColor(s->vg, color);
|
||||
nvgText(s->vg, x, y, string, NULL);
|
||||
}
|
||||
|
||||
static void draw_chevron(UIState *s, float x, float y, float sz, NVGcolor fillColor, NVGcolor glowColor) {
|
||||
// glow
|
||||
float g_xo = sz/5;
|
||||
float g_yo = sz/10;
|
||||
nvgBeginPath(s->vg);
|
||||
nvgMoveTo(s->vg, x+(sz*1.35)+g_xo, y+sz+g_yo);
|
||||
nvgLineTo(s->vg, x, y-g_xo);
|
||||
nvgLineTo(s->vg, x-(sz*1.35)-g_xo, y+sz+g_yo);
|
||||
nvgClosePath(s->vg);
|
||||
nvgFillColor(s->vg, glowColor);
|
||||
nvgFill(s->vg);
|
||||
|
||||
// chevron
|
||||
nvgBeginPath(s->vg);
|
||||
nvgMoveTo(s->vg, x+(sz*1.25), y+sz);
|
||||
nvgLineTo(s->vg, x, y);
|
||||
nvgLineTo(s->vg, x-(sz*1.25), y+sz);
|
||||
nvgClosePath(s->vg);
|
||||
nvgFillColor(s->vg, fillColor);
|
||||
nvgFill(s->vg);
|
||||
}
|
||||
|
||||
static void ui_draw_circle_image(const UIState *s, int center_x, int center_y, int radius, const char *image, NVGcolor color, float img_alpha) {
|
||||
nvgBeginPath(s->vg);
|
||||
nvgCircle(s->vg, center_x, center_y, radius);
|
||||
nvgFillColor(s->vg, color);
|
||||
nvgFill(s->vg);
|
||||
const int img_size = radius * 1.5;
|
||||
ui_draw_image(s, {center_x - (img_size / 2), center_y - (img_size / 2), img_size, img_size}, image, img_alpha);
|
||||
}
|
||||
|
||||
static void ui_draw_circle_image(const UIState *s, int center_x, int center_y, int radius, const char *image, bool active) {
|
||||
float bg_alpha = active ? 0.3f : 0.1f;
|
||||
float img_alpha = active ? 1.0f : 0.15f;
|
||||
ui_draw_circle_image(s, center_x, center_y, radius, image, nvgRGBA(0, 0, 0, (255 * bg_alpha)), img_alpha);
|
||||
}
|
||||
|
||||
static void draw_lead(UIState *s, const cereal::RadarState::LeadData::Reader &lead_data, const vertex_data &vd) {
|
||||
// Draw lead car indicator
|
||||
auto [x, y] = vd;
|
||||
|
||||
float fillAlpha = 0;
|
||||
float speedBuff = 10.;
|
||||
float leadBuff = 40.;
|
||||
float d_rel = lead_data.getDRel();
|
||||
float v_rel = lead_data.getVRel();
|
||||
if (d_rel < leadBuff) {
|
||||
fillAlpha = 255*(1.0-(d_rel/leadBuff));
|
||||
if (v_rel < 0) {
|
||||
fillAlpha += 255*(-1*(v_rel/speedBuff));
|
||||
}
|
||||
fillAlpha = (int)(fmin(fillAlpha, 255));
|
||||
}
|
||||
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * s->zoom;
|
||||
x = std::clamp(x, 0.f, s->viz_rect.right() - sz / 2);
|
||||
y = std::fmin(s->viz_rect.bottom() - sz * .6, y);
|
||||
draw_chevron(s, x, y, sz, nvgRGBA(201, 34, 49, fillAlpha), COLOR_YELLOW);
|
||||
}
|
||||
|
||||
static void ui_draw_line(UIState *s, const line_vertices_data &vd, NVGcolor *color, NVGpaint *paint) {
|
||||
if (vd.cnt == 0) return;
|
||||
|
||||
const vertex_data *v = &vd.v[0];
|
||||
nvgBeginPath(s->vg);
|
||||
nvgMoveTo(s->vg, v[0].x, v[0].y);
|
||||
for (int i = 1; i < vd.cnt; i++) {
|
||||
nvgLineTo(s->vg, v[i].x, v[i].y);
|
||||
}
|
||||
nvgClosePath(s->vg);
|
||||
if (color) {
|
||||
nvgFillColor(s->vg, *color);
|
||||
} else if (paint) {
|
||||
nvgFillPaint(s->vg, *paint);
|
||||
}
|
||||
nvgFill(s->vg);
|
||||
}
|
||||
|
||||
static void draw_frame(UIState *s) {
|
||||
glBindVertexArray(s->frame_vao);
|
||||
mat4 *out_mat = &s->rear_frame_mat;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (s->last_frame) {
|
||||
glBindTexture(GL_TEXTURE_2D, s->texture[s->last_frame->idx]->frame_tex);
|
||||
if (!Hardware::EON()) {
|
||||
// this is handled in ion on QCOM
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, s->last_frame->width, s->last_frame->height,
|
||||
0, GL_RGB, GL_UNSIGNED_BYTE, s->last_frame->addr);
|
||||
}
|
||||
}
|
||||
|
||||
glUseProgram(s->gl_shader->prog);
|
||||
glUniform1i(s->gl_shader->getUniformLocation("uTexture"), 0);
|
||||
glUniformMatrix4fv(s->gl_shader->getUniformLocation("uTransform"), 1, GL_TRUE, out_mat->v);
|
||||
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
glEnableVertexAttribArray(0);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (const void *)0);
|
||||
glDisableVertexAttribArray(0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
static void ui_draw_vision_lane_lines(UIState *s) {
|
||||
const UIScene &scene = s->scene;
|
||||
NVGpaint track_bg;
|
||||
if (!scene.end_to_end) {
|
||||
// paint lanelines
|
||||
for (int i = 0; i < std::size(scene.lane_line_vertices); i++) {
|
||||
NVGcolor color = nvgRGBAf(1.0, 1.0, 1.0, scene.lane_line_probs[i]);
|
||||
ui_draw_line(s, scene.lane_line_vertices[i], &color, nullptr);
|
||||
}
|
||||
|
||||
// paint road edges
|
||||
for (int i = 0; i < std::size(scene.road_edge_vertices); i++) {
|
||||
NVGcolor color = nvgRGBAf(1.0, 0.0, 0.0, std::clamp<float>(1.0 - scene.road_edge_stds[i], 0.0, 1.0));
|
||||
ui_draw_line(s, scene.road_edge_vertices[i], &color, nullptr);
|
||||
}
|
||||
track_bg = nvgLinearGradient(s->vg, s->fb_w, s->fb_h, s->fb_w, s->fb_h * .4,
|
||||
COLOR_WHITE, COLOR_WHITE_ALPHA(0));
|
||||
} else {
|
||||
track_bg = nvgLinearGradient(s->vg, s->fb_w, s->fb_h, s->fb_w, s->fb_h * .4,
|
||||
COLOR_RED, COLOR_RED_ALPHA(0));
|
||||
}
|
||||
// paint path
|
||||
ui_draw_line(s, scene.track_vertices, nullptr, &track_bg);
|
||||
}
|
||||
|
||||
// Draw all world space objects.
|
||||
static void ui_draw_world(UIState *s) {
|
||||
// Don't draw on top of sidebar
|
||||
nvgScissor(s->vg, s->viz_rect.x, s->viz_rect.y, s->viz_rect.w, s->viz_rect.h);
|
||||
|
||||
// Draw lane edges and vision/mpc tracks
|
||||
ui_draw_vision_lane_lines(s);
|
||||
|
||||
// Draw lead indicators if openpilot is handling longitudinal
|
||||
if (s->scene.longitudinal_control) {
|
||||
auto radar_state = (*s->sm)["radarState"].getRadarState();
|
||||
auto lead_one = radar_state.getLeadOne();
|
||||
auto lead_two = radar_state.getLeadTwo();
|
||||
if (lead_one.getStatus()) {
|
||||
draw_lead(s, lead_one, s->scene.lead_vertices[0]);
|
||||
}
|
||||
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
|
||||
draw_lead(s, lead_two, s->scene.lead_vertices[1]);
|
||||
}
|
||||
}
|
||||
nvgResetScissor(s->vg);
|
||||
}
|
||||
|
||||
static void ui_draw_vision_maxspeed(UIState *s) {
|
||||
const int SET_SPEED_NA = 255;
|
||||
float maxspeed = (*s->sm)["controlsState"].getControlsState().getVCruise();
|
||||
const bool is_cruise_set = maxspeed != 0 && maxspeed != SET_SPEED_NA;
|
||||
if (is_cruise_set && !s->scene.is_metric) { maxspeed *= 0.6225; }
|
||||
|
||||
const Rect rect = {s->viz_rect.x + (bdr_s * 2), int(s->viz_rect.y + (bdr_s * 1.5)), 184, 202};
|
||||
ui_fill_rect(s->vg, rect, COLOR_BLACK_ALPHA(100), 30.);
|
||||
ui_draw_rect(s->vg, rect, COLOR_WHITE_ALPHA(100), 10, 20.);
|
||||
|
||||
nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
|
||||
ui_draw_text(s, rect.centerX(), 148, "MAX", 26 * 2.5, COLOR_WHITE_ALPHA(is_cruise_set ? 200 : 100), "sans-regular");
|
||||
if (is_cruise_set) {
|
||||
const std::string maxspeed_str = std::to_string((int)std::nearbyint(maxspeed));
|
||||
ui_draw_text(s, rect.centerX(), 242, maxspeed_str.c_str(), 48 * 2.5, COLOR_WHITE, "sans-bold");
|
||||
} else {
|
||||
ui_draw_text(s, rect.centerX(), 242, "N/A", 42 * 2.5, COLOR_WHITE_ALPHA(100), "sans-semibold");
|
||||
}
|
||||
}
|
||||
|
||||
static void ui_draw_vision_speed(UIState *s) {
|
||||
const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? 3.6 : 2.2369363));
|
||||
const std::string speed_str = std::to_string((int)std::nearbyint(speed));
|
||||
nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
|
||||
ui_draw_text(s, s->viz_rect.centerX(), 240, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold");
|
||||
ui_draw_text(s, s->viz_rect.centerX(), 320, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
|
||||
}
|
||||
|
||||
static void ui_draw_vision_event(UIState *s) {
|
||||
if (s->scene.engageable) {
|
||||
// draw steering wheel
|
||||
const int radius = 96;
|
||||
const int center_x = s->viz_rect.right() - radius - bdr_s * 2;
|
||||
const int center_y = s->viz_rect.y + radius + (bdr_s * 1.5);
|
||||
const QColor &color = bg_colors[s->status];
|
||||
NVGcolor nvg_color = nvgRGBA(color.red(), color.green(), color.blue(), color.alpha());
|
||||
ui_draw_circle_image(s, center_x, center_y, radius, "wheel", nvg_color, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
static void ui_draw_vision_face(UIState *s) {
|
||||
const int radius = 96;
|
||||
const int center_x = s->viz_rect.x + radius + (bdr_s * 2);
|
||||
const int center_y = s->viz_rect.bottom() - footer_h / 2;
|
||||
ui_draw_circle_image(s, center_x, center_y, radius, "driver_face", s->scene.dm_active);
|
||||
}
|
||||
|
||||
static void ui_draw_vision_header(UIState *s) {
|
||||
NVGpaint gradient = nvgLinearGradient(s->vg, s->viz_rect.x,
|
||||
s->viz_rect.y+(header_h-(header_h/2.5)),
|
||||
s->viz_rect.x, s->viz_rect.y+header_h,
|
||||
nvgRGBAf(0,0,0,0.45), nvgRGBAf(0,0,0,0));
|
||||
|
||||
ui_fill_rect(s->vg, {s->viz_rect.x, s->viz_rect.y, s->viz_rect.w, header_h}, gradient);
|
||||
|
||||
ui_draw_vision_maxspeed(s);
|
||||
ui_draw_vision_speed(s);
|
||||
ui_draw_vision_event(s);
|
||||
}
|
||||
|
||||
static void ui_draw_vision_frame(UIState *s) {
|
||||
// Draw video frames
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glViewport(s->video_rect.x, s->video_rect.y, s->video_rect.w, s->video_rect.h);
|
||||
glScissor(s->viz_rect.x, s->viz_rect.y, s->viz_rect.w, s->viz_rect.h);
|
||||
draw_frame(s);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
||||
glViewport(0, 0, s->fb_w, s->fb_h);
|
||||
}
|
||||
|
||||
static void ui_draw_vision(UIState *s) {
|
||||
const UIScene *scene = &s->scene;
|
||||
// Draw augmented elements
|
||||
if (scene->world_objects_visible) {
|
||||
ui_draw_world(s);
|
||||
}
|
||||
// Set Speed, Current Speed, Status/Events
|
||||
ui_draw_vision_header(s);
|
||||
if ((*s->sm)["controlsState"].getControlsState().getAlertSize() == cereal::ControlsState::AlertSize::NONE) {
|
||||
ui_draw_vision_face(s);
|
||||
}
|
||||
}
|
||||
|
||||
static void ui_draw_background(UIState *s) {
|
||||
const QColor &color = bg_colors[s->status];
|
||||
glClearColor(color.redF(), color.greenF(), color.blueF(), 1.0);
|
||||
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void ui_draw(UIState *s, int w, int h) {
|
||||
s->viz_rect = Rect{bdr_s, bdr_s, w - 2 * bdr_s, h - 2 * bdr_s};
|
||||
|
||||
const bool draw_vision = s->scene.started && s->vipc_client->connected;
|
||||
|
||||
// GL drawing functions
|
||||
ui_draw_background(s);
|
||||
if (draw_vision) {
|
||||
ui_draw_vision_frame(s);
|
||||
}
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glViewport(0, 0, s->fb_w, s->fb_h);
|
||||
|
||||
// NVG drawing functions - should be no GL inside NVG frame
|
||||
nvgBeginFrame(s->vg, s->fb_w, s->fb_h, 1.0f);
|
||||
|
||||
if (draw_vision) {
|
||||
ui_draw_vision(s);
|
||||
}
|
||||
|
||||
nvgEndFrame(s->vg);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void ui_draw_image(const UIState *s, const Rect &r, const char *name, float alpha) {
|
||||
nvgBeginPath(s->vg);
|
||||
NVGpaint imgPaint = nvgImagePattern(s->vg, r.x, r.y, r.w, r.h, 0, s->images.at(name), alpha);
|
||||
nvgRect(s->vg, r.x, r.y, r.w, r.h);
|
||||
nvgFillPaint(s->vg, imgPaint);
|
||||
nvgFill(s->vg);
|
||||
}
|
||||
|
||||
void ui_draw_rect(NVGcontext *vg, const Rect &r, NVGcolor color, int width, float radius) {
|
||||
nvgBeginPath(vg);
|
||||
radius > 0 ? nvgRoundedRect(vg, r.x, r.y, r.w, r.h, radius) : nvgRect(vg, r.x, r.y, r.w, r.h);
|
||||
nvgStrokeColor(vg, color);
|
||||
nvgStrokeWidth(vg, width);
|
||||
nvgStroke(vg);
|
||||
}
|
||||
|
||||
static inline void fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor *color, const NVGpaint *paint, float radius) {
|
||||
nvgBeginPath(vg);
|
||||
radius > 0 ? nvgRoundedRect(vg, r.x, r.y, r.w, r.h, radius) : nvgRect(vg, r.x, r.y, r.w, r.h);
|
||||
if (color) nvgFillColor(vg, *color);
|
||||
if (paint) nvgFillPaint(vg, *paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor &color, float radius) {
|
||||
fill_rect(vg, r, &color, nullptr, radius);
|
||||
}
|
||||
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGpaint &paint, float radius) {
|
||||
fill_rect(vg, r, nullptr, &paint, radius);
|
||||
}
|
||||
|
||||
static const char frame_vertex_shader[] =
|
||||
#ifdef NANOVG_GL3_IMPLEMENTATION
|
||||
"#version 150 core\n"
|
||||
#else
|
||||
"#version 300 es\n"
|
||||
#endif
|
||||
"in vec4 aPosition;\n"
|
||||
"in vec4 aTexCoord;\n"
|
||||
"uniform mat4 uTransform;\n"
|
||||
"out vec4 vTexCoord;\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = uTransform * aPosition;\n"
|
||||
" vTexCoord = aTexCoord;\n"
|
||||
"}\n";
|
||||
|
||||
static const char frame_fragment_shader[] =
|
||||
#ifdef NANOVG_GL3_IMPLEMENTATION
|
||||
"#version 150 core\n"
|
||||
#else
|
||||
"#version 300 es\n"
|
||||
#endif
|
||||
"precision mediump float;\n"
|
||||
"uniform sampler2D uTexture;\n"
|
||||
"in vec4 vTexCoord;\n"
|
||||
"out vec4 colorOut;\n"
|
||||
"void main() {\n"
|
||||
" colorOut = texture(uTexture, vTexCoord.xy);\n"
|
||||
#ifdef QCOM
|
||||
" vec3 dz = vec3(0.0627f, 0.0627f, 0.0627f);\n"
|
||||
" colorOut.rgb = ((vec3(1.0f, 1.0f, 1.0f) - dz) * colorOut.rgb / vec3(1.0f, 1.0f, 1.0f)) + dz;\n"
|
||||
#endif
|
||||
"}\n";
|
||||
|
||||
static const mat4 device_transform = {{
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
|
||||
void ui_nvg_init(UIState *s) {
|
||||
// init drawing
|
||||
|
||||
// on EON, we enable MSAA
|
||||
s->vg = Hardware::EON() ? nvgCreate(0) : nvgCreate(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
|
||||
assert(s->vg);
|
||||
|
||||
// init fonts
|
||||
std::pair<const char *, const char *> fonts[] = {
|
||||
{"sans-regular", "../assets/fonts/opensans_regular.ttf"},
|
||||
{"sans-semibold", "../assets/fonts/opensans_semibold.ttf"},
|
||||
{"sans-bold", "../assets/fonts/opensans_bold.ttf"},
|
||||
};
|
||||
for (auto [name, file] : fonts) {
|
||||
int font_id = nvgCreateFont(s->vg, name, file);
|
||||
assert(font_id >= 0);
|
||||
}
|
||||
|
||||
// init images
|
||||
std::vector<std::pair<const char *, const char *>> images = {
|
||||
{"wheel", "../assets/img_chffr_wheel.png"},
|
||||
{"driver_face", "../assets/img_driver_face.png"},
|
||||
};
|
||||
for (auto [name, file] : images) {
|
||||
s->images[name] = nvgCreateImage(s->vg, file, 1);
|
||||
assert(s->images[name] != 0);
|
||||
}
|
||||
|
||||
// init gl
|
||||
s->gl_shader = std::make_unique<GLShader>(frame_vertex_shader, frame_fragment_shader);
|
||||
GLint frame_pos_loc = glGetAttribLocation(s->gl_shader->prog, "aPosition");
|
||||
GLint frame_texcoord_loc = glGetAttribLocation(s->gl_shader->prog, "aTexCoord");
|
||||
|
||||
glViewport(0, 0, s->fb_w, s->fb_h);
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
float x1 = 1.0, x2 = 0.0, y1 = 1.0, y2 = 0.0;
|
||||
const uint8_t frame_indicies[] = {0, 1, 2, 0, 2, 3};
|
||||
const float frame_coords[4][4] = {
|
||||
{-1.0, -1.0, x2, y1}, //bl
|
||||
{-1.0, 1.0, x2, y2}, //tl
|
||||
{ 1.0, 1.0, x1, y2}, //tr
|
||||
{ 1.0, -1.0, x1, y1}, //br
|
||||
};
|
||||
|
||||
glGenVertexArrays(1, &s->frame_vao);
|
||||
glBindVertexArray(s->frame_vao);
|
||||
glGenBuffers(1, &s->frame_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, s->frame_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(frame_coords), frame_coords, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(frame_pos_loc);
|
||||
glVertexAttribPointer(frame_pos_loc, 2, GL_FLOAT, GL_FALSE,
|
||||
sizeof(frame_coords[0]), (const void *)0);
|
||||
glEnableVertexAttribArray(frame_texcoord_loc);
|
||||
glVertexAttribPointer(frame_texcoord_loc, 2, GL_FLOAT, GL_FALSE,
|
||||
sizeof(frame_coords[0]), (const void *)(sizeof(float) * 2));
|
||||
glGenBuffers(1, &s->frame_ibo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->frame_ibo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(frame_indicies), frame_indicies, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
ui_resize(s, s->fb_w, s->fb_h);
|
||||
}
|
||||
|
||||
void ui_resize(UIState *s, int width, int height){
|
||||
s->fb_w = width;
|
||||
s->fb_h = height;
|
||||
|
||||
auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix;
|
||||
|
||||
s->zoom = zoom / intrinsic_matrix.v[0];
|
||||
|
||||
if (s->wide_camera) {
|
||||
s->zoom *= 0.5;
|
||||
}
|
||||
|
||||
s->video_rect = Rect{bdr_s, bdr_s, s->fb_w - 2 * bdr_s, s->fb_h - 2 * bdr_s};
|
||||
float zx = s->zoom * 2 * intrinsic_matrix.v[2] / s->video_rect.w;
|
||||
float zy = s->zoom * 2 * intrinsic_matrix.v[5] / s->video_rect.h;
|
||||
|
||||
const mat4 frame_transform = {{
|
||||
zx, 0.0, 0.0, 0.0,
|
||||
0.0, zy, 0.0, -y_offset / s->video_rect.h * 2,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
|
||||
s->rear_frame_mat = matmul(device_transform, frame_transform);
|
||||
|
||||
// Apply transformation such that video pixel coordinates match video
|
||||
// 1) Put (0, 0) in the middle of the video
|
||||
nvgTranslate(s->vg, s->video_rect.x + s->video_rect.w / 2, s->video_rect.y + s->video_rect.h / 2 + y_offset);
|
||||
|
||||
// 2) Apply same scaling as video
|
||||
nvgScale(s->vg, s->zoom, s->zoom);
|
||||
|
||||
// 3) Put (0, 0) in top left corner of video
|
||||
nvgTranslate(s->vg, -intrinsic_matrix.v[2], -intrinsic_matrix.v[5]);
|
||||
|
||||
nvgCurrentTransform(s->vg, s->car_space_transform);
|
||||
nvgResetTransform(s->vg);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
void ui_draw(UIState *s, int w, int h);
|
||||
void ui_draw_image(const UIState *s, const Rect &r, const char *name, float alpha);
|
||||
void ui_draw_rect(NVGcontext *vg, const Rect &r, NVGcolor color, int width, float radius = 0);
|
||||
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGpaint &paint, float radius = 0);
|
||||
void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor &color, float radius = 0);
|
||||
void ui_nvg_init(UIState *s);
|
||||
void ui_resize(UIState *s, int width, int height);
|
||||
@@ -1,141 +0,0 @@
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRandomGenerator>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
|
||||
const std::string private_key_path =
|
||||
Hardware::PC() ? util::getenv_default("HOME", "/.comma/persist/comma/id_rsa", "/persist/comma/id_rsa")
|
||||
: "/persist/comma/id_rsa";
|
||||
|
||||
QByteArray CommaApi::rsa_sign(const QByteArray &data) {
|
||||
auto file = QFile(private_key_path.c_str());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "No RSA private key found, please run manager.py or registration.py";
|
||||
return QByteArray();
|
||||
}
|
||||
auto key = file.readAll();
|
||||
file.close();
|
||||
file.deleteLater();
|
||||
BIO* mem = BIO_new_mem_buf(key.data(), key.size());
|
||||
assert(mem);
|
||||
RSA* rsa_private = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL);
|
||||
assert(rsa_private);
|
||||
auto sig = QByteArray();
|
||||
sig.resize(RSA_size(rsa_private));
|
||||
unsigned int sig_len;
|
||||
int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private);
|
||||
assert(ret == 1);
|
||||
assert(sig_len == sig.size());
|
||||
BIO_free(mem);
|
||||
RSA_free(rsa_private);
|
||||
return sig;
|
||||
}
|
||||
|
||||
QString CommaApi::create_jwt(const QVector<QPair<QString, QJsonValue>> &payloads, int expiry) {
|
||||
QString dongle_id = QString::fromStdString(Params().get("DongleId"));
|
||||
|
||||
QJsonObject header;
|
||||
header.insert("alg", "RS256");
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert("identity", dongle_id);
|
||||
|
||||
auto t = QDateTime::currentSecsSinceEpoch();
|
||||
payload.insert("nbf", t);
|
||||
payload.insert("iat", t);
|
||||
payload.insert("exp", t + expiry);
|
||||
for (auto &load : payloads) {
|
||||
payload.insert(load.first, load.second);
|
||||
}
|
||||
|
||||
auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
|
||||
QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' +
|
||||
QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts);
|
||||
|
||||
auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256);
|
||||
auto sig = rsa_sign(hash);
|
||||
jwt += '.' + sig.toBase64(b64_opts);
|
||||
return jwt;
|
||||
}
|
||||
|
||||
|
||||
HttpRequest::HttpRequest(QObject *parent, const QString &requestURL, const QString &cache_key, bool create_jwt_) : cache_key(cache_key), create_jwt(create_jwt_), QObject(parent) {
|
||||
networkAccessManager = new QNetworkAccessManager(this);
|
||||
reply = NULL;
|
||||
|
||||
networkTimer = new QTimer(this);
|
||||
networkTimer->setSingleShot(true);
|
||||
networkTimer->setInterval(20000);
|
||||
connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout);
|
||||
|
||||
sendRequest(requestURL);
|
||||
|
||||
if (!cache_key.isEmpty()) {
|
||||
if (std::string cached_resp = Params().get(cache_key.toStdString()); !cached_resp.empty()) {
|
||||
QTimer::singleShot(0, [=]() { emit receivedResponse(QString::fromStdString(cached_resp)); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::sendRequest(const QString &requestURL){
|
||||
QString token;
|
||||
if(create_jwt) {
|
||||
token = CommaApi::create_jwt();
|
||||
} else {
|
||||
QString token_json = QString::fromStdString(util::read_file(util::getenv_default("HOME", "/.comma/auth.json", "/.comma/auth.json")));
|
||||
QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8());
|
||||
token = json_d["access_token"].toString();
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(requestURL));
|
||||
request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8());
|
||||
|
||||
reply = networkAccessManager->get(request);
|
||||
|
||||
networkTimer->start();
|
||||
connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished);
|
||||
}
|
||||
|
||||
void HttpRequest::requestTimeout(){
|
||||
reply->abort();
|
||||
}
|
||||
|
||||
// This function should always emit something
|
||||
void HttpRequest::requestFinished(){
|
||||
if (reply->error() != QNetworkReply::OperationCanceledError) {
|
||||
networkTimer->stop();
|
||||
QString response = reply->readAll();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
// save to cache
|
||||
if (!cache_key.isEmpty()) {
|
||||
Params().put(cache_key.toStdString(), response.toStdString());
|
||||
}
|
||||
emit receivedResponse(response);
|
||||
} else {
|
||||
if (!cache_key.isEmpty()) {
|
||||
Params().remove(cache_key.toStdString());
|
||||
}
|
||||
qDebug() << reply->errorString();
|
||||
emit failedResponse(reply->errorString());
|
||||
}
|
||||
} else {
|
||||
emit timeoutResponse("timeout");
|
||||
}
|
||||
reply->deleteLater();
|
||||
reply = NULL;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonValue>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
#include <atomic>
|
||||
|
||||
class CommaApi : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QByteArray rsa_sign(const QByteArray &data);
|
||||
static QString create_jwt(const QVector<QPair<QString, QJsonValue>> &payloads = {}, int expiry = 3600);
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a request to the request endpoint.
|
||||
*/
|
||||
|
||||
class HttpRequest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpRequest(QObject* parent, const QString &requestURL, const QString &cache_key = "", bool create_jwt_ = true);
|
||||
QNetworkReply *reply;
|
||||
void sendRequest(const QString &requestURL);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *networkAccessManager;
|
||||
QTimer *networkTimer;
|
||||
QString cache_key;
|
||||
bool create_jwt;
|
||||
|
||||
private slots:
|
||||
void requestTimeout();
|
||||
void requestFinished();
|
||||
|
||||
signals:
|
||||
void receivedResponse(const QString &response);
|
||||
void failedResponse(const QString &errorString);
|
||||
void timeoutResponse(const QString &errorString);
|
||||
};
|
||||
@@ -1,210 +0,0 @@
|
||||
#include "selfdrive/ui/qt/home.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/common/swaglog.h"
|
||||
#include "selfdrive/common/timing.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/drive_stats.h"
|
||||
#include "selfdrive/ui/qt/widgets/setup.h"
|
||||
|
||||
// HomeWindow: the container for the offroad and onroad UIs
|
||||
|
||||
HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
sidebar = new Sidebar(this);
|
||||
layout->addWidget(sidebar);
|
||||
QObject::connect(this, &HomeWindow::update, sidebar, &Sidebar::updateState);
|
||||
QObject::connect(sidebar, &Sidebar::openSettings, this, &HomeWindow::openSettings);
|
||||
|
||||
slayout = new QStackedLayout();
|
||||
layout->addLayout(slayout);
|
||||
|
||||
onroad = new OnroadWindow(this);
|
||||
slayout->addWidget(onroad);
|
||||
|
||||
QObject::connect(this, &HomeWindow::update, onroad, &OnroadWindow::update);
|
||||
QObject::connect(this, &HomeWindow::offroadTransitionSignal, onroad, &OnroadWindow::offroadTransitionSignal);
|
||||
|
||||
home = new OffroadHome();
|
||||
slayout->addWidget(home);
|
||||
QObject::connect(this, &HomeWindow::openSettings, home, &OffroadHome::refresh);
|
||||
|
||||
driver_view = new DriverViewWindow(this);
|
||||
connect(driver_view, &DriverViewWindow::done, [=] {
|
||||
showDriverView(false);
|
||||
});
|
||||
slayout->addWidget(driver_view);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void HomeWindow::offroadTransition(bool offroad) {
|
||||
if (offroad) {
|
||||
slayout->setCurrentWidget(home);
|
||||
} else {
|
||||
slayout->setCurrentWidget(onroad);
|
||||
}
|
||||
sidebar->setVisible(offroad);
|
||||
emit offroadTransitionSignal(offroad);
|
||||
}
|
||||
|
||||
void HomeWindow::showDriverView(bool show) {
|
||||
if (show) {
|
||||
emit closeSettings();
|
||||
slayout->setCurrentWidget(driver_view);
|
||||
} else {
|
||||
slayout->setCurrentWidget(home);
|
||||
}
|
||||
sidebar->setVisible(show == false);
|
||||
}
|
||||
|
||||
void HomeWindow::mousePressEvent(QMouseEvent* e) {
|
||||
// Handle sidebar collapsing
|
||||
if (onroad->isVisible() && (!sidebar->isVisible() || e->x() > sidebar->width())) {
|
||||
|
||||
// TODO: Handle this without exposing pointer to map widget
|
||||
// Hide map first if visible, then hide sidebar
|
||||
if (onroad->map != nullptr && onroad->map->isVisible()){
|
||||
onroad->map->setVisible(false);
|
||||
} else if (!sidebar->isVisible()) {
|
||||
sidebar->setVisible(true);
|
||||
} else {
|
||||
sidebar->setVisible(false);
|
||||
|
||||
if (onroad->map != nullptr) onroad->map->setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OffroadHome: the offroad home page
|
||||
|
||||
OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
|
||||
QVBoxLayout* main_layout = new QVBoxLayout();
|
||||
main_layout->setMargin(50);
|
||||
|
||||
// top header
|
||||
QHBoxLayout* header_layout = new QHBoxLayout();
|
||||
|
||||
date = new QLabel();
|
||||
date->setStyleSheet(R"(font-size: 55px;)");
|
||||
header_layout->addWidget(date, 0, Qt::AlignHCenter | Qt::AlignLeft);
|
||||
|
||||
alert_notification = new QPushButton();
|
||||
alert_notification->setVisible(false);
|
||||
QObject::connect(alert_notification, &QPushButton::released, this, &OffroadHome::openAlerts);
|
||||
header_layout->addWidget(alert_notification, 0, Qt::AlignHCenter | Qt::AlignRight);
|
||||
|
||||
std::string brand = Params().getBool("Passive") ? "dashcam" : "openpilot";
|
||||
QLabel* version = new QLabel(QString::fromStdString(brand + " v" + Params().get("Version")));
|
||||
version->setStyleSheet(R"(font-size: 55px;)");
|
||||
header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight);
|
||||
|
||||
main_layout->addLayout(header_layout);
|
||||
|
||||
// main content
|
||||
main_layout->addSpacing(25);
|
||||
center_layout = new QStackedLayout();
|
||||
|
||||
QHBoxLayout* statsAndSetup = new QHBoxLayout();
|
||||
statsAndSetup->setMargin(0);
|
||||
|
||||
DriveStats* drive = new DriveStats;
|
||||
drive->setFixedSize(800, 800);
|
||||
statsAndSetup->addWidget(drive);
|
||||
|
||||
SetupWidget* setup = new SetupWidget;
|
||||
statsAndSetup->addWidget(setup);
|
||||
|
||||
QWidget* statsAndSetupWidget = new QWidget();
|
||||
statsAndSetupWidget->setLayout(statsAndSetup);
|
||||
|
||||
center_layout->addWidget(statsAndSetupWidget);
|
||||
|
||||
alerts_widget = new OffroadAlert();
|
||||
QObject::connect(alerts_widget, &OffroadAlert::closeAlerts, this, &OffroadHome::closeAlerts);
|
||||
center_layout->addWidget(alerts_widget);
|
||||
center_layout->setAlignment(alerts_widget, Qt::AlignCenter);
|
||||
|
||||
main_layout->addLayout(center_layout, 1);
|
||||
|
||||
// set up refresh timer
|
||||
timer = new QTimer(this);
|
||||
QObject::connect(timer, &QTimer::timeout, this, &OffroadHome::refresh);
|
||||
timer->start(10 * 1000);
|
||||
|
||||
setLayout(main_layout);
|
||||
setStyleSheet(R"(
|
||||
OffroadHome {
|
||||
background-color: black;
|
||||
}
|
||||
* {
|
||||
color: white;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void OffroadHome::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void OffroadHome::openAlerts() {
|
||||
center_layout->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void OffroadHome::closeAlerts() {
|
||||
center_layout->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void OffroadHome::refresh() {
|
||||
bool first_refresh = !date->text().size();
|
||||
if (!isVisible() && !first_refresh) {
|
||||
return;
|
||||
}
|
||||
|
||||
date->setText(QDateTime::currentDateTime().toString("dddd, MMMM d"));
|
||||
|
||||
// update alerts
|
||||
|
||||
alerts_widget->refresh();
|
||||
if (!alerts_widget->alertCount && !alerts_widget->updateAvailable) {
|
||||
emit closeAlerts();
|
||||
alert_notification->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (alerts_widget->updateAvailable) {
|
||||
alert_notification->setText("UPDATE");
|
||||
} else {
|
||||
int alerts = alerts_widget->alertCount;
|
||||
alert_notification->setText(QString::number(alerts) + " ALERT" + (alerts == 1 ? "" : "S"));
|
||||
}
|
||||
|
||||
if (!alert_notification->isVisible() && !first_refresh) {
|
||||
emit openAlerts();
|
||||
}
|
||||
alert_notification->setVisible(true);
|
||||
|
||||
// Red background for alerts, blue for update available
|
||||
QString style = QString(R"(
|
||||
padding: 15px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
background-color: #E22C2C;
|
||||
)");
|
||||
if (alerts_widget->updateAvailable) {
|
||||
style.replace("#E22C2C", "#364DEF");
|
||||
}
|
||||
alert_notification->setStyleSheet(style);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QStackedLayout>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/driverview.h"
|
||||
#include "selfdrive/ui/qt/onroad.h"
|
||||
#include "selfdrive/ui/qt/sidebar.h"
|
||||
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class OffroadHome : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OffroadHome(QWidget* parent = 0);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
QTimer* timer;
|
||||
|
||||
QLabel* date;
|
||||
QStackedLayout* center_layout;
|
||||
OffroadAlert* alerts_widget;
|
||||
QPushButton* alert_notification;
|
||||
|
||||
public slots:
|
||||
void closeAlerts();
|
||||
void openAlerts();
|
||||
void refresh();
|
||||
};
|
||||
|
||||
class HomeWindow : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HomeWindow(QWidget* parent = 0);
|
||||
|
||||
signals:
|
||||
void openSettings();
|
||||
void closeSettings();
|
||||
|
||||
// forwarded signals
|
||||
void displayPowerChanged(bool on);
|
||||
void update(const UIState &s);
|
||||
void offroadTransitionSignal(bool offroad);
|
||||
|
||||
public slots:
|
||||
void offroadTransition(bool offroad);
|
||||
void showDriverView(bool show);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* e) override;
|
||||
|
||||
private:
|
||||
Sidebar *sidebar;
|
||||
OffroadHome *home;
|
||||
OnroadWindow *onroad;
|
||||
DriverViewWindow *driver_view;
|
||||
QStackedLayout *slayout;
|
||||
};
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,109 +0,0 @@
|
||||
#include "selfdrive/ui/qt/offroad/driverview.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
const int FACE_IMG_SIZE = 130;
|
||||
|
||||
DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
layout = new QStackedLayout(this);
|
||||
layout->setStackingMode(QStackedLayout::StackAll);
|
||||
|
||||
cameraView = new CameraViewWidget(VISION_STREAM_RGB_FRONT, this);
|
||||
layout->addWidget(cameraView);
|
||||
|
||||
scene = new DriverViewScene(this);
|
||||
connect(cameraView, &CameraViewWidget::frameUpdated, scene, &DriverViewScene::frameUpdated);
|
||||
layout->addWidget(scene);
|
||||
layout->setCurrentWidget(scene);
|
||||
}
|
||||
|
||||
void DriverViewWindow::mousePressEvent(QMouseEvent* e) {
|
||||
emit done();
|
||||
}
|
||||
|
||||
DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverState"}), QWidget(parent) {
|
||||
face = QImage("../assets/img_driver_face.png").scaled(FACE_IMG_SIZE, FACE_IMG_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
void DriverViewScene::showEvent(QShowEvent* event) {
|
||||
frame_updated = false;
|
||||
is_rhd = params.getBool("IsRHD");
|
||||
params.putBool("IsDriverViewEnabled", true);
|
||||
}
|
||||
|
||||
void DriverViewScene::hideEvent(QHideEvent* event) {
|
||||
params.putBool("IsDriverViewEnabled", false);
|
||||
}
|
||||
|
||||
void DriverViewScene::frameUpdated() {
|
||||
frame_updated = true;
|
||||
sm.update(0);
|
||||
update();
|
||||
}
|
||||
|
||||
void DriverViewScene::paintEvent(QPaintEvent* event) {
|
||||
QPainter p(this);
|
||||
|
||||
// startup msg
|
||||
if (!frame_updated) {
|
||||
p.setPen(QColor(0xff, 0xff, 0xff));
|
||||
p.setRenderHint(QPainter::TextAntialiasing);
|
||||
configFont(p, "Inter", 100, "Bold");
|
||||
p.drawText(geometry(), Qt::AlignCenter, "camera starting");
|
||||
return;
|
||||
}
|
||||
|
||||
const int width = 4 * height() / 3;
|
||||
const QRect rect2 = {rect().center().x() - width / 2, rect().top(), width, rect().height()};
|
||||
const QRect valid_rect = {is_rhd ? rect2.right() - rect2.height() / 2 : rect2.left(), rect2.top(), rect2.height() / 2, rect2.height()};
|
||||
|
||||
// blackout
|
||||
const int blackout_x_r = valid_rect.right();
|
||||
const QRect& blackout_rect = Hardware::TICI() ? rect() : rect2;
|
||||
const int blackout_w_r = blackout_rect.right() - valid_rect.right();
|
||||
const int blackout_x_l = blackout_rect.left();
|
||||
const int blackout_w_l = valid_rect.left() - blackout_x_l;
|
||||
|
||||
QColor bg(0, 0, 0, 140);
|
||||
p.setPen(QPen(bg));
|
||||
p.setBrush(QBrush(bg));
|
||||
p.drawRect(blackout_x_l, rect2.top(), blackout_w_l, rect2.height());
|
||||
p.drawRect(blackout_x_r, rect2.top(), blackout_w_r, rect2.height());
|
||||
|
||||
// face bounding box
|
||||
cereal::DriverState::Reader driver_state = sm["driverState"].getDriverState();
|
||||
bool face_detected = driver_state.getFaceProb() > 0.4;
|
||||
if (face_detected) {
|
||||
auto fxy_list = driver_state.getFacePosition();
|
||||
float face_x = fxy_list[0];
|
||||
float face_y = fxy_list[1];
|
||||
int fbox_x = valid_rect.center().x() + (is_rhd ? face_x : -face_x) * valid_rect.width();
|
||||
int fbox_y = valid_rect.center().y() + face_y * valid_rect.height();
|
||||
|
||||
float alpha = 0.2;
|
||||
face_x = std::abs(face_x);
|
||||
face_y = std::abs(face_y);
|
||||
if (face_x <= 0.35 && face_y <= 0.4) {
|
||||
alpha = 0.8 - (face_x > face_y ? face_x : face_y) * 0.6 / 0.375;
|
||||
}
|
||||
|
||||
const int box_size = 0.6 * rect2.height() / 2;
|
||||
QPen pen(QColor(255, 255, 255, alpha * 255));
|
||||
pen.setWidth(10);
|
||||
p.setPen(pen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawRoundedRect(fbox_x - box_size / 2, fbox_y - box_size / 2, box_size, box_size, 35.0, 35.0);
|
||||
}
|
||||
|
||||
// icon
|
||||
const int img_offset = 30;
|
||||
const int img_x = is_rhd ? rect2.right() - FACE_IMG_SIZE - img_offset : rect2.left() + img_offset;
|
||||
const int img_y = rect2.bottom() - FACE_IMG_SIZE - img_offset;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setOpacity(face_detected ? 1.0 : 0.3);
|
||||
p.drawImage(img_x, img_y, face);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QStackedLayout>
|
||||
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
|
||||
class DriverViewScene : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DriverViewScene(QWidget *parent);
|
||||
|
||||
public slots:
|
||||
void frameUpdated();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
Params params;
|
||||
SubMaster sm;
|
||||
QImage face;
|
||||
bool is_rhd = false;
|
||||
bool frame_updated = false;
|
||||
};
|
||||
|
||||
class DriverViewWindow : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DriverViewWindow(QWidget *parent);
|
||||
|
||||
signals:
|
||||
void done();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* e) override;
|
||||
|
||||
private:
|
||||
CameraViewWidget *cameraView;
|
||||
DriverViewScene *scene;
|
||||
QStackedLayout *layout;
|
||||
};
|
||||
@@ -1,236 +0,0 @@
|
||||
#include "networking.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
// Networking functions
|
||||
|
||||
Networking::Networking(QWidget* parent, bool show_advanced) : QWidget(parent), show_advanced(show_advanced){
|
||||
s = new QStackedLayout;
|
||||
|
||||
QLabel* warning = new QLabel("Network manager is inactive!");
|
||||
warning->setAlignment(Qt::AlignCenter);
|
||||
warning->setStyleSheet(R"(font-size: 65px;)");
|
||||
|
||||
s->addWidget(warning);
|
||||
setLayout(s);
|
||||
|
||||
QTimer* timer = new QTimer(this);
|
||||
QObject::connect(timer, &QTimer::timeout, this, &Networking::refresh);
|
||||
timer->start(5000);
|
||||
attemptInitialization();
|
||||
}
|
||||
|
||||
void Networking::attemptInitialization(){
|
||||
// Checks if network manager is active
|
||||
try {
|
||||
wifi = new WifiManager(this);
|
||||
} catch (std::exception &e) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(wifi, &WifiManager::wrongPassword, this, &Networking::wrongPassword);
|
||||
|
||||
QVBoxLayout* vlayout = new QVBoxLayout;
|
||||
|
||||
if (show_advanced) {
|
||||
QPushButton* advancedSettings = new QPushButton("Advanced");
|
||||
advancedSettings->setStyleSheet("margin-right: 30px;");
|
||||
advancedSettings->setFixedSize(350, 100);
|
||||
connect(advancedSettings, &QPushButton::released, [=](){ s->setCurrentWidget(an); });
|
||||
vlayout->addSpacing(10);
|
||||
vlayout->addWidget(advancedSettings, 0, Qt::AlignRight);
|
||||
vlayout->addSpacing(10);
|
||||
}
|
||||
|
||||
wifiWidget = new WifiUI(this, wifi);
|
||||
connect(wifiWidget, &WifiUI::connectToNetwork, this, &Networking::connectToNetwork);
|
||||
vlayout->addWidget(new ScrollView(wifiWidget, this), 1);
|
||||
|
||||
QWidget* wifiScreen = new QWidget(this);
|
||||
wifiScreen->setLayout(vlayout);
|
||||
s->addWidget(wifiScreen);
|
||||
|
||||
an = new AdvancedNetworking(this, wifi);
|
||||
connect(an, &AdvancedNetworking::backPress, [=](){s->setCurrentWidget(wifiScreen);});
|
||||
s->addWidget(an);
|
||||
|
||||
setStyleSheet(R"(
|
||||
QPushButton {
|
||||
font-size: 50px;
|
||||
margin: 0px;
|
||||
padding: 15px;
|
||||
border-width: 0;
|
||||
border-radius: 30px;
|
||||
color: #dddddd;
|
||||
background-color: #444444;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
color: #777777;
|
||||
background-color: #222222;
|
||||
}
|
||||
)");
|
||||
s->setCurrentWidget(wifiScreen);
|
||||
ui_setup_complete = true;
|
||||
}
|
||||
|
||||
void Networking::refresh(){
|
||||
if (!this->isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (!ui_setup_complete) {
|
||||
attemptInitialization();
|
||||
if (!ui_setup_complete) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
wifiWidget->refresh();
|
||||
an->refresh();
|
||||
}
|
||||
|
||||
void Networking::connectToNetwork(Network n) {
|
||||
if (n.security_type == SecurityType::OPEN) {
|
||||
wifi->connect(n);
|
||||
} else if (n.security_type == SecurityType::WPA) {
|
||||
QString pass = InputDialog::getText("Enter password for \"" + n.ssid + "\"", 8);
|
||||
wifi->connect(n, pass);
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::wrongPassword(QString ssid) {
|
||||
for (Network n : wifi->seen_networks) {
|
||||
if (n.ssid == ssid) {
|
||||
QString pass = InputDialog::getText("Wrong password for \"" + n.ssid +"\"", 8);
|
||||
wifi->connect(n, pass);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AdvancedNetworking functions
|
||||
|
||||
AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWidget(parent), wifi(wifi){
|
||||
|
||||
QVBoxLayout* vlayout = new QVBoxLayout;
|
||||
vlayout->setMargin(40);
|
||||
vlayout->setSpacing(20);
|
||||
|
||||
// Back button
|
||||
QPushButton* back = new QPushButton("Back");
|
||||
back->setFixedSize(500, 100);
|
||||
connect(back, &QPushButton::released, [=](){emit backPress();});
|
||||
vlayout->addWidget(back, 0, Qt::AlignLeft);
|
||||
|
||||
// Enable tethering layout
|
||||
ToggleControl *tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->tetheringEnabled());
|
||||
vlayout->addWidget(tetheringToggle);
|
||||
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
|
||||
vlayout->addWidget(horizontal_line(), 0);
|
||||
|
||||
// Change tethering password
|
||||
editPasswordButton = new ButtonControl("Tethering Password", "EDIT", "", [=](){
|
||||
QString pass = InputDialog::getText("Enter new tethering password", 8);
|
||||
if (pass.size()) {
|
||||
wifi->changeTetheringPassword(pass);
|
||||
}
|
||||
});
|
||||
vlayout->addWidget(editPasswordButton, 0);
|
||||
vlayout->addWidget(horizontal_line(), 0);
|
||||
|
||||
// IP address
|
||||
ipLabel = new LabelControl("IP Address", wifi->ipv4_address);
|
||||
vlayout->addWidget(ipLabel, 0);
|
||||
vlayout->addWidget(horizontal_line(), 0);
|
||||
|
||||
// SSH keys
|
||||
vlayout->addWidget(new SshToggle());
|
||||
vlayout->addWidget(horizontal_line(), 0);
|
||||
vlayout->addWidget(new SshControl());
|
||||
|
||||
vlayout->addStretch(1);
|
||||
setLayout(vlayout);
|
||||
}
|
||||
|
||||
void AdvancedNetworking::refresh(){
|
||||
ipLabel->setText(wifi->ipv4_address);
|
||||
update();
|
||||
}
|
||||
|
||||
void AdvancedNetworking::toggleTethering(bool enable) {
|
||||
if (enable) {
|
||||
wifi->enableTethering();
|
||||
} else {
|
||||
wifi->disableTethering();
|
||||
}
|
||||
editPasswordButton->setEnabled(!enable);
|
||||
}
|
||||
|
||||
|
||||
// WifiUI functions
|
||||
|
||||
WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) {
|
||||
vlayout = new QVBoxLayout;
|
||||
|
||||
// Scan on startup
|
||||
QLabel *scanning = new QLabel("Scanning for networks");
|
||||
scanning->setStyleSheet(R"(font-size: 65px;)");
|
||||
vlayout->addWidget(scanning, 0, Qt::AlignCenter);
|
||||
vlayout->setSpacing(25);
|
||||
|
||||
setLayout(vlayout);
|
||||
}
|
||||
|
||||
void WifiUI::refresh() {
|
||||
wifi->request_scan();
|
||||
wifi->refreshNetworks();
|
||||
clearLayout(vlayout);
|
||||
|
||||
connectButtons = new QButtonGroup(this); // TODO check if this is a leak
|
||||
QObject::connect(connectButtons, qOverload<QAbstractButton*>(&QButtonGroup::buttonClicked), this, &WifiUI::handleButton);
|
||||
|
||||
int i = 0;
|
||||
for (Network &network : wifi->seen_networks) {
|
||||
QHBoxLayout *hlayout = new QHBoxLayout;
|
||||
hlayout->addSpacing(50);
|
||||
|
||||
QLabel *ssid_label = new QLabel(QString::fromUtf8(network.ssid));
|
||||
ssid_label->setStyleSheet("font-size: 55px;");
|
||||
hlayout->addWidget(ssid_label, 1, Qt::AlignLeft);
|
||||
|
||||
// TODO: don't use images for this
|
||||
// strength indicator
|
||||
unsigned int strength_scale = network.strength / 17;
|
||||
QPixmap pix("../assets/images/network_" + QString::number(strength_scale) + ".png");
|
||||
QLabel *icon = new QLabel();
|
||||
icon->setPixmap(pix.scaledToWidth(100, Qt::SmoothTransformation));
|
||||
icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||
hlayout->addWidget(icon, 0, Qt::AlignRight);
|
||||
|
||||
// connect button
|
||||
QPushButton* btn = new QPushButton(network.security_type == SecurityType::UNSUPPORTED ? "Unsupported" : (network.connected == ConnectedType::CONNECTED ? "Connected" : (network.connected == ConnectedType::CONNECTING ? "Connecting" : "Connect")));
|
||||
btn->setDisabled(network.connected == ConnectedType::CONNECTED || network.connected == ConnectedType::CONNECTING || network.security_type == SecurityType::UNSUPPORTED);
|
||||
btn->setFixedWidth(350);
|
||||
hlayout->addWidget(btn, 0, Qt::AlignRight);
|
||||
|
||||
connectButtons->addButton(btn, i);
|
||||
|
||||
vlayout->addLayout(hlayout, 1);
|
||||
// Don't add the last horizontal line
|
||||
if (i+1 < wifi->seen_networks.size()) {
|
||||
vlayout->addWidget(horizontal_line(), 0);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
vlayout->addStretch(3);
|
||||
}
|
||||
|
||||
void WifiUI::handleButton(QAbstractButton* button) {
|
||||
QPushButton* btn = static_cast<QPushButton*>(button);
|
||||
Network n = wifi->seen_networks[connectButtons->id(btn)];
|
||||
emit connectToNetwork(n);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/wifiManager.h"
|
||||
#include "selfdrive/ui/qt/widgets/input.h"
|
||||
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
|
||||
#include "selfdrive/ui/qt/widgets/toggle.h"
|
||||
|
||||
class WifiUI : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WifiUI(QWidget *parent = 0, WifiManager* wifi = 0);
|
||||
|
||||
private:
|
||||
WifiManager *wifi = nullptr;
|
||||
QVBoxLayout *vlayout;
|
||||
|
||||
QButtonGroup *connectButtons;
|
||||
bool tetheringEnabled;
|
||||
|
||||
signals:
|
||||
void connectToNetwork(Network n);
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
void handleButton(QAbstractButton* m_button);
|
||||
};
|
||||
|
||||
class AdvancedNetworking : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AdvancedNetworking(QWidget* parent = 0, WifiManager* wifi = 0);
|
||||
|
||||
private:
|
||||
LabelControl* ipLabel;
|
||||
ButtonControl* editPasswordButton;
|
||||
WifiManager* wifi = nullptr;
|
||||
|
||||
signals:
|
||||
void backPress();
|
||||
|
||||
public slots:
|
||||
void toggleTethering(bool enable);
|
||||
void refresh();
|
||||
};
|
||||
|
||||
class Networking : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Networking(QWidget* parent = 0, bool show_advanced = true);
|
||||
|
||||
private:
|
||||
QStackedLayout* s = nullptr; // nm_warning, wifiScreen, advanced
|
||||
QWidget* wifiScreen = nullptr;
|
||||
AdvancedNetworking* an = nullptr;
|
||||
bool ui_setup_complete = false;
|
||||
bool show_advanced;
|
||||
|
||||
Network selectedNetwork;
|
||||
|
||||
WifiUI* wifiWidget;
|
||||
WifiManager* wifi = nullptr;
|
||||
void attemptInitialization();
|
||||
|
||||
private slots:
|
||||
void connectToNetwork(Network n);
|
||||
void refresh();
|
||||
void wrongPassword(QString ssid);
|
||||
};
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
#include "onboarding.h"
|
||||
|
||||
#include <QDesktopWidget>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QQmlContext>
|
||||
#include <QQuickWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/ui/qt/home.h"
|
||||
#include "selfdrive/ui/qt/widgets/input.h"
|
||||
|
||||
|
||||
void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) {
|
||||
QPoint touch = QPoint(e->x(), e->y()) - imageCorner;
|
||||
//qDebug() << touch.x() << ", " << touch.y();
|
||||
|
||||
// Check for restart
|
||||
if (currentIndex == (boundingBox.size() - 1) && 200 <= touch.x() && touch.x() <= 920 &&
|
||||
760 <= touch.y() && touch.y() <= 960) {
|
||||
currentIndex = 0;
|
||||
} else if (boundingBox[currentIndex][0] <= touch.x() && touch.x() <= boundingBox[currentIndex][1] &&
|
||||
boundingBox[currentIndex][2] <= touch.y() && touch.y() <= boundingBox[currentIndex][3]) {
|
||||
currentIndex += 1;
|
||||
}
|
||||
|
||||
if (currentIndex >= boundingBox.size()) {
|
||||
emit completedTraining();
|
||||
} else {
|
||||
image.load("../assets/training/step" + QString::number(currentIndex) + ".png");
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void TrainingGuide::showEvent(QShowEvent *event) {
|
||||
currentIndex = 0;
|
||||
image.load("../assets/training/step0.png");
|
||||
}
|
||||
|
||||
void TrainingGuide::paintEvent(QPaintEvent *event) {
|
||||
QPainter painter(this);
|
||||
|
||||
QRect bg(0, 0, painter.device()->width(), painter.device()->height());
|
||||
QBrush bgBrush("#000000");
|
||||
painter.fillRect(bg, bgBrush);
|
||||
|
||||
QRect rect(image.rect());
|
||||
rect.moveCenter(bg.center());
|
||||
painter.drawImage(rect.topLeft(), image);
|
||||
imageCorner = rect.topLeft();
|
||||
}
|
||||
|
||||
void TermsPage::showEvent(QShowEvent *event) {
|
||||
// late init, building QML widget takes 200ms
|
||||
if (layout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout;
|
||||
main_layout->setMargin(40);
|
||||
main_layout->setSpacing(40);
|
||||
|
||||
QQuickWidget *text = new QQuickWidget(this);
|
||||
text->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
text->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
text->setAttribute(Qt::WA_AlwaysStackOnTop);
|
||||
text->setClearColor(Qt::transparent);
|
||||
|
||||
QString text_view = util::read_file("../assets/offroad/tc.html").c_str();
|
||||
text->rootContext()->setContextProperty("text_view", text_view);
|
||||
text->rootContext()->setContextProperty("font_size", 55);
|
||||
|
||||
text->setSource(QUrl::fromLocalFile("qt/offroad/text_view.qml"));
|
||||
|
||||
main_layout->addWidget(text);
|
||||
|
||||
QObject *obj = (QObject*)text->rootObject();
|
||||
QObject::connect(obj, SIGNAL(qmlSignal()), SLOT(enableAccept()));
|
||||
|
||||
QHBoxLayout* buttons = new QHBoxLayout;
|
||||
main_layout->addLayout(buttons);
|
||||
|
||||
decline_btn = new QPushButton("Decline");
|
||||
buttons->addWidget(decline_btn);
|
||||
QObject::connect(decline_btn, &QPushButton::released, this, &TermsPage::declinedTerms);
|
||||
|
||||
buttons->addSpacing(50);
|
||||
|
||||
accept_btn = new QPushButton("Scroll to accept");
|
||||
accept_btn->setEnabled(false);
|
||||
buttons->addWidget(accept_btn);
|
||||
QObject::connect(accept_btn, &QPushButton::released, this, &TermsPage::acceptedTerms);
|
||||
|
||||
setLayout(main_layout);
|
||||
setStyleSheet(R"(
|
||||
QPushButton {
|
||||
padding: 50px;
|
||||
font-size: 50px;
|
||||
border-radius: 10px;
|
||||
background-color: #292929;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void TermsPage::enableAccept(){
|
||||
accept_btn->setText("Accept");
|
||||
accept_btn->setEnabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
void DeclinePage::showEvent(QShowEvent *event) {
|
||||
if (layout()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout;
|
||||
main_layout->setMargin(40);
|
||||
main_layout->setSpacing(40);
|
||||
|
||||
QLabel *text = new QLabel(this);
|
||||
text->setText("You must accept the Terms and Conditions in order to use openpilot!");
|
||||
text->setStyleSheet(R"(font-size: 50px;)");
|
||||
main_layout->addWidget(text, 0, Qt::AlignCenter);
|
||||
|
||||
QHBoxLayout* buttons = new QHBoxLayout;
|
||||
main_layout->addLayout(buttons);
|
||||
|
||||
back_btn = new QPushButton("Back");
|
||||
buttons->addWidget(back_btn);
|
||||
buttons->addSpacing(50);
|
||||
|
||||
QObject::connect(back_btn, &QPushButton::released, this, &DeclinePage::getBack);
|
||||
|
||||
uninstall_btn = new QPushButton("Decline, uninstall openpilot");
|
||||
uninstall_btn->setStyleSheet("background-color: #E22C2C;");
|
||||
buttons->addWidget(uninstall_btn);
|
||||
|
||||
QObject::connect(uninstall_btn, &QPushButton::released, [=](){
|
||||
if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) {
|
||||
Params().putBool("DoUninstall", true);
|
||||
}
|
||||
});
|
||||
|
||||
setLayout(main_layout);
|
||||
setStyleSheet(R"(
|
||||
QPushButton {
|
||||
padding: 50px;
|
||||
font-size: 50px;
|
||||
border-radius: 10px;
|
||||
background-color: #292929;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void OnboardingWindow::updateActiveScreen() {
|
||||
updateOnboardingStatus();
|
||||
|
||||
if (!accepted_terms) {
|
||||
setCurrentIndex(0);
|
||||
} else if (!training_done) {
|
||||
setCurrentIndex(1);
|
||||
} else {
|
||||
emit onboardingDone();
|
||||
}
|
||||
}
|
||||
|
||||
OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) {
|
||||
params = Params();
|
||||
current_terms_version = params.get("TermsVersion", false);
|
||||
current_training_version = params.get("TrainingVersion", false);
|
||||
|
||||
TermsPage* terms = new TermsPage(this);
|
||||
addWidget(terms);
|
||||
|
||||
connect(terms, &TermsPage::acceptedTerms, [=](){
|
||||
Params().put("HasAcceptedTerms", current_terms_version);
|
||||
updateActiveScreen();
|
||||
});
|
||||
|
||||
TrainingGuide* tr = new TrainingGuide(this);
|
||||
connect(tr, &TrainingGuide::completedTraining, [=](){
|
||||
Params().put("CompletedTrainingVersion", current_training_version);
|
||||
updateActiveScreen();
|
||||
});
|
||||
addWidget(tr);
|
||||
|
||||
DeclinePage* declinePage = new DeclinePage(this);
|
||||
addWidget(declinePage);
|
||||
|
||||
connect(terms, &TermsPage::declinedTerms, [=](){
|
||||
setCurrentIndex(2);
|
||||
});
|
||||
|
||||
connect(declinePage, &DeclinePage::getBack, [=](){
|
||||
updateActiveScreen();
|
||||
});
|
||||
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
QPushButton {
|
||||
padding: 50px;
|
||||
border-radius: 30px;
|
||||
background-color: #292929;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
color: #777777;
|
||||
background-color: #222222;
|
||||
}
|
||||
)");
|
||||
|
||||
updateActiveScreen();
|
||||
}
|
||||
|
||||
void OnboardingWindow::updateOnboardingStatus() {
|
||||
accepted_terms = params.get("HasAcceptedTerms", false).compare(current_terms_version) == 0;
|
||||
training_done = params.get("CompletedTrainingVersion", false).compare(current_training_version) == 0;
|
||||
}
|
||||
|
||||
bool OnboardingWindow::isOnboardingDone() {
|
||||
updateOnboardingStatus();
|
||||
return accepted_terms && training_done;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QImage>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QTextEdit>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
|
||||
class TrainingGuide : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrainingGuide(QWidget *parent = 0) : QFrame(parent) {};
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* e) override;
|
||||
|
||||
private:
|
||||
QImage image;
|
||||
QPoint imageCorner;
|
||||
int currentIndex = 0;
|
||||
|
||||
// Bounding boxes for the a given training guide step
|
||||
// (minx, maxx, miny, maxy)
|
||||
QVector<QVector<int>> boundingBox {
|
||||
{650, 1375, 700, 900},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1240, 1750, 480, 1080},
|
||||
{1570, 1780, 620, 750},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1630, 1740, 620, 780},
|
||||
{1200, 1920, 0, 1080},
|
||||
{1455, 1850, 400, 660},
|
||||
{1460, 1800, 195, 520},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1350, 1920, 65, 1080},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1570, 1900, 130, 1000},
|
||||
{1350, 1770, 500, 700},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1600, 1920, 0, 1080},
|
||||
{1000, 1800, 760, 954},
|
||||
};
|
||||
|
||||
signals:
|
||||
void completedTraining();
|
||||
};
|
||||
|
||||
|
||||
class TermsPage : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TermsPage(QWidget *parent = 0) : QFrame(parent) {};
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
QPushButton *accept_btn;
|
||||
QPushButton *decline_btn;
|
||||
|
||||
public slots:
|
||||
void enableAccept();
|
||||
|
||||
signals:
|
||||
void acceptedTerms();
|
||||
void declinedTerms();
|
||||
};
|
||||
|
||||
class DeclinePage : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeclinePage(QWidget *parent = 0) : QFrame(parent) {};
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
QPushButton *back_btn;
|
||||
QPushButton *uninstall_btn;
|
||||
|
||||
signals:
|
||||
void getBack();
|
||||
};
|
||||
|
||||
class OnboardingWindow : public QStackedWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OnboardingWindow(QWidget *parent = 0);
|
||||
bool isOnboardingDone();
|
||||
|
||||
private:
|
||||
Params params;
|
||||
std::string current_terms_version;
|
||||
std::string current_training_version;
|
||||
bool accepted_terms = false;
|
||||
bool training_done = false;
|
||||
void updateOnboardingStatus();
|
||||
|
||||
signals:
|
||||
void onboardingDone();
|
||||
void resetTrainingGuide();
|
||||
|
||||
public slots:
|
||||
void updateActiveScreen();
|
||||
};
|
||||
@@ -1,425 +0,0 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#ifndef QCOM
|
||||
#include "selfdrive/ui/qt/offroad/networking.h"
|
||||
#endif
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
#include "selfdrive/ui/qt/widgets/input.h"
|
||||
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
#include "selfdrive/ui/qt/widgets/ssh_keys.h"
|
||||
#include "selfdrive/ui/qt/widgets/toggle.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
TogglesPanel::TogglesPanel(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *toggles_list = new QVBoxLayout();
|
||||
|
||||
QList<ParamControl*> toggles;
|
||||
|
||||
toggles.append(new ParamControl("OpenpilotEnabledToggle",
|
||||
"Enable openpilot",
|
||||
"Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.",
|
||||
"../assets/offroad/icon_openpilot.png",
|
||||
this));
|
||||
toggles.append(new ParamControl("IsLdwEnabled",
|
||||
"Enable Lane Departure Warnings",
|
||||
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31mph (50kph).",
|
||||
"../assets/offroad/icon_warning.png",
|
||||
this));
|
||||
toggles.append(new ParamControl("IsRHD",
|
||||
"Enable Right-Hand Drive",
|
||||
"Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.",
|
||||
"../assets/offroad/icon_openpilot_mirrored.png",
|
||||
this));
|
||||
toggles.append(new ParamControl("IsMetric",
|
||||
"Use Metric System",
|
||||
"Display speed in km/h instead of mp/h.",
|
||||
"../assets/offroad/icon_metric.png",
|
||||
this));
|
||||
toggles.append(new ParamControl("CommunityFeaturesToggle",
|
||||
"Enable Community Features",
|
||||
"Use features from the open source community that are not maintained or supported by comma.ai and have not been confirmed to meet the standard safety model. These features include community supported cars and community supported hardware. Be extra cautious when using these features",
|
||||
"../assets/offroad/icon_shell.png",
|
||||
this));
|
||||
|
||||
toggles.append(new ParamControl("UploadRaw",
|
||||
"Upload Raw Logs",
|
||||
"Upload full logs and full resolution video by default while on WiFi. If not enabled, individual logs can be marked for upload at my.comma.ai/useradmin.",
|
||||
"../assets/offroad/icon_network.png",
|
||||
this));
|
||||
|
||||
ParamControl *record_toggle = new ParamControl("RecordFront",
|
||||
"Record and Upload Driver Camera",
|
||||
"Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
|
||||
"../assets/offroad/icon_monitoring.png",
|
||||
this);
|
||||
toggles.append(record_toggle);
|
||||
toggles.append(new ParamControl("EndToEndToggle",
|
||||
"\U0001f96c Disable use of lanelines (Alpha) \U0001f96c",
|
||||
"In this mode openpilot will ignore lanelines and just drive how it thinks a human would.",
|
||||
"../assets/offroad/icon_road.png",
|
||||
this));
|
||||
|
||||
if (Hardware::TICI()) {
|
||||
toggles.append(new ParamControl("EnableWideCamera",
|
||||
"Enable use of Wide Angle Camera",
|
||||
"Use wide angle camera for driving and ui.",
|
||||
"../assets/offroad/icon_openpilot.png",
|
||||
this));
|
||||
QObject::connect(toggles.back(), &ToggleControl::toggleFlipped, [=](bool state) {
|
||||
Params().remove("CalibrationParams");
|
||||
});
|
||||
|
||||
toggles.append(new ParamControl("EnableLteOnroad",
|
||||
"Enable LTE while onroad",
|
||||
"",
|
||||
"../assets/offroad/icon_network.png",
|
||||
this));
|
||||
}
|
||||
|
||||
bool record_lock = Params().getBool("RecordFrontLock");
|
||||
record_toggle->setEnabled(!record_lock);
|
||||
|
||||
for(ParamControl *toggle : toggles){
|
||||
if(toggles_list->count() != 0){
|
||||
toggles_list->addWidget(horizontal_line());
|
||||
}
|
||||
toggles_list->addWidget(toggle);
|
||||
}
|
||||
|
||||
setLayout(toggles_list);
|
||||
}
|
||||
|
||||
DevicePanel::DevicePanel(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout *device_layout = new QVBoxLayout;
|
||||
Params params = Params();
|
||||
|
||||
QString dongle = QString::fromStdString(params.get("DongleId", false));
|
||||
device_layout->addWidget(new LabelControl("Dongle ID", dongle));
|
||||
device_layout->addWidget(horizontal_line());
|
||||
|
||||
QString serial = QString::fromStdString(params.get("HardwareSerial", false));
|
||||
device_layout->addWidget(new LabelControl("Serial", serial));
|
||||
|
||||
// offroad-only buttons
|
||||
QList<ButtonControl*> offroad_btns;
|
||||
|
||||
offroad_btns.append(new ButtonControl("Driver Camera", "PREVIEW",
|
||||
"Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)",
|
||||
[=]() { emit showDriverView(); }, "", this));
|
||||
|
||||
QString resetCalibDesc = "openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required.";
|
||||
ButtonControl *resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", resetCalibDesc, [=]() {
|
||||
if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) {
|
||||
Params().remove("CalibrationParams");
|
||||
}
|
||||
}, "", this);
|
||||
connect(resetCalibBtn, &ButtonControl::showDescription, [=]() {
|
||||
QString desc = resetCalibDesc;
|
||||
std::string calib_bytes = Params().get("CalibrationParams");
|
||||
if (!calib_bytes.empty()) {
|
||||
try {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size()));
|
||||
auto calib = cmsg.getRoot<cereal::Event>().getLiveCalibration();
|
||||
if (calib.getCalStatus() != 0) {
|
||||
double pitch = calib.getRpyCalib()[1] * (180 / M_PI);
|
||||
double yaw = calib.getRpyCalib()[2] * (180 / M_PI);
|
||||
desc += QString(" Your device is pointed %1° %2 and %3° %4.")
|
||||
.arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down",
|
||||
QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left");
|
||||
}
|
||||
} catch (kj::Exception) {
|
||||
qInfo() << "invalid CalibrationParams";
|
||||
}
|
||||
}
|
||||
resetCalibBtn->setDescription(desc);
|
||||
});
|
||||
offroad_btns.append(resetCalibBtn);
|
||||
|
||||
offroad_btns.append(new ButtonControl("Review Training Guide", "REVIEW",
|
||||
"Review the rules, features, and limitations of openpilot", [=]() {
|
||||
if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) {
|
||||
Params().remove("CompletedTrainingVersion");
|
||||
emit reviewTrainingGuide();
|
||||
}
|
||||
}, "", this));
|
||||
|
||||
QString brand = params.getBool("Passive") ? "dashcam" : "openpilot";
|
||||
offroad_btns.append(new ButtonControl("Uninstall " + brand, "UNINSTALL", "", [=]() {
|
||||
if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) {
|
||||
Params().putBool("DoUninstall", true);
|
||||
}
|
||||
}, "", this));
|
||||
|
||||
for(auto &btn : offroad_btns){
|
||||
device_layout->addWidget(horizontal_line());
|
||||
QObject::connect(parent, SIGNAL(offroadTransition(bool)), btn, SLOT(setEnabled(bool)));
|
||||
device_layout->addWidget(btn);
|
||||
}
|
||||
|
||||
// power buttons
|
||||
QHBoxLayout *power_layout = new QHBoxLayout();
|
||||
power_layout->setSpacing(30);
|
||||
|
||||
QPushButton *reboot_btn = new QPushButton("Reboot");
|
||||
power_layout->addWidget(reboot_btn);
|
||||
QObject::connect(reboot_btn, &QPushButton::released, [=]() {
|
||||
if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) {
|
||||
Hardware::reboot();
|
||||
}
|
||||
});
|
||||
|
||||
QPushButton *poweroff_btn = new QPushButton("Power Off");
|
||||
poweroff_btn->setStyleSheet("background-color: #E22C2C;");
|
||||
power_layout->addWidget(poweroff_btn);
|
||||
QObject::connect(poweroff_btn, &QPushButton::released, [=]() {
|
||||
if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) {
|
||||
Hardware::poweroff();
|
||||
}
|
||||
});
|
||||
|
||||
device_layout->addLayout(power_layout);
|
||||
|
||||
setLayout(device_layout);
|
||||
setStyleSheet(R"(
|
||||
QPushButton {
|
||||
padding: 0;
|
||||
height: 120px;
|
||||
border-radius: 15px;
|
||||
background-color: #393939;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
SoftwarePanel::SoftwarePanel(QWidget* parent) : QFrame(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
setLayout(main_layout);
|
||||
setStyleSheet(R"(QLabel {font-size: 50px;})");
|
||||
|
||||
fs_watch = new QFileSystemWatcher(this);
|
||||
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
|
||||
int update_failed_count = Params().get<int>("UpdateFailedCount").value_or(0);
|
||||
if (path.contains("UpdateFailedCount") && update_failed_count > 0) {
|
||||
lastUpdateTimeLbl->setText("failed to fetch update");
|
||||
updateButton->setText("CHECK");
|
||||
updateButton->setEnabled(true);
|
||||
} else if (path.contains("LastUpdateTime")) {
|
||||
updateLabels();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SoftwarePanel::showEvent(QShowEvent *event) {
|
||||
updateLabels();
|
||||
}
|
||||
|
||||
void SoftwarePanel::updateLabels() {
|
||||
Params params = Params();
|
||||
std::string brand = params.getBool("Passive") ? "dashcam" : "openpilot";
|
||||
QList<QPair<QString, std::string>> dev_params = {
|
||||
{"Git Branch", params.get("GitBranch")},
|
||||
{"Git Commit", params.get("GitCommit").substr(0, 10)},
|
||||
{"Panda Firmware", params.get("PandaFirmwareHex")},
|
||||
{"OS Version", Hardware::get_os_version()},
|
||||
};
|
||||
|
||||
QString version = QString::fromStdString(brand + " v" + params.get("Version").substr(0, 14)).trimmed();
|
||||
QString lastUpdateTime = "";
|
||||
|
||||
std::string last_update_param = params.get("LastUpdateTime");
|
||||
if (!last_update_param.empty()){
|
||||
QDateTime lastUpdateDate = QDateTime::fromString(QString::fromStdString(last_update_param + "Z"), Qt::ISODate);
|
||||
lastUpdateTime = timeAgo(lastUpdateDate);
|
||||
}
|
||||
|
||||
if (labels.size() < dev_params.size()) {
|
||||
versionLbl = new LabelControl("Version", version, QString::fromStdString(params.get("ReleaseNotes")).trimmed());
|
||||
layout()->addWidget(versionLbl);
|
||||
layout()->addWidget(horizontal_line());
|
||||
|
||||
lastUpdateTimeLbl = new LabelControl("Last Update Check", lastUpdateTime, "The last time openpilot successfully checked for an update. The updater only runs while the car is off.");
|
||||
layout()->addWidget(lastUpdateTimeLbl);
|
||||
layout()->addWidget(horizontal_line());
|
||||
|
||||
updateButton = new ButtonControl("Check for Update", "CHECK", "", [=]() {
|
||||
Params params = Params();
|
||||
if (params.getBool("IsOffroad")) {
|
||||
fs_watch->addPath(QString::fromStdString(params.getParamsPath()) + "/d/LastUpdateTime");
|
||||
fs_watch->addPath(QString::fromStdString(params.getParamsPath()) + "/d/UpdateFailedCount");
|
||||
updateButton->setText("CHECKING");
|
||||
updateButton->setEnabled(false);
|
||||
}
|
||||
std::system("pkill -1 -f selfdrive.updated");
|
||||
}, "", this);
|
||||
layout()->addWidget(updateButton);
|
||||
layout()->addWidget(horizontal_line());
|
||||
} else {
|
||||
versionLbl->setText(version);
|
||||
lastUpdateTimeLbl->setText(lastUpdateTime);
|
||||
updateButton->setText("CHECK");
|
||||
updateButton->setEnabled(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < dev_params.size(); i++) {
|
||||
const auto &[name, value] = dev_params[i];
|
||||
QString val = QString::fromStdString(value).trimmed();
|
||||
if (labels.size() > i) {
|
||||
labels[i]->setText(val);
|
||||
} else {
|
||||
labels.push_back(new LabelControl(name, val));
|
||||
layout()->addWidget(labels[i]);
|
||||
if (i < (dev_params.size() - 1)) {
|
||||
layout()->addWidget(horizontal_line());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QWidget * network_panel(QWidget * parent) {
|
||||
#ifdef QCOM
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->setSpacing(30);
|
||||
|
||||
// wifi + tethering buttons
|
||||
layout->addWidget(new ButtonControl("WiFi Settings", "OPEN", "",
|
||||
[=]() { HardwareEon::launch_wifi(); }));
|
||||
layout->addWidget(horizontal_line());
|
||||
|
||||
layout->addWidget(new ButtonControl("Tethering Settings", "OPEN", "",
|
||||
[=]() { HardwareEon::launch_tethering(); }));
|
||||
layout->addWidget(horizontal_line());
|
||||
|
||||
// SSH key management
|
||||
layout->addWidget(new SshToggle());
|
||||
layout->addWidget(horizontal_line());
|
||||
layout->addWidget(new SshControl());
|
||||
|
||||
layout->addStretch(1);
|
||||
|
||||
QWidget *w = new QWidget;
|
||||
w->setLayout(layout);
|
||||
#else
|
||||
Networking *w = new Networking(parent);
|
||||
#endif
|
||||
return w;
|
||||
}
|
||||
|
||||
void SettingsWindow::showEvent(QShowEvent *event) {
|
||||
if (layout()) {
|
||||
panel_widget->setCurrentIndex(0);
|
||||
nav_btns->buttons()[0]->setChecked(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// setup two main layouts
|
||||
QVBoxLayout *sidebar_layout = new QVBoxLayout();
|
||||
sidebar_layout->setMargin(0);
|
||||
panel_widget = new QStackedWidget();
|
||||
panel_widget->setStyleSheet(R"(
|
||||
border-radius: 30px;
|
||||
background-color: #292929;
|
||||
)");
|
||||
|
||||
// close button
|
||||
QPushButton *close_btn = new QPushButton("X");
|
||||
close_btn->setStyleSheet(R"(
|
||||
font-size: 90px;
|
||||
font-weight: bold;
|
||||
border 1px grey solid;
|
||||
border-radius: 100px;
|
||||
background-color: #292929;
|
||||
)");
|
||||
close_btn->setFixedSize(200, 200);
|
||||
sidebar_layout->addSpacing(45);
|
||||
sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter);
|
||||
QObject::connect(close_btn, &QPushButton::released, this, &SettingsWindow::closeSettings);
|
||||
|
||||
// setup panels
|
||||
DevicePanel *device = new DevicePanel(this);
|
||||
QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide);
|
||||
QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
|
||||
|
||||
QPair<QString, QWidget *> panels[] = {
|
||||
{"Device", device},
|
||||
{"Network", network_panel(this)},
|
||||
{"Toggles", new TogglesPanel(this)},
|
||||
{"Software", new SoftwarePanel()},
|
||||
};
|
||||
|
||||
sidebar_layout->addSpacing(45);
|
||||
nav_btns = new QButtonGroup();
|
||||
for (auto &[name, panel] : panels) {
|
||||
QPushButton *btn = new QPushButton(name);
|
||||
btn->setCheckable(true);
|
||||
btn->setChecked(nav_btns->buttons().size() == 0);
|
||||
btn->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
color: grey;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 65px;
|
||||
font-weight: 500;
|
||||
padding-top: 35px;
|
||||
padding-bottom: 35px;
|
||||
}
|
||||
QPushButton:checked {
|
||||
color: white;
|
||||
}
|
||||
)");
|
||||
|
||||
nav_btns->addButton(btn);
|
||||
sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
|
||||
|
||||
panel->setContentsMargins(50, 25, 50, 25);
|
||||
|
||||
ScrollView *panel_frame = new ScrollView(panel, this);
|
||||
panel_widget->addWidget(panel_frame);
|
||||
|
||||
QObject::connect(btn, &QPushButton::released, [=, w = panel_frame]() {
|
||||
panel_widget->setCurrentWidget(w);
|
||||
});
|
||||
}
|
||||
sidebar_layout->setContentsMargins(50, 50, 100, 50);
|
||||
|
||||
// main settings layout, sidebar + main panel
|
||||
QHBoxLayout *settings_layout = new QHBoxLayout();
|
||||
|
||||
sidebar_widget = new QWidget;
|
||||
sidebar_widget->setLayout(sidebar_layout);
|
||||
sidebar_widget->setFixedWidth(500);
|
||||
settings_layout->addWidget(sidebar_widget);
|
||||
settings_layout->addWidget(panel_widget);
|
||||
|
||||
setLayout(settings_layout);
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
color: white;
|
||||
font-size: 50px;
|
||||
}
|
||||
SettingsWindow {
|
||||
background-color: black;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void SettingsWindow::hideEvent(QHideEvent *event){
|
||||
#ifdef QCOM
|
||||
HardwareEon::close_activities();
|
||||
#endif
|
||||
|
||||
// TODO: this should be handled by the Dialog classes
|
||||
QList<QWidget*> children = findChildren<QWidget *>();
|
||||
for(auto &w : children){
|
||||
if(w->metaObject()->superClass()->className() == QString("QDialog")){
|
||||
w->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QStackedWidget>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
|
||||
// ********** settings window + top-level panels **********
|
||||
|
||||
class DevicePanel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DevicePanel(QWidget* parent = nullptr);
|
||||
signals:
|
||||
void reviewTrainingGuide();
|
||||
void showDriverView();
|
||||
};
|
||||
|
||||
class TogglesPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TogglesPanel(QWidget *parent = nullptr);
|
||||
};
|
||||
|
||||
class SoftwarePanel : public QFrame {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SoftwarePanel(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
QList<LabelControl *> labels;
|
||||
LabelControl *versionLbl;
|
||||
LabelControl *lastUpdateTimeLbl;
|
||||
ButtonControl *updateButton;
|
||||
void updateLabels();
|
||||
|
||||
QFileSystemWatcher *fs_watch;
|
||||
};
|
||||
|
||||
class SettingsWindow : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsWindow(QWidget *parent = 0) : QFrame(parent) {};
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
signals:
|
||||
void closeSettings();
|
||||
void offroadTransition(bool offroad);
|
||||
void reviewTrainingGuide();
|
||||
void showDriverView();
|
||||
|
||||
private:
|
||||
QPushButton *sidebar_alert_widget;
|
||||
QWidget *sidebar_widget;
|
||||
QButtonGroup *nav_btns;
|
||||
QStackedWidget *panel_widget;
|
||||
};
|
||||
@@ -1,522 +0,0 @@
|
||||
#include "wifiManager.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/common/swaglog.h"
|
||||
|
||||
/**
|
||||
* We are using a NetworkManager DBUS API : https://developer.gnome.org/NetworkManager/1.26/spec.html
|
||||
* */
|
||||
|
||||
// https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NM80211ApFlags
|
||||
const int NM_802_11_AP_FLAGS_PRIVACY = 0x00000001;
|
||||
|
||||
// https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NM80211ApSecurityFlags
|
||||
const int NM_802_11_AP_SEC_PAIR_WEP40 = 0x00000001;
|
||||
const int NM_802_11_AP_SEC_PAIR_WEP104 = 0x00000002;
|
||||
const int NM_802_11_AP_SEC_GROUP_WEP40 = 0x00000010;
|
||||
const int NM_802_11_AP_SEC_GROUP_WEP104 = 0x00000020;
|
||||
const int NM_802_11_AP_SEC_KEY_MGMT_PSK = 0x00000100;
|
||||
const int NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200;
|
||||
|
||||
const QString nm_path = "/org/freedesktop/NetworkManager";
|
||||
const QString nm_settings_path = "/org/freedesktop/NetworkManager/Settings";
|
||||
|
||||
const QString nm_iface = "org.freedesktop.NetworkManager";
|
||||
const QString props_iface = "org.freedesktop.DBus.Properties";
|
||||
const QString nm_settings_iface = "org.freedesktop.NetworkManager.Settings";
|
||||
const QString nm_settings_conn_iface = "org.freedesktop.NetworkManager.Settings.Connection";
|
||||
const QString device_iface = "org.freedesktop.NetworkManager.Device";
|
||||
const QString wireless_device_iface = "org.freedesktop.NetworkManager.Device.Wireless";
|
||||
const QString ap_iface = "org.freedesktop.NetworkManager.AccessPoint";
|
||||
const QString connection_iface = "org.freedesktop.NetworkManager.Connection.Active";
|
||||
const QString ipv4config_iface = "org.freedesktop.NetworkManager.IP4Config";
|
||||
|
||||
const QString nm_service = "org.freedesktop.NetworkManager";
|
||||
|
||||
const int state_connected = 100;
|
||||
const int state_need_auth = 60;
|
||||
const int reason_wrong_password = 8;
|
||||
const int dbus_timeout = 100;
|
||||
|
||||
template <typename T>
|
||||
T get_response(QDBusMessage response) {
|
||||
QVariant first = response.arguments().at(0);
|
||||
QDBusVariant dbvFirst = first.value<QDBusVariant>();
|
||||
QVariant vFirst = dbvFirst.variant();
|
||||
if (vFirst.canConvert<T>()) {
|
||||
return vFirst.value<T>();
|
||||
} else {
|
||||
LOGE("Variant unpacking failure");
|
||||
return T();
|
||||
}
|
||||
}
|
||||
|
||||
bool compare_by_strength(const Network &a, const Network &b) {
|
||||
if (a.connected == ConnectedType::CONNECTED) return true;
|
||||
if (b.connected == ConnectedType::CONNECTED) return false;
|
||||
if (a.connected == ConnectedType::CONNECTING) return true;
|
||||
if (b.connected == ConnectedType::CONNECTING) return false;
|
||||
return a.strength > b.strength;
|
||||
}
|
||||
|
||||
WifiManager::WifiManager(QWidget* parent) {
|
||||
qDBusRegisterMetaType<Connection>();
|
||||
qDBusRegisterMetaType<IpConfig>();
|
||||
connecting_to_network = "";
|
||||
adapter = get_adapter();
|
||||
|
||||
bool has_adapter = adapter != "";
|
||||
if (!has_adapter){
|
||||
throw std::runtime_error("Error connecting to NetworkManager");
|
||||
}
|
||||
|
||||
QDBusInterface nm(nm_service, adapter, device_iface, bus);
|
||||
bus.connect(nm_service, adapter, device_iface, "StateChanged", this, SLOT(change(unsigned int, unsigned int, unsigned int)));
|
||||
|
||||
QDBusInterface device_props(nm_service, adapter, props_iface, bus);
|
||||
device_props.setTimeout(dbus_timeout);
|
||||
QDBusMessage response = device_props.call("Get", device_iface, "State");
|
||||
raw_adapter_state = get_response<uint>(response);
|
||||
change(raw_adapter_state, 0, 0);
|
||||
|
||||
// Set tethering ssid as "weedle" + first 4 characters of a dongle id
|
||||
tethering_ssid = "weedle";
|
||||
std::string bytes = Params().get("DongleId");
|
||||
if (bytes.length() >= 4) {
|
||||
tethering_ssid+="-"+QString::fromStdString(bytes.substr(0,4));
|
||||
}
|
||||
|
||||
// Create dbus interface for tethering button. This populates the introspection cache,
|
||||
// making sure all future creations are non-blocking
|
||||
// https://bugreports.qt.io/browse/QTBUG-14485
|
||||
QDBusInterface(nm_service, nm_settings_path, nm_settings_iface, bus);
|
||||
}
|
||||
|
||||
void WifiManager::refreshNetworks() {
|
||||
seen_networks.clear();
|
||||
seen_ssids.clear();
|
||||
ipv4_address = get_ipv4_address();
|
||||
for (Network &network : get_networks()) {
|
||||
if (seen_ssids.count(network.ssid)) {
|
||||
continue;
|
||||
}
|
||||
seen_ssids.push_back(network.ssid);
|
||||
seen_networks.push_back(network);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString WifiManager::get_ipv4_address(){
|
||||
if (raw_adapter_state != state_connected){
|
||||
return "";
|
||||
}
|
||||
QVector<QDBusObjectPath> conns = get_active_connections();
|
||||
for (auto &p : conns){
|
||||
QString active_connection = p.path();
|
||||
QDBusInterface nm(nm_service, active_connection, props_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusObjectPath pth = get_response<QDBusObjectPath>(nm.call("Get", connection_iface, "Ip4Config"));
|
||||
QString ip4config = pth.path();
|
||||
|
||||
QString type = get_response<QString>(nm.call("Get", connection_iface, "Type"));
|
||||
|
||||
if (type == "802-11-wireless") {
|
||||
QDBusInterface nm2(nm_service, ip4config, props_iface, bus);
|
||||
nm2.setTimeout(dbus_timeout);
|
||||
|
||||
const QDBusArgument &arr = get_response<QDBusArgument>(nm2.call("Get", ipv4config_iface, "AddressData"));
|
||||
QMap<QString, QVariant> pth2;
|
||||
arr.beginArray();
|
||||
while (!arr.atEnd()){
|
||||
arr >> pth2;
|
||||
QString ipv4 = pth2.value("address").value<QString>();
|
||||
arr.endArray();
|
||||
return ipv4;
|
||||
}
|
||||
arr.endArray();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QList<Network> WifiManager::get_networks() {
|
||||
QList<Network> r;
|
||||
QDBusInterface nm(nm_service, adapter, wireless_device_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm.call("GetAllAccessPoints");
|
||||
QVariant first = response.arguments().at(0);
|
||||
|
||||
QString active_ap = get_active_ap();
|
||||
const QDBusArgument &args = first.value<QDBusArgument>();
|
||||
args.beginArray();
|
||||
while (!args.atEnd()) {
|
||||
QDBusObjectPath path;
|
||||
args >> path;
|
||||
|
||||
QByteArray ssid = get_property(path.path(), "Ssid");
|
||||
unsigned int strength = get_ap_strength(path.path());
|
||||
SecurityType security = getSecurityType(path.path());
|
||||
ConnectedType ctype;
|
||||
if (path.path() != active_ap) {
|
||||
ctype = ConnectedType::DISCONNECTED;
|
||||
} else {
|
||||
if (ssid == connecting_to_network) {
|
||||
ctype = ConnectedType::CONNECTING;
|
||||
} else {
|
||||
ctype = ConnectedType::CONNECTED;
|
||||
}
|
||||
}
|
||||
Network network = {path.path(), ssid, strength, ctype, security};
|
||||
|
||||
if (ssid.length()) {
|
||||
r.push_back(network);
|
||||
}
|
||||
}
|
||||
args.endArray();
|
||||
|
||||
std::sort(r.begin(), r.end(), compare_by_strength);
|
||||
return r;
|
||||
}
|
||||
|
||||
SecurityType WifiManager::getSecurityType(QString path) {
|
||||
int sflag = get_property(path, "Flags").toInt();
|
||||
int wpaflag = get_property(path, "WpaFlags").toInt();
|
||||
int rsnflag = get_property(path, "RsnFlags").toInt();
|
||||
int wpa_props = wpaflag | rsnflag;
|
||||
|
||||
// obtained by looking at flags of networks in the office as reported by an Android phone
|
||||
const int supports_wpa = NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104 | NM_802_11_AP_SEC_GROUP_WEP40 | NM_802_11_AP_SEC_GROUP_WEP104 | NM_802_11_AP_SEC_KEY_MGMT_PSK;
|
||||
|
||||
if (sflag == 0) {
|
||||
return SecurityType::OPEN;
|
||||
} else if ((sflag & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_props & supports_wpa) && !(wpa_props & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
|
||||
return SecurityType::WPA;
|
||||
} else {
|
||||
return SecurityType::UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
void WifiManager::connect(Network n) {
|
||||
return connect(n, "", "");
|
||||
}
|
||||
|
||||
void WifiManager::connect(Network n, QString password) {
|
||||
return connect(n, "", password);
|
||||
}
|
||||
|
||||
void WifiManager::connect(Network n, QString username, QString password) {
|
||||
connecting_to_network = n.ssid;
|
||||
// disconnect();
|
||||
clear_connections(n.ssid); //Clear all connections that may already exist to the network we are connecting
|
||||
connect(n.ssid, username, password, n.security_type);
|
||||
}
|
||||
|
||||
void WifiManager::connect(QByteArray ssid, QString username, QString password, SecurityType security_type) {
|
||||
Connection connection;
|
||||
connection["connection"]["type"] = "802-11-wireless";
|
||||
connection["connection"]["uuid"] = QUuid::createUuid().toString().remove('{').remove('}');
|
||||
connection["connection"]["id"] = "openpilot connection "+QString::fromStdString(ssid.toStdString());
|
||||
connection["connection"]["autoconnect-retries"] = 0;
|
||||
|
||||
connection["802-11-wireless"]["ssid"] = ssid;
|
||||
connection["802-11-wireless"]["mode"] = "infrastructure";
|
||||
|
||||
if (security_type == SecurityType::WPA) {
|
||||
connection["802-11-wireless-security"]["key-mgmt"] = "wpa-psk";
|
||||
connection["802-11-wireless-security"]["auth-alg"] = "open";
|
||||
connection["802-11-wireless-security"]["psk"] = password;
|
||||
}
|
||||
|
||||
connection["ipv4"]["method"] = "auto";
|
||||
connection["ipv6"]["method"] = "ignore";
|
||||
|
||||
QDBusInterface nm_settings(nm_service, nm_settings_path, nm_settings_iface, bus);
|
||||
nm_settings.setTimeout(dbus_timeout);
|
||||
|
||||
nm_settings.call("AddConnection", QVariant::fromValue(connection));
|
||||
activate_wifi_connection(QString(ssid));
|
||||
}
|
||||
|
||||
void WifiManager::deactivate_connections(QString ssid) {
|
||||
for (QDBusObjectPath active_connection_raw : get_active_connections()) {
|
||||
QString active_connection = active_connection_raw.path();
|
||||
QDBusInterface nm(nm_service, active_connection, props_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusObjectPath pth = get_response<QDBusObjectPath>(nm.call("Get", connection_iface, "SpecificObject"));
|
||||
if (pth.path() != "" && pth.path() != "/") {
|
||||
QString Ssid = get_property(pth.path(), "Ssid");
|
||||
if (Ssid == ssid) {
|
||||
QDBusInterface nm2(nm_service, nm_path, nm_iface, bus);
|
||||
nm2.setTimeout(dbus_timeout);
|
||||
nm2.call("DeactivateConnection", QVariant::fromValue(active_connection_raw));// TODO change to disconnect
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QDBusObjectPath> WifiManager::get_active_connections() {
|
||||
QDBusInterface nm(nm_service, nm_path, props_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm.call("Get", nm_iface, "ActiveConnections");
|
||||
const QDBusArgument &arr = get_response<QDBusArgument>(response);
|
||||
QVector<QDBusObjectPath> conns;
|
||||
|
||||
QDBusObjectPath path;
|
||||
arr.beginArray();
|
||||
while (!arr.atEnd()) {
|
||||
arr >> path;
|
||||
conns.push_back(path);
|
||||
}
|
||||
arr.endArray();
|
||||
return conns;
|
||||
}
|
||||
|
||||
void WifiManager::clear_connections(QString ssid) {
|
||||
for(QDBusObjectPath path : list_connections()){
|
||||
QDBusInterface nm2(nm_service, path.path(), nm_settings_conn_iface, bus);
|
||||
nm2.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm2.call("GetSettings");
|
||||
|
||||
const QDBusArgument &dbusArg = response.arguments().at(0).value<QDBusArgument>();
|
||||
|
||||
QMap<QString, QMap<QString,QVariant>> map;
|
||||
dbusArg >> map;
|
||||
for (auto &inner : map) {
|
||||
for (auto &val : inner) {
|
||||
QString key = inner.key(val);
|
||||
if (key == "ssid") {
|
||||
if (val == ssid) {
|
||||
nm2.call("Delete");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WifiManager::request_scan() {
|
||||
QDBusInterface nm(nm_service, adapter, wireless_device_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
nm.call("RequestScan", QVariantMap());
|
||||
}
|
||||
|
||||
uint WifiManager::get_wifi_device_state() {
|
||||
QDBusInterface device_props(nm_service, adapter, props_iface, bus);
|
||||
device_props.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = device_props.call("Get", device_iface, "State");
|
||||
uint resp = get_response<uint>(response);
|
||||
return resp;
|
||||
}
|
||||
|
||||
QString WifiManager::get_active_ap() {
|
||||
QDBusInterface device_props(nm_service, adapter, props_iface, bus);
|
||||
device_props.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = device_props.call("Get", wireless_device_iface, "ActiveAccessPoint");
|
||||
QDBusObjectPath r = get_response<QDBusObjectPath>(response);
|
||||
return r.path();
|
||||
}
|
||||
|
||||
QByteArray WifiManager::get_property(QString network_path ,QString property) {
|
||||
QDBusInterface device_props(nm_service, network_path, props_iface, bus);
|
||||
device_props.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = device_props.call("Get", ap_iface, property);
|
||||
return get_response<QByteArray>(response);
|
||||
}
|
||||
|
||||
unsigned int WifiManager::get_ap_strength(QString network_path) {
|
||||
QDBusInterface device_props(nm_service, network_path, props_iface, bus);
|
||||
device_props.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = device_props.call("Get", ap_iface, "Strength");
|
||||
return get_response<unsigned int>(response);
|
||||
}
|
||||
|
||||
QString WifiManager::get_adapter() {
|
||||
QDBusInterface nm(nm_service, nm_path, nm_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm.call("GetDevices");
|
||||
QVariant first = response.arguments().at(0);
|
||||
|
||||
QString adapter_path = "";
|
||||
|
||||
const QDBusArgument &args = first.value<QDBusArgument>();
|
||||
args.beginArray();
|
||||
while (!args.atEnd()) {
|
||||
QDBusObjectPath path;
|
||||
args >> path;
|
||||
|
||||
// Get device type
|
||||
QDBusInterface device_props(nm_service, path.path(), props_iface, bus);
|
||||
device_props.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = device_props.call("Get", device_iface, "DeviceType");
|
||||
uint device_type = get_response<uint>(response);
|
||||
|
||||
if (device_type == 2) { // Wireless
|
||||
adapter_path = path.path();
|
||||
break;
|
||||
}
|
||||
}
|
||||
args.endArray();
|
||||
|
||||
return adapter_path;
|
||||
}
|
||||
|
||||
void WifiManager::change(unsigned int new_state, unsigned int previous_state, unsigned int change_reason) {
|
||||
raw_adapter_state = new_state;
|
||||
if (new_state == state_need_auth && change_reason == reason_wrong_password) {
|
||||
emit wrongPassword(connecting_to_network);
|
||||
} else if (new_state == state_connected) {
|
||||
emit successfulConnection(connecting_to_network);
|
||||
connecting_to_network = "";
|
||||
}
|
||||
}
|
||||
|
||||
void WifiManager::disconnect() {
|
||||
QString active_ap = get_active_ap();
|
||||
if (active_ap != "" && active_ap != "/") {
|
||||
deactivate_connections(get_property(active_ap, "Ssid"));
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QDBusObjectPath> WifiManager::list_connections(){
|
||||
QVector<QDBusObjectPath> connections;
|
||||
QDBusInterface nm(nm_service, nm_settings_path, nm_settings_iface, bus);
|
||||
nm.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm.call("ListConnections");
|
||||
QVariant first = response.arguments().at(0);
|
||||
const QDBusArgument &args = first.value<QDBusArgument>();
|
||||
args.beginArray();
|
||||
while (!args.atEnd()) {
|
||||
QDBusObjectPath path;
|
||||
args >> path;
|
||||
connections.push_back(path);
|
||||
}
|
||||
return connections;
|
||||
}
|
||||
|
||||
bool WifiManager::activate_wifi_connection(QString ssid){
|
||||
QString devicePath = get_adapter();
|
||||
|
||||
for(QDBusObjectPath path : list_connections()){
|
||||
QDBusInterface nm2(nm_service, path.path(), nm_settings_conn_iface, bus);
|
||||
nm2.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm2.call("GetSettings");
|
||||
const QDBusArgument &dbusArg = response.arguments().at(0).value<QDBusArgument>();
|
||||
|
||||
QMap<QString, QMap<QString,QVariant>> map;
|
||||
dbusArg >> map;
|
||||
for (auto &inner : map) {
|
||||
for (auto &val : inner) {
|
||||
QString key = inner.key(val);
|
||||
if (key == "ssid") {
|
||||
if (val == ssid) {
|
||||
QDBusInterface nm3(nm_service, nm_path, nm_iface, bus);
|
||||
nm3.setTimeout(dbus_timeout);
|
||||
nm3.call("ActivateConnection", QVariant::fromValue(path), QVariant::fromValue(QDBusObjectPath(devicePath)), QVariant::fromValue(QDBusObjectPath("/")));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//Functions for tethering
|
||||
bool WifiManager::activate_tethering_connection(){
|
||||
QString devicePath = get_adapter();
|
||||
|
||||
for(QDBusObjectPath path : list_connections()){
|
||||
QDBusInterface nm2(nm_service, path.path(), nm_settings_conn_iface, bus);
|
||||
nm2.setTimeout(dbus_timeout);
|
||||
|
||||
QDBusMessage response = nm2.call("GetSettings");
|
||||
const QDBusArgument &dbusArg = response.arguments().at(0).value<QDBusArgument>();
|
||||
|
||||
QMap<QString, QMap<QString,QVariant>> map;
|
||||
dbusArg >> map;
|
||||
for (auto &inner : map) {
|
||||
for (auto &val : inner) {
|
||||
QString key = inner.key(val);
|
||||
if (key == "ssid") {
|
||||
if (val == tethering_ssid.toUtf8()) {
|
||||
QDBusInterface nm3(nm_service, nm_path, nm_iface, bus);
|
||||
nm3.setTimeout(dbus_timeout);
|
||||
nm3.call("ActivateConnection", QVariant::fromValue(path), QVariant::fromValue(QDBusObjectPath(devicePath)), QVariant::fromValue(QDBusObjectPath("/")));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void WifiManager::addTetheringConnection(){
|
||||
Connection connection;
|
||||
connection["connection"]["id"] = "Hotspot";
|
||||
connection["connection"]["uuid"] = QUuid::createUuid().toString().remove('{').remove('}');
|
||||
connection["connection"]["type"] = "802-11-wireless";
|
||||
connection["connection"]["interface-name"] = "wlan0";
|
||||
connection["connection"]["autoconnect"] = false;
|
||||
|
||||
connection["802-11-wireless"]["band"] = "bg";
|
||||
connection["802-11-wireless"]["mode"] = "ap";
|
||||
connection["802-11-wireless"]["ssid"] = tethering_ssid.toUtf8();
|
||||
|
||||
connection["802-11-wireless-security"]["group"] = QStringList("ccmp");
|
||||
connection["802-11-wireless-security"]["key-mgmt"] = "wpa-psk";
|
||||
connection["802-11-wireless-security"]["pairwise"] = QStringList("ccmp");
|
||||
connection["802-11-wireless-security"]["proto"] = QStringList("rsn");
|
||||
connection["802-11-wireless-security"]["psk"] = tetheringPassword;
|
||||
|
||||
connection["ipv4"]["method"] = "shared";
|
||||
QMap<QString,QVariant> address;
|
||||
address["address"] = "192.168.43.1";
|
||||
address["prefix"] = 24u;
|
||||
connection["ipv4"]["address-data"] = QVariant::fromValue(IpConfig() << address);
|
||||
connection["ipv4"]["gateway"] = "192.168.43.1";
|
||||
connection["ipv6"]["method"] = "ignore";
|
||||
|
||||
QDBusInterface nm_settings(nm_service, nm_settings_path, nm_settings_iface, bus);
|
||||
nm_settings.setTimeout(dbus_timeout);
|
||||
nm_settings.call("AddConnection", QVariant::fromValue(connection));
|
||||
}
|
||||
|
||||
void WifiManager::enableTethering() {
|
||||
if(activate_tethering_connection()){
|
||||
return;
|
||||
}
|
||||
addTetheringConnection();
|
||||
activate_tethering_connection();
|
||||
}
|
||||
|
||||
void WifiManager::disableTethering() {
|
||||
deactivate_connections(tethering_ssid.toUtf8());
|
||||
}
|
||||
|
||||
bool WifiManager::tetheringEnabled() {
|
||||
QString active_ap = get_active_ap();
|
||||
return get_property(active_ap, "Ssid") == tethering_ssid;
|
||||
}
|
||||
|
||||
void WifiManager::changeTetheringPassword(QString newPassword){
|
||||
tetheringPassword = newPassword;
|
||||
clear_connections(tethering_ssid.toUtf8());
|
||||
addTetheringConnection();
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QtDBus>
|
||||
|
||||
enum class SecurityType {
|
||||
OPEN,
|
||||
WPA,
|
||||
UNSUPPORTED
|
||||
};
|
||||
enum class ConnectedType{
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED
|
||||
};
|
||||
|
||||
typedef QMap<QString, QMap<QString, QVariant>> Connection;
|
||||
typedef QVector<QMap<QString, QVariant>> IpConfig;
|
||||
|
||||
struct Network {
|
||||
QString path;
|
||||
QByteArray ssid;
|
||||
unsigned int strength;
|
||||
ConnectedType connected;
|
||||
SecurityType security_type;
|
||||
};
|
||||
|
||||
class WifiManager : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WifiManager(QWidget* parent);
|
||||
|
||||
void request_scan();
|
||||
QVector<Network> seen_networks;
|
||||
QString ipv4_address;
|
||||
|
||||
void refreshNetworks();
|
||||
void connect(Network ssid);
|
||||
void connect(Network ssid, QString password);
|
||||
void connect(Network ssid, QString username, QString password);
|
||||
void disconnect();
|
||||
|
||||
// Tethering functions
|
||||
void enableTethering();
|
||||
void disableTethering();
|
||||
bool tetheringEnabled();
|
||||
|
||||
bool activate_tethering_connection();
|
||||
void addTetheringConnection();
|
||||
bool activate_wifi_connection(QString ssid);
|
||||
void changeTetheringPassword(QString newPassword);
|
||||
|
||||
private:
|
||||
QVector<QByteArray> seen_ssids;
|
||||
QString adapter;//Path to network manager wifi-device
|
||||
QDBusConnection bus = QDBusConnection::systemBus();
|
||||
unsigned int raw_adapter_state;//Connection status https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMDeviceState
|
||||
QString connecting_to_network;
|
||||
QString tethering_ssid;
|
||||
QString tetheringPassword = "swagswagcommma";
|
||||
|
||||
QString get_adapter();
|
||||
QString get_ipv4_address();
|
||||
QList<Network> get_networks();
|
||||
void connect(QByteArray ssid, QString username, QString password, SecurityType security_type);
|
||||
QString get_active_ap();
|
||||
void deactivate_connections(QString ssid);
|
||||
void clear_connections(QString ssid);
|
||||
QVector<QDBusObjectPath> get_active_connections();
|
||||
uint get_wifi_device_state();
|
||||
QByteArray get_property(QString network_path, QString property);
|
||||
unsigned int get_ap_strength(QString network_path);
|
||||
SecurityType getSecurityType(QString ssid);
|
||||
QVector<QDBusObjectPath> list_connections();
|
||||
|
||||
private slots:
|
||||
void change(unsigned int new_state, unsigned int previous_state, unsigned int change_reason);
|
||||
signals:
|
||||
void wrongPassword(QString ssid);
|
||||
void successfulConnection(QString ssid);
|
||||
void refresh();
|
||||
};
|
||||
@@ -1,246 +0,0 @@
|
||||
#include "selfdrive/ui/qt/onroad.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "selfdrive/common/swaglog.h"
|
||||
#include "selfdrive/common/timing.h"
|
||||
#include "selfdrive/ui/paint.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
#include "selfdrive/ui/qt/maps/map.h"
|
||||
#endif
|
||||
|
||||
OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
|
||||
layout = new QStackedLayout(this);
|
||||
layout->setStackingMode(QStackedLayout::StackAll);
|
||||
|
||||
// old UI on bottom
|
||||
nvg = new NvgWindow(this);
|
||||
QObject::connect(this, &OnroadWindow::update, nvg, &NvgWindow::update);
|
||||
|
||||
split = new QHBoxLayout();
|
||||
split->setContentsMargins(0, 0, 0, 0);
|
||||
split->setSpacing(0);
|
||||
split->addWidget(nvg);
|
||||
|
||||
|
||||
QWidget * split_wrapper = new QWidget;
|
||||
split_wrapper->setLayout(split);
|
||||
layout->addWidget(split_wrapper);
|
||||
|
||||
alerts = new OnroadAlerts(this);
|
||||
alerts->setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
||||
QObject::connect(this, &OnroadWindow::update, alerts, &OnroadAlerts::updateState);
|
||||
QObject::connect(this, &OnroadWindow::offroadTransitionSignal, alerts, &OnroadAlerts::offroadTransition);
|
||||
QObject::connect(this, &OnroadWindow::offroadTransitionSignal, this, &OnroadWindow::offroadTransition);
|
||||
layout->addWidget(alerts);
|
||||
|
||||
// setup stacking order
|
||||
alerts->raise();
|
||||
|
||||
setLayout(layout);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
|
||||
void OnroadWindow::offroadTransition(bool offroad) {
|
||||
#ifdef ENABLE_MAPS
|
||||
if (!offroad) {
|
||||
QString token = QString::fromStdString(Params().get("MapboxToken"));
|
||||
if (map == nullptr && !token.isEmpty()){
|
||||
QMapboxGLSettings settings;
|
||||
settings.setCacheDatabasePath("/data/mbgl-cache.db");
|
||||
settings.setCacheDatabaseMaximumSize(20 * 1024 * 1024);
|
||||
settings.setAccessToken(token.trimmed());
|
||||
|
||||
MapWindow * m = new MapWindow(settings);
|
||||
QObject::connect(this, &OnroadWindow::offroadTransitionSignal, m, &MapWindow::offroadTransition);
|
||||
split->addWidget(m);
|
||||
|
||||
map = m;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ***** onroad widgets *****
|
||||
|
||||
OnroadAlerts::OnroadAlerts(QWidget *parent) : QWidget(parent) {
|
||||
for (auto &kv : sound_map) {
|
||||
auto path = QUrl::fromLocalFile(kv.second.first);
|
||||
sounds[kv.first].setSource(path);
|
||||
}
|
||||
}
|
||||
|
||||
void OnroadAlerts::updateState(const UIState &s) {
|
||||
SubMaster &sm = *(s.sm);
|
||||
if (sm.updated("carState")) {
|
||||
// scale volume with speed
|
||||
volume = util::map_val(sm["carState"].getCarState().getVEgo(), 0.f, 20.f,
|
||||
Hardware::MIN_VOLUME, Hardware::MAX_VOLUME);
|
||||
}
|
||||
if (sm["deviceState"].getDeviceState().getStarted()) {
|
||||
if (sm.updated("controlsState")) {
|
||||
const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState();
|
||||
updateAlert(QString::fromStdString(cs.getAlertText1()), QString::fromStdString(cs.getAlertText2()),
|
||||
cs.getAlertBlinkingRate(), cs.getAlertType(), cs.getAlertSize(), cs.getAlertSound());
|
||||
} else if ((sm.frame - s.scene.started_frame) > 10 * UI_FREQ) {
|
||||
// Handle controls timeout
|
||||
if (sm.rcv_frame("controlsState") < s.scene.started_frame) {
|
||||
// car is started, but controlsState hasn't been seen at all
|
||||
updateAlert("openpilot Unavailable", "Waiting for controls to start", 0,
|
||||
"controlsWaiting", cereal::ControlsState::AlertSize::MID, AudibleAlert::NONE);
|
||||
} else if ((sm.frame - sm.rcv_frame("controlsState")) > 5 * UI_FREQ) {
|
||||
// car is started, but controls is lagging or died
|
||||
updateAlert("TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", 0,
|
||||
"controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, AudibleAlert::CHIME_WARNING_REPEAT);
|
||||
|
||||
// TODO: clean this up once Qt handles the border
|
||||
QUIState::ui_state.status = STATUS_ALERT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add blinking back if performant
|
||||
//float alpha = 0.375 * cos((millis_since_boot() / 1000) * 2 * M_PI * blinking_rate) + 0.625;
|
||||
bg = bg_colors[s.status];
|
||||
}
|
||||
|
||||
void OnroadAlerts::offroadTransition(bool offroad) {
|
||||
updateAlert("", "", 0, "", cereal::ControlsState::AlertSize::NONE, AudibleAlert::NONE);
|
||||
}
|
||||
|
||||
void OnroadAlerts::updateAlert(const QString &t1, const QString &t2, float blink_rate,
|
||||
const std::string &type, cereal::ControlsState::AlertSize size, AudibleAlert sound) {
|
||||
if (alert_type.compare(type) == 0 && text1.compare(t1) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopSounds();
|
||||
if (sound != AudibleAlert::NONE) {
|
||||
playSound(sound);
|
||||
}
|
||||
|
||||
text1 = t1;
|
||||
text2 = t2;
|
||||
alert_type = type;
|
||||
alert_size = size;
|
||||
blinking_rate = blink_rate;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void OnroadAlerts::playSound(AudibleAlert alert) {
|
||||
int loops = sound_map[alert].second ? QSoundEffect::Infinite : 0;
|
||||
sounds[alert].setLoopCount(loops);
|
||||
sounds[alert].setVolume(volume);
|
||||
sounds[alert].play();
|
||||
}
|
||||
|
||||
void OnroadAlerts::stopSounds() {
|
||||
for (auto &kv : sounds) {
|
||||
// Only stop repeating sounds
|
||||
if (kv.second.loopsRemaining() == QSoundEffect::Infinite) {
|
||||
kv.second.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnroadAlerts::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
|
||||
if (alert_size == cereal::ControlsState::AlertSize::NONE) {
|
||||
return;
|
||||
}
|
||||
static std::map<cereal::ControlsState::AlertSize, const int> alert_sizes = {
|
||||
{cereal::ControlsState::AlertSize::SMALL, 271},
|
||||
{cereal::ControlsState::AlertSize::MID, 420},
|
||||
{cereal::ControlsState::AlertSize::FULL, height()},
|
||||
};
|
||||
int h = alert_sizes[alert_size];
|
||||
QRect r = QRect(0, height() - h, width(), h);
|
||||
|
||||
// draw background + gradient
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
|
||||
|
||||
p.setBrush(QBrush(bg));
|
||||
p.drawRect(r);
|
||||
|
||||
QLinearGradient g(0, r.y(), 0, r.bottom());
|
||||
g.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.05));
|
||||
g.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0.35));
|
||||
p.setBrush(QBrush(g));
|
||||
p.fillRect(r, g);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
|
||||
// remove bottom border
|
||||
r = QRect(0, height() - h, width(), h - 30);
|
||||
|
||||
// text
|
||||
const QPoint c = r.center();
|
||||
p.setPen(QColor(0xff, 0xff, 0xff));
|
||||
p.setRenderHint(QPainter::TextAntialiasing);
|
||||
if (alert_size == cereal::ControlsState::AlertSize::SMALL) {
|
||||
configFont(p, "Open Sans", 74, "SemiBold");
|
||||
p.drawText(r, Qt::AlignCenter, text1);
|
||||
} else if (alert_size == cereal::ControlsState::AlertSize::MID) {
|
||||
configFont(p, "Open Sans", 88, "Bold");
|
||||
p.drawText(QRect(0, c.y() - 125, width(), 150), Qt::AlignHCenter | Qt::AlignTop, text1);
|
||||
configFont(p, "Open Sans", 66, "Regular");
|
||||
p.drawText(QRect(0, c.y() + 21, width(), 90), Qt::AlignHCenter, text2);
|
||||
} else if (alert_size == cereal::ControlsState::AlertSize::FULL) {
|
||||
bool l = text1.length() > 15;
|
||||
configFont(p, "Open Sans", l ? 132 : 177, "Bold");
|
||||
p.drawText(QRect(0, r.y() + (l ? 240 : 270), width(), 600), Qt::AlignHCenter | Qt::TextWordWrap, text1);
|
||||
configFont(p, "Open Sans", 88, "Regular");
|
||||
p.drawText(QRect(0, r.height() - (l ? 361 : 420), width(), 300), Qt::AlignHCenter | Qt::TextWordWrap, text2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NvgWindow::NvgWindow(QWidget *parent) : QOpenGLWidget(parent) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
NvgWindow::~NvgWindow() {
|
||||
makeCurrent();
|
||||
doneCurrent();
|
||||
}
|
||||
|
||||
void NvgWindow::initializeGL() {
|
||||
initializeOpenGLFunctions();
|
||||
std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
|
||||
std::cout << "OpenGL vendor: " << glGetString(GL_VENDOR) << std::endl;
|
||||
std::cout << "OpenGL renderer: " << glGetString(GL_RENDERER) << std::endl;
|
||||
std::cout << "OpenGL language version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
|
||||
|
||||
ui_nvg_init(&QUIState::ui_state);
|
||||
prev_draw_t = millis_since_boot();
|
||||
}
|
||||
|
||||
void NvgWindow::update(const UIState &s) {
|
||||
// Connecting to visionIPC requires opengl to be current
|
||||
if (s.vipc_client->connected){
|
||||
makeCurrent();
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
void NvgWindow::resizeGL(int w, int h) {
|
||||
ui_resize(&QUIState::ui_state, w, h);
|
||||
}
|
||||
|
||||
void NvgWindow::paintGL() {
|
||||
ui_draw(&QUIState::ui_state, width(), height());
|
||||
|
||||
double cur_draw_t = millis_since_boot();
|
||||
double dt = cur_draw_t - prev_draw_t;
|
||||
if (dt > 66) {
|
||||
// warn on sub 15fps
|
||||
LOGW("slow frame time: %.2f", dt);
|
||||
}
|
||||
prev_draw_t = cur_draw_t;
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <QSoundEffect>
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "cereal/gen/cpp/log.capnp.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
|
||||
typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert;
|
||||
|
||||
// ***** onroad widgets *****
|
||||
|
||||
class OnroadAlerts : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OnroadAlerts(QWidget *parent = 0);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
void stopSounds();
|
||||
void playSound(AudibleAlert alert);
|
||||
void updateAlert(const QString &t1, const QString &t2, float blink_rate,
|
||||
const std::string &type, cereal::ControlsState::AlertSize size, AudibleAlert sound);
|
||||
|
||||
std::map<AudibleAlert, std::pair<QString, bool>> sound_map {
|
||||
// AudibleAlert, (file path, inf loop)
|
||||
{AudibleAlert::CHIME_DISENGAGE, {"../assets/sounds/disengaged.wav", false}},
|
||||
{AudibleAlert::CHIME_ENGAGE, {"../assets/sounds/engaged.wav", false}},
|
||||
{AudibleAlert::CHIME_WARNING1, {"../assets/sounds/warning_1.wav", false}},
|
||||
{AudibleAlert::CHIME_WARNING2, {"../assets/sounds/warning_2.wav", false}},
|
||||
{AudibleAlert::CHIME_WARNING2_REPEAT, {"../assets/sounds/warning_2.wav", true}},
|
||||
{AudibleAlert::CHIME_WARNING_REPEAT, {"../assets/sounds/warning_repeat.wav", true}},
|
||||
{AudibleAlert::CHIME_ERROR, {"../assets/sounds/error.wav", false}},
|
||||
{AudibleAlert::CHIME_PROMPT, {"../assets/sounds/error.wav", false}}
|
||||
};
|
||||
|
||||
QColor bg;
|
||||
float volume = Hardware::MIN_VOLUME;
|
||||
std::map<AudibleAlert, QSoundEffect> sounds;
|
||||
float blinking_rate = 0;
|
||||
QString text1, text2;
|
||||
std::string alert_type;
|
||||
cereal::ControlsState::AlertSize alert_size;
|
||||
|
||||
public slots:
|
||||
void updateState(const UIState &s);
|
||||
void offroadTransition(bool offroad);
|
||||
};
|
||||
|
||||
// container window for the NVG UI
|
||||
class NvgWindow : public QOpenGLWidget, protected QOpenGLFunctions {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QOpenGLWidget::QOpenGLWidget;
|
||||
explicit NvgWindow(QWidget* parent = 0);
|
||||
~NvgWindow();
|
||||
|
||||
protected:
|
||||
void paintGL() override;
|
||||
void initializeGL() override;
|
||||
void resizeGL(int w, int h) override;
|
||||
|
||||
private:
|
||||
double prev_draw_t = 0;
|
||||
|
||||
public slots:
|
||||
void update(const UIState &s);
|
||||
};
|
||||
|
||||
// container for all onroad widgets
|
||||
class OnroadWindow : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OnroadWindow(QWidget* parent = 0);
|
||||
QWidget *map = nullptr;
|
||||
|
||||
private:
|
||||
OnroadAlerts *alerts;
|
||||
NvgWindow *nvg;
|
||||
QStackedLayout *layout;
|
||||
QHBoxLayout* split;
|
||||
|
||||
signals:
|
||||
void update(const UIState &s);
|
||||
void offroadTransitionSignal(bool offroad);
|
||||
|
||||
private slots:
|
||||
void offroadTransition(bool offroad);
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
|
||||
#ifdef QCOM2
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <QPlatformSurfaceEvent>
|
||||
#endif
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
|
||||
const int vwp_w = Hardware::TICI() ? 2160 : 1920;
|
||||
const int vwp_h = 1080;
|
||||
|
||||
inline void setMainWindow(QWidget *w) {
|
||||
const float scale = getenv("SCALE") != NULL ? std::stof(getenv("SCALE")) : 1.0;
|
||||
w->setFixedSize(vwp_w*scale, vwp_h*scale);
|
||||
w->show();
|
||||
|
||||
#ifdef QCOM2
|
||||
QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface();
|
||||
wl_surface *s = reinterpret_cast<wl_surface*>(native->nativeResourceForWindow("surface", w->windowHandle()));
|
||||
wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270);
|
||||
wl_surface_commit(s);
|
||||
w->showFullScreen();
|
||||
#endif
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "request_repeater.h"
|
||||
|
||||
RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey,
|
||||
int period) : HttpRequest(parent, requestURL, cacheKey) {
|
||||
timer = new QTimer(this);
|
||||
timer->setTimerType(Qt::VeryCoarseTimer);
|
||||
QObject::connect(timer, &QTimer::timeout, [=](){
|
||||
if (!QUIState::ui_state.scene.started && QUIState::ui_state.awake && reply == NULL) {
|
||||
sendRequest(requestURL);
|
||||
}
|
||||
});
|
||||
timer->start(period * 1000);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class RequestRepeater : public HttpRequest {
|
||||
public:
|
||||
RequestRepeater(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0);
|
||||
|
||||
private:
|
||||
QTimer *timer;
|
||||
};
|
||||
@@ -1,115 +0,0 @@
|
||||
#include "selfdrive/ui/qt/sidebar.h"
|
||||
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
void Sidebar::drawMetric(QPainter &p, const QString &label, const QString &val, QColor c, int y) {
|
||||
const QRect rect = {30, y, 240, val.isEmpty() ? (label.contains("\n") ? 124 : 100) : 148};
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(QBrush(c));
|
||||
p.setClipRect(rect.x() + 6, rect.y(), 18, rect.height(), Qt::ClipOperation::ReplaceClip);
|
||||
p.drawRoundedRect(QRect(rect.x() + 6, rect.y() + 6, 100, rect.height() - 12), 10, 10);
|
||||
p.setClipping(false);
|
||||
|
||||
QPen pen = QPen(QColor(0xff, 0xff, 0xff, 0x55));
|
||||
pen.setWidth(2);
|
||||
p.setPen(pen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawRoundedRect(rect, 20, 20);
|
||||
|
||||
p.setPen(QColor(0xff, 0xff, 0xff));
|
||||
if (val.isEmpty()) {
|
||||
configFont(p, "Open Sans", 35, "Bold");
|
||||
const QRect r = QRect(rect.x() + 35, rect.y(), rect.width() - 50, rect.height());
|
||||
p.drawText(r, Qt::AlignCenter, label);
|
||||
} else {
|
||||
configFont(p, "Open Sans", 58, "Bold");
|
||||
p.drawText(rect.x() + 50, rect.y() + 71, val);
|
||||
configFont(p, "Open Sans", 35, "Regular");
|
||||
p.drawText(rect.x() + 50, rect.y() + 50 + 77, label);
|
||||
}
|
||||
}
|
||||
|
||||
Sidebar::Sidebar(QWidget *parent) : QFrame(parent) {
|
||||
home_img = QImage("../assets/images/button_home.png").scaled(180, 180, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
settings_img = QImage("../assets/images/button_settings.png").scaled(settings_btn.width(), settings_btn.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);;
|
||||
|
||||
connect(this, &Sidebar::valueChanged, [=] { update(); });
|
||||
|
||||
setFixedWidth(300);
|
||||
setMinimumHeight(vwp_h);
|
||||
setStyleSheet("background-color: rgb(57, 57, 57);");
|
||||
}
|
||||
|
||||
void Sidebar::mousePressEvent(QMouseEvent *event) {
|
||||
if (settings_btn.contains(event->pos())) {
|
||||
emit openSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::updateState(const UIState &s) {
|
||||
auto &sm = *(s.sm);
|
||||
|
||||
auto deviceState = sm["deviceState"].getDeviceState();
|
||||
setProperty("netType", network_type[deviceState.getNetworkType()]);
|
||||
setProperty("netStrength", signal_imgs[deviceState.getNetworkStrength()]);
|
||||
|
||||
auto last_ping = deviceState.getLastAthenaPingTime();
|
||||
if (last_ping == 0) {
|
||||
setProperty("connectStr", "OFFLINE");
|
||||
setProperty("connectStatus", warning_color);
|
||||
} else {
|
||||
bool online = nanos_since_boot() - last_ping < 80e9;
|
||||
setProperty("connectStr", online ? "ONLINE" : "ERROR");
|
||||
setProperty("connectStatus", online ? good_color : danger_color);
|
||||
}
|
||||
|
||||
QColor tempStatus = danger_color;
|
||||
auto ts = deviceState.getThermalStatus();
|
||||
if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
|
||||
tempStatus = good_color;
|
||||
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
|
||||
tempStatus = warning_color;
|
||||
}
|
||||
setProperty("tempStatus", tempStatus);
|
||||
setProperty("tempVal", (int)deviceState.getAmbientTempC());
|
||||
|
||||
QString pandaStr = "VEHICLE\nONLINE";
|
||||
QColor pandaStatus = good_color;
|
||||
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
|
||||
pandaStatus = danger_color;
|
||||
pandaStr = "NO\nPANDA";
|
||||
} else if (Hardware::TICI() && s.scene.started) {
|
||||
pandaStr = QString("SATS %1\nACC %2").arg(s.scene.satelliteCount).arg(fmin(10, s.scene.gpsAccuracy), 0, 'f', 2);
|
||||
pandaStatus = sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK() ? good_color : warning_color;
|
||||
}
|
||||
setProperty("pandaStr", pandaStr);
|
||||
setProperty("pandaStatus", pandaStatus);
|
||||
}
|
||||
|
||||
void Sidebar::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// static imgs
|
||||
p.setOpacity(0.65);
|
||||
p.drawImage(settings_btn.x(), settings_btn.y(), settings_img);
|
||||
p.setOpacity(1.0);
|
||||
p.drawImage(60, 1080 - 180 - 40, home_img);
|
||||
|
||||
// network
|
||||
p.drawImage(58, 196, net_strength);
|
||||
configFont(p, "Open Sans", 35, "Regular");
|
||||
p.setPen(QColor(0xff, 0xff, 0xff));
|
||||
const QRect r = QRect(50, 247, 100, 50);
|
||||
p.drawText(r, Qt::AlignCenter, net_type);
|
||||
|
||||
// metrics
|
||||
drawMetric(p, "TEMP", QString("%1°C").arg(temp_val), temp_status, 338);
|
||||
drawMetric(p, panda_str, "", panda_status, 518);
|
||||
drawMetric(p, "CONNECT\n" + connect_str, "", connect_status, 676);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class Sidebar : public QFrame {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString connectStr MEMBER connect_str NOTIFY valueChanged);
|
||||
Q_PROPERTY(QColor connectStatus MEMBER connect_status NOTIFY valueChanged);
|
||||
Q_PROPERTY(QString pandaStr MEMBER panda_str NOTIFY valueChanged);
|
||||
Q_PROPERTY(QColor pandaStatus MEMBER panda_status NOTIFY valueChanged);
|
||||
Q_PROPERTY(int tempVal MEMBER temp_val NOTIFY valueChanged);
|
||||
Q_PROPERTY(QColor tempStatus MEMBER temp_status NOTIFY valueChanged);
|
||||
Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged);
|
||||
Q_PROPERTY(QImage netStrength MEMBER net_strength NOTIFY valueChanged);
|
||||
|
||||
public:
|
||||
explicit Sidebar(QWidget* parent = 0);
|
||||
|
||||
signals:
|
||||
void openSettings();
|
||||
void valueChanged();
|
||||
|
||||
public slots:
|
||||
void updateState(const UIState &s);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void drawMetric(QPainter &p, const QString &label, const QString &val, QColor c, int y);
|
||||
|
||||
QImage home_img, settings_img;
|
||||
const QMap<cereal::DeviceState::NetworkType, QString> network_type = {
|
||||
{cereal::DeviceState::NetworkType::NONE, "--"},
|
||||
{cereal::DeviceState::NetworkType::WIFI, "WiFi"},
|
||||
{cereal::DeviceState::NetworkType::CELL2_G, "2G"},
|
||||
{cereal::DeviceState::NetworkType::CELL3_G, "3G"},
|
||||
{cereal::DeviceState::NetworkType::CELL4_G, "LTE"},
|
||||
{cereal::DeviceState::NetworkType::CELL5_G, "5G"}
|
||||
};
|
||||
const QMap<cereal::DeviceState::NetworkStrength, QImage> signal_imgs = {
|
||||
{cereal::DeviceState::NetworkStrength::UNKNOWN, QImage("../assets/images/network_0.png")},
|
||||
{cereal::DeviceState::NetworkStrength::POOR, QImage("../assets/images/network_1.png")},
|
||||
{cereal::DeviceState::NetworkStrength::MODERATE, QImage("../assets/images/network_2.png")},
|
||||
{cereal::DeviceState::NetworkStrength::GOOD, QImage("../assets/images/network_3.png")},
|
||||
{cereal::DeviceState::NetworkStrength::GREAT, QImage("../assets/images/network_4.png")},
|
||||
};
|
||||
|
||||
const QRect settings_btn = QRect(50, 35, 200, 117);
|
||||
const QColor good_color = QColor(255, 255, 255);
|
||||
const QColor warning_color = QColor(218, 202, 37);
|
||||
const QColor danger_color = QColor(201, 34, 49);
|
||||
|
||||
QString connect_str = "OFFLINE";
|
||||
QColor connect_status = warning_color;
|
||||
QString panda_str = "NO\nPANDA";
|
||||
QColor panda_status = warning_color;
|
||||
int temp_val = 0;
|
||||
QColor temp_status = warning_color;
|
||||
QString net_type;
|
||||
QImage net_strength;
|
||||
};
|
||||
Executable
BIN
Binary file not shown.
@@ -1,134 +0,0 @@
|
||||
#include "spinner.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGridLayout>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
#include <QTransform>
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
|
||||
TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) {
|
||||
setFixedSize(spinner_size);
|
||||
setAutoFillBackground(false);
|
||||
|
||||
comma_img = QPixmap("../assets/img_spinner_comma.png").scaled(spinner_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
// pre-compute all the track imgs. make this a gif instead?
|
||||
QTransform transform;
|
||||
QPixmap track_img = QPixmap("../assets/img_spinner_track.png").scaled(spinner_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
for (auto &img : track_imgs) {
|
||||
img = track_img.transformed(transform.rotate(360/spinner_fps), Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
m_anim.setDuration(1000);
|
||||
m_anim.setStartValue(0);
|
||||
m_anim.setEndValue(int(track_imgs.size() -1));
|
||||
m_anim.setLoopCount(-1);
|
||||
m_anim.start();
|
||||
connect(&m_anim, SIGNAL(valueChanged(QVariant)), SLOT(update()));
|
||||
}
|
||||
|
||||
void TrackWidget::paintEvent(QPaintEvent *event) {
|
||||
QPainter painter(this);
|
||||
QRect bg(0, 0, painter.device()->width(), painter.device()->height());
|
||||
QBrush bgBrush("#000000");
|
||||
painter.fillRect(bg, bgBrush);
|
||||
|
||||
int track_idx = m_anim.currentValue().toInt();
|
||||
QRect rect(track_imgs[track_idx].rect());
|
||||
rect.moveCenter(bg.center());
|
||||
painter.drawPixmap(rect.topLeft(), track_imgs[track_idx]);
|
||||
|
||||
rect = comma_img.rect();
|
||||
rect.moveCenter(bg.center());
|
||||
painter.drawPixmap(rect.topLeft(), comma_img);
|
||||
}
|
||||
|
||||
// Spinner
|
||||
|
||||
Spinner::Spinner(QWidget *parent) {
|
||||
QGridLayout *main_layout = new QGridLayout();
|
||||
main_layout->setSpacing(0);
|
||||
main_layout->setMargin(200);
|
||||
|
||||
main_layout->addWidget(new TrackWidget(this), 0, 0, Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
|
||||
text = new QLabel();
|
||||
text->setVisible(false);
|
||||
main_layout->addWidget(text, 1, 0, Qt::AlignHCenter);
|
||||
|
||||
progress_bar = new QProgressBar();
|
||||
progress_bar->setRange(5, 100);
|
||||
progress_bar->setTextVisible(false);
|
||||
progress_bar->setVisible(false);
|
||||
progress_bar->setFixedHeight(20);
|
||||
main_layout->addWidget(progress_bar, 1, 0, Qt::AlignHCenter);
|
||||
|
||||
setLayout(main_layout);
|
||||
setStyleSheet(R"(
|
||||
Spinner {
|
||||
background-color: black;
|
||||
}
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
QLabel {
|
||||
color: white;
|
||||
font-size: 80px;
|
||||
}
|
||||
QProgressBar {
|
||||
background-color: #373737;
|
||||
width: 1000px;
|
||||
border solid white;
|
||||
border-radius: 10px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
border-radius: 10px;
|
||||
background-color: white;
|
||||
}
|
||||
)");
|
||||
|
||||
notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read);
|
||||
QObject::connect(notifier, &QSocketNotifier::activated, this, &Spinner::update);
|
||||
};
|
||||
|
||||
void Spinner::update(int n) {
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
|
||||
if (line.length()) {
|
||||
bool number = std::all_of(line.begin(), line.end(), ::isdigit);
|
||||
text->setVisible(!number);
|
||||
progress_bar->setVisible(number);
|
||||
text->setText(QString::fromStdString(line));
|
||||
if (number) {
|
||||
progress_bar->setValue(std::stoi(line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QSurfaceFormat fmt;
|
||||
#ifdef __APPLE__
|
||||
fmt.setVersion(3, 2);
|
||||
fmt.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
fmt.setRenderableType(QSurfaceFormat::OpenGL);
|
||||
#else
|
||||
fmt.setRenderableType(QSurfaceFormat::OpenGLES);
|
||||
#endif
|
||||
QSurfaceFormat::setDefaultFormat(fmt);
|
||||
|
||||
Hardware::set_display_power(true);
|
||||
Hardware::set_brightness(65);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
Spinner spinner;
|
||||
setMainWindow(&spinner);
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#include <array>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QPixmap>
|
||||
#include <QProgressBar>
|
||||
#include <QSocketNotifier>
|
||||
#include <QVariantAnimation>
|
||||
#include <QWidget>
|
||||
|
||||
constexpr int spinner_fps = 30;
|
||||
constexpr QSize spinner_size = QSize(360, 360);
|
||||
|
||||
class TrackWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TrackWidget(QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
std::array<QPixmap, spinner_fps> track_imgs;
|
||||
QPixmap comma_img;
|
||||
QVariantAnimation m_anim;
|
||||
};
|
||||
|
||||
class Spinner : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Spinner(QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
QLabel *text;
|
||||
QProgressBar *progress_bar;
|
||||
QSocketNotifier *notifier;
|
||||
|
||||
public slots:
|
||||
void update(int n);
|
||||
};
|
||||
Executable
BIN
Binary file not shown.
@@ -1,66 +0,0 @@
|
||||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
QWidget window;
|
||||
setMainWindow(&window);
|
||||
|
||||
Hardware::set_display_power(true);
|
||||
Hardware::set_brightness(65);
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->setMargin(50);
|
||||
|
||||
QLabel *label = new QLabel(argv[1]);
|
||||
label->setWordWrap(true);
|
||||
label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
|
||||
ScrollView *scroll = new ScrollView(label);
|
||||
scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
layout->addWidget(scroll, 0, 0, Qt::AlignTop);
|
||||
|
||||
// Scroll to the bottom
|
||||
QObject::connect(scroll->verticalScrollBar(), &QAbstractSlider::rangeChanged, [=](){
|
||||
scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum());
|
||||
});
|
||||
|
||||
QPushButton *btn = new QPushButton();
|
||||
#ifdef __aarch64__
|
||||
btn->setText("Reboot");
|
||||
QObject::connect(btn, &QPushButton::released, [=]() {
|
||||
Hardware::reboot();
|
||||
});
|
||||
#else
|
||||
btn->setText("Exit");
|
||||
QObject::connect(btn, &QPushButton::released, &a, &QApplication::quit);
|
||||
#endif
|
||||
layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom);
|
||||
|
||||
window.setLayout(layout);
|
||||
window.setStyleSheet(R"(
|
||||
* {
|
||||
outline: none;
|
||||
color: white;
|
||||
background-color: black;
|
||||
font-size: 60px;
|
||||
}
|
||||
QPushButton {
|
||||
padding: 50px;
|
||||
padding-right: 100px;
|
||||
padding-left: 100px;
|
||||
border: 2px solid white;
|
||||
border-radius: 20px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
)");
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
inline void configFont(QPainter &p, QString family, int size, const QString &style) {
|
||||
QFont f(family);
|
||||
f.setPixelSize(size);
|
||||
f.setStyleName(style);
|
||||
p.setFont(f);
|
||||
}
|
||||
|
||||
inline void clearLayout(QLayout* layout) {
|
||||
while (QLayoutItem* item = layout->takeAt(0)) {
|
||||
if (QWidget* widget = item->widget()) {
|
||||
widget->deleteLater();
|
||||
}
|
||||
if (QLayout* childLayout = item->layout()) {
|
||||
clearLayout(childLayout);
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
inline QString timeAgo(const QDateTime &date) {
|
||||
int diff = date.secsTo(QDateTime::currentDateTimeUtc());
|
||||
|
||||
QString s;
|
||||
if (diff < 60) {
|
||||
s = "now";
|
||||
} else if (diff < 60 * 60) {
|
||||
int minutes = diff / 60;
|
||||
s = QString("%1 minute%2 ago").arg(minutes).arg(minutes > 1 ? "s" : "");
|
||||
} else if (diff < 60 * 60 * 24) {
|
||||
int hours = diff / (60 * 60);
|
||||
s = QString("%1 hour%2 ago").arg(hours).arg(hours > 1 ? "s" : "");
|
||||
} else if (diff < 3600 * 24 * 7) {
|
||||
int days = diff / (60 * 60 * 24);
|
||||
s = QString("%1 day%2 ago").arg(days).arg(days > 1 ? "s" : "");
|
||||
} else {
|
||||
s = date.date().toString();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
|
||||
namespace {
|
||||
const char frame_vertex_shader[] =
|
||||
#ifdef NANOVG_GL3_IMPLEMENTATION
|
||||
"#version 150 core\n"
|
||||
#else
|
||||
"#version 300 es\n"
|
||||
#endif
|
||||
"in vec4 aPosition;\n"
|
||||
"in vec4 aTexCoord;\n"
|
||||
"uniform mat4 uTransform;\n"
|
||||
"out vec4 vTexCoord;\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = uTransform * aPosition;\n"
|
||||
" vTexCoord = aTexCoord;\n"
|
||||
"}\n";
|
||||
|
||||
const char frame_fragment_shader[] =
|
||||
#ifdef NANOVG_GL3_IMPLEMENTATION
|
||||
"#version 150 core\n"
|
||||
#else
|
||||
"#version 300 es\n"
|
||||
#endif
|
||||
"precision mediump float;\n"
|
||||
"uniform sampler2D uTexture;\n"
|
||||
"in vec4 vTexCoord;\n"
|
||||
"out vec4 colorOut;\n"
|
||||
"void main() {\n"
|
||||
" colorOut = texture(uTexture, vTexCoord.xy);\n"
|
||||
#ifdef QCOM
|
||||
" vec3 dz = vec3(0.0627f, 0.0627f, 0.0627f);\n"
|
||||
" colorOut.rgb = ((vec3(1.0f, 1.0f, 1.0f) - dz) * colorOut.rgb / vec3(1.0f, 1.0f, 1.0f)) + dz;\n"
|
||||
#endif
|
||||
"}\n";
|
||||
|
||||
const mat4 device_transform = {{
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
|
||||
mat4 get_driver_view_transform() {
|
||||
const float driver_view_ratio = 1.333;
|
||||
mat4 transform;
|
||||
if (Hardware::TICI()) {
|
||||
// from dmonitoring.cc
|
||||
const int full_width_tici = 1928;
|
||||
const int full_height_tici = 1208;
|
||||
const int adapt_width_tici = 668;
|
||||
const int crop_x_offset = 32;
|
||||
const int crop_y_offset = -196;
|
||||
const float yscale = full_height_tici * driver_view_ratio / adapt_width_tici;
|
||||
const float xscale = yscale*(1080)/(2160)*full_width_tici/full_height_tici;
|
||||
transform = (mat4){{
|
||||
xscale, 0.0, 0.0, xscale*crop_x_offset/full_width_tici*2,
|
||||
0.0, yscale, 0.0, yscale*crop_y_offset/full_height_tici*2,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
} else {
|
||||
// frame from 4/3 to 16/9 display
|
||||
transform = (mat4){{
|
||||
driver_view_ratio*(1080)/(1920), 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
}
|
||||
return transform;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CameraViewWidget::CameraViewWidget(VisionStreamType stream_type, QWidget* parent) : stream_type(stream_type), QOpenGLWidget(parent) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &CameraViewWidget::updateFrame);
|
||||
}
|
||||
|
||||
CameraViewWidget::~CameraViewWidget() {
|
||||
makeCurrent();
|
||||
doneCurrent();
|
||||
glDeleteVertexArrays(1, &frame_vao);
|
||||
glDeleteBuffers(1, &frame_vbo);
|
||||
glDeleteBuffers(1, &frame_ibo);
|
||||
}
|
||||
|
||||
void CameraViewWidget::initializeGL() {
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
gl_shader = std::make_unique<GLShader>(frame_vertex_shader, frame_fragment_shader);
|
||||
GLint frame_pos_loc = glGetAttribLocation(gl_shader->prog, "aPosition");
|
||||
GLint frame_texcoord_loc = glGetAttribLocation(gl_shader->prog, "aTexCoord");
|
||||
|
||||
auto [x1, x2, y1, y2] = stream_type == VISION_STREAM_RGB_FRONT ? std::tuple(0.f, 1.f, 1.f, 0.f) : std::tuple(1.f, 0.f, 1.f, 0.f);
|
||||
const uint8_t frame_indicies[] = {0, 1, 2, 0, 2, 3};
|
||||
const float frame_coords[4][4] = {
|
||||
{-1.0, -1.0, x2, y1}, //bl
|
||||
{-1.0, 1.0, x2, y2}, //tl
|
||||
{ 1.0, 1.0, x1, y2}, //tr
|
||||
{ 1.0, -1.0, x1, y1}, //br
|
||||
};
|
||||
|
||||
glGenVertexArrays(1, &frame_vao);
|
||||
glBindVertexArray(frame_vao);
|
||||
glGenBuffers(1, &frame_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, frame_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(frame_coords), frame_coords, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(frame_pos_loc);
|
||||
glVertexAttribPointer(frame_pos_loc, 2, GL_FLOAT, GL_FALSE,
|
||||
sizeof(frame_coords[0]), (const void *)0);
|
||||
glEnableVertexAttribArray(frame_texcoord_loc);
|
||||
glVertexAttribPointer(frame_texcoord_loc, 2, GL_FLOAT, GL_FALSE,
|
||||
sizeof(frame_coords[0]), (const void *)(sizeof(float) * 2));
|
||||
glGenBuffers(1, &frame_ibo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, frame_ibo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(frame_indicies), frame_indicies, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
if (stream_type == VISION_STREAM_RGB_FRONT) {
|
||||
frame_mat = matmul(device_transform, get_driver_view_transform());
|
||||
} else {
|
||||
auto intrinsic_matrix = stream_type == VISION_STREAM_RGB_WIDE ? ecam_intrinsic_matrix : fcam_intrinsic_matrix;
|
||||
float zoom_ = zoom / intrinsic_matrix.v[0];
|
||||
if (stream_type == VISION_STREAM_RGB_WIDE) {
|
||||
zoom_ *= 0.5;
|
||||
}
|
||||
float zx = zoom_ * 2 * intrinsic_matrix.v[2] / width();
|
||||
float zy = zoom_ * 2 * intrinsic_matrix.v[5] / height();
|
||||
|
||||
const mat4 frame_transform = {{
|
||||
zx, 0.0, 0.0, 0.0,
|
||||
0.0, zy, 0.0, -y_offset / height() * 2,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}};
|
||||
frame_mat = matmul(device_transform, frame_transform);
|
||||
}
|
||||
vipc_client = std::make_unique<VisionIpcClient>("camerad", stream_type, true);
|
||||
}
|
||||
|
||||
void CameraViewWidget::showEvent(QShowEvent *event) {
|
||||
timer->start(0);
|
||||
}
|
||||
|
||||
void CameraViewWidget::hideEvent(QHideEvent *event) {
|
||||
timer->stop();
|
||||
vipc_client->connected = false;
|
||||
latest_frame = nullptr;
|
||||
}
|
||||
|
||||
void CameraViewWidget::paintGL() {
|
||||
if (!latest_frame) {
|
||||
glClearColor(0, 0, 0, 1.0);
|
||||
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||
return;
|
||||
}
|
||||
|
||||
glViewport(0, 0, width(), height());
|
||||
|
||||
glBindVertexArray(frame_vao);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture[latest_frame->idx]->frame_tex);
|
||||
if (!Hardware::EON()) {
|
||||
// this is handled in ion on QCOM
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, latest_frame->width, latest_frame->height,
|
||||
0, GL_RGB, GL_UNSIGNED_BYTE, latest_frame->addr);
|
||||
}
|
||||
|
||||
glUseProgram(gl_shader->prog);
|
||||
glUniform1i(gl_shader->getUniformLocation("uTexture"), 0);
|
||||
glUniformMatrix4fv(gl_shader->getUniformLocation("uTransform"), 1, GL_TRUE, frame_mat.v);
|
||||
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
glEnableVertexAttribArray(0);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (const void *)0);
|
||||
glDisableVertexAttribArray(0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void CameraViewWidget::updateFrame() {
|
||||
if (!vipc_client->connected && vipc_client->connect(false)) {
|
||||
// init vision
|
||||
for (int i = 0; i < vipc_client->num_buffers; i++) {
|
||||
texture[i].reset(new EGLImageTexture(&vipc_client->buffers[i]));
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture[i]->frame_tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
// BGR
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
}
|
||||
latest_frame = nullptr;
|
||||
}
|
||||
|
||||
if (vipc_client->connected) {
|
||||
VisionBuf *buf = vipc_client->recv();
|
||||
if (buf != nullptr) {
|
||||
latest_frame = buf;
|
||||
update();
|
||||
emit frameUpdated();
|
||||
} else {
|
||||
LOGE("visionIPC receive timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLWidget>
|
||||
|
||||
#include "cereal/visionipc/visionipc_client.h"
|
||||
#include "selfdrive/common/glutil.h"
|
||||
#include "selfdrive/common/mat.h"
|
||||
#include "selfdrive/common/visionimg.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class CameraViewWidget : public QOpenGLWidget, protected QOpenGLFunctions {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QOpenGLWidget::QOpenGLWidget;
|
||||
explicit CameraViewWidget(VisionStreamType stream_type, QWidget* parent = nullptr);
|
||||
~CameraViewWidget();
|
||||
|
||||
signals:
|
||||
void frameUpdated();
|
||||
|
||||
protected:
|
||||
void paintGL() override;
|
||||
void initializeGL() override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
|
||||
protected slots:
|
||||
void updateFrame();
|
||||
|
||||
private:
|
||||
VisionBuf *latest_frame = nullptr;
|
||||
GLuint frame_vao, frame_vbo, frame_ibo;
|
||||
mat4 frame_mat;
|
||||
std::unique_ptr<VisionIpcClient> vipc_client;
|
||||
std::unique_ptr<EGLImageTexture> texture[UI_BUF_COUNT];
|
||||
std::unique_ptr<GLShader> gl_shader;
|
||||
|
||||
VisionStreamType stream_type;
|
||||
QTimer* timer;
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
#include "controls.h"
|
||||
|
||||
QFrame *horizontal_line(QWidget *parent) {
|
||||
QFrame *line = new QFrame(parent);
|
||||
line->setFrameShape(QFrame::StyledPanel);
|
||||
line->setStyleSheet(R"(
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
border-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-color: gray;
|
||||
)");
|
||||
line->setFixedHeight(2);
|
||||
return line;
|
||||
}
|
||||
|
||||
AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) {
|
||||
QVBoxLayout *vlayout = new QVBoxLayout();
|
||||
vlayout->setMargin(0);
|
||||
|
||||
hlayout = new QHBoxLayout;
|
||||
hlayout->setMargin(0);
|
||||
hlayout->setSpacing(20);
|
||||
|
||||
// left icon
|
||||
if (!icon.isEmpty()) {
|
||||
QPixmap pix(icon);
|
||||
QLabel *icon = new QLabel();
|
||||
icon->setPixmap(pix.scaledToWidth(80, Qt::SmoothTransformation));
|
||||
icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||
hlayout->addWidget(icon);
|
||||
}
|
||||
|
||||
// title
|
||||
title_label = new QPushButton(title);
|
||||
title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left;");
|
||||
hlayout->addWidget(title_label);
|
||||
|
||||
vlayout->addLayout(hlayout);
|
||||
|
||||
// description
|
||||
if (!desc.isEmpty()) {
|
||||
description = new QLabel(desc);
|
||||
description->setContentsMargins(40, 20, 40, 20);
|
||||
description->setStyleSheet("font-size: 40px; color:grey");
|
||||
description->setWordWrap(true);
|
||||
description->setVisible(false);
|
||||
vlayout->addWidget(description);
|
||||
|
||||
connect(title_label, &QPushButton::clicked, [=]() {
|
||||
if (!description->isVisible()) {
|
||||
emit showDescription();
|
||||
}
|
||||
description->setVisible(!description->isVisible());
|
||||
});
|
||||
}
|
||||
|
||||
setLayout(vlayout);
|
||||
setStyleSheet("background-color: transparent;");
|
||||
}
|
||||
|
||||
void AbstractControl::hideEvent(QHideEvent *e){
|
||||
if(description != nullptr){
|
||||
description->hide();
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/ui/qt/widgets/toggle.h"
|
||||
|
||||
QFrame *horizontal_line(QWidget *parent = nullptr);
|
||||
|
||||
class AbstractControl : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
void setDescription(const QString &desc) {
|
||||
if(description) description->setText(desc);
|
||||
}
|
||||
|
||||
signals:
|
||||
void showDescription();
|
||||
|
||||
protected:
|
||||
AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
|
||||
void hideEvent(QHideEvent *e) override;
|
||||
|
||||
QSize minimumSizeHint() const override {
|
||||
QSize size = QFrame::minimumSizeHint();
|
||||
size.setHeight(120);
|
||||
return size;
|
||||
};
|
||||
|
||||
QHBoxLayout *hlayout;
|
||||
QPushButton *title_label;
|
||||
QLabel *description = nullptr;
|
||||
};
|
||||
|
||||
// widget to display a value
|
||||
class LabelControl : public AbstractControl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LabelControl(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr) : AbstractControl(title, desc, "", parent) {
|
||||
label.setText(text);
|
||||
label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
hlayout->addWidget(&label);
|
||||
}
|
||||
void setText(const QString &text) { label.setText(text); }
|
||||
|
||||
private:
|
||||
QLabel label;
|
||||
};
|
||||
|
||||
// widget for a button with a label
|
||||
class ButtonControl : public AbstractControl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
template <typename Functor>
|
||||
ButtonControl(const QString &title, const QString &text, const QString &desc, Functor functor, const QString &icon = "", QWidget *parent = nullptr) : AbstractControl(title, desc, icon, parent) {
|
||||
btn.setText(text);
|
||||
btn.setStyleSheet(R"(
|
||||
QPushButton {
|
||||
padding: 0;
|
||||
border-radius: 50px;
|
||||
font-size: 35px;
|
||||
font-weight: 500;
|
||||
color: #E4E4E4;
|
||||
background-color: #393939;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
color: #33E4E4E4;
|
||||
}
|
||||
)");
|
||||
btn.setFixedSize(250, 100);
|
||||
QObject::connect(&btn, &QPushButton::released, functor);
|
||||
hlayout->addWidget(&btn);
|
||||
}
|
||||
void setText(const QString &text) { btn.setText(text); }
|
||||
|
||||
public slots:
|
||||
void setEnabled(bool enabled) {
|
||||
btn.setEnabled(enabled);
|
||||
};
|
||||
|
||||
private:
|
||||
QPushButton btn;
|
||||
};
|
||||
|
||||
class ToggleControl : public AbstractControl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToggleControl(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControl(title, desc, icon, parent) {
|
||||
toggle.setFixedSize(150, 100);
|
||||
if (state) {
|
||||
toggle.togglePosition();
|
||||
}
|
||||
hlayout->addWidget(&toggle);
|
||||
QObject::connect(&toggle, &Toggle::stateChanged, this, &ToggleControl::toggleFlipped);
|
||||
}
|
||||
|
||||
void setEnabled(bool enabled) { toggle.setEnabled(enabled); }
|
||||
|
||||
signals:
|
||||
void toggleFlipped(bool state);
|
||||
|
||||
protected:
|
||||
Toggle toggle;
|
||||
};
|
||||
|
||||
// widget to toggle params
|
||||
class ParamControl : public ToggleControl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) {
|
||||
if (params.getBool(param.toStdString().c_str())) {
|
||||
toggle.togglePosition();
|
||||
}
|
||||
QObject::connect(this, &ToggleControl::toggleFlipped, [=](bool state) {
|
||||
params.putBool(param.toStdString().c_str(), state);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
Params params;
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "drive_stats.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
|
||||
const double MILE_TO_KM = 1.60934;
|
||||
|
||||
static QLayout* build_stat_layout(QLabel** metric, const QString& name) {
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
*metric = new QLabel("0");
|
||||
(*metric)->setStyleSheet("font-size: 80px; font-weight: 600;");
|
||||
layout->addWidget(*metric, 0, Qt::AlignLeft);
|
||||
|
||||
QLabel* label = new QLabel(name);
|
||||
label->setStyleSheet("font-size: 45px; font-weight: 500;");
|
||||
layout->addWidget(label, 0, Qt::AlignLeft);
|
||||
return layout;
|
||||
}
|
||||
|
||||
void DriveStats::parseResponse(const QString& response) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qDebug() << "JSON Parse failed on getting past drives statistics";
|
||||
return;
|
||||
}
|
||||
|
||||
auto update = [](const QJsonObject &obj, StatsLabels& labels, bool metric) {
|
||||
labels.routes->setText(QString::number((int)obj["routes"].toDouble()));
|
||||
labels.distance->setText(QString::number(int(obj["distance"].toDouble() * (metric ? MILE_TO_KM : 1))));
|
||||
labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60)));
|
||||
};
|
||||
|
||||
QJsonObject json = doc.object();
|
||||
update(json["all"].toObject(), all_, metric);
|
||||
update(json["week"].toObject(), week_, metric);
|
||||
}
|
||||
|
||||
DriveStats::DriveStats(QWidget* parent) : QWidget(parent) {
|
||||
metric = Params().getBool("IsMetric");
|
||||
QString distance_unit = metric ? "KM" : "MILES";
|
||||
|
||||
auto add_stats_layouts = [&](QGridLayout* gl, StatsLabels& labels, int row, const QString &distance_unit) {
|
||||
gl->addLayout(build_stat_layout(&labels.routes, "DRIVES"), row, 0, 3, 1);
|
||||
gl->addLayout(build_stat_layout(&labels.distance, distance_unit), row, 1, 3, 1);
|
||||
gl->addLayout(build_stat_layout(&labels.hours, "HOURS"), row, 2, 3, 1);
|
||||
};
|
||||
|
||||
QGridLayout* gl = new QGridLayout(this);
|
||||
gl->setMargin(0);
|
||||
|
||||
gl->addWidget(new QLabel("ALL TIME"), 0, 0, 1, 3);
|
||||
add_stats_layouts(gl, all_, 1, distance_unit);
|
||||
|
||||
gl->addWidget(new QLabel("PAST WEEK"), 6, 0, 1, 3);
|
||||
add_stats_layouts(gl, week_, 7, distance_unit);
|
||||
|
||||
QString dongleId = QString::fromStdString(Params().get("DongleId"));
|
||||
QString url = "https://api.commadotai.com/v1.1/devices/" + dongleId + "/stats";
|
||||
RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_DriveStats", 30);
|
||||
QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &DriveStats::parseResponse);
|
||||
|
||||
setLayout(gl);
|
||||
setStyleSheet(R"(QLabel {font-size: 48px; font-weight: 500;})");
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class DriveStats : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DriveStats(QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
bool metric;
|
||||
struct StatsLabels {
|
||||
QLabel *routes, *distance, *hours;
|
||||
} all_, week_;
|
||||
|
||||
private slots:
|
||||
void parseResponse(const QString &response);
|
||||
};
|
||||
@@ -1,181 +0,0 @@
|
||||
#include "input.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
|
||||
InputDialog::InputDialog(const QString &prompt_text, QWidget *parent) : QDialog(parent) {
|
||||
layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(50, 50, 50, 50);
|
||||
layout->setSpacing(20);
|
||||
|
||||
// build header
|
||||
QHBoxLayout *header_layout = new QHBoxLayout();
|
||||
|
||||
label = new QLabel(prompt_text, this);
|
||||
label->setStyleSheet(R"(font-size: 75px; font-weight: 500;)");
|
||||
header_layout->addWidget(label, 1, Qt::AlignLeft);
|
||||
|
||||
QPushButton* cancel_btn = new QPushButton("Cancel");
|
||||
cancel_btn->setStyleSheet(R"(
|
||||
padding: 30px;
|
||||
padding-right: 45px;
|
||||
padding-left: 45px;
|
||||
border-radius: 7px;
|
||||
font-size: 45px;
|
||||
background-color: #444444;
|
||||
)");
|
||||
header_layout->addWidget(cancel_btn, 0, Qt::AlignRight);
|
||||
QObject::connect(cancel_btn, &QPushButton::released, this, &InputDialog::reject);
|
||||
QObject::connect(cancel_btn, &QPushButton::released, this, &InputDialog::cancel);
|
||||
|
||||
layout->addLayout(header_layout);
|
||||
|
||||
// text box
|
||||
layout->addSpacing(20);
|
||||
line = new QLineEdit();
|
||||
line->setStyleSheet(R"(
|
||||
border: none;
|
||||
background-color: #444444;
|
||||
font-size: 80px;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
)");
|
||||
layout->addWidget(line, 1, Qt::AlignTop);
|
||||
|
||||
k = new Keyboard(this);
|
||||
QObject::connect(k, &Keyboard::emitButton, this, &InputDialog::handleInput);
|
||||
layout->addWidget(k, 2, Qt::AlignBottom);
|
||||
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
)");
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
QString InputDialog::getText(const QString &prompt, int minLength) {
|
||||
InputDialog d = InputDialog(prompt);
|
||||
d.setMinLength(minLength);
|
||||
const int ret = d.exec();
|
||||
return ret ? d.text() : QString();
|
||||
}
|
||||
|
||||
QString InputDialog::text() {
|
||||
return line->text();
|
||||
}
|
||||
|
||||
int InputDialog::exec() {
|
||||
setMainWindow(this);
|
||||
return QDialog::exec();
|
||||
}
|
||||
|
||||
void InputDialog::show(){
|
||||
setMainWindow(this);
|
||||
}
|
||||
|
||||
void InputDialog::handleInput(const QString &s) {
|
||||
if (!QString::compare(s,"⌫")) {
|
||||
line->backspace();
|
||||
}
|
||||
|
||||
if (!QString::compare(s,"⏎")) {
|
||||
if (line->text().length() >= minLength){
|
||||
done(QDialog::Accepted);
|
||||
emitText(line->text());
|
||||
} else {
|
||||
setMessage("Need at least "+QString::number(minLength)+" characters!", false);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QString> control_buttons {"⇧", "↑", "ABC", "⏎", "#+=", "⌫", "123"};
|
||||
for(QString c : control_buttons) {
|
||||
if (!QString::compare(s, c)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
line->insert(s.left(1));
|
||||
}
|
||||
|
||||
void InputDialog::setMessage(const QString &message, bool clearInputField){
|
||||
label->setText(message);
|
||||
if (clearInputField){
|
||||
line->setText("");
|
||||
}
|
||||
}
|
||||
|
||||
void InputDialog::setMinLength(int length){
|
||||
minLength = length;
|
||||
}
|
||||
|
||||
ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text,
|
||||
QWidget *parent):QDialog(parent) {
|
||||
setWindowFlags(Qt::Popup);
|
||||
layout = new QVBoxLayout();
|
||||
layout->setMargin(25);
|
||||
|
||||
prompt = new QLabel(prompt_text, this);
|
||||
prompt->setWordWrap(true);
|
||||
prompt->setAlignment(Qt::AlignHCenter);
|
||||
prompt->setStyleSheet(R"(font-size: 55px; font-weight: 400;)");
|
||||
layout->addWidget(prompt, 1, Qt::AlignTop | Qt::AlignHCenter);
|
||||
|
||||
// cancel + confirm buttons
|
||||
QHBoxLayout *btn_layout = new QHBoxLayout();
|
||||
btn_layout->setSpacing(20);
|
||||
btn_layout->addStretch(1);
|
||||
layout->addLayout(btn_layout);
|
||||
|
||||
if (cancel_text.length()) {
|
||||
QPushButton* cancel_btn = new QPushButton(cancel_text);
|
||||
btn_layout->addWidget(cancel_btn, 0, Qt::AlignRight);
|
||||
QObject::connect(cancel_btn, &QPushButton::released, this, &ConfirmationDialog::reject);
|
||||
}
|
||||
|
||||
if (confirm_text.length()) {
|
||||
QPushButton* confirm_btn = new QPushButton(confirm_text);
|
||||
btn_layout->addWidget(confirm_btn, 0, Qt::AlignRight);
|
||||
QObject::connect(confirm_btn, &QPushButton::released, this, &ConfirmationDialog::accept);
|
||||
}
|
||||
|
||||
setFixedSize(900, 350);
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
color: black;
|
||||
background-color: white;
|
||||
}
|
||||
QPushButton {
|
||||
font-size: 40px;
|
||||
padding: 30px;
|
||||
padding-right: 45px;
|
||||
padding-left: 45px;
|
||||
border-radius: 7px;
|
||||
background-color: #44444400;
|
||||
}
|
||||
)");
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) {
|
||||
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "", parent);
|
||||
return d.exec();
|
||||
}
|
||||
|
||||
bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) {
|
||||
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "Cancel", parent);
|
||||
return d.exec();
|
||||
}
|
||||
|
||||
int ConfirmationDialog::exec() {
|
||||
// TODO: make this work without fullscreen
|
||||
if (Hardware::TICI()) {
|
||||
setMainWindow(this);
|
||||
}
|
||||
return QDialog::exec();
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/widgets/keyboard.h"
|
||||
|
||||
class InputDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InputDialog(const QString &prompt_text, QWidget* parent = 0);
|
||||
static QString getText(const QString &prompt, int minLength = -1);
|
||||
QString text();
|
||||
void setMessage(const QString &message, bool clearInputField = true);
|
||||
void setMinLength(int length);
|
||||
void show();
|
||||
|
||||
private:
|
||||
int minLength;
|
||||
QLineEdit *line;
|
||||
Keyboard *k;
|
||||
QLabel *label;
|
||||
QVBoxLayout *layout;
|
||||
|
||||
public slots:
|
||||
int exec() override;
|
||||
|
||||
private slots:
|
||||
void handleInput(const QString &s);
|
||||
|
||||
signals:
|
||||
void cancel();
|
||||
void emitText(const QString &text);
|
||||
};
|
||||
|
||||
class ConfirmationDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfirmationDialog(const QString &prompt_text, const QString &confirm_text = "Ok",
|
||||
const QString &cancel_text = "Cancel", QWidget* parent = 0);
|
||||
static bool alert(const QString &prompt_text, QWidget *parent = 0);
|
||||
static bool confirm(const QString &prompt_text, QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
QLabel *prompt;
|
||||
QVBoxLayout *layout;
|
||||
|
||||
public slots:
|
||||
int exec() override;
|
||||
};
|
||||
@@ -1,127 +0,0 @@
|
||||
#include "keyboard.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QStackedLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
const int DEFAULT_STRETCH = 1;
|
||||
const int SPACEBAR_STRETCH = 3;
|
||||
|
||||
KeyboardLayout::KeyboardLayout(QWidget* parent, const std::vector<QVector<QString>>& layout) : QWidget(parent) {
|
||||
QVBoxLayout* vlayout = new QVBoxLayout;
|
||||
vlayout->setMargin(0);
|
||||
vlayout->setSpacing(35);
|
||||
|
||||
QButtonGroup* btn_group = new QButtonGroup(this);
|
||||
QObject::connect(btn_group, SIGNAL(buttonClicked(QAbstractButton*)), parent, SLOT(handleButton(QAbstractButton*)));
|
||||
|
||||
for (const auto &s : layout) {
|
||||
QHBoxLayout *hlayout = new QHBoxLayout;
|
||||
hlayout->setSpacing(25);
|
||||
|
||||
if (vlayout->count() == 1) {
|
||||
hlayout->addSpacing(90);
|
||||
}
|
||||
|
||||
for (const QString &p : s) {
|
||||
QPushButton* btn = new QPushButton(p);
|
||||
btn->setFixedHeight(135);
|
||||
btn_group->addButton(btn);
|
||||
hlayout->addWidget(btn, p == QString(" ") ? SPACEBAR_STRETCH : DEFAULT_STRETCH);
|
||||
}
|
||||
|
||||
if (vlayout->count() == 1) {
|
||||
hlayout->addSpacing(90);
|
||||
}
|
||||
|
||||
vlayout->addLayout(hlayout);
|
||||
}
|
||||
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
outline: none;
|
||||
}
|
||||
QPushButton {
|
||||
font-size: 65px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border-radius: 30px;
|
||||
color: #dddddd;
|
||||
background-color: #444444;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #000000;
|
||||
}
|
||||
)");
|
||||
setLayout(vlayout);
|
||||
}
|
||||
|
||||
Keyboard::Keyboard(QWidget *parent) : QFrame(parent) {
|
||||
main_layout = new QStackedLayout;
|
||||
main_layout->setMargin(0);
|
||||
|
||||
// lowercase
|
||||
std::vector<QVector<QString>> lowercase = {
|
||||
{"q","w","e","r","t","y","u","i","o","p"},
|
||||
{"a","s","d","f","g","h","j","k","l"},
|
||||
{"⇧","z","x","c","v","b","n","m","⌫"},
|
||||
{"123"," ","⏎"},
|
||||
};
|
||||
main_layout->addWidget(new KeyboardLayout(this, lowercase));
|
||||
|
||||
// uppercase
|
||||
std::vector<QVector<QString>> uppercase = {
|
||||
{"Q","W","E","R","T","Y","U","I","O","P"},
|
||||
{"A","S","D","F","G","H","J","K","L"},
|
||||
{"↑","Z","X","C","V","B","N","M","⌫"},
|
||||
{"123"," ","⏎"},
|
||||
};
|
||||
main_layout->addWidget(new KeyboardLayout(this, uppercase));
|
||||
|
||||
// numbers + specials
|
||||
std::vector<QVector<QString>> numbers = {
|
||||
{"1","2","3","4","5","6","7","8","9","0"},
|
||||
{"-","/",":",";","(",")","$","&&","@","\""},
|
||||
{"#+=",".",",","?","!","`","⌫"},
|
||||
{"ABC"," ","⏎"},
|
||||
};
|
||||
main_layout->addWidget(new KeyboardLayout(this, numbers));
|
||||
|
||||
// extra specials
|
||||
std::vector<QVector<QString>> specials = {
|
||||
{"[","]","{","}","#","%","^","*","+","="},
|
||||
{"_","\\","|","~","<",">","€","£","¥","•"},
|
||||
{"123",".",",","?","!","`","⌫"},
|
||||
{"ABC"," ","⏎"},
|
||||
};
|
||||
main_layout->addWidget(new KeyboardLayout(this, specials));
|
||||
|
||||
setLayout(main_layout);
|
||||
main_layout->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void Keyboard::handleButton(QAbstractButton* m_button) {
|
||||
QString id = m_button->text();
|
||||
if (!QString::compare(m_button->text(), "↑") || !QString::compare(m_button->text(), "ABC")) {
|
||||
main_layout->setCurrentIndex(0);
|
||||
}
|
||||
if (!QString::compare(m_button->text(), "⇧")) {
|
||||
main_layout->setCurrentIndex(1);
|
||||
}
|
||||
if (!QString::compare(m_button->text(), "123")) {
|
||||
main_layout->setCurrentIndex(2);
|
||||
}
|
||||
if (!QString::compare(m_button->text(), "#+=")) {
|
||||
main_layout->setCurrentIndex(3);
|
||||
}
|
||||
if (!QString::compare(m_button->text(), "⏎")) {
|
||||
main_layout->setCurrentIndex(0);
|
||||
}
|
||||
if ("A" <= id && id <= "Z") {
|
||||
main_layout->setCurrentIndex(0);
|
||||
}
|
||||
emit emitButton(m_button->text());
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QFrame>
|
||||
#include <QStackedLayout>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
class KeyboardLayout : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KeyboardLayout(QWidget* parent, const std::vector<QVector<QString>>& layout);
|
||||
};
|
||||
|
||||
class Keyboard : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Keyboard(QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
QStackedLayout* main_layout;
|
||||
|
||||
private slots:
|
||||
void handleButton(QAbstractButton* m_button);
|
||||
|
||||
signals:
|
||||
void emitButton(const QString &s);
|
||||
};
|
||||
@@ -1,114 +0,0 @@
|
||||
#include "offroad_alerts.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
|
||||
OffroadAlert::OffroadAlert(QWidget* parent) : QFrame(parent) {
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->setMargin(50);
|
||||
layout->setSpacing(30);
|
||||
|
||||
QWidget *alerts_widget = new QWidget(this);
|
||||
alerts_layout = new QVBoxLayout;
|
||||
alerts_layout->setMargin(0);
|
||||
alerts_layout->setSpacing(30);
|
||||
alerts_widget->setLayout(alerts_layout);
|
||||
alerts_widget->setStyleSheet("background-color: transparent;");
|
||||
|
||||
// release notes
|
||||
releaseNotes.setWordWrap(true);
|
||||
releaseNotes.setVisible(false);
|
||||
releaseNotes.setStyleSheet("font-size: 48px;");
|
||||
releaseNotes.setAlignment(Qt::AlignTop);
|
||||
|
||||
releaseNotesScroll = new ScrollView(&releaseNotes, this);
|
||||
layout->addWidget(releaseNotesScroll);
|
||||
|
||||
alertsScroll = new ScrollView(alerts_widget, this);
|
||||
layout->addWidget(alertsScroll);
|
||||
|
||||
// bottom footer, dismiss + reboot buttons
|
||||
QHBoxLayout *footer_layout = new QHBoxLayout();
|
||||
layout->addLayout(footer_layout);
|
||||
|
||||
QPushButton *dismiss_btn = new QPushButton("Dismiss");
|
||||
dismiss_btn->setFixedSize(400, 125);
|
||||
footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
|
||||
QObject::connect(dismiss_btn, &QPushButton::released, this, &OffroadAlert::closeAlerts);
|
||||
|
||||
rebootBtn.setText("Reboot and Update");
|
||||
rebootBtn.setFixedSize(600, 125);
|
||||
rebootBtn.setVisible(false);
|
||||
footer_layout->addWidget(&rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
QObject::connect(&rebootBtn, &QPushButton::released, [=]() { Hardware::reboot(); });
|
||||
|
||||
setLayout(layout);
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
font-size: 48px;
|
||||
color: white;
|
||||
}
|
||||
QFrame {
|
||||
border-radius: 30px;
|
||||
background-color: #393939;
|
||||
}
|
||||
QPushButton {
|
||||
color: black;
|
||||
font-weight: 500;
|
||||
border-radius: 30px;
|
||||
background-color: white;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void OffroadAlert::refresh() {
|
||||
if (alerts.empty()) {
|
||||
// setup labels for each alert
|
||||
QString json = QString::fromStdString(util::read_file("../controls/lib/alerts_offroad.json"));
|
||||
QJsonObject obj = QJsonDocument::fromJson(json.toUtf8()).object();
|
||||
for (auto &k : obj.keys()) {
|
||||
QLabel *l = new QLabel(this);
|
||||
alerts[k.toStdString()] = l;
|
||||
int severity = obj[k].toObject()["severity"].toInt();
|
||||
|
||||
l->setMargin(60);
|
||||
l->setWordWrap(true);
|
||||
l->setStyleSheet("background-color: " + QString(severity ? "#E22C2C" : "#292929"));
|
||||
l->setVisible(false);
|
||||
alerts_layout->addWidget(l);
|
||||
}
|
||||
alerts_layout->addStretch(1);
|
||||
}
|
||||
|
||||
updateAlerts();
|
||||
|
||||
rebootBtn.setVisible(updateAvailable);
|
||||
releaseNotesScroll->setVisible(updateAvailable);
|
||||
releaseNotes.setText(QString::fromStdString(params.get("ReleaseNotes")));
|
||||
|
||||
alertsScroll->setVisible(!updateAvailable);
|
||||
for (const auto& [k, label] : alerts) {
|
||||
label->setVisible(!label->text().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void OffroadAlert::updateAlerts() {
|
||||
alertCount = 0;
|
||||
updateAvailable = params.getBool("UpdateAvailable");
|
||||
for (const auto& [key, label] : alerts) {
|
||||
auto bytes = params.get(key.c_str());
|
||||
if (bytes.size()) {
|
||||
QJsonDocument doc_par = QJsonDocument::fromJson(QByteArray(bytes.data(), bytes.size()));
|
||||
QJsonObject obj = doc_par.object();
|
||||
label->setText(obj.value("text").toString());
|
||||
alertCount++;
|
||||
} else {
|
||||
label->setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
|
||||
class OffroadAlert : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OffroadAlert(QWidget *parent = 0);
|
||||
int alertCount = 0;
|
||||
bool updateAvailable;
|
||||
|
||||
private:
|
||||
void updateAlerts();
|
||||
|
||||
Params params;
|
||||
std::map<std::string, QLabel*> alerts;
|
||||
|
||||
QLabel releaseNotes;
|
||||
QPushButton rebootBtn;
|
||||
ScrollView *alertsScroll;
|
||||
ScrollView *releaseNotesScroll;
|
||||
QVBoxLayout *alerts_layout;
|
||||
|
||||
signals:
|
||||
void closeAlerts();
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "scrollview.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
|
||||
ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent){
|
||||
setWidget(w);
|
||||
setWidgetResizable(true);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setStyleSheet("ScrollView { background-color:transparent; }");
|
||||
|
||||
QString style = R"(
|
||||
QScrollBar:vertical {
|
||||
border: none;
|
||||
background: transparent;
|
||||
width:10px;
|
||||
margin: 0;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
min-height: 0px;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||||
height: 0px;
|
||||
}
|
||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
||||
background: none;
|
||||
}
|
||||
)";
|
||||
|
||||
verticalScrollBar()->setStyleSheet(style);
|
||||
horizontalScrollBar()->setStyleSheet(style);
|
||||
|
||||
QScroller *scroller = QScroller::scroller(this->viewport());
|
||||
QScrollerProperties sp = scroller->scrollerProperties();
|
||||
|
||||
sp.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QVariant::fromValue<QScrollerProperties::OvershootPolicy>(QScrollerProperties::OvershootAlwaysOff));
|
||||
sp.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue<QScrollerProperties::OvershootPolicy>(QScrollerProperties::OvershootAlwaysOff));
|
||||
|
||||
scroller->grabGesture(this->viewport(), QScroller::LeftMouseButtonGesture);
|
||||
scroller->setScrollerProperties(sp);
|
||||
}
|
||||
|
||||
void ScrollView::hideEvent(QHideEvent *e){
|
||||
verticalScrollBar()->setValue(0);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QScrollArea>
|
||||
#include <QScroller>
|
||||
|
||||
class ScrollView : public QScrollArea {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ScrollView(QWidget *w = nullptr, QWidget *parent = nullptr);
|
||||
protected:
|
||||
void hideEvent(QHideEvent *e) override;
|
||||
};
|
||||
@@ -1,283 +0,0 @@
|
||||
#include "setup.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QrCode.hpp>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
|
||||
using qrcodegen::QrCode;
|
||||
|
||||
PairingQRWidget::PairingQRWidget(QWidget* parent) : QWidget(parent) {
|
||||
qrCode = new QLabel;
|
||||
qrCode->setScaledContents(true);
|
||||
QVBoxLayout* v = new QVBoxLayout;
|
||||
v->addWidget(qrCode, 0, Qt::AlignCenter);
|
||||
setLayout(v);
|
||||
|
||||
QTimer* timer = new QTimer(this);
|
||||
timer->start(30 * 1000);
|
||||
connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh);
|
||||
}
|
||||
|
||||
void PairingQRWidget::showEvent(QShowEvent *event){
|
||||
refresh();
|
||||
}
|
||||
|
||||
void PairingQRWidget::refresh(){
|
||||
Params params;
|
||||
QString IMEI = QString::fromStdString(params.get("IMEI"));
|
||||
QString serial = QString::fromStdString(params.get("HardwareSerial"));
|
||||
|
||||
if (std::min(IMEI.length(), serial.length()) <= 5) {
|
||||
qrCode->setText("Error getting serial: contact support");
|
||||
qrCode->setWordWrap(true);
|
||||
qrCode->setStyleSheet(R"(font-size: 60px;)");
|
||||
return;
|
||||
}
|
||||
QString pairToken = CommaApi::create_jwt({{"pair", true}});
|
||||
|
||||
QString qrString = IMEI + "--" + serial + "--" + pairToken;
|
||||
this->updateQrCode(qrString);
|
||||
}
|
||||
|
||||
void PairingQRWidget::updateQrCode(QString text) {
|
||||
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
|
||||
qint32 sz = qr.getSize();
|
||||
// make the image larger so we can have a white border
|
||||
QImage im(sz + 2, sz + 2, QImage::Format_RGB32);
|
||||
QRgb black = qRgb(0, 0, 0);
|
||||
QRgb white = qRgb(255, 255, 255);
|
||||
|
||||
for (int y = 0; y < sz + 2; y++) {
|
||||
for (int x = 0; x < sz + 2; x++) {
|
||||
im.setPixel(x, y, white);
|
||||
}
|
||||
}
|
||||
for (int y = 0; y < sz; y++) {
|
||||
for (int x = 0; x < sz; x++) {
|
||||
im.setPixel(x + 1, y + 1, qr.getModule(x, y) ? black : white);
|
||||
}
|
||||
}
|
||||
// Integer division to prevent anti-aliasing
|
||||
int approx500 = (500 / (sz + 2)) * (sz + 2);
|
||||
qrCode->setPixmap(QPixmap::fromImage(im.scaled(approx500, approx500, Qt::KeepAspectRatio, Qt::FastTransformation), Qt::MonoOnly));
|
||||
qrCode->setFixedSize(approx500, approx500);
|
||||
}
|
||||
|
||||
PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
|
||||
mainLayout = new QVBoxLayout;
|
||||
mainLayout->setMargin(30);
|
||||
|
||||
QLabel* commaPrime = new QLabel("COMMA PRIME");
|
||||
mainLayout->addWidget(commaPrime, 0, Qt::AlignTop);
|
||||
|
||||
username = new QLabel();
|
||||
username->setStyleSheet("font-size: 55px;"); // TODO: fit width
|
||||
mainLayout->addWidget(username, 0, Qt::AlignTop);
|
||||
|
||||
mainLayout->addSpacing(100);
|
||||
|
||||
QLabel* commaPoints = new QLabel("COMMA POINTS");
|
||||
commaPoints->setStyleSheet(R"(
|
||||
color: #b8b8b8;
|
||||
)");
|
||||
mainLayout->addWidget(commaPoints, 0, Qt::AlignTop);
|
||||
|
||||
points = new QLabel();
|
||||
mainLayout->addWidget(points, 0, Qt::AlignTop);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setStyleSheet(R"(
|
||||
QLabel {
|
||||
font-size: 70px;
|
||||
font-weight: 500;
|
||||
}
|
||||
)");
|
||||
|
||||
// set up API requests
|
||||
QString dongleId = QString::fromStdString(Params().get("DongleId"));
|
||||
if (!dongleId.length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString url = "https://api.commadotai.com/v1/devices/" + dongleId + "/owner";
|
||||
RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_Owner", 6);
|
||||
QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &PrimeUserWidget::replyFinished);
|
||||
}
|
||||
|
||||
void PrimeUserWidget::replyFinished(const QString &response) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qDebug() << "JSON Parse failed on getting username and points";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json = doc.object();
|
||||
QString points_str = QString::number(json["points"].toInt());
|
||||
QString username_str = json["username"].toString();
|
||||
if (username_str.length()) {
|
||||
username_str = "@" + username_str;
|
||||
}
|
||||
|
||||
username->setText(username_str);
|
||||
points->setText(points_str);
|
||||
}
|
||||
|
||||
PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* vlayout = new QVBoxLayout;
|
||||
vlayout->setMargin(30);
|
||||
vlayout->setSpacing(15);
|
||||
|
||||
vlayout->addWidget(new QLabel("Upgrade now"), 1, Qt::AlignTop);
|
||||
|
||||
QLabel* description = new QLabel("Become a comma prime member at my.comma.ai and get premium features!");
|
||||
description->setStyleSheet(R"(
|
||||
font-size: 50px;
|
||||
color: #b8b8b8;
|
||||
)");
|
||||
description->setWordWrap(true);
|
||||
vlayout->addWidget(description, 2, Qt::AlignTop);
|
||||
|
||||
QVector<QString> features = {"✓ REMOTE ACCESS", "✓ 14 DAYS OF STORAGE", "✓ DEVELOPER PERKS"};
|
||||
for (auto &f: features) {
|
||||
QLabel* feature = new QLabel(f);
|
||||
feature->setStyleSheet(R"(font-size: 40px;)");
|
||||
vlayout->addWidget(feature, 0, Qt::AlignBottom);
|
||||
}
|
||||
|
||||
setLayout(vlayout);
|
||||
}
|
||||
|
||||
|
||||
SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
|
||||
mainLayout = new QStackedWidget;
|
||||
|
||||
// Unpaired, registration prompt layout
|
||||
|
||||
QVBoxLayout* finishRegistationLayout = new QVBoxLayout;
|
||||
finishRegistationLayout->setMargin(30);
|
||||
|
||||
QLabel* registrationDescription = new QLabel("Pair your device with the comma connect app");
|
||||
registrationDescription->setWordWrap(true);
|
||||
registrationDescription->setAlignment(Qt::AlignCenter);
|
||||
registrationDescription->setStyleSheet(R"(
|
||||
font-size: 55px;
|
||||
font-weight: 400;
|
||||
)");
|
||||
|
||||
finishRegistationLayout->addWidget(registrationDescription);
|
||||
|
||||
QPushButton* finishButton = new QPushButton("Finish setup");
|
||||
finishButton->setFixedHeight(200);
|
||||
finishButton->setStyleSheet(R"(
|
||||
border-radius: 30px;
|
||||
font-size: 55px;
|
||||
font-weight: 500;
|
||||
background: #585858;
|
||||
)");
|
||||
finishRegistationLayout->addWidget(finishButton);
|
||||
QObject::connect(finishButton, &QPushButton::released, this, &SetupWidget::showQrCode);
|
||||
|
||||
QWidget* finishRegistration = new QWidget;
|
||||
finishRegistration->setLayout(finishRegistationLayout);
|
||||
mainLayout->addWidget(finishRegistration);
|
||||
|
||||
// Pairing QR code layout
|
||||
|
||||
QVBoxLayout* qrLayout = new QVBoxLayout;
|
||||
|
||||
qrLayout->addSpacing(30);
|
||||
QLabel* qrLabel = new QLabel("Scan with comma connect!");
|
||||
qrLabel->setWordWrap(true);
|
||||
qrLabel->setAlignment(Qt::AlignHCenter);
|
||||
qrLabel->setStyleSheet(R"(
|
||||
font-size: 55px;
|
||||
font-weight: 400;
|
||||
)");
|
||||
qrLayout->addWidget(qrLabel, 0, Qt::AlignTop);
|
||||
|
||||
qrLayout->addWidget(new PairingQRWidget, 1);
|
||||
|
||||
QWidget* q = new QWidget;
|
||||
q->setLayout(qrLayout);
|
||||
mainLayout->addWidget(q);
|
||||
|
||||
primeAd = new PrimeAdWidget;
|
||||
mainLayout->addWidget(primeAd);
|
||||
|
||||
primeUser = new PrimeUserWidget;
|
||||
mainLayout->addWidget(primeUser);
|
||||
|
||||
mainLayout->setCurrentWidget(primeAd);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(mainLayout);
|
||||
setLayout(layout);
|
||||
|
||||
setStyleSheet(R"(
|
||||
SetupWidget {
|
||||
background-color: #292929;
|
||||
}
|
||||
* {
|
||||
font-size: 90px;
|
||||
font-weight: 500;
|
||||
border-radius: 40px;
|
||||
}
|
||||
)");
|
||||
|
||||
// Retain size while hidden
|
||||
QSizePolicy sp_retain = sizePolicy();
|
||||
sp_retain.setRetainSizeWhenHidden(true);
|
||||
setSizePolicy(sp_retain);
|
||||
|
||||
// set up API requests
|
||||
QString dongleId = QString::fromStdString(Params().get("DongleId"));
|
||||
QString url = "https://api.commadotai.com/v1.1/devices/" + dongleId + "/";
|
||||
RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5);
|
||||
|
||||
QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &SetupWidget::replyFinished);
|
||||
QObject::connect(repeater, &RequestRepeater::failedResponse, this, &SetupWidget::parseError);
|
||||
hide(); // Only show when first request comes back
|
||||
}
|
||||
|
||||
void SetupWidget::parseError(const QString &response) {
|
||||
show();
|
||||
showQr = false;
|
||||
mainLayout->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void SetupWidget::showQrCode(){
|
||||
showQr = true;
|
||||
mainLayout->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void SetupWidget::replyFinished(const QString &response) {
|
||||
show();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qDebug() << "JSON Parse failed on getting pairing and prime status";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json = doc.object();
|
||||
bool is_paired = json["is_paired"].toBool();
|
||||
bool is_prime = json["prime"].toBool();
|
||||
|
||||
if (!is_paired) {
|
||||
mainLayout->setCurrentIndex(showQr);
|
||||
} else if (!is_prime) {
|
||||
showQr = false;
|
||||
mainLayout->setCurrentWidget(primeAd);
|
||||
} else {
|
||||
showQr = false;
|
||||
mainLayout->setCurrentWidget(primeUser);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
|
||||
class PairingQRWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PairingQRWidget(QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
QLabel* qrCode;
|
||||
void updateQrCode(QString text);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
};
|
||||
|
||||
class PrimeUserWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PrimeUserWidget(QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
QVBoxLayout* mainLayout;
|
||||
QLabel* username;
|
||||
QLabel* points;
|
||||
CommaApi* api;
|
||||
|
||||
private slots:
|
||||
void replyFinished(const QString &response);
|
||||
};
|
||||
|
||||
class PrimeAdWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PrimeAdWidget(QWidget* parent = 0);
|
||||
};
|
||||
|
||||
class SetupWidget : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SetupWidget(QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
QStackedWidget* mainLayout;
|
||||
CommaApi* api;
|
||||
PrimeAdWidget *primeAd;
|
||||
PrimeUserWidget *primeUser;
|
||||
bool showQr = false;
|
||||
|
||||
private slots:
|
||||
void parseError(const QString &response);
|
||||
void replyFinished(const QString &response);
|
||||
void showQrCode();
|
||||
};
|
||||
@@ -1,83 +0,0 @@
|
||||
#include "ssh_keys.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
#include "selfdrive/ui/qt/widgets/input.h"
|
||||
|
||||
SshControl::SshControl() : AbstractControl("SSH Keys", "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.", "") {
|
||||
|
||||
// setup widget
|
||||
hlayout->addStretch(1);
|
||||
|
||||
username_label.setAlignment(Qt::AlignVCenter);
|
||||
username_label.setStyleSheet("color: #aaaaaa");
|
||||
hlayout->addWidget(&username_label);
|
||||
|
||||
btn.setStyleSheet(R"(
|
||||
padding: 0;
|
||||
border-radius: 50px;
|
||||
font-size: 35px;
|
||||
font-weight: 500;
|
||||
color: #E4E4E4;
|
||||
background-color: #393939;
|
||||
)");
|
||||
btn.setFixedSize(250, 100);
|
||||
hlayout->addWidget(&btn);
|
||||
|
||||
QObject::connect(&btn, &QPushButton::released, [=]() {
|
||||
if (btn.text() == "ADD") {
|
||||
QString username = InputDialog::getText("Enter your GitHub username");
|
||||
if (username.length() > 0) {
|
||||
btn.setText("LOADING");
|
||||
btn.setEnabled(false);
|
||||
getUserKeys(username);
|
||||
}
|
||||
} else {
|
||||
params.remove("GithubUsername");
|
||||
params.remove("GithubSshKeys");
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SshControl::refresh() {
|
||||
QString param = QString::fromStdString(params.get("GithubSshKeys"));
|
||||
if (param.length()) {
|
||||
username_label.setText(QString::fromStdString(params.get("GithubUsername")));
|
||||
btn.setText("REMOVE");
|
||||
} else {
|
||||
username_label.setText("");
|
||||
btn.setText("ADD");
|
||||
}
|
||||
btn.setEnabled(true);
|
||||
}
|
||||
|
||||
void SshControl::getUserKeys(const QString &username) {
|
||||
HttpRequest *request = new HttpRequest(this, "https://github.com/" + username + ".keys", "", false);
|
||||
QObject::connect(request, &HttpRequest::receivedResponse, [=](const QString &resp) {
|
||||
if (!resp.isEmpty()) {
|
||||
Params params;
|
||||
params.put("GithubUsername", username.toStdString());
|
||||
params.put("GithubSshKeys", resp.toStdString());
|
||||
} else {
|
||||
ConfirmationDialog::alert("Username '" + username + "' has no keys on GitHub");
|
||||
}
|
||||
refresh();
|
||||
request->deleteLater();
|
||||
});
|
||||
QObject::connect(request, &HttpRequest::failedResponse, [=] {
|
||||
ConfirmationDialog::alert("Username '" + username + "' doesn't exist on GitHub");
|
||||
refresh();
|
||||
request->deleteLater();
|
||||
});
|
||||
QObject::connect(request, &HttpRequest::timeoutResponse, [=] {
|
||||
ConfirmationDialog::alert("Request timed out");
|
||||
refresh();
|
||||
request->deleteLater();
|
||||
});
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
|
||||
// SSH enable toggle
|
||||
class SshToggle : public ToggleControl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SshToggle() : ToggleControl("Enable SSH", "", "", Hardware::get_ssh_enabled()) {
|
||||
QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) {
|
||||
Hardware::set_ssh_enabled(state);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// SSH key management widget
|
||||
class SshControl : public AbstractControl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SshControl();
|
||||
|
||||
private:
|
||||
Params params;
|
||||
|
||||
QPushButton btn;
|
||||
QLabel username_label;
|
||||
|
||||
void refresh();
|
||||
void getUserKeys(const QString &username);
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
#include "toggle.h"
|
||||
|
||||
Toggle::Toggle(QWidget *parent) : QAbstractButton(parent),
|
||||
_height(80),
|
||||
_height_rect(60),
|
||||
on(false),
|
||||
_anim(new QPropertyAnimation(this, "offset_circle", this))
|
||||
{
|
||||
_radius = _height / 2;
|
||||
_x_circle = _radius;
|
||||
_y_circle = _radius;
|
||||
_y_rect = (_height - _height_rect)/2;
|
||||
circleColor = QColor(0xffffff); // placeholder
|
||||
green = QColor(0xffffff); // placeholder
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
void Toggle::paintEvent(QPaintEvent *e) {
|
||||
this->setFixedHeight(_height);
|
||||
QPainter p(this);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// Draw toggle background left
|
||||
p.setBrush(green);
|
||||
p.drawRoundedRect(QRect(0, _y_rect, _x_circle + _radius, _height_rect), _height_rect/2, _height_rect/2);
|
||||
|
||||
// Draw toggle background right
|
||||
p.setBrush(QColor(0x393939));
|
||||
p.drawRoundedRect(QRect(_x_circle - _radius, _y_rect, width() - (_x_circle - _radius), _height_rect), _height_rect/2, _height_rect/2);
|
||||
|
||||
// Draw toggle circle
|
||||
p.setBrush(circleColor);
|
||||
p.drawEllipse(QRectF(_x_circle - _radius, _y_circle - _radius, 2 * _radius, 2 * _radius));
|
||||
}
|
||||
|
||||
void Toggle::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if(!enabled){
|
||||
return;
|
||||
}
|
||||
const int left = _radius;
|
||||
const int right = width() - _radius;
|
||||
if(_x_circle != left && _x_circle != right){
|
||||
//Don't parse touch events, while the animation is running
|
||||
return;
|
||||
}
|
||||
if (e->button() & Qt::LeftButton) {
|
||||
togglePosition();
|
||||
emit stateChanged(on);
|
||||
}
|
||||
}
|
||||
|
||||
void Toggle::togglePosition() {
|
||||
on = !on;
|
||||
const int left = _radius;
|
||||
const int right = width() - _radius;
|
||||
_anim->setStartValue(on ? left + immediateOffset : right - immediateOffset);
|
||||
_anim->setEndValue(on ? right : left);
|
||||
_anim->setDuration(animation_duration);
|
||||
_anim->start();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Toggle::enterEvent(QEvent *e) {
|
||||
QAbstractButton::enterEvent(e);
|
||||
}
|
||||
|
||||
bool Toggle::getEnabled(){
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void Toggle::setEnabled(bool value){
|
||||
enabled = value;
|
||||
if(value){
|
||||
circleColor.setRgb(0xfafafa);
|
||||
green.setRgb(0x33ab4c);
|
||||
}else{
|
||||
circleColor.setRgb(0x888888);
|
||||
green.setRgb(0x227722);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
class Toggle : public QAbstractButton {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int offset_circle READ offset_circle WRITE set_offset_circle CONSTANT)
|
||||
|
||||
public:
|
||||
Toggle(QWidget* parent = nullptr);
|
||||
void togglePosition();
|
||||
bool on;
|
||||
int animation_duration = 150;
|
||||
int immediateOffset = 0;
|
||||
int offset_circle() const {
|
||||
return _x_circle;
|
||||
}
|
||||
|
||||
void set_offset_circle(int o) {
|
||||
_x_circle = o;
|
||||
update();
|
||||
}
|
||||
bool getEnabled();
|
||||
void setEnabled(bool value);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
void mouseReleaseEvent(QMouseEvent*) override;
|
||||
void enterEvent(QEvent*) override;
|
||||
|
||||
private:
|
||||
QColor circleColor;
|
||||
QColor green;
|
||||
bool enabled = true;
|
||||
int _x_circle, _y_circle;
|
||||
int _height, _radius;
|
||||
int _height_rect, _y_rect;
|
||||
QPropertyAnimation *_anim = nullptr;
|
||||
|
||||
signals:
|
||||
void stateChanged(bool new_state);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
#include "window.h"
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
|
||||
main_layout = new QStackedLayout;
|
||||
main_layout->setMargin(0);
|
||||
|
||||
homeWindow = new HomeWindow(this);
|
||||
main_layout->addWidget(homeWindow);
|
||||
QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings);
|
||||
QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings);
|
||||
QObject::connect(&qs, &QUIState::uiUpdate, homeWindow, &HomeWindow::update);
|
||||
QObject::connect(&qs, &QUIState::offroadTransition, homeWindow, &HomeWindow::offroadTransition);
|
||||
QObject::connect(&qs, &QUIState::offroadTransition, homeWindow, &HomeWindow::offroadTransitionSignal);
|
||||
QObject::connect(&device, &Device::displayPowerChanged, homeWindow, &HomeWindow::displayPowerChanged);
|
||||
|
||||
settingsWindow = new SettingsWindow(this);
|
||||
main_layout->addWidget(settingsWindow);
|
||||
QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings);
|
||||
QObject::connect(&qs, &QUIState::offroadTransition, settingsWindow, &SettingsWindow::offroadTransition);
|
||||
QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, this, &MainWindow::reviewTrainingGuide);
|
||||
QObject::connect(settingsWindow, &SettingsWindow::showDriverView, [=] {
|
||||
homeWindow->showDriverView(true);
|
||||
});
|
||||
|
||||
onboardingWindow = new OnboardingWindow(this);
|
||||
onboardingDone = onboardingWindow->isOnboardingDone();
|
||||
main_layout->addWidget(onboardingWindow);
|
||||
|
||||
main_layout->setCurrentWidget(onboardingWindow);
|
||||
QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=](){
|
||||
onboardingDone = true;
|
||||
closeSettings();
|
||||
});
|
||||
onboardingWindow->updateActiveScreen();
|
||||
|
||||
device.setAwake(true, true);
|
||||
QObject::connect(&qs, &QUIState::uiUpdate, &device, &Device::update);
|
||||
QObject::connect(&qs, &QUIState::offroadTransition, this, &MainWindow::offroadTransition);
|
||||
QObject::connect(&device, &Device::displayPowerChanged, this, &MainWindow::closeSettings);
|
||||
|
||||
// load fonts
|
||||
QFontDatabase::addApplicationFont("../assets/fonts/opensans_regular.ttf");
|
||||
QFontDatabase::addApplicationFont("../assets/fonts/opensans_bold.ttf");
|
||||
QFontDatabase::addApplicationFont("../assets/fonts/opensans_semibold.ttf");
|
||||
|
||||
// no outline to prevent the focus rectangle
|
||||
setLayout(main_layout);
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
font-family: Inter;
|
||||
outline: none;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void MainWindow::offroadTransition(bool offroad){
|
||||
if(!offroad){
|
||||
closeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openSettings() {
|
||||
main_layout->setCurrentWidget(settingsWindow);
|
||||
}
|
||||
|
||||
void MainWindow::closeSettings() {
|
||||
if(onboardingDone) {
|
||||
main_layout->setCurrentWidget(homeWindow);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::reviewTrainingGuide() {
|
||||
onboardingDone = false;
|
||||
main_layout->setCurrentWidget(onboardingWindow);
|
||||
onboardingWindow->updateActiveScreen();
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject *obj, QEvent *event){
|
||||
// wake screen on tap
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
device.setAwake(true, true);
|
||||
}
|
||||
|
||||
#ifdef QCOM
|
||||
// filter out touches while in android activity
|
||||
const static QSet<QEvent::Type> filter_events({QEvent::MouseButtonPress, QEvent::MouseMove, QEvent::TouchBegin, QEvent::TouchUpdate, QEvent::TouchEnd});
|
||||
if (HardwareEon::launched_activity && filter_events.contains(event->type())) {
|
||||
HardwareEon::check_activity();
|
||||
if (HardwareEon::launched_activity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStackedLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/home.h"
|
||||
#include "selfdrive/ui/qt/offroad/onboarding.h"
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class MainWindow : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
|
||||
private:
|
||||
Device device;
|
||||
QUIState qs;
|
||||
|
||||
QStackedLayout *main_layout;
|
||||
HomeWindow *homeWindow;
|
||||
SettingsWindow *settingsWindow;
|
||||
OnboardingWindow *onboardingWindow;
|
||||
bool onboardingDone = false;
|
||||
|
||||
public slots:
|
||||
void offroadTransition(bool offroad);
|
||||
void openSettings();
|
||||
void closeSettings();
|
||||
void reviewTrainingGuide();
|
||||
};
|
||||
@@ -4,6 +4,8 @@ if [ -f /EON ] && [ ! -f qt/spinner ]; then
|
||||
cp qt/spinner_aarch64 qt/spinner
|
||||
elif [ -f /TICI ] && [ ! -f qt/spinner ]; then
|
||||
cp qt/spinner_larch64 qt/spinner
|
||||
elif [ -f /JETSON ] && [ ! -f qt/spinner ]; then
|
||||
cp qt/spinner_larch64 qt/spinner
|
||||
fi
|
||||
|
||||
export LD_LIBRARY_PATH="/system/lib64:$LD_LIBRARY_PATH"
|
||||
|
||||
@@ -4,6 +4,8 @@ if [ -f /EON ] && [ ! -f qt/text ]; then
|
||||
cp qt/text_aarch64 qt/text
|
||||
elif [ -f /TICI ] && [ ! -f qt/text ]; then
|
||||
cp qt/text_larch64 qt/text
|
||||
elif [ -f /JETSON ] && [ ! -f qt/text ]; then
|
||||
cp qt/text_larch64 qt/text
|
||||
fi
|
||||
|
||||
export LD_LIBRARY_PATH="/system/lib64:$LD_LIBRARY_PATH"
|
||||
|
||||
@@ -1,382 +0,0 @@
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "selfdrive/common/swaglog.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/common/visionimg.h"
|
||||
#include "selfdrive/common/watchdog.h"
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/paint.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
|
||||
#define BACKLIGHT_DT 0.25
|
||||
#define BACKLIGHT_TS 2.00
|
||||
#define BACKLIGHT_OFFROAD 75
|
||||
|
||||
|
||||
// Projects a point in car to space to the corresponding point in full frame
|
||||
// image space.
|
||||
static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, vertex_data *out) {
|
||||
const float margin = 500.0f;
|
||||
const vec3 pt = (vec3){{in_x, in_y, in_z}};
|
||||
const vec3 Ep = matvecmul3(s->scene.view_from_calib, pt);
|
||||
const vec3 KEp = matvecmul3(s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep);
|
||||
|
||||
// Project.
|
||||
float x = KEp.v[0] / KEp.v[2];
|
||||
float y = KEp.v[1] / KEp.v[2];
|
||||
|
||||
nvgTransformPoint(&out->x, &out->y, s->car_space_transform, x, y);
|
||||
return out->x >= -margin && out->x <= s->fb_w + margin && out->y >= -margin && out->y <= s->fb_h + margin;
|
||||
}
|
||||
|
||||
static void ui_init_vision(UIState *s) {
|
||||
// Invisible until we receive a calibration message.
|
||||
s->scene.world_objects_visible = false;
|
||||
|
||||
for (int i = 0; i < s->vipc_client->num_buffers; i++) {
|
||||
s->texture[i].reset(new EGLImageTexture(&s->vipc_client->buffers[i]));
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, s->texture[i]->frame_tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
// BGR
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
}
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
}
|
||||
|
||||
static int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line, const float path_height) {
|
||||
const auto line_x = line.getX();
|
||||
int max_idx = 0;
|
||||
for (int i = 0; i < TRAJECTORY_SIZE && line_x[i] < path_height; ++i) {
|
||||
max_idx = i;
|
||||
}
|
||||
return max_idx;
|
||||
}
|
||||
|
||||
static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, std::optional<cereal::ModelDataV2::XYZTData::Reader> line) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo();
|
||||
if (lead_data.getStatus()) {
|
||||
float z = line ? (*line).getZ()[get_path_length_idx(*line, lead_data.getDRel())] : 0.0;
|
||||
// negative because radarState uses left positive convention
|
||||
calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &s->scene.lead_vertices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line,
|
||||
float y_off, float z_off, line_vertices_data *pvd, int max_idx) {
|
||||
const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ();
|
||||
vertex_data *v = &pvd->v[0];
|
||||
for (int i = 0; i <= max_idx; i++) {
|
||||
v += calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, v);
|
||||
}
|
||||
for (int i = max_idx; i >= 0; i--) {
|
||||
v += calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, v);
|
||||
}
|
||||
pvd->cnt = v - pvd->v;
|
||||
assert(pvd->cnt < std::size(pvd->v));
|
||||
}
|
||||
|
||||
static void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) {
|
||||
UIScene &scene = s->scene;
|
||||
auto model_position = model.getPosition();
|
||||
float max_distance = std::clamp(model_position.getX()[TRAJECTORY_SIZE - 1],
|
||||
MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE);
|
||||
|
||||
// update lane lines
|
||||
const auto lane_lines = model.getLaneLines();
|
||||
const auto lane_line_probs = model.getLaneLineProbs();
|
||||
int max_idx = get_path_length_idx(lane_lines[0], max_distance);
|
||||
for (int i = 0; i < std::size(scene.lane_line_vertices); i++) {
|
||||
scene.lane_line_probs[i] = lane_line_probs[i];
|
||||
update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx);
|
||||
}
|
||||
|
||||
// update road edges
|
||||
const auto road_edges = model.getRoadEdges();
|
||||
const auto road_edge_stds = model.getRoadEdgeStds();
|
||||
for (int i = 0; i < std::size(scene.road_edge_vertices); i++) {
|
||||
scene.road_edge_stds[i] = road_edge_stds[i];
|
||||
update_line_data(s, road_edges[i], 0.025, 0, &scene.road_edge_vertices[i], max_idx);
|
||||
}
|
||||
|
||||
// update path
|
||||
auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne();
|
||||
if (lead_one.getStatus()) {
|
||||
const float lead_d = lead_one.getDRel() * 2.;
|
||||
max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance);
|
||||
}
|
||||
max_idx = get_path_length_idx(model_position, max_distance);
|
||||
update_line_data(s, model_position, 0.5, 1.22, &scene.track_vertices, max_idx);
|
||||
}
|
||||
|
||||
static void update_sockets(UIState *s){
|
||||
s->sm->update(0);
|
||||
}
|
||||
|
||||
static void update_state(UIState *s) {
|
||||
SubMaster &sm = *(s->sm);
|
||||
UIScene &scene = s->scene;
|
||||
|
||||
// update engageability and DM icons at 2Hz
|
||||
if (sm.frame % (UI_FREQ / 2) == 0) {
|
||||
scene.engageable = sm["controlsState"].getControlsState().getEngageable();
|
||||
scene.dm_active = sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode();
|
||||
}
|
||||
if (sm.updated("radarState")) {
|
||||
std::optional<cereal::ModelDataV2::XYZTData::Reader> line;
|
||||
if (sm.rcv_frame("modelV2") > 0) {
|
||||
line = sm["modelV2"].getModelV2().getPosition();
|
||||
}
|
||||
update_leads(s, sm["radarState"].getRadarState(), line);
|
||||
}
|
||||
if (sm.updated("liveCalibration")) {
|
||||
scene.world_objects_visible = true;
|
||||
auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib();
|
||||
Eigen::Vector3d rpy;
|
||||
rpy << rpy_list[0], rpy_list[1], rpy_list[2];
|
||||
Eigen::Matrix3d device_from_calib = euler2rot(rpy);
|
||||
Eigen::Matrix3d view_from_device;
|
||||
view_from_device << 0,1,0,
|
||||
0,0,1,
|
||||
1,0,0;
|
||||
Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sm.updated("modelV2")) {
|
||||
update_model(s, sm["modelV2"].getModelV2());
|
||||
}
|
||||
if (sm.updated("pandaState")) {
|
||||
auto pandaState = sm["pandaState"].getPandaState();
|
||||
scene.pandaType = pandaState.getPandaType();
|
||||
scene.ignition = pandaState.getIgnitionLine() || pandaState.getIgnitionCan();
|
||||
} else if ((s->sm->frame - s->sm->rcv_frame("pandaState")) > 5*UI_FREQ) {
|
||||
scene.pandaType = cereal::PandaState::PandaType::UNKNOWN;
|
||||
}
|
||||
if (sm.updated("ubloxGnss")) {
|
||||
auto data = sm["ubloxGnss"].getUbloxGnss();
|
||||
if (data.which() == cereal::UbloxGnss::MEASUREMENT_REPORT) {
|
||||
scene.satelliteCount = data.getMeasurementReport().getNumMeas();
|
||||
}
|
||||
}
|
||||
if (sm.updated("gpsLocationExternal")) {
|
||||
scene.gpsAccuracy = sm["gpsLocationExternal"].getGpsLocationExternal().getAccuracy();
|
||||
}
|
||||
if (sm.updated("carParams")) {
|
||||
scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
||||
}
|
||||
if (sm.updated("sensorEvents")) {
|
||||
for (auto sensor : sm["sensorEvents"].getSensorEvents()) {
|
||||
if (!scene.started && sensor.which() == cereal::SensorEventData::ACCELERATION) {
|
||||
auto accel = sensor.getAcceleration().getV();
|
||||
if (accel.totalSize().wordCount){ // TODO: sometimes empty lists are received. Figure out why
|
||||
scene.accel_sensor = accel[2];
|
||||
}
|
||||
} else if (!scene.started && sensor.which() == cereal::SensorEventData::GYRO_UNCALIBRATED) {
|
||||
auto gyro = sensor.getGyroUncalibrated().getV();
|
||||
if (gyro.totalSize().wordCount){
|
||||
scene.gyro_sensor = gyro[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sm.updated("roadCameraState")) {
|
||||
auto camera_state = sm["roadCameraState"].getRoadCameraState();
|
||||
|
||||
float max_lines = Hardware::EON() ? 5408 : 1757;
|
||||
float gain = camera_state.getGainFrac();
|
||||
|
||||
if (Hardware::TICI()) {
|
||||
// gainFrac can go up to 4, with another 2.5x multiplier based on globalGain. Scale back to 0 - 1
|
||||
gain *= (camera_state.getGlobalGain() > 100 ? 2.5 : 1.0) / 10.0;
|
||||
}
|
||||
|
||||
scene.light_sensor = std::clamp<float>((1023.0 / max_lines) * (max_lines - camera_state.getIntegLines() * gain), 0.0, 1023.0);
|
||||
}
|
||||
scene.started = sm["deviceState"].getDeviceState().getStarted();
|
||||
}
|
||||
|
||||
static void update_params(UIState *s) {
|
||||
const uint64_t frame = s->sm->frame;
|
||||
UIScene &scene = s->scene;
|
||||
if (frame % (5*UI_FREQ) == 0) {
|
||||
scene.is_metric = Params().getBool("IsMetric");
|
||||
}
|
||||
}
|
||||
|
||||
static void update_vision(UIState *s) {
|
||||
if (!s->vipc_client->connected && s->scene.started) {
|
||||
if (s->vipc_client->connect(false)){
|
||||
ui_init_vision(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->vipc_client->connected){
|
||||
VisionBuf * buf = s->vipc_client->recv();
|
||||
if (buf != nullptr){
|
||||
s->last_frame = buf;
|
||||
} else if (!Hardware::PC()) {
|
||||
LOGE("visionIPC receive timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_status(UIState *s) {
|
||||
if (s->scene.started && s->sm->updated("controlsState")) {
|
||||
auto controls_state = (*s->sm)["controlsState"].getControlsState();
|
||||
auto alert_status = controls_state.getAlertStatus();
|
||||
if (alert_status == cereal::ControlsState::AlertStatus::USER_PROMPT) {
|
||||
s->status = STATUS_WARNING;
|
||||
} else if (alert_status == cereal::ControlsState::AlertStatus::CRITICAL) {
|
||||
s->status = STATUS_ALERT;
|
||||
} else {
|
||||
s->status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle onroad/offroad transition
|
||||
static bool started_prev = false;
|
||||
if (s->scene.started != started_prev) {
|
||||
if (s->scene.started) {
|
||||
s->status = STATUS_DISENGAGED;
|
||||
s->scene.started_frame = s->sm->frame;
|
||||
|
||||
s->scene.end_to_end = Params().getBool("EndToEndToggle");
|
||||
s->wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false;
|
||||
|
||||
// Update intrinsics matrix after possible wide camera toggle change
|
||||
if (s->vg) {
|
||||
ui_resize(s, s->fb_w, s->fb_h);
|
||||
}
|
||||
|
||||
// Choose vision ipc client
|
||||
if (s->wide_camera){
|
||||
s->vipc_client = s->vipc_client_wide;
|
||||
} else {
|
||||
s->vipc_client = s->vipc_client_rear;
|
||||
}
|
||||
} else {
|
||||
s->vipc_client->connected = false;
|
||||
}
|
||||
}
|
||||
started_prev = s->scene.started;
|
||||
}
|
||||
|
||||
|
||||
QUIState::QUIState(QObject *parent) : QObject(parent) {
|
||||
ui_state.sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({
|
||||
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "liveLocationKalman",
|
||||
"pandaState", "carParams", "driverMonitoringState", "sensorEvents", "carState", "ubloxGnss",
|
||||
"gpsLocationExternal", "roadCameraState",
|
||||
});
|
||||
|
||||
ui_state.fb_w = vwp_w;
|
||||
ui_state.fb_h = vwp_h;
|
||||
ui_state.scene.started = false;
|
||||
ui_state.last_frame = nullptr;
|
||||
ui_state.wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false;
|
||||
|
||||
ui_state.vipc_client_rear = new VisionIpcClient("camerad", VISION_STREAM_RGB_BACK, true);
|
||||
ui_state.vipc_client_wide = new VisionIpcClient("camerad", VISION_STREAM_RGB_WIDE, true);
|
||||
|
||||
ui_state.vipc_client = ui_state.vipc_client_rear;
|
||||
|
||||
// update timer
|
||||
timer = new QTimer(this);
|
||||
QObject::connect(timer, &QTimer::timeout, this, &QUIState::update);
|
||||
timer->start(0);
|
||||
}
|
||||
|
||||
void QUIState::update() {
|
||||
update_params(&ui_state);
|
||||
update_sockets(&ui_state);
|
||||
update_state(&ui_state);
|
||||
update_status(&ui_state);
|
||||
update_vision(&ui_state);
|
||||
|
||||
if (ui_state.scene.started != started_prev || ui_state.sm->frame == 1) {
|
||||
started_prev = ui_state.scene.started;
|
||||
emit offroadTransition(!ui_state.scene.started);
|
||||
|
||||
// Change timeout to 0 when onroad, this will call update continously.
|
||||
// This puts visionIPC in charge of update frequency, reducing video latency
|
||||
timer->start(ui_state.scene.started ? 0 : 1000 / UI_FREQ);
|
||||
}
|
||||
|
||||
watchdog_kick();
|
||||
emit uiUpdate(ui_state);
|
||||
}
|
||||
|
||||
Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) {
|
||||
}
|
||||
|
||||
void Device::update(const UIState &s) {
|
||||
updateBrightness(s);
|
||||
updateWakefulness(s);
|
||||
|
||||
// TODO: remove from UIState and use signals
|
||||
QUIState::ui_state.awake = awake;
|
||||
}
|
||||
|
||||
void Device::setAwake(bool on, bool reset) {
|
||||
if (on != awake) {
|
||||
awake = on;
|
||||
Hardware::set_display_power(awake);
|
||||
LOGD("setting display power %d", awake);
|
||||
emit displayPowerChanged(awake);
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
awake_timeout = 30 * UI_FREQ;
|
||||
}
|
||||
}
|
||||
|
||||
void Device::updateBrightness(const UIState &s) {
|
||||
float brightness_b = 10;
|
||||
float brightness_m = 0.1;
|
||||
float clipped_brightness = std::min(100.0f, (s.scene.light_sensor * brightness_m) + brightness_b);
|
||||
if (!s.scene.started) {
|
||||
clipped_brightness = BACKLIGHT_OFFROAD;
|
||||
}
|
||||
|
||||
int brightness = brightness_filter.update(clipped_brightness);
|
||||
if (!awake) {
|
||||
brightness = 0;
|
||||
}
|
||||
|
||||
if (brightness != last_brightness) {
|
||||
std::thread{Hardware::set_brightness, brightness}.detach();
|
||||
}
|
||||
last_brightness = brightness;
|
||||
}
|
||||
|
||||
void Device::updateWakefulness(const UIState &s) {
|
||||
awake_timeout = std::max(awake_timeout - 1, 0);
|
||||
|
||||
bool should_wake = s.scene.started || s.scene.ignition;
|
||||
if (!should_wake) {
|
||||
// tap detection while display is off
|
||||
bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2;
|
||||
bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15;
|
||||
should_wake = accel_trigger && gyro_trigger;
|
||||
gyro_prev = s.scene.gyro_sensor;
|
||||
accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples;
|
||||
}
|
||||
|
||||
setAwake(awake_timeout, should_wake);
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QColor>
|
||||
|
||||
#include "nanovg.h"
|
||||
|
||||
#include "cereal/messaging/messaging.h"
|
||||
#include "cereal/visionipc/visionipc.h"
|
||||
#include "cereal/visionipc/visionipc_client.h"
|
||||
#include "common/transformations/orientation.hpp"
|
||||
#include "selfdrive/camerad/cameras/camera_common.h"
|
||||
#include "selfdrive/common/glutil.h"
|
||||
#include "selfdrive/common/mat.h"
|
||||
#include "selfdrive/common/modeldata.h"
|
||||
#include "selfdrive/common/params.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
#include "selfdrive/common/visionimg.h"
|
||||
|
||||
#define COLOR_BLACK nvgRGBA(0, 0, 0, 255)
|
||||
#define COLOR_BLACK_ALPHA(x) nvgRGBA(0, 0, 0, x)
|
||||
#define COLOR_WHITE nvgRGBA(255, 255, 255, 255)
|
||||
#define COLOR_WHITE_ALPHA(x) nvgRGBA(255, 255, 255, x)
|
||||
#define COLOR_RED_ALPHA(x) nvgRGBA(201, 34, 49, x)
|
||||
#define COLOR_YELLOW nvgRGBA(218, 202, 37, 255)
|
||||
#define COLOR_RED nvgRGBA(201, 34, 49, 255)
|
||||
|
||||
// TODO: this is also hardcoded in common/transformations/camera.py
|
||||
// TODO: choose based on frame input size
|
||||
const float y_offset = Hardware::TICI() ? 150.0 : 0.0;
|
||||
const float zoom = Hardware::TICI() ? 2912.8 : 2138.5;
|
||||
|
||||
typedef struct Rect {
|
||||
int x, y, w, h;
|
||||
int centerX() const { return x + w / 2; }
|
||||
int centerY() const { return y + h / 2; }
|
||||
int right() const { return x + w; }
|
||||
int bottom() const { return y + h; }
|
||||
bool ptInRect(int px, int py) const {
|
||||
return px >= x && px < (x + w) && py >= y && py < (y + h);
|
||||
}
|
||||
} Rect;
|
||||
|
||||
const int bdr_s = 30;
|
||||
const int header_h = 420;
|
||||
const int footer_h = 280;
|
||||
|
||||
const int UI_FREQ = 20; // Hz
|
||||
|
||||
typedef enum UIStatus {
|
||||
STATUS_DISENGAGED,
|
||||
STATUS_ENGAGED,
|
||||
STATUS_WARNING,
|
||||
STATUS_ALERT,
|
||||
} UIStatus;
|
||||
|
||||
const QColor bg_colors [] = {
|
||||
[STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8),
|
||||
[STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1),
|
||||
[STATUS_WARNING] = QColor(0xDA, 0x6F, 0x25, 0xf1),
|
||||
[STATUS_ALERT] = QColor(0xC9, 0x22, 0x31, 0xf1),
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} vertex_data;
|
||||
|
||||
typedef struct {
|
||||
vertex_data v[TRAJECTORY_SIZE * 2];
|
||||
int cnt;
|
||||
} line_vertices_data;
|
||||
|
||||
typedef struct UIScene {
|
||||
|
||||
mat3 view_from_calib;
|
||||
bool world_objects_visible;
|
||||
|
||||
cereal::PandaState::PandaType pandaType;
|
||||
|
||||
// gps
|
||||
int satelliteCount;
|
||||
float gpsAccuracy;
|
||||
|
||||
// modelV2
|
||||
float lane_line_probs[4];
|
||||
float road_edge_stds[2];
|
||||
line_vertices_data track_vertices;
|
||||
line_vertices_data lane_line_vertices[4];
|
||||
line_vertices_data road_edge_vertices[2];
|
||||
|
||||
bool dm_active, engageable;
|
||||
|
||||
// lead
|
||||
vertex_data lead_vertices[2];
|
||||
|
||||
float light_sensor, accel_sensor, gyro_sensor;
|
||||
bool started, ignition, is_metric, longitudinal_control, end_to_end;
|
||||
uint64_t started_frame;
|
||||
} UIScene;
|
||||
|
||||
typedef struct UIState {
|
||||
VisionIpcClient * vipc_client;
|
||||
VisionIpcClient * vipc_client_rear;
|
||||
VisionIpcClient * vipc_client_wide;
|
||||
VisionBuf * last_frame;
|
||||
|
||||
// framebuffer
|
||||
int fb_w, fb_h;
|
||||
|
||||
// NVG
|
||||
NVGcontext *vg;
|
||||
|
||||
// images
|
||||
std::map<std::string, int> images;
|
||||
|
||||
std::unique_ptr<SubMaster> sm;
|
||||
|
||||
UIStatus status;
|
||||
UIScene scene;
|
||||
|
||||
// graphics
|
||||
std::unique_ptr<GLShader> gl_shader;
|
||||
std::unique_ptr<EGLImageTexture> texture[UI_BUF_COUNT];
|
||||
|
||||
GLuint frame_vao, frame_vbo, frame_ibo;
|
||||
mat4 rear_frame_mat;
|
||||
|
||||
bool awake;
|
||||
|
||||
Rect video_rect, viz_rect;
|
||||
float car_space_transform[6];
|
||||
bool wide_camera;
|
||||
float zoom;
|
||||
} UIState;
|
||||
|
||||
|
||||
class QUIState : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QUIState(QObject* parent = 0);
|
||||
|
||||
// TODO: get rid of this, only use signal
|
||||
inline static UIState ui_state = {0};
|
||||
|
||||
signals:
|
||||
void uiUpdate(const UIState &s);
|
||||
void offroadTransition(bool offroad);
|
||||
|
||||
private slots:
|
||||
void update();
|
||||
|
||||
private:
|
||||
QTimer *timer;
|
||||
bool started_prev = true;
|
||||
};
|
||||
|
||||
|
||||
// device management class
|
||||
|
||||
class Device : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Device(QObject *parent = 0);
|
||||
|
||||
private:
|
||||
// auto brightness
|
||||
const float accel_samples = 5*UI_FREQ;
|
||||
|
||||
bool awake;
|
||||
int awake_timeout = 0;
|
||||
float accel_prev = 0;
|
||||
float gyro_prev = 0;
|
||||
float last_brightness = 0;
|
||||
FirstOrderFilter brightness_filter;
|
||||
|
||||
QTimer *timer;
|
||||
|
||||
void updateBrightness(const UIState &s);
|
||||
void updateWakefulness(const UIState &s);
|
||||
|
||||
signals:
|
||||
void displayPowerChanged(bool on);
|
||||
|
||||
public slots:
|
||||
void setAwake(bool on, bool reset);
|
||||
void update(const UIState &s);
|
||||
};
|
||||
Reference in New Issue
Block a user