mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-07 14:42:08 +08:00
Operation Bigfoot
This commit is contained in:
@@ -584,6 +584,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SteerOffsetStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerRatio", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"SteerRatioStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StockConfidenceBallWidget", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"StockDongleId", {PERSISTENT, STRING, "", ""}},
|
||||
{"StopAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
{"StopAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
|
||||
|
||||
@@ -327,9 +327,18 @@ def get_bolt_acc_pedal_friction_brake(apply_brake, full_brake_accel, v_ego, para
|
||||
full_brake_accel = min(full_brake_accel, -0.1)
|
||||
legacy_full_scale = max(-params.ACCEL_MIN, 0.1)
|
||||
corrected_scale = legacy_full_scale / max(-full_brake_accel, 0.1)
|
||||
speed_gain = float(np.interp(v_ego, [0.0, 10.0, 25.0], [1.0, 1.15, 1.3]))
|
||||
speed_gain = float(np.interp(v_ego, [0.0, 8.0, 15.0, 25.0], [1.0, 1.08, 1.2, 1.35]))
|
||||
onset_gain = float(np.interp(
|
||||
apply_brake,
|
||||
[0.0, 5.0, 20.0, 60.0, 120.0, 240.0, params.MAX_BRAKE],
|
||||
[0.0, 1.8, 1.65, 1.4, 1.22, 1.08, 1.0],
|
||||
))
|
||||
|
||||
return int(round(np.clip(apply_brake * corrected_scale * speed_gain, 0, params.MAX_BRAKE)))
|
||||
shaped_brake = apply_brake * corrected_scale * speed_gain * onset_gain
|
||||
minimum_brake = float(np.interp(v_ego, [0.0, 6.0, 8.0, 12.0, 18.0, 25.0], [0.0, 0.0, 4.0, 10.0, 20.0, 28.0]))
|
||||
shaped_brake = max(shaped_brake, minimum_brake)
|
||||
|
||||
return int(round(np.clip(shaped_brake, 0, params.MAX_BRAKE)))
|
||||
|
||||
|
||||
def shape_bolt_acc_pedal_low_speed_friction(apply_brake: int, v_ego: float, stopping: bool, active: bool):
|
||||
@@ -383,6 +392,16 @@ def get_bolt_acc_pedal_friction_command_state(apply_brake: int, cruise_main_on:
|
||||
return command_brake, release_frames, should_send
|
||||
|
||||
|
||||
def get_interceptor_sng_gas_cmd(CP, interceptor_gas_cmd: float, accel: float, params, maneuver_mode: bool) -> float:
|
||||
if maneuver_mode:
|
||||
return max(interceptor_gas_cmd, float(np.interp(accel, [0.0, 1.0, 2.0], [params.SNG_INTERCEPTOR_GAS, 0.11, 0.16])))
|
||||
|
||||
if supports_bolt_acc_pedal_friction_experiment(CP):
|
||||
return max(interceptor_gas_cmd, params.SNG_INTERCEPTOR_GAS)
|
||||
|
||||
return params.SNG_INTERCEPTOR_GAS
|
||||
|
||||
|
||||
def should_use_fixed_stopping_brake(CP, near_stop: bool, stopping: bool, resume: bool) -> bool:
|
||||
if not (near_stop and stopping and not resume):
|
||||
return False
|
||||
@@ -971,9 +990,9 @@ class CarController(CarControllerBase):
|
||||
self.apply_gas > self.params.INACTIVE_REGEN and
|
||||
use_interceptor_sng_launch(self.CP, CS, maneuver_sng_launch)
|
||||
):
|
||||
interceptor_gas_cmd = self.params.SNG_INTERCEPTOR_GAS
|
||||
if maneuver_sng_launch:
|
||||
interceptor_gas_cmd = max(interceptor_gas_cmd, float(np.interp(actuators.accel, [0.0, 1.0, 2.0], [self.params.SNG_INTERCEPTOR_GAS, 0.11, 0.16])))
|
||||
interceptor_gas_cmd = get_interceptor_sng_gas_cmd(
|
||||
self.CP, interceptor_gas_cmd, actuators.accel, self.params, maneuver_sng_launch,
|
||||
)
|
||||
self.apply_brake = 0
|
||||
self.apply_gas = self.params.INACTIVE_REGEN
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ from opendbc.car.gm.carcontroller import (
|
||||
get_bolt_acc_pedal_effective_brake_switch,
|
||||
get_bolt_acc_pedal_planner_brake_switch,
|
||||
get_bolt_pedal_long_accel_limit,
|
||||
get_interceptor_sng_gas_cmd,
|
||||
get_lka_steering_cmd_counter,
|
||||
get_volt_one_pedal_target_decel,
|
||||
get_testing_ground_1_brake_switch_bias,
|
||||
@@ -164,6 +165,18 @@ def test_bolt_acc_pedal_friction_blend_biases_small_commands_upward_at_speed():
|
||||
assert high_speed > low_speed
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_applies_a_minimum_pre_stop_command_at_speed():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
assert get_bolt_acc_pedal_friction_brake(2, -2.86, 17.0, params) >= 18
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_friction_blend_boosts_midrange_commands_before_stopping_phase():
|
||||
params = SimpleNamespace(ACCEL_MIN=-4.0, MAX_BRAKE=400)
|
||||
|
||||
assert get_bolt_acc_pedal_friction_brake(40, -2.86, 15.0, params) >= 80
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_low_speed_friction_ignores_tiny_inactive_brake_requests():
|
||||
apply_brake, active = shape_bolt_acc_pedal_low_speed_friction(9, 5.0, False, False)
|
||||
|
||||
@@ -818,6 +831,30 @@ def test_bolt_acc_pedal_sng_launch_uses_physical_standstill_without_stock_acc_bi
|
||||
assert not use_interceptor_sng_launch(CP, _sng_cs(1.2, False, False))
|
||||
|
||||
|
||||
def test_bolt_acc_pedal_sng_launch_preserves_stronger_computed_pedal():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
)
|
||||
params = SimpleNamespace(SNG_INTERCEPTOR_GAS=18. / 255.)
|
||||
|
||||
assert get_interceptor_sng_gas_cmd(CP, 0.2, 0.54, params, False) == pytest.approx(0.2)
|
||||
|
||||
|
||||
def test_other_pedal_sng_launch_keeps_fixed_floor_behavior():
|
||||
CP = SimpleNamespace(
|
||||
carFingerprint=CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
openpilotLongitudinalControl=True,
|
||||
enableGasInterceptorDEPRECATED=True,
|
||||
flags=GMFlags.PEDAL_LONG.value,
|
||||
)
|
||||
params = SimpleNamespace(SNG_INTERCEPTOR_GAS=18. / 255.)
|
||||
|
||||
assert get_interceptor_sng_gas_cmd(CP, 0.2, 0.54, params, False) == pytest.approx(18. / 255.)
|
||||
|
||||
|
||||
def test_use_interceptor_sng_launch_extends_for_maneuver_mode():
|
||||
CP = SimpleNamespace(vEgoStarting=0.25)
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ REDNECK_BUTTON_COPIES_TIME_METRIC = [REDNECK_BUTTON_COPIES_TIME, 40]
|
||||
ANGLE_SAFETY_BASELINE_MODEL = str(CAR.KIA_SPORTAGE_HEV_2026)
|
||||
DEFAULT_ANGLE_SMOOTHING_VEGO_BP = [5.0, 10.0, 20.0]
|
||||
DEFAULT_ANGLE_SMOOTHING_ALPHA_V = [0.2, 0.1, 0.0]
|
||||
EV9_HIGH_ANGLE_CONTROL_LIMIT = MAX_ANGLE
|
||||
|
||||
|
||||
def egmp_dynamic_longitudinal_tuning(CP) -> bool:
|
||||
@@ -394,6 +395,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
v_ego_raw = CS.out.vEgoRaw
|
||||
ev9_high_angle_inhibit = self.CP.carFingerprint == CAR.KIA_EV9 and abs(CS.out.steeringAngleDeg) >= EV9_HIGH_ANGLE_CONTROL_LIMIT
|
||||
desired_angle = float(np.clip(actuators.steeringAngleDeg,
|
||||
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
|
||||
self.params.ANGLE_LIMITS.STEER_ANGLE_MAX))
|
||||
@@ -418,6 +420,14 @@ class CarController(CarControllerBase):
|
||||
apply_angle = CS.out.steeringAngleDeg
|
||||
apply_steer_req = False
|
||||
|
||||
if ev9_high_angle_inhibit:
|
||||
apply_torque = 0
|
||||
apply_angle = float(np.clip(CS.out.steeringAngleDeg,
|
||||
-self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
|
||||
self.params.ANGLE_LIMITS.STEER_ANGLE_MAX))
|
||||
apply_steer_req = False
|
||||
self.angle_filter.x = apply_angle
|
||||
|
||||
self.apply_angle_last = apply_angle
|
||||
if not CC.latActive:
|
||||
self.apply_angle_last = float(np.clip(CS.out.steeringAngleDeg,
|
||||
@@ -617,8 +627,8 @@ class CarController(CarControllerBase):
|
||||
steering_msg_active = apply_steer_req
|
||||
if self.CP.carFingerprint == CAR.KIA_EV9 and self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
# EV9 faults if the angle-steering status drops inactive during torque limiting.
|
||||
# Hold the angle path active while lateral is active; gain/angle are already limited above.
|
||||
steering_msg_active = CC.latActive
|
||||
# Hold the angle path active while lateral is active, except during high-angle maneuvers.
|
||||
steering_msg_active = CC.latActive and abs(CS.out.steeringAngleDeg) < EV9_HIGH_ANGLE_CONTROL_LIMIT
|
||||
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled,
|
||||
steering_msg_active, apply_torque, apply_angle,
|
||||
@@ -627,7 +637,10 @@ class CarController(CarControllerBase):
|
||||
lka_icon=lka_icon))
|
||||
|
||||
# prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU
|
||||
if self.frame % 5 == 0 and lka_steering:
|
||||
suppress_lfa = bool(lka_steering)
|
||||
if self.CP.carFingerprint == CAR.KIA_EV9 and self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
suppress_lfa = bool(steering_msg_active)
|
||||
if self.frame % 5 == 0 and suppress_lfa:
|
||||
can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg,
|
||||
self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT))
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@ class CarState(CarStateBase):
|
||||
self.cruise_buttons: deque = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
|
||||
self.main_buttons: deque = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
|
||||
self.lda_button = 0
|
||||
self.sonata_hybrid_lkas_source = None
|
||||
self.sonata_hybrid_lkas_sources = {
|
||||
"bcm": 0,
|
||||
"clu13": 0,
|
||||
"swl_stat": 0,
|
||||
}
|
||||
self.lda_button_raw = 0
|
||||
self.lda_button_raw_initialized = False
|
||||
self.lda_button_last_raw_rise_ts_nanos = 0
|
||||
@@ -204,10 +210,8 @@ class CarState(CarStateBase):
|
||||
return button_events
|
||||
|
||||
def create_lkas_button_events(self, cp: CANParser, prev_lda_button: int) -> list[structs.CarState.ButtonEvent]:
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID and cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0:
|
||||
# Route-proven: late-model Sonata Hybrid publishes a live LKAS button on BCM_PO_11
|
||||
# while CLU13 is present on the main bus but does not carry the LKAS state.
|
||||
self.lda_button = int(cp.vl["BCM_PO_11"]["LDA_BTN"])
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID:
|
||||
self.lda_button = self.get_sonata_hybrid_lkas_button_state(cp)
|
||||
# Some classic HKG platforms publish the LKAS button on the cluster bus instead of BCM_PO_11.
|
||||
elif cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0:
|
||||
self.lda_button = int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"])
|
||||
@@ -218,6 +222,32 @@ class CarState(CarStateBase):
|
||||
|
||||
return create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})
|
||||
|
||||
def get_sonata_hybrid_lkas_button_state(self, cp: CANParser) -> int:
|
||||
source_states = {
|
||||
"bcm": int(cp.vl["BCM_PO_11"]["LDA_BTN"]) if cp.ts_nanos["BCM_PO_11"]["LDA_BTN"] > 0 else 0,
|
||||
"clu13": int(cp.vl["CLU13"]["CF_Clu_LdwsLkasSW"]) if cp.ts_nanos["CLU13"]["CF_Clu_LdwsLkasSW"] > 0 else 0,
|
||||
"swl_stat": int(cp.vl["CLU13"]["CF_Clu_SWL_Stat"] == 4) if cp.ts_nanos["CLU13"]["CF_Clu_SWL_Stat"] > 0 else 0,
|
||||
}
|
||||
|
||||
changed_sources = [source for source, state in source_states.items() if state != self.sonata_hybrid_lkas_sources[source]]
|
||||
active_sources = [source for source, state in source_states.items() if state]
|
||||
|
||||
selected_source = None
|
||||
if self.sonata_hybrid_lkas_source in changed_sources:
|
||||
selected_source = self.sonata_hybrid_lkas_source
|
||||
elif active_sources:
|
||||
selected_source = active_sources[0]
|
||||
elif changed_sources:
|
||||
selected_source = changed_sources[0]
|
||||
elif self.sonata_hybrid_lkas_source is not None:
|
||||
selected_source = self.sonata_hybrid_lkas_source
|
||||
|
||||
self.sonata_hybrid_lkas_sources.update(source_states)
|
||||
if selected_source is not None:
|
||||
self.sonata_hybrid_lkas_source = selected_source
|
||||
return source_states[selected_source]
|
||||
return 0
|
||||
|
||||
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
@@ -879,6 +879,69 @@ class TestHyundaiFingerprint:
|
||||
ret = update(0, 0, 3)
|
||||
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
|
||||
|
||||
def test_sonata_hybrid_falls_back_to_main_bus_clu13_lkas_button_when_bcm_stays_dead(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x391] = 8
|
||||
fingerprint[1][0x50C] = 8
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], False, False, False, toggles)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], CP, toggles)
|
||||
|
||||
car_state = CarState(CP, FPCP)
|
||||
can_parsers = car_state.get_can_parsers(CP)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
|
||||
def update(clu13_lkas_button: int, bcm_lkas_button: int, frame: int):
|
||||
msgs = [
|
||||
packer.make_can_msg("CLU13", 0, {
|
||||
"CF_Clu_LdwsLkasSW": clu13_lkas_button,
|
||||
}),
|
||||
packer.make_can_msg("BCM_PO_11", 0, {
|
||||
"LDA_BTN": bcm_lkas_button,
|
||||
}),
|
||||
]
|
||||
can_parsers[Bus.pt].update([(frame, msgs)])
|
||||
return car_state.update(can_parsers, toggles)[0]
|
||||
|
||||
update(0, 0, 1)
|
||||
ret = update(1, 0, 2)
|
||||
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
|
||||
|
||||
ret = update(0, 0, 3)
|
||||
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
|
||||
|
||||
def test_sonata_hybrid_falls_back_to_main_bus_clu13_swl_stat_lkas_button_when_other_sources_are_dead(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x391] = 8
|
||||
fingerprint[1][0x50C] = 8
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], False, False, False, toggles)
|
||||
FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_SONATA_HYBRID, fingerprint, [], CP, toggles)
|
||||
|
||||
car_state = CarState(CP, FPCP)
|
||||
can_parsers = car_state.get_can_parsers(CP)
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
|
||||
def update(swl_stat: int, frame: int):
|
||||
msgs = [
|
||||
packer.make_can_msg("CLU13", 0, {
|
||||
"CF_Clu_LdwsLkasSW": 0,
|
||||
"CF_Clu_SWL_Stat": swl_stat,
|
||||
}),
|
||||
packer.make_can_msg("BCM_PO_11", 0, {
|
||||
"LDA_BTN": 0,
|
||||
}),
|
||||
]
|
||||
can_parsers[Bus.pt].update([(frame, msgs)])
|
||||
return car_state.update(can_parsers, toggles)[0]
|
||||
|
||||
update(0, 1)
|
||||
ret = update(4, 2)
|
||||
assert any(be.type == ButtonType.lkas and be.pressed for be in ret.buttonEvents)
|
||||
|
||||
ret = update(0, 3)
|
||||
assert any(be.type == ButtonType.lkas and not be.pressed for be in ret.buttonEvents)
|
||||
|
||||
def test_sonata_hybrid_ignores_noisy_alt_bus_clu13_lkas_button(self):
|
||||
toggles = get_test_toggles()
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
@@ -1327,7 +1390,8 @@ class TestHyundaiFingerprint:
|
||||
}
|
||||
cc = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
|
||||
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg=stock_lkas)
|
||||
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg=stock_lkas,
|
||||
out=SimpleNamespace(steeringAngleDeg=0.0))
|
||||
|
||||
msgs = controller.create_canfd_msgs(0, False, 0.0, 8.5, 0.0, 0.0, False, cc.hudControl, cs, cc,
|
||||
get_test_toggles(), lka_icon=2, lfa_icon=2)
|
||||
@@ -1341,6 +1405,56 @@ class TestHyundaiFingerprint:
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0)
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(8.5)
|
||||
|
||||
def test_ev9_high_steering_angle_inhibits_angle_control_and_lfa_suppress(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.KIA_EV9
|
||||
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_ANGLE_STEERING |
|
||||
HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
|
||||
CP.openpilotLongitudinalControl = False
|
||||
|
||||
controller = CarController(DBC[CP.carFingerprint], CP)
|
||||
controller.frame = 5
|
||||
can_bus = CanBus(CP)
|
||||
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
|
||||
stock_lkas = {
|
||||
"CHECKSUM": 1234,
|
||||
"COUNTER": 42,
|
||||
"LKA_MODE": 2,
|
||||
"LKA_AVAILABLE": 3,
|
||||
"LKA_WARNING": 1,
|
||||
"LKA_ICON": 1,
|
||||
"FCA_SYSWARN": 1,
|
||||
"TORQUE_REQUEST": 17,
|
||||
"STEER_REQ": 1,
|
||||
"LFA_BUTTON": 1,
|
||||
"LKA_ASSIST": 1,
|
||||
"STEER_MODE": 5,
|
||||
"NEW_SIGNAL_2": 0,
|
||||
"LKAS_ANGLE_ACTIVE": 1,
|
||||
"HAS_LANE_SAFETY": 1,
|
||||
"ADAS_StrAnglReqVal": 12.3,
|
||||
"ADAS_ACIAnglTqRedcGainVal": 0.42,
|
||||
"DAMP_FACTOR": 0,
|
||||
}
|
||||
cc = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(longControlState=LongCtrlState.off),
|
||||
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
|
||||
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg=stock_lkas, lfa_block_msg={},
|
||||
out=SimpleNamespace(steeringAngleDeg=120.0))
|
||||
|
||||
msgs = controller.create_canfd_msgs(0, True, 0.44, 120.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
|
||||
get_test_toggles(), lka_icon=2, lfa_icon=2)
|
||||
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
|
||||
suppress_msgs = [msg for msg in msgs if msg[0] == 0x362]
|
||||
assert len(lkas_msgs) == 1
|
||||
assert len(suppress_msgs) == 0
|
||||
|
||||
parser.update([(1, lkas_msgs)])
|
||||
|
||||
assert parser.can_valid
|
||||
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0)
|
||||
assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(120.0)
|
||||
|
||||
def test_can_acc_commands_use_default_values(self):
|
||||
CP = CarParams.new_message()
|
||||
CP.carFingerprint = CAR.GENESIS_G90
|
||||
|
||||
@@ -27,12 +27,13 @@ env_var_truthy() {
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) [-nav] [--prefix name] <route-or-replay-args...>
|
||||
./onroad [jobs] (--c3 | --c4 | --raybig | --all | --replay-only) [-nav] [--mici-widget-demo] [--prefix name] <route-or-replay-args...>
|
||||
|
||||
Examples:
|
||||
./onroad --c3 <route>
|
||||
./onroad --c4 <route> --start 30
|
||||
./onroad --c4 -nav <route>
|
||||
./onroad --c4 --mici-widget-demo --demo
|
||||
./onroad --all <route>
|
||||
./onroad --replay-only --demo --no-vipc --no-loop
|
||||
|
||||
@@ -41,6 +42,7 @@ Notes:
|
||||
- A private comma connect route still requires tools/lib/auth.py before replay can download it.
|
||||
- Use multiple UI flags together if you want more than one desktop UI at once.
|
||||
- -nav injects a fake navigation demo stream and blocks replay from publishing navInstruction/navRoute.
|
||||
- --mici-widget-demo makes the small C4 sidebar cycle through widget states for visual review.
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -56,6 +58,7 @@ UI_TARGETS=()
|
||||
LEGACY_UI_SELECTION=""
|
||||
REPLAY_ONLY=0
|
||||
NAV_DEMO=0
|
||||
MICI_WIDGET_DEMO=0
|
||||
REPLAY_PID=""
|
||||
NAV_PID=""
|
||||
UI_PIDS=()
|
||||
@@ -91,6 +94,10 @@ parse_args() {
|
||||
NAV_DEMO=1
|
||||
shift
|
||||
;;
|
||||
--mici-widget-demo|--widget-demo)
|
||||
MICI_WIDGET_DEMO=1
|
||||
shift
|
||||
;;
|
||||
--ui)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Missing value for --ui" >&2
|
||||
@@ -315,6 +322,7 @@ prepare_env() {
|
||||
export SP_RAYBIG_FAKE_WIFI=0
|
||||
export SP_ALLOW_DESKTOP_FAKE_WIFI=0
|
||||
export SP_ONROAD_NAV_DEMO="${NAV_DEMO}"
|
||||
export SP_MICI_WIDGET_DEMO="${MICI_WIDGET_DEMO}"
|
||||
|
||||
if [[ "$(uname -s)" == "Darwin" ]] || env_var_truthy "${ZMQ:-0}"; then
|
||||
export OPENPILOT_ZMQ_NAMESPACE="${PREFIX_ARG:-${OPENPILOT_ZMQ_NAMESPACE:-desktop-onroad-$$}}"
|
||||
|
||||
@@ -464,6 +464,10 @@ class StarPilotAppearanceLayout(_SettingsPage):
|
||||
subtitle="",
|
||||
get_state=lambda: self._params.get_bool("DriverCamera"),
|
||||
set_state=lambda s: self._params.put_bool("DriverCamera", s)),
|
||||
SettingRow("StockConfidenceBallWidget", "toggle", tr_noop("Stock Confidence Ball"),
|
||||
subtitle=tr_noop("Use the original moving confidence ball on the small comma 4 UI."),
|
||||
get_state=lambda: self._params.get_bool("StockConfidenceBallWidget"),
|
||||
set_state=lambda s: self._params.put_bool("StockConfidenceBallWidget", s)),
|
||||
SettingRow("BootLogo", "value", tr_noop("Boot Logo"),
|
||||
subtitle="",
|
||||
get_value=lambda: self._get_theme_value("BootLogo"),
|
||||
|
||||
@@ -60,6 +60,7 @@ class VisualsLayoutMici(NavScroller):
|
||||
self._camera_view_btn = CameraViewBigButton()
|
||||
self._driver_camera_btn = BigParamControl("driver camera on reverse", "DriverCamera")
|
||||
self._stopped_timer_btn = BigParamControl("stopped timer", "StoppedTimer")
|
||||
self._stock_confidence_ball_btn = BigParamControl("stock confidence ball", "StockConfidenceBallWidget")
|
||||
self._rainbow_path_btn = BigParamControl("rainbow road", "RainbowPath")
|
||||
self._lead_indicator_btn = LeadIndicatorBigButton()
|
||||
self._speed_limit_signs_btn = BigParamControl("speed limit signs", "ShowSpeedLimits")
|
||||
@@ -71,6 +72,7 @@ class VisualsLayoutMici(NavScroller):
|
||||
self._camera_view_btn,
|
||||
self._driver_camera_btn,
|
||||
self._stopped_timer_btn,
|
||||
self._stock_confidence_ball_btn,
|
||||
self._rainbow_path_btn,
|
||||
self._lead_indicator_btn,
|
||||
self._speed_limit_signs_btn,
|
||||
|
||||
@@ -12,6 +12,7 @@ from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer
|
||||
from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
|
||||
from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer
|
||||
from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall
|
||||
from openpilot.selfdrive.ui.mici.onroad.sidebar_widgets import MiciSidebarWidgets
|
||||
from openpilot.selfdrive.ui.mici.onroad.starpilot_status import (
|
||||
ENGAGED_COLOR,
|
||||
EXPERIMENTAL_COLOR,
|
||||
@@ -483,6 +484,7 @@ class AugmentedRoadView(CameraView):
|
||||
self._alert_renderer = AlertRenderer()
|
||||
self._driver_state_renderer = DriverStateRenderer()
|
||||
self._confidence_ball = ConfidenceBall()
|
||||
self._sidebar_widgets = MiciSidebarWidgets(self._confidence_ball)
|
||||
self._min_steer_speed_banner = MinSteerSpeedBanner()
|
||||
self._standstill_timer = StandstillTimerOverlay()
|
||||
self._favorite_slots = self._child(FavoriteSlotsOverlay())
|
||||
@@ -623,7 +625,10 @@ class AugmentedRoadView(CameraView):
|
||||
# Custom UI extension point - add custom overlays here
|
||||
# Use self._content_rect for positioning within camera bounds
|
||||
if draw_road_overlays:
|
||||
self._confidence_ball.render(self.rect)
|
||||
if ui_state.params.get_bool("StockConfidenceBallWidget") and not self._sidebar_widgets.demo_active:
|
||||
self._confidence_ball.render(self.rect)
|
||||
else:
|
||||
self._sidebar_widgets.render(self.rect)
|
||||
if draw_hud_controls and (camera_view_none or is_driver_stream or not in_reverse):
|
||||
self._favorite_slots.render(self._content_rect)
|
||||
if camera_view_none or is_driver_stream or not in_reverse:
|
||||
|
||||
@@ -43,19 +43,7 @@ class ConfidenceBall(Widget):
|
||||
self._confidence_filter.update((1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs or [1])) *
|
||||
(1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.steerOverrideProbs or [1])))
|
||||
|
||||
def _render(self, _):
|
||||
content_rect = rl.Rectangle(
|
||||
self.rect.x + self.rect.width - SIDE_PANEL_WIDTH,
|
||||
self.rect.y,
|
||||
SIDE_PANEL_WIDTH,
|
||||
self.rect.height,
|
||||
)
|
||||
|
||||
status_dot_radius = 24
|
||||
dot_height = (1 - self._confidence_filter.x) * (content_rect.height - 2 * status_dot_radius) + status_dot_radius
|
||||
dot_height = self._rect.y + dot_height
|
||||
|
||||
# confidence zones
|
||||
def _dot_colors(self) -> tuple[rl.Color, rl.Color]:
|
||||
if ui_state.status == UIStatus.ENGAGED or ui_state.always_on_lateral_active or self._demo:
|
||||
if self._confidence_filter.x > 0.5:
|
||||
top_dot_color = rl.Color(0, 255, 204, 255)
|
||||
@@ -75,6 +63,31 @@ class ConfidenceBall(Widget):
|
||||
top_dot_color = rl.Color(50, 50, 50, 255)
|
||||
bottom_dot_color = rl.Color(13, 13, 13, 255)
|
||||
|
||||
return top_dot_color, bottom_dot_color
|
||||
|
||||
def render_static(self, rect: rl.Rectangle, radius: int = 20) -> None:
|
||||
self._update_state()
|
||||
top_dot_color, bottom_dot_color = self._dot_colors()
|
||||
draw_circle_gradient(rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2,
|
||||
radius,
|
||||
top_dot_color,
|
||||
bottom_dot_color)
|
||||
|
||||
def _render(self, _):
|
||||
content_rect = rl.Rectangle(
|
||||
self.rect.x + self.rect.width - SIDE_PANEL_WIDTH,
|
||||
self.rect.y,
|
||||
SIDE_PANEL_WIDTH,
|
||||
self.rect.height,
|
||||
)
|
||||
|
||||
status_dot_radius = 24
|
||||
dot_height = (1 - self._confidence_filter.x) * (content_rect.height - 2 * status_dot_radius) + status_dot_radius
|
||||
dot_height = self._rect.y + dot_height
|
||||
|
||||
top_dot_color, bottom_dot_color = self._dot_colors()
|
||||
|
||||
draw_circle_gradient(content_rect.x + content_rect.width - status_dot_radius,
|
||||
dot_height, status_dot_radius,
|
||||
top_dot_color, bottom_dot_color)
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
import os
|
||||
import pyray as rl
|
||||
from cereal import log
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH
|
||||
from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.starpilot.common.experimental_state import CCStatus, CEStatus
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
|
||||
DEMO_HOLD_SECONDS = 1.5
|
||||
DEMO_REASONS = ("chill", "lead", "stop", "curve", "speed")
|
||||
DEMO_PERSONALITIES = (
|
||||
(False, 0), # aggressive
|
||||
(False, 1), # standard
|
||||
(False, 2), # relaxed
|
||||
(True, 0), # traffic
|
||||
)
|
||||
|
||||
WHITE = rl.Color(255, 255, 255, 255)
|
||||
WHITE_DIM = rl.Color(255, 255, 255, 170)
|
||||
BLACK = rl.Color(0, 0, 0, 255)
|
||||
PERSONALITY_BLUE = rl.Color(112, 192, 216, 255)
|
||||
CEM_BLUE = PERSONALITY_BLUE
|
||||
TRAFFIC_RED = rl.Color(200, 32, 48, 255)
|
||||
|
||||
|
||||
def _with_alpha(color: rl.Color, alpha: int) -> rl.Color:
|
||||
return rl.Color(color.r, color.g, color.b, max(0, min(255, alpha)))
|
||||
|
||||
|
||||
def _env_truthy(name: str) -> bool:
|
||||
return os.getenv(name, "").lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _v(x: float, y: float) -> rl.Vector2:
|
||||
return rl.Vector2(float(x), float(y))
|
||||
|
||||
|
||||
def _draw_line(x1: float, y1: float, x2: float, y2: float, thickness: float, color: rl.Color) -> None:
|
||||
rl.draw_line_ex(_v(x1, y1), _v(x2, y2), thickness, color)
|
||||
|
||||
|
||||
def _draw_tri(p1: tuple[float, float], p2: tuple[float, float], p3: tuple[float, float], color: rl.Color) -> None:
|
||||
rl.draw_triangle(_v(*p1), _v(*p2), _v(*p3), color)
|
||||
|
||||
|
||||
def _draw_quad(points: tuple[tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float]], color: rl.Color) -> None:
|
||||
p1, p2, p3, p4 = points
|
||||
rl.draw_triangle(_v(*p1), _v(*p2), _v(*p3), color)
|
||||
rl.draw_triangle(_v(*p1), _v(*p3), _v(*p4), color)
|
||||
|
||||
|
||||
def _draw_quad_outline(points: tuple[tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float]], thickness: float, color: rl.Color) -> None:
|
||||
for idx, start in enumerate(points):
|
||||
end = points[(idx + 1) % len(points)]
|
||||
_draw_line(start[0], start[1], end[0], end[1], thickness, color)
|
||||
|
||||
|
||||
def _draw_bezier(p0: tuple[float, float], p1: tuple[float, float], p2: tuple[float, float], p3: tuple[float, float], thickness: float, color: rl.Color) -> None:
|
||||
rl.draw_spline_segment_bezier_cubic(_v(*p0), _v(*p1), _v(*p2), _v(*p3), thickness, color)
|
||||
|
||||
|
||||
def _bezier_point(p0: tuple[float, float], p1: tuple[float, float], p2: tuple[float, float], p3: tuple[float, float], t: float) -> tuple[float, float]:
|
||||
inv_t = 1.0 - t
|
||||
x = inv_t ** 3 * p0[0] + 3 * inv_t ** 2 * t * p1[0] + 3 * inv_t * t ** 2 * p2[0] + t ** 3 * p3[0]
|
||||
y = inv_t ** 3 * p0[1] + 3 * inv_t ** 2 * t * p1[1] + 3 * inv_t * t ** 2 * p2[1] + t ** 3 * p3[1]
|
||||
return x, y
|
||||
|
||||
|
||||
def _draw_dashed_bezier(p0: tuple[float, float], p1: tuple[float, float], p2: tuple[float, float], p3: tuple[float, float], thickness: float, color: rl.Color) -> None:
|
||||
for start_t, end_t in ((0.18, 0.28), (0.39, 0.50), (0.61, 0.72)):
|
||||
start = _bezier_point(p0, p1, p2, p3, start_t)
|
||||
end = _bezier_point(p0, p1, p2, p3, end_t)
|
||||
_draw_line(start[0], start[1], end[0], end[1], thickness, color)
|
||||
|
||||
|
||||
class MiciSidebarWidgets(Widget):
|
||||
def __init__(self, confidence_ball: ConfidenceBall):
|
||||
super().__init__()
|
||||
self._confidence_ball = confidence_ball
|
||||
self._font_bold = gui_app.font(FontWeight.BOLD)
|
||||
self._font_semi_bold = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
self._demo = _env_truthy("SP_MICI_WIDGET_DEMO")
|
||||
|
||||
@property
|
||||
def demo_active(self) -> bool:
|
||||
return self._demo
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
sidebar = rl.Rectangle(
|
||||
rect.x + rect.width - SIDE_PANEL_WIDTH,
|
||||
rect.y,
|
||||
SIDE_PANEL_WIDTH,
|
||||
rect.height,
|
||||
)
|
||||
rl.draw_rectangle(int(sidebar.x), int(sidebar.y), int(sidebar.width), int(sidebar.height), BLACK)
|
||||
|
||||
slot_height = sidebar.height / 3
|
||||
confidence_slot = rl.Rectangle(sidebar.x, sidebar.y, sidebar.width, slot_height)
|
||||
cem_slot = rl.Rectangle(sidebar.x, sidebar.y + slot_height, sidebar.width, slot_height)
|
||||
personality_slot = rl.Rectangle(sidebar.x, sidebar.y + slot_height * 2, sidebar.width, sidebar.height - slot_height * 2)
|
||||
|
||||
self._confidence_ball.render_static(confidence_slot, max(16, min(20, int(slot_height * 0.28))))
|
||||
self._draw_cem_widget(cem_slot)
|
||||
self._draw_personality_widget(personality_slot)
|
||||
|
||||
def _draw_personality_widget(self, rect: rl.Rectangle) -> None:
|
||||
center_x = rect.x + rect.width / 2
|
||||
center_y = rect.y + rect.height / 2
|
||||
icon_height = min(rect.height - 6, 64)
|
||||
bottom_y = center_y + icon_height * 0.42
|
||||
lane_top_y = center_y - icon_height * 0.38
|
||||
|
||||
traffic_mode, personality = self._personality_state()
|
||||
|
||||
active_color = TRAFFIC_RED if traffic_mode else PERSONALITY_BLUE
|
||||
stack_count = 1 if traffic_mode else max(1, min(3, personality + 1))
|
||||
|
||||
_draw_line(center_x - 23, bottom_y, center_x - 14, lane_top_y, 3, WHITE)
|
||||
_draw_line(center_x + 23, bottom_y, center_x + 14, lane_top_y, 3, WHITE)
|
||||
|
||||
car_top_y = bottom_y - 4
|
||||
car_bottom_y = bottom_y + 8
|
||||
car_outline = (
|
||||
(center_x - 8, car_top_y),
|
||||
(center_x + 8, car_top_y),
|
||||
(center_x + 13, car_bottom_y),
|
||||
(center_x - 13, car_bottom_y),
|
||||
)
|
||||
_draw_quad_outline(car_outline, 3, WHITE)
|
||||
|
||||
bar_h = max(6, min(9, int(icon_height * 0.14)))
|
||||
gap = max(3, min(5, int(icon_height * 0.08)))
|
||||
y = bottom_y - bar_h - 4
|
||||
widths = ((36, 30), (28, 23), (20, 16))
|
||||
for idx in range(stack_count):
|
||||
bottom_w, top_w = widths[idx]
|
||||
top_y = y
|
||||
bottom = y + bar_h
|
||||
points = (
|
||||
(center_x - top_w / 2, top_y),
|
||||
(center_x + top_w / 2, top_y),
|
||||
(center_x + bottom_w / 2, bottom),
|
||||
(center_x - bottom_w / 2, bottom),
|
||||
)
|
||||
_draw_quad(points, _with_alpha(active_color, 60))
|
||||
_draw_quad_outline(points, 3, active_color)
|
||||
y -= bar_h + gap
|
||||
|
||||
def _personality_state(self) -> tuple[bool, int]:
|
||||
if self._demo:
|
||||
return DEMO_PERSONALITIES[int(rl.get_time() / DEMO_HOLD_SECONDS) % len(DEMO_PERSONALITIES)]
|
||||
|
||||
personality = 1
|
||||
try:
|
||||
if ui_state.sm.valid.get("selfdriveState", False):
|
||||
personality = PERSONALITY_TO_INT[ui_state.sm["selfdriveState"].personality]
|
||||
elif isinstance(ui_state.personality, int):
|
||||
personality = ui_state.personality
|
||||
else:
|
||||
personality = PERSONALITY_TO_INT[ui_state.personality]
|
||||
except Exception:
|
||||
personality = 1
|
||||
return ui_state.traffic_mode_enabled, personality
|
||||
|
||||
def _draw_cem_widget(self, rect: rl.Rectangle) -> None:
|
||||
reason, color = self._cem_reason()
|
||||
if reason == "chill":
|
||||
self._draw_chill_icon(rect)
|
||||
elif reason == "lead":
|
||||
self._draw_lead_icon(rect, color)
|
||||
elif reason == "stop":
|
||||
self._draw_stop_light_icon(rect)
|
||||
elif reason == "curve":
|
||||
self._draw_curve_icon(rect, color)
|
||||
elif reason == "turn":
|
||||
self._draw_turn_icon(rect, color)
|
||||
elif reason == "speed":
|
||||
self._draw_speed_icon(rect, color)
|
||||
else:
|
||||
self._draw_chill_icon(rect)
|
||||
|
||||
def _model_stop_active(self) -> bool:
|
||||
try:
|
||||
if ui_state.sm.recv_frame["starpilotPlan"] >= ui_state.started_frame:
|
||||
starpilot_plan = ui_state.sm["starpilotPlan"]
|
||||
return bool(starpilot_plan.redLight or starpilot_plan.forcingStop)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _cem_reason(self) -> tuple[str, rl.Color]:
|
||||
if self._demo:
|
||||
reason = DEMO_REASONS[int(rl.get_time() / DEMO_HOLD_SECONDS) % len(DEMO_REASONS)]
|
||||
if reason == "chill":
|
||||
return reason, WHITE
|
||||
if reason == "stop":
|
||||
return reason, TRAFFIC_RED
|
||||
return reason, CEM_BLUE
|
||||
|
||||
conditional_experimental = ui_state.params.get_bool("ConditionalExperimental")
|
||||
conditional_chill = ui_state.params.get_bool("ConditionalChill") and not conditional_experimental
|
||||
if conditional_chill:
|
||||
status = ui_state.params_memory.get_int("CCStatus", default=CCStatus["OFF"])
|
||||
if status == CCStatus["LEAD"]:
|
||||
return "lead", CEM_BLUE
|
||||
if status == CCStatus["SPEED"]:
|
||||
return "speed", CEM_BLUE
|
||||
if status == CCStatus["USER_EXPERIMENTAL"]:
|
||||
return "chill", WHITE
|
||||
if status == CCStatus["USER_CHILL"]:
|
||||
return "chill", WHITE
|
||||
return "chill", WHITE
|
||||
|
||||
status = ui_state.params_memory.get_int("CEStatus", default=CEStatus["OFF"]) if conditional_experimental else CEStatus["OFF"]
|
||||
if status == CEStatus["CURVATURE"]:
|
||||
return "curve", CEM_BLUE
|
||||
if status == CEStatus["LEAD"]:
|
||||
return "lead", CEM_BLUE
|
||||
if status == CEStatus["SIGNAL"]:
|
||||
return "turn", CEM_BLUE
|
||||
if status in (CEStatus["SPEED"], CEStatus["SPEED_LIMIT"]):
|
||||
return "speed", CEM_BLUE
|
||||
if status == CEStatus["STOP_LIGHT"]:
|
||||
return "stop", TRAFFIC_RED
|
||||
if status == CEStatus["USER_DISABLED"]:
|
||||
return "chill", WHITE
|
||||
if status == CEStatus["USER_OVERRIDDEN"]:
|
||||
return "chill", WHITE
|
||||
if self._model_stop_active():
|
||||
return "stop", TRAFFIC_RED
|
||||
return "chill", WHITE
|
||||
|
||||
def _draw_chill_icon(self, rect: rl.Rectangle) -> None:
|
||||
cx = rect.x + rect.width / 2
|
||||
cy = rect.y + rect.height / 2
|
||||
stroke = 3
|
||||
|
||||
_draw_line(cx - 14, cy - 15, cx + 14, cy - 15, stroke, WHITE)
|
||||
_draw_line(cx - 16, cy - 13, cx - 16, cy + 1, stroke, WHITE)
|
||||
_draw_line(cx + 16, cy - 13, cx + 16, cy + 1, stroke, WHITE)
|
||||
_draw_line(cx - 19, cy + 3, cx + 19, cy + 3, stroke, WHITE)
|
||||
_draw_line(cx - 21, cy - 3, cx - 21, cy + 10, stroke, WHITE)
|
||||
_draw_line(cx + 21, cy - 3, cx + 21, cy + 10, stroke, WHITE)
|
||||
_draw_line(cx - 21, cy + 10, cx + 21, cy + 10, stroke, WHITE)
|
||||
_draw_line(cx - 14, cy + 6, cx + 14, cy + 6, 2, CEM_BLUE)
|
||||
_draw_line(cx - 14, cy + 12, cx - 17, cy + 19, stroke, WHITE)
|
||||
_draw_line(cx + 14, cy + 12, cx + 17, cy + 19, stroke, WHITE)
|
||||
|
||||
def _draw_lead_icon(self, rect: rl.Rectangle, color: rl.Color) -> None:
|
||||
cx = rect.x + rect.width / 2
|
||||
cy = rect.y + rect.height / 2
|
||||
self._draw_car(cx, cy - 13, 31, 18, WHITE, TRAFFIC_RED)
|
||||
self._draw_car(cx, cy + 12, 43, 22, WHITE, TRAFFIC_RED)
|
||||
_draw_line(cx - 11, cy - 1, cx - 11, cy + 3, 2, _with_alpha(WHITE, 130))
|
||||
_draw_line(cx + 11, cy - 1, cx + 11, cy + 3, 2, _with_alpha(WHITE, 130))
|
||||
|
||||
def _draw_car(self, cx: float, cy: float, width: float, height: float, color: rl.Color, accent: rl.Color) -> None:
|
||||
top = cy - height / 2
|
||||
bottom = cy + height / 2
|
||||
body = (
|
||||
(cx - width * 0.34, top + height * 0.10),
|
||||
(cx + width * 0.34, top + height * 0.10),
|
||||
(cx + width * 0.50, bottom - height * 0.04),
|
||||
(cx - width * 0.50, bottom - height * 0.04),
|
||||
)
|
||||
windshield = (
|
||||
(cx - width * 0.22, top + height * 0.24),
|
||||
(cx + width * 0.22, top + height * 0.24),
|
||||
(cx + width * 0.32, top + height * 0.50),
|
||||
(cx - width * 0.32, top + height * 0.50),
|
||||
)
|
||||
_draw_quad_outline(body, 3, color)
|
||||
_draw_quad_outline(windshield, 2, color)
|
||||
_draw_line(cx - width * 0.56, top + height * 0.46, cx - width * 0.50, top + height * 0.46, 2, color)
|
||||
_draw_line(cx + width * 0.50, top + height * 0.46, cx + width * 0.56, top + height * 0.46, 2, color)
|
||||
_draw_line(cx - width * 0.32, bottom - height * 0.22, cx - width * 0.16, bottom - height * 0.22, 2, accent)
|
||||
_draw_line(cx + width * 0.16, bottom - height * 0.22, cx + width * 0.32, bottom - height * 0.22, 2, accent)
|
||||
|
||||
def _draw_stop_light_icon(self, rect: rl.Rectangle) -> None:
|
||||
cx = rect.x + rect.width / 2
|
||||
cy = rect.y + rect.height / 2
|
||||
housing = rl.Rectangle(cx - 12, cy - 27, 24, 54)
|
||||
rl.draw_rectangle_rounded_lines_ex(housing, 0.35, 8, 3, WHITE)
|
||||
for bulb_y in (cy - 16, cy, cy + 16):
|
||||
rl.draw_circle_lines(int(cx), int(bulb_y), 6, WHITE)
|
||||
rl.draw_circle_lines(int(cx), int(cy - 16), 7, TRAFFIC_RED)
|
||||
rl.draw_circle(int(cx), int(cy - 16), 4, TRAFFIC_RED)
|
||||
|
||||
def _draw_curve_icon(self, rect: rl.Rectangle, color: rl.Color) -> None:
|
||||
cx = rect.x + rect.width / 2
|
||||
cy = rect.y + rect.height / 2
|
||||
def road_curve_points(x_offset: float) -> tuple[tuple[float, float], tuple[float, float], tuple[float, float], tuple[float, float]]:
|
||||
return (
|
||||
(cx + x_offset, cy + 24),
|
||||
(cx + x_offset - 1, cy + 9),
|
||||
(cx + x_offset + 1, cy - 9),
|
||||
(cx + x_offset + 6, cy - 24),
|
||||
)
|
||||
|
||||
for points in (road_curve_points(-18), road_curve_points(14)):
|
||||
_draw_bezier(*points, 3, WHITE)
|
||||
rl.draw_circle(int(points[0][0]), int(points[0][1]), 2, WHITE)
|
||||
rl.draw_circle(int(points[3][0]), int(points[3][1]), 2, WHITE)
|
||||
_draw_dashed_bezier(*road_curve_points(-2), 4, color)
|
||||
|
||||
def _draw_turn_icon(self, rect: rl.Rectangle, color: rl.Color) -> None:
|
||||
cx = rect.x + rect.width / 2
|
||||
cy = rect.y + rect.height / 2
|
||||
left = False
|
||||
right = True
|
||||
try:
|
||||
left = ui_state.sm["carState"].leftBlinker
|
||||
right = ui_state.sm["carState"].rightBlinker
|
||||
except Exception:
|
||||
pass
|
||||
direction = -1 if left or not right else 1
|
||||
shaft = (
|
||||
(cx - direction * 10, cy + 25),
|
||||
(cx - direction * 13, cy + 6),
|
||||
(cx - direction * 4, cy - 8),
|
||||
(cx + direction * 18, cy - 20),
|
||||
)
|
||||
_draw_bezier(*shaft, 4, color)
|
||||
tip = (cx + direction * 24, cy - 26)
|
||||
_draw_tri(
|
||||
tip,
|
||||
(cx + direction * 4, cy - 24),
|
||||
(cx + direction * 19, cy - 7),
|
||||
color,
|
||||
)
|
||||
|
||||
def _draw_speed_icon(self, rect: rl.Rectangle, color: rl.Color) -> None:
|
||||
cx = rect.x + rect.width / 2
|
||||
cy = rect.y + rect.height / 2
|
||||
radius = min(rect.width, rect.height) * 0.34
|
||||
text = self._speed_text()
|
||||
|
||||
rl.draw_circle(int(cx), int(cy), radius, WHITE)
|
||||
rl.draw_ring(_v(cx, cy), radius - 5, radius, 0, 360, 48, color)
|
||||
|
||||
font_size = 19 if len(text) <= 2 else 16
|
||||
text_size = measure_text_cached(self._font_bold, text, font_size)
|
||||
rl.draw_text_ex(self._font_bold, text, rl.Vector2(cx - text_size.x / 2, cy - text_size.y / 2 + 1), font_size, 0, BLACK)
|
||||
|
||||
def _speed_text(self) -> str:
|
||||
if ui_state.sm.recv_frame["starpilotPlan"] < ui_state.started_frame:
|
||||
return "S"
|
||||
|
||||
speed_limit = ui_state.sm["starpilotPlan"].slcSpeedLimit
|
||||
if speed_limit <= 0:
|
||||
return "S"
|
||||
|
||||
conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
||||
speed_text = str(round(speed_limit * conversion))
|
||||
return speed_text[:3]
|
||||
@@ -1223,6 +1223,7 @@ class StarPilotVariables:
|
||||
toggle.driver_camera_in_reverse = self.get_value("DriverCamera", condition=quality_of_life_visuals)
|
||||
toggle.onroad_distance_button = toggle.openpilot_longitudinal and (self.get_value("OnroadDistanceButton", condition=quality_of_life_visuals) or toggle.debug_mode)
|
||||
toggle.stopped_timer = self.get_value("StoppedTimer", condition=quality_of_life_visuals)
|
||||
toggle.stock_confidence_ball_widget = self.get_value("StockConfidenceBallWidget", condition=quality_of_life_visuals)
|
||||
|
||||
toggle.rainbow_path = self.get_value("RainbowPath", condition=not toggle.debug_mode)
|
||||
|
||||
|
||||
@@ -114,18 +114,20 @@ class StarPilotCard:
|
||||
def update(self, carState, starpilotCarState, sm, starpilot_toggles):
|
||||
self.switchback_mode_enabled = self.params_memory.get_bool("SwitchbackModeEnabled")
|
||||
button_event_types = [self._button_type_raw(be) for be in carState.buttonEvents]
|
||||
sonata_hybrid_cruise_ready = self.hyundai_lkas_aol_requires_engagement and carState.cruiseState.available
|
||||
hyundai_lkas_aol_can_toggle = (
|
||||
not self.hyundai_lkas_aol_requires_engagement or
|
||||
self.hyundai_aol_ready or
|
||||
sm["selfdriveState"].active or
|
||||
carState.cruiseState.enabled
|
||||
carState.cruiseState.enabled or
|
||||
sonata_hybrid_cruise_ready
|
||||
)
|
||||
|
||||
if self.hyundai_aol_needs_engagement:
|
||||
if carState.gearShifter in NON_DRIVING_GEARS:
|
||||
self.hyundai_aol_ready = False
|
||||
self.always_on_lateral_allowed = False
|
||||
elif sm["selfdriveState"].active or carState.cruiseState.enabled:
|
||||
elif sm["selfdriveState"].active or carState.cruiseState.enabled or sonata_hybrid_cruise_ready:
|
||||
self.hyundai_aol_ready = True
|
||||
|
||||
if self.CP.brand == "hyundai" or starpilot_toggles.lkas_allowed_for_aol:
|
||||
|
||||
@@ -181,8 +181,8 @@ def test_sonata_hybrid_lkas_button_can_toggle_aol_after_engagement(monkeypatch,
|
||||
sm = make_sm()
|
||||
toggles = make_toggles(always_on_lateral=True, always_on_lateral_lkas=True)
|
||||
|
||||
engaged_state = make_car_state(available=True, enabled=True)
|
||||
card.update(engaged_state, starpilot_car_state, sm, toggles)
|
||||
cruise_main_state = make_car_state(available=True, enabled=False)
|
||||
card.update(cruise_main_state, starpilot_car_state, sm, toggles)
|
||||
|
||||
lkas_state = make_car_state(available=False, enabled=False, button_events=[SimpleNamespace(type=spc.ButtonType.lkas, pressed=True)])
|
||||
ret = card.update(lkas_state, starpilot_car_state, sm, toggles)
|
||||
|
||||
@@ -2105,6 +2105,14 @@
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "QOLVisuals"
|
||||
},
|
||||
{
|
||||
"key": "StockConfidenceBallWidget",
|
||||
"label": "Stock Confidence Ball Widget",
|
||||
"description": "Use the original moving confidence ball on the small comma 4 UI instead of the fixed confidence, CEM/CCM, and personality sidebar.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"parent_key": "QOLVisuals"
|
||||
},
|
||||
{
|
||||
"key": "SimpleMode",
|
||||
"label": "Simple Mode",
|
||||
|
||||
@@ -71,6 +71,7 @@ StarPilotVisualsPanel::StarPilotVisualsPanel(StarPilotSettingsWindow *parent, bo
|
||||
{"CameraView", tr("Camera View"), tr("<b>Select the active camera view.</b> This is purely a visual change and doesn't impact how openpilot drives!"), ""},
|
||||
{"DriverCamera", tr("Show Driver Camera When In Reverse"), tr("<b>Show the driver camera feed</b> when the vehicle is in reverse."), ""},
|
||||
{"StoppedTimer", tr("Stopped Timer"), tr("<b>Show a timer when stopped</b> in place of the current speed to indicate how long the vehicle has been stopped."), ""},
|
||||
{"StockConfidenceBallWidget", tr("Stock Confidence Ball Widget"), tr("<b>Use the original moving confidence ball</b> on the small comma 4 UI instead of the fixed confidence, CEM/CCM, and personality sidebar."), ""},
|
||||
|
||||
{"DisableWideRoad", tr("Disable Wide Road Camera"), QString("<b>%1</b><br><br>%2").arg(tr("Only enable this if the wide camera is broken or for development!")).arg(tr("<b>Disabling the wide camera may degrade driving performance and cause instability.</b><br><br>Requires a reboot to take effect.")), "../../starpilot/assets/toggle_icons/icon_advanced_device.png"}
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ private:
|
||||
QSet<QString> customOnroadUIKeys = {"AccelerationPath", "AdjacentPath", "BlindSpotPath", "Compass", "OnroadDistanceButton", "PedalsOnUI", "RotatingWheel"};
|
||||
QSet<QString> modelUIKeys = {"DynamicPathWidth", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth"};
|
||||
QSet<QString> navigationUIKeys = {"RoadNameUI", "ShowSpeedLimits", "SLCMapboxFiller", "UseVienna"};
|
||||
QSet<QString> qualityOfLifeKeys = {"CameraView", "DriverCamera", "StoppedTimer"};
|
||||
QSet<QString> qualityOfLifeKeys = {"CameraView", "DriverCamera", "StoppedTimer", "StockConfidenceBallWidget"};
|
||||
|
||||
QSet<QString> parentKeys;
|
||||
|
||||
|
||||
@@ -339,6 +339,7 @@ SteerLatAccel
|
||||
SteerLatAccelStock
|
||||
SteerRatio
|
||||
SteerRatioStock
|
||||
StockConfidenceBallWidget
|
||||
StopAccel
|
||||
StopAccelStock
|
||||
StopDistance
|
||||
|
||||
@@ -92,7 +92,7 @@ void LogReader::migrateOldEvents() {
|
||||
|
||||
new_state.setActive(old_state.getActiveDEPRECATED());
|
||||
new_state.setAlertSize(old_state.getAlertSizeDEPRECATED());
|
||||
new_state.setAlertSound(old_state.getAlertSound2DEPRECATED());
|
||||
new_state.setAlertSound(static_cast<cereal::SelfdriveState::AudibleAlert>(old_state.getAlertSound2DEPRECATED()));
|
||||
new_state.setAlertStatus(old_state.getAlertStatusDEPRECATED());
|
||||
new_state.setAlertText1(old_state.getAlertText1DEPRECATED());
|
||||
new_state.setAlertText2(old_state.getAlertText2DEPRECATED());
|
||||
|
||||
Reference in New Issue
Block a user