Annie Are You Okay

This commit is contained in:
firestar5683
2026-06-18 23:48:23 -05:00
parent 9c85ebc365
commit c8128d2cde
14 changed files with 385 additions and 74 deletions
+36 -6
View File
@@ -54,6 +54,10 @@ VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_DOWN = 0.8 * DT_CTRL * 4
VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_BP = [4.0, 8.0]
VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_V = [0.4, 1.0]
VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_INCLINE_V = [0.2, 1.0]
TRUCK_LONG_SMOOTH_CARS = {
CAR.CHEVROLET_SILVERADO,
CAR.CHEVROLET_SILVERADO_CC,
}
def get_stock_cc_active_for_cancel(CP, CS):
@@ -149,6 +153,22 @@ def get_testing_ground_1_brake_switch_bias(v_ego: float) -> int:
return int(round(np.interp(v_ego, [0.0, 6.0, 15.0, 30.0], [40.0, 85.0, 130.0, 170.0])))
def shape_truck_positive_accel(accel: float, v_ego: float, enabled: bool) -> float:
if not enabled or accel <= 0.0 or v_ego < 8.0:
return accel
low_scale = float(np.interp(v_ego, [8.0, 15.0, 25.0, 35.0], [0.60, 0.45, 0.32, 0.25]))
mid_scale = float(np.interp(v_ego, [8.0, 15.0, 25.0, 35.0], [0.82, 0.72, 0.60, 0.52]))
if accel <= 0.18:
return accel * low_scale
if accel <= 0.45:
return float(np.interp(accel, [0.18, 0.45], [0.18 * low_scale, 0.45 * mid_scale]))
if accel <= 0.8:
return float(np.interp(accel, [0.45, 0.8], [0.45 * mid_scale, 0.8]))
return accel
def get_lka_steering_cmd_counter(next_counter: int, CS) -> int:
if getattr(CS, "loopback_lka_steering_cmd_updated", False):
return (getattr(CS, "loopback_lka_steering_cmd_counter", next_counter) + 1) % 4
@@ -197,17 +217,18 @@ def get_volt_one_pedal_target_decel(v_ego: float) -> float:
def should_activate_volt_one_pedal(one_pedal_ready: bool, cruise_main: bool, long_active: bool,
gas_pressed: bool, brake_pressed: bool, regen_braking: bool,
single_pedal_mode: bool, gear_shifter, moving_backward: bool) -> bool:
single_pedal_mode: bool, gear_shifter, drive_time_s: float) -> bool:
# Volt rear wheel direction bits can falsely report reverse while stopping in L.
return (
one_pedal_ready and
cruise_main and
single_pedal_mode and
gear_shifter in AUTO_HOLD_DRIVE_GEARS and
drive_time_s >= AUTO_HOLD_MIN_DRIVE_TIME_S and
not long_active and
not gas_pressed and
not brake_pressed and
not regen_braking and
not moving_backward
not regen_braking
)
@@ -505,7 +526,7 @@ class CarController(CarControllerBase):
CS.out.regenBraking,
bool(getattr(CS, "single_pedal_mode", False)),
CS.out.gearShifter,
bool(getattr(CS, "moving_backward", False)),
float(getattr(CS, "one_pedal_drive_time", 0.0)),
)
if self.frame % 4 == 0:
@@ -638,7 +659,7 @@ class CarController(CarControllerBase):
volt_one_pedal_hold_active = (
volt_one_pedal_braking and
not auto_hold_active and
CS.auto_hold_drive_time >= AUTO_HOLD_MIN_DRIVE_TIME_S and
CS.one_pedal_drive_time >= AUTO_HOLD_MIN_DRIVE_TIME_S and
(CS.out.standstill or CS.out.vEgo < 0.02)
)
@@ -745,7 +766,16 @@ class CarController(CarControllerBase):
if testing_ground.use_1:
accel_max = min(accel_max, np.interp(CS.out.vEgo, [0.0, 4.0, 12.0], [1.25, 1.6, self.params.ACCEL_MAX]))
accel_cmd = float(np.clip(actuators.accel + accel_due_to_pitch, self.params.ACCEL_MIN, accel_max))
accel_input = actuators.accel + accel_due_to_pitch
if (
getattr(starpilot_toggles, "truck_tuning", False) and
self.CP.carFingerprint in TRUCK_LONG_SMOOTH_CARS and
getattr(self.CP, "transmissionType", None) == TransmissionType.automatic and
not self.CP.enableGasInterceptorDEPRECATED
):
accel_input = shape_truck_positive_accel(accel_input, CS.out.vEgo, True)
accel_cmd = float(np.clip(accel_input, self.params.ACCEL_MIN, accel_max))
torque = self.tireRadius * ((self.mass * accel_cmd) + (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2))
scaled_torque = torque + self.params.ZERO_GAS
apply_gas_torque = np.clip(scaled_torque, self.params.MAX_ACC_REGEN, gas_max)
+3
View File
@@ -78,6 +78,7 @@ class CarState(CarStateBase):
self.auto_hold_armed = False
self.auto_hold_engaged = False
self.auto_hold_drive_time = 0.0
self.one_pedal_drive_time = 0.0
self.auto_hold_fault_suppression_timer = 0.0
self.regen_release_timer = 0.0
self.user_regen_paddle_pressed = False
@@ -215,8 +216,10 @@ class CarState(CarStateBase):
self.auto_hold_drive_time = AUTO_HOLD_MIN_DRIVE_TIME_S
else:
self.auto_hold_drive_time = min(self.auto_hold_drive_time + DT_CTRL, AUTO_HOLD_MIN_DRIVE_TIME_S)
self.one_pedal_drive_time = min(self.one_pedal_drive_time + DT_CTRL, AUTO_HOLD_MIN_DRIVE_TIME_S)
else:
self.auto_hold_drive_time = 0.0
self.one_pedal_drive_time = 0.0
self.auto_hold_armed = False
self.auto_hold_engaged = False
+11 -7
View File
@@ -217,6 +217,10 @@ class CarInterface(CarInterfaceBase):
gm_auto_hold = params.get_bool("GMAutoHold")
except UnknownKeyName:
gm_auto_hold = False
try:
volt_one_pedal_mode = params.get_bool("VoltOnePedalMode")
except UnknownKeyName:
volt_one_pedal_mode = False
ret.brand = "gm"
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.gm)]
@@ -671,8 +675,8 @@ class CarInterface(CarInterfaceBase):
if remote_start_boots_comma:
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value
volt_stock_auto_hold_safety = (
gm_auto_hold and
volt_stock_friction_brake_safety = (
(gm_auto_hold or volt_one_pedal_mode) and
candidate in {
CAR.CHEVROLET_VOLT,
CAR.CHEVROLET_VOLT_2019,
@@ -680,11 +684,11 @@ class CarInterface(CarInterfaceBase):
CAR.CHEVROLET_VOLT_CAMERA,
}
)
if volt_stock_auto_hold_safety:
# Reuse the paddle-scheduler safety bit as a Volt auto-hold marker on
# non-pedal paths. Hold can run while OP longitudinal is configured but
# not currently active, so the bit must be present regardless of the
# current long-control mode.
if volt_stock_friction_brake_safety:
# Reuse the paddle-scheduler safety bit as a Volt stock friction-brake
# marker on non-pedal paths. Both auto hold and one-pedal can run while
# OP longitudinal is configured but not currently active, so the bit must
# be present regardless of the current long-control mode.
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
use_panda_3d1_sched = (
@@ -43,6 +43,7 @@ from opendbc.car.gm.carcontroller import (
get_volt_one_pedal_target_decel,
get_testing_ground_1_brake_switch_bias,
get_stock_cc_active_for_cancel,
shape_truck_positive_accel,
should_activate_auto_hold,
should_activate_volt_one_pedal,
should_send_stock_long_cancel,
@@ -109,7 +110,7 @@ def test_stock_cancel_is_suppressed_when_acc_is_faulted():
cs = _cs(True, AccState.FAULTED)
cs.out.accFaulted = True
assert not get_stock_cc_active_for_cancel(CP, cs)
assert get_stock_cc_active_for_cancel(CP, cs)
assert not should_send_stock_long_cancel(11, cs)
@@ -328,6 +329,7 @@ def test_auto_hold_activation_blocks_when_long_is_active_or_motion_is_above_thre
False,
False,
False,
False,
0.03,
)
@@ -370,7 +372,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
False,
True,
structs.CarState.GearShifter.low,
False,
3.0,
)
assert not should_activate_volt_one_pedal(
True,
@@ -381,7 +383,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
False,
True,
structs.CarState.GearShifter.low,
False,
3.0,
)
assert not should_activate_volt_one_pedal(
True,
@@ -392,7 +394,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
False,
True,
structs.CarState.GearShifter.low,
False,
3.0,
)
assert not should_activate_volt_one_pedal(
True,
@@ -403,7 +405,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
False,
True,
structs.CarState.GearShifter.low,
False,
3.0,
)
assert not should_activate_volt_one_pedal(
True,
@@ -414,7 +416,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
True,
True,
structs.CarState.GearShifter.low,
False,
3.0,
)
assert not should_activate_volt_one_pedal(
True,
@@ -425,7 +427,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input():
False,
False,
structs.CarState.GearShifter.drive,
False,
3.0,
)
@@ -435,6 +437,34 @@ def test_volt_one_pedal_target_decel_stays_active_above_low_speed_band():
assert get_volt_one_pedal_target_decel(20.0 * CV.MPH_TO_MS) == -1.1
def test_volt_one_pedal_regression_ignores_noisy_wheel_direction_bits():
assert should_activate_volt_one_pedal(
True,
True,
False,
False,
False,
False,
True,
structs.CarState.GearShifter.low,
3.0,
)
def test_volt_one_pedal_requires_time_in_drive_before_arming():
assert not should_activate_volt_one_pedal(
True,
True,
False,
False,
False,
False,
True,
structs.CarState.GearShifter.low,
2.5,
)
def test_friction_brake_mode_keeps_near_stop_disabled_for_regular_long_braking():
CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM)
@@ -494,6 +524,27 @@ def test_calc_pedal_command_keeps_strong_positive_requests_responsive():
assert pedal_gas - 0.18 > 0.04
def test_shape_truck_positive_accel_softens_small_highway_requests():
shaped = shape_truck_positive_accel(0.12, 26.0, True)
assert shaped < 0.05
def test_shape_truck_positive_accel_keeps_mid_follow_requests_available():
shaped = shape_truck_positive_accel(0.45, 13.5, True)
assert 0.30 < shaped < 0.35
def test_shape_truck_positive_accel_leaves_large_requests_alone():
assert shape_truck_positive_accel(1.0, 26.0, True) == 1.0
def test_shape_truck_positive_accel_is_inactive_when_disabled_or_low_speed():
assert shape_truck_positive_accel(0.12, 26.0, False) == 0.12
assert shape_truck_positive_accel(0.12, 6.0, True) == 0.12
def test_use_interceptor_sng_launch_requires_actual_near_stop():
CP = SimpleNamespace(vEgoStarting=0.25)
@@ -186,6 +186,24 @@ class TestGMInterface:
assert car_params.openpilotLongitudinalControl
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
def test_volt_one_pedal_sets_stock_hold_safety_bit_without_auto_hold(self):
CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM]
fingerprint = _empty_fingerprint()
fingerprint[0][0x2FF] = 8
params = Params()
try:
params.put_bool("GMAutoHold", False)
params.put_bool("VoltOnePedalMode", True)
car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=True, is_release=False,
docs=False, starpilot_toggles=_test_starpilot_toggles())
finally:
params.remove("GMAutoHold")
params.remove("VoltOnePedalMode")
assert car_params.openpilotLongitudinalControl
assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value
@parameterized.expand(VOLT_CARS)
def test_volt_bsm_is_enabled_without_fingerprint_match(self, car_model):
CarInterface = interfaces[car_model]
@@ -28,15 +28,6 @@
color: var(--text-muted);
}
#searchSuggestions p {
font-size: var(--font-size-sm);
margin: 0;
max-width: calc(100% - 8rem);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#searchSuggestions .suggestion-item {
align-items: center;
background-color: var(--input-bg);
@@ -50,6 +41,32 @@
width: 100%;
}
#searchSuggestions .suggestion-copy {
flex: 1;
min-width: 0;
padding-right: 1rem;
}
#searchSuggestions .suggestion-primary,
#searchSuggestions .suggestion-secondary {
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
}
#searchSuggestions .suggestion-primary {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-demi-bold);
white-space: nowrap;
}
#searchSuggestions .suggestion-secondary {
color: var(--text-muted);
font-size: var(--font-size-xs);
margin-top: 0.25rem;
white-space: nowrap;
}
#searchSuggestions .suggestion-item:first-child {
border-top-left-radius: var(--border-radius-xl);
border-top-right-radius: var(--border-radius-xl);
@@ -533,4 +550,4 @@
min-width: 0;
width: calc(100% - var(--padding-xl));
}
}
}
@@ -140,6 +140,76 @@ function parseCoordinatePair(value) {
return { latitude, longitude };
}
function cleanSuggestionText(value) {
if (typeof value !== "string") return "";
return value.replace(/\s+/g, " ").trim();
}
function joinSuggestionParts(parts) {
const seen = new Set();
const unique = [];
for (const part of parts) {
const text = cleanSuggestionText(part);
if (!text) continue;
const key = text.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
unique.push(text);
}
return unique.join(", ");
}
function splitSuggestionLabel(value) {
const cleaned = cleanSuggestionText(value);
if (!cleaned) {
return { primary: "", secondary: "" };
}
const [primary, ...rest] = cleaned.split(",").map(part => part.trim()).filter(Boolean);
return {
primary: primary || cleaned,
secondary: rest.join(", "),
};
}
function getSuggestionText(suggestion) {
const rawName = cleanSuggestionText(suggestion.name || suggestion.title || "");
const rawPlaceName = cleanSuggestionText(suggestion.place_name || "");
const explicitAddress = joinSuggestionParts([
suggestion.full_address,
suggestion.address,
suggestion.district,
suggestion.city,
suggestion.cityname,
suggestion.adcode && suggestion.address ? "" : suggestion.adname,
suggestion.pname,
]);
if (rawName) {
const splitPlaceName = rawPlaceName && rawPlaceName.toLowerCase().startsWith(`${rawName.toLowerCase()},`)
? splitSuggestionLabel(rawPlaceName)
: { primary: rawName, secondary: "" };
const secondary = explicitAddress || splitPlaceName.secondary;
return {
primary: splitPlaceName.primary || rawName,
secondary: secondary && secondary.toLowerCase() !== rawName.toLowerCase() ? secondary : "",
};
}
if (rawPlaceName) {
return splitSuggestionLabel(rawPlaceName);
}
if (explicitAddress) {
return splitSuggestionLabel(explicitAddress);
}
return { primary: "Unnamed Location", secondary: "" };
}
let map;
let destinationMarker;
let favoriteMarkers = [];
@@ -711,7 +781,7 @@ export function NavDestination() {
${() => (state.favoritesCount > 0 ? html`<button class="favorites-toggle-button" @click="${handleFavoritesClick}">❤️ Favorites</button>` : "")}
${() => (state.canToggleProvider ? html`
<div class="search-provider-toggle">
<button class="${() => (state.searchProvider === "amap" ? "active" : "")}" @click="${() => { state.searchProvider = "amap"; state.suggestions = "[]"; }}">AMap</button>
<button class="${() => (state.searchProvider === "amap" ? "active" : "")}" title="AMap / Gaode search provider" @click="${() => { state.searchProvider = "amap"; state.suggestions = "[]"; }}">AMap</button>
<button class="${() => (state.searchProvider === "mapbox" ? "active" : "")}" @click="${() => { state.searchProvider = "mapbox"; state.suggestions = "[]"; }}">Mapbox</button>
</div>
` : "")}
@@ -789,11 +859,19 @@ function SearchSuggestions({ suggestions, selectSuggestion, removeFavorite, rena
const isFavorite = s => s.name && s.latitude != null && s.longitude != null && s.routeId;
const item = s => html`
<div class="suggestion-item" @click="${() => selectSuggestion(s)}">
<p>
${s.is_home ? "🏠 " : ""}
${s.is_work ? "💼 " : ""}
${s.name || s.address}
</p>
${(() => {
const text = getSuggestionText(s);
return html`
<div class="suggestion-copy">
<p class="suggestion-primary">
${s.is_home ? "🏠 " : ""}
${s.is_work ? "💼 " : ""}
${text.primary}
</p>
${text.secondary ? html`<p class="suggestion-secondary">${text.secondary}</p>` : ""}
</div>
`;
})()}
${isFavorite(s) ? html`
<div class="favorite-actions">
<button class="home-favorite-button ${s.is_home ? "active" : ""}" title="Set as Home" @click="${e => { e.stopPropagation(); setHome(s); }}">🏠</button>
@@ -132,6 +132,13 @@
text-align: center;
}
.navkeys-subtitle {
color: var(--text-muted);
font-size: var(--font-size-xs);
margin: calc(var(--margin-base) * -1) 0 var(--margin-base);
text-align: center;
}
.navkeys-wrapper {
align-items: flex-start;
display: flex;
@@ -153,4 +160,4 @@
.navkeys-input {
font-size: var(--font-size-sm);
}
}
}
@@ -86,8 +86,8 @@ export function NavKeys() {
const getDeleteLabel = (kind) => {
switch (kind) {
case "amap1": return "Amap 1"
case "amap2": return "Amap 2"
case "amap1": return "AMap / Gaode 1"
case "amap2": return "AMap / Gaode 2"
case "public": return "Public Mapbox"
case "secret": return "Secret Mapbox"
default: return kind
@@ -203,20 +203,22 @@ export function NavKeys() {
queueMicrotask(api.load)
function renderGroup(title, kinds) {
const isMapbox = title === "Mapbox Keys"
const isMapbox = title === "Mapbox Keys"
const isAMap = title === "AMap / Gaode Keys"
return html`
<div class="navkeys-group">
<div class="navkeys-title">
${title}
${isMapbox ? html`
<span class="navkeys-help-icon" @click="${() => state.showMapboxHelp = !state.showMapboxHelp}">
<i class="bi bi-question-circle-fill"></i>
</span>
` : ""}
</div>
${kinds.map(kind => {
<div class="navkeys-title">
${title}
${isMapbox ? html`
<span class="navkeys-help-icon" @click="${() => state.showMapboxHelp = !state.showMapboxHelp}">
<i class="bi bi-question-circle-fill"></i>
</span>
` : ""}
</div>
${isAMap ? html`<div class="navkeys-subtitle">AMap is the Gaode provider, not Google Maps.</div>` : ""}
${kinds.map(kind => {
const keyMeta = meta[kind]
const label = kind[0].toUpperCase() + kind.slice(1).replace(/[0-9]/, d => " " + d)
@@ -296,7 +298,7 @@ export function NavKeys() {
return html`
<div class="navkeys-wrapper navkeys-offset-top">
<div class="navkeys-container">
${renderGroup("AMap Keys", ["amap1", "amap2"])}
${renderGroup("AMap / Gaode Keys", ["amap1", "amap2"])}
${renderStatus("amap")}
</div>
<div class="navkeys-container">
@@ -9,8 +9,8 @@ import { Home } from "/assets/components/home/home.js"
import { LateralManeuvers } from "/assets/components/tools/lateral_maneuvers.js"
import { LongitudinalManeuvers } from "/assets/components/tools/longitudinal_maneuvers.js"
import { MapsManager } from "/assets/components/tools/maps.js"
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-favorites-2"
import { NavKeys } from "/assets/components/navigation/navigation_keys.js"
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-search-context-1"
import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=nav-search-context-1"
import { RouteRecordings } from "/assets/components/recordings/dashcam_routes.js"
import { SettingsView } from "/assets/components/settings.js"
import { ScreenRecordings } from "/assets/components/recordings/screen_recordings.js"
@@ -19,8 +19,8 @@
<link rel="stylesheet" href="/assets/components/home/home.css">
<link rel="stylesheet" href="/assets/components/main.css">
<link rel="stylesheet" href="/assets/components/modal.css">
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-favorites-2">
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css">
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-search-context-1">
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css?v=nav-search-context-1">
<link rel="stylesheet" href="/assets/components/recordings/dashcam_routes.css">
<link rel="stylesheet" href="/assets/components/recordings/screen_recordings.css">
<link rel="stylesheet" href="/assets/components/settings.css">
@@ -461,6 +461,7 @@ def test_persistent_personal_records_and_model_usage_survive_empty_routes():
"distractedMoments": 0,
"unresponsiveMoments": 0,
"routeModifiedAt": 100,
"analysisComplete": True,
},
{
"name": "route-2",
@@ -472,6 +473,7 @@ def test_persistent_personal_records_and_model_usage_survive_empty_routes():
"distractedMoments": 1,
"unresponsiveMoments": 0,
"routeModifiedAt": 100,
"analysisComplete": True,
},
{
"name": "route-3",
@@ -483,6 +485,7 @@ def test_persistent_personal_records_and_model_usage_survive_empty_routes():
"distractedMoments": 0,
"unresponsiveMoments": 0,
"routeModifiedAt": 100,
"analysisComplete": True,
},
]
@@ -509,6 +512,67 @@ def test_persistent_personal_records_and_model_usage_survive_empty_routes():
assert restored_records["cleanDriveStreak"]["value"] == "3 drives"
def test_model_usage_ignores_pending_route_shells():
params = FakeParams({"AvailableModels": "orion,vega", "AvailableModelNames": "Orion,Vega"})
drives = [
{
"name": "route-1",
"date": "2026-06-15T08:00:00",
"distanceMeters": 10000.0,
"duration": 1200,
"engagedSeconds": 900.0,
"model": "Orion",
"routeModifiedAt": 100,
"analysisComplete": True,
},
{
"name": "route-2",
"date": "2026-06-16T08:00:00",
"distanceMeters": 0.0,
"duration": 600,
"engagedSeconds": 0.0,
"model": "Vega",
"routeModifiedAt": 100,
"analysisComplete": False,
"attentionKnown": False,
},
]
persistent = utilities._update_dashboard_persistent_stats(params, drives, wall_now=1000)
favorites = utilities._build_favorite_models(params, persistent)
assert [model["name"] for model in favorites] == ["Orion"]
assert favorites[0]["drives"] == 1
def test_cpu_temp_reader_uses_hardware_cpu_values(monkeypatch):
hardware_module = _simple_module(
"openpilot.system.hardware",
HARDWARE=SimpleNamespace(
get_thermal_config=lambda: SimpleNamespace(
get_msg=lambda: {"cpuTempC": [55.1, 56.6], "memoryTempC": 75.0}
)
),
)
monkeypatch.setitem(sys.modules, "openpilot.system.hardware", hardware_module)
assert utilities._read_cpu_temp_c() == 57
def test_cpu_temp_reader_ignores_non_cpu_thermal_zones(tmp_path):
cpu_zone = tmp_path / "thermal_zone0"
cpu_zone.mkdir()
(cpu_zone / "type").write_text("cpu0-silver-usr", encoding="utf-8")
(cpu_zone / "temp").write_text("61000", encoding="utf-8")
pmic_zone = tmp_path / "thermal_zone1"
pmic_zone.mkdir()
(pmic_zone / "type").write_text("pm8998_tz", encoding="utf-8")
(pmic_zone / "temp").write_text("75000", encoding="utf-8")
assert utilities._read_cpu_temp_c(tmp_path) == 61
def test_persistent_loader_accepts_decoded_param_dict():
params = FakeParams({
utilities.DASHBOARD_PERSISTENT_STATS_PARAM: {
@@ -587,8 +651,7 @@ def test_lightweight_routes_surface_recent_drives_without_log_analysis(monkeypat
assert [drive["name"] for drive in dashboard["recentDrives"]] == ["route-new", "route-old"]
assert dashboard["lastDrive"]["model"] == "Orion"
assert dashboard["week"]["drives"] == 2
assert dashboard["favoriteModels"][0]["name"] == "Orion"
assert dashboard["favoriteModels"][0]["drives"] == 2
assert dashboard["favoriteModels"] == []
assert dashboard["analysis"]["pendingRoutes"] == 2
assert dashboard["analysis"]["batchSize"] == 2
utilities._invalidate_dashboard_cache()
@@ -948,4 +1011,5 @@ def test_stats_endpoint_keeps_existing_keys_and_adds_dashboard(monkeypatch):
assert "diskUsage" in payload
assert "driveStats" in payload
assert "softwareInfo" in payload
assert payload["softwareInfo"]["buildEnvironment"] == "Experimental"
assert payload["dashboard"]["recentDrives"] == []
+8 -10
View File
@@ -478,8 +478,8 @@ except TypeError:
]
KEYS = {
"amap1": ("amap1", "", "AMapKey1", "Amap key #1", 39),
"amap2": ("amap2", "", "AMapKey2", "Amap key #2", 39),
"amap1": ("amap1", "", "AMapKey1", "AMap / Gaode key #1", 39),
"amap2": ("amap2", "", "AMapKey2", "AMap / Gaode key #2", 39),
"public": ("public", "pk.", "MapboxPublicKey", "Public key", 80),
"secret": ("secret", "sk.", "MapboxSecretKey", "Secret key", 80),
}
@@ -5176,18 +5176,16 @@ def setup(app):
build_metadata = get_build_metadata()
short_branch = build_metadata.channel
if build_metadata.release_channel:
env = "Release"
elif short_branch in ("StarPilot-Development", "StarPilot-Testing"):
env = "Testing"
elif build_metadata.tested_channel:
env = "Staging"
if short_branch == "StarPilot":
galaxy_label = "Stable"
elif short_branch == "Dom":
galaxy_label = "Testing"
else:
env = short_branch
galaxy_label = "Experimental"
software_info = {
"branchName": build_metadata.channel,
"buildEnvironment": env,
"buildEnvironment": galaxy_label,
"changelogUrl": utilities.get_github_changelog_url(build_metadata.openpilot.git_normalized_origin, build_metadata.channel),
"commitHash": build_metadata.openpilot.git_commit,
"commitUrl": utilities.get_github_commit_url(build_metadata.openpilot.git_normalized_origin, build_metadata.openpilot.git_commit),
+46 -7
View File
@@ -1993,6 +1993,8 @@ def _recalculate_persistent_stats(stats):
model_usage = {}
for _, entry in ordered_routes:
if not bool(entry.get("analysisComplete", False)):
continue
model_name = _clean_model_label(entry.get("model", ""))
model_key = canonical_model_key(entry.get("modelKey", "")) or _model_usage_key(model_name)
if model_key:
@@ -2140,8 +2142,40 @@ def _read_uptime_seconds():
return None
def _read_cpu_temp_c():
thermal_root = Path("/sys/class/thermal")
def _normalize_temp_c(value):
try:
raw = float(value)
except (TypeError, ValueError):
return None
if raw > 1000:
raw /= 1000.0
return raw if 0 < raw < 150 else None
def _read_hardware_cpu_temps():
try:
from openpilot.system.hardware import HARDWARE
thermal_config = HARDWARE.get_thermal_config()
thermal_msg = thermal_config.get_msg()
except Exception:
return []
cpu_temps = thermal_msg.get("cpuTempC", [])
if not isinstance(cpu_temps, (list, tuple)):
cpu_temps = [cpu_temps]
return [
temp for temp in (_normalize_temp_c(value) for value in cpu_temps)
if temp is not None
]
def _read_cpu_temp_c(thermal_root=None):
if thermal_root is None:
hardware_temps = _read_hardware_cpu_temps()
if hardware_temps:
return round(max(hardware_temps))
thermal_root = Path("/sys/class/thermal")
try:
zones = sorted(thermal_root.glob("thermal_zone*/temp"))
except Exception:
@@ -2150,13 +2184,18 @@ def _read_cpu_temp_c():
values = []
for temp_path in zones:
try:
raw = float(temp_path.read_text().strip())
zone_type = temp_path.with_name("type").read_text(encoding="utf-8").strip().lower()
except Exception:
zone_type = ""
if "cpu" not in zone_type:
continue
try:
raw = temp_path.read_text().strip()
except Exception:
continue
if raw > 1000:
raw /= 1000.0
if 0 < raw < 150:
values.append(raw)
temp = _normalize_temp_c(raw)
if temp is not None:
values.append(temp)
return round(max(values)) if values else None