mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-09 04:54:23 +08:00
Compare commits
1 Commits
master-dev
...
mapd-sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ae27a5d1c |
1
.github/workflows/repo-maintenance.yaml
vendored
1
.github/workflows/repo-maintenance.yaml
vendored
@@ -43,7 +43,6 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: uv lock
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
run: |
|
||||
python3 -m ensurepip --upgrade
|
||||
pip3 install uv
|
||||
|
||||
@@ -172,8 +172,6 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
experimentalModeSwitched @14;
|
||||
wrongCarModeAlertOnly @15;
|
||||
pedalPressedAlertOnly @16;
|
||||
laneTurnLeft @17;
|
||||
laneTurnRight @18;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,16 +258,9 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||
roadName @5 :Text;
|
||||
}
|
||||
|
||||
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||
laneTurnDirection @0 :TurnDirection;
|
||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
||||
}
|
||||
|
||||
enum TurnDirection {
|
||||
none @0;
|
||||
turnLeft @1;
|
||||
turnRight @2;
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
}
|
||||
|
||||
|
||||
@@ -2631,7 +2631,7 @@ struct Event {
|
||||
backupManagerSP @113 :Custom.BackupManagerSP;
|
||||
carStateSP @114 :Custom.CarStateSP;
|
||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||
customReserved9 @116 :Custom.CustomReserved9;
|
||||
customReserved10 @136 :Custom.CustomReserved10;
|
||||
customReserved11 @137 :Custom.CustomReserved11;
|
||||
customReserved12 @138 :Custom.CustomReserved12;
|
||||
|
||||
@@ -88,7 +88,6 @@ _services: dict[str, tuple] = {
|
||||
"carControlSP": (True, 100., 10),
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20.),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
|
||||
@@ -154,7 +154,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
||||
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
@@ -196,12 +195,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// sunnypilot model params
|
||||
// model panel params
|
||||
{"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}},
|
||||
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||
|
||||
// mapd
|
||||
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
|
||||
|
||||
Submodule opendbc_repo updated: 42bbd450b9...004fa8df07
2
panda
2
panda
Submodule panda updated: 7eab6fd61b...f10ddc6a89
@@ -1,8 +1,7 @@
|
||||
from cereal import log, custom
|
||||
from cereal import log
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
|
||||
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
@@ -31,12 +30,6 @@ DESIRES = {
|
||||
},
|
||||
}
|
||||
|
||||
TURN_DESIRES = {
|
||||
custom.TurnDirection.none: log.Desire.none,
|
||||
custom.TurnDirection.turnLeft: log.Desire.turnLeft,
|
||||
custom.TurnDirection.turnRight: log.Desire.turnRight,
|
||||
}
|
||||
|
||||
|
||||
class DesireHelper:
|
||||
def __init__(self):
|
||||
@@ -48,21 +41,13 @@ class DesireHelper:
|
||||
self.prev_one_blinker = False
|
||||
self.desire = log.Desire.none
|
||||
self.alc = AutoLaneChangeController(self)
|
||||
self.lane_turn_controller = LaneTurnController(self)
|
||||
self.lane_turn_direction = custom.TurnDirection.none
|
||||
|
||||
def update(self, carstate, lateral_active, lane_change_prob):
|
||||
self.alc.update_params()
|
||||
self.lane_turn_controller.update_params()
|
||||
v_ego = carstate.vEgo
|
||||
one_blinker = carstate.leftBlinker != carstate.rightBlinker
|
||||
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
|
||||
|
||||
# Lane turn controller update
|
||||
self.lane_turn_controller.update_lane_turn(blindspot_left=carstate.leftBlindspot, blindspot_right=carstate.rightBlindspot,
|
||||
left_blinker=carstate.leftBlinker, right_blinker=carstate.rightBlinker, v_ego=v_ego)
|
||||
self.lane_turn_direction = self.lane_turn_controller.get_turn_direction()
|
||||
|
||||
if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX or self.alc.lane_change_set_timer == AutoLaneChangeMode.OFF:
|
||||
self.lane_change_state = LaneChangeState.off
|
||||
self.lane_change_direction = LaneChangeDirection.none
|
||||
@@ -121,10 +106,7 @@ class DesireHelper:
|
||||
|
||||
self.prev_one_blinker = one_blinker
|
||||
|
||||
if self.lane_turn_direction != custom.TurnDirection.none:
|
||||
self.desire = TURN_DESIRES[self.lane_turn_direction]
|
||||
else:
|
||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||
|
||||
# Send keep pulse once per second during LaneChangeStart.preLaneChange
|
||||
if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
|
||||
|
||||
@@ -216,7 +216,7 @@ def main(demo=False):
|
||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
@@ -333,7 +333,6 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
posenet_send = messaging.new_message('cameraOdometry')
|
||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||
|
||||
action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
||||
prev_action = action
|
||||
@@ -348,7 +347,6 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -356,7 +354,6 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,5 @@
|
||||
"text": "openpilot detected excessive %1 actuation on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.",
|
||||
"severity": 1,
|
||||
"_comment": "Set extra field to lateral or longitudinal."
|
||||
},
|
||||
"Offroad_TiciSupport": {
|
||||
"text": "<b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three.",
|
||||
"severity": 1,
|
||||
"_comment": "Set extra field to the current branch name."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class SelfdriveD(CruiseHelper):
|
||||
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
|
||||
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
||||
|
||||
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
|
||||
ignore = self.sensor_packets + self.gps_packets + ['alertDebug']
|
||||
if SIMULATION:
|
||||
ignore += ['driverCameraState', 'managerState']
|
||||
if REPLAY:
|
||||
@@ -95,8 +95,7 @@ class SelfdriveD(CruiseHelper):
|
||||
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
|
||||
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
|
||||
'modelDataV2SP'] + \
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \
|
||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||
@@ -301,13 +300,6 @@ class SelfdriveD(CruiseHelper):
|
||||
LaneChangeState.laneChangeFinishing):
|
||||
self.events.add(EventName.laneChange)
|
||||
|
||||
# Handle lane turn
|
||||
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
|
||||
if lane_turn_direction == custom.TurnDirection.turnLeft:
|
||||
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
|
||||
elif lane_turn_direction == custom.TurnDirection.turnRight:
|
||||
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
|
||||
|
||||
for i, pandaState in enumerate(self.sm['pandaStates']):
|
||||
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
|
||||
if i < len(self.CP.safetyConfigs):
|
||||
|
||||
@@ -34,6 +34,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
|
||||
}
|
||||
}
|
||||
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
@@ -174,6 +175,172 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
|
||||
}
|
||||
|
||||
|
||||
void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) {
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
|
||||
if (!sm.alive("radarState")) return;
|
||||
|
||||
const auto &radar_state = sm["radarState"].getRadarState();
|
||||
const auto &lead_one = radar_state.getLeadOne();
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
|
||||
// Check if we have any active leads
|
||||
bool has_lead_one = lead_one.getStatus();
|
||||
bool has_lead_two = lead_two.getStatus();
|
||||
|
||||
if (!has_lead_one && !has_lead_two) {
|
||||
// Fade out status display
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
if (lead_status_alpha <= 0.0f) return;
|
||||
} else {
|
||||
// Fade in status display
|
||||
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
|
||||
}
|
||||
|
||||
// Draw status for each lead vehicle under its chevron
|
||||
if (true) {
|
||||
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
|
||||
}
|
||||
|
||||
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
|
||||
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter,
|
||||
const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos,
|
||||
int height, int width,
|
||||
const QString &label) {
|
||||
|
||||
float d_rel = lead_data.getDRel();
|
||||
float v_rel = lead_data.getVRel();
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
float v_ego = sm["carState"].getCarState().getVEgo();
|
||||
|
||||
int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
|
||||
|
||||
// Calculate chevron size (same logic as drawLead)
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
|
||||
|
||||
QFont content_font = painter.font();
|
||||
content_font.setPixelSize(35);
|
||||
content_font.setBold(true);
|
||||
painter.setFont(content_font);
|
||||
|
||||
QFontMetrics fm(content_font);
|
||||
bool is_metric = s->scene.is_metric;
|
||||
|
||||
QStringList text_lines;
|
||||
|
||||
const int chevron_types = 3;
|
||||
const int chevron_all = chevron_types + 1; // All metrics (value 4)
|
||||
QStringList chevron_text[chevron_types];
|
||||
int position;
|
||||
float val;
|
||||
|
||||
// Distance display (chevron_data == 1 or all)
|
||||
if (chevron_data == 1 || chevron_data == chevron_all) {
|
||||
position = 0;
|
||||
val = std::max(0.0f, d_rel);
|
||||
QString distance_unit = is_metric ? "m" : "ft";
|
||||
if (!is_metric) {
|
||||
val *= 3.28084f; // Convert meters to feet
|
||||
}
|
||||
chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit);
|
||||
}
|
||||
|
||||
// Absolute velocity display (chevron_data == 2 or all)
|
||||
if (chevron_data == 2 || chevron_data == chevron_all) {
|
||||
position = (chevron_data == 2) ? 0 : 1;
|
||||
val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH)));
|
||||
chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph"));
|
||||
}
|
||||
|
||||
// Time-to-contact display (chevron_data == 3 or all)
|
||||
if (chevron_data == 3 || chevron_data == chevron_all) {
|
||||
position = (chevron_data == 3) ? 0 : 2;
|
||||
val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
|
||||
QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
|
||||
chevron_text[position].append(ttc_str);
|
||||
}
|
||||
|
||||
// Collect all non-empty text lines
|
||||
for (int i = 0; i < chevron_types; ++i) {
|
||||
if (!chevron_text[i].isEmpty()) {
|
||||
text_lines.append(chevron_text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// If no text to display, return early
|
||||
if (text_lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Text box dimensions
|
||||
float str_w = 150; // Width of text area
|
||||
float str_h = 45; // Height per line
|
||||
|
||||
// Position text below chevron, centered horizontally
|
||||
float text_x = chevron_pos.x() - str_w / 2;
|
||||
float text_y = chevron_pos.y() + sz + 15;
|
||||
|
||||
// Clamp to screen bounds
|
||||
text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10);
|
||||
|
||||
// Shadow offset
|
||||
QPoint shadow_offset(2, 2);
|
||||
|
||||
// Draw each line of text with shadow
|
||||
for (int i = 0; i < text_lines.size(); ++i) {
|
||||
if (!text_lines[i].isEmpty()) {
|
||||
QRect textRect(text_x, text_y + (i * str_h), str_w, str_h);
|
||||
|
||||
// Draw shadow
|
||||
painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha)));
|
||||
painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()),
|
||||
Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
|
||||
|
||||
// Determine text color based on content and danger level
|
||||
QColor text_color;
|
||||
|
||||
// Check if this is a distance line (contains 'm' or 'ft')
|
||||
if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
|
||||
if (d_rel < 20.0f) {
|
||||
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger
|
||||
} else if (d_rel < 40.0f) {
|
||||
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
|
||||
} else {
|
||||
text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe
|
||||
}
|
||||
}
|
||||
// Enhanced color coding for time-to-contact
|
||||
else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) {
|
||||
float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat();
|
||||
if (ttc_val < 3.0f) {
|
||||
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent
|
||||
} else if (ttc_val < 6.0f) {
|
||||
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
|
||||
} else {
|
||||
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe
|
||||
}
|
||||
}
|
||||
else {
|
||||
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines
|
||||
}
|
||||
|
||||
// Draw main text
|
||||
painter.setPen(text_color);
|
||||
painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset pen
|
||||
painter.setPen(Qt::NoPen);
|
||||
}
|
||||
|
||||
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &vd, const QRect &surface_rect) {
|
||||
const float speedBuff = 10.;
|
||||
|
||||
@@ -34,6 +34,12 @@ protected:
|
||||
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
|
||||
void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
|
||||
QPolygonF *pvd, int max_idx, bool allow_invert = true);
|
||||
void drawLeadStatus(QPainter &painter, int height, int width);
|
||||
void drawLeadStatusAtPosition(QPainter &painter,
|
||||
const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos,
|
||||
int height, int width,
|
||||
const QString &label);
|
||||
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect);
|
||||
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
|
||||
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
|
||||
@@ -59,4 +65,8 @@ protected:
|
||||
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
|
||||
QRectF clip_region;
|
||||
|
||||
float lead_status_alpha = 0.0f;
|
||||
QPointF lead_status_pos;
|
||||
QString lead_status_text;
|
||||
QColor lead_status_color;
|
||||
};
|
||||
|
||||
@@ -101,42 +101,32 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
|
||||
QString policyType = tr("Policy Model");
|
||||
policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
|
||||
list->addItem(policyFrame);
|
||||
|
||||
list->addItem(horizontal_line());
|
||||
|
||||
// Lane Turn Desire toggle
|
||||
lane_turn_desire_toggle = new ParamControlSP("LaneTurnDesire", tr("Use Lane Turn Desires"),
|
||||
"If you’re driving at 20 mph (32 km/h) or below and have your blinker on, "
|
||||
"the car will plan a turn in that direction at the nearest drivable path. "
|
||||
"This prevents situations (like at red lights) where the car might plan the wrong turn direction.",
|
||||
"../assets/offroad/icon_shell.png");
|
||||
list->addItem(lane_turn_desire_toggle);
|
||||
|
||||
// Lane Turn Value control
|
||||
int max_value_mph = 20;
|
||||
bool is_metric_initial = params.getBool("IsMetric");
|
||||
const float K = 1.609344f;
|
||||
int per_value_change_scaled = is_metric_initial ? static_cast<int>(std::round((1.0f / K) * 100.0f)) : 100; // 100 -> 1 mph
|
||||
lane_turn_value_control = new OptionControlSP("LaneTurnValue", tr("Adjust Lane Turn Speed"),
|
||||
tr("Set the maximum speed for lane turn desires. Default is 19 %1.").arg(is_metric_initial ? "km/h" : "mph"),
|
||||
"", {5 * 100, max_value_mph * 100}, per_value_change_scaled, false, nullptr, true, true);
|
||||
lane_turn_value_control->showDescription();
|
||||
list->addItem(lane_turn_value_control);
|
||||
|
||||
// Show based on toggle
|
||||
refreshLaneTurnValueControl();
|
||||
connect(lane_turn_desire_toggle, &ParamControlSP::toggleFlipped, this, &ModelsPanel::refreshLaneTurnValueControl);
|
||||
connect(lane_turn_value_control, &OptionControlSP::updateLabels, this, &ModelsPanel::refreshLaneTurnValueControl);
|
||||
|
||||
// LiveDelay toggle
|
||||
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
|
||||
lagd_toggle_control->showDescription();
|
||||
list->addItem(lagd_toggle_control);
|
||||
|
||||
// Software delay control
|
||||
int liveDelayMaxInt = 30;
|
||||
std::string liveDelayBytes = params.get("LiveDelay");
|
||||
if (!liveDelayBytes.empty()) {
|
||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
||||
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
|
||||
liveDelayBytes.size() / sizeof(capnp::word)));
|
||||
auto event = msg.getRoot<cereal::Event>();
|
||||
if (event.hasLiveDelay()) {
|
||||
auto liveDelay = event.getLiveDelay();
|
||||
float lateralDelay = liveDelay.getLateralDelay();
|
||||
liveDelayMaxInt = static_cast<int>(lateralDelay * 100.0f) + 20;
|
||||
}
|
||||
}
|
||||
delay_control = new OptionControlSP("LagdToggleDelay", tr("Adjust Software Delay"),
|
||||
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
|
||||
"\nThe default software delay value is 0.2"),
|
||||
"", {5, 50}, 1, false, nullptr, true, true);
|
||||
tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
|
||||
"\nThe default software delay value is 0.2"),
|
||||
"", {5, liveDelayMaxInt}, 1, false, nullptr, true, true);
|
||||
|
||||
connect(delay_control, &OptionControlSP::updateLabels, [=]() {
|
||||
float value = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
||||
@@ -169,19 +159,6 @@ QFrame* ModelsPanel::createModelDetailFrame(QWidget *parent, QString &typeName,
|
||||
return frame;
|
||||
}
|
||||
|
||||
void ModelsPanel::refreshLaneTurnValueControl() {
|
||||
if (!lane_turn_value_control) return;
|
||||
float stored_mph = QString::fromStdString(params.get("LaneTurnValue")).toFloat();
|
||||
bool is_metric = params.getBool("IsMetric");
|
||||
QString unit = is_metric ? "km/h" : "mph";
|
||||
float display_value = stored_mph;
|
||||
if (is_metric) {
|
||||
display_value = stored_mph * 1.609344f;
|
||||
}
|
||||
lane_turn_value_control->setLabel(QString::number(static_cast<int>(std::round(display_value))) + " " + unit);
|
||||
lane_turn_value_control->setVisible(params.getBool("LaneTurnDesire"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the UI with bundle download progress information
|
||||
* Reads status from modelManagerSP cereal message and displays status for all models
|
||||
@@ -424,28 +401,34 @@ void ModelsPanel::updateLabels() {
|
||||
"Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.");
|
||||
bool lagdEnabled = params.getBool("LagdToggle");
|
||||
if (lagdEnabled) {
|
||||
auto liveDelayBytes = params.get("LiveDelay");
|
||||
std::string liveDelayBytes = params.get("LiveDelay");
|
||||
if (!liveDelayBytes.empty()) {
|
||||
auto LD = loadCerealEvent(params, "LiveDelay");
|
||||
float lateralDelay = LD->getLiveDelay().getLateralDelay();
|
||||
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
|
||||
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
|
||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
||||
reinterpret_cast<const capnp::word*>(liveDelayBytes.data()),
|
||||
liveDelayBytes.size() / sizeof(capnp::word)));
|
||||
auto event = msg.getRoot<cereal::Event>();
|
||||
if (event.hasLiveDelay()) {
|
||||
auto liveDelay = event.getLiveDelay();
|
||||
float lateralDelay = liveDelay.getLateralDelay();
|
||||
desc += QString("<br><br><b><span style=\"color:#e0e0e0\">%1</span></b> <span style=\"color:#e0e0e0\">%2 s</span>")
|
||||
.arg(tr("Live Steer Delay:")).arg(QString::number(lateralDelay, 'f', 3));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto carParamsBytes = params.get("CarParamsPersistent");
|
||||
std::string carParamsBytes = params.get("CarParamsPersistent");
|
||||
if (!carParamsBytes.empty()) {
|
||||
AlignedBuffer aligned_buf_cp;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf_cp.align(carParamsBytes.data(), carParamsBytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
float steerDelay = CP.getSteerActuatorDelay();
|
||||
capnp::FlatArrayMessageReader msg(kj::ArrayPtr<const capnp::word>(
|
||||
reinterpret_cast<const capnp::word*>(carParamsBytes.data()),
|
||||
carParamsBytes.size() / sizeof(capnp::word)));
|
||||
auto carParams = msg.getRoot<cereal::CarParams>();
|
||||
float steerDelay = carParams.getSteerActuatorDelay();
|
||||
float softwareDelay = QString::fromStdString(params.get("LagdToggleDelay")).toFloat();
|
||||
float totalLag = steerDelay + softwareDelay;
|
||||
desc += QString("<br><br><span style=\"color:#e0e0e0\">"
|
||||
"<b>%1</b> %2 s + <b>%3</b> %4 s = <b>%5</b> %6 s</span>")
|
||||
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
|
||||
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
|
||||
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
|
||||
.arg(tr("Actuator Delay:"), QString::number(steerDelay, 'f', 2),
|
||||
tr("Software Delay:"), QString::number(softwareDelay, 'f', 2),
|
||||
tr("Total Delay:"), QString::number(totalLag, 'f', 2));
|
||||
}
|
||||
}
|
||||
lagd_toggle_control->setDescription(desc);
|
||||
@@ -456,9 +439,6 @@ void ModelsPanel::updateLabels() {
|
||||
delay_control->setLabel(QString::number(value, 'f', 2) + "s");
|
||||
}
|
||||
|
||||
// Update lane turn desire label and visibility
|
||||
refreshLaneTurnValueControl();
|
||||
|
||||
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <QProgressBar>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
|
||||
class ModelsPanel : public QWidget {
|
||||
@@ -38,7 +37,6 @@ private:
|
||||
void updateLabels();
|
||||
void handleCurrentModelLblBtnClicked();
|
||||
void handleBundleDownloadProgress();
|
||||
void refreshLaneTurnValueControl();
|
||||
void showResetParamsDialog();
|
||||
QProgressBar* createProgressBar(QWidget *parent);
|
||||
QFrame* createModelDetailFrame(QWidget *parent, QString &typeName, QProgressBar *progressBar);
|
||||
@@ -83,6 +81,5 @@ private:
|
||||
Params params;
|
||||
ButtonControlSP *clearModelCacheBtn;
|
||||
ButtonControlSP *refreshAvailableModelsBtn;
|
||||
ParamControlSP *lane_turn_desire_toggle;
|
||||
OptionControlSP *lane_turn_value_control;
|
||||
|
||||
};
|
||||
|
||||
@@ -11,39 +11,12 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
|
||||
// branch selector
|
||||
QObject::disconnect(targetBranchBtn, nullptr, nullptr, nullptr);
|
||||
connect(targetBranchBtn, &ButtonControlSP::clicked, [=]() {
|
||||
if (Hardware::get_device_type() == cereal::InitData::DeviceType::TICI) {
|
||||
auto current = params.get("GitBranch");
|
||||
QStringList allBranches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
|
||||
QStringList branches;
|
||||
for (const QString &b : allBranches) {
|
||||
if (b.endsWith("-tici")) {
|
||||
branches.append(b);
|
||||
}
|
||||
}
|
||||
|
||||
for (QString b : {current.c_str(), "master-tici", "staging-tici", "release-tici"}) {
|
||||
auto i = branches.indexOf(b);
|
||||
if (i >= 0) {
|
||||
branches.removeAt(i);
|
||||
branches.insert(0, b);
|
||||
}
|
||||
}
|
||||
|
||||
QString cur = QString::fromStdString(params.get("UpdaterTargetBranch"));
|
||||
QString selection = MultiOptionDialog::getSelection(tr("Select a branch"), branches, cur, this);
|
||||
if (!selection.isEmpty()) {
|
||||
params.put("UpdaterTargetBranch", selection.toStdString());
|
||||
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
|
||||
checkForUpdates();
|
||||
}
|
||||
} else {
|
||||
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
|
||||
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
|
||||
d.setMinLength(0);
|
||||
const int ret = d.exec();
|
||||
if (ret) {
|
||||
searchBranches(d.text());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Disable Updates toggle
|
||||
|
||||
@@ -86,154 +86,5 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea
|
||||
} else {
|
||||
// Normal path rendering
|
||||
ModelRenderer::drawPath(painter, model, surface_rect.height());
|
||||
|
||||
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) {
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
|
||||
bool longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
||||
if (!longitudinal_control) {
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sm.alive("radarState")) {
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &radar_state = sm["radarState"].getRadarState();
|
||||
const auto &lead_one = radar_state.getLeadOne();
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
|
||||
bool has_lead_one = lead_one.getStatus();
|
||||
bool has_lead_two = lead_two.getStatus();
|
||||
|
||||
if (!has_lead_one && !has_lead_two) {
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
if (lead_status_alpha <= 0.0f) return;
|
||||
} else {
|
||||
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
|
||||
}
|
||||
|
||||
if (has_lead_one) {
|
||||
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
|
||||
}
|
||||
|
||||
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
|
||||
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter,
|
||||
const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos,
|
||||
int height, int width,
|
||||
const QString &label) {
|
||||
float d_rel = lead_data.getDRel();
|
||||
float v_rel = lead_data.getVRel();
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
float v_ego = sm["carState"].getCarState().getVEgo();
|
||||
|
||||
int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
|
||||
|
||||
QFont content_font = painter.font();
|
||||
content_font.setPixelSize(42);
|
||||
content_font.setBold(true);
|
||||
painter.setFont(content_font);
|
||||
|
||||
bool is_metric = s->scene.is_metric;
|
||||
QStringList text_lines;
|
||||
|
||||
const int chevron_all = 4;
|
||||
QStringList chevron_text[3];
|
||||
|
||||
// Distance display
|
||||
if (chevron_data == 1 || chevron_data == chevron_all) {
|
||||
int pos = 0;
|
||||
float val = std::max(0.0f, d_rel);
|
||||
QString unit = is_metric ? "m" : "ft";
|
||||
if (!is_metric) val *= 3.28084f;
|
||||
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
|
||||
}
|
||||
|
||||
// Speed display
|
||||
if (chevron_data == 2 || chevron_data == chevron_all) {
|
||||
int pos = (chevron_data == 2) ? 0 : 1;
|
||||
float multiplier = is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH);
|
||||
float val = std::max(0.0f, (v_rel + v_ego) * multiplier);
|
||||
QString unit = is_metric ? "km/h" : "mph";
|
||||
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
|
||||
}
|
||||
|
||||
// Time to contact
|
||||
if (chevron_data == 3 || chevron_data == chevron_all) {
|
||||
int pos = (chevron_data == 3) ? 0 : 2;
|
||||
float val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
|
||||
QString ttc = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
|
||||
chevron_text[pos].append(ttc);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!chevron_text[i].isEmpty()) {
|
||||
text_lines.append(chevron_text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (text_lines.isEmpty()) return;
|
||||
|
||||
QFontMetrics fm(content_font);
|
||||
float text_width = 120.0f;
|
||||
for (const QString &line : text_lines) {
|
||||
text_width = std::max(text_width, fm.horizontalAdvance(line) + 20.0f);
|
||||
}
|
||||
text_width = std::min(text_width, 250.0f);
|
||||
|
||||
float line_height = 45.0f;
|
||||
float total_height = text_lines.size() * line_height;
|
||||
float margin = 20.0f;
|
||||
|
||||
float text_y = chevron_pos.y() + sz + 15;
|
||||
if (text_y + total_height > height - margin) {
|
||||
text_y = chevron_pos.y() - sz - 15 - total_height;
|
||||
text_y = std::max(margin, text_y);
|
||||
}
|
||||
|
||||
float text_x = chevron_pos.x() - text_width / 2;
|
||||
text_x = std::clamp(text_x, margin, (float)width - text_width - margin);
|
||||
|
||||
QPoint shadow_offset(2, 2);
|
||||
|
||||
for (int i = 0; i < text_lines.size(); ++i) {
|
||||
float y = text_y + (i * line_height);
|
||||
if (y + line_height > height - margin) break;
|
||||
|
||||
QRect rect(text_x, y, text_width, line_height);
|
||||
|
||||
// Draw shadow
|
||||
painter.setPen(QColor(0, 0, 0, (int)(200 * lead_status_alpha)));
|
||||
painter.drawText(rect.translated(shadow_offset), Qt::AlignCenter, text_lines[i]);
|
||||
|
||||
QColor text_color = QColor(255, 255, 255, (int)(255 * lead_status_alpha));
|
||||
if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
|
||||
if (d_rel < 20.0f) {
|
||||
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha));
|
||||
} else if (d_rel < 40.0f) {
|
||||
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha));
|
||||
} else {
|
||||
text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha));
|
||||
}
|
||||
}
|
||||
|
||||
painter.setPen(text_color);
|
||||
painter.drawText(rect, Qt::AlignCenter, text_lines[i]);
|
||||
}
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
}
|
||||
|
||||
@@ -17,17 +17,6 @@ private:
|
||||
void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override;
|
||||
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override;
|
||||
|
||||
// Lead status display methods
|
||||
void drawLeadStatus(QPainter &painter, int height, int width);
|
||||
void drawLeadStatusAtPosition(QPainter &painter,
|
||||
const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos,
|
||||
int height, int width,
|
||||
const QString &label);
|
||||
|
||||
QPolygonF left_blindspot_vertices;
|
||||
QPolygonF right_blindspot_vertices;
|
||||
|
||||
// Lead status animation
|
||||
float lead_status_alpha = 0.0f;
|
||||
};
|
||||
|
||||
@@ -110,16 +110,3 @@ QStringList searchFromList(const QString &query, const QStringList &list) {
|
||||
}
|
||||
return search_results;
|
||||
}
|
||||
|
||||
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param) {
|
||||
std::string bytes = params.get(_param);
|
||||
|
||||
try {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(bytes.data(), bytes.size()));
|
||||
return cmsg.getRoot<cereal::Event>();
|
||||
} catch (kj::Exception& e) {
|
||||
qInfo() << "invalid " << QString::fromStdString(_param) << ":" << e.getDescription().cStr();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,8 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
|
||||
QString getUserAgent(bool sunnylink = false);
|
||||
std::optional<QString> getSunnylinkDongleId();
|
||||
std::optional<QString> getParamIgnoringDefault(const std::string ¶m_name, const std::string &default_value);
|
||||
QMap<QString, QVariantMap> loadPlatformList();
|
||||
QStringList searchFromList(const QString &query, const QStringList &list);
|
||||
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param);
|
||||
|
||||
@@ -177,7 +177,7 @@ def main(demo=False):
|
||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
@@ -304,7 +304,6 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
posenet_send = messaging.new_message('cameraOdometry')
|
||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||
action = model.get_action_from_model(model_output, prev_action, long_delay + DT_MDL)
|
||||
fill_model_msg(drivingdata_send, modelv2_send, model_output, action, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
||||
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen,
|
||||
@@ -317,7 +316,6 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -325,7 +323,6 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
|
||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad"
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld"
|
||||
|
||||
|
||||
class FrameMeta:
|
||||
@@ -77,47 +77,42 @@ class ModelState(ModelStateBase):
|
||||
self.numpy_inputs[key] = np.zeros(shape, dtype=np.float32)
|
||||
# Temporal input: shape is [batch, history, features]
|
||||
if len(shape) == 3 and shape[1] > 1:
|
||||
buffer_history_len = shape[1] * 4 if shape[1] < 99 else shape[1] # Allow for higher history buffers in the future
|
||||
buffer_history_len = max(100, (shape[1] * 4 if shape[1] < 100 else shape[1])) # Allow for higher history buffers in the future
|
||||
feature_len = shape[2]
|
||||
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
|
||||
features_buffer_shape = self.model_runner.input_shapes.get('features_buffer')
|
||||
if shape[1] in (24, 25) and features_buffer_shape is not None and features_buffer_shape[1] == 24: # 20Hz
|
||||
buffer_history_len = (features_buffer_shape[1] + 1) * 4
|
||||
step = int(-buffer_history_len / shape[1])
|
||||
self.temporal_idxs_map[key] = np.arange(step, step * (shape[1] + 1), step)[::-1]
|
||||
elif shape[1] == 25: # Split
|
||||
skip = buffer_history_len // shape[1]
|
||||
self.temporal_idxs_map[key] = np.arange(buffer_history_len)[-1 - (skip * (shape[1] - 1))::skip]
|
||||
elif shape[1] >= 99: # non20hz
|
||||
self.temporal_idxs_map[key] = np.arange(shape[1])
|
||||
self.temporal_buffers[key] = np.zeros((1, buffer_history_len, feature_len), dtype=np.float32)
|
||||
elif shape[1] == buffer_history_len: # non20hz
|
||||
self.temporal_idxs_map[key] = np.arange(buffer_history_len)
|
||||
|
||||
@property
|
||||
def mlsim(self) -> bool:
|
||||
return bool(self.generation is not None and self.generation >= 11)
|
||||
|
||||
@property
|
||||
def desire_key(self) -> str:
|
||||
return next(key for key in self.numpy_inputs if key.startswith('desire'))
|
||||
|
||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
||||
# Model decides when action is completed, so desire input is just a pulse triggered on rising edge
|
||||
inputs[self.desire_key][0] = 0
|
||||
new_desire = np.where(inputs[self.desire_key] - self.prev_desire > .99, inputs[self.desire_key], 0)
|
||||
self.prev_desire[:] = inputs[self.desire_key]
|
||||
self.temporal_buffers[self.desire_key][0,:-1] = self.temporal_buffers[self.desire_key][0,1:]
|
||||
self.temporal_buffers[self.desire_key][0,-1] = new_desire
|
||||
inputs['desire'][0] = 0
|
||||
new_desire = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0)
|
||||
self.prev_desire[:] = inputs['desire']
|
||||
self.temporal_buffers['desire'][0,:-1] = self.temporal_buffers['desire'][0,1:]
|
||||
self.temporal_buffers['desire'][0,-1] = new_desire
|
||||
|
||||
# Roll buffer and assign based on desire.shape[1] value
|
||||
if self.temporal_buffers[self.desire_key].shape[1] > self.numpy_inputs[self.desire_key].shape[1]:
|
||||
skip = self.temporal_buffers[self.desire_key].shape[1] // self.numpy_inputs[self.desire_key].shape[1]
|
||||
self.numpy_inputs[self.desire_key][:] = (self.temporal_buffers[self.desire_key][0].reshape(
|
||||
self.numpy_inputs[self.desire_key].shape[0], self.numpy_inputs[self.desire_key].shape[1], skip, -1).max(axis=2))
|
||||
if self.temporal_buffers['desire'].shape[1] > self.numpy_inputs['desire'].shape[1]:
|
||||
skip = self.temporal_buffers['desire'].shape[1] // self.numpy_inputs['desire'].shape[1]
|
||||
self.numpy_inputs['desire'][:] = (
|
||||
self.temporal_buffers['desire'][0].reshape(self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], skip, -1).max(axis=2))
|
||||
else:
|
||||
self.numpy_inputs[self.desire_key][:] = self.temporal_buffers[self.desire_key][0, self.temporal_idxs_map[self.desire_key]]
|
||||
self.numpy_inputs['desire'][:] = self.temporal_buffers['desire'][0, self.temporal_idxs_map['desire']]
|
||||
|
||||
for key in self.numpy_inputs:
|
||||
if key in inputs and key not in [self.desire_key]:
|
||||
if key in inputs and key not in ['desire']:
|
||||
self.numpy_inputs[key][:] = inputs[key]
|
||||
|
||||
imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.model_runner.vision_input_names}
|
||||
@@ -161,11 +156,10 @@ class ModelState(ModelStateBase):
|
||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
|
||||
|
||||
desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.mlsim)
|
||||
if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models
|
||||
if v_ego > self.MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
desired_curvature = prev_action.desiredCurvature
|
||||
if v_ego > self.MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
desired_curvature = prev_action.desiredCurvature
|
||||
|
||||
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),desiredAcceleration=float(desired_accel), shouldStop=bool(should_stop))
|
||||
|
||||
@@ -208,7 +202,7 @@ def main(demo=False):
|
||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||
|
||||
# messaging
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
@@ -312,7 +306,7 @@ def main(demo=False):
|
||||
bufs = {name: buf_extra if 'big' in name else buf_main for name in model.model_runner.vision_input_names}
|
||||
transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.model_runner.vision_input_names}
|
||||
inputs:dict[str, np.ndarray] = {
|
||||
model.desire_key: vec_desire,
|
||||
'desire': vec_desire,
|
||||
'traffic_convention': traffic_convention,
|
||||
}
|
||||
|
||||
@@ -328,7 +322,6 @@ def main(demo=False):
|
||||
modelv2_send = messaging.new_message('modelV2')
|
||||
drivingdata_send = messaging.new_message('drivingModelData')
|
||||
posenet_send = messaging.new_message('cameraOdometry')
|
||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
||||
|
||||
action = model.get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego)
|
||||
prev_action = action
|
||||
@@ -343,7 +336,6 @@ def main(demo=False):
|
||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
@@ -351,7 +343,6 @@ def main(demo=False):
|
||||
pm.send('modelV2', modelv2_send)
|
||||
pm.send('drivingModelData', drivingdata_send)
|
||||
pm.send('cameraOdometry', posenet_send)
|
||||
pm.send('modelDataV2SP', mdv2sp_send)
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
|
||||
|
||||
|
||||
@@ -8,16 +8,12 @@ import openpilot.sunnypilot.modeld_v2.modeld as modeld_module
|
||||
|
||||
ModelState = modeld_module.ModelState
|
||||
|
||||
|
||||
# These are the shapes extracted/loaded from the model onnx
|
||||
SHAPE_MODE_PARAMS = [
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "nav_features": (1, 256), "nav_instructions": (1, 150)}, 'non20hz'), # Optimus Prime
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "lat_planner_state": (1, 4),}, 'non20hz'), # farmville
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), "lateral_control_params": (1, 2), "prev_desired_curv": (1, 100, 1)}, 'non20hz'), # wd40
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1), "lateral_control_params": (1, 2),}, 'non20hz'), # NTS
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512)}, '20hz'), # NPR
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1), "lateral_control_params": (1, 2),}, 'non20hz'), # NTS
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512)}, 'split'), # Steam Powered v2
|
||||
({'desire_pulse': (1, 25, 8), 'features_buffer': (1, 25, 512)}, 'split'), # desire rename
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 25, 512), 'prev_desired_curv': (1, 25, 1)}, 'split'),
|
||||
({'desire': (1, 25, 8), 'features_buffer': (1, 24, 512), 'prev_desired_curv': (1, 25, 1)}, '20hz'),
|
||||
({'desire': (1, 100, 8), 'features_buffer': (1, 99, 512), 'prev_desired_curv': (1, 100, 1)}, 'non20hz'),
|
||||
]
|
||||
|
||||
|
||||
@@ -99,7 +95,9 @@ def get_expected_indices(shape, constants, mode, key=None):
|
||||
idxs = np.arange(step_size, step_size * (num_elements + 1), step_size)[::-1]
|
||||
return idxs
|
||||
elif mode == 'non20hz':
|
||||
return np.arange(shape[1])
|
||||
if key and shape[1] == constants.FULL_HISTORY_BUFFER_LEN:
|
||||
return np.arange(constants.FULL_HISTORY_BUFFER_LEN)
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
@@ -110,8 +108,6 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
|
||||
for key in shapes:
|
||||
buf = state.temporal_buffers.get(key, None)
|
||||
idxs = state.temporal_idxs_map.get(key, None)
|
||||
if buf is None:
|
||||
continue # not all shapes are 3D, and the non-3D ones are not buffered
|
||||
# Buffer shape logic
|
||||
if mode == 'split':
|
||||
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
|
||||
@@ -120,7 +116,10 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
|
||||
expected_shape = (1, constants.FULL_HISTORY_BUFFER_LEN, shapes[key][2])
|
||||
expected_idxs = get_expected_indices(shapes[key], constants, '20hz', key)
|
||||
elif mode == 'non20hz':
|
||||
expected_shape = (1, shapes[key][1], shapes[key][2])
|
||||
if key == 'features_buffer':
|
||||
expected_shape = (1, shapes[key][1]*4, shapes[key][2])
|
||||
else:
|
||||
expected_shape = (1, shapes[key][1], shapes[key][2])
|
||||
expected_idxs = get_expected_indices(shapes[key], constants, 'non20hz', key)
|
||||
|
||||
assert buf is not None, f"{key}: buffer not found"
|
||||
@@ -131,10 +130,10 @@ def test_buffer_shapes_and_indices(shapes, mode, apply_patches):
|
||||
assert idxs is None or idxs.size == 0, f"{key}: buffer idxs should be None or empty"
|
||||
|
||||
|
||||
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs, input_shape, prev_desire=None):
|
||||
def legacy_buffer_update(buf, new_val, mode, key, constants, idxs):
|
||||
# This is what we compare the new dynamic logic to, to ensure it does the same thing
|
||||
if mode == 'split':
|
||||
if key == 'desire' or key.startswith('desire'):
|
||||
if key == 'desire':
|
||||
buf[0,:-1] = buf[0,1:]
|
||||
buf[0,-1] = new_val
|
||||
return buf.reshape((1, constants.INPUT_HISTORY_BUFFER_LEN, constants.TEMPORAL_SKIP, -1)).max(axis=2)
|
||||
@@ -174,22 +173,15 @@ def legacy_buffer_update(buf, new_val, mode, key, constants, idxs, input_shape,
|
||||
return legacy_buf[idxs]
|
||||
elif mode == 'non20hz':
|
||||
if key == 'desire':
|
||||
desire_len = constants.DESIRE_LEN
|
||||
if prev_desire is None:
|
||||
prev_desire = np.zeros(desire_len, dtype=np.float32)
|
||||
# Set first element to zero
|
||||
new_val = new_val.copy()
|
||||
new_val[0] = 0
|
||||
# Shift buffer by desire len
|
||||
buf[0][:-desire_len] = buf[0][desire_len:]
|
||||
# Only insert new desire if rising edge
|
||||
buf[0][-desire_len:] = np.where(new_val - prev_desire > 0.99, new_val, 0)
|
||||
prev_desire[:] = new_val
|
||||
length = new_val.shape[0]
|
||||
buf[0,:-1,:length] = buf[0,1:,:length]
|
||||
buf[0,-1,:length] = new_val[:length]
|
||||
return buf[0]
|
||||
elif key == 'features_buffer':
|
||||
buf[0, :-1] = buf[0, 1:]
|
||||
buf[0, -1] = new_val
|
||||
return buf[0, -input_shape[1]:] # (99, 512)
|
||||
feature_len = new_val.shape[0]
|
||||
buf[0,:-1,:feature_len] = buf[0,1:,:feature_len]
|
||||
buf[0,-1,:feature_len] = new_val[:feature_len]
|
||||
return buf[0]
|
||||
elif key == 'prev_desired_curv':
|
||||
length = new_val.shape[0]
|
||||
buf[0,:-length,0] = buf[0,length:,0]
|
||||
@@ -199,18 +191,32 @@ def legacy_buffer_update(buf, new_val, mode, key, constants, idxs, input_shape,
|
||||
|
||||
|
||||
def dynamic_buffer_update(state, key, new_val, mode):
|
||||
if key == 'desire' or key.startswith('desire'):
|
||||
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
|
||||
for k, v in state.model_runner.input_shapes.items() if k != key}
|
||||
inputs[key] = new_val.copy()
|
||||
# ModelState.run expects desire as a pulse, so we zero the first element.
|
||||
inputs[key][0] = 0
|
||||
state.run({}, {}, inputs, prepare_only=False)
|
||||
return state.numpy_inputs[key]
|
||||
if key == 'desire':
|
||||
state.temporal_buffers['desire'][0,:-1] = state.temporal_buffers['desire'][0,1:]
|
||||
state.temporal_buffers['desire'][0,-1] = new_val
|
||||
if state.temporal_buffers['desire'].shape[1] > state.numpy_inputs['desire'].shape[1]:
|
||||
skip = state.temporal_buffers['desire'].shape[1] // state.numpy_inputs['desire'].shape[1]
|
||||
return state.temporal_buffers['desire'][0].reshape(
|
||||
state.numpy_inputs['desire'].shape[0], state.numpy_inputs['desire'].shape[1], skip, -1
|
||||
).max(axis=2)
|
||||
else:
|
||||
return state.temporal_buffers['desire'][0, state.temporal_idxs_map['desire']]
|
||||
|
||||
inputs = {'desire': np.zeros((1, state.constants.DESIRE_LEN), dtype=np.float32)}
|
||||
for k, tb in state.temporal_buffers.items():
|
||||
if k in state.temporal_idxs_map:
|
||||
continue
|
||||
buf_len = tb.shape[1]
|
||||
if k in state.numpy_inputs:
|
||||
out_len = state.numpy_inputs[k].shape[1]
|
||||
if out_len <= buf_len:
|
||||
state.temporal_idxs_map[k] = np.arange(buf_len)[-out_len:]
|
||||
else:
|
||||
state.temporal_idxs_map[k] = np.arange(buf_len)
|
||||
else:
|
||||
state.temporal_idxs_map[k] = np.arange(buf_len)
|
||||
|
||||
if key == 'features_buffer':
|
||||
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
|
||||
for k, v in state.model_runner.input_shapes.items() if k != 'features_buffer'}
|
||||
def run_model_stub():
|
||||
return {
|
||||
'hidden_state': np.asarray(new_val, dtype=np.float32).reshape(1, -1),
|
||||
@@ -220,8 +226,6 @@ def dynamic_buffer_update(state, key, new_val, mode):
|
||||
return state.numpy_inputs['features_buffer'][0]
|
||||
|
||||
if key == 'prev_desired_curv':
|
||||
inputs = {k: np.zeros(v[2], dtype=np.float32) if len(v) == 3 else np.zeros(v[1], dtype=np.float32)
|
||||
for k, v in state.model_runner.input_shapes.items() if k != 'prev_desired_curv'}
|
||||
def run_model_stub():
|
||||
return {
|
||||
'hidden_state': np.zeros((1, state.constants.FEATURE_LEN), dtype=np.float32),
|
||||
@@ -237,27 +241,16 @@ def dynamic_buffer_update(state, key, new_val, mode):
|
||||
@pytest.mark.parametrize("key", ["desire", "features_buffer", "prev_desired_curv"])
|
||||
def test_buffer_update_equivalence(shapes, mode, key, apply_patches):
|
||||
state = ModelState(None)
|
||||
if key == "desire":
|
||||
desire_keys = [k for k in shapes.keys() if k.startswith('desire')]
|
||||
if desire_keys:
|
||||
actual_key = desire_keys[0] # Use the first (and likely only) desire key
|
||||
else:
|
||||
actual_key = key
|
||||
|
||||
if actual_key not in state.numpy_inputs:
|
||||
pytest.skip()
|
||||
|
||||
constants = DummyModelRunner(shapes).constants
|
||||
buf = state.temporal_buffers.get(actual_key, None)
|
||||
idxs = state.temporal_idxs_map.get(actual_key, None)
|
||||
input_shape = shapes[actual_key]
|
||||
prev_desire = np.zeros(constants.DESIRE_LEN, dtype=np.float32) if key == 'desire' else None
|
||||
|
||||
buf = state.temporal_buffers.get(key, None)
|
||||
idxs = state.temporal_idxs_map.get(key, None)
|
||||
input_shape = shapes[key]
|
||||
for step in range(20): # multiple steps to ensure history is built up
|
||||
new_val = np.full((input_shape[2],), step, dtype=np.float32)
|
||||
expected = legacy_buffer_update(buf, new_val, mode, actual_key, constants, idxs, input_shape, prev_desire)
|
||||
actual = dynamic_buffer_update(state, actual_key, new_val, mode)
|
||||
expected = legacy_buffer_update(buf, new_val, mode, key, constants, idxs)
|
||||
actual = dynamic_buffer_update(state, key, new_val, mode)
|
||||
# Model returns the reduced numpy_inputs history, compare the last n entries so the test is checking the same slices.
|
||||
if expected is not None and actual is not None and expected.shape != actual.shape:
|
||||
if expected.ndim == 2 and actual.ndim == 2 and expected.shape[1] == actual.shape[1]:
|
||||
expected = expected[-actual.shape[0]:]
|
||||
assert np.allclose(actual, expected), f"{mode} {actual_key}: dynamic buffer update does not match legacy logic"
|
||||
assert np.allclose(actual, expected), f"{mode} {key}: dynamic buffer update does not match legacy logic"
|
||||
|
||||
@@ -8,7 +8,6 @@ See the LICENSE.md file in the root directory for more details.
|
||||
import time
|
||||
|
||||
import requests
|
||||
from requests.exceptions import (SSLError, RequestException, HTTPError)
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from sunnypilot.models.helpers import is_bundle_version_compatible
|
||||
@@ -123,36 +122,19 @@ class ModelFetcher:
|
||||
self.model_cache = ModelCache(params)
|
||||
self.model_parser = ModelParser()
|
||||
|
||||
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
||||
"""Fetches fresh model data from remote and updates cache.
|
||||
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
|
||||
"""
|
||||
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
"""Fetches fresh model data from remote and updates cache"""
|
||||
try:
|
||||
response = requests.get(self.MODEL_URL, timeout=10)
|
||||
|
||||
# Explicitly handle 404 differently
|
||||
if response.status_code == 404:
|
||||
cloudlog.error(f"Models URL returned 404 Not Found: {self.MODEL_URL}")
|
||||
raise HTTPError(f"404 Not Found: {self.MODEL_URL}", response=response)
|
||||
|
||||
# Raise for any other 4xx/5xx
|
||||
response.raise_for_status()
|
||||
|
||||
json_data = response.json()
|
||||
|
||||
self.model_cache.set(json_data)
|
||||
cloudlog.debug("Successfully updated models cache")
|
||||
return self.model_parser.parse_models(json_data)
|
||||
|
||||
except ConnectionError as e:
|
||||
cloudlog.warning(f"DNS/connection error while fetching models: {e}")
|
||||
except SSLError as e:
|
||||
cloudlog.warning(f"SSL error while fetching models: {e}")
|
||||
except RequestException as e:
|
||||
cloudlog.warning(f"Request transport error while fetching models: {e}")
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"Unexpected error fetching models: {e}")
|
||||
|
||||
return None
|
||||
except Exception:
|
||||
cloudlog.exception("Error fetching models")
|
||||
raise
|
||||
|
||||
def get_available_bundles(self) -> list[custom.ModelManagerSP.ModelBundle]:
|
||||
"""Gets the list of available models, with smart cache handling"""
|
||||
@@ -162,12 +144,12 @@ class ModelFetcher:
|
||||
cloudlog.debug("Using valid cached models data")
|
||||
return self.model_parser.parse_models(cached_data)
|
||||
|
||||
fetched_bundles = self._fetch_and_cache_models()
|
||||
if fetched_bundles is not None:
|
||||
return fetched_bundles
|
||||
|
||||
if not cached_data:
|
||||
cloudlog.warning("Failed to fetch fresh data and no cache available")
|
||||
try:
|
||||
return self._fetch_and_cache_models()
|
||||
except Exception:
|
||||
if not cached_data:
|
||||
cloudlog.exception("Failed to fetch fresh data and no cache available")
|
||||
raise
|
||||
|
||||
cloudlog.warning("Failed to fetch fresh data. Using expired cache as fallback")
|
||||
return self.model_parser.parse_models(cached_data)
|
||||
|
||||
@@ -19,7 +19,7 @@ from openpilot.system.hardware.hw import Paths
|
||||
from pathlib import Path
|
||||
|
||||
# see the README.md for more details on the model selector versioning
|
||||
CURRENT_SELECTOR_VERSION = 10
|
||||
CURRENT_SELECTOR_VERSION = 9
|
||||
REQUIRED_MIN_SELECTOR_VERSION = 9
|
||||
|
||||
USE_ONNX = os.getenv('USE_ONNX', PC)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
from cereal import custom
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
|
||||
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
|
||||
|
||||
|
||||
class LaneTurnController:
|
||||
def __init__(self, desire_helper):
|
||||
self.DH = desire_helper
|
||||
self.turn_direction = custom.TurnDirection.none
|
||||
self.params = Params()
|
||||
self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS
|
||||
self.param_read_counter = 0
|
||||
self.enabled = self.params.get_bool("LaneTurnDesire")
|
||||
|
||||
def read_params(self):
|
||||
self.enabled = self.params.get_bool("LaneTurnDesire")
|
||||
value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS
|
||||
self.lane_turn_value = min(float(LANE_CHANGE_SPEED_MIN), value)
|
||||
|
||||
def update_params(self) -> None:
|
||||
if self.param_read_counter % 50 == 0:
|
||||
self.read_params()
|
||||
self.param_read_counter += 1
|
||||
|
||||
def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None:
|
||||
if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left:
|
||||
self.turn_direction = custom.TurnDirection.turnLeft
|
||||
elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right:
|
||||
self.turn_direction = custom.TurnDirection.turnRight
|
||||
else:
|
||||
self.turn_direction = custom.TurnDirection.none
|
||||
|
||||
def get_turn_direction(self):
|
||||
if not self.enabled:
|
||||
return custom.TurnDirection.none
|
||||
return self.turn_direction
|
||||
@@ -1,113 +0,0 @@
|
||||
import pytest
|
||||
from cereal import log
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController, LANE_CHANGE_SPEED_MIN
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode
|
||||
|
||||
|
||||
class TurnDirection:
|
||||
none = 0
|
||||
turnLeft = 1
|
||||
turnRight = 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("left_blinker,right_blinker,v_ego,blindspot_left,blindspot_right,expected", [
|
||||
(True, False, 5, False, False, TurnDirection.turnLeft),
|
||||
(False, True, 6, False, False, TurnDirection.turnRight),
|
||||
(True, False, 9, False, False, TurnDirection.none),
|
||||
(True, False, 7, True, False, TurnDirection.none),
|
||||
(False, True, 6, False, True, TurnDirection.none),
|
||||
(False, False, 5, False, False, TurnDirection.none),
|
||||
(True, True, 5, False, False, TurnDirection.none),
|
||||
])
|
||||
def test_lane_turn_desire_conditions(left_blinker, right_blinker, v_ego, blindspot_left, blindspot_right, expected):
|
||||
dh = DesireHelper()
|
||||
controller = LaneTurnController(dh)
|
||||
controller.enabled = True
|
||||
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
|
||||
controller.turn_direction = TurnDirection.none
|
||||
controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego)
|
||||
assert controller.get_turn_direction() == expected
|
||||
|
||||
|
||||
def test_lane_turn_desire_disabled():
|
||||
dh = DesireHelper()
|
||||
controller = LaneTurnController(dh)
|
||||
controller.enabled = False
|
||||
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
|
||||
controller.turn_direction = TurnDirection.none
|
||||
controller.update_lane_turn(False, False, True, False, 7)
|
||||
assert controller.get_turn_direction() == TurnDirection.none
|
||||
|
||||
|
||||
def test_lane_turn_overrides_lane_change():
|
||||
dh = DesireHelper()
|
||||
controller = LaneTurnController(dh)
|
||||
controller.enabled = True
|
||||
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
|
||||
controller.turn_direction = TurnDirection.none
|
||||
# left turn desire
|
||||
controller.update_lane_turn(False, False, True, False, 5)
|
||||
assert controller.get_turn_direction() == TurnDirection.turnLeft
|
||||
# right turn desire
|
||||
controller.update_lane_turn(False, False, False, True, 6)
|
||||
assert controller.get_turn_direction() == TurnDirection.turnRight
|
||||
# no turn
|
||||
controller.update_lane_turn(False, False, False, False, 7)
|
||||
assert controller.get_turn_direction() == TurnDirection.none
|
||||
|
||||
|
||||
@pytest.mark.parametrize("v_ego,expected", [
|
||||
(8.93, TurnDirection.turnLeft), # just below threshold
|
||||
(8.96, TurnDirection.none), # above threshold
|
||||
(8.95, TurnDirection.none), # just above threshold
|
||||
])
|
||||
def test_lane_turn_desire_speed_boundary(v_ego, expected):
|
||||
dh = DesireHelper()
|
||||
controller = LaneTurnController(dh)
|
||||
controller.enabled = True
|
||||
controller.lane_turn_value = LANE_CHANGE_SPEED_MIN
|
||||
controller.turn_direction = TurnDirection.none
|
||||
controller.update_lane_turn(False, True, True, False, v_ego)
|
||||
assert controller.get_turn_direction() == expected
|
||||
|
||||
|
||||
class DummyCarState:
|
||||
def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
|
||||
steeringPressed=False, steeringTorque=0, brakePressed=False):
|
||||
self.vEgo = vEgo
|
||||
self.leftBlinker = leftBlinker
|
||||
self.rightBlinker = rightBlinker
|
||||
self.leftBlindspot = leftBlindspot
|
||||
self.rightBlindspot = rightBlindspot
|
||||
self.steeringPressed = steeringPressed
|
||||
self.steeringTorque = steeringTorque
|
||||
self.brakePressed = brakePressed
|
||||
|
||||
@pytest.fixture
|
||||
def set_lane_turn_params():
|
||||
params = Params()
|
||||
params.put("LaneTurnDesire", True)
|
||||
params.put("LaneTurnValue", 20.0)
|
||||
|
||||
@pytest.mark.parametrize("carstate, lateral_active, lane_change_prob, expected_desire", [
|
||||
# Lane turn desire overrides lane change desire
|
||||
(DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnLeft),
|
||||
(DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnRight),
|
||||
# Lane change desire only (no turn desires)
|
||||
(DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
|
||||
steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft),
|
||||
(DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False,
|
||||
steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight),
|
||||
# No desire (inactive)
|
||||
(DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none),
|
||||
(DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire!
|
||||
])
|
||||
def test_desire_helper_integration(carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params):
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
|
||||
for _ in range(10):
|
||||
dh.update(carstate, lateral_active, lane_change_prob)
|
||||
assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers
|
||||
@@ -132,21 +132,6 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
|
||||
EventNameSP.pedalPressedAlertOnly: {
|
||||
ET.WARNING: NoEntryAlert("Pedal Pressed")
|
||||
},
|
||||
|
||||
EventNameSP.laneTurnLeft: {
|
||||
ET.WARNING: Alert(
|
||||
"Turning Left",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.small,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1.),
|
||||
},
|
||||
|
||||
EventNameSP.laneTurnRight: {
|
||||
ET.WARNING: Alert(
|
||||
"Turning Right",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.small,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1.),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import errno
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import ssl
|
||||
import threading
|
||||
@@ -19,11 +17,11 @@ from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \
|
||||
recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, start_local_proxy_shim, upload_handler
|
||||
from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException,
|
||||
create_connection, WebSocketConnectionClosedException)
|
||||
create_connection)
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from sunnypilot.sunnylink.api import SunnylinkApi
|
||||
from sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, get_param_as_byte
|
||||
from sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready
|
||||
|
||||
SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai')
|
||||
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
|
||||
@@ -50,7 +48,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None:
|
||||
threading.Thread(target=ws_ping, args=(ws, end_event), name='ws_ping'),
|
||||
threading.Thread(target=ws_queue, args=(end_event,), name='ws_queue'),
|
||||
threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'),
|
||||
threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'),
|
||||
# threading.Thread(target=sunny_log_handler, args=(end_event, comma_prime_cellular_end_event), name='log_handler'),
|
||||
# threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'),
|
||||
] + [
|
||||
threading.Thread(target=jsonrpc_handler, args=(end_event, partial(startLocalProxy, end_event),), name=f'worker_{x}')
|
||||
@@ -109,13 +107,10 @@ def ws_recv(ws: WebSocket, end_event: threading.Event) -> None:
|
||||
except WebSocketTimeoutException:
|
||||
ns_since_last_ping = int(time.monotonic() * 1e9) - last_ping
|
||||
if ns_since_last_ping > SUNNYLINK_RECONNECT_TIMEOUT_S * 1e9:
|
||||
cloudlog.warning("sunnylinkd.ws_recv.timeout")
|
||||
cloudlog.exception("sunnylinkd.ws_recv.timeout")
|
||||
end_event.set()
|
||||
except Exception as e:
|
||||
if isinstance(e, WebSocketConnectionClosedException):
|
||||
cloudlog.warning(f"sunnylinkd.ws_recv.{type(e).__name__}")
|
||||
else:
|
||||
cloudlog.exception("sunnylinkd.ws_recv.exception")
|
||||
except Exception:
|
||||
cloudlog.exception("sunnylinkd.ws_recv.exception")
|
||||
end_event.set()
|
||||
|
||||
|
||||
@@ -142,15 +137,11 @@ def ws_queue(end_event: threading.Event) -> None:
|
||||
sunnylink_api.resume_queued(timeout=29)
|
||||
resume_requested = True
|
||||
tries = 0
|
||||
except Exception as e:
|
||||
if isinstance(e, (ConnectionError, TimeoutError)):
|
||||
cloudlog.warning(f"sunnylinkd.ws_queue.resume_queued.{type(e).__name__}")
|
||||
else:
|
||||
cloudlog.exception("sunnylinkd.ws_queue.resume_queued.exception")
|
||||
|
||||
except Exception:
|
||||
cloudlog.exception("sunnylinkd.ws_queue.resume_queued.exception")
|
||||
resume_requested = False
|
||||
tries += 1
|
||||
time.sleep(backoff(tries))
|
||||
time.sleep(backoff(tries)) # Wait for the backoff time before the next attempt
|
||||
|
||||
if end_event.is_set():
|
||||
cloudlog.debug("end_event is set, exiting ws_queue thread")
|
||||
@@ -180,22 +171,16 @@ def getParamsAllKeys() -> list[str]:
|
||||
|
||||
@dispatcher.add_method
|
||||
def getParams(params_keys: list[str], compression: bool = False) -> str | dict[str, str]:
|
||||
params = Params()
|
||||
|
||||
try:
|
||||
param_keys_validated = [key for key in params_keys if key in getParamsAllKeys()]
|
||||
params_dict: dict[str, list[dict[str, str | bool | int ]]] = {"params": [
|
||||
{
|
||||
"key": key,
|
||||
"value": base64.b64encode(gzip.compress(get_param_as_byte(key)) if compression else get_param_as_byte(key)).decode('utf-8'),
|
||||
"type": int(params.get_type(key).value),
|
||||
"is_compressed": compression
|
||||
} for key in param_keys_validated
|
||||
]}
|
||||
params = Params()
|
||||
params_dict: dict[str, bytes] = {key: params.get(key) or b'' for key in params_keys}
|
||||
|
||||
response = {str(param.get('key')): str(param.get('value')) for param in params_dict.get("params", [])}
|
||||
response |= {"params": json.dumps(params_dict.get("params", []))} # Upcoming for settings v1
|
||||
return response
|
||||
# Compress the values before encoding to base64 as output from params.get is bytes and same for compression
|
||||
if compression:
|
||||
params_dict = {key: gzip.compress(value) for key, value in params_dict.items()}
|
||||
|
||||
# Last step is to encode the values to base64 and decode to utf-8 for JSON serialization
|
||||
return {key: base64.b64encode(value).decode('utf-8') for key, value in params_dict.items()}
|
||||
|
||||
except Exception as e:
|
||||
cloudlog.exception("sunnylinkd.getParams.exception", e)
|
||||
@@ -267,19 +252,14 @@ def main(exit_event: threading.Event = None):
|
||||
handle_long_poll(ws, exit_event)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
break
|
||||
except Exception as e:
|
||||
except (ConnectionError, TimeoutError, WebSocketException):
|
||||
conn_retries += 1
|
||||
params.remove("LastSunnylinkPingTime")
|
||||
except Exception:
|
||||
cloudlog.exception("sunnylinkd.main.exception")
|
||||
|
||||
if isinstance(e, (ConnectionError, TimeoutError, WebSocketException)):
|
||||
cloudlog.warning(f"sunnylinkd.main.{type(e).__name__}")
|
||||
elif isinstance(e, OSError):
|
||||
name = errno.errorcode.get(e.errno or -1, "UNKNOWN")
|
||||
msg = f"sunnylinkd.main.OSError.{name} ({e.errno})"
|
||||
is_expected_error = e.errno in (errno.ENETDOWN, errno.ENETRESET, errno.ENETUNREACH)
|
||||
cloudlog.warning(msg) if is_expected_error else cloudlog.exception(msg)
|
||||
else:
|
||||
cloudlog.exception("sunnylinkd.main.exception")
|
||||
conn_retries += 1
|
||||
params.remove("LastSunnylinkPingTime")
|
||||
|
||||
time.sleep(backoff(conn_retries))
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ from openpilot.system.version import get_version
|
||||
from cereal import messaging, custom
|
||||
from sunnypilot.sunnylink.api import SunnylinkApi
|
||||
from sunnypilot.sunnylink.backups.utils import decrypt_compressed_data, encrypt_compress_data, SnakeCaseEncoder
|
||||
from sunnypilot.sunnylink.utils import get_param_as_byte
|
||||
|
||||
|
||||
class OperationType(Enum):
|
||||
@@ -75,7 +74,7 @@ class BackupManagerSP:
|
||||
config_data = {}
|
||||
params_to_backup = [k.decode('utf-8') for k in self.params.all_keys(ParamKeyFlag.BACKUP)]
|
||||
for param in params_to_backup:
|
||||
value = get_param_as_byte(param)
|
||||
value = str(self.params.get(param)).encode('utf-8')
|
||||
if value is not None:
|
||||
config_data[param] = base64.b64encode(value).decode('utf-8')
|
||||
return config_data
|
||||
@@ -114,7 +113,6 @@ class BackupManagerSP:
|
||||
payload = json.loads(json.dumps(backup_info.to_dict(), cls=SnakeCaseEncoder))
|
||||
self._update_progress(75.0, OperationType.BACKUP)
|
||||
|
||||
cloudlog.debug(f"Uploading backup with payload: {json.dumps(payload)}")
|
||||
# Upload to sunnylink
|
||||
result = self.api.api_get(
|
||||
f"backup/{self.device_id}",
|
||||
@@ -126,11 +124,9 @@ class BackupManagerSP:
|
||||
if result:
|
||||
self.backup_status = custom.BackupManagerSP.Status.completed
|
||||
self._update_progress(100.0, OperationType.BACKUP)
|
||||
cloudlog.info("Backup successfully created and uploaded")
|
||||
else:
|
||||
self.backup_status = custom.BackupManagerSP.Status.failed
|
||||
self.last_error = "Failed to upload backup"
|
||||
cloudlog.error(result)
|
||||
self._report_status()
|
||||
|
||||
return bool(self.backup_status == custom.BackupManagerSP.Status.completed)
|
||||
@@ -268,8 +264,8 @@ class BackupManagerSP:
|
||||
# Check for backup command
|
||||
if self.params.get_bool("BackupManager_CreateBackup"):
|
||||
try:
|
||||
if await self.create_backup():
|
||||
reset_progress = True
|
||||
await self.create_backup()
|
||||
reset_progress = True
|
||||
finally:
|
||||
self.params.remove("BackupManager_CreateBackup")
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
from sunnypilot.sunnylink.api import SunnylinkApi, UNREGISTERED_SUNNYLINK_DONGLE_ID
|
||||
from openpilot.common.params import Params, ParamKeyType
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.version import is_prebuilt
|
||||
|
||||
|
||||
@@ -56,15 +55,3 @@ def get_api_token():
|
||||
sunnylink_api = SunnylinkApi(sunnylink_dongle_id)
|
||||
token = sunnylink_api.get_token()
|
||||
print(f"API Token: {token}")
|
||||
|
||||
|
||||
def get_param_as_byte(param_name: str) -> bytes:
|
||||
params = Params()
|
||||
param = params.get(param_name)
|
||||
param_type = params.get_type(param_name)
|
||||
|
||||
if param_type == ParamKeyType.BYTES:
|
||||
return bytes(param)
|
||||
elif param_type == ParamKeyType.JSON:
|
||||
return json.dumps(param).encode('utf-8')
|
||||
return str(param).encode('utf-8')
|
||||
|
||||
@@ -9,5 +9,3 @@ export VECLIB_MAXIMUM_THREADS=1
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="12.8"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
@@ -24,7 +24,7 @@ from openpilot.system.statsd import statlog
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system.hardware.power_monitoring import PowerMonitoring
|
||||
from openpilot.system.hardware.fan_controller import TiciFanController
|
||||
from openpilot.system.version import terms_version, training_version, get_build_metadata
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
|
||||
ThermalStatus = log.DeviceState.ThermalStatus
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
@@ -326,16 +326,6 @@ def hardware_thread(end_event, hw_queue) -> None:
|
||||
startup_conditions["not_always_offroad"] = not offroad_mode
|
||||
onroad_conditions["not_always_offroad"] = not offroad_mode
|
||||
|
||||
# if an unsupported device and branch is detected, going onroad is blocked
|
||||
# only allow going onroad when:
|
||||
# - TIZI, or
|
||||
# - TICI and channel_type is "tici"
|
||||
build_metadata = get_build_metadata()
|
||||
is_unsupported_combo = TICI and HARDWARE.get_device_type() == "tici" and build_metadata.channel_type != "tici"
|
||||
startup_conditions["not_tici"] = not is_unsupported_combo
|
||||
onroad_conditions["not_tici"] = not is_unsupported_combo
|
||||
set_offroad_alert("Offroad_TiciSupport", is_unsupported_combo, extra_text=build_metadata.channel)
|
||||
|
||||
# if the temperature enters the danger zone, go offroad to cool down
|
||||
onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger
|
||||
extra_text = f"{offroad_comp_temp:.1f}C"
|
||||
|
||||
@@ -7,6 +7,7 @@ import psutil
|
||||
import shutil
|
||||
import signal
|
||||
import fcntl
|
||||
import time
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
@@ -18,7 +19,7 @@ from openpilot.common.markdown import parse_markdown
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
||||
from openpilot.system.hardware import AGNOS, HARDWARE
|
||||
from openpilot.system.version import get_build_metadata, SP_BRANCH_MIGRATIONS
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock")
|
||||
STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging")
|
||||
@@ -189,6 +190,15 @@ def finalize_update() -> None:
|
||||
run(["git", "reset", "--hard"], FINALIZED)
|
||||
run(["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"], FINALIZED)
|
||||
|
||||
cloudlog.info("Starting git cleanup in finalized update")
|
||||
t = time.monotonic()
|
||||
try:
|
||||
run(["git", "gc"], FINALIZED)
|
||||
run(["git", "lfs", "prune"], FINALIZED)
|
||||
cloudlog.event("Done git cleanup", duration=time.monotonic() - t)
|
||||
except subprocess.CalledProcessError:
|
||||
cloudlog.exception(f"Failed git cleanup, took {time.monotonic() - t:.3f} s")
|
||||
|
||||
set_consistent_flag(True)
|
||||
cloudlog.info("done finalizing overlay")
|
||||
|
||||
@@ -232,7 +242,9 @@ class Updater:
|
||||
b: str | None = self.params.get("UpdaterTargetBranch")
|
||||
if b is None:
|
||||
b = self.get_branch(BASEDIR)
|
||||
b = SP_BRANCH_MIGRATIONS.get((HARDWARE.get_device_type(), b), b)
|
||||
b = {
|
||||
("tici", "release3"): "release-tici"
|
||||
}.get((HARDWARE.get_device_type(), b), b)
|
||||
return b
|
||||
|
||||
@property
|
||||
|
||||
@@ -16,13 +16,6 @@ MASTER_SP_BRANCHES = ['master']
|
||||
RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] + RELEASE_SP_BRANCHES
|
||||
TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + TESTED_SP_BRANCHES
|
||||
|
||||
SP_BRANCH_MIGRATIONS = {
|
||||
("tici", "staging-c3-new"): "staging-tici",
|
||||
("tici", "dev-c3-new"): "staging-tici",
|
||||
("tici", "master"): "master-tici",
|
||||
("tici", "master-dev-c3-new"): "master-tici",
|
||||
}
|
||||
|
||||
BUILD_METADATA_FILENAME = "build.json"
|
||||
|
||||
training_version: str = "0.2.0"
|
||||
@@ -92,8 +85,7 @@ class OpenpilotMetadata:
|
||||
|
||||
@property
|
||||
def sunnypilot_remote(self) -> bool:
|
||||
return self.git_normalized_origin in ("github.com/sunnypilot/sunnypilot",
|
||||
"github.com/sunnypilot/openpilot")
|
||||
return self.git_normalized_origin == "github.com/sunnypilot/sunnypilot"
|
||||
|
||||
@property
|
||||
def git_normalized_origin(self) -> str:
|
||||
@@ -135,9 +127,7 @@ class BuildMetadata:
|
||||
|
||||
@property
|
||||
def channel_type(self) -> str:
|
||||
if self.channel.endswith("-tici"):
|
||||
return "tici"
|
||||
elif self.development_channel:
|
||||
if self.development_channel:
|
||||
return "development"
|
||||
elif self.channel.startswith("staging-"):
|
||||
return "staging"
|
||||
|
||||
Reference in New Issue
Block a user