mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-01 06:23:44 +08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c3558e017 | |||
| bf567847de | |||
| bd1d54e239 | |||
| 8d49466b62 | |||
| 0fbca979df | |||
| dcddb2a0bd | |||
| 699eaf7957 | |||
| c246e6318a | |||
| 718db8c62e | |||
| c2214d4c32 | |||
| 0de7fbf33d | |||
| 084747c75d | |||
| 555f48c5d2 | |||
| dcf9d25bf3 | |||
| a8d1a280c6 | |||
| 5b36799eec | |||
| 20fdc3d824 | |||
| 7bd6cad821 |
+3
-1
@@ -24,7 +24,9 @@ function agnos_init {
|
||||
if $AGNOS_PY --verify $MANIFEST; then
|
||||
sudo reboot
|
||||
fi
|
||||
$DIR/openpilot/common/hardware/comma/updater $AGNOS_PY $MANIFEST
|
||||
while true; do
|
||||
$DIR/openpilot/common/hardware/comma/updater $AGNOS_PY $MANIFEST
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 0819b0e8e0...06743dfb39
@@ -59,7 +59,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IsDriverViewEnabled", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsEngaged", {PERSISTENT, BOOL}},
|
||||
{"IsLdwEnabled", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"IsLiveStreaming", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsLiveStreaming", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||
{"IsMetric", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"IsOffroad", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsRhdDetected", {PERSISTENT, BOOL}},
|
||||
|
||||
@@ -575,7 +575,7 @@ class SelfdriveD(CruiseHelper):
|
||||
clear_event_types = set()
|
||||
if ET.WARNING not in self.state_machine.current_alert_types:
|
||||
clear_event_types.add(ET.WARNING)
|
||||
if self.enabled:
|
||||
if self.enabled or (ET.NO_ENTRY not in (c := self.state_machine.current_alert_types) and (ET.ENABLE in c or ET.USER_DISABLE in c)):
|
||||
clear_event_types.add(ET.NO_ENTRY)
|
||||
|
||||
pers = LONGITUDINAL_PERSONALITY_MAP[self.personality]
|
||||
|
||||
@@ -149,11 +149,15 @@ class BigButton(Widget):
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None)
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
# A value moves the title to the top, where it shares space with the icon.
|
||||
def _title_width_hint(self) -> int:
|
||||
# A value moves the title to the top, where it shares space with the icon
|
||||
icon_size = self._txt_icon.width if self._txt_icon and self.value else 0
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size)
|
||||
|
||||
def _subtitle_width_hint(self) -> int:
|
||||
# Bottom aligned, so it sits below the icon
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2)
|
||||
|
||||
def _get_label_font_size(self):
|
||||
if len(self.text) <= 18:
|
||||
return 48
|
||||
@@ -228,14 +232,14 @@ class BigButton(Widget):
|
||||
|
||||
label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35))
|
||||
self._label.set_color(label_color)
|
||||
label_rect = rl.Rectangle(label_x, btn_y + self.LABEL_VERTICAL_PADDING, self._width_hint(),
|
||||
label_rect = rl.Rectangle(label_x, btn_y + self.LABEL_VERTICAL_PADDING, self._title_width_hint(),
|
||||
self._rect.height - self.LABEL_VERTICAL_PADDING * 2)
|
||||
self._label.render(label_rect)
|
||||
|
||||
if self.value:
|
||||
label_y = btn_y + self.LABEL_VERTICAL_PADDING + self._label.get_content_height(self._width_hint())
|
||||
label_y = label_rect.y + self._label.get_content_height(int(label_rect.width))
|
||||
sub_label_height = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING - label_y
|
||||
sub_label_rect = rl.Rectangle(label_x, label_y, self._width_hint(), sub_label_height)
|
||||
sub_label_rect = rl.Rectangle(label_x, label_y, self._subtitle_width_hint(), sub_label_height)
|
||||
self._sub_label.render(sub_label_rect)
|
||||
|
||||
# ICON -------------------------------------------------------------------
|
||||
@@ -312,9 +316,6 @@ class BigMultiToggle(BigToggle):
|
||||
|
||||
self.set_value(self._options[0])
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
cur_idx = self._options.index(self.value)
|
||||
@@ -363,9 +364,6 @@ class GreyBigButton(BigButton):
|
||||
def LABEL_VERTICAL_PADDING(self):
|
||||
return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2)
|
||||
|
||||
def _get_label_font_size(self):
|
||||
return 36
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL, DEFAULT_BIG_MO
|
||||
|
||||
|
||||
def get_default_model() -> str:
|
||||
show_big_model = (ui_state.usbgpu and ui_state.usbgpu_compiled
|
||||
show_big_model = (ui_state.usbgpu
|
||||
and (ui_state.usbgpu_active or ui_state.usbgpu_loading or ui_state.is_offroad()))
|
||||
|
||||
return DEFAULT_BIG_MODEL if show_big_model else DEFAULT_MODEL
|
||||
|
||||
@@ -28,7 +28,7 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce
|
||||
create_connection, WebSocketConnectionClosedException)
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.selfdrive.modeld.helpers import usbgpu_present, usbgpu_compiled
|
||||
from openpilot.selfdrive.modeld.helpers import usbgpu_present
|
||||
from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL, DEFAULT_BIG_MODEL
|
||||
from openpilot.sunnypilot.selfdrive.car.sync_sunnylink_params import update_car_list_param
|
||||
from openpilot.sunnypilot.sunnylink.api import SunnylinkApi
|
||||
@@ -183,9 +183,10 @@ def getParamsMetadata() -> str:
|
||||
schema["capabilities"] = generate_capabilities()
|
||||
schema["capability_labels"] = CAPABILITY_LABELS
|
||||
# mirrors get_default_model() — ui_state unavailable in sunnylinkd process
|
||||
show_big = (usbgpu_present() and usbgpu_compiled()
|
||||
show_big = (usbgpu_present()
|
||||
and (params.get_bool("UsbGpuActive") or params.get_bool("UsbGpuLoading") or params.get_bool("IsOffroad")))
|
||||
schema["default_model"] = DEFAULT_BIG_MODEL if show_big else DEFAULT_MODEL
|
||||
schema["usbgpu_active"] = params.get_bool("UsbGpuActive")
|
||||
raw = json.dumps(schema, separators=(",", ":")).encode("utf-8")
|
||||
return base64.b64encode(gzip.compress(raw)).decode("utf-8")
|
||||
except Exception:
|
||||
|
||||
@@ -828,20 +828,22 @@ def startStream(sdp: str, enabled: bool) -> dict:
|
||||
bridge_services_in = []
|
||||
|
||||
# stale car params case taken care of by webrtcd being shut off on ignition
|
||||
cp_bytes = Params().get("CarParamsPersistent")
|
||||
cp_bytes = params.get("CarParamsPersistent")
|
||||
if cp_bytes is not None:
|
||||
with car.CarParams.from_bytes(cp_bytes) as CP:
|
||||
if CP.notCar:
|
||||
bridge_services_in.append("testJoystick")
|
||||
else:
|
||||
raise Exception("failed to get CarParamsPersistent")
|
||||
|
||||
if params.get_bool("IsOffroad"):
|
||||
# manager owns camerad/stream_encoderd/webrtcd; flip the param and let it bring them up.
|
||||
# webrtcd clears IsLiveStreaming when the session ends
|
||||
params.put_bool("IsLiveStreaming", True)
|
||||
# wait for webrtcd end points to wake up
|
||||
wait_for_webrtcd()
|
||||
try:
|
||||
wait_for_webrtcd()
|
||||
except TimeoutError:
|
||||
cloudlog.event("athena.startStream.webrtcd_offroad_start_timeout", error=True)
|
||||
raise
|
||||
|
||||
return post_stream_request(StreamRequestBody(sdp, ["wideRoad"], enabled, bridge_services_in, ["carState", "deviceState"]))
|
||||
|
||||
|
||||
@@ -106,15 +106,12 @@ def or_(*fns):
|
||||
def and_(*fns):
|
||||
return lambda *args: operator.and_(*(fn(*args) for fn in fns))
|
||||
|
||||
def not_(*fns):
|
||||
return lambda *args: operator.not_(*(fn(*args) for fn in fns))
|
||||
|
||||
procs = [
|
||||
DaemonProcess("manage_athenad", "openpilot.system.athena.manage_athenad", "AthenadPid"),
|
||||
|
||||
NativeProcess("loggerd", "openpilot/system/loggerd", ["./loggerd"], logging),
|
||||
NativeProcess("encoderd", "openpilot/system/loggerd", ["./encoderd"], only_onroad),
|
||||
NativeProcess("stream_encoderd", "openpilot/system/loggerd", ["./encoderd", "--stream"], or_(and_(livestream, not_(iscar)), notcar)),
|
||||
NativeProcess("stream_encoderd", "openpilot/system/loggerd", ["./encoderd", "--stream"], or_(livestream, notcar)),
|
||||
PythonProcess("logmessaged", "openpilot.system.logmessaged", always_run),
|
||||
|
||||
NativeProcess("camerad", "openpilot/system/camerad", ["./camerad"], or_(driverview, livestream), enabled=not WEBCAM),
|
||||
@@ -159,7 +156,7 @@ procs = [
|
||||
|
||||
# debug procs
|
||||
NativeProcess("bridge", "openpilot/cereal/messaging", ["./bridge"], notcar),
|
||||
PythonProcess("webrtcd", "openpilot.system.webrtc.webrtcd", or_(and_(livestream, not_(iscar)), notcar)),
|
||||
PythonProcess("webrtcd", "openpilot.system.webrtc.webrtcd", or_(livestream, notcar)),
|
||||
PythonProcess("joystick", "openpilot.tools.joystick.joystick_control", and_(joystick, iscar)),
|
||||
|
||||
# sunnylink <3
|
||||
|
||||
@@ -23,9 +23,9 @@ def post_stream_request(body: StreamRequestBody) -> dict:
|
||||
ret["time"] = (t_end - t_start) * 1000
|
||||
return ret
|
||||
except requests.ConnectTimeout as e:
|
||||
raise Exception("webrtc took too long to respond.") from e
|
||||
raise Exception("device took too long to respond.") from e
|
||||
except requests.ConnectionError as e:
|
||||
raise Exception("webrtc server on device is not running.") from e
|
||||
raise Exception("turn car ignition off to use livestreaming.") from e
|
||||
|
||||
|
||||
def wait_for_webrtcd(max_retries: float = 10) -> None:
|
||||
@@ -37,4 +37,4 @@ def wait_for_webrtcd(max_retries: float = 10) -> None:
|
||||
except requests.ConnectionError:
|
||||
attempts += 1
|
||||
time.sleep(0.5)
|
||||
raise TimeoutError("webrtcd did not initialize in time.")
|
||||
raise TimeoutError("livestreaming service did not initialize in time.")
|
||||
|
||||
@@ -21,10 +21,16 @@ from typing import Any
|
||||
from openpilot.system.webrtc.helpers import StreamRequestBody
|
||||
from openpilot.system.webrtc.schema import generate_field
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.cereal import messaging, log
|
||||
|
||||
SESSION_TIMEOUT_SECONDS = 300
|
||||
|
||||
|
||||
# ice candidate parser for logging
|
||||
def _ice_candidates(sdp: str) -> list[str]:
|
||||
return [line.removeprefix("a=") for line in sdp.splitlines() if line.startswith("a=candidate:")]
|
||||
|
||||
# socket trick: route lookup for 8.8.8.8 (nothing is sent or actually connected to)
|
||||
# return the source interfaces IP which is the default interface of the device
|
||||
def _default_route_ip() -> str | None:
|
||||
@@ -253,7 +259,7 @@ class StreamSession:
|
||||
self._cleanup_lock = asyncio.Lock()
|
||||
self._cleanup_done = False
|
||||
self.logger = logging.getLogger("webrtcd")
|
||||
self.logger.info(
|
||||
cloudlog.warning(
|
||||
"New stream session (%s), video cameras %s, video enabled %s, incoming services %s, outgoing services %s",
|
||||
self.identifier, [t.id for t in self.video_tracks], body.enabled, body.bridge_services_in, body.bridge_services_out,
|
||||
)
|
||||
@@ -329,9 +335,12 @@ class StreamSession:
|
||||
async def run(self):
|
||||
try:
|
||||
self.params.put("LivestreamRequestKeyframe", True)
|
||||
|
||||
# avoid datachannel race by adding messange_handler immediately
|
||||
self.stream.set_message_handler(self.message_handler)
|
||||
|
||||
await asyncio.wait_for(self.stream.wait_for_connection(), timeout=15)
|
||||
if self.stream.has_messaging_channel():
|
||||
self.stream.set_message_handler(self.message_handler)
|
||||
if self.incoming_bridge is not None:
|
||||
await self.shared_pub_master.add_services_if_needed(self.incoming_bridge_services)
|
||||
if self.outgoing_bridge is not None:
|
||||
@@ -341,14 +350,18 @@ class StreamSession:
|
||||
if self.bitrate_controller is not None:
|
||||
self.bitrate_controller.start()
|
||||
|
||||
self.logger.info("Stream session (%s) connected", self.identifier)
|
||||
with cloudlog.ctx(session_id=self.identifier):
|
||||
cloudlog.warning("webrtcd.session.connected")
|
||||
if self.is_body:
|
||||
await self.run_body_session()
|
||||
else:
|
||||
await self.run_normal_session()
|
||||
self.logger.info("Stream session (%s) ended", self.identifier)
|
||||
with cloudlog.ctx(session_id=self.identifier):
|
||||
cloudlog.warning("webrtcd.session.ended")
|
||||
except Exception:
|
||||
self.logger.exception("Stream session failure")
|
||||
with cloudlog.ctx(session_id=self.identifier):
|
||||
cloudlog.exception("webrtcd.session.exception")
|
||||
finally:
|
||||
await self.post_run_cleanup()
|
||||
|
||||
@@ -422,15 +435,25 @@ async def handle_get_stream(state: ServerState, raw_body: bytes, content_type: s
|
||||
stream_dict[session.identifier] = session
|
||||
try:
|
||||
answer = await asyncio.wait_for(session.get_answer(), timeout=30)
|
||||
cloudlog.event(
|
||||
"webrtcd.session.ice_candidates",
|
||||
session_id=session.identifier,
|
||||
offer_candidates=_ice_candidates(body.sdp),
|
||||
answer_candidates=_ice_candidates(answer.sdp),
|
||||
)
|
||||
except TimeoutError:
|
||||
await session.stop()
|
||||
stream_dict.pop(session.identifier, None)
|
||||
logging.getLogger("webrtcd").exception("Timed out creating stream answer")
|
||||
with cloudlog.ctx(session_id=session.identifier):
|
||||
cloudlog.warning("webrtcd.session.answer_timeout")
|
||||
raise
|
||||
except Exception:
|
||||
await session.stop()
|
||||
stream_dict.pop(session.identifier, None)
|
||||
logging.getLogger("webrtcd").exception("Failed to create stream answer")
|
||||
with cloudlog.ctx(session_id=session.identifier):
|
||||
cloudlog.exception("webrtcd.session.answer_exception")
|
||||
raise
|
||||
session.start()
|
||||
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
# Live UI
|
||||
|
||||
View a comma device's live sunnypilot UI over Wi-Fi. Replay, ADB, and USB are
|
||||
not used.
|
||||
|
||||
| Mode | Output | Device bridge |
|
||||
| --- | --- | --- |
|
||||
| Mimic (default) | Local UI rendered from live device data | Required |
|
||||
| Exact (`--exact`) | Actual pixels shown on the device | Not required |
|
||||
|
||||
Mimic mode does not directly require SSH, but the device bridge must be started
|
||||
somehow; the examples use SSH. Exact mode, touch control, and `--stop-exact`
|
||||
require key-based SSH access.
|
||||
|
||||
Choose **mimic** when developing or inspecting the local UI with live device
|
||||
data. Choose **exact** when you need to see or control what is physically shown
|
||||
on the device.
|
||||
|
||||
## Before the first run
|
||||
|
||||
1. Replace `192.168.43.1` in the examples if your device uses a different IP.
|
||||
2. Connect the computer and device to networks that can reach each other.
|
||||
3. Run local commands from the sunnypilot repository root.
|
||||
4. For exact mode, verify non-interactive SSH access:
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes comma@192.168.43.1 true
|
||||
```
|
||||
|
||||
The script automatically switches to the repository's `.venv` when present.
|
||||
|
||||
## Mimic UI
|
||||
|
||||
Mimic mode receives live cereal messages and encoded road-camera frames, then
|
||||
renders a new UI instance on the computer. It resembles the device UI but is
|
||||
not a pixel-for-pixel screen mirror.
|
||||
|
||||
Build the messaging bridge once on the computer:
|
||||
|
||||
```bash
|
||||
scons -u cereal/messaging/bridge
|
||||
```
|
||||
|
||||
Use two terminals. In the first, start the bridge on the device and leave it
|
||||
running:
|
||||
|
||||
```bash
|
||||
ssh comma@192.168.43.1
|
||||
./cereal/messaging/bridge
|
||||
```
|
||||
|
||||
In the second terminal, start the local UI:
|
||||
|
||||
```bash
|
||||
BIG=0 ./openpilot/tools/live/ui.py 192.168.43.1
|
||||
```
|
||||
|
||||
`BIG` is inherited from your environment and is not forced by the tool.
|
||||
|
||||
## Exact device screen
|
||||
|
||||
Exact mode streams the actual device display. It does not need either
|
||||
messaging bridge:
|
||||
|
||||
```bash
|
||||
./openpilot/tools/live/ui.py --exact 192.168.43.1
|
||||
```
|
||||
|
||||
The tool runs a temporary capture helper through SSH using AGNOS's existing
|
||||
DRM broker. It does not install software or modify openpilot source on the
|
||||
device. Exact mode requires a compatible AGNOS DRM writeback connector and
|
||||
captures the post-processed display output when that mode is available.
|
||||
|
||||
Exact mode is experimental. Some Qualcomm/AGNOS combinations can time out
|
||||
during DRM writeback, freezing both the mirrored window and physical device UI.
|
||||
This can happen without `--control`; mouse, keyboard, and shortcut forwarding
|
||||
are not required to trigger the capture failure.
|
||||
The tool stops its verified helper when capture stalls, but the kernel display
|
||||
state may require a full device reboot before exact mode can be tried again.
|
||||
Restarting openpilot alone does not reset the display driver.
|
||||
|
||||
Set the capture rate from 1 to 60 FPS; the default is 15:
|
||||
|
||||
```bash
|
||||
./openpilot/tools/live/ui.py --exact --exact-fps 30 192.168.43.1
|
||||
./openpilot/tools/live/ui.py --exact --exact-fps 60 192.168.43.1
|
||||
```
|
||||
|
||||
Start with 15 or 30 FPS. Higher rates use more device CPU and Wi-Fi bandwidth.
|
||||
|
||||
Use `SCALE` to resize the local window:
|
||||
|
||||
```bash
|
||||
SCALE=2 ./openpilot/tools/live/ui.py --exact 192.168.43.1
|
||||
```
|
||||
|
||||
Only one exact session can run at a time.
|
||||
|
||||
## Touch control
|
||||
|
||||
Touch control is available only with exact mode:
|
||||
|
||||
```bash
|
||||
./openpilot/tools/live/ui.py --exact --control 192.168.43.1
|
||||
```
|
||||
|
||||
Left-clicks and drags are forwarded to the physical touchscreen over SSH.
|
||||
These inputs affect the real device, including settings and confirmation
|
||||
buttons.
|
||||
|
||||
When the device's on-screen keyboard is visible, type with the computer
|
||||
keyboard. Letters, numbers, symbols, Space, Backspace, and Return are converted
|
||||
to taps on the device keyboard.
|
||||
|
||||
Exact control also provides simulator-style shortcuts:
|
||||
|
||||
| Shortcut | Action |
|
||||
| --- | --- |
|
||||
| `↓` | Go back with a swipe-down gesture |
|
||||
| `←` | Swipe right |
|
||||
| `→` | Swipe left |
|
||||
| `Command`+`K` (macOS) or `Ctrl`+`K` (Linux/Windows) | Force keyboard forwarding on, or return to automatic detection |
|
||||
|
||||
Hold an arrow key to repeat its gesture until the key is released.
|
||||
|
||||
Escape has no action and does not close the program; use `Ctrl-C` or the window
|
||||
close button to exit. `Command`+`K`/`Ctrl`+`K` enables manual keyboard forwarding
|
||||
regardless of exact-screen resolution; touch locations scale to the captured
|
||||
screen. Automatic keyboard detection targets the 536x240 mici UI. Turn forced
|
||||
forwarding back off after typing so ordinary keys cannot produce unintended
|
||||
taps on incompatible keyboard layouts.
|
||||
|
||||
## Connection status
|
||||
|
||||
Status appears in the window title and terminal:
|
||||
|
||||
- `connected`: mimic telemetry and camera frames, or exact screen frames, are
|
||||
arriving.
|
||||
- `keyboard`: exact-mode keyboard forwarding is currently active. This appears
|
||||
only while the device keyboard is detected or forwarding is forced on.
|
||||
- `no camera`: mimic telemetry is arriving, but road-camera frames are not.
|
||||
- `disconnected`: expected mimic-mode live data has stopped.
|
||||
|
||||
At the default capture rate, exact mode reports a stopped screen stream in
|
||||
about one second. Lower `--exact-fps` values allow more time between frames.
|
||||
If Qualcomm DRM writeback stalls, exact mode immediately stops its verified
|
||||
device helper and exits to avoid leaving the physical display frozen.
|
||||
|
||||
## Stop and clean up
|
||||
|
||||
Press `Ctrl-C` or close the window. If an exact helper remains orphaned, run:
|
||||
|
||||
```bash
|
||||
./openpilot/tools/live/ui.py --stop-exact 192.168.43.1
|
||||
```
|
||||
|
||||
`--kill-exact` is an alias. Stop exact mode before restarting or rebuilding
|
||||
openpilot to avoid blocking the physical UI during a display transition.
|
||||
|
||||
## Options
|
||||
|
||||
```text
|
||||
--exact Show the actual device screen
|
||||
--control Forward mouse and keyboard input; requires --exact
|
||||
--exact-fps FPS Exact capture rate, 1-60 (default: 15)
|
||||
--stop-exact Stop orphaned exact helpers
|
||||
--kill-exact Alias for --stop-exact
|
||||
--ip ADDRESS Alternative to the positional address
|
||||
```
|
||||
|
||||
See the complete command help:
|
||||
|
||||
```bash
|
||||
./openpilot/tools/live/ui.py --help
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`cereal/messaging/bridge` is not built:** Run:
|
||||
|
||||
```bash
|
||||
scons -u cereal/messaging/bridge
|
||||
```
|
||||
|
||||
- **Mimic is disconnected:** Confirm the device bridge is still running and
|
||||
the IP is reachable.
|
||||
- **Mimic reports no camera:** Confirm the device is publishing
|
||||
`roadEncodeData`. Camera display may also wait briefly for the next keyframe.
|
||||
- **Exact says another session is running:** Close the other exact window. If
|
||||
none is open, run `--stop-exact`.
|
||||
- **Exact cannot connect:** Recheck key-based SSH and the device IP using the
|
||||
command in "Before the first run."
|
||||
- **Exact stalls or the physical UI freezes:** Stop the exact window and run
|
||||
`--stop-exact`. If the device UI remains unstable or exact immediately stalls
|
||||
again, fully reboot the device; restarting openpilot is not sufficient. If
|
||||
the problem returns after reboot, use mimic mode because continuous DRM
|
||||
writeback is not reliable on that device/AGNOS combination. Removing
|
||||
`--control` does not address this capture-layer failure.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user