O3 - 2025/06/10

This commit is contained in:
Rick Lan
2025-04-08 22:45:08 +08:00
parent c85f3eab3f
commit 3b15e530e0
20 changed files with 384 additions and 18 deletions
+3
View File
@@ -122,4 +122,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
{"Version", PERSISTENT},
{"dp_device_last_log", CLEAR_ON_ONROAD_TRANSITION},
{"dp_device_reset_conf", CLEAR_ON_MANAGER_START},
{"dp_device_is_rhd", PERSISTENT},
{"dp_device_monitoring_disabled", PERSISTENT},
{"dp_device_beep", PERSISTENT},
};
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
'''
MIT Non-Commercial License
Copyright (c) 2019, dragonpilot
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, for non-commercial purposes only, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- Commercial use (e.g., use in a product, service, or activity intended to generate revenue) is prohibited without explicit written permission from dragonpilot. Contact ricklan@gmail.com for inquiries.
- Any project that uses the Software must visibly mention the following acknowledgment: "This project uses software from dragonpilot and is licensed under a custom license requiring permission for use."
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
beepd is a soundd replacement that makes beep alerts via GPIO
'''
import subprocess
import time
from cereal import car, messaging
from openpilot.common.realtime import Ratekeeper
import threading
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
class Beepd:
def __init__(self):
self.current_alert = AudibleAlert.none
self.enable_gpio()
def enable_gpio(self):
try:
subprocess.run("echo 42 | sudo tee /sys/class/gpio/export",
shell=True,
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
encoding='utf8')
except Exception:
pass
subprocess.run("echo \"out\" | sudo tee /sys/class/gpio/gpio42/direction",
shell=True,
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
encoding='utf8')
def _beep(self, on):
val = "1" if on else "0"
subprocess.run(f"echo \"{val}\" | sudo tee /sys/class/gpio/gpio42/value",
shell=True,
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
encoding='utf8')
def engage(self):
self._beep(True)
time.sleep(0.05)
self._beep(False)
def disengage(self):
for _ in range(2):
self._beep(True)
time.sleep(0.01)
self._beep(False)
time.sleep(0.01)
def warning(self):
for _ in range(3):
self._beep(True)
time.sleep(0.01)
self._beep(False)
time.sleep(0.01)
def warning_immediate(self):
for _ in range(5):
self._beep(True)
time.sleep(0.01)
self._beep(False)
time.sleep(0.01)
def dispatch_beep(self, func):
threading.Thread(target=func, daemon=True).start()
def update_alert(self, new_alert):
current_alert_played_once = self.current_alert == AudibleAlert.none
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
self.current_alert = new_alert
# if new_alert == AudibleAlert.engage:
# self.dispatch_beep(self.engage)
# if new_alert == AudibleAlert.disengage:
# self.dispatch_beep(self.disengage)
if new_alert == AudibleAlert.warningImmediate:
self.dispatch_beep(self.warning_immediate)
def get_audible_alert(self, sm):
if sm.updated['selfdriveState']:
new_alert = sm['selfdriveState'].alertSound.raw
self.update_alert(new_alert)
def test_beepd_thread(self):
frame = 0
rk = Ratekeeper(20)
pm = messaging.PubMaster(['selfdriveState'])
while True:
cs = messaging.new_message('selfdriveState')
if frame == 20:
cs.selfdriveState.alertSound = AudibleAlert.engage
if frame == 40:
cs.selfdriveState.alertSound = AudibleAlert.disengage
if frame == 60:
cs.selfdriveState.alertSound = AudibleAlert.prompt
if frame == 80:
cs.selfdriveState.alertSound = AudibleAlert.disengage
if frame == 85:
cs.selfdriveState.alertSound = AudibleAlert.prompt
pm.send("selfdriveState", cs)
frame += 1
rk.keep_time()
def beepd_thread(self, test=False):
if test:
threading.Thread(target=self.test_beepd_thread, daemon=True).start()
sm = messaging.SubMaster(['selfdriveState'])
rk = Ratekeeper(20)
while True:
sm.update(0)
self.get_audible_alert(sm)
rk.keep_time()
def main():
s = Beepd()
s.beepd_thread(test=False)
if __name__ == "__main__":
main()
+19
View File
@@ -27,6 +27,24 @@ function agnos_init {
fi
}
no_amp() {
output=$(i2cget -y 0 0x10 0x00 2>/dev/null)
if [ -z "$output" ]; then
return 0
else
return 1
fi
}
function check_device_mode {
if no_amp; then
echo "O3 Lite Mode"
export DISABLE_DRIVER=1
else
echo "C3 Mode"
fi
}
function launch {
# Remove orphaned git lock if it exists on boot
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
@@ -71,6 +89,7 @@ function launch {
# hardware specific init
if [ -f /AGNOS ]; then
check_device_mode
agnos_init
fi
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
import time
import cereal.messaging as messaging
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_DMON
from cereal import log
EventName = log.OnroadEvent.EventName
class SimpleDriverMonitoring:
def __init__(self):
# Timing configuration (in seconds)
self.FIRST_WARNING_TIME = 45.0
self.SECOND_WARNING_TIME = 60.0
self.THIRD_WARNING_TIME = 75.0
# State variables
self.awareness = 1.0 # Full awareness
self.current_events = []
# self.last_interaction_time = 0
self.hands_on_steering = False
# Warning thresholds (normalized to 0-1 scale)
self.threshold_prompt = self.FIRST_WARNING_TIME / self.THIRD_WARNING_TIME # ~0.643 for first warning
self.threshold_critical = self.SECOND_WARNING_TIME / self.THIRD_WARNING_TIME # ~0.857 for second warning
# Step change (how much awareness decreases per step)
self.step_change = DT_DMON / self.THIRD_WARNING_TIME
params = Params()
self.is_rhd = params.get_bool("dp_device_is_rhd")
self.monitoring_disabled = params.get_bool("dp_device_monitoring_disabled")
def update_events(self, reset_condition, op_engaged):
self.current_events = []
if self.monitoring_disabled:
return
# If not engaged, reset awareness and return
if not op_engaged:
self.awareness = 1.0
return
# Reset awareness on any reset condition (standstill, any input)
if reset_condition:
self.awareness = 1.0
return
# Only decrease awareness if we're not detecting hands on steering
self.awareness = max(self.awareness - self.step_change, 0.0)
# Determine alert level based on awareness
if self.awareness <= 0.0:
# Third warning (red alert) at 70 seconds
self.current_events.append(EventName.driverUnresponsive)
elif self.awareness <= (1.0 - self.threshold_critical):
# Second warning (orange alert) at 60 seconds
self.current_events.append(EventName.promptDriverUnresponsive)
elif self.awareness <= (1.0 - self.threshold_prompt):
# First warning (green alert) at 45 seconds
self.current_events.append(EventName.preDriverUnresponsive)
def get_state_packet(self, valid=True):
# Create driver monitoring state message
dat = messaging.new_message('driverMonitoringState', valid=valid)
events = []
for event_name in self.current_events:
event = log.OnroadEvent.new_message()
event.name = event_name
events.append(event)
dat.driverMonitoringState = {
"events": events,
"faceDetected": False, # Not using face detection
"isDistracted": self.awareness <= (1.0 - self.threshold_prompt),
"distractedType": 0, # Not using distraction types
"awarenessStatus": 1.0, #self.awareness, (always 1.0 so no decel)
"posePitchOffset": 0.0,
"posePitchValidCount": 0,
"poseYawOffset": 0.0,
"poseYawValidCount": 0,
"stepChange": self.step_change,
"awarenessActive": self.awareness,
"awarenessPassive": self.awareness,
"isLowStd": True,
"hiStdCount": 0,
"isActiveMode": True,
"isRHD": self.is_rhd,
}
return dat
def dmonitoringd_thread():
# Configure process priority
config_realtime_process([0, 1, 2, 3], 5)
# Initialize parameters and messaging
pm = messaging.PubMaster(['driverMonitoringState'])
sm = messaging.SubMaster(['carState', 'selfdriveState'])
# Initialize driver monitoring system
DM = SimpleDriverMonitoring()
# Create ratekeeper for 20Hz operation
rk = Ratekeeper(20, None)
# Main loop running at 20Hz
while True:
sm.update()
# Check if steering is touched (only monitoring steering for hands-on)
# Reset conditions: not engaged, standstill, or any input
reset_condition = (
sm['carState'].standstill or
sm['carState'].steeringPressed or
sm['carState'].gasPressed or
sm['carState'].brakePressed or
sm['carState'].leftBlinker or
sm['carState'].rightBlinker
)
# Process driver monitoring - monitoring only steering for hands-on
# but resetting on any input
DM.update_events(
reset_condition=reset_condition,
op_engaged=sm['selfdriveState'].enabled
)
# Publish driver monitoring state
dat = DM.get_state_packet()
pm.send('driverMonitoringState', dat)
# Maintain 20Hz
rk.keep_time()
def main():
dmonitoringd_thread()
if __name__ == '__main__':
main()
+3 -2
View File
@@ -372,6 +372,7 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control)
static uint16_t prev_fan_speed = 999;
static uint16_t ir_pwr = 0;
static uint16_t prev_ir_pwr = 999;
const bool disable_driver = getenv("DISABLE_DRIVER");
static FirstOrderFilter integ_lines_filter(0, 30.0, 0.05);
@@ -386,7 +387,7 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control)
}
}
if (sm.updated("driverCameraState")) {
if (!disable_driver && sm.updated("driverCameraState")) {
auto event = sm["driverCameraState"];
int cur_integ_lines = event.getDriverCameraState().getIntegLines();
@@ -403,7 +404,7 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control)
}
// Disable IR on input timeout
if (nanos_since_boot() - last_driver_camera_t > 1e9) {
if (!disable_driver && nanos_since_boot() - last_driver_camera_t > 1e9) {
ir_pwr = 0;
}
+2
View File
@@ -72,6 +72,8 @@ class SelfdriveD:
if REPLAY:
# no vipc in replay will make them ignored anyways
ignore += ['roadCameraState', 'wideRoadCameraState']
if os.getenv("DISABLE_DRIVER"):
ignore += ['driverCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
+19
View File
@@ -205,17 +205,36 @@ void DPPanel::add_device_toggles() {
QString::fromUtf8("🐉 ") + tr("Device"),
"",
},
{
"dp_device_is_rhd",
tr("Enable Right-Hand Drive Mode"),
tr("Allow openpilot to obey right-hand traffic conventions on right driver seat."),
},
{
"dp_device_monitoring_disabled",
tr("Disable Driver Monitoring"),
"",
},
{
"dp_device_beep",
tr("Enable Beep (Warning)"),
"",
}
};
QWidget *label = nullptr;
bool has_toggle = false;
const bool disable_driver = getenv("DISABLE_DRIVER");
for (auto &[param, title, desc] : toggle_defs) {
if (param.isEmpty()) {
label = new LabelControl(title, "");
addItem(label);
continue;
}
if ((param == "dp_device_is_rhd" || param == "dp_device_monitoring_disabled" || param == "dp_device_beep") && !disable_driver) {
continue;
}
has_toggle = true;
auto toggle = new ParamControl(param, title, desc, "", this);
+7 -3
View File
@@ -96,8 +96,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
const bool disable_driver = getenv("DISABLE_DRIVER");
for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) {
if ((param == "AlwaysOnDM" || param == "RecordFront") && disable_driver) {
continue;
}
auto toggle = new ParamControl(param, title, desc, icon, this);
bool locked = params.getBool((param + "Lock").toStdString());
@@ -207,6 +210,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A"))));
addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
const bool disable_driver = getenv("DISABLE_DRIVER");
pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"),
tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
connect(pair_device, &ButtonControl::clicked, [=]() {
@@ -216,12 +220,12 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(pair_device);
// offroad-only buttons
if (!disable_driver) {
auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"));
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
addItem(dcamBtn);
}
auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
+6 -2
View File
@@ -24,7 +24,9 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget *par
void AnnotatedCameraWidget::updateState(const UIState &s) {
// update engageability/experimental mode button
experimental_btn->updateState(s);
dmon.updateState(s);
if (!s.scene.disable_driver) {
dmon.updateState(s);
}
}
void AnnotatedCameraWidget::initializeGL() {
@@ -130,7 +132,9 @@ void AnnotatedCameraWidget::paintGL() {
painter.setPen(Qt::NoPen);
model.draw(painter, rect());
dmon.draw(painter, rect());
if (!s->scene.disable_driver) {
dmon.draw(painter, rect());
}
hud.updateState(*s);
hud.draw(painter, rect());
+2 -1
View File
@@ -26,7 +26,8 @@ QString getVersion() {
}
QString getBrand() {
return QObject::tr("dragonpilot");
const bool disable_driver = getenv("DISABLE_DRIVER");
return QObject::tr("dragonpilot") + (disable_driver ? QString::fromStdString(" - Lite") : QString(""));
}
QString getUserAgent() {
+1
View File
@@ -65,6 +65,7 @@ static void update_state(UIState *s) {
void ui_update_params(UIState *s) {
auto params = Params();
s->scene.is_metric = params.getBool("IsMetric");
s->scene.disable_driver = getenv("DISABLE_DRIVER");
}
void UIState::updateStatus() {
+1
View File
@@ -60,6 +60,7 @@ typedef struct UIScene {
float light_sensor = -1;
bool started, ignition, is_metric;
uint64_t started_frame;
bool disable_driver = false;
} UIScene;
class UIState : public QObject {
+5 -1
View File
@@ -11,6 +11,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
import os
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
@@ -43,6 +44,9 @@ def register(show_spinner=False) -> str | None:
dongle_id = UNREGISTERED_DONGLE_ID
cloudlog.warning(f"missing public key: {pubkey}")
elif dongle_id is None:
if os.getenv("DISABLE_DRIVER"):
params.put("DongleId", UNREGISTERED_DONGLE_ID)
return dongle_id
if show_spinner:
from openpilot.system.ui.spinner import Spinner
spinner = Spinner()
@@ -74,7 +78,7 @@ def register(show_spinner=False) -> str | None:
try:
register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, private_key, algorithm='RS256')
cloudlog.info("getting pilotauth")
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
resp = api_get("v2/pilotauth/", method='POST', timeout=5,
imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
if resp.status_code in (402, 403):
+1
View File
@@ -270,6 +270,7 @@ void camerad_thread() {
// *** per-cam init ***
std::vector<std::unique_ptr<CameraState>> cams;
for (const auto &config : ALL_CAMERA_CONFIGS) {
if (!config.enabled) continue;
auto cam = std::make_unique<CameraState>(&m, config);
cam->init(&v, device_id, ctx);
cams.emplace_back(std::move(cam));
+1 -1
View File
@@ -326,7 +326,7 @@ def hardware_thread(end_event, hw_queue) -> None:
set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text)
# TODO: this should move to TICI.initialize_hardware, but we currently can't import params there
if TICI and HARDWARE.get_device_type() == "tici":
if TICI and HARDWARE.get_device_type() == "tici" and not os.getenv("DISABLE_DRIVER"):
if not os.path.isfile("/persist/comma/living-in-the-moment"):
if not Path("/data/media").is_mount():
set_offroad_alert_if_changed("Offroad_StorageMissing", True)
+4 -2
View File
@@ -95,7 +95,7 @@ class Tici(HardwareBase):
@cached_property
def amplifier(self):
if self.get_device_type() == "mici":
if self.get_device_type() == "mici" or os.getenv("DISABLE_DRIVER"):
return None
return Amplifier()
@@ -191,7 +191,7 @@ class Tici(HardwareBase):
return str(self.get_modem().Get(MM_MODEM, 'EquipmentIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT))
def get_network_info(self):
if self.get_device_type() == "mici":
if self.get_device_type() == "mici" or os.getenv("DISABLE_DRIVER"):
return None
try:
modem = self.get_modem()
@@ -283,6 +283,8 @@ class Tici(HardwareBase):
return None
def get_modem_temperatures(self):
if os.getenv("DISABLE_DRIVER"):
return []
timeout = 0.2 # Default timeout is too short
try:
modem = self.get_modem()
+21 -1
View File
@@ -222,11 +222,23 @@ void loggerd_thread() {
std::unique_ptr<Context> ctx(Context::create());
std::unique_ptr<Poller> poller(Poller::create());
const bool disable_driver = getenv("DISABLE_DRIVER");
const std::set<std::string> driver_signals = {
"driverCameraState",
"driverEncodeIdx",
"driverStateV2",
"driverMonitoringState",
"driverEncodeData",
"livestreamDriverEncodeIdx",
"livestreamDriverEncodeData"
};
// subscribe to all socks
for (const auto& [_, it] : services) {
const bool encoder = util::ends_with(it.name, "EncodeData");
const bool livestream_encoder = util::starts_with(it.name, "livestream");
if (!it.should_log && (!encoder || livestream_encoder)) continue;
if (disable_driver && driver_signals.count(it.name)) continue;
LOGD("logging %s", it.name.c_str());
SubSocket * sock = SubSocket::create(ctx.get(), it.name);
@@ -249,7 +261,15 @@ void loggerd_thread() {
std::map<std::string, EncoderInfo> encoder_infos_dict;
for (const auto &cam : cameras_logged) {
for (const auto &encoder_info : cam.encoder_infos) {
encoder_infos_dict[encoder_info.publish_name] = encoder_info;
const std::string &name = encoder_info.publish_name;
if (disable_driver && (
name == "driverEncodeData" ||
name == "driverEncodeIdx" ||
name == "livestreamDriverEncodeData" ||
name == "livestreamDriverEncodeIdx")) {
continue;
}
encoder_infos_dict[name] = encoder_info;
s.max_waiting++;
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ const EncoderInfo main_wide_road_encoder_info = {
const EncoderInfo main_driver_encoder_info = {
.publish_name = "driverEncodeData",
.filename = "dcamera.hevc",
.record = Params().getBool("RecordFront"),
.record = !getenv("DISABLE_DRIVER") && Params().getBool("RecordFront"),
INIT_ENCODE_FUNCTIONS(DriverEncode),
};
+3
View File
@@ -40,6 +40,9 @@ def manager_init() -> None:
("OpenpilotEnabledToggle", "1"),
("LongitudinalPersonality", str(log.LongitudinalPersonality.standard)),
("DisableLogging", "0"),
("dp_device_is_rhd", "0"),
("dp_device_monitoring_disabled", "0"),
("dp_device_beep", "0"),
]
if params.get_bool("RecordFrontLock"):
+6 -4
View File
@@ -73,15 +73,16 @@ procs = [
PythonProcess("webcamerad", "tools.webcam.camerad", driverview, enabled=WEBCAM),
NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad, platform.system() != "Darwin"),
NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad, platform.system() != "Darwin"),
PythonProcess("micd", "system.micd", iscar),
PythonProcess("micd", "system.micd", iscar, enabled=not os.getenv("DISABLE_DRIVER")),
PythonProcess("timed", "system.timed", always_run, enabled=not PC),
PythonProcess("modeld", "selfdrive.modeld.modeld", only_onroad),
PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC) and not os.getenv("DISABLE_DRIVER")),
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)),
PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad),
PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad, enabled=not os.getenv("DISABLE_DRIVER")),
PythonProcess("beepd", "dragonpilot.selfdrive.ui.beepd", only_onroad, enabled=(Params().get_bool("dp_device_beep") and os.getenv("DISABLE_DRIVER"))),
PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad),
NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False),
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
@@ -91,7 +92,8 @@ procs = [
PythonProcess("selfdrived", "selfdrive.selfdrived.selfdrived", only_onroad),
PythonProcess("card", "selfdrive.car.card", only_onroad),
PythonProcess("deleter", "system.loggerd.deleter", always_run),
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(WEBCAM or not PC) and not os.getenv("DISABLE_DRIVER")),
PythonProcess("dpmonitoringd", "selfdrive.monitoring.dpmonitoringd", only_onroad, enabled=os.getenv("DISABLE_DRIVER")),
PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
PythonProcess("pandad", "selfdrive.pandad.pandad", always_run),
PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad),