mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-21 20:03:55 +08:00
Using the narrow camera as a light sensor meant that the reflection of our own headlights, tail lights, and head lights of opposing vehicles dominated the sensor value at night even though the broader overall scene is very dark. This has very little effect during daylight hours. The change affects nightime driving the most, resulting in a dimmer screen when it is dark. I think this makes more sense as the wide angle camera's field of view is much closer to the human eye's field of view than the narrow road camera.
341 lines
12 KiB
C++
341 lines
12 KiB
C++
#include "selfdrive/ui/ui.h"
|
|
|
|
#include <cassert>
|
|
#include <cmath>
|
|
|
|
#include <QtConcurrent>
|
|
|
|
#include "common/transformations/orientation.hpp"
|
|
#include "selfdrive/common/params.h"
|
|
#include "selfdrive/common/swaglog.h"
|
|
#include "selfdrive/common/util.h"
|
|
#include "selfdrive/common/watchdog.h"
|
|
#include "selfdrive/hardware/hw.h"
|
|
|
|
#define BACKLIGHT_DT 0.05
|
|
#define BACKLIGHT_TS 10.00
|
|
#define BACKLIGHT_OFFROAD 50
|
|
|
|
// 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, QPointF *out) {
|
|
const float margin = 500.0f;
|
|
const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin};
|
|
|
|
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.
|
|
QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]});
|
|
if (clip_region.contains(point)) {
|
|
*out = point;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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 = 1; 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, const 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.getZ()[get_path_length_idx(line, lead_data.getDRel())];
|
|
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();
|
|
QPointF *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;
|
|
|
|
if (sm.updated("liveCalibration")) {
|
|
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 (s->worldObjectsVisible()) {
|
|
if (sm.updated("modelV2")) {
|
|
update_model(s, sm["modelV2"].getModelV2());
|
|
}
|
|
if (sm.updated("radarState") && sm.rcv_frame("modelV2") >= s->scene.started_frame) {
|
|
update_leads(s, sm["radarState"].getRadarState(), sm["modelV2"].getModelV2().getPosition());
|
|
}
|
|
}
|
|
if (sm.updated("pandaStates")) {
|
|
auto pandaStates = sm["pandaStates"].getPandaStates();
|
|
if (pandaStates.size() > 0) {
|
|
scene.pandaType = pandaStates[0].getPandaType();
|
|
|
|
if (scene.pandaType != cereal::PandaState::PandaType::UNKNOWN) {
|
|
scene.ignition = false;
|
|
for (const auto& pandaState : pandaStates) {
|
|
scene.ignition |= pandaState.getIgnitionLine() || pandaState.getIgnitionCan();
|
|
}
|
|
}
|
|
}
|
|
} else if ((s->sm->frame - s->sm->rcv_frame("pandaStates")) > 5*UI_FREQ) {
|
|
scene.pandaType = cereal::PandaState::PandaType::UNKNOWN;
|
|
}
|
|
if (sm.updated("carParams")) {
|
|
scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
|
}
|
|
if (!scene.started && sm.updated("sensorEvents")) {
|
|
for (auto sensor : sm["sensorEvents"].getSensorEvents()) {
|
|
if (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 (sensor.which() == cereal::SensorEventData::GYRO_UNCALIBRATED) {
|
|
auto gyro = sensor.getGyroUncalibrated().getV();
|
|
if (gyro.totalSize().wordCount) {
|
|
scene.gyro_sensor = gyro[1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!Hardware::TICI() && sm.updated("roadCameraState")) {
|
|
auto camera_state = sm["roadCameraState"].getRoadCameraState();
|
|
|
|
float max_lines = Hardware::EON() ? 5408 : 1904;
|
|
float max_gain = Hardware::EON() ? 1.0: 10.0;
|
|
float max_ev = max_lines * max_gain;
|
|
|
|
float ev = camera_state.getGain() * float(camera_state.getIntegLines());
|
|
|
|
scene.light_sensor = std::clamp<float>(1.0 - (ev / max_ev), 0.0, 1.0);
|
|
} else if (Hardware::TICI() && sm.updated("wideRoadCameraState")) {
|
|
auto camera_state = sm["wideRoadCameraState"].getWideRoadCameraState();
|
|
|
|
float max_lines = 1904;
|
|
float max_gain = 10.0;
|
|
float max_ev = max_lines * max_gain / 6;
|
|
|
|
float ev = camera_state.getGain() * float(camera_state.getIntegLines());
|
|
|
|
scene.light_sensor = std::clamp<float>(1.0 - (ev / max_ev), 0.0, 1.0);
|
|
}
|
|
scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition;
|
|
}
|
|
|
|
void ui_update_params(UIState *s) {
|
|
s->scene.is_metric = Params().getBool("IsMetric");
|
|
}
|
|
|
|
void UIState::updateStatus() {
|
|
if (scene.started && sm->updated("controlsState")) {
|
|
auto controls_state = (*sm)["controlsState"].getControlsState();
|
|
auto alert_status = controls_state.getAlertStatus();
|
|
if (alert_status == cereal::ControlsState::AlertStatus::USER_PROMPT) {
|
|
status = STATUS_WARNING;
|
|
} else if (alert_status == cereal::ControlsState::AlertStatus::CRITICAL) {
|
|
status = STATUS_ALERT;
|
|
} else {
|
|
status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED;
|
|
}
|
|
}
|
|
|
|
// Handle onroad/offroad transition
|
|
if (scene.started != started_prev) {
|
|
if (scene.started) {
|
|
status = STATUS_DISENGAGED;
|
|
scene.started_frame = sm->frame;
|
|
scene.end_to_end = Params().getBool("EndToEndToggle");
|
|
wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false;
|
|
}
|
|
started_prev = scene.started;
|
|
emit offroadTransition(!scene.started);
|
|
} else if (sm->frame == 1) {
|
|
emit offroadTransition(!scene.started);
|
|
}
|
|
}
|
|
|
|
UIState::UIState(QObject *parent) : QObject(parent) {
|
|
sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({
|
|
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState",
|
|
"pandaStates", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman",
|
|
"wideRoadCameraState",
|
|
});
|
|
|
|
Params params;
|
|
wide_camera = Hardware::TICI() ? params.getBool("EnableWideCamera") : false;
|
|
has_prime = params.getBool("HasPrime");
|
|
|
|
// update timer
|
|
timer = new QTimer(this);
|
|
QObject::connect(timer, &QTimer::timeout, this, &UIState::update);
|
|
timer->start(1000 / UI_FREQ);
|
|
}
|
|
|
|
void UIState::update() {
|
|
update_sockets(this);
|
|
update_state(this);
|
|
updateStatus();
|
|
|
|
if (sm->frame % UI_FREQ == 0) {
|
|
watchdog_kick();
|
|
}
|
|
emit uiUpdate(*this);
|
|
}
|
|
|
|
Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) {
|
|
setAwake(true);
|
|
resetInteractiveTimout();
|
|
|
|
QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update);
|
|
}
|
|
|
|
void Device::update(const UIState &s) {
|
|
updateBrightness(s);
|
|
updateWakefulness(s);
|
|
|
|
// TODO: remove from UIState and use signals
|
|
uiState()->awake = awake;
|
|
}
|
|
|
|
void Device::setAwake(bool on) {
|
|
if (on != awake) {
|
|
awake = on;
|
|
Hardware::set_display_power(awake);
|
|
LOGD("setting display power %d", awake);
|
|
emit displayPowerChanged(awake);
|
|
}
|
|
}
|
|
|
|
void Device::resetInteractiveTimout() {
|
|
interactive_timeout = (ignition_on ? 10 : 30) * UI_FREQ;
|
|
}
|
|
|
|
void Device::updateBrightness(const UIState &s) {
|
|
float clipped_brightness = BACKLIGHT_OFFROAD;
|
|
if (s.scene.started) {
|
|
// Scale to 0% to 100%
|
|
clipped_brightness = 100.0 * s.scene.light_sensor;
|
|
|
|
// CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm
|
|
if (clipped_brightness <= 8) {
|
|
clipped_brightness = (clipped_brightness / 903.3);
|
|
} else {
|
|
clipped_brightness = std::pow((clipped_brightness + 16.0) / 116.0, 3.0);
|
|
}
|
|
|
|
// Scale back to 10% to 100%
|
|
clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f);
|
|
}
|
|
|
|
int brightness = brightness_filter.update(clipped_brightness);
|
|
if (!awake) {
|
|
brightness = 0;
|
|
}
|
|
|
|
if (brightness != last_brightness) {
|
|
if (!brightness_future.isRunning()) {
|
|
brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness);
|
|
last_brightness = brightness;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Device::motionTriggered(const UIState &s) {
|
|
static float accel_prev = 0;
|
|
static float gyro_prev = 0;
|
|
|
|
bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2;
|
|
bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15;
|
|
|
|
gyro_prev = s.scene.gyro_sensor;
|
|
accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples;
|
|
|
|
return (!awake && accel_trigger && gyro_trigger);
|
|
}
|
|
|
|
void Device::updateWakefulness(const UIState &s) {
|
|
bool ignition_just_turned_off = !s.scene.ignition && ignition_on;
|
|
ignition_on = s.scene.ignition;
|
|
|
|
if (ignition_just_turned_off || motionTriggered(s)) {
|
|
resetInteractiveTimout();
|
|
} else if (interactive_timeout > 0 && --interactive_timeout == 0) {
|
|
emit interactiveTimout();
|
|
}
|
|
|
|
setAwake(s.scene.ignition || interactive_timeout > 0);
|
|
}
|
|
|
|
UIState *uiState() {
|
|
static UIState ui_state;
|
|
return &ui_state;
|
|
}
|