Skip to content
Merged

web #491

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion openpilot/selfdrive/carrot/web/css/generated/settings.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion openpilot/selfdrive/carrot/web/css/generated/tools.css

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions openpilot/selfdrive/carrot/web/generated/asset-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"kind": "bundle",
"source": "src/entries/drive.js",
"path": "js/generated/drive.js",
"hash": "2e11c9e9141b9396373097180dcaed046a405b962112f0c26e025d768d799c20"
"hash": "943d8a1b246ecaec44eef755b993462d000377aedc1dddf4a339645876b1170f"
},
{
"id": "replay.runtime",
Expand All @@ -27,14 +27,14 @@
"kind": "bundle",
"source": "src/entries/settings.js",
"path": "js/generated/settings.js",
"hash": "1020feb80e629f5607a61390b980f36577be8ac01d1a05e2389a1fcaf89a162a"
"hash": "3a942b6194bd1ce7e08f30b873fd6cabb3bdd94cc96dcd8c7c4c96d900bb3071"
},
{
"id": "tools.runtime",
"kind": "bundle",
"source": "src/entries/tools.js",
"path": "js/generated/tools.js",
"hash": "7a2c0e3cf0ae6e063fa5540007d679ccca2cad5c6339bbae7a184a3d9e6af98e"
"hash": "9f1d6a68db1f27b849ca2f7b7550b39f94e84bbe5ec01ce6969f4a2c6e564427"
},
{
"id": "logs.runtime",
Expand Down Expand Up @@ -90,7 +90,7 @@
"kind": "bundle",
"source": "src/entries/design_system.css",
"path": "css/generated/design-system.css",
"hash": "68169a78e3fa9e18be4d2ca3f4d9c97a4281df52e27d6fe1396c8413bd737211"
"hash": "daeefa6282ba71d465e91f6622c35bef55a64b345a1e67367a85b1ebfecb1713"
},
{
"id": "drive.styles",
Expand All @@ -111,14 +111,14 @@
"kind": "bundle",
"source": "src/entries/settings.css",
"path": "css/generated/settings.css",
"hash": "c0aa3fae081eb2f72fcf9195a5392371c1029cb25731788e609c177c9cb9f208"
"hash": "fd8e6f6f2f156797c29bbcac562b66dcff94023353a778db00036183aa5059ad"
},
{
"id": "tools.styles",
"kind": "bundle",
"source": "src/entries/tools.css",
"path": "css/generated/tools.css",
"hash": "e11a953164b6be5134ff1920f7f6dd8541f28255bdde39fdd54ea1886e3a8c2d"
"hash": "e6e2426c73eace81c6cd5d1d08e519c7d2223869c0609fc72ec58bf1ac51f07a"
},
{
"id": "logs.styles",
Expand Down
39 changes: 21 additions & 18 deletions openpilot/selfdrive/carrot/web/js/generated/drive.js

Large diffs are not rendered by default.

32 changes: 16 additions & 16 deletions openpilot/selfdrive/carrot/web/js/generated/settings.js

Large diffs are not rendered by default.

166 changes: 94 additions & 72 deletions openpilot/selfdrive/carrot/web/js/generated/tools.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function renderSshKeysRow(statusOrUsername, fallbackHasKeys) {
],
});
return `
<div class="setting device-setting">
<div class="setting device-setting device-setting--ssh">
<div class="settingTop">
<div>
<div class="title">${escapeHtml(getUIText("ssh_keys", "SSH Keys"))}</div>
Expand Down
8 changes: 8 additions & 0 deletions openpilot/selfdrive/carrot/web/js/realtime/vision_raw.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ function drivingHudUpdateFromCarPayload(j) {
gear: j.gear,
gearStep: j.gearStep,
lfaActive: j.lfaActive,
laneModeRequested: j.laneModeRequested,
laneModePlanned: j.laneModePlanned,
steeringAngleDeg: j.steeringAngleDeg,
aEgo: j.aEgo,
steerOutput: j.steerOutput,
Expand All @@ -435,6 +437,9 @@ function drivingHudUpdateFromCarPayload(j) {
...basePayload,
evActive: j.evActive === true,
activeLaneLine: j.activeLaneLine == null ? null : j.activeLaneLine === true,
laneModeRequested: j.laneModeRequested == null ? null : j.laneModeRequested === true,
laneModePlanned: j.laneModePlanned == null ? null : j.laneModePlanned === true,
laneModePresentation: j.laneModePresentation ?? null,
cruiseOverride: j.cruiseOverride && typeof j.cruiseOverride === "object"
? j.cruiseOverride
: null,
Expand Down Expand Up @@ -752,6 +757,9 @@ function deriveCompactHudPayload(state) {
...payload,
evActive: vehiclePayload.evActive === true,
activeLaneLine: vehiclePayload.activeLaneLine == null ? null : vehiclePayload.activeLaneLine === true,
laneModeRequested: vehiclePayload.laneModeRequested ?? null,
laneModePlanned: vehiclePayload.laneModePlanned ?? null,
laneModePresentation: vehiclePayload.laneModePresentation ?? null,
cruiseOverride: vehiclePayload.cruiseOverride ?? null,
trafficState: Number.isFinite(trafficState) ? trafficState : 0,
drivingMode: vehiclePayload.drivingMode ?? null,
Expand Down
9 changes: 9 additions & 0 deletions openpilot/selfdrive/carrot/web/js/translations/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,15 @@ window.CarrotTranslations.register("en", {
web_settings_hud: "HUD",
web_settings_drive_layout: "Screen layout",
web_settings_navigation: "Carrot Navi",
web_navi_map_type: "Map type",
web_navi_map_type_desc: "Choose the Carrot Navi map background.",
web_navi_map_type_normal: "Normal mode",
web_navi_map_type_satellite: "Satellite mode",
web_navi_map_theme: "Map theme",
web_navi_map_theme_desc: "Choose the color theme for routes and crossroad views.",
web_navi_map_theme_dark: "Dark",
web_navi_map_theme_light: "Light",
web_navi_map_settings_unavailable: "Carrot Navi map settings are unavailable.",
web_settings_upload: "Web upload",
web_upload_url: "Upload server",
web_upload_url_desc: "HTTPS API address; sessions are created automatically",
Expand Down
9 changes: 9 additions & 0 deletions openpilot/selfdrive/carrot/web/js/translations/ko.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,15 @@ window.CarrotTranslations.register("ko", {
web_settings_hud: "HUD",
web_settings_drive_layout: "화면 구성",
web_settings_navigation: "당근네비",
web_navi_map_type: "지도 종류",
web_navi_map_type_desc: "당근네비 지도 배경을 선택합니다.",
web_navi_map_type_normal: "일반 모드",
web_navi_map_type_satellite: "위성 모드",
web_navi_map_theme: "지도 테마",
web_navi_map_theme_desc: "지도 경로와 교차로 화면의 색상 테마를 선택합니다.",
web_navi_map_theme_dark: "다크",
web_navi_map_theme_light: "라이트",
web_navi_map_settings_unavailable: "당근네비 지도 설정을 불러올 수 없습니다.",
web_settings_upload: "웹 업로드",
web_upload_url: "업로드 서버",
web_upload_url_desc: "자동 세션으로 로그를 전송할 HTTPS API 주소",
Expand Down
9 changes: 9 additions & 0 deletions openpilot/selfdrive/carrot/web/js/translations/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,15 @@ window.CarrotTranslations.register("zh", {
web_settings_hud: "HUD",
web_settings_drive_layout: "画面布局",
web_settings_navigation: "Carrot 导航",
web_navi_map_type: "地图类型",
web_navi_map_type_desc: "选择 Carrot 导航的地图背景。",
web_navi_map_type_normal: "普通模式",
web_navi_map_type_satellite: "卫星模式",
web_navi_map_theme: "地图主题",
web_navi_map_theme_desc: "选择路线和交叉路口画面的颜色主题。",
web_navi_map_theme_dark: "深色",
web_navi_map_theme_light: "浅色",
web_navi_map_settings_unavailable: "无法加载 Carrot 导航地图设置。",
web_settings_upload: "Web upload",
web_upload_url: "Upload server",
web_upload_url_desc: "HTTPS API address; sessions are created automatically",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
"use strict";

import {
resolveLaneModeFields,
resolveLaneModeState,
} from "../lane_mode.js";

export { resolveLaneModeState };

function finite(value) {
if (value === null || value === undefined || value === "") return null;
const number = Number(value);
Expand Down Expand Up @@ -145,12 +152,18 @@ export function createCruiseOverrideHold(options = {}) {
// Keep the cluster-only fields in one shared rule so live and replay cannot
// silently drop them at that boundary.
export function withVehicleHudFields(payload = {}, source = {}) {
const laneMode = resolveLaneModeFields({
requested: source.laneModeRequested,
planned: source.laneModePlanned,
controlled: source.activeLaneLine,
});
return {
...payload,
evActive: source.evActive === true,
activeLaneLine: source.activeLaneLine == null
? null
: source.activeLaneLine === true,
activeLaneLine: laneMode.controlled,
laneModeRequested: laneMode.requested,
laneModePlanned: laneMode.planned,
laneModePresentation: laneMode.presentation,
cruiseOverride: cruiseOverridePayload(source.cruiseOverride),
trafficState: trafficSignalState(source.trafficState ?? payload.trafficState),
drivingMode: drivingModeState(source.drivingMode ?? payload.drivingMode),
Expand All @@ -164,6 +177,9 @@ export function vehicleHudSignature(payload = {}) {
return [
payload.evActive === true ? 1 : 0,
payload.activeLaneLine == null ? "-" : (payload.activeLaneLine === true ? 1 : 0),
payload.laneModeRequested == null ? "-" : (payload.laneModeRequested === true ? 1 : 0),
payload.laneModePlanned == null ? "-" : (payload.laneModePlanned === true ? 1 : 0),
payload.laneModePresentation ?? "-",
override?.kph ?? "-",
override?.label ?? "-",
override?.mode ?? "-",
Expand Down Expand Up @@ -267,19 +283,20 @@ export function deriveVehicleHudPayload(state = {}) {
? Math.round(rawGearStep)
: null;

// Cluster parity: EV telltale (carState.evModeValid & evModeActive) and the
// LFA lane-line lateral mode (controlsState.activeLaneLine → green lane wings).
// Keep user intent, planner selection and final control activation separate.
// They update on different service clocks in both live and replay.
const evActive = Boolean(carState.evModeValid) && Boolean(carState.evModeActive);
const activeLaneLine = controlsState.activeLaneLine == null
? null
: Boolean(controlsState.activeLaneLine);
const laneMode = resolveLaneModeState(state);
const latActive = optionalBoolean(carControl.latActive);

return {
gear,
gearStep,
evActive,
activeLaneLine,
activeLaneLine: laneMode.controlled,
laneModeRequested: laneMode.requested,
laneModePlanned: laneMode.planned,
laneModePresentation: laneMode.presentation,
cruiseOverride: deriveCruiseOverride(state),
trafficState: resolveTrafficState(state),
drivingMode: drivingModeState(state.longitudinalPlan?.myDrivingMode),
Expand All @@ -303,6 +320,7 @@ export function deriveVehicleHudPayload(state = {}) {

export const CarrotHudDataBridge = Object.freeze({
deriveVehicleHudPayload,
resolveLaneModeState,
deriveCruiseOverride,
createCruiseOverrideHold,
resolveTrafficState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function own(object, key) {
function selectedSourceState(state = {}) {
const carState = state.carState || {};
const controlsState = state.controlsState || {};
const lateralPlan = state.lateralPlan || {};
const longitudinalPlan = state.longitudinalPlan || {};
const carrotMan = state.carrotMan || {};
const selfdriveState = state.selfdriveState || {};
Expand All @@ -15,6 +16,7 @@ function selectedSourceState(state = {}) {
services: {
carState: own(state, "carState"),
controlsState: own(state, "controlsState"),
lateralPlan: own(state, "lateralPlan"),
longitudinalPlan: own(state, "longitudinalPlan"),
carrotMan: own(state, "carrotMan"),
selfdriveState: own(state, "selfdriveState"),
Expand All @@ -27,12 +29,16 @@ function selectedSourceState(state = {}) {
evModeValid: carState.evModeValid ?? null,
evModeActive: carState.evModeActive ?? null,
gearShifter: carState.gearShifter ?? null,
useLaneLineSpeed: carState.useLaneLineSpeed ?? null,
},
controlsState: {
enabled: controlsState.enabled ?? null,
vCruiseCluster: controlsState.vCruiseCluster ?? null,
activeLaneLine: controlsState.activeLaneLine ?? null,
},
lateralPlan: {
useLaneLines: lateralPlan.useLaneLines ?? null,
},
longitudinalPlan: {
cruiseTarget: longitudinalPlan.cruiseTarget ?? null,
trafficState: longitudinalPlan.trafficState ?? null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createClock } from "./widgets/clock.js";
import { createDeviceTemp } from "./widgets/device_temp.js";
import { createWifiIcon } from "./widgets/wifi_icon.js";
import { createLfaIcon } from "./widgets/lfa_icon.js";
import { resolveLaneModeFields } from "../lane_mode.js";
import { createAccelGauge } from "./widgets/accel_gauge.js";
import { createSteerGauge } from "./widgets/steer_gauge.js";
import { createTurnSignal } from "./widgets/turn_signal.js";
Expand Down Expand Up @@ -53,6 +54,11 @@ export function mapPayload(p = {}) {
: (p.gear == null ? null : String(p.gear).trim().toUpperCase().slice(0, 2));
const override = p.cruiseOverride && typeof p.cruiseOverride === "object" ? p.cruiseOverride : null;
const overrideKph = override != null ? num(override.kph) : null;
const laneMode = resolveLaneModeFields({
requested: p.laneModeRequested,
planned: p.laneModePlanned,
controlled: p.activeLaneLine,
});
return {
speed: toUnit(speed),
// 크루즈 off면 0/음수로 오므로 숨김
Expand All @@ -62,7 +68,10 @@ export function mapPayload(p = {}) {
speedLimit: limit != null && limit > 0 ? toUnit(limit) : null,
// 클러스터 패리티: EV 텔테일 / LFA 레인 초록 날개 / 크루즈 오버라이드(감속=주황·eco=초록)
evActive: p.evActive === true,
activeLaneLine: p.activeLaneLine === true,
activeLaneLine: laneMode.controlled,
laneModeRequested: laneMode.requested,
laneModePlanned: laneMode.planned,
laneModePresentation: laneMode.presentation,
cruiseOverride: overrideKph != null && overrideKph > 0
? { kph: toUnit(overrideKph), label: override.label == null ? "" : String(override.label), mode: num(override.mode) ?? 0 }
: null,
Expand Down Expand Up @@ -175,8 +184,6 @@ export function createHudOverlay(doc) {
data.speedLimit != null && data.speedLimit > 0,
data.leftBlinker,
data.rightBlinker,
// 레인 날개는 LFA 폭을 바꾸므로 등장/소멸 시 재배치 판정이 필요하다.
data.activeLaneLine,
data.fuelGauge != null && data.fuelGauge > 0 && data.fuelGauge <= 1,
data.ureaGauge != null && data.ureaGauge > 0 && data.ureaGauge <= 1,
].join(":");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,26 +119,31 @@ html:not([data-carrot-mini-hud="1"]) #carrotMiniHud { display:none !important; }
.chud-tlight-halo{opacity:.28}
.chud-tlight-dot{stroke:rgba(255,255,255,.85);stroke-width:3}

/* LFA 당근 — 클러스터 동작: 활성 풀컬러 / 비활성 회색 + 조향각 회전 */
.chud-lfa{position:relative;display:flex;align-items:center;justify-content:center}
/* 레인 날개(폭 2x)가 뜰 때만 그 오버행(각 변 휠 절반)을 패딩으로 예약 →
* 옆 위젯과 겹치거나 좌측으로 삐져나가지 않는다(휠 위치는 중앙 유지). */
.chud-lfa.has-lane{padding-inline:clamp(14px,2.8cqw,28px)}
.chud-lfa-img{position:relative;z-index:1;height:clamp(28px,5.6cqw,56px);width:clamp(28px,5.6cqw,56px);display:block;object-fit:contain;
/* LFA 아이콘의 flex 점유 폭은 모드와 무관하게 휠 크기로 고정한다.
* 2x 레인 날개는 absolute overflow로만 그려 옆 Wi-Fi/시계가 움직이지 않는다. */
.chud-lfa{position:relative;display:flex;align-items:center;justify-content:center;
--chud-lfa-size:clamp(28px,5.6cqw,56px);--chud-lfa-lane-width:clamp(56px,11.2cqw,112px);
--chud-lfa-armed-opacity:.48;--chud-lfa-active-opacity:.78;
width:var(--chud-lfa-size);height:var(--chud-lfa-size);flex:0 0 auto}
.chud-lfa-img{position:relative;z-index:1;height:var(--chud-lfa-size);width:var(--chud-lfa-size);display:block;object-fit:contain;
transform-origin:50% 50%;transition:transform .12s linear,filter .2s,opacity .2s;
filter:drop-shadow(0 2px 5px rgba(0,0,0,.55))}
.chud-lfa:not(.is-active) .chud-lfa-img{opacity:.5;filter:grayscale(.7) drop-shadow(0 2px 5px rgba(0,0,0,.55))}
/* 레인 초록 날개(carrot_wheel_lane 실루엣). 폭 2x·높이=휠(cluster LFA_LANE_ICON_WIDTH_SCALE=2),
* 휠 뒤에 깔리고 좌우로 삐져나온다. 색=배경(활성 초록/비활성 muted), 모양=mask(JS가 URL 주입). */
.chud-lfa-lane{position:absolute;left:50%;top:50%;
transform:translate(-50%,-56%);pointer-events:none;z-index:0;
width:clamp(56px,11.2cqw,112px);height:clamp(28px,5.6cqw,56px);
background-color:var(--chud-muted);
width:var(--chud-lfa-lane-width);height:var(--chud-lfa-size);
background-color:var(--chud-muted);opacity:0;visibility:hidden;
-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
-webkit-mask-position:center;mask-position:center;
-webkit-mask-size:contain;mask-size:contain;
filter:drop-shadow(0 2px 5px rgba(0,0,0,.5))}
.chud-lfa.is-active .chud-lfa-lane{background-color:var(--chud-carrot)}
filter:drop-shadow(0 2px 5px rgba(0,0,0,.5));
transition:opacity .18s ease-out,background-color .18s ease-out,visibility 0s linear .18s}
.chud-lfa.has-lane .chud-lfa-lane{opacity:var(--chud-lfa-armed-opacity);visibility:visible;transition-delay:0s}
.chud-lfa.is-lane-active .chud-lfa-lane{opacity:var(--chud-lfa-active-opacity)}
.chud-lfa.is-lane-active.is-active .chud-lfa-lane{background-color:var(--chud-carrot);opacity:1}
@media (prefers-reduced-motion:reduce){.chud-lfa-lane{transition:none}}

/* WiFi */
.chud-wifi{height:clamp(24px,4.8cqw,48px);width:clamp(24px,4.8cqw,48px);display:block;color:#fff;
Expand Down Expand Up @@ -196,9 +201,7 @@ html:not([data-carrot-mini-hud="1"]) #carrotMiniHud { display:none !important; }
* 커진 결과가 다른 존을 침범하면 layout.js가 낮은 우선순위부터 제거한다. */
@container chud (max-width:520px){
.chud-gauge,.chud-level{width:clamp(44px,16cqw,64px)}
.chud-lfa-img{width:clamp(32px,12cqw,46px);height:clamp(32px,12cqw,46px)}
.chud-lfa-lane{width:clamp(64px,24cqw,92px);height:clamp(32px,12cqw,46px)}
.chud-lfa.has-lane{padding-inline:clamp(16px,6cqw,23px)}
.chud-lfa{--chud-lfa-size:clamp(32px,12cqw,46px);--chud-lfa-lane-width:clamp(64px,24cqw,92px)}
.chud-wifi{width:clamp(28px,10cqw,40px);height:clamp(28px,10cqw,40px)}
.chud-clock{font-size:clamp(28px,10cqw,40px);height:clamp(28px,10cqw,40px)}
.chud-limit{width:clamp(52px,16cqw,68px)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import { el } from "../dom.js";
import { CARROT_URI, LFA_LANE_URI } from "../assets.js";
import { LANE_MODE_PRESENTATION } from "../../lane_mode.js";

/* LFA 아이콘 = 클러스터와 동일한 원본 에셋. 클러스터 동작:
* - 활성 시 풀컬러 / 비활성 시 회색(muted)
* - 조향각만큼 회전(cluster: rotation = -steering_angle_deg)
* - controlsState.activeLaneLine(횡방향 레인모드) 시 carrot_wheel_lane 초록 날개 오버레이.
* - 사용자 레인모드 선택/자동 폴백/최종 활성 상태를 고정 폭 날개로 구분.
* 클러스터는 흰 실루엣 텍스처를 GREEN(활성)/muted(비활성)로 틴트 → 웹은 동일 자산을
* mask-image로 깔고 배경색만 토큰(--chud-carrot/--chud-muted)으로 칠한다(픽셀루프/새 에셋 없음). */
export function createLfaIcon(doc) {
Expand All @@ -20,10 +21,19 @@ export function createLfaIcon(doc) {

function update(data = {}) {
root.classList.toggle("is-active", data.lfaActive !== false);
const laneOn = data.activeLaneLine === true;
// 날개가 보일 때만 컨테이너가 2x 폭을 예약 → 옆 위젯과 겹침/좌측 오버플로우 방지.
root.classList.toggle("has-lane", laneOn);
lane.style.display = laneOn ? "" : "none";
const presentation = data.laneModePresentation || (
data.activeLaneLine === true
? LANE_MODE_PRESENTATION.ACTIVE
: LANE_MODE_PRESENTATION.LANELESS
);
const hasLane = (
presentation === LANE_MODE_PRESENTATION.ARMED
|| presentation === LANE_MODE_PRESENTATION.ACTIVE
);
root.classList.toggle("has-lane", hasLane);
root.classList.toggle("is-lane-armed", presentation === LANE_MODE_PRESENTATION.ARMED);
root.classList.toggle("is-lane-active", presentation === LANE_MODE_PRESENTATION.ACTIVE);
root.setAttribute("data-lane-state", presentation);
const angle = Number(data.steerAngle);
if (Number.isFinite(angle)) img.style.transform = `rotate(${(-angle).toFixed(1)}deg)`;
}
Expand Down
Loading
Loading