diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 65cf38c18a..8d553ac60f 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -782,6 +782,7 @@ class CarController(CarControllerBase): if blended_hda2: can_sends.extend(hyundaicanfd.create_steering_messages( self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0, + lka_icon=lka_icon, longitudinal_active=longitudinal_active, )) if self.long_active_ecu: @@ -792,6 +793,7 @@ class CarController(CarControllerBase): left_lane_warning, right_lane_warning, CS.msg_364, include_alerts=False, counter_mod=0xF, + fcw_opt_usm=2 if apply_steer_req or lka_icon == 3 else 1, )) if self.frame % 5 == 0: can_sends.append(hyundaicanfd.create_suppress_lfa( diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaican.py b/opendbc_repo/opendbc/car/hyundai/hyundaican.py index 5d3b333e8c..ef3d927060 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaican.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaican.py @@ -129,13 +129,13 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req, torque_fault, lkas11, sys_warning, sys_state, enabled, left_lane, right_lane, left_lane_depart, right_lane_depart, msg_364, - include_alerts=True, counter_mod=0x10): + include_alerts=True, counter_mod=0x10, fcw_opt_usm=None): bus = CanBus(CP).ECAN values = { "CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1), "CF_Lkas_LdwsLHWarning": left_lane_depart, "CF_Lkas_LdwsRHWarning": right_lane_depart, - "CF_Lkas_FcwOpt_USM": 2 if enabled else 1, + "CF_Lkas_FcwOpt_USM": (2 if enabled else 1) if fcw_opt_usm is None else fcw_opt_usm, "CR_Lkas_StrToqReq": apply_steer, "CF_Lkas_ActToi": steer_req, "CF_Lkas_ToiFlt": torque_fault, diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 2ad5d32821..fa4785d688 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -727,6 +727,45 @@ class TestHyundaiFingerprint: } <= msg_addrs_buses assert (0x364, 1) not in msg_addrs_buses + def test_palisade_telluride_hda2_aol_keeps_lkas_status_after_long_cancel(self): + fingerprint = gen_empty_fingerprint() + fingerprint[2][0x50] = 16 + car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")] + CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, fingerprint, car_fw, True, False, False, None) + controller = CarController(DBC[CP.carFingerprint], CP) + controller.frame = 1 + + hud_control = SimpleNamespace( + visualAlert=CarControl.HUDControl.VisualAlert.none, + leftLaneVisible=True, + rightLaneVisible=True, + leftLaneDepart=False, + rightLaneDepart=False, + leadDistanceBars=3, + leadVisible=False, + ) + lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 24) if i != 7} + lfa_block_msg["COUNTER"] = 0 + CS = SimpleNamespace(lfa_block_msg=lfa_block_msg, redneck_send_button=Buttons.NONE, lkas11={}, msg_364={}, + out=SimpleNamespace(vEgoRaw=5.0)) + CC = SimpleNamespace(enabled=False, latActive=True, longActive=False, + cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False)) + actuators = SimpleNamespace(longControlState=LongCtrlState.off) + adas_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], 0) + ecan_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 1) + + for steering_requested, icon, expected_status in ((True, 2, 2), (False, 1, 1)): + msgs = controller.create_can_msgs(steering_requested, 16 if steering_requested else 0, False, + 0.0, 0.0, False, hud_control, actuators, CS, CC, icon, icon) + adas_parser.update([(1, [msg for msg in msgs if msg[0] == 0x50])]) + ecan_parser.update([(1, [msg for msg in msgs if msg[0] == 0x340])]) + + assert adas_parser.vl["LKAS"]["LKA_ICON"] == icon + assert adas_parser.vl["LKAS"]["STEER_REQ"] == int(steering_requested) + assert ecan_parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status + assert ecan_parser.vl["LKAS11"]["CF_Lkas_ActToi"] == int(steering_requested) + assert not any(msg[0] == 0x364 for msg in msgs) + def test_g70_aol_uses_active_lkas_icon(self): CP = CarInterface.get_params(CAR.GENESIS_G70_2020, gen_empty_fingerprint(), [], False, False, False, None) controller = CarController(DBC[CP.carFingerprint], CP) diff --git a/starpilot/system/the_galaxy/assets/mobile/css/material.css b/starpilot/system/the_galaxy/assets/mobile/css/material.css index 5d5321c9db..b56870845a 100644 --- a/starpilot/system/the_galaxy/assets/mobile/css/material.css +++ b/starpilot/system/the_galaxy/assets/mobile/css/material.css @@ -1833,6 +1833,69 @@ button.gx-chip:hover { margin-top: var(--sp-5); } +.gx-sentry-images { + display: flex; + flex-wrap: wrap; + gap: var(--sp-2); +} + +.gx-sentry-image-button { + background: transparent; + border: 0; + border-radius: var(--radius-sm); + cursor: zoom-in; + flex: 1 1 45%; + min-width: 120px; + padding: 0; +} + +.gx-sentry-image-button img { + border-radius: var(--radius-sm); + display: block; + width: 100%; +} + +.gx-sentry-image-button:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 3px; +} + +.gx-scrim--image-viewer { + padding: var(--sp-3); +} + +.gx-image-viewer { + display: flex; + flex-direction: column; + max-height: calc(100dvh - var(--sp-6)); + max-width: min(960px, 100%); + padding: var(--sp-3); +} + +.gx-image-viewer__header { + align-items: center; + display: flex; + gap: var(--sp-2); + justify-content: space-between; + margin-bottom: var(--sp-3); +} + +.gx-image-viewer__header .gx-sheet__title { + margin: 0; + min-width: 0; + overflow-wrap: anywhere; +} + +.gx-image-viewer__image { + border-radius: var(--radius-sm); + display: block; + height: auto; + margin: auto; + max-height: calc(100dvh - 120px); + max-width: 100%; + object-fit: contain; +} + /* Native-style bottom sheet used for fullscreen player overlays. Kept as a sibling of gx-sheet so confirm dialogs keep their centered look. */ .gx-scrim--bottomsheet { diff --git a/starpilot/system/the_galaxy/assets/mobile/js/views/Sentry.js b/starpilot/system/the_galaxy/assets/mobile/js/views/Sentry.js index 1db43cb0fc..6c4e1e79eb 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/views/Sentry.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/views/Sentry.js @@ -28,14 +28,24 @@ export const Sentry = { liveBusy: false, deleteBusy: false, pushBusy: false, + selectedImage: null, } }, created() { this.poll = usePolling(() => this.loadStatus(), { interval: 5000 }) this.poll.start() }, - mounted() { this.loadParams() }, - beforeUnmount() { this.poll?.destroy() }, + mounted() { + this.loadParams() + this._onKeydown = (event) => { + if (event.key === "Escape" && this.selectedImage) this.closeImage() + } + window.addEventListener("keydown", this._onKeydown) + }, + beforeUnmount() { + this.poll?.destroy() + window.removeEventListener("keydown", this._onKeydown) + }, computed: { statusText() { return String(this.status?.state || "unknown") }, hasEvent() { return !!(this.event && this.event.eventId) }, @@ -129,6 +139,13 @@ export const Sentry = { const cacheKey = encodeURIComponent(this.liveCapture?.capturedAt || "") return cacheKey ? `${url}?t=${cacheKey}` : url }, + openImage(src, alt) { + if (!src) return + this.selectedImage = { src: String(src), alt: String(alt || "Sentry capture") } + }, + closeImage() { + this.selectedImage = null + }, async enablePush() { if (this.pushBusy) return this.pushBusy = true @@ -309,10 +326,11 @@ export const Sentry = {

No live snapshot captured yet.

@@ -341,10 +359,11 @@ export const Sentry = {

{{ event.message || 'Movement detected while parked.' }}

Power-off alerts do not include camera captures because the device is shutting down.

@@ -368,10 +387,11 @@ export const Sentry = {

{{ ev.message || 'Movement detected while parked.' }}

No camera images were available for this event.

@@ -381,6 +401,18 @@ export const Sentry = { + +
+ +
`, } diff --git a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py index c827cf35bf..8919ac2c60 100644 --- a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py +++ b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py @@ -489,6 +489,10 @@ def test_ui_all_remaining_classic_tools_native_no_embed(): assert '"/sentry": Cameras' in app sentry = _read("js/views/Sentry.js") assert "GalaxyEmbed" not in sentry and "fetch(" not in sentry + assert "selectedImage" in sentry and "openImage" in sentry and "closeImage" in sentry + assert 'class="gx-scrim gx-scrim--image-viewer"' in sentry + assert 'aria-label="Close image"' in sentry + assert 'target="_blank"' not in sentry # Navigation maps + App Keys and Tuning lateral are native tabs now. nav = _read("js/views/Navigation.js")