Skip to content

Commit 574ef8d

Browse files
jominki354jominki354
andauthored
web work (#279)
Co-authored-by: jominki354 <jomin354@gmail.com>
1 parent 8989796 commit 574ef8d

8 files changed

Lines changed: 426 additions & 118 deletions

File tree

selfdrive/carrot/realtime/transports/raw_ws.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,27 @@ def __init__(self, messaging: Any) -> None:
2121
self._tasks: dict[str, asyncio.Task] = {}
2222
self._sockets: dict[str, Any] = {}
2323
self._send_failures: dict[str, dict[web.WebSocketResponse, int]] = {}
24+
self._last_send_time: dict[str, float] = {}
2425
self._lock = asyncio.Lock()
2526

2627
def is_allowed_service(self, service: str) -> bool:
2728
return is_supported_raw_service(service)
2829

30+
# Phase 2-2: per-service throttle intervals (seconds)
31+
# 0 = no throttle (send every message)
32+
_THROTTLE_MAP = {
33+
"modelV2": 0, # camera-synced, don't throttle
34+
"roadCameraState": 0, # camera-synced
35+
"liveCalibration": 0.25, # slow-changing
36+
"liveParameters": 0.25,
37+
"liveTorqueParameters": 0.25,
38+
"liveDelay": 0.25,
39+
}
40+
_THROTTLE_DEFAULT = 0.05 # 20Hz for everything else
41+
42+
def _throttle_interval(self, service: str) -> float:
43+
return self._THROTTLE_MAP.get(service, self._THROTTLE_DEFAULT)
44+
2945
def client_count(self, service: str | None = None) -> int:
3046
if service is None:
3147
return sum(len(clients) for clients in self._clients.values())
@@ -104,6 +120,15 @@ async def _service_loop(self, service: str) -> None:
104120
await asyncio.sleep(self.ACTIVE_POLL_SLEEP)
105121
continue
106122

123+
# Phase 2-2: per-service throttle — skip if too soon since last send
124+
now = asyncio.get_running_loop().time()
125+
interval = self._throttle_interval(service)
126+
last_send = self._last_send_time.get(service, 0.0)
127+
if interval > 0 and (now - last_send) < interval:
128+
await asyncio.sleep(self.ACTIVE_POLL_SLEEP)
129+
continue
130+
self._last_send_time[service] = now
131+
107132
stale: list[web.WebSocketResponse] = []
108133
client_list = list(clients)
109134
results = await asyncio.gather(

selfdrive/carrot/web/css/app.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,6 +1623,19 @@ body[data-page="carrot"] #driveHudCard {
16231623
display: none;
16241624
}
16251625

1626+
.vision-start-overlay {
1627+
position: absolute;
1628+
top: 0; left: 0; right: 0; bottom: 0;
1629+
display: flex;
1630+
align-items: center;
1631+
justify-content: center;
1632+
background: rgba(0, 0, 0, 0.4);
1633+
backdrop-filter: blur(4px);
1634+
-webkit-backdrop-filter: blur(4px);
1635+
z-index: 50;
1636+
transition: opacity 0.3s ease;
1637+
}
1638+
16261639
body[data-page="carrot"] #driveHudCard.driveHudCard--inline {
16271640
position: relative !important;
16281641
inset: auto !important;
@@ -1675,6 +1688,12 @@ body[data-page="carrot"] .carrot-hud-dock {
16751688
overflow: hidden;
16761689
}
16771690

1691+
@media (orientation: landscape) {
1692+
body[data-page="carrot"] .carrot-hud-dock {
1693+
display: none !important;
1694+
}
1695+
}
1696+
16781697
body[data-page="carrot"] #driveHudCard.driveHudCard--inline .hudWrap {
16791698
margin-inline: auto;
16801699
}
@@ -1687,6 +1706,7 @@ body[data-page="carrot"] #driveHudCard.driveHudCard--overlay {
16871706
top: auto !important;
16881707
width: auto;
16891708
max-width: calc(100% - (var(--carrot-hud-left, 12px) * 2));
1709+
z-index: 60;
16901710
}
16911711

16921712
body[data-page="carrot"] #driveHudCard.driveHudCard--overlay .hudWrap {
@@ -1754,6 +1774,7 @@ body[data-page="carrot"] #driveHudCard.driveHudCard--overlay .hudWrap {
17541774
linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0)),
17551775
#04070c;
17561776
box-shadow: 0 20px 48px rgba(0, 0, 0, 0.24);
1777+
contain: layout style;
17571778
}
17581779

