Compare commits

..

2 Commits

Author SHA1 Message Date
firestar5683 79b791a26b EV6 Hotfix 2026-09-10 11:30:48 -05:00
firestar5683 1c35e376e9 uno mas lil dip 2026-09-08 21:15:28 -05:00
9 changed files with 65 additions and 273 deletions
@@ -860,7 +860,7 @@ class CarController(CarControllerBase):
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False)) longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN) lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_EV6)
lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \ lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \
if self.CP.carFingerprint in lfa_status_cars else longitudinal_active if self.CP.carFingerprint in lfa_status_cars else longitudinal_active
lka_steering_long = lka_steering and lfa_longitudinal_active lka_steering_long = lka_steering and lfa_longitudinal_active
+10 -8
View File
@@ -27,6 +27,15 @@ from openpilot.starpilot.common.testing_grounds import testing_ground
ButtonType = structs.CarState.ButtonEvent.Type ButtonType = structs.CarState.ButtonEvent.Type
Ecu = structs.CarParams.Ecu Ecu = structs.CarParams.Ecu
def get_communication_control_request(car_fingerprint):
if car_fingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and car_fingerprint != CAR.KIA_EV6:
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
# Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars # Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars
ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise) ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise)
@@ -353,14 +362,7 @@ class CarInterface(CarInterfaceBase):
params = Params() params = Params()
if communication_control is None: if communication_control is None:
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR: communication_control = get_communication_control_request(CP.carFingerprint)
# Don't use 0x80 suppress bit so we can read the ECU response.
# Use ENABLE_RX_DISABLE_TX (0x01) so the ECU can still receive from rear radars for BSM
# while blocking SCC TX.
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
else:
# 0x80 silences response for other cars (original behavior)
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===") ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
@@ -4,7 +4,7 @@ from types import SimpleNamespace
import pytest import pytest
from opendbc.can import CANPacker, CANParser from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs, uds
from opendbc.car.structs import CarControl, CarParams from opendbc.car.structs import CarControl, CarParams
from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
@@ -24,7 +24,7 @@ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY
clear_ioniq_6_torque_when_request_inactive clear_ioniq_6_torque_when_request_inactive
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
get_canfd_cruise_available get_canfd_cruise_available
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX, get_communication_control_request
from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai import hyundaican, hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \ from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
@@ -129,6 +129,14 @@ def get_test_toggles() -> SimpleNamespace:
class TestHyundaiFingerprint: class TestHyundaiFingerprint:
def test_ev6_uses_stock_hda2_communication_control_path(self):
stock_request = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x83, uds.MESSAGE_TYPE.NORMAL])
radar_keepalive_request = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL,
uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
assert get_communication_control_request(CAR.KIA_EV6) == stock_request
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
def test_carnival_hev_low_speed_torque_rate_limits(self): def test_carnival_hev_low_speed_torque_rate_limits(self):
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [], CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
False, False, False, None) False, False, False, None)
@@ -2552,9 +2560,10 @@ class TestHyundaiFingerprint:
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")] if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)] assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
def test_ioniq_6_keeps_lfa_status_when_longitudinal_is_inactive(self): @pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6])
def test_egmp_keeps_lfa_status_when_longitudinal_is_inactive(self, car):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.HYUNDAI_IONIQ_6 CP.carFingerprint = car
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING) CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = True CP.openpilotLongitudinalControl = True
@@ -36,7 +36,6 @@
--glass-border-light: rgba(255, 255, 255, 0.2); --glass-border-light: rgba(255, 255, 255, 0.2);
--glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35); --glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35);
--glass-blur: blur(4px); --glass-blur: blur(4px);
--card-scroll-bg: rgba(22, 18, 38, 0.95);
--glass-text: #f3f2f8; --glass-text: #f3f2f8;
--glass-accent: #9d72ff; --glass-accent: #9d72ff;
@@ -106,7 +105,6 @@
--surface-container: rgba(255, 255, 255, 0.75); --surface-container: rgba(255, 255, 255, 0.75);
--surface-container-high: rgba(255, 255, 255, 0.95); --surface-container-high: rgba(255, 255, 255, 0.95);
--surface-container-low: rgba(240, 243, 252, 0.75); --surface-container-low: rgba(240, 243, 252, 0.75);
--card-scroll-bg: rgba(245, 246, 252, 0.96);
--on-surface: #171526; --on-surface: #171526;
--on-surface-variant: #5f5b7d; --on-surface-variant: #5f5b7d;
@@ -188,8 +186,6 @@ body {
pointer-events: none; pointer-events: none;
transform: translateZ(0); transform: translateZ(0);
will-change: transform; will-change: transform;
isolation: isolate;
contain: strict;
} }
#galaxy-bg::before { #galaxy-bg::before {
@@ -208,6 +204,7 @@ body {
opacity: 0.5; opacity: 0.5;
will-change: transform; will-change: transform;
transform: translate3d(var(--gx-para-x), 0, 0); transform: translate3d(var(--gx-para-x), 0, 0);
animation: galaxy-drift-smooth 160s linear infinite;
} }
#galaxy-bg::after { #galaxy-bg::after {
@@ -224,6 +221,7 @@ body {
opacity: 0.6; opacity: 0.6;
will-change: transform; will-change: transform;
transform: translate3d(var(--gx-para-x), 0, 0); transform: translate3d(var(--gx-para-x), 0, 0);
animation: galaxy-drift-smooth 100s linear infinite;
} }
#galaxy-bg .galaxy-hero { #galaxy-bg .galaxy-hero {
@@ -236,6 +234,11 @@ body {
animation: galaxy-twinkle 6s ease-in-out infinite alternate; animation: galaxy-twinkle 6s ease-in-out infinite alternate;
} }
@keyframes galaxy-drift-smooth {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(0, -400px, 0); }
}
@keyframes galaxy-twinkle { @keyframes galaxy-twinkle {
0%, 100% { opacity: 0.3; transform: scale(0.9); } 0%, 100% { opacity: 0.3; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.2); } 50% { opacity: 1; transform: scale(1.2); }
@@ -247,11 +250,6 @@ body {
} }
} }
/* Keep decorative animation out of modal backdrops and drawer content. */
body:has(.gx-scrim, .gx-underlay, .gx-drawer.open) #galaxy-bg .galaxy-hero {
display: none;
}
[data-theme="light"] #galaxy-bg { [data-theme="light"] #galaxy-bg {
background-color: #eef1fb; background-color: #eef1fb;
background-image: background-image:
@@ -308,8 +306,6 @@ ul { list-style: none; margin: 0; padding: 0; }
position: sticky; position: sticky;
top: 0; top: 0;
z-index: var(--z-appbar); z-index: var(--z-appbar);
transform: translateZ(0);
isolation: isolate;
} }
.gx-appbar__pill { .gx-appbar__pill {
@@ -329,8 +325,7 @@ ul { list-style: none; margin: 0; padding: 0; }
transition: background-color var(--motion-fast), border-color var(--motion-fast); transition: background-color var(--motion-fast), border-color var(--motion-fast);
} }
.gx-appbar .gx-appbar__back, .gx-appbar__back {
.gx-appbar .gx-theme-toggle {
background: var(--glass-bg); background: var(--glass-bg);
backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
@@ -344,8 +339,7 @@ ul { list-style: none; margin: 0; padding: 0; }
transition: transform var(--motion-fast), background-color var(--motion-fast); transition: transform var(--motion-fast), background-color var(--motion-fast);
} }
.gx-appbar .gx-appbar__back:active, .gx-appbar__back:active { transform: scale(0.92); }
.gx-appbar .gx-theme-toggle:active { transform: scale(0.92); }
.gx-appbar__title { .gx-appbar__title {
background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%); background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%);
@@ -364,24 +358,6 @@ ul { list-style: none; margin: 0; padding: 0; }
vertical-align: middle; vertical-align: middle;
} }
.gx-appbar__home {
display: inline-flex;
align-items: center;
cursor: pointer;
padding: 2px 8px;
border-radius: var(--radius-full);
transition: background-color var(--motion-fast), transform var(--motion-fast);
}
.gx-appbar__home:hover { background-color: var(--glass-bg-hover); }
.gx-appbar__home:active { transform: scale(0.96); }
.gx-appbar__home:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.gx-appbar__right { .gx-appbar__right {
align-items: center; align-items: center;
display: flex; display: flex;
@@ -435,6 +411,27 @@ ul { list-style: none; margin: 0; padding: 0; }
.gx-back-btn { display: none; } .gx-back-btn { display: none; }
.gx-menu-btn { display: inline-flex; } .gx-menu-btn { display: inline-flex; }
.gx-theme-toggle {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 50%;
box-shadow: var(--glass-shadow);
color: var(--glass-text);
flex: none;
height: var(--touch-target);
width: var(--touch-target);
transition: transform var(--motion-fast), background-color var(--motion-fast), border-color var(--motion-fast);
}
.gx-theme-toggle:active { transform: scale(0.92); }
[data-theme="light"] .gx-theme-toggle {
border: 1.5px solid var(--primary);
box-shadow: 0 2px 8px rgba(120, 73, 232, 0.2);
}
.gx-searchwrap { .gx-searchwrap {
align-items: center; align-items: center;
display: flex; display: flex;
@@ -516,10 +513,7 @@ ul { list-style: none; margin: 0; padding: 0; }
position: fixed; position: fixed;
bottom: calc(18px + env(safe-area-inset-bottom, 0px)); bottom: calc(18px + env(safe-area-inset-bottom, 0px));
left: 50%; left: 50%;
transform: translateX(-50%) translateZ(0); transform: translateX(-50%);
will-change: transform;
isolation: isolate;
contain: layout style;
z-index: 1000; z-index: 1000;
display: flex; display: flex;
@@ -595,37 +589,7 @@ ul { list-style: none; margin: 0; padding: 0; }
box-shadow: var(--elev-1); box-shadow: var(--elev-1);
margin-bottom: var(--sp-4); margin-bottom: var(--sp-4);
overflow: hidden; overflow: hidden;
transition: transform var(--motion-fast), box-shadow var(--motion-fast), background-color 0.15s ease-out; transition: transform var(--motion-fast), box-shadow var(--motion-fast);
}
/* Surgical content-visibility for top-level non-collapsible cards */
.dh-card:not(.dh-stat),
.gx-content > div > .gx-card {
content-visibility: auto;
contain-intrinsic-size: 280px;
contain-intrinsic-size: auto 280px;
}
.dh-stat {
content-visibility: auto;
contain-intrinsic-size: 110px;
contain-intrinsic-size: auto 110px;
}
/* Disarm containment on collapsible sections and nested cards */
.gx-card:has(.gx-section__body),
.gx-card .gx-card,
.gx-tree-node .gx-card {
content-visibility: visible !important;
contain-intrinsic-size: none !important;
}
/* Velocity-Adaptive Glass: bypass blur on scrolling cards with contrast boost */
body.is-scrolling .gx-card,
body.is-scrolling .gx-tile {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
background: var(--card-scroll-bg) !important;
} }
.gx-unit-note { .gx-unit-note {
@@ -1495,9 +1459,6 @@ input[type="color"].gx-color {
.gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); } .gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); }
.gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; } .gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; }
/* Keep the underlay (and star suppression) until the drawer finishes closing. */
.gx-underlay.gx-fade-leave-active { transition: opacity var(--motion-slow); }
.gx-slide-enter-active, .gx-slide-leave-active { .gx-slide-enter-active, .gx-slide-leave-active {
transition: transform var(--motion-base), opacity var(--motion-base); transition: transform var(--motion-base), opacity var(--motion-base);
} }
@@ -90,71 +90,6 @@ app.mount("#galaxy-app")
initRouter() initRouter()
// Disable card blur during document scrolling.
;(() => {
let timer = null
let scrollEnded = true
const touches = new Set()
const nativeScrollEnd = "onscrollend" in document
const body = document.body
const isModalEvent = (e) => {
const t = e.target
return t instanceof Element && t.closest(".gx-scrim, .gx-sheet, .gx-dialog, .gx-drawer") !== null
}
const setScrolling = (active) => {
if (active) {
if (!body.classList.contains("is-scrolling")) body.classList.add("is-scrolling")
} else if (!touches.size && body.classList.contains("is-scrolling")) {
body.classList.remove("is-scrolling")
}
}
const scheduleRestore = () => {
clearTimeout(timer)
const scrollY = window.scrollY
// Finger release and completion notifications can precede the last movement.
// Keep glass disabled until the scroll position has also settled.
timer = setTimeout(() => {
if (window.scrollY !== scrollY) scheduleRestore()
else setScrolling(false)
}, 120)
}
document.addEventListener("scroll", () => {
scrollEnded = false
setScrolling(true)
clearTimeout(timer)
if (!nativeScrollEnd) scheduleRestore()
}, { passive: true })
document.addEventListener("scrollend", () => {
scrollEnded = true
scheduleRestore()
}, { passive: true })
window.addEventListener("touchstart", (e) => {
if (isModalEvent(e)) return
for (const touch of e.changedTouches) touches.add(touch.identifier)
}, { passive: true })
const releaseTouches = (e) => {
for (const touch of e.changedTouches) touches.delete(touch.identifier)
if (!touches.size && (scrollEnded || !nativeScrollEnd)) scheduleRestore()
}
window.addEventListener("touchend", releaseTouches, { passive: true })
window.addEventListener("touchcancel", releaseTouches, { passive: true })
window.addEventListener("hashchange", () => {
scrollEnded = true
touches.clear()
clearTimeout(timer)
body.classList.remove("is-scrolling")
}, { passive: true })
})()
// Layer 2: Ambient Hero Stars Spawner
;(() => { ;(() => {
const bg = document.getElementById("galaxy-bg") const bg = document.getElementById("galaxy-bg")
if (!bg) return if (!bg) return
@@ -81,9 +81,6 @@ export const AppShell = {
bottomNavTo(item) { bottomNavTo(item) {
navigate(item.link) navigate(item.link)
}, },
goHome() {
navigate("/")
},
isActive(link) { isActive(link) {
return this.activePath === link || (link !== "/" && this.activePath.startsWith(link)) return this.activePath === link || (link !== "/" && this.activePath.startsWith(link))
}, },
@@ -105,10 +102,7 @@ export const AppShell = {
<button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true"> <button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true">
<i class="bi bi-list"></i> <i class="bi bi-list"></i>
</button> </button>
<span class="gx-appbar__home" role="button" tabindex="0" <span class="gx-appbar__title">Galaxy</span>
aria-label="Galaxy home" @click="goHome" @keydown.enter="goHome" @keydown.space.prevent="goHome">
<span class="gx-appbar__title">Galaxy</span>
</span>
<div class="gx-searchwrap"> <div class="gx-searchwrap">
<input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..." <input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..."
v-model="search" aria-label="Search toggles" /> v-model="search" aria-label="Search toggles" />
@@ -17,9 +17,9 @@ export const DevModeBanner = {
<i class="bi bi-shield-lock gx-alert__icon"></i> <i class="bi bi-shield-lock gx-alert__icon"></i>
<div class="gx-alert__body"> <div class="gx-alert__body">
<strong>{{ hiddenCount }} advanced setting{{ hiddenCount !== 1 ? "s" : "" }} hidden.</strong> <strong>{{ hiddenCount }} advanced setting{{ hiddenCount !== 1 ? "s" : "" }} hidden.</strong>
<span>Want more advanced features or missing a few toggles? Enable Developer Mode whenever youre ready.</span> <span>Advanced features are tucked away until you enable Developer Mode.</span>
</div> </div>
<button type="button" class="gx-btn gx-btn--tonal" @click="unlock">Go to Developer Tab</button> <button type="button" class="gx-btn gx-btn--tonal" @click="unlock">Enable Developer Mode</button>
</div> </div>
`, `,
} }
@@ -277,7 +277,7 @@ def test_ui_eliminates_slider_toggle_flicker():
def test_ui_developer_mode_banner_offers_unlock(): def test_ui_developer_mode_banner_offers_unlock():
banner = _read("js/components/DevModeBanner.js") banner = _read("js/components/DevModeBanner.js")
assert "Go to Developer Tab" in banner assert "Enable Developer Mode" in banner
assert 'navigate("/settings/developer")' in banner assert 'navigate("/settings/developer")' in banner
assert "advanced setting" in banner assert "advanced setting" in banner
@@ -295,8 +295,6 @@ def test_ui_has_bottom_navigation_and_drawer():
assert "gx-appbar" in shell assert "gx-appbar" in shell
assert "Search toggles" in shell assert "Search toggles" in shell
assert ">Galaxy</span>" in shell assert ">Galaxy</span>" in shell
assert "gx-appbar__home" in shell
assert "goHome" in shell and 'navigate("/")' in shell
def test_ui_search_visible_on_mobile_and_content_full_width(): def test_ui_search_visible_on_mobile_and_content_full_width():
@@ -540,110 +538,6 @@ def test_ui_mobile_polish_regressions():
assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer
@pytest.mark.parametrize("native_scrollend", [False, True])
def test_scroll_coordinator_gesture_lifecycle(native_scrollend):
node = _node_exe()
if node is None:
pytest.skip("no node.js runtime available")
script = r"""
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const native = process.argv[2] === 'true';
const source = fs.readFileSync(process.argv[1], 'utf8');
const handlers = { document: {}, window: {} };
const classes = new Set();
const timers = new Map();
let nextTimer = 0;
const window = {
scrollY: 0,
addEventListener: (name, fn) => { handlers.window[name] = fn; },
};
class Element {
constructor(modal = false) { this.modal = modal; }
closest() { return this.modal ? this : null; }
}
const document = {
body: { classList: {
contains: k => classes.has(k), add: k => classes.add(k), remove: k => classes.delete(k),
} },
addEventListener: (name, fn) => { handlers.document[name] = fn; },
};
if (native) document.onscrollend = null;
vm.runInNewContext(source.slice(source.indexOf('// Disable card blur'), source.indexOf('// Layer 2')), {
document, Element,
window,
setTimeout: fn => { timers.set(++nextTimer, fn); return nextTimer; },
clearTimeout: id => timers.delete(id),
});
const fire = (scope, name, ids = [], modal = false) => handlers[scope][name]?.({
target: new Element(modal), changedTouches: ids.map(identifier => ({ identifier })),
});
const tick = () => { const pending = [...timers.values()]; timers.clear(); pending.forEach(fn => fn()); };
const active = () => classes.has('is-scrolling');
// Inertial movement can continue between delivered scroll events after release.
fire('window', 'touchstart', [10]);
fire('document', 'scroll');
fire('window', 'touchend', [10]);
if (native) fire('document', 'scrollend');
window.scrollY = 100;
tick();
assert.equal(active(), true, 'finger release must not end momentum scrolling');
window.scrollY = 160;
tick();
assert.equal(active(), true, 'continued movement must keep blur disabled');
tick();
assert.equal(active(), false, 'restore after completion and a stable position');
fire('document', 'scrollend');
fire('window', 'wheel');
assert.equal(active(), false, 'wheel without document movement');
fire('window', 'touchstart', [1, 2]);
fire('document', 'scroll');
fire('window', 'touchend', [1]);
fire('window', 'touchstart', [3], true);
fire('window', 'touchend', [3], true);
tick();
assert.equal(active(), true, 'remaining page finger holds blur disabled');
fire('window', 'touchcancel', [2]);
tick();
assert.equal(active(), native, 'native completion must be authoritative');
fire('document', 'scrollend');
tick();
assert.equal(active(), false);
// More scroll events after a completion signal invalidate its pending restore.
fire('document', 'scroll');
fire('document', 'scrollend');
fire('document', 'scroll');
tick();
assert.equal(active(), native, 'new scrolling cancels the previous native completion');
fire('document', 'scrollend');
tick();
assert.equal(active(), false);
fire('window', 'touchstart', [4]);
fire('document', 'scroll');
fire('document', 'scrollend');
assert.equal(active(), true, 'hold survives completion');
fire('window', 'touchend', [4]);
tick();
assert.equal(active(), false, 'release after completion cannot leave state stuck');
fire('document', 'scroll');
fire('window', 'hashchange');
tick();
assert.equal(active(), false, 'navigation clears pending gesture');
fire('document', 'scroll');
tick();
assert.equal(active(), native, 'fallback only on unsupported browsers');
fire('document', 'scrollend');
tick();
assert.equal(active(), false);
"""
result = subprocess.run(
[node, "-e", script, str(UI_ROOT / "js/app.js"), str(native_scrollend).lower()],
capture_output=True, text=True,
)
assert result.returncode == 0, result.stdout + result.stderr
def _node_exe(): def _node_exe():
candidates = [ candidates = [
shutil.which("node"), shutil.which("node"),
+5 -8
View File
@@ -7,12 +7,9 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.pid import PIDController from openpilot.common.pid import PIDController
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
# comma 3/3X (tici/tizi) run a more aggressive, cooler-targeting curve than comma 4 (mici) # raise fan setpoint on tici/tizi to reduce noise
IS_MICI = HARDWARE.get_device_type() == "mici" # after raising LMH threshold in AGNOS 18.1 to prevent CPU throttling
OFFSET = 0 if IS_MICI else -5 OFFSET = 0 if HARDWARE.get_device_type() == "mici" else 5
K_P = 0 if IS_MICI else 1.0
FF_LOW = 60.0 if IS_MICI else 55.0
FF_HIGH = 100.0 if IS_MICI else 80.0
class BaseFanController(ABC): class BaseFanController(ABC):
@abstractmethod @abstractmethod
@@ -26,7 +23,7 @@ class TiciFanController(BaseFanController):
cloudlog.info("Setting up TICI fan handler") cloudlog.info("Setting up TICI fan handler")
self.last_ignition = False self.last_ignition = False
self.controller = PIDController(k_p=K_P, k_i=4e-3, rate=(1 / DT_HW)) self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW))
def update(self, cur_temp: float, ignition: bool) -> int: def update(self, cur_temp: float, ignition: bool) -> int:
self.controller.pos_limit = 100 if ignition else 30 self.controller.pos_limit = 100 if ignition else 30
@@ -38,7 +35,7 @@ class TiciFanController(BaseFanController):
error = cur_temp - (75 + OFFSET) error = cur_temp - (75 + OFFSET)
fan_pwr_out = int(self.controller.update( fan_pwr_out = int(self.controller.update(
error=error, error=error,
feedforward=np.interp(cur_temp, [FF_LOW, FF_HIGH], [0, 100]) feedforward=np.interp(cur_temp, [60.0 + OFFSET, 100.0 + OFFSET], [0, 100])
)) ))
self.last_ignition = ignition self.last_ignition = ignition