Merge branch 'master-new' of https://github.com/sunnypilot/sunnypilot into master-new

This commit is contained in:
infiniteCable2
2025-04-13 20:51:07 +02:00
15 changed files with 245 additions and 55 deletions
@@ -122,10 +122,6 @@ jobs:
fi
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
- name: Build Panda
run: |
scons -j$(nproc) cache_dir=${{env.SCONS_CACHE_DIR}} ${{ github.workspace }}/panda
- name: Build Main Project
run: |
export PYTHONPATH="$BUILD_DIR"
+1
View File
@@ -132,6 +132,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"CarParamsSPPersistent", PERSISTENT},
{"CarPlatformBundle", PERSISTENT},
{"EnableGithubRunner", PERSISTENT | BACKUP},
{"MaxTimeOffroad", PERSISTENT | BACKUP},
{"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION},
{"OffroadMode", CLEAR_ON_MANAGER_START},
{"OffroadMode_Status", CLEAR_ON_MANAGER_START},
+27 -6
View File
@@ -5,13 +5,17 @@ from collections import deque
from typing import Any
import capnp
from cereal import messaging, log, car
from cereal import messaging, log, car, custom
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL, Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog
from openpilot.common.simple_kalman import KF1D
from opendbc.car import structs
from opendbc.car.hyundai.values import HyundaiFlags
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP
# Default lead acceleration decay set to 50% at 1s
_LEAD_ACCEL_TAU = 1.5
@@ -157,7 +161,7 @@ def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: floa
def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capnp._DynamicStructReader,
model_v_ego: float, low_speed_override: bool = True) -> dict[str, Any]:
model_v_ego: float, CP: structs.CarParams, CP_SP: structs.CarParamsSP, low_speed_override: bool = True) -> dict[str, Any]:
# Determine leads, this is where the essential logic happens
if len(tracks) > 0 and ready and lead_msg.prob > .5:
track = match_vision_to_track(v_ego, lead_msg, tracks)
@@ -167,6 +171,7 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn
lead_dict = {'status': False}
if track is not None:
lead_dict = track.get_RadarState(lead_msg.prob)
lead_dict = get_custom_yrel(CP, CP_SP, lead_dict, lead_msg)
elif (track is None) and ready and (lead_msg.prob > .5):
lead_dict = get_RadarState_from_vision(lead_msg, v_ego, model_v_ego)
@@ -182,8 +187,20 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn
return lead_dict
def get_custom_yrel(CP: structs.CarParams, CP_SP: structs.CarParamsSP, lead_dict: dict[str, Any],
lead_msg: capnp._DynamicStructReader) -> dict[str, Any]:
if CP.brand == "hyundai" and (CP_SP.flags & HyundaiFlagsSP.ENHANCED_SCC or
CP.flags & (HyundaiFlags.CANFD_CAMERA_SCC | HyundaiFlags.CAMERA_SCC)):
lead_dict['yRel'] = float(-lead_msg.y[0])
return lead_dict
class RadarD:
def __init__(self, delay: float = 0.0):
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParams, delay: float = 0.0):
self.CP = CP
self.CP_SP = CP_SP
self.current_time = 0.0
self.tracks: dict[int, Track] = {}
@@ -239,8 +256,8 @@ class RadarD:
model_v_ego = self.v_ego
leads_v3 = sm['modelV2'].leadsV3
if len(leads_v3) > 1:
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False)
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, self.CP, self.CP_SP, low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, self.CP, self.CP_SP, low_speed_override=False)
def publish(self, pm: messaging.PubMaster):
assert self.radar_state is not None
@@ -260,11 +277,15 @@ def main() -> None:
CP = messaging.log_from_bytes(Params().get("CarParams", block=True), car.CarParams)
cloudlog.info("radard got CarParams")
cloudlog.info("radard is waiting for CarParamsSP")
CP_SP = messaging.log_from_bytes(Params().get("CarParamsSP", block=True), custom.CarParamsSP)
cloudlog.info("radard got CarParamsSP")
# *** setup messaging
sm = messaging.SubMaster(['modelV2', 'carState', 'liveTracks'], poll='modelV2')
pm = messaging.PubMaster(['radarState'])
RD = RadarD(CP.radarDelay)
RD = RadarD(CP, CP_SP, CP.radarDelay)
while 1:
sm.update()
+1
View File
@@ -21,6 +21,7 @@ qt_src = [
"sunnypilot/qt/offroad/offroad_home.cc",
"sunnypilot/qt/offroad/settings/device_panel.cc",
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
"sunnypilot/qt/offroad/settings/settings.cc",
"sunnypilot/qt/offroad/settings/software_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink_panel.cc",
@@ -31,15 +31,16 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
continue;
}
auto *btn = new PushButtonSP(text, 720, this, param);
auto *btn = new PushButtonSP(text, 750, this, param);
btn->setObjectName(id);
device_grid_layout->addWidget(btn, row, col);
buttons[id] = btn;
col++;
if (col > 1) {
col = 0;
if (col==0) {
device_grid_layout->addWidget(btn, row, col, Qt::AlignLeft);
col++;
} else {
device_grid_layout->addWidget(btn, row, col, Qt::AlignRight);
col=0;
row++;
}
}
@@ -74,19 +75,24 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
connect(buttons["resetParams"], &PushButtonSP::clicked, this, &DevicePanelSP::resetSettings);
// Max Time Offroad
maxTimeOffroad = new MaxTimeOffroad();
connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh);
addItem(maxTimeOffroad);
addItem(device_grid_layout);
// offroad mode and power buttons
QHBoxLayout *power_layout = new QHBoxLayout();
power_layout->setSpacing(5);
power_layout->setSpacing(25);
PushButtonSP *rebootBtn = new PushButtonSP(tr("Reboot"), 720, this);
PushButtonSP *rebootBtn = new PushButtonSP(tr("Reboot"), 750, this);
rebootBtn->setStyleSheet(rebootButtonStyle);
power_layout->addWidget(rebootBtn);
QObject::connect(rebootBtn, &PushButtonSP::clicked, this, &DevicePanelSP::reboot);
PushButtonSP *poweroffBtn = new PushButtonSP(tr("Power Off"), 720, this);
PushButtonSP *poweroffBtn = new PushButtonSP(tr("Power Off"), 750, this);
poweroffBtn->setStyleSheet(powerOffButtonStyle);
power_layout->addWidget(poweroffBtn);
QObject::connect(poweroffBtn, &PushButtonSP::clicked, this, &DevicePanelSP::poweroff);
@@ -100,7 +106,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode);
QVBoxLayout *power_group_layout = new QVBoxLayout();
power_group_layout->setSpacing(30);
power_group_layout->setSpacing(25);
power_group_layout->addWidget(offroadBtn, 0, Qt::AlignHCenter);
power_group_layout->addLayout(power_layout);
@@ -7,6 +7,7 @@
#pragma once
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/max_time_offroad.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
@@ -23,6 +24,7 @@ public:
private:
std::map<QString, PushButtonSP*> buttons;
PushButtonSP *offroadBtn;
MaxTimeOffroad *maxTimeOffroad;
const QString alwaysOffroadStyle = R"(
PushButtonSP {
@@ -0,0 +1,53 @@
/**
* 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/max_time_offroad.h"
// Map of Max Offroad Time Options (Minutes)
const QMap<QString, QString> MaxTimeOffroad::offroad_time_options = {
{"0", "0"}, // Always On
{"1", "5"},
{"2", "10"},
{"3", "15"},
{"4", "30"},
{"5", "60"},
{"6", "120"},
{"7", "180"},
{"8", "300"},
{"9", "600"},
{"10", "1440"},
{"11", "1800"}
};
MaxTimeOffroad::MaxTimeOffroad() : OptionControlSP(
"MaxTimeOffroad",
tr("Max Time Offroad"),
tr("Device will automatically shutdown after set time once the engine is turned off.<br/>(30h is the default)"),
"../assets/offroad/icon_blank.png",
{0, 11}, 1, true, &offroad_time_options) {
refresh();
}
void MaxTimeOffroad::refresh() {
const int maxOffroadInMinutes = QString::fromStdString(params.get("MaxTimeOffroad")).toInt();
const bool useHours = maxOffroadInMinutes >= 60;
QString label;
if (maxOffroadInMinutes == 0) {
label = tr("Always On");
} else {
const int value = useHours ? maxOffroadInMinutes / 60 : maxOffroadInMinutes;
label = QString("%1%2").arg(value).arg(useHours ? tr("h") : tr("m"));
}
if (maxOffroadInMinutes == 1800) {
label += tr(" (default)");
}
setLabel(label);
}
@@ -0,0 +1,25 @@
/**
* 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/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
class MaxTimeOffroad : public OptionControlSP {
Q_OBJECT
public:
static const QMap<QString, QString> offroad_time_options;
MaxTimeOffroad();
void refresh();
private:
Params params;
};
@@ -85,7 +85,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
});
// Backup Settings
backupSettings = new PushButtonSP(tr("Backup Settings"), 730, this);
backupSettings = new PushButtonSP(tr("Backup Settings"), 750, this);
backupSettings->setObjectName("backup_btn");
connect(backupSettings, &QPushButton::clicked, [=]() {
backupSettings->setEnabled(false);
@@ -96,7 +96,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
});
// Restore Settings
restoreSettings = new PushButtonSP(tr("Restore Settings"), 730, this);
restoreSettings = new PushButtonSP(tr("Restore Settings"), 750, this);
restoreSettings->setObjectName("restore_btn");
connect(restoreSettings, &QPushButton::clicked, [=]() {
restoreSettings->setEnabled(false);
@@ -108,10 +108,9 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
// Settings Restore and Settings Backup in the same horizontal space
auto settings_layout = new QHBoxLayout;
settings_layout->setContentsMargins(0, 0, 0, 30);
settings_layout->addWidget(backupSettings);
settings_layout->addWidget(backupSettings, 0, Qt::AlignLeft);
settings_layout->addSpacing(10);
settings_layout->addWidget(restoreSettings);
settings_layout->setAlignment(Qt::AlignLeft);
settings_layout->addWidget(restoreSettings, 0, Qt::AlignRight);
list->addItem(settings_layout);
QObject::connect(uiState(), &UIState::offroadTransition, this, &SunnylinkPanel::updatePanel);
@@ -132,10 +132,7 @@ AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, con
if (isVisible && spacingItem) {
main_layout->removeItem(spacingItem);
delete spacingItem;
spacingItem = nullptr;
} else if (!isVisible && spacingItem == nullptr) {
spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
} else if (!isVisible && spacingItem != nullptr && main_layout->indexOf(spacingItem) == -1) {
main_layout->insertItem(main_layout->indexOf(description), spacingItem);
}
}
@@ -145,8 +142,7 @@ AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, con
}
main_layout->addLayout(hlayout);
if (!desc.isEmpty() && spacingItem == nullptr) {
spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
if (!desc.isEmpty() && spacingItem != nullptr && main_layout->indexOf(spacingItem) == -1) {
main_layout->insertItem(main_layout->count(), spacingItem);
}
@@ -166,8 +162,7 @@ void AbstractControlSP_SELECTOR::hideEvent(QHideEvent *e) {
description->hide();
}
if (spacingItem == nullptr) {
spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
if (spacingItem != nullptr && main_layout->indexOf(spacingItem) == -1) {
main_layout->insertItem(main_layout->indexOf(description), spacingItem);
}
}
+50 -13
View File
@@ -96,11 +96,10 @@ class AbstractControlSP_SELECTOR : public AbstractControlSP {
Q_OBJECT
protected:
QSpacerItem *spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
void hideEvent(QHideEvent *e) override;
private:
QSpacerItem *spacingItem = nullptr;
};
// widget to display a value
@@ -417,14 +416,29 @@ class OptionControlSP : public AbstractControlSP_SELECTOR {
Q_OBJECT
private:
bool isInlineLayout;
QHBoxLayout *optionSelectorLayout = isInlineLayout ? new QHBoxLayout() : hlayout;
struct MinMaxValue {
int min_value;
int max_value;
};
int getParamValue() {
const auto param_value = QString::fromStdString(params.get(key));
const auto result = valueMap != nullptr ? valueMap->key(param_value) : param_value;
return result.toInt();
}
// Although the method is not static, and thus has access to the value property, I prefer to be explicit about the value.
void setParamValue(const int new_value) {
const auto value_str = valueMap != nullptr ? valueMap->value(QString::number(new_value)) : QString::number(new_value);
params.put(key, value_str.toStdString());
}
public:
OptionControlSP(const QString &param, const QString &title, const QString &desc, const QString &icon,
const MinMaxValue &range, const int per_value_change = 1) : _title(title), AbstractControlSP_SELECTOR(title, desc, icon) {
const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap<QString, QString> *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), isInlineLayout(inline_layout) {
const QString style = R"(
QPushButton {
border-radius: 20px;
@@ -444,14 +458,27 @@ public:
}
)";
if (inline_layout) {
optionSelectorLayout->setMargin(0);
optionSelectorLayout->setSpacing(0);
if (!title.isEmpty()) {
main_layout->removeWidget(title_label);
hlayout->addWidget(title_label, 1);
}
if (spacingItem != nullptr && main_layout->indexOf(spacingItem) != -1) {
main_layout->removeItem(spacingItem);
spacingItem = nullptr;
}
}
label.setStyleSheet(label_enabled_style);
label.setFixedWidth(300);
label.setFixedWidth(inline_layout ? 350 : 300);
label.setAlignment(Qt::AlignCenter);
const std::vector<QString> button_texts{"", ""};
key = param.toStdString();
value = atoi(params.get(key).c_str());
value = getParamValue();
button_group = new QButtonGroup(this);
button_group->setExclusive(true);
@@ -459,19 +486,18 @@ public:
QPushButton *button = new QPushButton(button_texts[i], this);
button->setStyleSheet(style + ((i == 0) ? "QPushButton { text-align: left; }" :
"QPushButton { text-align: right; }"));
hlayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter);
optionSelectorLayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter);
if (i == 0) {
hlayout->addWidget(&label, 0, Qt::AlignCenter);
optionSelectorLayout->addWidget(&label, 0, Qt::AlignCenter);
}
button_group->addButton(button, i);
QObject::connect(button, &QPushButton::clicked, [=]() {
int change_value = (i == 0) ? -per_value_change : per_value_change;
key = param.toStdString();
value = atoi(params.get(key).c_str());
value = getParamValue(); // in case it changed externally, we need to get the latest value.
value += change_value;
value = std::clamp(value, range.min_value, range.max_value);
params.put(key, QString::number(value).toStdString());
setParamValue(value);
button_group->button(0)->setEnabled(!(value <= range.min_value));
button_group->button(1)->setEnabled(!(value >= range.max_value));
@@ -484,7 +510,13 @@ public:
});
}
hlayout->setAlignment(Qt::AlignLeft);
optionSelectorLayout->setAlignment(Qt::AlignLeft);
if (isInlineLayout) {
QFrame *container = new QFrame;
container->setLayout(optionSelectorLayout);
container->setStyleSheet("background-color: #393939; border-radius: 20px;");
hlayout->addWidget(container);
}
}
void setUpdateOtherToggles(bool _update) {
@@ -506,6 +538,10 @@ public:
protected:
void paintEvent(QPaintEvent *event) override {
if (isInlineLayout) {
return;
}
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
@@ -513,8 +549,8 @@ protected:
int w = 0;
int h = 150;
for (int i = 0; i < hlayout->count(); ++i) {
QWidget *widget = qobject_cast<QWidget *>(hlayout->itemAt(i)->widget());
for (int i = 0; i < optionSelectorLayout->count(); ++i) {
QWidget *widget = qobject_cast<QWidget *>(optionSelectorLayout->itemAt(i)->widget());
if (widget) {
w += widget->width();
}
@@ -544,6 +580,7 @@ private:
std::map<QString, QString> option_label = {};
bool request_update = false;
QString _title = "";
const QMap<QString, QString> *valueMap;
const QString label_enabled_style = "font-size: 50px; font-weight: 450; color: #FFFFFF;";
const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;";
+8 -6
View File
@@ -5,16 +5,18 @@ This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from cereal import car, log, custom
from cereal import log, custom
from opendbc.car import structs
from opendbc.car.hyundai.values import HyundaiFlags
from openpilot.sunnypilot.mads.state import StateMachine, GEARS_ALLOW_PAUSED_SILENT
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
ButtonType = car.CarState.ButtonEvent.Type
ButtonType = structs.CarState.ButtonEvent.Type
EventName = log.OnroadEvent.EventName
EventNameSP = custom.OnroadEventSP.EventName
SafetyModel = car.CarParams.SafetyModel
GearShifter = structs.CarState.GearShifter
SafetyModel = structs.CarParams.SafetyModel
SET_SPEED_BUTTONS = (ButtonType.accelCruise, ButtonType.resumeCruise, ButtonType.decelCruise, ButtonType.setCruise)
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
@@ -48,7 +50,7 @@ class ModularAssistiveDrivingSystem:
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
def update_events(self, CS: car.CarState):
def update_events(self, CS: structs.CarState):
def update_unified_engagement_mode():
uem_blocked = self.enabled or (self.selfdrive.enabled and self.selfdrive.enabled_prev)
if (self.unified_engagement_mode and uem_blocked) or not self.unified_engagement_mode:
@@ -70,7 +72,7 @@ class ModularAssistiveDrivingSystem:
if self.events.has(EventName.seatbeltNotLatched):
replace_event(EventName.seatbeltNotLatched, EventNameSP.silentSeatbeltNotLatched)
transition_paused_state()
if self.events.has(EventName.wrongGear):
if self.events.has(EventName.wrongGear) and (CS.standstill or CS.gearShifter == GearShifter.reverse):
replace_event(EventName.wrongGear, EventNameSP.silentWrongGear)
transition_paused_state()
if self.events.has(EventName.reverseGear):
@@ -133,7 +135,7 @@ class ModularAssistiveDrivingSystem:
else:
self.events.remove(EventName.wrongCarMode)
def update(self, CS: car.CarState):
def update(self, CS: structs.CarState):
if not self.enabled_toggle:
return
+18 -2
View File
@@ -106,7 +106,23 @@ class PowerMonitoring:
def get_car_battery_capacity(self) -> int:
return int(self.car_battery_capacity_uWh)
# See if we need to shutdown
# Max Time Offroad
def max_time_offroad_exceeded(self, offroad_time):
"""
Check if the max time offroad has been exceeded. If the value is 0, it means no limit.
:param offroad_time: Time spent offroad in seconds
:return: True if the max time offroad has been exceeded, False otherwise
"""
try:
param = self.params.get("MaxTimeOffroad", encoding="utf8")
sp_max_time_val_s = int(param) * 60 if param is not None and int(param) >= 0 else MAX_TIME_OFFROAD_S
except Exception:
sp_max_time_val_s = MAX_TIME_OFFROAD_S
return sp_max_time_val_s > 0 and offroad_time >= sp_max_time_val_s
# See if we need to shutdown
def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, started_seen: bool):
if offroad_timestamp is None:
return False
@@ -116,7 +132,7 @@ class PowerMonitoring:
offroad_time = (now - offroad_timestamp)
low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S
should_shutdown |= self.max_time_offroad_exceeded(offroad_time)
should_shutdown |= low_voltage_shutdown
should_shutdown |= (self.car_battery_capacity_uWh <= 0)
should_shutdown &= not ignition
+36 -1
View File
@@ -2,7 +2,7 @@ import pytest
from openpilot.common.params import Params
from openpilot.system.hardware.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \
CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S
CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S, MAX_TIME_OFFROAD_S
# Create fake time
ssb = 0.
@@ -197,3 +197,38 @@ class TestPowerMonitoring:
offroad_timestamp,
started_seen), \
f"Should shutdown after {DELAY_SHUTDOWN_TIME_S} seconds offroad time"
@pytest.mark.parametrize(
"max_time_offroad, offroad_time_min, expected_result",
[
# No max time set fallback to default (30 hours)
(None, 0, False),
(None, MAX_TIME_OFFROAD_S + 1, True), # exceeds 30h (1800+ mins)
# Valid max time values (in minutes)
("60", 59, False), # under limit
("60", 120, True), # over limit
("10", 8, False), # under limit
("10", 11, True), # over limit
# Edge case: max time is zero → no limit enforced
("0", 0, False),
("0", 400, False),
# Invalid max time formats or negative values → fallback to 30 hours
("invalid", 100, False), # should fallback to 30h
("-1", MAX_TIME_OFFROAD_S + 1, True), # should fallback to 30h, and exceed it
]
)
def test_max_time_offroad_exceeded(self, max_time_offroad, offroad_time_min, expected_result):
# Set the parameter if provided
if max_time_offroad is not None:
self.params.put("MaxTimeOffroad", max_time_offroad)
# Convert offroad time from minutes to seconds
offroad_time_s = offroad_time_min * 60
pm = PowerMonitoring()
result = pm.max_time_offroad_exceeded(offroad_time_s)
assert result == expected_result
+1
View File
@@ -50,6 +50,7 @@ def manager_init() -> None:
("MadsMainCruiseAllowed", "1"),
("MadsPauseLateralOnBrake", "0"),
("MadsUnifiedEngagementMode", "1"),
("MaxTimeOffroad", "1800"),
("ModelManager_LastSyncTime", "0"),
("ModelManager_ModelsCache", ""),
("NeuralNetworkLateralControl", "0"),