Compare commits

..

19 Commits

Author SHA1 Message Date
Nayan 924eb1aa57 Merge branch 'master-new' into dynamic-outputs-toggle 2025-07-19 01:03:33 -04:00
discountchubbs 4faeedec57 dynamic outputs toggle 2025-07-16 12:03:35 -07:00
discountchubbs 638a92bffb Rm toggle from refactor 2025-07-16 11:57:30 -07:00
James Vecellio-Grant ad2fab062e Merge branch 'master-new' into modeld-refactor-july7 2025-07-15 06:38:09 -07:00
discountchubbs d5b59d2b19 Make most outputs dynamic 2025-07-15 06:34:13 -07:00
discountchubbs 079ce94cb4 dynamic 2025-07-14 16:40:19 -07:00
discountchubbs f7efd7c641 seems a bit repetitive yea? 2025-07-14 16:34:36 -07:00
discountchubbs 30907c3cf9 Affix to generation, while allowing older models to use this IF param is set. 2025-07-13 20:21:33 -07:00
James Vecellio-Grant a39bc65883 Even clearer! 2025-07-12 22:26:20 +00:00
James Vecellio-Grant 2cc3755760 Make generation a property for clarity 2025-07-12 22:20:51 +00:00
James Vecellio-Grant abf836a2b8 Merge branch 'master-new' into modeld-refactor-july7 2025-07-10 07:02:13 -07:00
discountchubbs 3c100e3d92 red diff 2025-07-09 19:37:03 -07:00
James Vecellio-Grant cda3c90468 Mypy from myphone! 2025-07-08 13:05:15 -07:00
James Vecellio-Grant 2c66b6bb75 Update longitudinal_planner.py 2025-07-08 10:22:48 -07:00
discountchubbs ee12e4b7b4 Add full conditional 2025-07-08 06:37:41 -07:00
discountchubbs a7751702b7 This needs to be apart of the conditional else fail 2025-07-08 06:16:45 -07:00
discountchubbs eaa0b44f71 We can revert this after dev-c3-new testing and ready to merge. 2025-07-07 20:40:46 -07:00
discountchubbs 445b101a8b Clean this up 2025-07-07 20:40:38 -07:00
discountchubbs 64cc311161 Introduce zero inputs for Lead, and plan to conform with new SP model introduced Monday, July 7, 2025 2025-07-07 19:15:51 -07:00
49 changed files with 160 additions and 4064 deletions
-1
View File
@@ -2,5 +2,4 @@ Wen
REGIST REGIST
PullRequest PullRequest
cancelled cancelled
indeces
FOF FOF
+2 -2
View File
@@ -2,7 +2,7 @@
<tool name="uv Scons Build Debug" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true"> <tool name="uv Scons Build Debug" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec> <exec>
<option name="COMMAND" value="bash" /> <option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) --ccflags=\&quot;-fno-inline\&quot;&quot;" /> <option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) --compile_db --ccflags=\&quot;-fno-inline\&quot;&quot;" />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" /> <option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec> </exec>
</tool> </tool>
@@ -16,7 +16,7 @@
<tool name="uv Scons Build Release" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true"> <tool name="uv Scons Build Release" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec> <exec>
<option name="COMMAND" value="bash" /> <option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc)&quot; " /> <option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) --compile_db&quot; " />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" /> <option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec> </exec>
</tool> </tool>
+10 -23
View File
@@ -5,8 +5,8 @@
inline static std::unordered_map<std::string, uint32_t> keys = { inline static std::unordered_map<std::string, uint32_t> keys = {
{"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG},
{"AdbEnabled", PERSISTENT | BACKUP}, {"AdbEnabled", PERSISTENT},
{"AlwaysOnDM", PERSISTENT | BACKUP}, {"AlwaysOnDM", PERSISTENT},
{"ApiCache_Device", PERSISTENT}, {"ApiCache_Device", PERSISTENT},
{"ApiCache_FirehoseStats", PERSISTENT}, {"ApiCache_FirehoseStats", PERSISTENT},
{"AssistNowToken", PERSISTENT}, {"AssistNowToken", PERSISTENT},
@@ -78,7 +78,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"LocationFilterInitialState", PERSISTENT}, {"LocationFilterInitialState", PERSISTENT},
{"LongitudinalManeuverMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"LongitudinalManeuverMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"LongitudinalPersonality", PERSISTENT | BACKUP}, {"LongitudinalPersonality", PERSISTENT | BACKUP},
{"NetworkMetered", PERSISTENT | BACKUP}, {"NetworkMetered", PERSISTENT},
{"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"Offroad_BadNvme", CLEAR_ON_MANAGER_START}, {"Offroad_BadNvme", CLEAR_ON_MANAGER_START},
@@ -124,29 +124,25 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
// --- sunnypilot params --- // // --- sunnypilot params --- //
{"ApiCache_DriveStats", PERSISTENT}, {"ApiCache_DriveStats", PERSISTENT},
{"AutoLaneChangeBsmDelay", PERSISTENT | BACKUP}, {"AutoLaneChangeBsmDelay", PERSISTENT},
{"AutoLaneChangeTimer", PERSISTENT | BACKUP}, {"AutoLaneChangeTimer", PERSISTENT},
{"BlinkerMinLateralControlSpeed", PERSISTENT | BACKUP}, {"BlinkerMinLateralControlSpeed", PERSISTENT | BACKUP},
{"BlinkerPauseLateralControl", PERSISTENT | BACKUP}, {"BlinkerPauseLateralControl", PERSISTENT | BACKUP},
{"Brightness", PERSISTENT | BACKUP},
{"CarParamsSP", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"CarParamsSP", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"CarParamsSPCache", CLEAR_ON_MANAGER_START}, {"CarParamsSPCache", CLEAR_ON_MANAGER_START},
{"CarParamsSPPersistent", PERSISTENT}, {"CarParamsSPPersistent", PERSISTENT},
{"CarPlatformBundle", PERSISTENT | BACKUP}, {"CarPlatformBundle", PERSISTENT},
{"ChevronInfo", PERSISTENT | BACKUP},
{"CustomAccIncrementsEnabled", PERSISTENT | BACKUP}, {"CustomAccIncrementsEnabled", PERSISTENT | BACKUP},
{"CustomAccLongPressIncrement", PERSISTENT | BACKUP}, {"CustomAccLongPressIncrement", PERSISTENT | BACKUP},
{"CustomAccShortPressIncrement", PERSISTENT | BACKUP}, {"CustomAccShortPressIncrement", PERSISTENT | BACKUP},
{"DeviceBootMode", PERSISTENT | BACKUP}, {"DeviceBootMode", PERSISTENT | BACKUP},
{"EnableGithubRunner", PERSISTENT | BACKUP}, {"EnableGithubRunner", PERSISTENT | BACKUP},
{"InteractivityTimeout", PERSISTENT | BACKUP}, {"InteractivityTimeout", PERSISTENT | BACKUP},
{"IsDevelopmentBranch", CLEAR_ON_MANAGER_START},
{"MaxTimeOffroad", PERSISTENT | BACKUP}, {"MaxTimeOffroad", PERSISTENT | BACKUP},
{"Brightness", PERSISTENT | BACKUP},
{"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION}, {"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION},
{"OffroadMode", CLEAR_ON_MANAGER_START}, {"OffroadMode", CLEAR_ON_MANAGER_START},
{"QuickBootToggle", PERSISTENT | BACKUP},
{"QuietMode", PERSISTENT | BACKUP}, {"QuietMode", PERSISTENT | BACKUP},
{"ShowAdvancedControls", PERSISTENT | BACKUP},
// MADS params // MADS params
{"Mads", PERSISTENT | BACKUP}, {"Mads", PERSISTENT | BACKUP},
@@ -155,8 +151,8 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"MadsUnifiedEngagementMode", PERSISTENT | BACKUP}, {"MadsUnifiedEngagementMode", PERSISTENT | BACKUP},
// Model Manager params // Model Manager params
{"DynamicModeldOutputs", PERSISTENT | BACKUP},
{"ModelManager_ActiveBundle", PERSISTENT}, {"ModelManager_ActiveBundle", PERSISTENT},
{"ModelManager_ClearCache", CLEAR_ON_MANAGER_START},
{"ModelManager_DownloadIndex", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ModelManager_DownloadIndex", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"ModelManager_LastSyncTime", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"ModelManager_LastSyncTime", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"ModelManager_ModelsCache", PERSISTENT | BACKUP}, {"ModelManager_ModelsCache", PERSISTENT | BACKUP},
@@ -178,9 +174,9 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"BackupManager_RestoreVersion", PERSISTENT}, {"BackupManager_RestoreVersion", PERSISTENT},
// sunnypilot car specific params // sunnypilot car specific params
{"HyundaiLongitudinalTuning", PERSISTENT | BACKUP}, {"HyundaiLongitudinalTuning", PERSISTENT},
{"DynamicExperimentalControl", PERSISTENT | BACKUP}, {"DynamicExperimentalControl", PERSISTENT},
{"BlindSpot", PERSISTENT | BACKUP}, {"BlindSpot", PERSISTENT | BACKUP},
// model panel params // model panel params
@@ -207,13 +203,4 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"OsmStateTitle", PERSISTENT}, {"OsmStateTitle", PERSISTENT},
{"OsmWayTest", PERSISTENT}, {"OsmWayTest", PERSISTENT},
{"RoadName", CLEAR_ON_ONROAD_TRANSITION}, {"RoadName", CLEAR_ON_ONROAD_TRANSITION},
// Tuning keys
{"EnableHkgTuningAngleSmoothingFactor", PERSISTENT | BACKUP},
{"HkgTuningAngleMinTorqueReductionGain", PERSISTENT | BACKUP},
{"HkgTuningAngleMaxTorqueReductionGain", PERSISTENT | BACKUP},
{"HkgTuningAngleActiveTorqueReductionGain", PERSISTENT | BACKUP},
{"HkgTuningOverridingCycles", PERSISTENT | BACKUP},
{"HkgAngleLiveTuning", CLEAR_ON_MANAGER_START}
}; };
+2 -1
View File
@@ -59,7 +59,7 @@ dependencies = [
"future-fstrings", "future-fstrings",
# joystickd # joystickd
"pygame", "inputs",
# these should be removed # these should be removed
"psutil", "psutil",
@@ -108,6 +108,7 @@ dev = [
"opencv-python-headless", "opencv-python-headless",
"parameterized >=0.8, <0.9", "parameterized >=0.8, <0.9",
"pyautogui", "pyautogui",
"pygame",
"pyopencl; platform_machine != 'aarch64'", # broken on arm64 "pyopencl; platform_machine != 'aarch64'", # broken on arm64
"pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version "pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version
"pywinctl", "pywinctl",
+1 -1
View File
@@ -13,7 +13,7 @@ cd $ROOT
FAILED=0 FAILED=0
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md|layouts\/.*\.xml" IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md"
IGNORED_DIRS="^third_party.*|^msgq.*|^msgq_repo.*|^opendbc.*|^opendbc_repo.*|^cereal.*|^panda.*|^rednose.*|^rednose_repo.*|^tinygrad.*|^tinygrad_repo.*|^teleoprtc.*|^teleoprtc_repo.*" IGNORED_DIRS="^third_party.*|^msgq.*|^msgq_repo.*|^opendbc.*|^opendbc_repo.*|^cereal.*|^panda.*|^rednose.*|^rednose_repo.*|^tinygrad.*|^tinygrad_repo.*|^teleoprtc.*|^teleoprtc_repo.*"
function run() { function run() {
+1 -4
View File
@@ -17,7 +17,6 @@ from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature
from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD
from openpilot.selfdrive.controls.lib.latcontrol_angle_torque import LatControlAngleTorque
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.controls.lib.longcontrol import LongControl
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose
@@ -59,9 +58,7 @@ class Controls(ControlsExt):
self.LoC = LongControl(self.CP) self.LoC = LongControl(self.CP)
self.VM = VehicleModel(self.CP) self.VM = VehicleModel(self.CP)
self.LaC: LatControl self.LaC: LatControl
if self.CP.steerControlType == car.CarParams.SteerControlType.angle and self.CP.lateralTuning.which() == 'torque': if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.LaC = LatControlAngleTorque(self.CP, self.CP_SP, self.CI)
elif self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.LaC = LatControlAngle(self.CP, self.CP_SP, self.CI) self.LaC = LatControlAngle(self.CP, self.CP_SP, self.CI)
elif self.CP.lateralTuning.which() == 'pid': elif self.CP.lateralTuning.which() == 'pid':
self.LaC = LatControlPID(self.CP, self.CP_SP, self.CI) self.LaC = LatControlPID(self.CP, self.CP_SP, self.CI)
@@ -1,13 +0,0 @@
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
class LatControlAngleTorque(LatControlTorque, LatControlAngle):
def __init__(self, CP, CP_SP, CI):
LatControlTorque.__init__(self, CP, CP_SP, CI)
LatControlAngle.__init__(self, CP, CP_SP, CI)
def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited):
torque, _, _ = LatControlTorque.update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited)
_, angle, angle_log = LatControlAngle.update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited)
return torque, angle, angle_log
+1 -1
View File
@@ -90,7 +90,7 @@ class ModelState:
prev_desire: np.ndarray # for tracking the rising edge of the pulse prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext): def __init__(self, context: CLContext):
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS self.LAT_SMOOTH_SECONDS = 0.0
with open(VISION_METADATA_PATH, 'rb') as f: with open(VISION_METADATA_PATH, 'rb') as f:
vision_metadata = pickle.load(f) vision_metadata = pickle.load(f)
self.vision_input_shapes = vision_metadata['input_shapes'] self.vision_input_shapes = vision_metadata['input_shapes']
@@ -45,6 +45,17 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
}); });
addItem(experimentalLongitudinalToggle); addItem(experimentalLongitudinalToggle);
enableGithubRunner = new ParamControl("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "");
addItem(enableGithubRunner);
// error log button
errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
connect(errorLogBtn, &ButtonControl::clicked, [=]() {
std::string txt = util::read_file("/data/community/crashes/error.log");
ConfirmationDialog::rich(QString::fromStdString(txt), this);
});
addItem(errorLogBtn);
// Joystick and longitudinal maneuvers should be hidden on release branches // Joystick and longitudinal maneuvers should be hidden on release branches
is_release = params.getBool("IsReleaseBranch"); is_release = params.getBool("IsReleaseBranch");
@@ -93,6 +104,8 @@ void DeveloperPanel::updateToggles(bool _offroad) {
experimentalLongitudinalToggle->refresh(); experimentalLongitudinalToggle->refresh();
// Handle specific controls visibility for release branches // Handle specific controls visibility for release branches
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
joystickToggle->setVisible(!is_release); joystickToggle->setVisible(!is_release);
offroad = _offroad; offroad = _offroad;
@@ -16,8 +16,10 @@ private:
Params params; Params params;
ParamControl* adbToggle; ParamControl* adbToggle;
ParamControl* joystickToggle; ParamControl* joystickToggle;
ButtonControl* errorLogBtn;
ParamControl* longManeuverToggle; ParamControl* longManeuverToggle;
ParamControl* experimentalLongitudinalToggle; ParamControl* experimentalLongitudinalToggle;
ParamControl* enableGithubRunner;
bool is_release; bool is_release;
bool offroad = false; bool offroad = false;
-168
View File
@@ -34,7 +34,6 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect); drawLead(painter, lead_two, lead_vertices[1], surface_rect);
} }
} }
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
painter.restore(); painter.restore();
} }
@@ -174,173 +173,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
(1 - t) * start.alphaF() + t * end.alphaF()); (1 - t) * start.alphaF() + t * end.alphaF());
} }
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, void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) { const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.; const float speedBuff = 10.;
-11
View File
@@ -34,12 +34,6 @@ protected:
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out); 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, void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
QPolygonF *pvd, int max_idx, bool allow_invert = true); 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 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); 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); virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
@@ -64,9 +58,4 @@ protected:
QPointF lead_vertices[2] = {}; QPointF lead_vertices[2] = {};
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region; QRectF clip_region;
float lead_status_alpha = 0.0f;
QPointF lead_status_pos;
QString lead_status_text;
QColor lead_status_color;
}; };
-139
View File
@@ -2,7 +2,6 @@
#include <QPushButton> #include <QPushButton>
#include <QButtonGroup> #include <QButtonGroup>
#include <QScroller>
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
@@ -335,141 +334,3 @@ QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStrin
} }
return ""; return "";
} }
TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items,
const QString &current, QWidget *parent) : DialogBase(parent) {
QFrame *container = new QFrame(this);
container->setStyleSheet(R"(
QFrame { background-color: #1B1B1B; }
#confirm_btn[enabled="false"] { background-color: #2B2B2B; }
#confirm_btn:enabled { background-color: #465BEA; }
#confirm_btn:enabled:pressed { background-color: #3049F4; }
QTreeWidget {
background-color: transparent;
border: none;
}
QTreeWidget::item {
height: 135;
padding: 0px 50px;
margin: 5px;
text-align: left;
font-size: 55px;
font-weight: 300;
border-radius: 10px;
background-color: #4F4F4F;
color: white;
}
QTreeWidget::item:selected {
background-color: #465BEA;
}
QTreeWidget::branch {
background-color: transparent;
}
)");
QVBoxLayout *main_layout = new QVBoxLayout(container);
main_layout->setContentsMargins(55, 50, 55, 50);
QLabel *title = new QLabel(prompt_text, this);
title->setStyleSheet("font-size: 70px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
main_layout->addSpacing(25);
treeWidget = new QTreeWidget(this);
treeWidget->setHeaderHidden(true);
treeWidget->setIndentation(50);
treeWidget->setExpandsOnDoubleClick(false); // Disable double-click expansion
treeWidget->setAnimated(true);
treeWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
treeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
treeWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
treeWidget->setDragEnabled(false);
treeWidget->setMouseTracking(true);
// Connect single-click to expand/collapse
QObject::connect(treeWidget, &QTreeWidget::itemClicked, [=](QTreeWidgetItem *item, int) {
if (item->childCount() > 0) {
item->setExpanded(!item->isExpanded());
treeWidget->scrollToItem(item->child(0), QAbstractItemView::EnsureVisible);
}
});
QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture);
// Populate tree
QListIterator<QPair<QString, QStringList>> iter(items);
while (iter.hasNext()) {
QPair currItem = iter.next();
if (currItem.first.isEmpty()) {
for (const QString &item : currItem.second) {
QTreeWidgetItem *topLevel = new QTreeWidgetItem();
topLevel->setText(0, item);
topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable);
treeWidget->addTopLevelItem(topLevel);
if (item == current) {
topLevel->setSelected(true);
}
}
} else {
QTreeWidgetItem *folderItem = new QTreeWidgetItem(treeWidget);
folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
folderItem->setText(0, " " + currItem.first);
folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable);
for (const QString &item : currItem.second)
{
QTreeWidgetItem *childItem = new QTreeWidgetItem(folderItem);
childItem->setText(0, item);
childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
if (item == current) {
childItem->setSelected(true);
folderItem->setExpanded(true);
}
}
}
}
confirm_btn = new QPushButton(tr("Select"));
confirm_btn->setObjectName("confirm_btn");
confirm_btn->setEnabled(false);
QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() {
QList<QTreeWidgetItem*> selectedItems = treeWidget->selectedItems();
if (!selectedItems.isEmpty()) {
selection = selectedItems.first()->text(0);
confirm_btn->setEnabled(selection != current);
}
});
ScrollView *scroll_view = new ScrollView(treeWidget, this);
scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
main_layout->addWidget(scroll_view);
main_layout->addSpacing(35);
// cancel + confirm buttons
QHBoxLayout *blayout = new QHBoxLayout;
main_layout->addLayout(blayout);
blayout->setSpacing(50);
QPushButton *cancel_btn = new QPushButton(tr("Cancel"));
QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject);
QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept);
blayout->addWidget(cancel_btn);
blayout->addWidget(confirm_btn);
QVBoxLayout *outer_layout = new QVBoxLayout(this);
outer_layout->setContentsMargins(50, 50, 50, 50);
outer_layout->addWidget(container);
}
QString TreeOptionDialog::getSelection(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items,
const QString &current, QWidget *parent) {
TreeOptionDialog d(prompt_text, items, current, parent);
if (d.exec()) {
return d.selection;
}
return "";
}
-14
View File
@@ -6,7 +6,6 @@
#include <QString> #include <QString>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QWidget> #include <QWidget>
#include <QTreeWidget>
#include "selfdrive/ui/qt/widgets/keyboard.h" #include "selfdrive/ui/qt/widgets/keyboard.h"
@@ -70,16 +69,3 @@ public:
static QString getSelection(const QString &prompt_text, const QStringList &l, const QString &current, QWidget *parent); static QString getSelection(const QString &prompt_text, const QStringList &l, const QString &current, QWidget *parent);
QString selection; QString selection;
}; };
class TreeOptionDialog : public DialogBase {
Q_OBJECT
public:
explicit TreeOptionDialog(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items, const QString &current, QWidget *parent = nullptr);
static QString getSelection(const QString &prompt_text, const QList<QPair<QString, QStringList>> &items, const QString &current, QWidget *parent = nullptr);
QString selection;
private:
QTreeWidget *treeWidget;
QPushButton *confirm_btn;
};
-2
View File
@@ -21,7 +21,6 @@ qt_src = [
"sunnypilot/qt/home.cc", "sunnypilot/qt/home.cc",
"sunnypilot/qt/offroad/exit_offroad_button.cc", "sunnypilot/qt/offroad/exit_offroad_button.cc",
"sunnypilot/qt/offroad/offroad_home.cc", "sunnypilot/qt/offroad/offroad_home.cc",
"sunnypilot/qt/offroad/settings/developer_panel.cc",
"sunnypilot/qt/offroad/settings/device_panel.cc", "sunnypilot/qt/offroad/settings/device_panel.cc",
"sunnypilot/qt/offroad/settings/lateral_panel.cc", "sunnypilot/qt/offroad/settings/lateral_panel.cc",
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc", "sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
@@ -47,7 +46,6 @@ lateral_panel_qt_src = [
"sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc", "sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc", "sunnypilot/qt/offroad/settings/lateral/lane_change_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/mads_settings.cc", "sunnypilot/qt/offroad/settings/lateral/mads_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.cc",
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc", "sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
] ]
@@ -1,80 +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.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) {
// Advanced Controls Toggle
showAdvancedControls = new ParamControlSP("ShowAdvancedControls", tr("Show Advanced Controls"), tr("Toggle visibility of advanced sunnypilot controls.\nThis only toggles the visibility of the controls; it does not toggle the actual control enabled/disabled state."), "");
addItem(showAdvancedControls);
QObject::connect(showAdvancedControls, &ParamControlSP::toggleFlipped, this, [=](bool) {
AbstractControlSP::UpdateAllAdvancedControls();
updateToggles(!uiState()->scene.started);
});
showAdvancedControls->showDescription();
// Github Runner Toggle
enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
addItem(enableGithubRunner);
// Quickboot Mode Toggle
prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
addItem(prebuiltToggle);
QObject::connect(prebuiltToggle, &ParamControl::toggleFlipped, [=](bool state) {
QString prebuiltPath = "/data/openpilot/prebuilt";
state ? QFile(prebuiltPath).open(QIODevice::WriteOnly) : QFile::remove(prebuiltPath);
prebuiltToggle->refresh();
});
prebuiltToggle->setVisible(false);
// Error log button
errorLogBtn = new ButtonControlSP(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
connect(errorLogBtn, &ButtonControlSP::clicked, [=]() {
QFileInfo file("/data/community/crashes/error.log");
QString text;
if (file.exists()) {
text = "<b>" + file.lastModified().toString("dd-MMM-yyyy hh:mm:ss ").toUpper() + "</b><br><br>";
}
text += QString::fromStdString(util::read_file("/data/community/crashes/error.log"));
ConfirmationDialog::rich(text, this);
});
addItem(errorLogBtn);
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
}
void DeveloperPanelSP::updateToggles(bool offroad) {
bool is_release = params.getBool("IsReleaseBranch");
bool is_tested = params.getBool("IsTestedBranch");
bool is_development = params.getBool("IsDevelopmentBranch");
bool disable_updates = params.getBool("DisableUpdates");
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
prebuiltToggle->setEnabled(disable_updates);
params.putBool("QuickBootToggle", QFile::exists("/data/openpilot/prebuilt"));
prebuiltToggle->refresh();
prebuiltToggle->setDescription(disable_updates
? tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, "
"it immediately removes the prebuilt file so compilation of locally edited cpp files can be made. "
"<br><br><b>To edit C++ files locally on device, you MUST first turn off this toggle so the changes can recompile.</b>")
: tr("Quickboot mode requires updates to be disabled.<br>Enable 'Disable Updates' in the Software panel first."));
enableGithubRunner->setVisible(!is_release);
errorLogBtn->setVisible(!is_release);
showAdvancedControls->setEnabled(true);
}
void DeveloperPanelSP::showEvent(QShowEvent *event) {
DeveloperPanel::showEvent(event);
updateToggles(!uiState()->scene.started);
AbstractControlSP::UpdateAllAdvancedControls();
prebuiltToggle->showDescription();
}
@@ -1,30 +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.
*/
#pragma once
#include <QFile>
#include <QFileInfo>
#include "selfdrive/ui/qt/offroad/developer_panel.h"
class DeveloperPanelSP : public DeveloperPanel {
Q_OBJECT
public:
explicit DeveloperPanelSP(SettingsWindow *parent);
private:
ParamControlSP *enableGithubRunner;
ButtonControlSP *errorLogBtn;
ParamControlSP *prebuiltToggle;
Params params;
ParamControlSP *showAdvancedControls;
private slots:
void updateToggles(bool offroad);
protected:
void showEvent(QShowEvent *event) override;
};
@@ -1,87 +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.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
AngleTunningSettings::AngleTunningSettings(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
main_layout->setSpacing(20);
// Back button
PanelBackButton *back = new PanelBackButton();
connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
main_layout->addWidget(back, 0, Qt::AlignLeft);
auto *list = new ListWidgetSP(this, false);
main_layout->addWidget(new QWidget());
enableHkgAngleSmoothingFactor = new ExpandableToggleRow("EnableHkgTuningAngleSmoothingFactor", tr("HKG Angle Smoothing Factor"), tr("Applies EMA (Exponential Moving Average) to the desired angle steering and avoid overcorrections."), "../assets/offroad/icon_blank.png");
list->addItem(enableHkgAngleSmoothingFactor);
auto first_row = new QHBoxLayout();
hkgTuningOverridingCycles = new OptionControlSP("HkgTuningOverridingCycles", tr("Override Ramp-Down Cycles"), tr("Number of cycles to ramp down the current amount of torque on the steering wheel.<br/>A smaller value means a faster override by the user (less effort)"), "../assets/offroad/icon_blank.png", {10, 30}, 1);
connect(hkgTuningOverridingCycles, &OptionControlSP::updateLabels, hkgTuningOverridingCycles, [=]() {
this->updateToggles(offroad);
});
first_row->addWidget(hkgTuningOverridingCycles);
hkgAngleMinTorque = new OptionControlSP("HkgTuningAngleMinTorqueReductionGain", tr("Override Steering Effort"), tr("Sets the steering effort percentage used when the driver is overriding lateral control.<br/>Higher values increase resistance and make the wheel feel stiffer."), "../assets/offroad/icon_blank.png", {5, 60}, 1);
connect(hkgAngleMinTorque, &OptionControlSP::updateLabels, hkgAngleMinTorque, [=]() {
this->updateToggles(offroad);
});
first_row->addWidget(hkgAngleMinTorque);
list->addItem(first_row);
auto second_row = new QHBoxLayout();
hkgAngleActiveTorque = new OptionControlSP("HkgTuningAngleActiveTorqueReductionGain", tr("Min Active Torque"), tr("Torque applied when lateral control is active but the vehicle is not turning.<br/>Used to maintain lane centering on straight paths when no user input is detected."), "../assets/offroad/icon_blank.png", {10, 100}, 1);
connect(hkgAngleActiveTorque, &OptionControlSP::updateLabels, hkgAngleActiveTorque, [=]() {
this->updateToggles(offroad);
});
second_row->addWidget(hkgAngleActiveTorque);
hkgAngleMaxTorque = new OptionControlSP("HkgTuningAngleMaxTorqueReductionGain", tr("Max Torque Allowance"), tr("Sets the maximum torque reduction percentage the controller can apply during normal lateral control.<br/>"), "../assets/offroad/icon_blank.png", {10, 100}, 1);
connect(hkgAngleMaxTorque, &OptionControlSP::updateLabels, hkgAngleMaxTorque, [=]() {
this->updateToggles(offroad);
});
second_row->addWidget(hkgAngleMaxTorque);
list->addItem(second_row);
QObject::connect(uiState(), &UIState::offroadTransition, this, &AngleTunningSettings::updateToggles);
main_layout->addWidget(new ScrollViewSP(list, this));
auto *warning = new QLabel(tr("Reboot required for settings to apply; Tap on each setting to see more details."));
warning->setStyleSheet("font-size: 30px; font-weight: 500; font-family: 'Noto Color Emoji'; color: orange;");
main_layout->addWidget(warning, 0, Qt::AlignCenter);
}
void AngleTunningSettings::showEvent(QShowEvent *event) {
updateToggles(offroad);
}
void AngleTunningSettings::updateToggles(bool _offroad) {
auto HkgAngleSmoothingFactorValue = params.getBool("EnableHkgTuningAngleSmoothingFactor");
enableHkgAngleSmoothingFactor->toggleFlipped(HkgAngleSmoothingFactorValue);
auto HkgAngleMinTorqueValue = QString::fromStdString(params.get("HkgTuningAngleMinTorqueReductionGain")).toInt();
hkgAngleMinTorque->setLabel(QString::number(HkgAngleMinTorqueValue)+"%");
auto HkgAngleActiveTorqueValue = QString::fromStdString(params.get("HkgTuningAngleActiveTorqueReductionGain")).toInt();
hkgAngleActiveTorque->setLabel(QString::number(HkgAngleActiveTorqueValue)+"%");
auto HkgAngleMaxTorqueValue = QString::fromStdString(params.get("HkgTuningAngleMaxTorqueReductionGain")).toInt();
hkgAngleMaxTorque->setLabel(QString::number(HkgAngleMaxTorqueValue)+"%");
auto HkgTuningOverridingCyclesValue = QString::fromStdString(params.get("HkgTuningOverridingCycles")).toInt();
hkgTuningOverridingCycles->setLabel(QString::number(HkgTuningOverridingCyclesValue));
offroad = _offroad;
}
@@ -1,40 +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.
*/
#pragma once
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
class AngleTunningSettings : public QWidget {
Q_OBJECT
public:
explicit AngleTunningSettings(QWidget *parent = nullptr);
void showEvent(QShowEvent *event) override;
signals:
void backPress();
public slots:
void updateToggles(bool _offroad);
private:
Params params;
bool offroad;
ExpandableToggleRow* enableHkgAngleSmoothingFactor;
OptionControlSP* hkgAngleMinTorque;
OptionControlSP* hkgAngleActiveTorque;
OptionControlSP* hkgAngleMaxTorque;
OptionControlSP* hkgTuningOverridingCycles;
ParamControlSP* hkgAngleLiveTuning;
};
@@ -89,36 +89,6 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
nnlcToggle->updateToggle(); nnlcToggle->updateToggle();
}); });
#pragma region hkg angle tuning
list->addItem(vertical_space());
list->addItem(horizontal_line());
list->addItem(vertical_space());
// HKG Angle Tuning
// angleTuningToggle = new ParamControl(
// "AngleTuning",
// tr("Modular Assistive Driving System (MADS)"),
// tr("Enable the beloved MADS feature. Disable toggle to revert back to stock sunnypilot engagement/disengagement."),
// "");
// angleTuningToggle->setConfirmation(true, false);
// list->addItem(angleTuningToggle);
angleTuningSettingsButton = new PushButtonSP(tr("Customize ANGLE Tuning"));
angleTuningSettingsButton->setObjectName("angle_btn");
connect(angleTuningSettingsButton, &QPushButton::clicked, [=]() {
sunnypilotScroller->setLastScrollPosition();
main_layout->setCurrentWidget(angleTuningWidget);
});
// QObject::connect(angleTuningToggle, &ToggleControl::toggleFlipped, angleTuningSettingsButton, &PushButtonSP::setEnabled);
angleTuningWidget = new AngleTunningSettings(this);
connect(angleTuningWidget, &AngleTunningSettings::backPress, [=]() {
sunnypilotScroller->restoreScrollPosition();
main_layout->setCurrentWidget(sunnypilotScreen);
});
list->addItem(angleTuningSettingsButton);
#pragma endregion
toggleOffroadOnly = { toggleOffroadOnly = {
madsToggle, nnlcToggle, madsToggle, nnlcToggle,
}; };
@@ -129,7 +99,6 @@ LateralPanel::LateralPanel(SettingsWindowSP *parent) : QFrame(parent) {
main_layout->addWidget(sunnypilotScreen); main_layout->addWidget(sunnypilotScreen);
main_layout->addWidget(madsWidget); main_layout->addWidget(madsWidget);
main_layout->addWidget(angleTuningWidget);
main_layout->addWidget(laneChangeWidget); main_layout->addWidget(laneChangeWidget);
setStyleSheet(R"( setStyleSheet(R"(
@@ -180,7 +149,6 @@ void LateralPanel::updateToggles(bool _offroad) {
} }
madsSettingsButton->setEnabled(madsToggle->isToggled()); madsSettingsButton->setEnabled(madsToggle->isToggled());
// angleTuningSettingsButton->setEnabled(angleTuningToggle->isToggled());
blinkerPauseLateralSettings->refresh(); blinkerPauseLateralSettings->refresh();
@@ -13,7 +13,6 @@
#include "selfdrive/ui/sunnypilot/ui.h" #include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/blinker_pause_lateral_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/angle_tuning_settings.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/lane_change_settings.h"
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
@@ -40,11 +39,8 @@ private:
bool offroad; bool offroad;
ParamControl *madsToggle; ParamControl *madsToggle;
// ParamControl *angleTuningToggle;
PushButtonSP *madsSettingsButton; PushButtonSP *madsSettingsButton;
PushButtonSP *angleTuningSettingsButton;
MadsSettings *madsWidget = nullptr; MadsSettings *madsWidget = nullptr;
AngleTunningSettings *angleTuningWidget = nullptr;
PushButtonSP *laneChangeSettingsButton; PushButtonSP *laneChangeSettingsButton;
LaneChangeSettings *laneChangeWidget = nullptr; LaneChangeSettings *laneChangeWidget = nullptr;
NeuralNetworkLateralControl *nnlcToggle = nullptr; NeuralNetworkLateralControl *nnlcToggle = nullptr;
@@ -8,8 +8,6 @@
#include <algorithm> #include <algorithm>
#include <QJsonDocument> #include <QJsonDocument>
#include <QStyle> #include <QStyle>
#include <QtConcurrent/QtConcurrent>
#include <QDir>
#include "common/model.h" #include "common/model.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
@@ -68,11 +66,6 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels); connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels);
list->addItem(currentModelLblBtn); list->addItem(currentModelLblBtn);
clearModelCacheBtn = new ButtonControlSP(tr("Clear Model Cache"), tr("CLEAR"), "", this);
connect(clearModelCacheBtn, &ButtonControlSP::clicked, this, &ModelsPanel::clearModelCache);
list->addItem(clearModelCacheBtn);
// Create progress bars for downloads // Create progress bars for downloads
supercomboProgressBar = createProgressBar(this); supercomboProgressBar = createProgressBar(this);
QString supercomboType = tr("Driving Model"); QString supercomboType = tr("Driving Model");
@@ -96,6 +89,14 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
list->addItem(horizontal_line()); list->addItem(horizontal_line());
// Dynamic Modeld Outputs toggle
dynamicModeldOutputs = new ParamControlSP("DynamicModeldOutputs", tr("Allow Dynamic Model Outputs"),
tr("Enable this to allow potentially smoother Gas and Brake controls on all models produced "
"after September, 2024."),
"../assets/offroad/icon_shell.png");
dynamicModeldOutputs->showDescription();
list->addItem(dynamicModeldOutputs);
// LiveDelay toggle // LiveDelay toggle
lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png"); lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
lagd_toggle_control->showDescription(); lagd_toggle_control->showDescription();
@@ -105,7 +106,7 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"), delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"),
tr("Adjust the software delay when Live Learning Steer Delay is toggled off." tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
"\nThe default software delay value is 0.2"), "\nThe default software delay value is 0.2"),
"", {5, 30}, 1, false, nullptr, true, true); "", {10, 30}, 1, false, nullptr, true);
connect(delay_control, &OptionControlSP::updateLabels, [=]() { connect(delay_control, &OptionControlSP::updateLabels, [=]() {
float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat(); float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat();
@@ -251,73 +252,28 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false); currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models...")); currentModelLblBtn->setValue(tr("Fetching models..."));
struct ModelEntry { // Create mapping of bundle indices to display names
QString folder; QMap<uint32_t, QString> index_to_bundle;
QString displayName;
int index;
};
QList<ModelEntry> sortedModels;
QSet<QString> modelFolders;
const auto bundles = model_manager.getAvailableBundles(); const auto bundles = model_manager.getAvailableBundles();
for (const auto &bundle: bundles) {
for (const auto &bundle : bundles) { index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName()));
auto overrides = bundle.getOverrides();
QString gen;
for (const auto &override : overrides) {
if (override.getKey() == "folder") {
gen = QString::fromStdString(override.getValue().cStr());
}
}
modelFolders.insert(gen);
sortedModels.append(ModelEntry{
gen,
QString::fromStdString(bundle.getDisplayName()),
static_cast<int>(bundle.getIndex())
});
} }
std::sort(sortedModels.begin(), sortedModels.end(), // Sort bundles by index in descending order
[](const ModelEntry &a, const ModelEntry &b) { QStringList bundleNames;
return a.index > b.index; // Add "Default" as the first option
}); bundleNames.append(DEFAULT_MODEL);
// Create a list of folder-maxIndex pairs for sorting auto indices = index_to_bundle.keys();
QList<QPair<QString, int>> folderMaxIndices; std::sort(indices.begin(), indices.end(), std::greater<uint32_t>());
for (const auto &folder : modelFolders) { for (const auto &index: indices) {
int maxIndex = -1; bundleNames.append(index_to_bundle[index]);
for (const auto &model : sortedModels) {
if (model.folder == folder) {
maxIndex = std::max(maxIndex, model.index);
}
}
folderMaxIndices.append(qMakePair(folder, maxIndex));
} }
// Sort folders by their highest model index
std::sort(folderMaxIndices.begin(), folderMaxIndices.end(),
[](const QPair<QString, int> &a, const QPair<QString, int> &b) {
return a.second > b.second;
});
// Create the final items list using sorted folders
QList<QPair<QString, QStringList>> items;
for (const auto &folderPair : folderMaxIndices) {
QStringList folderModels;
for (const auto &model : sortedModels) {
if (model.folder == folderPair.first) {
folderModels.append(model.displayName);
}
}
items.append(qMakePair(folderPair.first, folderModels));
}
items.insert(0, qMakePair(QString(""), QStringList{DEFAULT_MODEL}));
currentModelLblBtn->setValue(GetActiveModelInternalName()); currentModelLblBtn->setValue(GetActiveModelInternalName());
const QString selectedBundleName = TreeOptionDialog::getSelection( const QString selectedBundleName = MultiOptionDialog::getSelection(
tr("Select a Model"), items, GetActiveModelName(), this); tr("Select a Model"), bundleNames, GetActiveModelName(), this);
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) { if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
return; return;
@@ -356,6 +312,7 @@ void ModelsPanel::updateLabels() {
handleBundleDownloadProgress(); handleBundleDownloadProgress();
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading()); currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
currentModelLblBtn->setValue(GetActiveModelInternalName()); currentModelLblBtn->setValue(GetActiveModelInternalName());
dynamicModeldOutputs->showDescription();
// Update lagdToggle description with current value // Update lagdToggle description with current value
QString desc = tr("Enable this for the car to learn and adapt its steering response time. " QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
@@ -374,8 +331,6 @@ void ModelsPanel::updateLabels() {
delay_control->setLabel(QString::number(value, 'f', 2) + "s"); delay_control->setLabel(QString::number(value, 'f', 2) + "s");
delay_control->showDescription(); delay_control->showDescription();
} }
clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
} }
/** /**
@@ -396,32 +351,3 @@ void ModelsPanel::showResetParamsDialog() {
params.remove("LiveTorqueParameters"); params.remove("LiveTorqueParameters");
} }
} }
void ModelsPanel::clearModelCache() {
QString confirmMsg = tr("This will delete ALL downloaded models from the cache"
"<br/><u>except the currently active model</u>."
"<br/><br/>Are you sure you want to continue?");
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(
content,
tr("Clear Cache"))) {
params.putBool("ModelManager_ClearCache", true);
}
}
double ModelsPanel::calculateCacheSize() {
QFuture<qint64> future_ModelCacheSize = QtConcurrent::run([=]() {
QDir model_dir(QString::fromStdString(Path::model_root()));
QFileInfoList model_files = model_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
qint64 totalSize = 0;
for (const QFileInfo &model_file : model_files) {
if (model_file.isFile()) {
totalSize += model_file.size();
}
}
return totalSize;
});
return static_cast<double>(future_ModelCacheSize) / (1024.0 * 1024.0);
}
@@ -41,8 +41,6 @@ private:
cereal::ModelManagerSP::Reader model_manager; cereal::ModelManagerSP::Reader model_manager;
cereal::ModelManagerSP::DownloadStatus download_status{}; cereal::ModelManagerSP::DownloadStatus download_status{};
cereal::ModelManagerSP::DownloadStatus prev_download_status{}; cereal::ModelManagerSP::DownloadStatus prev_download_status{};
void clearModelCache();
double calculateCacheSize();
bool canContinueOnMeteredDialog() { bool canContinueOnMeteredDialog() {
if (!is_metered) return true; if (!is_metered) return true;
@@ -66,6 +64,7 @@ private:
bool is_onroad = false; bool is_onroad = false;
ButtonControlSP *currentModelLblBtn; ButtonControlSP *currentModelLblBtn;
ParamControlSP *dynamicModeldOutputs;
ParamControlSP *lagd_toggle_control; ParamControlSP *lagd_toggle_control;
OptionControlSP *delay_control; OptionControlSP *delay_control;
QProgressBar *supercomboProgressBar; QProgressBar *supercomboProgressBar;
@@ -77,6 +76,5 @@ private:
QProgressBar *policyProgressBar; QProgressBar *policyProgressBar;
QFrame *policyFrame; QFrame *policyFrame;
Params params; Params params;
ButtonControlSP *clearModelCacheBtn;
}; };
@@ -8,10 +8,10 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" #include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/qt/offroad/developer_panel.h"
#include "selfdrive/ui/qt/offroad/firehose.h" #include "selfdrive/ui/qt/offroad/firehose.h"
#include "selfdrive/ui/sunnypilot/qt/network/networking.h" #include "selfdrive/ui/sunnypilot/qt/network/networking.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
@@ -91,7 +91,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"), PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"), PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"), PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
PanelInfo(" " + tr("Developer"), new DeveloperPanelSP(this), "../assets/icons/shell.png"), PanelInfo(" " + tr("Developer"), new DeveloperPanel(this), "../assets/icons/shell.png"),
}; };
nav_btns = new QButtonGroup(this); nav_btns = new QButtonGroup(this);
@@ -18,18 +18,6 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
searchBranches(d.text()); searchBranches(d.text());
} }
}); });
// Disable Updates toggle
disableUpdatesToggle = new ParamControl("DisableUpdates",
tr("Disable Updates"),
tr("When enabled, software updates will be disabled. <b>This requires a reboot to take effect.</b>"),
"../assets/icons/icon_warning.png",
this, true);
disableUpdatesToggle->showDescription();
addItem(disableUpdatesToggle);
connect(disableUpdatesToggle, &ParamControl::toggleFlipped, this, &SoftwarePanelSP::handleDisableUpdatesToggled);
connect(uiState(), &UIState::offroadTransition, this, &SoftwarePanelSP::updateDisableUpdatesToggle);
updateDisableUpdatesToggle(!uiState()->scene.started);
} }
/** /**
@@ -61,27 +49,3 @@ void SoftwarePanelSP::searchBranches(const QString &query) {
checkForUpdates(); checkForUpdates();
} }
} }
void SoftwarePanelSP::handleDisableUpdatesToggled(bool state) {
if (ConfirmationDialog::confirm(tr("%1 updates requires a reboot.<br>Reboot now?")
.arg(state ? "Disabling" : "Enabling"), tr("Reboot"), this)) {
params.putBool("DoReboot", true);
} else {
params.putBool("DisableUpdates", !state);
disableUpdatesToggle->refresh();
}
}
void SoftwarePanelSP::updateDisableUpdatesToggle(bool offroad) {
bool enabled = offroad;
disableUpdatesToggle->setEnabled(enabled);
disableUpdatesToggle->setDescription(enabled
? tr("When enabled, software updates will be disabled.<br><b>This requires a reboot to take effect.</b>")
: tr("Please enable always offroad mode or turn off vehicle to adjust these toggles"));
}
void SoftwarePanelSP::showEvent(QShowEvent *event) {
SoftwarePanel::showEvent(event);
updateDisableUpdatesToggle(!uiState()->scene.started);
disableUpdatesToggle->showDescription();
}
@@ -19,10 +19,4 @@ public:
private: private:
void searchBranches(const QString &query); void searchBranches(const QString &query);
ParamControl *disableUpdatesToggle = nullptr;
void handleDisableUpdatesToggled(bool state);
private slots:
void updateDisableUpdatesToggle(bool offroad);
protected:
void showEvent(QShowEvent *event) override;
}; };
@@ -12,7 +12,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) { connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString &param_name, const QString &param_value) {
paramsRefresh(); paramsRefresh();
}); });
main_layout = new QStackedLayout(this); main_layout = new QStackedLayout(this);
ListWidgetSP *list = new ListWidgetSP(this, false); ListWidgetSP *list = new ListWidgetSP(this, false);
@@ -30,7 +30,6 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
}, },
}; };
// Add regular toggles first
for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) {
auto toggle = new ParamControlSP(param, title, desc, icon, this); auto toggle = new ParamControlSP(param, title, desc, icon, this);
@@ -54,20 +53,9 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
param_watcher->addParam(param); param_watcher->addParam(param);
} }
// Visuals: Display Metrics below Chevron
std::vector<QString> chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")};
chevron_info_settings = new ButtonParamControlSP(
"ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."),
"",
chevron_info_settings_texts,
200);
chevron_info_settings->showDescription();
list->addItem(chevron_info_settings);
param_watcher->addParam("ChevronInfo");
sunnypilotScroller = new ScrollViewSP(list, this); sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller); vlayout->addWidget(sunnypilotScroller);
main_layout->addWidget(sunnypilotScreen); main_layout->addWidget(sunnypilotScreen);
} }
@@ -79,8 +67,4 @@ void VisualsPanel::paramsRefresh() {
for (auto toggle : toggles) { for (auto toggle : toggles) {
toggle.second->refresh(); toggle.second->refresh();
} }
if (chevron_info_settings) {
chevron_info_settings->refresh();
}
} }
@@ -27,5 +27,4 @@ protected:
Params params; Params params;
std::map<std::string, ParamControlSP*> toggles; std::map<std::string, ParamControlSP*> toggles;
ParamWatcher * param_watcher; ParamWatcher * param_watcher;
ButtonParamControlSP *chevron_info_settings;
}; };
+8 -23
View File
@@ -30,24 +30,9 @@ QFrame *vertical_space(int height, QWidget *parent) {
} }
// AbstractControlSP // AbstractControlSP
std::vector<AbstractControlSP*> AbstractControlSP::advanced_controls_;
AbstractControlSP::~AbstractControlSP() { UnregisterAdvancedControl(this); }
void AbstractControlSP::RegisterAdvancedControl(AbstractControlSP *ctrl) { advanced_controls_.push_back(ctrl); } AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: AbstractControl(title, desc, icon, parent) {
void AbstractControlSP::UnregisterAdvancedControl(AbstractControlSP *ctrl) {
advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), ctrl), advanced_controls_.end());
}
void AbstractControlSP::UpdateAllAdvancedControls() {
bool visibility = Params().getBool("ShowAdvancedControls");
advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), nullptr), advanced_controls_.end());
for (auto *ctrl : advanced_controls_) ctrl->setVisible(visibility);
}
AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
: AbstractControl(title, desc, icon, parent), isAdvancedControl(advancedControl) {
if (isAdvancedControl) RegisterAdvancedControl(this);
main_layout = new QVBoxLayout(this); main_layout = new QVBoxLayout(this);
main_layout->setMargin(0); main_layout->setMargin(0);
@@ -97,8 +82,8 @@ void AbstractControlSP::hideEvent(QHideEvent *e) {
} }
} }
AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl) AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: AbstractControlSP(title, desc, icon, parent, advancedControl) { : AbstractControlSP(title, desc, icon, parent) {
if (title_label != nullptr) { if (title_label != nullptr) {
delete title_label; delete title_label;
@@ -184,8 +169,8 @@ void AbstractControlSP_SELECTOR::hideEvent(QHideEvent *e) {
// controls // controls
ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent, bool advancedControl) ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent)
: AbstractControlSP(title, desc, "", parent, advancedControl) { : AbstractControlSP(title, desc, "", parent) {
btn.setText(text); btn.setText(text);
btn.setStyleSheet(R"( btn.setStyleSheet(R"(
@@ -240,8 +225,8 @@ void ElidedLabelSP::paintEvent(QPaintEvent *event) {
// ParamControlSP // ParamControlSP
ParamControlSP::ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl) ParamControlSP::ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: ToggleControlSP(title, desc, icon, false, parent, advancedControl){ : ToggleControlSP(title, desc, icon, false, parent) {
key = param.toStdString(); key = param.toStdString();
QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::toggleClicked); QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::toggleClicked);
+9 -27
View File
@@ -57,7 +57,6 @@ class AbstractControlSP : public AbstractControl {
Q_OBJECT Q_OBJECT
public: public:
~AbstractControlSP();
void setDescription(const QString &desc) override { void setDescription(const QString &desc) override {
if (description) description->setText(desc); if (description) description->setText(desc);
} }
@@ -82,30 +81,13 @@ public slots:
description->setVisible(true); description->setVisible(true);
} }
void setVisible(bool visible) override {
bool _visible = visible;
if (isAdvancedControl && !params.getBool("ShowAdvancedControls")) {
_visible = false;
}
AbstractControl::setVisible(_visible);
}
static void RegisterAdvancedControl(AbstractControlSP *ctrl);
static void UnregisterAdvancedControl(AbstractControlSP *ctrl);
static void UpdateAllAdvancedControls();
protected: protected:
AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false); AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
void hideEvent(QHideEvent *e) override; void hideEvent(QHideEvent *e) override;
QVBoxLayout *main_layout; QVBoxLayout *main_layout;
ElidedLabelSP *value; ElidedLabelSP *value;
QLabel *description = nullptr; QLabel *description = nullptr;
bool isAdvancedControl;
private:
Params params;
static std::vector<AbstractControlSP*> advanced_controls_;
}; };
// AbstractControlSP_SELECTOR // AbstractControlSP_SELECTOR
@@ -115,7 +97,7 @@ class AbstractControlSP_SELECTOR : public AbstractControlSP {
protected: protected:
QSpacerItem *spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed); QSpacerItem *spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false); AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
void hideEvent(QHideEvent *e) override; void hideEvent(QHideEvent *e) override;
}; };
@@ -141,7 +123,7 @@ class ButtonControlSP : public AbstractControlSP {
Q_OBJECT Q_OBJECT
public: public:
ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr, bool advancedControl = false); ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr);
inline void setText(const QString &text) { btn.setText(text); } inline void setText(const QString &text) { btn.setText(text); }
inline QString text() const { return btn.text(); } inline QString text() const { return btn.text(); }
inline void click() { btn.click(); } inline void click() { btn.click(); }
@@ -160,7 +142,7 @@ class ToggleControlSP : public AbstractControlSP {
Q_OBJECT Q_OBJECT
public: public:
ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr, bool advancedControl = false) : AbstractControlSP(title, desc, icon, parent, advancedControl) { ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP(title, desc, icon, parent) {
// space between toggle and title // space between toggle and title
icon_label = new QLabel(this); icon_label = new QLabel(this);
hlayout->addWidget(icon_label); hlayout->addWidget(icon_label);
@@ -191,7 +173,7 @@ class ParamControlSP : public ToggleControlSP {
Q_OBJECT Q_OBJECT
public: public:
ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr, bool advancedControl = false); ParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
void setConfirmation(bool _confirm, bool _store_confirm) { void setConfirmation(bool _confirm, bool _store_confirm) {
confirm = _confirm; confirm = _confirm;
store_confirm = _store_confirm; store_confirm = _store_confirm;
@@ -237,7 +219,7 @@ class MultiButtonControlSP : public AbstractControlSP_SELECTOR {
public: public:
MultiButtonControlSP(const QString &title, const QString &desc, const QString &icon, MultiButtonControlSP(const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), button_texts(button_texts), is_inline_layout(inline_layout) { const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
const QString style = R"( const QString style = R"(
QPushButton { QPushButton {
border-radius: 20px; border-radius: 20px;
@@ -389,8 +371,8 @@ class ButtonParamControlSP : public MultiButtonControlSP {
Q_OBJECT Q_OBJECT
public: public:
ButtonParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, ButtonParamControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon, const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : MultiButtonControlSP(title, desc, icon,
button_texts, minimum_button_width, inline_layout, advancedControl) { button_texts, minimum_button_width, inline_layout) {
key = param.toStdString(); key = param.toStdString();
int value = atoi(params.get(key).c_str()); int value = atoi(params.get(key).c_str());
@@ -517,7 +499,7 @@ private:
public: public:
OptionControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon, OptionControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon,
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false,
const QMap<QString, QString> *valMap = nullptr, bool scale_float = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) { const QMap<QString, QString> *valMap = nullptr, bool scale_float = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) {
const QString style = R"( const QString style = R"(
QPushButton { QPushButton {
border-radius: 20px; border-radius: 20px;
+1 -1
View File
@@ -103,7 +103,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose # temporal pose
temporal_pose = modelV2.temporalPoseDEPRECATED temporal_pose = modelV2.temporalPose
temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist() temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist()
temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist() temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist()
temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist() temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist()
+1 -1
View File
@@ -100,7 +100,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose # temporal pose
temporal_pose = modelV2.temporalPoseDEPRECATED temporal_pose = modelV2.temporalPose
if 'sim_pose' in net_output_data: if 'sim_pose' in net_output_data:
temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist() temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist() temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
@@ -1,4 +1,5 @@
import numpy as np import numpy as np
from openpilot.common.params import Params
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
from openpilot.sunnypilot.models.helpers import get_active_bundle from openpilot.sunnypilot.models.helpers import get_active_bundle
@@ -25,6 +26,7 @@ def softmax(x, axis=-1):
class Parser: class Parser:
def __init__(self, ignore_missing=False): def __init__(self, ignore_missing=False):
self.ignore_missing = ignore_missing self.ignore_missing = ignore_missing
self._params = Params()
model_bundle = get_active_bundle() model_bundle = get_active_bundle()
self.generation = model_bundle.generation if model_bundle is not None else None self.generation = model_bundle.generation if model_bundle is not None else None
@@ -91,28 +93,29 @@ class Parser:
outs[name] = pred_mu_final.reshape(final_shape) outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape)
def _parse_plan_mhp(self, outs):
self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION,
out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None: def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'lead' in outs: if self._params.get_bool("DynamicModeldOutputs") or (self.generation >= 12):
if self.generation >= 12 and \ if 'lead' in outs:
outs['lead'].shape[1] == 2 * SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH: if outs['lead'].shape[1] == 2 * SplitModelConstants.LEAD_MHP_SELECTION *SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH:
self.parse_mdn('lead', outs, in_N=0, out_N=0, self.parse_mdn('lead', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH)) out_shape=(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN,SplitModelConstants.LEAD_WIDTH))
else: else:
self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION,
out_shape=(SplitModelConstants.LEAD_TRAJ_LEN,SplitModelConstants.LEAD_WIDTH))
if 'plan' in outs:
if outs['plan'].shape[1] > 2 * SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N:
self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION,
out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
else:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
else:
if 'lead' in outs:
self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION, self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION,
out_shape=(SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH)) out_shape=(SplitModelConstants.LEAD_TRAJ_LEN,SplitModelConstants.LEAD_WIDTH))
if 'plan' in outs: if 'plan' in outs:
if self.generation >= 12 and \ self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION,
outs['plan'].shape[1] > 2 * SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N: out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
self._parse_plan_mhp(outs)
elif self.generation >= 12:
self.parse_mdn('plan', outs, in_N=0, out_N=0,
out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
else:
self._parse_plan_mhp(outs)
def split_outputs(self, outs: dict[str, np.ndarray]) -> None: def split_outputs(self, outs: dict[str, np.ndarray]) -> None:
if 'desired_curvature' in outs: if 'desired_curvature' in outs:
-29
View File
@@ -178,10 +178,6 @@ class ModelManagerSP:
finally: finally:
self.params.put("ModelManager_DownloadIndex", "") self.params.put("ModelManager_DownloadIndex", "")
if self.params.get("ModelManager_ClearCache", block=False, encoding="utf-8"):
self.clear_model_cache()
self.params.remove("ModelManager_ClearCache")
self._report_status() self._report_status()
rk.keep_time() rk.keep_time()
@@ -189,31 +185,6 @@ class ModelManagerSP:
cloudlog.exception(f"Error in main thread: {str(e)}") cloudlog.exception(f"Error in main thread: {str(e)}")
rk.keep_time() rk.keep_time()
def clear_model_cache(self) -> None:
"""
Clears the model cache directory of all files except those in the active model bundle.
"""
# Get list of files used by active model bundle
active_files = []
if self.active_bundle is not None: # When the default model is active
for model in self.active_bundle.models:
if hasattr(model, 'artifact') and model.artifact.fileName:
active_files.append(model.artifact.fileName)
if hasattr(model, 'metadata') and model.metadata.fileName:
active_files.append(model.metadata.fileName)
# Remove all files except active ones
model_dir = Paths.model_root()
try:
for filename in os.listdir(model_dir):
if filename not in active_files:
file_path = os.path.join(model_dir, filename)
if os.path.isfile(file_path):
os.remove(file_path)
cloudlog.info("Model cache cleared, keeping active model files")
except Exception as e:
cloudlog.exception(f"Error clearing model cache: {str(e)}")
def main(): def main():
ModelManagerSP().main_thread() ModelManagerSP().main_thread()
@@ -16,12 +16,12 @@ DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimen
class LongitudinalPlannerSP: class LongitudinalPlannerSP:
def __init__(self, CP: structs.CarParams, mpc): def __init__(self, CP: structs.CarParams, mpc):
self.dec = DynamicExperimentalController(CP, mpc) self.dec = DynamicExperimentalController(CP, mpc)
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None model_bundle = get_active_bundle()
self.generation = model_bundle.generation if model_bundle is not None else None
@property @property
def mlsim(self) -> bool: def mlsim(self) -> bool:
# If we don't have a generation set, we assume it's default model. Which as of today are mlsim. return bool(self.generation is not None and self.generation >= 11)
return bool(self.generation is None or self.generation >= 11)
def get_mpc_mode(self) -> str | None: def get_mpc_mode(self) -> str | None:
if not self.dec.active(): if not self.dec.active():
-4
View File
@@ -55,8 +55,4 @@ namespace Path {
return "/dev/shm"; return "/dev/shm";
#endif #endif
} }
inline std::string model_root() {
return Hardware::PC() ? Path::comma_home() + "/media/0/models" : "/data/media/0/models";
}
} // namespace Path } // namespace Path
+2 -12
View File
@@ -50,14 +50,12 @@ def manager_init() -> None:
("BlindSpot", "0"), ("BlindSpot", "0"),
("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h ("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h
("BlinkerPauseLateralControl", "0"), ("BlinkerPauseLateralControl", "0"),
("Brightness", "0"),
("ChevronInfo", "4"),
("CustomAccIncrementsEnabled", "0"), ("CustomAccIncrementsEnabled", "0"),
("CustomAccLongPressIncrement", "5"), ("CustomAccLongPressIncrement", "5"),
("CustomAccShortPressIncrement", "1"), ("CustomAccShortPressIncrement", "1"),
("DeviceBootMode", "0"), ("DeviceBootMode", "0"),
("DisableUpdates", "0"),
("DynamicExperimentalControl", "0"), ("DynamicExperimentalControl", "0"),
("DynamicModeldOutputs", "0"),
("HyundaiLongitudinalTuning", "0"), ("HyundaiLongitudinalTuning", "0"),
("InteractivityTimeout", "0"), ("InteractivityTimeout", "0"),
("LagdToggle", "1"), ("LagdToggle", "1"),
@@ -68,18 +66,11 @@ def manager_init() -> None:
("MadsUnifiedEngagementMode", "1"), ("MadsUnifiedEngagementMode", "1"),
("MapdVersion", f"{VERSION}"), ("MapdVersion", f"{VERSION}"),
("MaxTimeOffroad", "1800"), ("MaxTimeOffroad", "1800"),
("Brightness", "0"),
("ModelManager_LastSyncTime", "0"), ("ModelManager_LastSyncTime", "0"),
("ModelManager_ModelsCache", ""), ("ModelManager_ModelsCache", ""),
("NeuralNetworkLateralControl", "0"), ("NeuralNetworkLateralControl", "0"),
("QuickBootToggle", "0"),
("QuietMode", "0"), ("QuietMode", "0"),
("ShowAdvancedControls", "0" if build_metadata.tested_channel else "1"),
("EnableHkgTuningAngleSmoothingFactor", "1"),
("HkgTuningAngleMinTorqueReductionGain", "10"),
("HkgTuningAngleActiveTorqueReductionGain", "60"),
("HkgTuningAngleMaxTorqueReductionGain", "100"),
("HkgTuningOverridingCycles", "17"),
("HkgAngleLiveTuning", "0"),
] ]
# device boot mode # device boot mode
@@ -111,7 +102,6 @@ def manager_init() -> None:
params.put("GitCommitDate", build_metadata.openpilot.git_commit_date) params.put("GitCommitDate", build_metadata.openpilot.git_commit_date)
params.put("GitBranch", build_metadata.channel) params.put("GitBranch", build_metadata.channel)
params.put("GitRemote", build_metadata.openpilot.git_origin) params.put("GitRemote", build_metadata.openpilot.git_origin)
params.put_bool("IsDevelopmentBranch", build_metadata.development_channel)
params.put_bool("IsTestedBranch", build_metadata.tested_channel) params.put_bool("IsTestedBranch", build_metadata.tested_channel)
params.put_bool("IsReleaseBranch", build_metadata.release_channel) params.put_bool("IsReleaseBranch", build_metadata.release_channel)
params.put("HardwareSerial", serial) params.put("HardwareSerial", serial)
+1 -1
View File
@@ -114,7 +114,7 @@ procs = [
PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC),
NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)),
PythonProcess("soundd", "selfdrive.ui.soundd", and_(only_onroad, not_joystick)), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad),
PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad),
NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False),
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
+1 -5
View File
@@ -117,13 +117,9 @@ class BuildMetadata:
def master_channel(self) -> bool: def master_channel(self) -> bool:
return self.channel in MASTER_SP_BRANCHES return self.channel in MASTER_SP_BRANCHES
@property
def development_channel(self) -> bool:
return self.channel.startswith("dev-") or self.channel.endswith("-prebuilt")
@property @property
def channel_type(self) -> str: def channel_type(self) -> str:
if self.development_channel: if self.channel.startswith("dev-"):
return "development" return "development"
elif self.channel.startswith("staging-"): elif self.channel.startswith("staging-"):
return "staging" return "staging"
File diff suppressed because it is too large Load Diff
-4
View File
@@ -5,10 +5,6 @@
With joystick_control, you can connect your laptop to your comma device over the network and debug controls using a joystick or keyboard. With joystick_control, you can connect your laptop to your comma device over the network and debug controls using a joystick or keyboard.
joystick_control uses [inputs](https://pypi.org/project/inputs) which supports many common gamepads and joysticks. joystick_control uses [inputs](https://pypi.org/project/inputs) which supports many common gamepads and joysticks.
## Note
You might need to install [xow](https://github.com/medusalix/xow) to use the Xbox One controller on comma device.
Even though **xone** is recommended on the xow page, **xow** is the one that works with the comma device.
## Usage ## Usage
The car must be off, and openpilot must be offroad before starting `joystick_control`. The car must be off, and openpilot must be offroad before starting `joystick_control`.
+46 -115
View File
@@ -3,10 +3,7 @@ import os
import argparse import argparse
import threading import threading
import numpy as np import numpy as np
try: from inputs import UnpluggedError, get_gamepad
import pygame as pg
except ImportError:
print("pygame is not installed. Please install it with 'uv pip install pygame'")
from cereal import messaging from cereal import messaging
from openpilot.common.params import Params from openpilot.common.params import Params
@@ -14,10 +11,7 @@ from openpilot.common.realtime import Ratekeeper
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
from openpilot.tools.lib.kbhit import KBHit from openpilot.tools.lib.kbhit import KBHit
# Set SDL environment variable to avoid using a video driver for headless operation EXPO = 0.4
os.environ["SDL_VIDEODRIVER"] = "dummy"
EXPO = 0.4 # Exponential factor for joystick response curve
class Keyboard: class Keyboard:
@@ -46,122 +40,60 @@ class Keyboard:
return True return True
class PyGameJoystick: class Joystick:
def __init__(self): def __init__(self):
# Initialize pygame and joystick subsystem # This class supports a PlayStation 5 DualSense controller on the comma 3X
pg.init() # TODO: find a way to get this from API or detect gamepad/PC, perhaps "inputs" doesn't support it
if not pg.joystick.get_init(): self.cancel_button = 'BTN_NORTH' # BTN_NORTH=X/triangle
pg.joystick.init()
# Find connected joysticks
joystick_count = pg.joystick.get_count()
if joystick_count == 0:
print("No joysticks found. Please connect a controller.")
exit(1)
# Initialize the first joystick
self.joystick = pg.joystick.Joystick(0)
self.joystick.init()
print(f"Using joystick: {self.joystick.get_name()}")
print(f"Number of axes: {self.joystick.get_numaxes()}")
print(f"Number of buttons: {self.joystick.get_numbuttons()}")
print(f"Number of hats: {self.joystick.get_numhats()}")
# This supports PlayStation and Xbox controllers on different platforms
if HARDWARE.get_device_type() == 'pc': if HARDWARE.get_device_type() == 'pc':
# Xbox mapping on PC accel_axis = 'ABS_Z'
self.accel_axis = 5 # Right trigger steer_axis = 'ABS_RX'
self.brake_axis = 4 # Left trigger # TODO: once the longcontrol API is finalized, we can replace this with outputting gas/brake and steering
self.steer_axis = 0 # Left stick horizontal self.flip_map = {'ABS_RZ': accel_axis}
self.cancel_button = 3 # Y/Triangle button
else: else:
# PlayStation mapping on comma device accel_axis = 'ABS_RX'
self.accel_axis = 5 # R2 steer_axis = 'ABS_Z'
self.brake_axis = 4 # L2 self.flip_map = {'ABS_RY': accel_axis}
self.steer_axis = 0 # Left stick horizontal
self.cancel_button = 3 # Triangle button
# Configure for adaptive mappings based on detected controller self.min_axis_value = {accel_axis: 0., steer_axis: 0.}
controller_name = self.joystick.get_name().lower() self.max_axis_value = {accel_axis: 255., steer_axis: 255.}
if "xbox" in controller_name: self.axes_values = {accel_axis: 0., steer_axis: 0.}
print("Xbox controller detected, using Xbox mappings") self.axes_order = [accel_axis, steer_axis]
self.accel_axis = 5 # Right trigger (RT)
self.brake_axis = 4 # Left trigger (LT)
self.cancel_button = 3 # Y button
elif "playstation" in controller_name or "dual" in controller_name:
print("PlayStation controller detected, using PlayStation mappings")
self.accel_axis = 5 # R2
self.brake_axis = 4 # L2
self.cancel_button = 3 # Triangle
# Initialize values
self.axes_values = {'gb': 0., 'steer': 0.} # Maintain same keys as Keyboard class
self.axes_order = ['gb', 'steer'] # Match expected format
self.cancel = False self.cancel = False
self.deadzone = 0.03 # 3% deadzone for noisy joysticks
# Process events once to clear the event queue
pg.event.pump()
def update(self): def update(self):
# Process pygame events
try: try:
for event in pg.event.get(): joystick_event = get_gamepad()[0]
if event.type == pg.JOYDEVICEREMOVED: except (OSError, UnpluggedError):
if event.instance_id == self.joystick.get_instance_id(): self.axes_values = dict.fromkeys(self.axes_values, 0.)
print("Joystick disconnected!")
self.axes_values = {'gb': 0., 'steer': 0.}
return False
elif event.type == pg.JOYBUTTONDOWN:
if event.button == self.cancel_button:
self.cancel = True
elif event.type == pg.JOYBUTTONUP:
if event.button == self.cancel_button:
self.cancel = False
except Exception as e:
print(f"Error processing events: {e}")
return False return False
# Read current joystick state directly event = (joystick_event.code, joystick_event.state)
try:
if not self.joystick.get_init():
print("Joystick not initialized")
return False
# Read steering (left stick horizontal) # flip left trigger to negative accel
steer_raw = self.joystick.get_axis(self.steer_axis) * -1 if event[0] in self.flip_map:
steer = steer_raw if abs(steer_raw) > self.deadzone else 0.0 event = (self.flip_map[event[0]], -event[1])
# Read gas (right trigger) if event[0] == self.cancel_button:
accel_raw = self.joystick.get_axis(self.accel_axis) if event[1] == 1:
# Convert from [-1, 1] to [0, 1] range (triggers often start at -1 when not pressed) self.cancel = True
accel = (accel_raw + 1) / 2 if self.accel_axis in [4, 5] else accel_raw elif event[1] == 0: # state 0 is falling edge
accel = accel if accel > self.deadzone else 0.0 self.cancel = False
elif event[0] in self.axes_values:
self.max_axis_value[event[0]] = max(event[1], self.max_axis_value[event[0]])
self.min_axis_value[event[0]] = min(event[1], self.min_axis_value[event[0]])
# Read brake (left trigger) norm = -float(np.interp(event[1], [self.min_axis_value[event[0]], self.max_axis_value[event[0]]], [-1., 1.]))
brake_raw = self.joystick.get_axis(self.brake_axis) norm = norm if abs(norm) > 0.03 else 0. # center can be noisy, deadzone of 3%
# Convert from [-1, 1] to [0, 1] range (triggers often start at -1 when not pressed) self.axes_values[event[0]] = EXPO * norm ** 3 + (1 - EXPO) * norm # less action near center for fine control
brake = (brake_raw + 1) / 2 if self.brake_axis in [4, 5] else brake_raw else:
brake = brake if brake > self.deadzone else 0.0
# Apply expo for steering
self.axes_values['steer'] = EXPO * steer**3 + (1 - EXPO) * steer # Apply expo for fine control
# Calculate combined gas/brake value for output [-1, 1] where negative is brake
self.axes_values['gb'] = accel - brake
except Exception as e:
print(f"Error reading joystick: {e}")
self.axes_values = {'gb': 0., 'steer': 0.}
return False return False
return True return True
def send_thread(joystick): def send_thread(joystick):
pm = messaging.PubMaster(['testJoystick']) pm = messaging.PubMaster(['testJoystick'])
rk = Ratekeeper(100, print_delay_threshold=None) rk = Ratekeeper(100, print_delay_threshold=None)
while True: while True:
@@ -173,6 +105,7 @@ def send_thread(joystick):
joystick_msg.testJoystick.axes = [joystick.axes_values[ax] for ax in joystick.axes_order] joystick_msg.testJoystick.axes = [joystick.axes_values[ax] for ax in joystick.axes_order]
pm.send('testJoystick', joystick_msg) pm.send('testJoystick', joystick_msg)
rk.keep_time() rk.keep_time()
@@ -184,12 +117,13 @@ def joystick_control_thread(joystick):
def main(): def main():
joystick_control_thread(PyGameJoystick()) joystick_control_thread(Joystick())
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' + parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' +
'openpilot must be offroad before starting joystick_control. This tool supports ' + 'openpilot must be offroad before starting joystick_control. This tool supports ' +
'PlayStation and Xbox controllers on various platforms.', 'a PlayStation 5 DualSense controller on the comma 3X.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick')
args = parser.parse_args() args = parser.parse_args()
@@ -205,12 +139,9 @@ if __name__ == '__main__':
print('Buttons') print('Buttons')
print('- `R`: Resets axes') print('- `R`: Resets axes')
print('- `C`: Cancel cruise control') print('- `C`: Cancel cruise control')
joystick_control_thread(Keyboard())
else: else:
print('Using pygame joystick') print('Using joystick, make sure to run cereal/messaging/bridge on your device if running over the network!')
print('Standard controller mapping:') print('If not running on a comma device, the mapping may need to be adjusted.')
print('- Left stick: Steering')
print('- Right trigger (R2): Gas') joystick = Keyboard() if args.keyboard else Joystick()
print('- Left trigger (L2): Brake') joystick_control_thread(joystick)
print('- Triangle/Y button: Cancel')
joystick_control_thread(PyGameJoystick())
+2 -8
View File
@@ -19,24 +19,18 @@ def joystickd_thread():
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
VM = VehicleModel(CP) VM = VehicleModel(CP)
sm = messaging.SubMaster(['carState', 'onroadEvents', 'liveParameters', 'selfdriveState', 'testJoystick', 'selfdriveStateSP'], frequency=1. / DT_CTRL) sm = messaging.SubMaster(['carState', 'onroadEvents', 'liveParameters', 'selfdriveState', 'testJoystick'], frequency=1. / DT_CTRL)
pm = messaging.PubMaster(['carControl', 'controlsState']) pm = messaging.PubMaster(['carControl', 'controlsState'])
rk = Ratekeeper(100, print_delay_threshold=None) rk = Ratekeeper(100, print_delay_threshold=None)
while 1: while 1:
sm.update(0) sm.update(0)
ss_sp = sm['selfdriveStateSP']
_lat_active = False
if ss_sp.mads.available:
_lat_active = ss_sp.mads.active
else:
_lat_active = sm['selfdriveState'].active
cc_msg = messaging.new_message('carControl') cc_msg = messaging.new_message('carControl')
cc_msg.valid = True cc_msg.valid = True
CC = cc_msg.carControl CC = cc_msg.carControl
CC.enabled = sm['selfdriveState'].enabled CC.enabled = sm['selfdriveState'].enabled
CC.latActive = _lat_active and not sm['carState'].steerFaultTemporary and not sm['carState'].steerFaultPermanent CC.latActive = sm['selfdriveState'].active and not sm['carState'].steerFaultTemporary and not sm['carState'].steerFaultPermanent
CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in sm['onroadEvents']) and CP.openpilotLongitudinalControl CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in sm['onroadEvents']) and CP.openpilotLongitudinalControl
CC.cruiseControl.cancel = sm['carState'].cruiseState.enabled and (not CC.enabled or not CP.pcmCruise) CC.cruiseControl.cancel = sm['carState'].cruiseState.enabled and (not CC.enabled or not CP.pcmCruise)
CC.hudControl.leadDistanceBars = 2 CC.hudControl.leadDistanceBars = 2
@@ -1,131 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab tab_name="actuator data" containers="1">
<Container>
<DockSplitter sizes="0.25;0.25;0.25;0.25" count="4" orientation="-">
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-1.050000" top="1.050000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#17becf" name="/carControl/actuators/torque"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-385.132391" top="536.899115" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#1f77b4" name="/carControl/actuators/steeringAngleDeg"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-0.364800" top="14.956815" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#ff7f0e" name="/carState/vEgoRaw"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-0.208517" top="0.149191" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#f14cc1" name="/carControl/actuators/curvature"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab tab_name="steering messages (CAN)" containers="1">
<Container>
<DockSplitter sizes="0.200218;0.200218;0.199129;0.200218;0.200218" count="5" orientation="-">
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-189.000000" top="189.000000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#1ac938" name="/can/2/LKAS_ALT/LKAS_ANGLE_CMD"/>
<curve color="#9467bd" name="/can/128/LKAS_ALT/LKAS_ANGLE_CMD"/>
<curve color="#ff0e1c" name="/can/192/LKAS_ALT/LKAS_ANGLE_CMD"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-6.200000" top="254.200000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#f14cc1" name="/can/128/LKAS_ALT/LKAS_ANGLE_MAX_TORQUE"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-0.025000" top="1.025000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#d62728" name="/carState/steeringPressed"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="460.325000" top="1874.675000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#1f77b4" name="/pandaStates/0/safetyTxBlocked"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-0.025000" top="1.025000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#f14cc1" name="/can/1/CCNC_0x161/DAW_ICON"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab tab_name="Understanding Torque" containers="1">
<Container>
<DockSplitter sizes="0.5;0.5" count="2" orientation="-">
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-6.175000" top="253.175000" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#1f77b4" name="/can/1/LFA_ALT/LKAS_ANGLE_MAX_TORQUE"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" style="Lines" mode="TimeSeries">
<range bottom="-41.182501" top="42.082498" right="590.696728" left="429.901019"/>
<limitY/>
<curve color="#1ac938" name="/carState/steeringTorqueEps"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="1"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<customMathEquations/>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -1,209 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab tab_name="actuator data" containers="1">
<Container>
<DockSplitter orientation="-" sizes="0.25;0.25;0.25;0.25" count="4">
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-1.050000" top="1.050000" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#17becf" name="/carControl/actuators/torque"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-453.043646" top="538.555487" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#1f77b4" name="/carControl/actuators/steeringAngleDeg"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-0.647461" top="26.545898" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#ff7f0e" name="/carState/vEgoRaw"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-0.209157" top="0.175448" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#f14cc1" name="/carControl/actuators/curvature"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab tab_name="steering messages (CAN)" containers="1">
<Container>
<DockSplitter orientation="-" sizes="0.200218;0.200218;0.199129;0.200218;0.200218" count="5">
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-189.000000" top="189.000000" right="723.115449" left="6.371879"/>
<limitY/>
<curve color="#b6ff00" name="/can/128/LKAS_ALT/LKAS_ANGLE_CMD"/>
<curve color="#00fab2" name="/can/0/LKAS_ALT/LKAS_ANGLE_CMD"/>
<curve color="#d62728" name="/can/192/LKAS_ALT/LKAS_ANGLE_CMD"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-6.250000" top="256.250000" right="722.420439" left="6.497277"/>
<limitY/>
<curve color="#00ffe8" name="/can/128/LKAS_ALT/LKAS_ANGLE_MAX_TORQUE"/>
<curve color="#e5fd00" name="/can/0/LKAS_ALT/LKAS_ANGLE_MAX_TORQUE"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-4.399729" top="4.056632" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#ff7f0e" name="IMU_LatAccelVal_ms^2_roll_compensated"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-0.025000" top="1.025000" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#d62728" name="/carState/steeringPressed"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-0.025000" top="1.025000" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#f14cc1" name="/can/1/CCNC_0x161/DAW_ICON"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab tab_name="Understanding Torque" containers="1">
<Container>
<DockSplitter orientation="-" sizes="0.5;0.5" count="2">
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-6.250000" top="256.250000" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#1f77b4" name="/can/1/LFA_ALT/LKAS_ANGLE_MAX_TORQUE"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" mode="TimeSeries" flip_x="false" flip_y="false">
<range bottom="-42.062501" top="78.162503" right="722.420439" left="0.000000"/>
<limitY/>
<curve color="#1ac938" name="/carState/steeringTorqueEps"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="1"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default delimiter="0" time_axis=""/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<customMathEquations>
<snippet name="Desired lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
<snippet name="Actual lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_ms^2_roll_compensated">
<global></global>
<function>if (v1 == 0 and v2 == 1) then
return (value * -9.8) - (v3 * 9.81)
end
--return 0
return (value * -9.8) - (v3 * 9.81)</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
<v3>/liveParameters/roll</v3>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^3">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^2">
<global></global>
<function>if (v1 == 0 and v2 == 1) then
return value * -9.8
end
return 0</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="roll compensated lateral acceleration">
<global></global>
<function>if (v3 == 0 and v4 == 1) then
return (value * v1 ^ 2) - (v2 * 9.81)
end
return 0</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
<v3>/carState/steeringPressed</v3>
<v4>/carControl/latActive</v4>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_ms^2">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
</customMathEquations>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -1,281 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget name="Main Window" parent="main_window">
<Tab containers="1" tab_name="tab1">
<Container>
<DockSplitter sizes="0.500397;0.499603" count="2" orientation="-">
<DockArea name="...">
<plot flip_x="false" flip_y="false" mode="TimeSeries" style="Lines">
<range top="256.250000" left="0.000000" right="421.102293" bottom="-6.250000"/>
<limitY/>
<curve color="#1ac938" name="/can/1/LFA_ALT/LKAS_ANGLE_MAX_TORQUE"/>
<curve color="#17becf" name="max torque(calc)">
<transform name="Moving Average" alias="max torque(calc)[Moving Average]">
<options compensate_offset="true" value="10"/>
</transform>
</curve>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" mode="TimeSeries" style="Lines">
<range top="2.776497" left="0.000000" right="421.102293" bottom="-2.918548"/>
<limitY/>
<curve color="#f14cc1" name="desired lat accel"/>
<curve color="#888888" name="zero"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="0"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<previouslyLoaded_Datafiles>
<fileInfo prefix="" filename="../tmpa_e8kwpj.rlog">
<selected_datasources value=""/>
</fileInfo>
</previouslyLoaded_Datafiles>
<!-- - - - - - - - - - - - - - - -->
<customMathEquations>
<snippet name="zero">
<global>min=0
max=250
max_from_speed=96
rate_lim = 500
la_deadzone = 0.38
k1=200
k2=20
k3=1.0
k4=1
k5=10
old = 0
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
function apply_rate_limit(old, new, limit)
return math.min(math.max(new, old - limit), old + limit)
end
function apply_deadzone(val, deadzone)
if math.abs(val) &lt;= deadzone then
return 0.0
elseif val &lt; 0.0 then
return val + deadzone
else
return val - deadzone
end
end</global>
<function>return 0</function>
<linked_source>/carState/aEgo</linked_source>
</snippet>
<snippet name="max torque lj adj">
<global>min=0
max=250
max_from_speed=96
k1=200
k2=30
k3=1
k4=1
k5=10
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end</global>
<function>return 250 - value * 20</function>
<linked_source>desired lateral jark</linked_source>
</snippet>
<snippet name="ang_cmd rate">
<global>firstX = 0
firstY = 0
is_first = true
secondX = 0
secondY = 0
is_second = false</global>
<function>-- Wait for initial values
if (is_first) then
is_first = false
is_second = true
firstX = time
firstY = value
end
if (is_second) then
is_second = false
secondX = time
secondY = value
end
-- Central derivative: dy/dx ~= f(x+delta_x)-f(x-delta_x)/(2*delta_x)
dx = time - firstX
dy = value - firstY
-- Increment
firstX = secondX
firstY = secondY
secondX = time
secondY = value
return dy/dx</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
</snippet>
<snippet name="max torque(calc)">
<global>min=0
max=250
max_from_speed=96
rate_lim = 500
la_deadzone = 0.38
k1=200
k2=20
k3=1.0
k4=1
k5=10
old = 0
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
function apply_rate_limit(old, new, limit)
return math.min(math.max(new, old - limit), old + limit)
end
function apply_deadzone(val, deadzone)
if math.abs(val) &lt;= deadzone then
return 0.0
elseif val &lt; 0.0 then
return val + deadzone
else
return val - deadzone
end
end</global>
<function>la = apply_deadzone(v2, la_deadzone)
lj = v3
if la == 0.0 then
lj = 0.0
end
fla = math.min(math.abs(k1 * la)^k3, max)
flj = math.min(math.abs(k2 * lj)^k4, max)
out = fla
flv = math.min(max_from_speed, k5 * v4)
out = out + flv
out = math.max(math.min(out, max), min)
if sign(la) == sign(lj) then
out = out - flj
else
out = out + flj
end
if v5 == 1.0 then
out = 0.0
end
out = math.max(math.min(out, max), min)
out = apply_rate_limit(old, out, rate_lim)
old = out
return out</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
<additional_sources>
<v1>ang_cmd rate</v1>
<v2>desired lat accel</v2>
<v3>desired lateral jark</v3>
<v4>/carState/vEgo</v4>
<v5>/can/1/LFA_ALT/LKAS_ANGLE_ACTIVE</v5>
</additional_sources>
</snippet>
<snippet name="desired lateral jark">
<global>firstX = 0
firstY = 0
is_first = true
secondX = 0
secondY = 0
is_second = false</global>
<function>-- Wait for initial values
if (is_first) then
is_first = false
is_second = true
firstX = time
firstY = value
end
if (is_second) then
is_second = false
secondX = time
secondY = value
end
-- Central derivative: dy/dx ~= f(x+delta_x)-f(x-delta_x)/(2*delta_x)
dx = time - firstX
dy = value - firstY
-- Increment
firstX = secondX
firstY = secondY
secondX = time
secondY = value
return dy/dx</function>
<linked_source>desired lat accel</linked_source>
</snippet>
<snippet name="abs(des la accel)">
<global></global>
<function>return math.abs(value)</function>
<linked_source>desired lat accel</linked_source>
</snippet>
<snippet name="desired lat accel">
<global></global>
<function>return value * v1^2</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
</additional_sources>
</snippet>
<snippet name="abs(ang_cmd)">
<global></global>
<function>return math.abs(value)</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
</snippet>
</customMathEquations>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -1,175 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab tab_name="tab1" containers="1">
<Container>
<DockSplitter sizes="0.25;0.25;0.25;0.25" count="4" orientation="-">
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-3.582051" right="1431.113121" top="5.314632" left="5.322399"/>
<limitY/>
<curve name="Actual lateral accel (roll compensated)" color="#1ac938"/>
<curve name="Desired lateral accel (roll compensated)" color="#ff7f0e"/>
<curve name="IMU_LatAccelVal_ms^2" color="#f14cc1"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-3.741271" right="1431.113121" top="3.756006" left="5.322399"/>
<limitY/>
<curve name="roll compensated lateral acceleration" color="#ff7f0e"/>
<curve name="IMU_LatAccelVal_Ms^2" color="#1ac938"/>
<curve name="IMU_LatAccelVal_ms^2_roll_compensated" color="#9467bd"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-0.025000" right="1431.113121" top="1.025000" left="5.322399"/>
<limitY/>
<curve name="/carState/steeringPressed" color="#0097ff"/>
<curve name="/carOutput/actuatorsOutput/torque" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-1.660728" right="1431.113121" top="67.942958" left="5.322399"/>
<limitY/>
<curve name="/carState/vEgo" color="#f14cc1">
<transform name="Scale/Offset" alias="/carState/vEgo[Scale/Offset]">
<options value_scale="2.23694" time_offset="0" value_offset="0"/>
</transform>
</curve>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab tab_name="tab2" containers="1">
<Container>
<DockSplitter sizes="0.5;0.5" count="2" orientation="-">
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="30595.900000" right="1431.113121" top="34884.100000" left="5.322399"/>
<limitY/>
<curve name="/can/1/IMU_01_10ms/IMU_RollRtVal" color="#17becf"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-0.058795" right="1431.113121" top="0.072448" left="5.322399"/>
<limitY/>
<curve name="/liveParameters/roll" color="#bcbd22"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="1"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<customMathEquations>
<snippet name="IMU_LatAccelVal_ms^2">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="roll compensated lateral acceleration">
<global></global>
<function>if (v3 == 0 and v4 == 1) then
return (value * v1 ^ 2) - (v2 * 9.81)
end
return 0</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
<v3>/carState/steeringPressed</v3>
<v4>/carControl/latActive</v4>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^2">
<global></global>
<function>if (v1 == 0 and v2 == 1) then
return value * -9.8
end
return 0</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^3">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_ms^2_roll_compensated">
<global></global>
<function>
if (v1 == 0 and v2 == 1) then
return (value * -9.8) - (v3 * 9.81)
end
return 0</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
<v3>/liveParameters/roll</v3>
</additional_sources>
</snippet>
<snippet name="Actual lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
<snippet name="Desired lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
</customMathEquations>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>