bench mode, ClearPilot menu, status window, UI introspection RPC
Bench mode (--bench flag): - bench_onroad.py publishes fake vehicle state as managed process - manager blocks real car processes (pandad, thermald, controlsd, etc.) - bench_cmd.py for setting params and querying UI state - BLOCKER: UI segfaults (SIGSEGV) when OnroadWindow becomes visible without camera frames — need to make CameraWidget handle missing VisionIPC gracefully before bench drive mode works ClearPilot menu: - Replaced grid launcher with sidebar settings panel (ClearPilotPanel) - Sidebar: Home, Dashcam, Debug - Home panel: Status and System Settings buttons - Debug panel: telemetry logging toggle (ParamControl) - Tap from any view (splash, onroad) opens ClearPilotPanel - Back button returns to splash/onroad Status window: - Live system stats refreshing every 1 second - Storage, RAM, load, IP, WiFi, VPN, GPS, time, telemetry status - Tap anywhere to close, returns to previous view - Honors interactive timeout UI introspection RPC: - ZMQ REP server at ipc:///tmp/clearpilot_ui_rpc - Dumps full widget tree with visibility, geometry, stacked indices - bench_cmd dump queries it, detects crash loops via process uptime - ui_dump.py standalone tool Other: - Telemetry toggle wired to TelemetryEnabled param with disk space guard - Telemetry disabled on every manager start - Blinking red circle on onroad UI when telemetry recording - Fixed showDriverView overriding park/drive transitions every frame - Fixed offroadTransition sidebar visibility race in MainWindow - launch_openpilot.sh: cd to /data/openpilot, --bench flag support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
61
CLAUDE.md
61
CLAUDE.md
@@ -57,7 +57,7 @@ chown -R comma:comma /data/openpilot
|
|||||||
|
|
||||||
### Testing Changes
|
### Testing Changes
|
||||||
|
|
||||||
Always use `build_only.sh` to compile, then start the manager separately. Never compile individual targets with scons directly — always use the full build script. Always use full paths with `su - comma` — the login shell drops into `/home/comma` (volatile tmpfs), not `/data/openpilot`.
|
Always use `build_only.sh` to compile, then start the manager separately. Never compile individual targets with scons directly — always use the full build script. Always use full paths with `su - comma` — the login shell drops into `/home/comma` (volatile tmpfs), not `/data/openpilot`. **Always start the manager after a successful build** — don't wait for the user to ask.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Fix ownership (we edit as root, openpilot runs as comma)
|
# 1. Fix ownership (we edit as root, openpilot runs as comma)
|
||||||
@@ -93,6 +93,65 @@ The params system uses a C++ whitelist. Adding a new param name in `manager.py`
|
|||||||
- OMX encoder object (`omx_encoder.o`) is compiled by the UI build — reference the pre-built `.o` file rather than recompiling (avoids "two environments" scons error)
|
- OMX encoder object (`omx_encoder.o`) is compiled by the UI build — reference the pre-built `.o` file rather than recompiling (avoids "two environments" scons error)
|
||||||
- `prebuilt` is recreated after every successful build — always remove it before rebuilding
|
- `prebuilt` is recreated after every successful build — always remove it before rebuilding
|
||||||
|
|
||||||
|
## Bench Mode (Onroad UI Testing)
|
||||||
|
|
||||||
|
Bench mode allows testing the onroad UI without a car connected. It runs a fake vehicle simulator (`bench_onroad.py`) as a managed process and disables real car processes (pandad, thermald, controlsd, etc.).
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
**IMPORTANT**: Do NOT use `echo` to write bench params — `echo` appends a newline which causes param parsing to fail silently (e.g. gear stays in park). Always use the `bench_cmd.py` tool.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Start in bench mode
|
||||||
|
su - comma -c "bash /data/openpilot/launch_openpilot.sh --bench"
|
||||||
|
|
||||||
|
# 2. Wait for UI to be ready (polls RPC, up to 10s)
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd wait_ready"
|
||||||
|
|
||||||
|
# 3. Control vehicle state
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd gear D"
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd speed 20"
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd speedlimit 45"
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd cruise 55"
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd engaged 1"
|
||||||
|
|
||||||
|
# 4. Inspect UI widget tree (RPC call, instant response)
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_cmd dump"
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI Introspection RPC
|
||||||
|
|
||||||
|
The UI process runs a ZMQ REP server at `ipc:///tmp/clearpilot_ui_rpc`. Send `"dump"` to get a recursive widget tree showing class name, visibility, geometry, and stacked layout current indices. This is the primary debugging tool for understanding what the UI is displaying.
|
||||||
|
|
||||||
|
- `bench_cmd dump` — queries the RPC and prints the widget tree
|
||||||
|
- `bench_cmd wait_ready` — polls the RPC every second until `ReadyWindow` is visible (up to 10s)
|
||||||
|
- `ui_dump.py` — standalone dump tool (same as `bench_cmd dump`)
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
- `launch_openpilot.sh --bench` sets `BENCH_MODE=1` env var
|
||||||
|
- `manager.py` reads `BENCH_MODE`, blocks real car processes, enables `bench_onroad` process
|
||||||
|
- `bench_onroad.py` publishes fake `deviceState`, `pandaStates`, `carState`, `controlsState` at correct frequencies
|
||||||
|
- The UI receives these messages identically to real car data
|
||||||
|
- Blocked processes in bench mode: pandad, thermald, controlsd, radard, plannerd, calibrationd, torqued, paramsd, locationd, sensord, ubloxd, pigeond, dmonitoringmodeld, dmonitoringd, modeld, soundd, camerad, loggerd, micd, dashcamd
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `selfdrive/clearpilot/bench_onroad.py` | Fake vehicle state publisher |
|
||||||
|
| `selfdrive/clearpilot/bench_cmd.py` | Command tool for setting bench params and querying UI |
|
||||||
|
| `selfdrive/clearpilot/ui_dump.py` | Standalone UI widget tree dump |
|
||||||
|
| `selfdrive/manager/process_config.py` | Registers bench_onroad as managed process (enabled=BENCH_MODE) |
|
||||||
|
| `selfdrive/manager/manager.py` | Blocks conflicting processes in bench mode |
|
||||||
|
| `launch_openpilot.sh` | Accepts `--bench` flag, exports BENCH_MODE env var |
|
||||||
|
| `selfdrive/ui/qt/window.cc` | UI RPC server (`ipc:///tmp/clearpilot_ui_rpc`), widget tree dump |
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
- The UI segfaults (exit code -11) when switching to the onroad camera view without a camera feed. The `CameraWidget` / `AnnotatedCameraWidget` crash on empty VisionIPC buffers. camerad is blocked in bench mode, but the `OnroadWindow` still contains a `CameraWidget` that crashes when made visible. Need to either provide fake VisionIPC frames or make the camera widget gracefully handle missing data.
|
||||||
|
- The `showDriverView` function in `home.cc` was previously overriding park/drive transitions every frame. Fixed to only act when not in started+parked state, but transitions to onroad (drive) view still trigger the camera crash.
|
||||||
|
|
||||||
## Session Logging
|
## Session Logging
|
||||||
|
|
||||||
Per-process stderr and an aggregate event log are captured in `/data/log2/{session}/`.
|
Per-process stderr and an aggregate event log are captured in `/data/log2/{session}/`.
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
|||||||
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
|
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
|
||||||
{"SshEnabled", PERSISTENT},
|
{"SshEnabled", PERSISTENT},
|
||||||
{"TermsVersion", PERSISTENT},
|
{"TermsVersion", PERSISTENT},
|
||||||
|
{"TelemetryEnabled", PERSISTENT},
|
||||||
{"Timezone", PERSISTENT},
|
{"Timezone", PERSISTENT},
|
||||||
{"TrainingVersion", PERSISTENT},
|
{"TrainingVersion", PERSISTENT},
|
||||||
{"UbloxAvailable", PERSISTENT},
|
{"UbloxAvailable", PERSISTENT},
|
||||||
@@ -211,6 +212,12 @@ std::unordered_map<std::string, uint32_t> keys = {
|
|||||||
|
|
||||||
// FrogPilot parameters
|
// FrogPilot parameters
|
||||||
{"AccelerationPath", PERSISTENT},
|
{"AccelerationPath", PERSISTENT},
|
||||||
|
{"BenchCruiseSpeed", CLEAR_ON_MANAGER_START},
|
||||||
|
{"ClpUiState", CLEAR_ON_MANAGER_START},
|
||||||
|
{"BenchEngaged", CLEAR_ON_MANAGER_START},
|
||||||
|
{"BenchGear", CLEAR_ON_MANAGER_START},
|
||||||
|
{"BenchSpeed", CLEAR_ON_MANAGER_START},
|
||||||
|
{"BenchSpeedLimit", CLEAR_ON_MANAGER_START},
|
||||||
{"AccelerationProfile", PERSISTENT},
|
{"AccelerationProfile", PERSISTENT},
|
||||||
{"AdjacentPath", PERSISTENT},
|
{"AdjacentPath", PERSISTENT},
|
||||||
{"AdjacentPathMetrics", PERSISTENT},
|
{"AdjacentPathMetrics", PERSISTENT},
|
||||||
|
|||||||
@@ -12,5 +12,10 @@ sleep 1
|
|||||||
|
|
||||||
bash /data/openpilot/system/clearpilot/on_start.sh
|
bash /data/openpilot/system/clearpilot/on_start.sh
|
||||||
|
|
||||||
|
# CLEARPILOT: pass --bench flag through to manager via env var
|
||||||
|
if [ "$1" = "--bench" ]; then
|
||||||
|
export BENCH_MODE=1
|
||||||
|
fi
|
||||||
|
|
||||||
cd /data/openpilot
|
cd /data/openpilot
|
||||||
exec ./launch_chffrplus.sh
|
exec ./launch_chffrplus.sh
|
||||||
|
|||||||
122
selfdrive/clearpilot/bench_cmd.py
Normal file
122
selfdrive/clearpilot/bench_cmd.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
ClearPilot bench command tool. Sets bench params and queries UI state.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd gear D
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd speed 20
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd speedlimit 45
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd cruise 55
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd engaged 1
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd dump
|
||||||
|
python3 -m selfdrive.clearpilot.bench_cmd wait_ready
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import zmq
|
||||||
|
from openpilot.common.params import Params
|
||||||
|
|
||||||
|
|
||||||
|
def check_ui_process():
|
||||||
|
"""Check if UI process is running and healthy. Returns error string or None if OK."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(["pgrep", "-a", "-f", "./ui"], capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return "ERROR: UI process not running"
|
||||||
|
# Get the PID and check its uptime
|
||||||
|
for line in result.stdout.strip().split("\n"):
|
||||||
|
parts = line.split(None, 1)
|
||||||
|
if len(parts) >= 2 and "./ui" in parts[1]:
|
||||||
|
pid = parts[0]
|
||||||
|
try:
|
||||||
|
stat = os.stat(f"/proc/{pid}")
|
||||||
|
uptime = time.time() - stat.st_mtime
|
||||||
|
if uptime < 5:
|
||||||
|
return f"ERROR: UI process (pid {pid}) uptime {uptime:.1f}s — likely crash looping. Check: tail /data/log2/$(ls -t /data/log2/ | head -1)/session.log"
|
||||||
|
except FileNotFoundError:
|
||||||
|
return "ERROR: UI process disappeared"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def ui_dump():
|
||||||
|
ctx = zmq.Context()
|
||||||
|
sock = ctx.socket(zmq.REQ)
|
||||||
|
sock.setsockopt(zmq.RCVTIMEO, 2000)
|
||||||
|
sock.connect("ipc:///tmp/clearpilot_ui_rpc")
|
||||||
|
sock.send_string("dump")
|
||||||
|
try:
|
||||||
|
return sock.recv_string()
|
||||||
|
except zmq.Again:
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
ctx.term()
|
||||||
|
|
||||||
|
|
||||||
|
def wait_ready(timeout=10):
|
||||||
|
"""Wait until the UI shows ReadyWindow, up to timeout seconds."""
|
||||||
|
start = time.time()
|
||||||
|
while time.time() - start < timeout:
|
||||||
|
dump = ui_dump()
|
||||||
|
if dump and "ReadyWindow" in dump:
|
||||||
|
# Check it's actually visible
|
||||||
|
for line in dump.split("\n"):
|
||||||
|
if "ReadyWindow" in line and "vis=Y" in line:
|
||||||
|
print("UI ready (ReadyWindow visible)")
|
||||||
|
return True
|
||||||
|
time.sleep(1)
|
||||||
|
print(f"ERROR: UI not ready after {timeout}s")
|
||||||
|
if dump:
|
||||||
|
print(dump)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(__doc__)
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = sys.argv[1].lower()
|
||||||
|
params = Params("/dev/shm/params")
|
||||||
|
|
||||||
|
param_map = {
|
||||||
|
"gear": "BenchGear",
|
||||||
|
"speed": "BenchSpeed",
|
||||||
|
"speedlimit": "BenchSpeedLimit",
|
||||||
|
"cruise": "BenchCruiseSpeed",
|
||||||
|
"engaged": "BenchEngaged",
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd == "dump":
|
||||||
|
ui_status = check_ui_process()
|
||||||
|
if ui_status:
|
||||||
|
print(ui_status)
|
||||||
|
else:
|
||||||
|
result = ui_dump()
|
||||||
|
if result:
|
||||||
|
print(result)
|
||||||
|
else:
|
||||||
|
print("ERROR: UI not responding")
|
||||||
|
|
||||||
|
elif cmd == "wait_ready":
|
||||||
|
wait_ready()
|
||||||
|
|
||||||
|
elif cmd in param_map:
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print(f"Usage: bench_cmd {cmd} <value>")
|
||||||
|
return
|
||||||
|
value = sys.argv[2]
|
||||||
|
params.put(param_map[cmd], value)
|
||||||
|
print(f"{param_map[cmd]} = {value}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Unknown command: {cmd}")
|
||||||
|
print(__doc__)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
129
selfdrive/clearpilot/bench_onroad.py
Normal file
129
selfdrive/clearpilot/bench_onroad.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
ClearPilot bench onroad simulator.
|
||||||
|
|
||||||
|
Publishes fake messages to make the UI go onroad and display
|
||||||
|
configurable vehicle state. Control values via params in /dev/shm/params:
|
||||||
|
|
||||||
|
BenchSpeed - vehicle speed in mph (default: 0)
|
||||||
|
BenchSpeedLimit - speed limit in mph (default: 0, 0=hidden)
|
||||||
|
BenchCruiseSpeed - cruise set speed in mph (default: 0, 0=not set)
|
||||||
|
BenchGear - P, D, R, N (default: P)
|
||||||
|
BenchEngaged - 0 or 1, cruise engaged (default: 0)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
su - comma -c "PYTHONPATH=/data/openpilot python3 -m selfdrive.clearpilot.bench_onroad"
|
||||||
|
|
||||||
|
To stop: Ctrl+C or kill the process. UI returns to offroad.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
import cereal.messaging as messaging
|
||||||
|
from cereal import log, car
|
||||||
|
from openpilot.common.params import Params
|
||||||
|
|
||||||
|
MS_PER_MPH = 0.44704
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
params = Params()
|
||||||
|
params_mem = Params("/dev/shm/params")
|
||||||
|
|
||||||
|
# Set defaults
|
||||||
|
params_mem.put("BenchSpeed", "0")
|
||||||
|
params_mem.put("BenchSpeedLimit", "0")
|
||||||
|
params_mem.put("BenchCruiseSpeed", "0")
|
||||||
|
params_mem.put("BenchGear", "P")
|
||||||
|
params_mem.put("BenchEngaged", "0")
|
||||||
|
|
||||||
|
pm = messaging.PubMaster([
|
||||||
|
"deviceState", "pandaStates", "carState", "controlsState",
|
||||||
|
"driverMonitoringState", "liveCalibration",
|
||||||
|
])
|
||||||
|
|
||||||
|
print("Bench onroad simulator started")
|
||||||
|
print("Set values in /dev/shm/params/d/Bench*")
|
||||||
|
print(" BenchSpeed=0 BenchSpeedLimit=0 BenchCruiseSpeed=0 BenchGear=P BenchEngaged=0")
|
||||||
|
|
||||||
|
gear_map = {
|
||||||
|
"P": car.CarState.GearShifter.park,
|
||||||
|
"D": car.CarState.GearShifter.drive,
|
||||||
|
"R": car.CarState.GearShifter.reverse,
|
||||||
|
"N": car.CarState.GearShifter.neutral,
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = 0
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Read bench params
|
||||||
|
speed_mph = float((params_mem.get("BenchSpeed", encoding="utf-8") or "0").strip())
|
||||||
|
speed_limit_mph = float((params_mem.get("BenchSpeedLimit", encoding="utf-8") or "0").strip())
|
||||||
|
cruise_mph = float((params_mem.get("BenchCruiseSpeed", encoding="utf-8") or "0").strip())
|
||||||
|
gear_str = (params_mem.get("BenchGear", encoding="utf-8") or "P").strip().upper()
|
||||||
|
engaged = (params_mem.get("BenchEngaged", encoding="utf-8") or "0").strip() == "1"
|
||||||
|
|
||||||
|
speed_ms = speed_mph * MS_PER_MPH
|
||||||
|
gear = gear_map.get(gear_str, car.CarState.GearShifter.park)
|
||||||
|
|
||||||
|
# deviceState — 2 Hz
|
||||||
|
if frame % 5 == 0:
|
||||||
|
dat = messaging.new_message("deviceState")
|
||||||
|
dat.deviceState.started = True
|
||||||
|
dat.deviceState.freeSpacePercent = 80
|
||||||
|
dat.deviceState.memoryUsagePercent = 30
|
||||||
|
dat.deviceState.cpuTempC = [40.0] * 3
|
||||||
|
dat.deviceState.gpuTempC = [40.0] * 3
|
||||||
|
dat.deviceState.cpuUsagePercent = [20] * 8
|
||||||
|
dat.deviceState.networkType = log.DeviceState.NetworkType.cell4G
|
||||||
|
pm.send("deviceState", dat)
|
||||||
|
|
||||||
|
# pandaStates — 10 Hz
|
||||||
|
if frame % 1 == 0:
|
||||||
|
dat = messaging.new_message("pandaStates", 1)
|
||||||
|
dat.pandaStates[0].ignitionLine = True
|
||||||
|
dat.pandaStates[0].pandaType = log.PandaState.PandaType.tres
|
||||||
|
pm.send("pandaStates", dat)
|
||||||
|
|
||||||
|
# carState — 10 Hz
|
||||||
|
dat = messaging.new_message("carState")
|
||||||
|
cs = dat.carState
|
||||||
|
cs.vEgo = speed_ms
|
||||||
|
cs.vEgoCluster = speed_ms
|
||||||
|
cs.gearShifter = gear
|
||||||
|
cs.standstill = speed_ms < 0.1
|
||||||
|
cs.cruiseState.available = True
|
||||||
|
cs.cruiseState.enabled = engaged
|
||||||
|
cs.cruiseState.speed = cruise_mph * MS_PER_MPH if cruise_mph > 0 else 0
|
||||||
|
pm.send("carState", dat)
|
||||||
|
|
||||||
|
# controlsState — 10 Hz
|
||||||
|
dat = messaging.new_message("controlsState")
|
||||||
|
ctl = dat.controlsState
|
||||||
|
ctl.vCruise = cruise_mph * 1.60934 if cruise_mph > 0 else 255 # km/h or 255=not set
|
||||||
|
ctl.vCruiseCluster = ctl.vCruise
|
||||||
|
ctl.enabled = engaged
|
||||||
|
ctl.active = engaged
|
||||||
|
pm.send("controlsState", dat)
|
||||||
|
|
||||||
|
# driverMonitoringState — low freq
|
||||||
|
if frame % 10 == 0:
|
||||||
|
dat = messaging.new_message("driverMonitoringState")
|
||||||
|
dat.driverMonitoringState.isActiveMode = True
|
||||||
|
pm.send("driverMonitoringState", dat)
|
||||||
|
|
||||||
|
# liveCalibration — low freq
|
||||||
|
if frame % 50 == 0:
|
||||||
|
dat = messaging.new_message("liveCalibration")
|
||||||
|
dat.liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated
|
||||||
|
dat.liveCalibration.rpyCalib = [0.0, 0.0, 0.0]
|
||||||
|
pm.send("liveCalibration", dat)
|
||||||
|
|
||||||
|
frame += 1
|
||||||
|
time.sleep(0.1) # 10 Hz base loop
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nBench simulator stopped")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -5,18 +5,28 @@ ClearPilot telemetry collector.
|
|||||||
Receives telemetry packets from any process via ZMQ, diffs against previous
|
Receives telemetry packets from any process via ZMQ, diffs against previous
|
||||||
state per group, and writes only changed values to CSV.
|
state per group, and writes only changed values to CSV.
|
||||||
|
|
||||||
|
Controlled by TelemetryEnabled param — when toggled on, the first packet
|
||||||
|
from each group writes all values (full dump). When toggled off, stops writing.
|
||||||
|
|
||||||
CSV format: timestamp,group,key,value
|
CSV format: timestamp,group,key,value
|
||||||
"""
|
"""
|
||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import time
|
||||||
import zmq
|
import zmq
|
||||||
|
|
||||||
|
from openpilot.common.params import Params
|
||||||
from openpilot.selfdrive.clearpilot.telemetry import TELEMETRY_SOCK
|
from openpilot.selfdrive.clearpilot.telemetry import TELEMETRY_SOCK
|
||||||
from openpilot.selfdrive.manager.process import _log_dir
|
from openpilot.selfdrive.manager.process import _log_dir, session_log
|
||||||
|
|
||||||
|
MIN_DISK_FREE_GB = 5
|
||||||
|
DISK_CHECK_INTERVAL = 10 # seconds
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
params = Params()
|
||||||
ctx = zmq.Context.instance()
|
ctx = zmq.Context.instance()
|
||||||
sock = ctx.socket(zmq.PULL)
|
sock = ctx.socket(zmq.PULL)
|
||||||
sock.setsockopt(zmq.RCVHWM, 1000)
|
sock.setsockopt(zmq.RCVHWM, 1000)
|
||||||
@@ -24,43 +34,80 @@ def main():
|
|||||||
|
|
||||||
csv_path = os.path.join(_log_dir, "telemetry.csv")
|
csv_path = os.path.join(_log_dir, "telemetry.csv")
|
||||||
state: dict[str, dict] = {} # group -> {key: last_value}
|
state: dict[str, dict] = {} # group -> {key: last_value}
|
||||||
|
was_enabled = False
|
||||||
|
writer = None
|
||||||
|
f = None
|
||||||
|
last_disk_check = 0
|
||||||
|
|
||||||
with open(csv_path, "w", newline="") as f:
|
while True:
|
||||||
writer = csv.writer(f)
|
# Check enable state every iteration (cheap param read)
|
||||||
writer.writerow(["timestamp", "group", "key", "value"])
|
enabled = params.get("TelemetryEnabled") == b"1"
|
||||||
f.flush()
|
|
||||||
|
|
||||||
while True:
|
# Check disk space every 10 seconds while enabled
|
||||||
try:
|
if enabled and (time.monotonic() - last_disk_check) > DISK_CHECK_INTERVAL:
|
||||||
raw = sock.recv_string()
|
last_disk_check = time.monotonic()
|
||||||
except zmq.ZMQError:
|
disk = shutil.disk_usage("/data")
|
||||||
continue
|
free_gb = disk.free / (1024 ** 3)
|
||||||
|
if free_gb < MIN_DISK_FREE_GB:
|
||||||
|
session_log.warning("telemetry disabled: disk free %.1f GB < %d GB", free_gb, MIN_DISK_FREE_GB)
|
||||||
|
params.put("TelemetryEnabled", "0")
|
||||||
|
enabled = False
|
||||||
|
|
||||||
try:
|
# Toggled on: open CSV, clear state so first packet is a full dump
|
||||||
pkt = json.loads(raw)
|
if enabled and not was_enabled:
|
||||||
except json.JSONDecodeError:
|
f = open(csv_path, "a", newline="")
|
||||||
continue
|
writer = csv.writer(f)
|
||||||
|
if os.path.getsize(csv_path) == 0:
|
||||||
|
writer.writerow(["timestamp", "group", "key", "value"])
|
||||||
|
state.clear() # force full dump on first packet per group
|
||||||
|
f.flush()
|
||||||
|
|
||||||
ts = pkt.get("ts", 0)
|
# Toggled off: close file
|
||||||
group = pkt.get("group", "")
|
if not enabled and was_enabled:
|
||||||
data = pkt.get("data", {})
|
if f:
|
||||||
|
f.close()
|
||||||
|
f = None
|
||||||
|
writer = None
|
||||||
|
|
||||||
if group not in state:
|
was_enabled = enabled
|
||||||
state[group] = {}
|
|
||||||
|
|
||||||
prev = state[group]
|
# Always drain the socket (even when disabled) to avoid backpressure
|
||||||
changed = False
|
try:
|
||||||
|
raw = sock.recv_string(zmq.NOBLOCK)
|
||||||
|
except zmq.Again:
|
||||||
|
time.sleep(0.01)
|
||||||
|
continue
|
||||||
|
except zmq.ZMQError:
|
||||||
|
time.sleep(0.01)
|
||||||
|
continue
|
||||||
|
|
||||||
for key, value in data.items():
|
if not enabled or writer is None:
|
||||||
# Convert to string for comparison so floats/ints/bools all diff correctly
|
continue
|
||||||
str_val = str(value)
|
|
||||||
if key not in prev or prev[key] != str_val:
|
|
||||||
writer.writerow([f"{ts:.6f}", group, key, str_val])
|
|
||||||
prev[key] = str_val
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
try:
|
||||||
f.flush()
|
pkt = json.loads(raw)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ts = pkt.get("ts", 0)
|
||||||
|
group = pkt.get("group", "")
|
||||||
|
data = pkt.get("data", {})
|
||||||
|
|
||||||
|
if group not in state:
|
||||||
|
state[group] = {}
|
||||||
|
|
||||||
|
prev = state[group]
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
str_val = str(value)
|
||||||
|
if key not in prev or prev[key] != str_val:
|
||||||
|
writer.writerow([f"{ts:.6f}", group, key, str_val])
|
||||||
|
prev[key] = str_val
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
12
selfdrive/clearpilot/ui_dump.py
Normal file
12
selfdrive/clearpilot/ui_dump.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Query the UI process for its current widget tree. Usage: python3 -m selfdrive.clearpilot.ui_dump"""
|
||||||
|
import zmq
|
||||||
|
ctx = zmq.Context()
|
||||||
|
sock = ctx.socket(zmq.REQ)
|
||||||
|
sock.setsockopt(zmq.RCVTIMEO, 2000)
|
||||||
|
sock.connect("ipc:///tmp/clearpilot_ui_rpc")
|
||||||
|
sock.send_string("dump")
|
||||||
|
try:
|
||||||
|
print(sock.recv_string())
|
||||||
|
except zmq.Again:
|
||||||
|
print("ERROR: UI not responding (timeout)")
|
||||||
@@ -79,6 +79,9 @@ def manager_init(frogpilot_functions) -> None:
|
|||||||
params = Params()
|
params = Params()
|
||||||
params_storage = Params("/persist/params")
|
params_storage = Params("/persist/params")
|
||||||
params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
|
params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
|
||||||
|
|
||||||
|
# CLEARPILOT: always start with telemetry disabled
|
||||||
|
params.put("TelemetryEnabled", "0")
|
||||||
params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION)
|
params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION)
|
||||||
params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION)
|
params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION)
|
||||||
if is_release_branch():
|
if is_release_branch():
|
||||||
@@ -155,6 +158,7 @@ def manager_init(frogpilot_functions) -> None:
|
|||||||
("DisableVTSCSmoothing", "0"),
|
("DisableVTSCSmoothing", "0"),
|
||||||
("DisengageVolume", "100"),
|
("DisengageVolume", "100"),
|
||||||
("DashcamDebug", "1"),
|
("DashcamDebug", "1"),
|
||||||
|
("TelemetryEnabled", "0"),
|
||||||
("DragonPilotTune", "0"),
|
("DragonPilotTune", "0"),
|
||||||
("DriverCamera", "0"),
|
("DriverCamera", "0"),
|
||||||
("DynamicPathWidth", "0"),
|
("DynamicPathWidth", "0"),
|
||||||
@@ -396,6 +400,14 @@ def manager_thread(frogpilot_functions) -> None:
|
|||||||
ignore += ["manage_athenad", "uploader"]
|
ignore += ["manage_athenad", "uploader"]
|
||||||
if os.getenv("NOBOARD") is not None:
|
if os.getenv("NOBOARD") is not None:
|
||||||
ignore.append("pandad")
|
ignore.append("pandad")
|
||||||
|
# CLEARPILOT: bench mode — disable real car processes, enable bench simulator
|
||||||
|
if os.getenv("BENCH_MODE") is not None:
|
||||||
|
ignore += ["pandad", "thermald", "controlsd", "radard", "plannerd",
|
||||||
|
"calibrationd", "torqued", "paramsd", "locationd", "sensord",
|
||||||
|
"ubloxd", "pigeond", "dmonitoringmodeld", "dmonitoringd",
|
||||||
|
"modeld", "soundd", "camerad", "loggerd", "micd",
|
||||||
|
"dashcamd"]
|
||||||
|
session_log.info("bench mode enabled")
|
||||||
ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0]
|
ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0]
|
||||||
|
|
||||||
sm = messaging.SubMaster(['deviceState', 'carParams'], poll='deviceState')
|
sm = messaging.SubMaster(['deviceState', 'carParams'], poll='deviceState')
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from openpilot.selfdrive.manager.process import PythonProcess, NativeProcess, Da
|
|||||||
|
|
||||||
|
|
||||||
WEBCAM = os.getenv("USE_WEBCAM") is not None
|
WEBCAM = os.getenv("USE_WEBCAM") is not None
|
||||||
|
BENCH_MODE = os.getenv("BENCH_MODE") is not None
|
||||||
|
|
||||||
def driverview(started: bool, params: Params, CP: car.CarParams) -> bool:
|
def driverview(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||||
return started or params.get_bool("IsDriverViewEnabled")
|
return started or params.get_bool("IsDriverViewEnabled")
|
||||||
@@ -110,6 +111,7 @@ procs = [
|
|||||||
# ClearPilot processes
|
# ClearPilot processes
|
||||||
NativeProcess("dashcamd", "selfdrive/clearpilot", ["./dashcamd"], dashcam_should_run),
|
NativeProcess("dashcamd", "selfdrive/clearpilot", ["./dashcamd"], dashcam_should_run),
|
||||||
PythonProcess("telemetryd", "selfdrive.clearpilot.telemetryd", always_run),
|
PythonProcess("telemetryd", "selfdrive.clearpilot.telemetryd", always_run),
|
||||||
|
PythonProcess("bench_onroad", "selfdrive.clearpilot.bench_onroad", always_run, enabled=BENCH_MODE),
|
||||||
]
|
]
|
||||||
|
|
||||||
managed_processes = {p.name: p for p in procs}
|
managed_processes = {p.name: p for p in procs}
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
#include <QStackedWidget>
|
#include <QStackedWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "common/swaglog.h"
|
||||||
#include "selfdrive/ui/qt/util.h"
|
#include "selfdrive/ui/qt/util.h"
|
||||||
|
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||||
|
|
||||||
// HomeWindow: the container for the offroad and onroad UIs
|
// HomeWindow: the container for the offroad and onroad UIs
|
||||||
|
|
||||||
@@ -25,8 +27,17 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
|||||||
slayout = new QStackedLayout();
|
slayout = new QStackedLayout();
|
||||||
main_layout->addLayout(slayout);
|
main_layout->addLayout(slayout);
|
||||||
|
|
||||||
home = new OffroadHome(this);
|
home = new ClearPilotPanel(this);
|
||||||
QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings);
|
QObject::connect(home, &ClearPilotPanel::openSettings, this, &HomeWindow::openSettings);
|
||||||
|
QObject::connect(home, &ClearPilotPanel::openStatus, this, &HomeWindow::openStatus);
|
||||||
|
QObject::connect(home, &ClearPilotPanel::closePanel, [=]() {
|
||||||
|
// Return to splash or onroad depending on state
|
||||||
|
if (uiState()->scene.started) {
|
||||||
|
slayout->setCurrentWidget(onroad);
|
||||||
|
} else {
|
||||||
|
slayout->setCurrentWidget(ready);
|
||||||
|
}
|
||||||
|
});
|
||||||
slayout->addWidget(home);
|
slayout->addWidget(home);
|
||||||
|
|
||||||
onroad = new OnroadWindow(this);
|
onroad = new OnroadWindow(this);
|
||||||
@@ -67,10 +78,10 @@ void HomeWindow::updateState(const UIState &s) {
|
|||||||
// CLEARPILOT: show splash screen when onroad but in park
|
// CLEARPILOT: show splash screen when onroad but in park
|
||||||
bool parked = s.scene.parked;
|
bool parked = s.scene.parked;
|
||||||
if (parked && !was_parked_onroad) {
|
if (parked && !was_parked_onroad) {
|
||||||
// just shifted into park — show splash
|
LOGW("CLP UI: park transition -> showing splash");
|
||||||
slayout->setCurrentWidget(ready);
|
slayout->setCurrentWidget(ready);
|
||||||
} else if (!parked && was_parked_onroad) {
|
} else if (!parked && was_parked_onroad) {
|
||||||
// just shifted out of park — show onroad camera
|
LOGW("CLP UI: drive transition -> showing onroad");
|
||||||
slayout->setCurrentWidget(onroad);
|
slayout->setCurrentWidget(onroad);
|
||||||
}
|
}
|
||||||
was_parked_onroad = parked;
|
was_parked_onroad = parked;
|
||||||
@@ -90,11 +101,13 @@ void HomeWindow::updateState(const UIState &s) {
|
|||||||
void HomeWindow::offroadTransition(bool offroad) {
|
void HomeWindow::offroadTransition(bool offroad) {
|
||||||
sidebar->setVisible(false);
|
sidebar->setVisible(false);
|
||||||
if (offroad) {
|
if (offroad) {
|
||||||
|
LOGW("CLP UI: offroad transition -> showing splash");
|
||||||
was_parked_onroad = false;
|
was_parked_onroad = false;
|
||||||
slayout->setCurrentWidget(ready);
|
slayout->setCurrentWidget(ready);
|
||||||
} else {
|
} else {
|
||||||
// CLEARPILOT: start onroad in splash — updateState will switch to
|
// CLEARPILOT: start onroad in splash — updateState will switch to
|
||||||
// camera view once the car shifts out of park
|
// camera view once the car shifts out of park
|
||||||
|
LOGW("CLP UI: onroad transition -> showing splash (parked)");
|
||||||
was_parked_onroad = true;
|
was_parked_onroad = true;
|
||||||
slayout->setCurrentWidget(ready);
|
slayout->setCurrentWidget(ready);
|
||||||
}
|
}
|
||||||
@@ -102,34 +115,26 @@ void HomeWindow::offroadTransition(bool offroad) {
|
|||||||
|
|
||||||
void HomeWindow::showDriverView(bool show, bool started) {
|
void HomeWindow::showDriverView(bool show, bool started) {
|
||||||
if (show) {
|
if (show) {
|
||||||
|
LOGW("CLP UI: showDriverView(true) -> driver_view");
|
||||||
emit closeSettings();
|
emit closeSettings();
|
||||||
slayout->setCurrentWidget(driver_view);
|
slayout->setCurrentWidget(driver_view);
|
||||||
sidebar->setVisible(show == false);
|
sidebar->setVisible(false);
|
||||||
} else {
|
} else if (!started) {
|
||||||
if (started) {
|
// Offroad, not started — show home menu
|
||||||
slayout->setCurrentWidget(onroad);
|
slayout->setCurrentWidget(home);
|
||||||
sidebar->setVisible(params.getBool("Sidebar"));
|
sidebar->setVisible(false);
|
||||||
} else {
|
|
||||||
slayout->setCurrentWidget(home);
|
|
||||||
sidebar->setVisible(show == false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// CLEARPILOT: when started, don't touch slayout here —
|
||||||
|
// updateState handles park->splash and drive->onroad transitions
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeWindow::mousePressEvent(QMouseEvent* e) {
|
void HomeWindow::mousePressEvent(QMouseEvent* e) {
|
||||||
// CLEARPILOT todo - tap on main goes straight to settings
|
// CLEARPILOT: tap from any view goes to ClearPilotPanel
|
||||||
// Unless we click a debug widget.
|
if (ready->isVisible() || onroad->isVisible()) {
|
||||||
|
LOGW("CLP UI: tap -> showing ClearPilotPanel");
|
||||||
// CLEARPILOT - click ready shows home (no sidebar)
|
|
||||||
if (!onroad->isVisible() && ready->isVisible()) {
|
|
||||||
sidebar->setVisible(false);
|
sidebar->setVisible(false);
|
||||||
slayout->setCurrentWidget(home);
|
slayout->setCurrentWidget(home);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: widgets
|
|
||||||
if (onroad->isVisible()) {
|
|
||||||
emit openSettings();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
|
void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
|
||||||
@@ -137,59 +142,149 @@ void HomeWindow::mouseDoubleClickEvent(QMouseEvent* e) {
|
|||||||
// const SubMaster &sm = *(uiState()->sm);
|
// const SubMaster &sm = *(uiState()->sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLEARPILOT: OffroadHome — clean grid launcher
|
// CLEARPILOT: ClearPilotPanel — settings-style sidebar menu
|
||||||
|
|
||||||
OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
|
static const char *clpSidebarBtnStyle = R"(
|
||||||
QVBoxLayout* main_layout = new QVBoxLayout(this);
|
QPushButton {
|
||||||
main_layout->setContentsMargins(80, 80, 80, 80);
|
color: grey;
|
||||||
main_layout->setSpacing(0);
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: 65px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
QPushButton:checked {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
color: #ADADAD;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
// grid of launcher buttons
|
ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
|
||||||
QGridLayout *grid = new QGridLayout();
|
// Sidebar
|
||||||
grid->setSpacing(40);
|
QWidget *sidebar_widget = new QWidget;
|
||||||
|
QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget);
|
||||||
|
sidebar_layout->setContentsMargins(50, 50, 100, 50);
|
||||||
|
|
||||||
// Dashcam viewer button
|
// Close button
|
||||||
QPushButton *dashcam_btn = new QPushButton("Dashcam");
|
QPushButton *close_btn = new QPushButton("← Back");
|
||||||
dashcam_btn->setFixedSize(400, 300);
|
close_btn->setStyleSheet(R"(
|
||||||
dashcam_btn->setStyleSheet(R"(
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #333333;
|
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 20px;
|
border-radius: 25px;
|
||||||
font-size: 48px;
|
background: #292929;
|
||||||
font-weight: 600;
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
QPushButton:pressed {
|
QPushButton:pressed {
|
||||||
background-color: #555555;
|
color: #ADADAD;
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
grid->addWidget(dashcam_btn, 0, 0);
|
close_btn->setFixedSize(300, 125);
|
||||||
|
sidebar_layout->addSpacing(10);
|
||||||
|
sidebar_layout->addWidget(close_btn, 0, Qt::AlignRight);
|
||||||
|
QObject::connect(close_btn, &QPushButton::clicked, [=]() { emit closePanel(); });
|
||||||
|
|
||||||
// Settings button
|
// Panel content area
|
||||||
QPushButton *settings_btn = new QPushButton("Settings");
|
panel_widget = new QStackedWidget();
|
||||||
settings_btn->setFixedSize(400, 300);
|
|
||||||
settings_btn->setStyleSheet(R"(
|
// Define panels: sidebar label -> content widget
|
||||||
|
// Home panel: buttons for Status and System Settings
|
||||||
|
QWidget *home_panel = new QWidget(this);
|
||||||
|
QVBoxLayout *home_layout = new QVBoxLayout(home_panel);
|
||||||
|
home_layout->setContentsMargins(50, 25, 50, 25);
|
||||||
|
home_layout->setSpacing(20);
|
||||||
|
|
||||||
|
QPushButton *status_btn = new QPushButton("Status");
|
||||||
|
status_btn->setFixedHeight(120);
|
||||||
|
status_btn->setStyleSheet(R"(
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #333333;
|
background-color: #393939;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 20px;
|
border-radius: 15px;
|
||||||
font-size: 48px;
|
font-size: 50px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
}
|
text-align: left;
|
||||||
QPushButton:pressed {
|
padding-left: 30px;
|
||||||
background-color: #555555;
|
|
||||||
}
|
}
|
||||||
|
QPushButton:pressed { background-color: #4a4a4a; }
|
||||||
)");
|
)");
|
||||||
QObject::connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(); });
|
QObject::connect(status_btn, &QPushButton::clicked, [=]() { emit openStatus(); });
|
||||||
grid->addWidget(settings_btn, 0, 1);
|
home_layout->addWidget(status_btn);
|
||||||
|
|
||||||
main_layout->addStretch();
|
QPushButton *sysset_btn = new QPushButton("System Settings");
|
||||||
main_layout->addLayout(grid);
|
sysset_btn->setFixedHeight(120);
|
||||||
main_layout->addStretch();
|
sysset_btn->setStyleSheet(status_btn->styleSheet());
|
||||||
|
QObject::connect(sysset_btn, &QPushButton::clicked, [=]() { emit openSettings(); });
|
||||||
|
home_layout->addWidget(sysset_btn);
|
||||||
|
|
||||||
|
home_layout->addStretch();
|
||||||
|
|
||||||
|
// Dashcam panel: placeholder
|
||||||
|
QWidget *dashcam_panel = new QWidget(this);
|
||||||
|
QVBoxLayout *dash_layout = new QVBoxLayout(dashcam_panel);
|
||||||
|
dash_layout->setContentsMargins(50, 25, 50, 25);
|
||||||
|
QLabel *dash_label = new QLabel("Dashcam viewer coming soon");
|
||||||
|
dash_label->setStyleSheet("color: grey; font-size: 40px;");
|
||||||
|
dash_label->setAlignment(Qt::AlignCenter);
|
||||||
|
dash_layout->addWidget(dash_label);
|
||||||
|
dash_layout->addStretch();
|
||||||
|
|
||||||
|
// Debug panel
|
||||||
|
ListWidget *debug_panel = new ListWidget(this);
|
||||||
|
debug_panel->setContentsMargins(50, 25, 50, 25);
|
||||||
|
|
||||||
|
auto *telemetry_toggle = new ParamControl("TelemetryEnabled", "Telemetry Logging",
|
||||||
|
"Record telemetry data to CSV in the session log directory. "
|
||||||
|
"Captures only changed values for efficiency.", "", this);
|
||||||
|
debug_panel->addItem(telemetry_toggle);
|
||||||
|
|
||||||
|
// Register panels with sidebar buttons
|
||||||
|
QList<QPair<QString, QWidget *>> panels = {
|
||||||
|
{"Home", home_panel},
|
||||||
|
{"Dashcam", dashcam_panel},
|
||||||
|
{"Debug", debug_panel},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &[name, panel] : panels) {
|
||||||
|
QPushButton *btn = new QPushButton(name);
|
||||||
|
btn->setCheckable(true);
|
||||||
|
btn->setStyleSheet(clpSidebarBtnStyle);
|
||||||
|
btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
|
sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
|
||||||
|
|
||||||
|
ScrollView *panel_frame = new ScrollView(panel, this);
|
||||||
|
panel_widget->addWidget(panel_frame);
|
||||||
|
|
||||||
|
QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() {
|
||||||
|
btn->setChecked(true);
|
||||||
|
panel_widget->setCurrentWidget(w);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select Home by default
|
||||||
|
if (auto *first_btn = sidebar_widget->findChild<QPushButton *>(QString(), Qt::FindDirectChildrenOnly)) {
|
||||||
|
// Skip close_btn, find first sidebar btn
|
||||||
|
}
|
||||||
|
panel_widget->setCurrentIndex(0);
|
||||||
|
|
||||||
|
// Main layout: sidebar + panels
|
||||||
|
QHBoxLayout *main_layout = new QHBoxLayout(this);
|
||||||
|
sidebar_widget->setFixedWidth(500);
|
||||||
|
main_layout->addWidget(sidebar_widget);
|
||||||
|
main_layout->addWidget(panel_widget);
|
||||||
|
|
||||||
setStyleSheet(R"(
|
setStyleSheet(R"(
|
||||||
OffroadHome {
|
* {
|
||||||
|
color: white;
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
ClearPilotPanel {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
QStackedWidget, ScrollView {
|
||||||
|
background-color: #292929;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QStackedLayout>
|
#include <QStackedLayout>
|
||||||
|
#include <QStackedWidget>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
@@ -16,14 +17,19 @@
|
|||||||
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
|
#include "selfdrive/ui/qt/widgets/offroad_alerts.h"
|
||||||
#include "selfdrive/ui/ui.h"
|
#include "selfdrive/ui/ui.h"
|
||||||
|
|
||||||
class OffroadHome : public QFrame {
|
class ClearPilotPanel : public QFrame {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit OffroadHome(QWidget* parent = 0);
|
explicit ClearPilotPanel(QWidget* parent = 0);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void openSettings(int index = 0, const QString ¶m = "");
|
void openSettings(int index = 0, const QString ¶m = "");
|
||||||
|
void openStatus();
|
||||||
|
void closePanel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QStackedWidget *panel_widget;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HomeWindow : public QWidget {
|
class HomeWindow : public QWidget {
|
||||||
@@ -35,6 +41,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void openSettings(int index = 0, const QString ¶m = "");
|
void openSettings(int index = 0, const QString ¶m = "");
|
||||||
|
void openStatus();
|
||||||
void closeSettings();
|
void closeSettings();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -49,7 +56,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Sidebar *sidebar;
|
Sidebar *sidebar;
|
||||||
OffroadHome *home;
|
ClearPilotPanel *home;
|
||||||
OnroadWindow *onroad;
|
OnroadWindow *onroad;
|
||||||
DriverViewWindow *driver_view;
|
DriverViewWindow *driver_view;
|
||||||
QStackedLayout *slayout;
|
QStackedLayout *slayout;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
|
||||||
@@ -402,6 +403,17 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
|||||||
Hardware::set_display_power(true);
|
Hardware::set_display_power(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CLEARPILOT: blinking red circle when telemetry is recording
|
||||||
|
if (Params().get("TelemetryEnabled") == "1") {
|
||||||
|
// Blink: visible for 500ms, hidden for 500ms
|
||||||
|
int phase = (QDateTime::currentMSecsSinceEpoch() / 500) % 2;
|
||||||
|
if (phase == 0) {
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(QColor(220, 30, 30));
|
||||||
|
p.drawEllipse(width() - 150, 50, 100, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Header gradient
|
// Header gradient
|
||||||
QLinearGradient bg(0, UI_HEADER_HEIGHT - (UI_HEADER_HEIGHT / 2.5), 0, UI_HEADER_HEIGHT);
|
QLinearGradient bg(0, UI_HEADER_HEIGHT - (UI_HEADER_HEIGHT / 2.5), 0, UI_HEADER_HEIGHT);
|
||||||
bg.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.45));
|
bg.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.45));
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,9 @@
|
|||||||
#include "selfdrive/ui/qt/window.h"
|
#include "selfdrive/ui/qt/window.h"
|
||||||
|
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
#include <zmq.h>
|
||||||
|
|
||||||
#include "system/hardware/hw.h"
|
#include "system/hardware/hw.h"
|
||||||
|
|
||||||
@@ -11,6 +14,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
|
|||||||
homeWindow = new HomeWindow(this);
|
homeWindow = new HomeWindow(this);
|
||||||
main_layout->addWidget(homeWindow);
|
main_layout->addWidget(homeWindow);
|
||||||
QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings);
|
QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings);
|
||||||
|
QObject::connect(homeWindow, &HomeWindow::openStatus, this, &MainWindow::openStatus);
|
||||||
QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings);
|
QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings);
|
||||||
|
|
||||||
settingsWindow = new SettingsWindow(this);
|
settingsWindow = new SettingsWindow(this);
|
||||||
@@ -24,6 +28,11 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
|
|||||||
homeWindow->showDriverView(true);
|
homeWindow->showDriverView(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CLEARPILOT: Status window
|
||||||
|
statusWindow = new StatusWindow(this);
|
||||||
|
main_layout->addWidget(statusWindow);
|
||||||
|
QObject::connect(statusWindow, &StatusWindow::closeStatus, this, &MainWindow::closeSettings);
|
||||||
|
|
||||||
onboardingWindow = new OnboardingWindow(this);
|
onboardingWindow = new OnboardingWindow(this);
|
||||||
main_layout->addWidget(onboardingWindow);
|
main_layout->addWidget(onboardingWindow);
|
||||||
QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=]() {
|
QObject::connect(onboardingWindow, &OnboardingWindow::onboardingDone, [=]() {
|
||||||
@@ -35,11 +44,14 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
|
|||||||
|
|
||||||
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
|
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
|
||||||
if (!offroad) {
|
if (!offroad) {
|
||||||
closeSettings();
|
// CLEARPILOT: just switch to homeWindow, don't show sidebar
|
||||||
|
// HomeWindow::offroadTransition handles the internal view
|
||||||
|
main_layout->setCurrentWidget(homeWindow);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
QObject::connect(device(), &Device::interactiveTimeout, [=]() {
|
QObject::connect(device(), &Device::interactiveTimeout, [=]() {
|
||||||
if (main_layout->currentWidget() == settingsWindow) {
|
if (main_layout->currentWidget() == settingsWindow ||
|
||||||
|
main_layout->currentWidget() == statusWindow) {
|
||||||
closeSettings();
|
closeSettings();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -63,6 +75,74 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
|
|||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
setAttribute(Qt::WA_NoSystemBackground);
|
setAttribute(Qt::WA_NoSystemBackground);
|
||||||
|
|
||||||
|
// CLEARPILOT: UI introspection RPC server
|
||||||
|
zmq_ctx = zmq_ctx_new();
|
||||||
|
zmq_sock = zmq_socket(zmq_ctx, ZMQ_REP);
|
||||||
|
zmq_bind(zmq_sock, "ipc:///tmp/clearpilot_ui_rpc");
|
||||||
|
int fd;
|
||||||
|
size_t fd_sz = sizeof(fd);
|
||||||
|
zmq_getsockopt(zmq_sock, ZMQ_FD, &fd, &fd_sz);
|
||||||
|
rpc_notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
|
||||||
|
connect(rpc_notifier, &QSocketNotifier::activated, this, &MainWindow::handleRpcRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::handleRpcRequest() {
|
||||||
|
int events = 0;
|
||||||
|
size_t events_sz = sizeof(events);
|
||||||
|
zmq_getsockopt(zmq_sock, ZMQ_EVENTS, &events, &events_sz);
|
||||||
|
if (!(events & ZMQ_POLLIN)) return;
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
int rc = zmq_recv(zmq_sock, buf, sizeof(buf) - 1, ZMQ_DONTWAIT);
|
||||||
|
if (rc < 0) return;
|
||||||
|
buf[rc] = 0;
|
||||||
|
|
||||||
|
QString response;
|
||||||
|
if (strcmp(buf, "dump") == 0) {
|
||||||
|
response = dumpWidgetTree(this);
|
||||||
|
} else {
|
||||||
|
response = "unknown command";
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray resp = response.toUtf8();
|
||||||
|
zmq_send(zmq_sock, resp.data(), resp.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MainWindow::dumpWidgetTree(QWidget *w, int depth) {
|
||||||
|
QString result;
|
||||||
|
QString indent(depth * 2, ' ');
|
||||||
|
QString className = w->metaObject()->className();
|
||||||
|
QString name = w->objectName().isEmpty() ? "(no name)" : w->objectName();
|
||||||
|
bool visible = w->isVisible();
|
||||||
|
QRect geo = w->geometry();
|
||||||
|
|
||||||
|
result += QString("%1%2 [%3] vis=%4 geo=%5,%6 %7x%8")
|
||||||
|
.arg(indent, className, name)
|
||||||
|
.arg(visible ? "Y" : "N")
|
||||||
|
.arg(geo.x()).arg(geo.y()).arg(geo.width()).arg(geo.height());
|
||||||
|
|
||||||
|
// Show stacked layout/widget current index
|
||||||
|
if (auto *sl = w->findChild<QStackedLayout *>(QString(), Qt::FindDirectChildrenOnly)) {
|
||||||
|
QWidget *cur = sl->currentWidget();
|
||||||
|
QString curClass = cur ? cur->metaObject()->className() : "null";
|
||||||
|
result += QString(" stack_cur=%1/%2(%3)").arg(sl->currentIndex()).arg(sl->count()).arg(curClass);
|
||||||
|
}
|
||||||
|
if (auto *sw = qobject_cast<QStackedWidget *>(w)) {
|
||||||
|
QWidget *cur = sw->currentWidget();
|
||||||
|
QString curClass = cur ? cur->metaObject()->className() : "null";
|
||||||
|
result += QString(" stack_cur=%1/%2(%3)").arg(sw->currentIndex()).arg(sw->count()).arg(curClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "\n";
|
||||||
|
|
||||||
|
for (QObject *child : w->children()) {
|
||||||
|
QWidget *cw = qobject_cast<QWidget *>(child);
|
||||||
|
if (cw && depth < 4) {
|
||||||
|
result += dumpWidgetTree(cw, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::openSettings(int index, const QString ¶m) {
|
void MainWindow::openSettings(int index, const QString ¶m) {
|
||||||
@@ -70,6 +150,10 @@ void MainWindow::openSettings(int index, const QString ¶m) {
|
|||||||
settingsWindow->setCurrentPanel(index, param);
|
settingsWindow->setCurrentPanel(index, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::openStatus() {
|
||||||
|
main_layout->setCurrentWidget(statusWindow);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::closeSettings() {
|
void MainWindow::closeSettings() {
|
||||||
main_layout->setCurrentWidget(homeWindow);
|
main_layout->setCurrentWidget(homeWindow);
|
||||||
|
|
||||||
@@ -96,3 +180,138 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
|
|||||||
}
|
}
|
||||||
return ignore;
|
return ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CLEARPILOT: Status window — live system stats, refreshed every second
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
static QString readFile(const QString &path) {
|
||||||
|
QFile f(path);
|
||||||
|
if (f.open(QIODevice::ReadOnly)) return QString(f.readAll()).trimmed();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString shellCmd(const QString &cmd) {
|
||||||
|
QProcess p;
|
||||||
|
p.start("bash", QStringList() << "-c" << cmd);
|
||||||
|
p.waitForFinished(1000);
|
||||||
|
return QString(p.readAllStandardOutput()).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusWindow::StatusWindow(QWidget *parent) : QFrame(parent) {
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||||
|
layout->setContentsMargins(50, 60, 50, 40);
|
||||||
|
layout->setSpacing(0);
|
||||||
|
|
||||||
|
// Status rows
|
||||||
|
auto makeRow = [&](const QString &label) -> QLabel* {
|
||||||
|
QHBoxLayout *row = new QHBoxLayout();
|
||||||
|
row->setContentsMargins(20, 0, 20, 0);
|
||||||
|
|
||||||
|
QLabel *name = new QLabel(label);
|
||||||
|
name->setStyleSheet("color: grey; font-size: 38px;");
|
||||||
|
name->setFixedWidth(350);
|
||||||
|
row->addWidget(name);
|
||||||
|
|
||||||
|
QLabel *value = new QLabel("—");
|
||||||
|
value->setStyleSheet("color: white; font-size: 38px;");
|
||||||
|
row->addWidget(value);
|
||||||
|
row->addStretch();
|
||||||
|
|
||||||
|
layout->addLayout(row);
|
||||||
|
layout->addSpacing(12);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
time_label = makeRow("Time");
|
||||||
|
storage_label = makeRow("Storage");
|
||||||
|
ram_label = makeRow("Memory");
|
||||||
|
load_label = makeRow("Load");
|
||||||
|
ip_label = makeRow("IP Address");
|
||||||
|
wifi_label = makeRow("WiFi");
|
||||||
|
vpn_label = makeRow("VPN");
|
||||||
|
gps_label = makeRow("GPS");
|
||||||
|
telemetry_label = makeRow("Telemetry");
|
||||||
|
|
||||||
|
layout->addStretch();
|
||||||
|
|
||||||
|
setStyleSheet("StatusWindow { background-color: black; }");
|
||||||
|
|
||||||
|
// Refresh every second
|
||||||
|
QTimer *timer = new QTimer(this);
|
||||||
|
connect(timer, &QTimer::timeout, this, &StatusWindow::refresh);
|
||||||
|
timer->start(1000);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusWindow::refresh() {
|
||||||
|
// Time
|
||||||
|
time_label->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
QString df = shellCmd("df -h /data | tail -1 | awk '{print $3 \" / \" $2 \" (\" $5 \" used)\"}'");
|
||||||
|
storage_label->setText(df);
|
||||||
|
|
||||||
|
// RAM
|
||||||
|
QString meminfo = readFile("/proc/meminfo");
|
||||||
|
long total = 0, avail = 0;
|
||||||
|
for (const QString &line : meminfo.split('\n')) {
|
||||||
|
if (line.startsWith("MemTotal:")) total = line.split(QRegExp("\\s+"))[1].toLong();
|
||||||
|
if (line.startsWith("MemAvailable:")) avail = line.split(QRegExp("\\s+"))[1].toLong();
|
||||||
|
}
|
||||||
|
if (total > 0) {
|
||||||
|
long used = total - avail;
|
||||||
|
ram_label->setText(QString("%1 / %2 MB").arg(used / 1024).arg(total / 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load
|
||||||
|
QString loadavg = readFile("/proc/loadavg");
|
||||||
|
QStringList parts = loadavg.split(' ');
|
||||||
|
if (parts.size() >= 3) {
|
||||||
|
load_label->setText(QString("%1 %2 %3").arg(parts[0], parts[1], parts[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP + WiFi
|
||||||
|
QString ip = shellCmd("ip route get 1.1.1.1 2>/dev/null | head -1 | awk '{print $7}'");
|
||||||
|
ip_label->setText(ip.isEmpty() ? "No connection" : ip);
|
||||||
|
|
||||||
|
QString essid = shellCmd("iwconfig wlan0 2>/dev/null | grep -oP 'ESSID:\"\\K[^\"]*'");
|
||||||
|
wifi_label->setText(essid.isEmpty() ? "Not connected" : essid);
|
||||||
|
|
||||||
|
// VPN
|
||||||
|
QString tun = shellCmd("ip link show tun0 2>/dev/null | head -1");
|
||||||
|
if (tun.contains("UP")) {
|
||||||
|
QString vpn_ip = shellCmd("ip addr show tun0 2>/dev/null | grep 'inet ' | awk '{print $2}'");
|
||||||
|
vpn_label->setText("Connected (" + vpn_ip + ")");
|
||||||
|
vpn_label->setStyleSheet("color: #17c44d; font-size: 38px;");
|
||||||
|
} else {
|
||||||
|
vpn_label->setText("Not connected");
|
||||||
|
vpn_label->setStyleSheet("color: #ff4444; font-size: 38px;");
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPS
|
||||||
|
QString gps_raw = shellCmd("cat /data/params/d/LastGPSPosition 2>/dev/null");
|
||||||
|
if (gps_raw.isEmpty()) {
|
||||||
|
gps_label->setText("No fix");
|
||||||
|
gps_label->setStyleSheet("color: #ff4444; font-size: 38px;");
|
||||||
|
} else {
|
||||||
|
gps_label->setText(gps_raw);
|
||||||
|
gps_label->setStyleSheet("color: white; font-size: 38px;");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Telemetry
|
||||||
|
QString telem = shellCmd("cat /data/params/d/TelemetryEnabled 2>/dev/null");
|
||||||
|
if (telem == "1") {
|
||||||
|
telemetry_label->setText("Enabled");
|
||||||
|
telemetry_label->setStyleSheet("color: #17c44d; font-size: 38px;");
|
||||||
|
} else {
|
||||||
|
telemetry_label->setText("Disabled");
|
||||||
|
telemetry_label->setStyleSheet("color: grey; font-size: 38px;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusWindow::mousePressEvent(QMouseEvent *e) {
|
||||||
|
emit closeStatus();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,42 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QStackedLayout>
|
#include <QStackedLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
#include <QTimer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "selfdrive/ui/qt/home.h"
|
#include "selfdrive/ui/qt/home.h"
|
||||||
#include "selfdrive/ui/qt/offroad/onboarding.h"
|
#include "selfdrive/ui/qt/offroad/onboarding.h"
|
||||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||||
|
|
||||||
|
class StatusWindow : public QFrame {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit StatusWindow(QWidget *parent = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeStatus();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *storage_label;
|
||||||
|
QLabel *ram_label;
|
||||||
|
QLabel *load_label;
|
||||||
|
QLabel *ip_label;
|
||||||
|
QLabel *wifi_label;
|
||||||
|
QLabel *vpn_label;
|
||||||
|
QLabel *gps_label;
|
||||||
|
QLabel *time_label;
|
||||||
|
QLabel *telemetry_label;
|
||||||
|
};
|
||||||
|
|
||||||
class MainWindow : public QWidget {
|
class MainWindow : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -16,13 +46,24 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
void openSettings(int index = 0, const QString ¶m = "");
|
void openSettings(int index = 0, const QString ¶m = "");
|
||||||
|
void openStatus();
|
||||||
void closeSettings();
|
void closeSettings();
|
||||||
|
QString dumpWidgetTree(QWidget *w, int depth = 0);
|
||||||
|
|
||||||
QStackedLayout *main_layout;
|
QStackedLayout *main_layout;
|
||||||
HomeWindow *homeWindow;
|
HomeWindow *homeWindow;
|
||||||
SettingsWindow *settingsWindow;
|
SettingsWindow *settingsWindow;
|
||||||
|
StatusWindow *statusWindow;
|
||||||
OnboardingWindow *onboardingWindow;
|
OnboardingWindow *onboardingWindow;
|
||||||
|
|
||||||
|
// CLEARPILOT: UI introspection RPC
|
||||||
|
void *zmq_ctx = nullptr;
|
||||||
|
void *zmq_sock = nullptr;
|
||||||
|
QSocketNotifier *rpc_notifier = nullptr;
|
||||||
|
|
||||||
// FrogPilot variables
|
// FrogPilot variables
|
||||||
Params params;
|
Params params;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleRpcRequest();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user