mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-11 02:33:51 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79b791a26b | |||
| 1c35e376e9 |
@@ -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
|
||||||
|
|||||||
@@ -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 you’re 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"),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user