mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-22 09:42:10 +08:00
Kirkland Rotisserie
This commit is contained in:
@@ -367,7 +367,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
|
||||
{"KonikMinutes", {PERSISTENT, INT, "0", "0", 0}},
|
||||
{"LaneChanges", {PERSISTENT, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
|
||||
{"LaneChangeSmoothing", {PERSISTENT, INT, "10", "10", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneChangeSmoothing", {PERSISTENT, INT, "5", "10", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneChangeTime", {PERSISTENT, FLOAT, "1.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneDetectionWidth", {PERSISTENT, FLOAT, "0.0", "0.0", 1, SETTINGS_SIMPLE}},
|
||||
{"LaneLinesColor", {PERSISTENT, STRING, "", "", 2, SETTINGS_SIMPLE}},
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Contribute to StarPilot
|
||||
|
||||
Contributions are welcome. StarPilot is driver-assistance software, so changes should be focused, testable, and safe for vehicles outside the change's intended scope.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Open all pull requests against the **`Dom` branch**. `Dom` is StarPilot's testing and integration branch; do not target the release branch directly.
|
||||
|
||||
A pull request should:
|
||||
|
||||
* have one clear purpose and contain only changes needed for that purpose;
|
||||
* explain what changed, why it changed, and how it was tested;
|
||||
* link the relevant issue or feedback item when one exists;
|
||||
* avoid unrelated cleanup, refactors, dependency changes, and formatting churn; and
|
||||
* pass the existing tests and checks.
|
||||
|
||||
Keep commits reviewable and update documentation when behavior or configuration changes. If a change needs a large refactor, separate that work from the behavior change when practical.
|
||||
|
||||
### Vehicle-specific changes
|
||||
|
||||
Do your best to ensure a vehicle-specific change affects only the intended vehicle, platform, or brand. Prefer the narrowest appropriate condition instead of changing shared behavior for every vehicle.
|
||||
|
||||
Tests should prove both sides of that boundary:
|
||||
|
||||
* the affected vehicle receives the new or corrected behavior; and
|
||||
* an unaffected vehicle, platform, brand, or configuration retains the existing behavior.
|
||||
|
||||
Include the vehicle and hardware used for on-road or bench testing in the pull request. Hardware testing is valuable, but it does not replace an automated regression test when the behavior can be tested in code.
|
||||
|
||||
## Development environment
|
||||
|
||||
StarPilot keeps host-native development tools separate from device-target builds. Run the setup and development commands from the repository root.
|
||||
|
||||
Install the Python dependencies before starting:
|
||||
|
||||
```bash
|
||||
tools/install_python_dependencies.sh
|
||||
```
|
||||
|
||||
On Ubuntu, `tools/ubuntu_setup.sh` installs both the system and Python dependencies. The development tools also require `uv`.
|
||||
|
||||
Use `./dev` for host-native tools. It creates and reuses an isolated environment under `.host_runtime/`, keeping host build artifacts out of the working tree:
|
||||
|
||||
```bash
|
||||
./dev replay
|
||||
./dev cabana
|
||||
./dev plotjuggler
|
||||
./dev shell
|
||||
```
|
||||
|
||||
For desktop UI work, use `./c3`, `./c4`, or `./raybig`. These commands use the same isolated host environment.
|
||||
|
||||
Use `./build` when you need comma-compatible device artifacts. This is the expected validation for changes that affect compiled device code or runtime behavior:
|
||||
|
||||
```bash
|
||||
./build
|
||||
```
|
||||
|
||||
Device builds require Docker Desktop or Podman with Linux/aarch64 support and a configured comma sysroot. See the [laptop device-build guide](../how-to/laptop-device-build.md) for setup instructions and the [complete StarPilot development workflow](https://github.com/firestar5683/StarPilot/blob/Dom/tools/STARPILOT_DEVELOPMENT.md) for all host commands and troubleshooting.
|
||||
|
||||
## Code formatting
|
||||
|
||||
Match the style of the code around your change and do not reformat unrelated files. Python formatting and lint rules are defined in `pyproject.toml`; the project uses two-space indentation and Ruff.
|
||||
|
||||
Run Ruff on changed Python files while developing:
|
||||
|
||||
```bash
|
||||
ruff check path/to/changed_file.py
|
||||
ruff format --check path/to/changed_file.py
|
||||
```
|
||||
|
||||
Before submitting, run the repository lint checks from the project root:
|
||||
|
||||
```bash
|
||||
./scripts/lint/lint.sh
|
||||
```
|
||||
|
||||
## Testing standards
|
||||
|
||||
Every behavior change or bug fix should include focused automated tests when practical. Recent StarPilot tests favor small regression cases that construct the relevant state, exercise one behavior, and assert the exact result.
|
||||
|
||||
At minimum:
|
||||
|
||||
1. Add or update a test that would fail without the change.
|
||||
2. Cover important boundaries, modes, and disabled states related to the change.
|
||||
3. For vehicle-specific behavior, add a negative case showing the change does not bleed into an unaffected vehicle or configuration.
|
||||
4. Run the directly affected test module and the existing tests for the affected subsystem.
|
||||
5. Ensure the pull request passes all existing CI checks before it is ready to merge.
|
||||
|
||||
Run a focused test module with pytest:
|
||||
|
||||
```bash
|
||||
pytest path/to/test_file.py
|
||||
```
|
||||
|
||||
Run the full test suite when your development environment supports it:
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
If a test requires special hardware or cannot run in your environment, say so in the pull request and document the closest validation you completed.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This flow builds **device-target (`larch64`) binaries on your laptop** using a Linux/aarch64 container and a synced comma sysroot.
|
||||
|
||||
For the full StarPilot branch workflow, including host-native shorthand tools such as `./dev`, `./c3`, `./c4`, and `./raybig`, see [tools/STARPILOT_DEVELOPMENT.md](../../tools/STARPILOT_DEVELOPMENT.md).
|
||||
For the full StarPilot branch workflow, including host-native shorthand tools such as `./dev`, `./c3`, `./c4`, and `./raybig`, see the [StarPilot development guide](https://github.com/firestar5683/StarPilot/blob/Dom/tools/STARPILOT_DEVELOPMENT.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
+2
-2
@@ -32,10 +32,10 @@ nav:
|
||||
- What is a car port?: car-porting/what-is-a-car-port.md
|
||||
- Porting a car brand: car-porting/brand-port.md
|
||||
- Porting a car model: car-porting/model-port.md
|
||||
- Contributing:
|
||||
- Contribute:
|
||||
- Contributing Guide: contributing/contribute.md
|
||||
- Roadmap: contributing/roadmap.md
|
||||
#- Architecture: contributing/architecture.md
|
||||
- Contributing Guide →: https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md
|
||||
- Links:
|
||||
- Blog →: https://blog.comma.ai
|
||||
- Bounties →: https://comma.ai/bounties
|
||||
|
||||
@@ -479,6 +479,9 @@ class CarController(CarControllerBase):
|
||||
self.last_steer_frame = 0
|
||||
self.last_button_frame = 0
|
||||
self.cancel_counter = 0
|
||||
self.xt4_cc_button_burst_remaining = 0
|
||||
self.xt4_cc_button_burst_button = CruiseButtons.INIT
|
||||
self.xt4_cc_button_burst_last_counter = -1
|
||||
|
||||
self.lka_steering_cmd_counter = 0
|
||||
self.lka_icon_status_last = (False, False)
|
||||
@@ -930,6 +933,16 @@ class CarController(CarControllerBase):
|
||||
can_sends.append(gmcan.create_ecm_cruise_control_command(
|
||||
self.packer_pt, CanBus.POWERTRAIN, True, hud_v_cruise * CV.MS_TO_KPH))
|
||||
|
||||
xt4_cc_button_spam = (
|
||||
self.CP.carFingerprint == CAR.CADILLAC_XT4_CC and
|
||||
should_send_cc_button_spam(self.CP, CC, CS)
|
||||
)
|
||||
if xt4_cc_button_spam:
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
elif self.CP.carFingerprint == CAR.CADILLAC_XT4_CC:
|
||||
self.xt4_cc_button_burst_remaining = 0
|
||||
self.xt4_cc_button_burst_button = CruiseButtons.INIT
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# Gas/regen, brakes, and UI commands - all at 25Hz
|
||||
if self.frame % 4 == 0:
|
||||
@@ -1087,17 +1100,19 @@ class CarController(CarControllerBase):
|
||||
|
||||
if self.CP.flags & GMFlags.CC_LONG.value:
|
||||
if should_send_cc_button_spam(self.CP, CC, CS):
|
||||
# Using extend instead of append since the message is only sent intermittently
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
elif (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
|
||||
if self.CP.carFingerprint != CAR.CADILLAC_XT4_CC:
|
||||
# Using extend instead of append since the message is only sent intermittently
|
||||
can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators, starpilot_toggles))
|
||||
else:
|
||||
if (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
|
||||
CS.cruise_buttons == CruiseButtons.UNPRESS and CS.out.gasPressed and CS.out.cruiseState.speed < CS.out.vEgo < hud_v_cruise):
|
||||
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
|
||||
can_sends.append(gmcan.create_buttons_malibu(
|
||||
self.packer_pt, CanBus.POWERTRAIN, CruiseButtons.DECEL_SET,
|
||||
self.malibu_button_phase, CS.steering_button_prefix))
|
||||
self.malibu_button_phase = (self.malibu_button_phase + 1) % 4
|
||||
else:
|
||||
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.DECEL_SET))
|
||||
if self.CP.carFingerprint == CAR.CHEVROLET_MALIBU_HYBRID_CC:
|
||||
can_sends.append(gmcan.create_buttons_malibu(
|
||||
self.packer_pt, CanBus.POWERTRAIN, CruiseButtons.DECEL_SET,
|
||||
self.malibu_button_phase, CS.steering_button_prefix))
|
||||
self.malibu_button_phase = (self.malibu_button_phase + 1) % 4
|
||||
else:
|
||||
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.DECEL_SET))
|
||||
if self.CP.enableGasInterceptorDEPRECATED:
|
||||
can_sends.append(create_gas_interceptor_command(self.packer_pt, interceptor_gas_cmd, idx))
|
||||
if bolt_acc_pedal_friction_experiment:
|
||||
|
||||
@@ -18,6 +18,7 @@ MALIBU_BUTTON_MAP = {
|
||||
}
|
||||
|
||||
ACC_CRUISE_STATE_ADAPTIVE = 2
|
||||
XT4_CC_BUTTON_BURST_FRAMES = 6
|
||||
|
||||
|
||||
def malibu_phase_map_for_button(button):
|
||||
@@ -318,6 +319,34 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
|
||||
cruise_btn = CruiseButtons.INIT
|
||||
controller.apply_speed = speed_setpoint
|
||||
|
||||
if CS.CP.carFingerprint == CAR.CADILLAC_XT4_CC:
|
||||
if cruise_btn == CruiseButtons.INIT:
|
||||
controller.xt4_cc_button_burst_remaining = 0
|
||||
controller.xt4_cc_button_burst_button = CruiseButtons.INIT
|
||||
return []
|
||||
|
||||
if (controller.xt4_cc_button_burst_remaining > 0 and
|
||||
controller.xt4_cc_button_burst_button != cruise_btn):
|
||||
controller.xt4_cc_button_burst_remaining = 0
|
||||
|
||||
if controller.xt4_cc_button_burst_remaining == 0:
|
||||
if (controller.frame - controller.last_button_frame) * DT_CTRL <= rate:
|
||||
return []
|
||||
controller.last_button_frame = controller.frame
|
||||
controller.xt4_cc_button_burst_button = cruise_btn
|
||||
controller.xt4_cc_button_burst_remaining = XT4_CC_BUTTON_BURST_FRAMES
|
||||
controller.xt4_cc_button_burst_last_counter = -1
|
||||
|
||||
# XT4 physical taps hold the button for 5-7 consecutive 33 Hz frames.
|
||||
# Send once per observed stock counter so the injected sequence has the same cadence.
|
||||
if controller.xt4_cc_button_burst_last_counter == CS.buttons_counter:
|
||||
return []
|
||||
|
||||
controller.xt4_cc_button_burst_last_counter = CS.buttons_counter
|
||||
controller.xt4_cc_button_burst_remaining -= 1
|
||||
idx = (CS.buttons_counter + 1) % 4
|
||||
return [create_buttons(packer, CanBus.POWERTRAIN, idx, controller.xt4_cc_button_burst_button)]
|
||||
|
||||
# Check rlogs closely - our message shouldn't show up on the pt bus for us
|
||||
# Or bus 2, since we're forwarding... but I think it does
|
||||
if (cruise_btn != CruiseButtons.INIT) and ((controller.frame - controller.last_button_frame) * DT_CTRL > rate):
|
||||
|
||||
@@ -554,6 +554,48 @@ class TestGMCarController:
|
||||
|
||||
assert [msg[2] for msg in msgs] == [0]
|
||||
|
||||
def test_xt4_cc_redneck_spam_matches_physical_button_burst(self):
|
||||
packer = CANPacker(DBC[CAR.CADILLAC_XT4_CC][Bus.pt])
|
||||
controller = SimpleNamespace(
|
||||
frame=int(0.3 / DT_CTRL),
|
||||
last_button_frame=0,
|
||||
apply_speed=0,
|
||||
malibu_button_phase=0,
|
||||
xt4_cc_button_burst_remaining=0,
|
||||
xt4_cc_button_burst_button=CruiseButtons.INIT,
|
||||
xt4_cc_button_burst_last_counter=-1,
|
||||
)
|
||||
cs = SimpleNamespace(
|
||||
CP=SimpleNamespace(
|
||||
carFingerprint=CAR.CADILLAC_XT4_CC,
|
||||
flags=GMFlags.CC_LONG.value,
|
||||
minEnableSpeed=24 * CV.MPH_TO_MS,
|
||||
),
|
||||
buttons_counter=0,
|
||||
out=SimpleNamespace(
|
||||
vEgo=25.0,
|
||||
cruiseState=SimpleNamespace(speed=20.0),
|
||||
),
|
||||
)
|
||||
actuators = SimpleNamespace(accel=1.0)
|
||||
|
||||
dats = []
|
||||
for counter in (0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 0, 0, 0, 1, 1, 1):
|
||||
cs.buttons_counter = counter
|
||||
msgs = gmcan.create_gm_cc_spam_command(packer, controller, cs, actuators, SimpleNamespace(is_metric=False))
|
||||
dats.extend(bytes(msg[1]).hex() for msg in msgs)
|
||||
controller.frame += 1
|
||||
|
||||
assert dats == [
|
||||
"000000010125de",
|
||||
"00000001022acd",
|
||||
"00000001032fbc",
|
||||
"000000010020ef",
|
||||
"000000010125de",
|
||||
"00000001022acd",
|
||||
]
|
||||
assert controller.xt4_cc_button_burst_remaining == 0
|
||||
|
||||
def test_acc_dashboard_command_preserves_raw_fcw_alert_level(self):
|
||||
packer = CANPacker(DBC[CAR.CHEVROLET_BOLT_ACC_2022_2023][Bus.pt])
|
||||
parser = CANParser(DBC[CAR.CHEVROLET_BOLT_ACC_2022_2023][Bus.pt], [("ASCMActiveCruiseControlStatus", 0)], 0)
|
||||
|
||||
@@ -98,6 +98,7 @@ class LatControlTorque(LatControl):
|
||||
self.is_kia_forte = CP.carFingerprint in KIA_FORTE_CARS
|
||||
self.is_kia_ev6 = CP.carFingerprint in KIA_EV6_CARS
|
||||
self.is_kia_carnival = CP.carFingerprint in KIA_CARNIVAL_CARS
|
||||
self.is_tucson_4th_gen = CP.carFingerprint in TUCSON_4TH_GEN_CARS
|
||||
self.is_civic_bosch_modified = CP.carFingerprint == HONDA_CAR.HONDA_CIVIC_BOSCH and bool(CP.flags & HondaFlags.EPS_MODIFIED)
|
||||
self.is_silverado = CP.carFingerprint in SILVERADO_CARS
|
||||
self.is_gm = CP.brand == "gm"
|
||||
@@ -251,6 +252,7 @@ class LatControlTorque(LatControl):
|
||||
kia_forte_active = self.is_kia_forte
|
||||
kia_ev6_test_active = self.is_kia_ev6 and kia_ev6_lateral_testing_ground_active()
|
||||
kia_carnival_active = self.is_kia_carnival
|
||||
tucson_4th_gen_active = self.is_tucson_4th_gen
|
||||
volt_plexy_test_active = self.is_volt_standard and volt_plexy_lateral_testing_ground_active()
|
||||
ioniq_5_center_taper = get_ioniq_5_center_taper_scale(setpoint, CS.vEgo) if ioniq_5_active else 1.0
|
||||
prius_center_taper = get_prius_center_taper_scale(setpoint, CS.vEgo) if prius_active else 1.0
|
||||
@@ -266,6 +268,7 @@ class LatControlTorque(LatControl):
|
||||
kia_ev6_center_taper = get_kia_ev6_center_taper_scale(setpoint, CS.vEgo) if kia_ev6_test_active else 1.0
|
||||
kia_ev6_low_speed_center_taper = get_kia_ev6_low_speed_center_taper_scale(setpoint, CS.vEgo) if kia_ev6_test_active else 1.0
|
||||
kia_carnival_center_taper = get_kia_carnival_center_taper_scale(setpoint, CS.vEgo) if kia_carnival_active else 1.0
|
||||
tucson_4th_gen_center_taper = get_tucson_4th_gen_center_taper_scale(setpoint, CS.vEgo) if tucson_4th_gen_active else 1.0
|
||||
silverado_center_taper = get_silverado_center_taper_scale(setpoint, CS.vEgo) if self.is_silverado else 1.0
|
||||
civic_bosch_modified_a_center_taper = get_civic_bosch_modified_a_center_taper_scale(setpoint, CS.vEgo) if (
|
||||
self.is_civic_bosch_modified and civic_bosch_modified_a_lateral_testing_ground_active()
|
||||
@@ -409,6 +412,8 @@ class LatControlTorque(LatControl):
|
||||
output_torque *= kia_ev6_low_speed_center_taper
|
||||
elif kia_carnival_active:
|
||||
output_torque *= kia_carnival_center_taper
|
||||
elif tucson_4th_gen_active:
|
||||
output_torque *= tucson_4th_gen_center_taper
|
||||
elif self.is_silverado:
|
||||
output_torque *= silverado_center_taper
|
||||
elif kia_niro_phev_2022_active:
|
||||
|
||||
@@ -114,6 +114,9 @@ KIA_EV6_CARS = (
|
||||
KIA_CARNIVAL_CARS = (
|
||||
HYUNDAI_CAR.KIA_CARNIVAL_2025,
|
||||
)
|
||||
TUCSON_4TH_GEN_CARS = (
|
||||
HYUNDAI_CAR.HYUNDAI_TUCSON_4TH_GEN,
|
||||
)
|
||||
KIA_XCEED_CARS = (
|
||||
HYUNDAI_CAR.KIA_XCEED_PHEV,
|
||||
)
|
||||
@@ -333,15 +336,21 @@ KIA_NIRO_PHEV_2022_FRICTION_CALM_JERK = 0.22
|
||||
KIA_NIRO_PHEV_2022_FRICTION_CALM_JERK_WIDTH = 0.06
|
||||
KIA_NIRO_PHEV_2022_FRICTION_THRESHOLD_GAIN = 0.12
|
||||
|
||||
KIA_CARNIVAL_CENTER_TAPER_MAX = 0.10
|
||||
KIA_CARNIVAL_CENTER_TAPER_LAT = 0.16
|
||||
KIA_CARNIVAL_CENTER_TAPER_LAT_WIDTH = 0.05
|
||||
KIA_CARNIVAL_CENTER_TAPER_MAX = 0.20
|
||||
KIA_CARNIVAL_CENTER_TAPER_LAT = 0.20
|
||||
KIA_CARNIVAL_CENTER_TAPER_LAT_WIDTH = 0.055
|
||||
KIA_CARNIVAL_CENTER_TAPER_SPEED = 3.5
|
||||
KIA_CARNIVAL_CENTER_TAPER_SPEED_WIDTH = 1.8
|
||||
KIA_CARNIVAL_CENTER_TAPER_SPEED_MAX = 16.0
|
||||
KIA_CARNIVAL_CENTER_TAPER_SPEED_MAX_WIDTH = 2.5
|
||||
KIA_CARNIVAL_FRICTION_THRESHOLD_GAIN = 0.18
|
||||
KIA_CARNIVAL_FRICTION_CENTER_FADE_MAX = 0.25
|
||||
KIA_CARNIVAL_CENTER_TAPER_SPEED_MAX = 14.5
|
||||
KIA_CARNIVAL_CENTER_TAPER_SPEED_MAX_WIDTH = 2.0
|
||||
KIA_CARNIVAL_FRICTION_THRESHOLD_GAIN = 0.24
|
||||
KIA_CARNIVAL_FRICTION_CENTER_FADE_MAX = 0.34
|
||||
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_MAX = 0.36
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_LAT = 0.28
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_LAT_WIDTH = 0.055
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_SPEED_MAX = 14.0
|
||||
TUCSON_4TH_GEN_CENTER_TAPER_SPEED_WIDTH = 1.5
|
||||
|
||||
KIA_FORTE_BASE_LAT_ACCEL_FACTOR_MULT = 1.05
|
||||
KIA_FORTE_FF_REDUCTION_LEFT = 0.05
|
||||
@@ -1636,6 +1645,12 @@ def get_kia_carnival_friction_center_fade_scale(desired_lateral_accel: float, v_
|
||||
return 1.0 - (KIA_CARNIVAL_FRICTION_CENTER_FADE_MAX * speed_weight * center_weight)
|
||||
|
||||
|
||||
def get_tucson_4th_gen_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
speed_weight = _sigmoid((TUCSON_4TH_GEN_CENTER_TAPER_SPEED_MAX - v_ego) / TUCSON_4TH_GEN_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _sigmoid((TUCSON_4TH_GEN_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / TUCSON_4TH_GEN_CENTER_TAPER_LAT_WIDTH)
|
||||
return 1.0 - (TUCSON_4TH_GEN_CENTER_TAPER_MAX * speed_weight * center_weight)
|
||||
|
||||
|
||||
def _kia_forte_sigmoid(x: float) -> float:
|
||||
return _sigmoid(x)
|
||||
|
||||
@@ -2769,7 +2784,7 @@ def get_flm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool =
|
||||
set(PRIUS_CARS) | set(BOLT_CARS) | set(VOLT_STANDARD_CARS) | set(IONIQ_5_CARS) |
|
||||
set(IONIQ_EV_OLD_CARS) | set(IONIQ_6_CARS) | set(SONATA_CARS) | set(SONATA_HYBRID_CARS) |
|
||||
set(KIA_XCEED_CARS) | set(KIA_NIRO_PHEV_2022_CARS) | set(KIA_FORTE_CARS) | set(KIA_EV6_CARS) |
|
||||
set(KIA_CARNIVAL_CARS) | set(SILVERADO_CARS)
|
||||
set(KIA_CARNIVAL_CARS) | set(TUCSON_4TH_GEN_CARS) | set(SILVERADO_CARS)
|
||||
)
|
||||
rich_knobs = [name for name, meta in FLM_SUPPORTED_VEHICLE_KNOBS.items() if meta["profile"] == profile_key]
|
||||
return {
|
||||
|
||||
@@ -75,6 +75,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import (
|
||||
get_kia_carnival_center_taper_scale,
|
||||
get_kia_carnival_friction_center_fade_scale,
|
||||
get_kia_carnival_friction_threshold,
|
||||
get_tucson_4th_gen_center_taper_scale,
|
||||
get_kia_ev6_center_taper_scale,
|
||||
get_kia_ev6_ff_scale,
|
||||
get_kia_ev6_friction_scale,
|
||||
@@ -416,6 +417,7 @@ class TestLatControl:
|
||||
assert center_taper < turn_taper <= 1.0
|
||||
assert center_taper < low_speed_taper <= 1.0
|
||||
assert center_taper < highway_taper <= 1.0
|
||||
assert center_taper < 0.84
|
||||
assert neighborhood_taper < 0.94
|
||||
assert neighborhood_turn_taper > 0.99
|
||||
|
||||
@@ -425,7 +427,7 @@ class TestLatControl:
|
||||
|
||||
center_fade = get_kia_carnival_friction_center_fade_scale(0.04, 8.5)
|
||||
turn_fade = get_kia_carnival_friction_center_fade_scale(0.35, 8.5)
|
||||
assert center_fade < turn_fade <= 1.0
|
||||
assert center_fade < 0.75 < turn_fade <= 1.0
|
||||
|
||||
def test_genesis_g90_ff_scale_curve(self):
|
||||
assert get_genesis_g90_ff_scale(0.0, 0.0, 20.0) == 1.0
|
||||
@@ -826,6 +828,38 @@ class TestLatControl:
|
||||
assert controller.is_kia_carnival
|
||||
assert lac_log.active
|
||||
|
||||
def test_tucson_4th_gen_low_speed_center_taper_curve(self):
|
||||
low_speed_center = get_tucson_4th_gen_center_taper_scale(0.0, 8.5)
|
||||
low_speed_moderate = get_tucson_4th_gen_center_taper_scale(0.30, 8.5)
|
||||
low_speed_turn = get_tucson_4th_gen_center_taper_scale(0.50, 8.5)
|
||||
high_speed_center = get_tucson_4th_gen_center_taper_scale(0.0, 20.0)
|
||||
|
||||
assert low_speed_center < 0.70
|
||||
assert low_speed_center < low_speed_moderate < low_speed_turn
|
||||
assert low_speed_turn > 0.98
|
||||
assert high_speed_center > 0.98
|
||||
|
||||
def test_tucson_4th_gen_default_update_path(self):
|
||||
controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_TUCSON_4TH_GEN)
|
||||
CS.vEgo = 8.5
|
||||
|
||||
_, _, lac_log = controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles)
|
||||
|
||||
assert controller.is_tucson_4th_gen
|
||||
assert lac_log.active
|
||||
|
||||
def test_tucson_4th_gen_tapers_low_speed_output(self, monkeypatch):
|
||||
tapered_controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_TUCSON_4TH_GEN)
|
||||
CS.vEgo = 8.5
|
||||
tapered_output, _, _ = tapered_controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles)
|
||||
|
||||
monkeypatch.setattr(latcontrol_torque, "get_tucson_4th_gen_center_taper_scale", lambda *_args: 1.0)
|
||||
base_controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_TUCSON_4TH_GEN)
|
||||
CS.vEgo = 8.5
|
||||
base_output, _, _ = base_controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles)
|
||||
|
||||
assert abs(tapered_output) < abs(base_output)
|
||||
|
||||
def test_ioniq_6_update_path_does_not_post_taper_output(self, monkeypatch):
|
||||
base_controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(HYUNDAI.HYUNDAI_IONIQ_6)
|
||||
base_output, _, _ = base_controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles)
|
||||
|
||||
@@ -26,6 +26,14 @@ MIN_ACTIVE_SPEED = 1.0
|
||||
LOW_ACTIVE_SPEED = 10.0
|
||||
|
||||
|
||||
def resolve_vehicle_model_params(learned_steer_ratio: float, learned_stiffness: float, starpilot_toggles) -> tuple[float, float]:
|
||||
if getattr(starpilot_toggles, "force_auto_tune_off", False):
|
||||
return float(starpilot_toggles.steerRatio), 1.0
|
||||
|
||||
steer_ratio = starpilot_toggles.steerRatio if getattr(starpilot_toggles, "use_custom_steerRatio", False) else learned_steer_ratio
|
||||
return float(steer_ratio), float(learned_stiffness)
|
||||
|
||||
|
||||
class VehicleParamsLearner:
|
||||
def __init__(self, CP: car.CarParams, steer_ratio: float, stiffness_factor: float, angle_offset: float, P_initial: np.ndarray | None = None):
|
||||
self.kf = CarKalman(GENERATED_DIR)
|
||||
@@ -165,8 +173,11 @@ class VehicleParamsLearner:
|
||||
liveParameters = msg.liveParameters
|
||||
liveParameters.posenetValid = True
|
||||
liveParameters.sensorValid = sensors_valid
|
||||
liveParameters.steerRatio = float(x[States.STEER_RATIO].item() if not self.starpilot_toggles.use_custom_steerRatio else self.starpilot_toggles.steerRatio)
|
||||
liveParameters.stiffnessFactor = float(x[States.STIFFNESS].item())
|
||||
liveParameters.steerRatio, liveParameters.stiffnessFactor = resolve_vehicle_model_params(
|
||||
float(x[States.STEER_RATIO].item()),
|
||||
float(x[States.STIFFNESS].item()),
|
||||
self.starpilot_toggles,
|
||||
)
|
||||
liveParameters.roll = float(self.roll)
|
||||
liveParameters.angleOffsetAverageDeg = float(self.avg_angle_offset)
|
||||
liveParameters.angleOffsetDeg = float(self.angle_offset)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import random
|
||||
from types import SimpleNamespace
|
||||
import numpy as np
|
||||
|
||||
from cereal import messaging
|
||||
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params, migrate_cached_vehicle_params_if_needed
|
||||
from openpilot.selfdrive.locationd.paramsd import resolve_vehicle_model_params, retrieve_initial_vehicle_params, migrate_cached_vehicle_params_if_needed
|
||||
from openpilot.selfdrive.locationd.models.car_kf import CarKalman
|
||||
from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
|
||||
from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
|
||||
@@ -20,6 +21,30 @@ def get_random_live_parameters(CP):
|
||||
|
||||
|
||||
class TestParamsd:
|
||||
def test_force_auto_tune_off_locks_vehicle_model_params(self):
|
||||
toggles = SimpleNamespace(force_auto_tune_off=True, use_custom_steerRatio=True, steerRatio=16.8)
|
||||
|
||||
steer_ratio, stiffness = resolve_vehicle_model_params(17.2, 0.84, toggles)
|
||||
|
||||
assert steer_ratio == 16.8
|
||||
assert stiffness == 1.0
|
||||
|
||||
def test_custom_steer_ratio_keeps_learned_stiffness(self):
|
||||
toggles = SimpleNamespace(force_auto_tune_off=False, use_custom_steerRatio=True, steerRatio=16.8)
|
||||
|
||||
steer_ratio, stiffness = resolve_vehicle_model_params(17.2, 0.84, toggles)
|
||||
|
||||
assert steer_ratio == 16.8
|
||||
assert stiffness == 0.84
|
||||
|
||||
def test_vehicle_model_learning_remains_default(self):
|
||||
toggles = SimpleNamespace(force_auto_tune_off=False, use_custom_steerRatio=False, steerRatio=16.8)
|
||||
|
||||
steer_ratio, stiffness = resolve_vehicle_model_params(17.2, 0.84, toggles)
|
||||
|
||||
assert steer_ratio == 17.2
|
||||
assert stiffness == 0.84
|
||||
|
||||
def test_read_saved_params(self):
|
||||
params = Params()
|
||||
|
||||
|
||||
@@ -247,12 +247,12 @@ class StarPilotLateralLayout(_SettingsPage):
|
||||
),
|
||||
SettingRow(
|
||||
"ForceAutoTuneOff", "toggle", tr_noop("Force Auto-Tune Off"),
|
||||
subtitle=tr_noop("Force-disable auto-tuning and use your set values."),
|
||||
subtitle=tr_noop("Force-disable learned lateral values and use your set values."),
|
||||
get_state=lambda: p.get_bool("ForceAutoTuneOff"),
|
||||
set_state=lambda s: (p.put_bool("ForceAutoTuneOff", s),
|
||||
s and p.put_bool("ForceAutoTune", False),
|
||||
_sync_parent(p, "AdvancedLateralTune", _ADVANCED_LATERAL_KEYS)),
|
||||
enabled=lambda: cs.hasAutoTune and cs.isTorqueCar and not cs.isAngleCar,
|
||||
enabled=lambda: cs.isTorqueCar and not cs.isAngleCar,
|
||||
disabled_label=tr_noop("Not Available"),
|
||||
visible=alt_on,
|
||||
),
|
||||
@@ -379,6 +379,6 @@ class StarPilotLateralLayout(_SettingsPage):
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._params.put_int("LaneChangeSmoothing", int(val))
|
||||
current = self._params.get_int("LaneChangeSmoothing") if self._params.get_int("LaneChangeSmoothing") > 0 else 10
|
||||
current = self._params.get_int("LaneChangeSmoothing") if self._params.get_int("LaneChangeSmoothing") > 0 else 5
|
||||
gui_app.push_widget(AetherSliderDialog(tr("Lane Change Smoothing"), 1, 10, 1, current, on_close,
|
||||
color=self.SLIDER_COLOR))
|
||||
|
||||
@@ -316,12 +316,13 @@ class DeveloperSidebar:
|
||||
fallback_use_custom_steer_ratio = force_auto_tune_off or (_setting_changed(self._cached_ratio, self._cached_ratio_stock) and not force_auto_tune)
|
||||
use_custom_steer_ratio = self._toggle_bool(toggles, "use_custom_steerRatio", fallback_use_custom_steer_ratio)
|
||||
custom_steer_ratio = self._toggle_float(toggles, "steerRatio", self._cached_ratio)
|
||||
if live_parameters:
|
||||
if use_custom_steer_ratio:
|
||||
steer_ratio = custom_steer_ratio
|
||||
elif live_parameters:
|
||||
steer_ratio = live_parameters.steerRatio
|
||||
stiff_factor = live_parameters.stiffnessFactor
|
||||
else:
|
||||
steer_ratio = custom_steer_ratio if use_custom_steer_ratio else (self._cached_ratio_stock if self._cached_ratio_stock != 0.0 else self._cached_ratio)
|
||||
stiff_factor = 0.0
|
||||
steer_ratio = self._cached_ratio_stock if self._cached_ratio_stock != 0.0 else self._cached_ratio
|
||||
stiff_factor = 1.0 if force_auto_tune_off else (live_parameters.stiffnessFactor if live_parameters else 1.0)
|
||||
|
||||
force_auto_tune_flm = self._flm_changed("ForceAutoTune", "ForceAutoTuneOff")
|
||||
self._metric_colors = {
|
||||
@@ -341,7 +342,10 @@ class DeveloperSidebar:
|
||||
auto=live_parameters is not None and not use_custom_steer_ratio,
|
||||
flm=self._flm_changed("SteerRatio") or (force_auto_tune_flm and use_custom_steer_ratio),
|
||||
),
|
||||
7: self._tuning_color(auto=live_parameters is not None),
|
||||
7: self._tuning_color(
|
||||
auto=live_parameters is not None and not force_auto_tune_off,
|
||||
flm=force_auto_tune_flm and force_auto_tune_off,
|
||||
),
|
||||
}
|
||||
|
||||
model_name = ui_state.starpilot_toggles.get("model_name", "N/A")
|
||||
|
||||
@@ -717,7 +717,9 @@ class StarPilotVariables:
|
||||
|
||||
advanced_lateral_tuning = self.get_value("AdvancedLateralTune")
|
||||
toggle.force_auto_tune = self.get_value("ForceAutoTune", condition=advanced_lateral_tuning and not has_auto_tune and is_torque_car and not is_angle_car)
|
||||
toggle.force_auto_tune_off = self.get_value("ForceAutoTuneOff", condition=advanced_lateral_tuning and has_auto_tune and is_torque_car and not is_angle_car)
|
||||
# Force-off is also meaningful on manually tuned torque cars: it locks the
|
||||
# vehicle-model parameters instead of allowing paramsd to learn over them.
|
||||
toggle.force_auto_tune_off = self.get_value("ForceAutoTuneOff", condition=advanced_lateral_tuning and is_torque_car and not is_angle_car)
|
||||
toggle.flm_active_profile_id = self.params.get("FLMActiveProfileId", encoding="utf-8") or ""
|
||||
toggle.flm_trial_applied = self.params.get_bool("FLMTrialApplied")
|
||||
flm_overrides_raw = self.params.get("FLMActiveOverrides", encoding="utf-8") or ""
|
||||
@@ -1052,7 +1054,7 @@ class StarPilotVariables:
|
||||
# The jerk factor is derived from a sinusoidal lane-change profile: j = pi^3 * W / T^3,
|
||||
# with 1.3x headroom. Only jerk (curvature rate) is shaped; lateral accel stays at the
|
||||
# stock envelope so the end-of-maneuver arrest is never starved of authority.
|
||||
pace = self.get_value("LaneChangeSmoothing", cast=int, condition=toggle.lane_changes) or 10
|
||||
pace = self.get_value("LaneChangeSmoothing", cast=int, condition=toggle.lane_changes) or 5
|
||||
pace = max(1, min(10, pace))
|
||||
lane_w = 3.5
|
||||
t_target = 3.0 + (10 - pace) * 5.0 / 9.0
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from collections import defaultdict, deque
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import messaging
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
@@ -21,6 +20,7 @@ MISSING_TILE_BACKOFF_S = 30.0
|
||||
FAILURE_WINDOW_S = 3.0
|
||||
FAILURE_THRESHOLD = 3
|
||||
MISSING_COVERAGE_EXIT_CODE = 3
|
||||
WAIT_FOR_GPS_EXIT_CODE = 4
|
||||
ROAD_STATE_POLL_S = 1.0
|
||||
|
||||
|
||||
@@ -46,6 +46,15 @@ def is_offline_read_error(line: str) -> bool:
|
||||
return payload.get("msg") == "could not unmarshal offline data"
|
||||
|
||||
|
||||
def is_null_island_tile(filename: str) -> bool:
|
||||
try:
|
||||
min_lat, min_lon, max_lat, max_lon = (float(value) for value in Path(filename).name.split("_"))
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
return min_lat <= 0 <= max_lat and min_lon <= 0 <= max_lon
|
||||
|
||||
|
||||
class CorruptTileMonitor:
|
||||
def __init__(self, threshold: int = FAILURE_THRESHOLD, window_s: float = FAILURE_WINDOW_S):
|
||||
self.threshold = threshold
|
||||
@@ -86,7 +95,7 @@ def quarantine_offline_tile(filename: str) -> Path | None:
|
||||
if not tile_path.is_file():
|
||||
return None
|
||||
|
||||
quarantined = tile_path.with_name(f"{tile_path.name}.corrupt.{int(time.time())}")
|
||||
quarantined = tile_path.with_name(f"{tile_path.name}.corrupt.{time.monotonic_ns()}")
|
||||
try:
|
||||
tile_path.rename(quarantined)
|
||||
except OSError:
|
||||
@@ -147,6 +156,11 @@ def run_mapd_once() -> int:
|
||||
# Stop its resulting hot loop until the next onroad process cycle.
|
||||
missing_tile = monitor.current_filename
|
||||
if is_offline_read_error(line) and missing_tile is not None and not Path(missing_tile).is_file():
|
||||
if is_null_island_tile(missing_tile):
|
||||
cloudlog.info(f"mapd_wrapper received a location before GPS fix; waiting to restart mapd: {missing_tile}")
|
||||
terminate_child(proc)
|
||||
return WAIT_FOR_GPS_EXIT_CODE
|
||||
|
||||
cloudlog.info(f"mapd_wrapper has no offline tile for {missing_tile}; stopping mapd until the next drive")
|
||||
terminate_child(proc)
|
||||
return MISSING_COVERAGE_EXIT_CODE
|
||||
@@ -157,10 +171,7 @@ def run_mapd_once() -> int:
|
||||
quarantined = quarantine_offline_tile(bad_tile)
|
||||
if quarantined is None:
|
||||
if not OFFLINE_ROOT.exists():
|
||||
cloudlog.warning(
|
||||
f"mapd_wrapper detected repeated offline read failures for {bad_tile}, "
|
||||
f"but {OFFLINE_ROOT} does not exist; backing off mapd restarts"
|
||||
)
|
||||
cloudlog.warning(f"mapd_wrapper detected repeated offline read failures for {bad_tile}, but {OFFLINE_ROOT} does not exist; backing off mapd restarts")
|
||||
terminate_child(proc)
|
||||
return 2
|
||||
|
||||
@@ -182,6 +193,16 @@ def wait_for_road_state_change(params: Params) -> None:
|
||||
time.sleep(ROAD_STATE_POLL_S)
|
||||
|
||||
|
||||
def wait_for_gps_fix_or_road_state_change(params: Params, sm=None) -> None:
|
||||
initial_onroad = params.get_bool("IsOnroad")
|
||||
sm = sm or messaging.SubMaster(["gpsLocationExternal"])
|
||||
|
||||
while params.get_bool("IsOnroad") == initial_onroad:
|
||||
sm.update(1000)
|
||||
if sm.updated["gpsLocationExternal"] and sm["gpsLocationExternal"].hasFix:
|
||||
return
|
||||
|
||||
|
||||
def main() -> None:
|
||||
params = Params()
|
||||
while True:
|
||||
@@ -195,6 +216,9 @@ def main() -> None:
|
||||
if exit_code == MISSING_COVERAGE_EXIT_CODE:
|
||||
wait_for_road_state_change(params)
|
||||
continue
|
||||
if exit_code == WAIT_FOR_GPS_EXIT_CODE:
|
||||
wait_for_gps_fix_or_road_state_change(params)
|
||||
continue
|
||||
raise SystemExit(exit_code)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,16 @@ import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.starpilot.navigation.mapd_wrapper import CorruptTileMonitor, quarantine_offline_tile, run_mapd_once, terminate_child, wait_for_road_state_change
|
||||
from openpilot.starpilot.navigation.mapd_wrapper import (
|
||||
WAIT_FOR_GPS_EXIT_CODE,
|
||||
CorruptTileMonitor,
|
||||
is_null_island_tile,
|
||||
quarantine_offline_tile,
|
||||
run_mapd_once,
|
||||
terminate_child,
|
||||
wait_for_gps_fix_or_road_state_change,
|
||||
wait_for_road_state_change,
|
||||
)
|
||||
|
||||
|
||||
def _loading_line(filename: str) -> str:
|
||||
@@ -52,6 +61,12 @@ def test_quarantine_offline_tile_ignores_missing_file(tmp_path, monkeypatch):
|
||||
assert quarantine_offline_tile(missing_tile.as_posix()) is None
|
||||
|
||||
|
||||
def test_null_island_tile_detection():
|
||||
assert is_null_island_tile("/data/media/0/osm/offline/-2/-2/-0.250000_-0.250000_0.000000_0.000000")
|
||||
assert not is_null_island_tile("/data/media/0/osm/offline/42/-72/42.500000_-71.750000_42.750000_-71.500000")
|
||||
assert not is_null_island_tile("not-a-tile")
|
||||
|
||||
|
||||
def test_run_mapd_once_stops_for_missing_offline_coverage(tmp_path, monkeypatch):
|
||||
missing_tile = tmp_path / "offline/36/-98/37.500000_-98.000000_37.750000_-97.750000"
|
||||
output = []
|
||||
@@ -83,6 +98,35 @@ def test_run_mapd_once_stops_for_missing_offline_coverage(tmp_path, monkeypatch)
|
||||
assert proc.terminated
|
||||
|
||||
|
||||
def test_run_mapd_once_waits_for_gps_after_null_island_lookup(tmp_path, monkeypatch):
|
||||
missing_tile = tmp_path / "offline/-2/-2/-0.250000_-0.250000_0.000000_0.000000"
|
||||
output = (_loading_line(missing_tile.as_posix()), _error_line())
|
||||
|
||||
class CompletedProcess:
|
||||
pid = 123
|
||||
|
||||
def __init__(self):
|
||||
self.stdout = iter(f"{line}\n" for line in output)
|
||||
self.terminated = False
|
||||
|
||||
def poll(self):
|
||||
return 0 if self.terminated else None
|
||||
|
||||
def terminate(self):
|
||||
self.terminated = True
|
||||
|
||||
def wait(self, timeout=None):
|
||||
return 0
|
||||
|
||||
proc = CompletedProcess()
|
||||
monkeypatch.setitem(run_mapd_once.__globals__, "OFFLINE_ROOT", tmp_path / "offline")
|
||||
monkeypatch.setattr(subprocess, "Popen", lambda *args, **kwargs: proc)
|
||||
monkeypatch.setattr("signal.signal", lambda *args: None)
|
||||
|
||||
assert run_mapd_once() == WAIT_FOR_GPS_EXIT_CODE
|
||||
assert proc.terminated
|
||||
|
||||
|
||||
def test_missing_coverage_waits_for_road_state_change(monkeypatch):
|
||||
class Params:
|
||||
states = iter((True, True, False))
|
||||
@@ -99,6 +143,37 @@ def test_missing_coverage_waits_for_road_state_change(monkeypatch):
|
||||
assert sleeps == [1.0]
|
||||
|
||||
|
||||
def test_null_island_wait_ends_when_gps_gets_a_fix():
|
||||
class Params:
|
||||
def get_bool(self, key):
|
||||
assert key == "IsOnroad"
|
||||
return True
|
||||
|
||||
class GpsLocation:
|
||||
hasFix = False
|
||||
|
||||
class SubMaster:
|
||||
def __init__(self):
|
||||
self.updated = {"gpsLocationExternal": False}
|
||||
self.location = GpsLocation()
|
||||
self.update_count = 0
|
||||
|
||||
def update(self, timeout):
|
||||
assert timeout == 1000
|
||||
self.update_count += 1
|
||||
self.updated["gpsLocationExternal"] = True
|
||||
self.location.hasFix = self.update_count == 2
|
||||
|
||||
def __getitem__(self, key):
|
||||
assert key == "gpsLocationExternal"
|
||||
return self.location
|
||||
|
||||
sm = SubMaster()
|
||||
wait_for_gps_fix_or_road_state_change(Params(), sm)
|
||||
|
||||
assert sm.update_count == 2
|
||||
|
||||
|
||||
def test_terminate_child_tolerates_wedged_process():
|
||||
class WedgedProcess:
|
||||
pid = 123
|
||||
|
||||
Reference in New Issue
Block a user