17591780
.carrot-stage__video,
@@ -1818,6 +1839,7 @@ body[data-page="carrot"] #driveHudCard.driveHudCard--overlay .hudWrap {
18181839
transform-origin: 0 0;
18191840
will-change: transform;
18201841
pointer-events: none;
1842+
contain: strict;
18211843
}
18221844

18231845
.carrot-stage__hud {

selfdrive/carrot/web/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ <h2 id="carrotTitle" class="page-title">Home</h2>
230230
<canvas id="carrotOverlayCanvas" class="carrot-stage__canvas"></canvas>
231231
<canvas id="carrotHudCanvas" class="carrot-stage__hud"></canvas>
232232

233+
<div id="visionStartOverlay" class="vision-start-overlay">
234+
<button id="btnStartVision" class="btn btn--filled" type="button" style="font-size:1.2rem; padding: 16px 32px; border-radius: 99px;">
235+
▶ 주행 비전 시작
236+
</button>
237+
</div>
238+
233239
<div class="carrot-stage__topbar">
234240
<div id="carrotStageStatus" class="carrot-stage__status">waiting stream...</div>
235241
</div>

selfdrive/carrot/web/js/app_core.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,11 +825,13 @@ function showPage(page, pushHistory = false, transition = null) {
825825
else if (typeof syncSettingViewportLayout === "function" && typeof isCompactLandscapeMode === "function" && isCompactLandscapeMode()) {
826826
syncSettingViewportLayout().catch(() => {});
827827
} else if (pushHistory || !CURRENT_GROUP) showSettingScreen("groups", false);
828+
loadCurrentCar().catch(() => {});
828829
}
829830

830831
if (page === "car") {
831832
showCarScreen("makers", false);
832833
if (!CARS) loadCars();
834+
loadCurrentCar().catch(() => {});
833835
}
834836
if (page === "tools") {
835837
initToolsPage();

selfdrive/carrot/web/js/app_realtime.js

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async function fetchLiveRuntimeState(force = false) {
8181

8282
function getLiveRuntimePollMs() {
8383
const carrotVisible = document.body?.dataset?.page === "carrot" && !document.hidden;
84-
return carrotVisible ? 350 : 1500;
84+
return carrotVisible ? 1000 : 3000;
8585
}
8686

8787
function scheduleLiveRuntimeStateFetch(ms = getLiveRuntimePollMs()) {
@@ -101,6 +101,7 @@ scheduleLiveRuntimeStateFetch(1500);
101101
let RTC_PC = null;
102102
let RTC_RETRY_T = null;
103103
let RTC_WAIT_TRACK_T = null;
104+
let RTC_FAIL_COUNT = 0;
104105
function rtcHasLiveTrack() {
105106
const video = document.getElementById("rtcVideo");
106107
return Boolean(video && video.srcObject);
@@ -133,10 +134,12 @@ async function rtcDisconnect() {
133134

134135
function rtcScheduleRetry(ms = 2000) {
135136
rtcCancelRetry();
137+
const backoff = Math.min(ms * Math.pow(1.5, RTC_FAIL_COUNT), 30000);
138+
RTC_FAIL_COUNT = Math.min(RTC_FAIL_COUNT + 1, 20);
136139
RTC_RETRY_T = setTimeout(async () => {
137140
RTC_RETRY_T = null;
138141
await rtcConnectOnce().catch(() => {});
139-
}, ms);
142+
}, backoff);
140143
}
141144

142145
function rtcArmTrackTimeout(ms = 5000) {
@@ -145,7 +148,7 @@ function rtcArmTrackTimeout(ms = 5000) {
145148
RTC_WAIT_TRACK_T = null;
146149
rtcStatusSet("no track, retry...");
147150
await rtcDisconnect();
148-
rtcScheduleRetry(1000);
151+
rtcScheduleRetry(2000);
149152
}, ms);
150153
}
151154

@@ -171,9 +174,13 @@ async function waitIceComplete(pc, timeoutMs = 8000) {
171174
});
172175
}
173176

177+
let _rtcConnecting = false;
178+
174179
async function rtcConnectOnce() {
180+
if (_rtcConnecting) return;
175181
if (RTC_PC && (RTC_PC.connectionState === "connected" || RTC_PC.connectionState === "connecting")) return;
176182

183+
_rtcConnecting = true;
177184
try {
178185
await rtcDisconnect();
179186
rtcStatusSet("connecting...");
@@ -207,12 +214,13 @@ async function rtcConnectOnce() {
207214
try { await videoEl.play(); } catch (e) { console.log("[RTC] play() failed", e); }
208215
rtcStatusSet("track: " + ev.track.kind);
209216
rtcDisarmTrackTimeout();
217+
RTC_FAIL_COUNT = 0;
210218
};
211219

212220
pc.onconnectionstatechange = () => {
213221
const state = pc.connectionState;
214-
console.log("[RTC] connectionState:", state);
215222
rtcStatusSet("conn: " + state);
223+
if (state === "connected") RTC_FAIL_COUNT = 0;
216224
if (state === "failed" || state === "disconnected" || state === "closed") {
217225
rtcDisconnect();
218226
rtcScheduleRetry(2000);
@@ -221,7 +229,6 @@ async function rtcConnectOnce() {
221229

222230
pc.oniceconnectionstatechange = () => {
223231
const state = pc.iceConnectionState;
224-
console.log("[RTC] iceConnectionState:", state);
225232
rtcStatusSet("ice: " + state);
226233
if (state === "failed" || state === "disconnected" || state === "closed") {
227234
rtcDisconnect();
@@ -261,15 +268,15 @@ async function rtcConnectOnce() {
261268
rtcStatusSet("error: " + e.message);
262269
await rtcDisconnect();
263270
rtcScheduleRetry(2000);
264-
throw e;
271+
} finally {
272+
_rtcConnecting = false;
265273
}
266274
}
267275

268276
async function waitServerReady(timeoutMs = 8000) {
269277
const t0 = Date.now();
270278
while (Date.now() - t0 < timeoutMs) {
271279
try {
272-
// ���� ����ִ����� Ȯ�� (������ API)
273280
const r = await fetch("/api/settings", { cache: "no-store" });
274281
if (r.ok) return true;
275282
} catch {}
@@ -278,15 +285,30 @@ async function waitServerReady(timeoutMs = 8000) {
278285
return false;
279286
}
280287

288+
window.CARROT_VISION_ACTIVE = false;
289+
290+
window.CarrotVisionStart = async function() {
291+
if (window.CARROT_VISION_ACTIVE) return;
292+
window.CARROT_VISION_ACTIVE = true;
293+
294+
const btn = document.getElementById("visionStartOverlay");
295+
if (btn) btn.style.display = "none";
296+
297+
rtcStatusSet("waiting server...");
298+
await waitServerReady(8000);
299+
await rtcConnectOnce().catch(() => {});
300+
};
301+
281302
function rtcInitAuto() {
282-
(async () => {
283-
rtcStatusSet("waiting server...");
284-
await waitServerReady(8000); // �����ص� ��� ����
285-
await rtcConnectOnce().catch(() => {});
286-
})();
303+
const btn = document.getElementById("btnStartVision");
304+
if (btn) btn.onclick = window.CarrotVisionStart;
287305

288306
document.addEventListener("visibilitychange", () => {
289-
if (!document.hidden) rtcConnectOnce().catch(() => {});
307+
if (!document.hidden && !_rtcConnecting && window.CARROT_VISION_ACTIVE) {
308+
rtcCancelRetry();
309+
RTC_FAIL_COUNT = 0;
310+
rtcConnectOnce().catch(() => {});
311+
}
290312
});
291313
}
292314

@@ -539,6 +561,33 @@ function startAll() {
539561
rawOverlayConnectAll();
540562
}
541563

564+
/* ── Phase 1: visibilitychange tab pause + WS release ── */
565+
let _visibilityPaused = false;
566+
567+
function _onVisibilityChange() {
568+
if (document.hidden) {
569+
if (_visibilityPaused) return;
570+
_visibilityPaused = true;
571+
console.log("[perf] tab hidden → pausing");
572+
_hudStopRenderLoop();
573+
rawOverlayDisconnectAll();
574+
if (LIVE_RUNTIME_FETCH_T) {
575+
clearTimeout(LIVE_RUNTIME_FETCH_T);
576+
LIVE_RUNTIME_FETCH_T = null;
577+
}
578+
} else {
579+
if (!_visibilityPaused) return;
580+
_visibilityPaused = false;
581+
console.log("[perf] tab visible → resuming");
582+
_hudStartRenderLoop();
583+
_hudMarkDirty();
584+
rawOverlayConnectAll();
585+
scheduleLiveRuntimeStateFetch(100);
586+
}
587+
}
588+
589+
document.addEventListener("visibilitychange", _onVisibilityChange);
590+
542591

543592

544593
if (document.readyState === "loading") {

0 commit comments

Comments
 (0)