reset to pre-modification baseline; restart feature work from clean state

Restoring the working tree to the pristine pre-Claude baseline previously
preserved at /data/clearpilot (now /data/clearpilot-baseline). The prior
modified-but-broken tree is snapshotted at /data/openpilot-broken-2026-05-03
and tagged here as pre-reset-2026-05-03 for reference.

From here, features (UI changes, dashcam, telemetry, GPS, display modes,
speed logic, standstill power saving, etc.) will be re-introduced one at
a time with proper testing.
This commit is contained in:
2026-05-03 20:53:51 -05:00
parent f7e602c00b
commit c2ab0fa662
146 changed files with 950 additions and 10172 deletions
+2 -12
View File
@@ -7,27 +7,17 @@
#include "system/hardware/hw.h"
int main(int argc, char *argv[]) {
fprintf(stderr, "camerad: starting\n");
if (Hardware::PC()) {
fprintf(stderr, "camerad: exiting, not meant to run on PC\n");
printf("exiting, camerad is not meant to run on PC\n");
return 0;
}
int ret;
fprintf(stderr, "camerad: setting realtime priority 53\n");
ret = util::set_realtime_priority(53);
fprintf(stderr, "camerad: set_realtime_priority ret=%d\n", ret);
assert(ret == 0);
fprintf(stderr, "camerad: setting core affinity to cpu6\n");
ret = util::set_core_affinity({6});
bool isOffroad = Params().getBool("IsOffroad");
fprintf(stderr, "camerad: set_core_affinity ret=%d, IsOffroad=%d\n", ret, isOffroad);
assert(ret == 0 || isOffroad); // failure ok while offroad due to offlining cores
assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores
fprintf(stderr, "camerad: starting camerad_thread\n");
camerad_thread();
fprintf(stderr, "camerad: camerad_thread returned\n");
return 0;
}
View File
-2
View File
@@ -1,5 +1,3 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQtHTzkeRlOXDWyK/IvO2RgjSdoq6V81u3YtcyIxBZVX2zCj1xzE9zWcUcVxloe63rB/DBasChODIRBtp1vGnWb/EkLWAuOqS2V5rzhlcSfM103++TI81e04A7HDspWSNUXRh5OD/mUvwtYIH7S4QAkBiCro5lAgSToXNAOR4b4cXgNQecf+RhPc0Nm3K8Is1wEeQajmlC1E22YWBDDV+uoB3Uagl90e58Psbp8PunCdbeY9EfqQsymyloiTeqzKwHnmHnMXSlZluh7A+ifoKgohDsarT1FixAgxT0LSIxxINORhE4P6em/7y3xpgubPhNpbuQSzDlb3op3fwMoFcAEEYKWg+d9OGOrdiWa13aV0g7UNdW/XmmF/BAaBdSOZeomVNnxmftmmJWfu3jtFdwTDRQpZn7nDYC+aZ1R3Q0Xd4lLuqkA/9smUXLZuiBDJXwM5nDyWQR9tESIwlTLcdKAUpj0gQqpcozVehksNksTekZBAg/mYb6DKyYCTY0ti0=
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCm/Vq50kqf94allqGq9luBGjDh2Do/rOCA719CRlDOErCvdY+ZaYNumQZ5AbFfU5KcPZwirJLBvhEoH/G0lEAg9TUaUgH/VvqBBztlpcmA1eplZHzEFLnTDn0oO4Tk46bXwjL0anOZfNaUGhbaO4Th7m+9+o16WUduEabPiyVbnqD6P44CANsvBJNKlyUDBzsdkE9z5gULp06i1+JqqXiGV81HoFWZe5YCFv4j4QUPvfmFhcBHViVrOFs87hS4Eu0gWNxQmQBhh6R1ZbjaBlGdE5GyDZQZwlofjfuO06e0HvCDuIAELSYqlGFCmUhlM/LZ6YkF79/HFrg5sS3gsuY5
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHbrOZrByUb2Ci21DdJkhWv/4Bz4oghL9joraQYFq4Om
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkbLtbZ6jRwmvYiAtXDp7JZ+IBVJIrxY3ZPJ93aQCha root@concordia
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCWTdewQ+jCUj9+ZJfte5h0meZhXayd7owIGyXHV0ELCeRAyQrurBPsdTTr7QhcugVuibI+Tr8L3BNuCN8nID5DH+BFAUejulGyMEmQ4Vh22p6Nt0niJHkfiJ2stfayPqe3qGRScVCcY3TpQqlzjyBWOvtwI9/118Gq/lKsjN7DYVwVMHhe1Yzh4SDHOKpsnmDfguRvCSzsg3ZeR1AduaqqM2y0sLZ09Cjpj/vJC/QQ2q2EWtzfmQPejFtjdbqvgoDEQ1OttD5dBgwCMOTPmPMmJ5ns6YJ4L+bi6hynO4/xn3efSHS2mSC6ACwiD/LtTMpsjbUVQsJ4pM/5GY08UoIdnH7P1N+6DA/hah+KAJe8U3WznT6OSXdwWXnYyV+hx4VHBz+/3MnbB1CwtoZoJDnQVpnT3IxBK+pnLHzZJh/g+bFrFbbAC50MRDsoV8nbYvHG3HJQ5GvK96S5NvllGTC/6zo/39ARvfrGtK/2NgNU+NZRjNN67cXjgcUIRdu6eJs= root@concordia
+6 -8
View File
@@ -1,19 +1,17 @@
#!/bin/bash
# Uses hardware serial as identity check and encryption key
serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline)
if [[ $serial != 3889765b ]]; then
echo "Wrong device (serial=$serial)"
dongle_id=$(cat /data/params/d/DongleId)
if [[ ! $dongle_id == 90bb71* ]]; then
exit 1
fi
# Encrypt SSH keys if source files exist using the custom encrypt tool
if [ -f /data/openpilot/system/clearpilot/dev/id_ed25519.pub ]; then
bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_ed25519.pub /data/openpilot/system/clearpilot/dev/id_ed25519.pub.cpt
if [ -f /data/openpilot/system/clearpilot/dev/id_rsa.pub ]; then
bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_rsa.pub /data/openpilot/system/clearpilot/dev/id_rsa.pub.cpt
fi
if [ -f /data/openpilot/system/clearpilot/dev/id_ed25519 ]; then
bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_ed25519 /data/openpilot/system/clearpilot/dev/id_ed25519.cpt
if [ -f /data/openpilot/system/clearpilot/dev/id_rsa ]; then
bash /data/openpilot/system/clearpilot/tools/encrypt /data/openpilot/system/clearpilot/dev/id_rsa /data/openpilot/system/clearpilot/dev/id_rsa.cpt
fi
if [ -f /data/openpilot/system/clearpilot/dev/reverse_ssh ]; then
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
•í-À‘-j¦ñqã A†3ä"|}ôÚÁñžš.\ñ`þQ¥¶ßA^´­Ð×~LìbýÊ ÞÔm!Òzï[®Wí(¯«rýfo¼ À˜¦Miê[&ÄoúÏV=ˆQ"2A“i 8ÐpÀ­"Á!þ1­“æ–G:š4ïá<-Ý
#
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# tmp for debugging
date >> /tmp/dongles
echo check dongle >> /tmp/dongles
cat /data/params/d/DongleId >> /tmp/dongles
echo done >> /tmp/dongles
dongle_id=$(cat /data/params/d/DongleId)
if [[ ! $dongle_id == 90bb71* ]]; then
exit 1
fi
echo Bringing up brian dev environment
bash /data/openpilot/system/clearpilot/dev/provision.sh
bash /data/openpilot/system/clearpilot/dev/on_start_brian.sh
+2
View File
@@ -0,0 +1,2 @@
•Í’T4üoŠd¿á¹³€å–³!qús^§‘1EŒ½—ðÓÉQ¯Åe|0b.7ša|Þ¶$Âï)x‰ ÷9Sü8BÌQÛ÷ øÃ;TÝ`~?Q!hj2ÔŒwq ô/[´ Xðt¬Ç5‡ü,«Ëñm¾^v¯$vf‚ÇH°)J½A
²W°n`<@’‹.¬ç&>­&}m8˜‰àÃ;½\$^`Aª›Œ
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# Provision script for BrianBot
# These actions only occur on BrianBot's comma device.
# 1. Check the string in /data/params/d/DongleId
dongle_id=$(cat /data/params/d/DongleId)
if [[ ! $dongle_id == 90bb71* ]]; then
exit 1
fi
echo "BrianBot dongle ID detected."
# 2. Check if ccrypt is installed, install if not
if ! command -v ccrypt >/dev/null 2>&1; then
echo "Installing ccrypt..."
sudo apt-get update
sudo apt-get install -y ccrypt
fi
# 3. Decrypt SSH keys if they have not been decrypted yet
if [ ! -f /data/openpilot/system/clearpilot/dev/id_rsa.pub ]; then
echo "Decrypting SSH keys..."
bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_rsa.pub.cpt /data/openpilot/system/clearpilot/dev/id_rsa.pub
bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_rsa.cpt /data/openpilot/system/clearpilot/dev/id_rsa
bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/on_start_brian.sh.cpt /data/openpilot/system/clearpilot/dev/on_start_brian.sh
fi
# 4. Ensure .ssh directory and keys exist
ssh_dir="/data/ssh/.ssh"
if [[ ! -f "$ssh_dir/id_rsa" || ! -f "$ssh_dir/id_rsa.pub" ]]; then
echo "Setting up SSH directory and keys..."
mkdir -p "$ssh_dir"
cp /data/openpilot/system/clearpilot/dev/id_rsa /data/openpilot/system/clearpilot/dev/id_rsa.pub "$ssh_dir"
chmod 700 "$ssh_dir"
chmod 600 "$ssh_dir/id_rsa" "$ssh_dir/id_rsa.pub"
echo hansonxyz > /data/params/d/GithubUsername
cat /data/openpilot/system/clearpilot/dev/GithubSshKeys > /data/params/d/GithubSshKeys
echo 1 > /data/params/d/SshEnabled
sudo systemctl restart ssh
cd /data/openpilot
git remote remove origin
git remote add origin git@privategit.hanson.xyz:brianhansonxyz/clearpilot.git
fi
echo "Script execution complete."
-258
View File
@@ -1,258 +0,0 @@
#!/usr/bin/env python3
"""
ClearPilot GPS daemon — reads GPS from Quectel EC25 modem via AT commands
and publishes gpsLocation messages for locationd/timed.
Replaces qcomgpsd which uses the diag interface (broken on this device).
"""
import math
import os
import subprocess
import time
import datetime
from cereal import log
import cereal.messaging as messaging
from openpilot.common.gpio import gpio_init, gpio_set
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.common.time import system_time_valid
from openpilot.system.hardware.tici.pins import GPIO
def _sunrise_sunset_min(lat: float, lon: float, utc_dt: datetime.datetime):
"""Compute (sunrise_min, sunset_min) in UTC minutes since midnight of utc_dt's day.
Values can be negative or >1440 for western/eastern longitudes. Returns
(None, None) for polar night, ('always', 'always') for midnight sun."""
n = utc_dt.timetuple().tm_yday
gamma = 2 * math.pi / 365 * (n - 1 + (utc_dt.hour - 12) / 24)
eqtime = 229.18 * (0.000075 + 0.001868 * math.cos(gamma)
- 0.032077 * math.sin(gamma)
- 0.014615 * math.cos(2 * gamma)
- 0.040849 * math.sin(2 * gamma))
decl = (0.006918 - 0.399912 * math.cos(gamma)
+ 0.070257 * math.sin(gamma)
- 0.006758 * math.cos(2 * gamma)
+ 0.000907 * math.sin(2 * gamma)
- 0.002697 * math.cos(3 * gamma)
+ 0.00148 * math.sin(3 * gamma))
lat_rad = math.radians(lat)
zenith = math.radians(90.833)
cos_ha = (math.cos(zenith) / (math.cos(lat_rad) * math.cos(decl))
- math.tan(lat_rad) * math.tan(decl))
if cos_ha < -1:
return ('always', 'always') # midnight sun
if cos_ha > 1:
return (None, None) # polar night
ha = math.degrees(math.acos(cos_ha))
sunrise_min = 720 - 4 * (lon + ha) - eqtime
sunset_min = 720 - 4 * (lon - ha) - eqtime
return (sunrise_min, sunset_min)
def is_daylight(lat: float, lon: float, utc_dt: datetime.datetime) -> bool:
"""Return True if the sun is currently above the horizon at (lat, lon).
Handles west-of-Greenwich correctly: at UTC midnight it may still be
evening local time, and the relevant sunset is the PREVIOUS UTC day's
value (which is >1440 min if we re-ref to that day, i.e. it's past
midnight UTC). Symmetric case for east-of-Greenwich at the other end.
Strategy: compute sunrise/sunset for yesterday, today, and tomorrow (each
relative to its own UTC midnight), shift them all onto today's clock
(yesterday = -1440, tomorrow = +1440), and check if now_min falls inside
any of the three [sunrise, sunset] intervals.
"""
now_min = utc_dt.hour * 60 + utc_dt.minute + utc_dt.second / 60
for day_offset in (-1, 0, 1):
d = utc_dt + datetime.timedelta(days=day_offset)
sr, ss = _sunrise_sunset_min(lat, lon, d)
if sr == 'always':
return True
if sr is None:
continue # polar night this day
sr += day_offset * 1440
ss += day_offset * 1440
if sr <= now_min <= ss:
return True
return False
def at_cmd(cmd: str) -> str:
try:
result = subprocess.check_output(
f"mmcli -m any --timeout 10 --command='{cmd}'",
shell=True, encoding='utf8', stderr=subprocess.DEVNULL
).strip()
# mmcli wraps AT responses: response: '+QGPSLOC: ...'
# Strip the wrapper to get just the AT response
if result.startswith("response: '") and result.endswith("'"):
result = result[len("response: '"):-1]
return result
except subprocess.CalledProcessError:
return ""
def wait_for_modem():
cloudlog.warning("gpsd: waiting for modem")
while True:
ret = subprocess.call(
"mmcli -m any --timeout 10 --command='AT+QGPS?'",
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True
)
if ret == 0:
return
time.sleep(0.5)
def parse_qgpsloc(response: str):
"""Parse AT+QGPSLOC=2 response into a dict.
Format: +QGPSLOC: UTC,lat,lon,hdop,alt,fix,cog,spkm,spkn,date,nsat
"""
if "+QGPSLOC:" not in response:
return None
try:
data = response.split("+QGPSLOC:")[1].strip()
fields = data.split(",")
if len(fields) < 11:
return None
utc = fields[0] # HHMMSS.S
lat = float(fields[1])
lon = float(fields[2])
hdop = float(fields[3])
alt = float(fields[4])
fix = int(fields[5]) # 2=2D, 3=3D
cog = float(fields[6]) # course over ground
spkm = float(fields[7]) # speed km/h
spkn = float(fields[8]) # speed knots
date = fields[9] # DDMMYY
nsat = int(fields[10])
# Build unix timestamp from UTC + date
# utc: "HHMMSS.S", date: "DDMMYY"
hh, mm = int(utc[0:2]), int(utc[2:4])
ss = float(utc[4:])
dd, mo, yy = int(date[0:2]), int(date[2:4]), 2000 + int(date[4:6])
dt = datetime.datetime(yy, mo, dd, hh, mm, int(ss),
int((ss % 1) * 1e6), datetime.timezone.utc)
return {
"latitude": lat,
"longitude": lon,
"altitude": alt,
"speed": spkm / 3.6, # convert km/h to m/s
"bearing": cog,
"accuracy": hdop * 5, # rough conversion from HDOP to meters
"timestamp_ms": dt.timestamp() * 1e3,
"satellites": nsat,
"fix": fix,
}
except (ValueError, IndexError) as e:
cloudlog.error(f"gpsd: parse error: {e}")
return None
def main():
import sys
print("gpsd: starting", file=sys.stderr, flush=True)
# Kill system gpsd which may respawn and interfere with modem access
subprocess.run("pkill -f /usr/sbin/gpsd", shell=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
wait_for_modem()
print("gpsd: modem ready", file=sys.stderr, flush=True)
# Enable GPS antenna power
gpio_init(GPIO.GNSS_PWR_EN, True)
gpio_set(GPIO.GNSS_PWR_EN, True)
print("gpsd: GPIO power enabled", file=sys.stderr, flush=True)
# Don't restart GPS if already running (preserve existing fix)
resp = at_cmd("AT+QGPS?")
print(f"gpsd: QGPS status: {resp}", file=sys.stderr, flush=True)
if "QGPS: 1" not in resp:
at_cmd('AT+QGPSCFG="dpoenable",0')
at_cmd('AT+QGPSCFG="outport","none"')
at_cmd("AT+QGPS=1")
print("gpsd: GPS started fresh", file=sys.stderr, flush=True)
else:
print("gpsd: GPS already running, keeping fix", file=sys.stderr, flush=True)
pm = messaging.PubMaster(["gpsLocation"])
clock_set = system_time_valid()
params_memory = Params("/dev/shm/params")
last_daylight_check = 0.0
daylight_computed = False
prev_daylight = None # CLEARPILOT: gate IsDaylight write on change
print("gpsd: entering main loop", file=sys.stderr, flush=True)
while True:
resp = at_cmd("AT+QGPSLOC=2")
fix = parse_qgpsloc(resp)
if fix:
# Set system clock from GPS on first valid fix if clock is invalid
if not clock_set:
gps_dt = datetime.datetime.utcfromtimestamp(fix["timestamp_ms"] / 1000)
ret = subprocess.run(["date", "-s", gps_dt.strftime("%Y-%m-%d %H:%M:%S")],
env={**os.environ, "TZ": "UTC"},
capture_output=True)
if ret.returncode == 0:
clock_set = True
cloudlog.warning("gpsd: system clock set from GPS: %s", gps_dt)
print(f"gpsd: system clock set from GPS: {gps_dt}", file=sys.stderr, flush=True)
else:
cloudlog.error("gpsd: failed to set clock: %s", ret.stderr.decode().strip())
msg = messaging.new_message("gpsLocation", valid=True)
gps = msg.gpsLocation
gps.latitude = fix["latitude"]
gps.longitude = fix["longitude"]
gps.altitude = fix["altitude"]
gps.speed = fix["speed"]
gps.bearingDeg = fix["bearing"]
gps.horizontalAccuracy = fix["accuracy"]
gps.unixTimestampMillis = int(fix["timestamp_ms"])
gps.source = log.GpsLocationData.SensorSource.qcomdiag
gps.hasFix = fix["fix"] >= 2
gps.flags = 1
gps.vNED = [0.0, 0.0, 0.0]
gps.verticalAccuracy = fix["accuracy"]
gps.bearingAccuracyDeg = 10.0
gps.speedAccuracy = 1.0
pm.send("gpsLocation", msg)
# CLEARPILOT: daylight calculation for auto display mode switching
if clock_set:
now_mono = time.monotonic()
interval = 1.0 if not daylight_computed else 30.0
if (now_mono - last_daylight_check) >= interval:
last_daylight_check = now_mono
utc_now = datetime.datetime.utcfromtimestamp(fix["timestamp_ms"] / 1000)
daylight = is_daylight(fix["latitude"], fix["longitude"], utc_now)
# CLEARPILOT: gate on change — daylight flips twice a day, don't rewrite every 30s
if daylight != prev_daylight:
params_memory.put_bool("IsDaylight", daylight)
prev_daylight = daylight
if not daylight_computed:
daylight_computed = True
cloudlog.warning("gpsd: initial daylight calc: %s", "day" if daylight else "night")
print(f"gpsd: initial daylight calc: {'day' if daylight else 'night'}", file=sys.stderr, flush=True)
# Auto-transition: only touch states 0 and 1
current_mode = params_memory.get_int("ScreenDisplayMode")
if current_mode == 0 and not daylight:
params_memory.put_int("ScreenDisplayMode", 1)
cloudlog.warning("gpsd: auto-switch to nightrider (sunset)")
elif current_mode == 1 and daylight:
params_memory.put_int("ScreenDisplayMode", 0)
cloudlog.warning("gpsd: auto-switch to normal (sunrise)")
time.sleep(0.5) # 2 Hz polling
if __name__ == "__main__":
main()
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/bash
# Nice monitor — ensures claude processes run at lowest CPU priority.
# Checks every 30 seconds and renices any claude process not already at nice 19.
# Kill other instances of this script
for pid in $(pgrep -f 'nice-monitor.sh' | grep -v $$); do
kill "$pid" 2>/dev/null
done
while true; do
for pid in $(pgrep -f 'claude' 2>/dev/null); do
cur=$(awk '{print $19}' /proc/$pid/stat 2>/dev/null)
if [ -n "$cur" ] && [ "$cur" != "19" ]; then
renice 19 -p "$pid" > /dev/null 2>&1
fi
done
sleep 30
done
+2 -42
View File
@@ -3,45 +3,5 @@
# Install logo
bash /data/openpilot/system/clearpilot/startup_logo/set_logo.sh
# SSH — always, unconditionally, first thing
cat /data/openpilot/system/clearpilot/dev/GithubSshKeys > /data/params/d/GithubSshKeys
echo -n 1 > /data/params/d/SshEnabled
sudo systemctl enable ssh 2>/dev/null
sudo systemctl start ssh
# Decrypt and install SSH identity keys for root (git auth)
serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline)
ssh_dir="/root/.ssh"
if [[ $serial == 3889765b ]] && [[ ! -f "$ssh_dir/id_ed25519" || ! -f "$ssh_dir/id_ed25519.pub" ]]; then
echo "Decrypting SSH identity keys for root (serial=$serial)..."
tmpdir=$(mktemp -d)
bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_ed25519.cpt "$tmpdir/id_ed25519"
bash /data/openpilot/system/clearpilot/tools/decrypt /data/openpilot/system/clearpilot/dev/id_ed25519.pub.cpt "$tmpdir/id_ed25519.pub"
sudo mkdir -p "$ssh_dir"
sudo cp "$tmpdir/id_ed25519" "$tmpdir/id_ed25519.pub" "$ssh_dir/"
rm -rf "$tmpdir"
sudo chmod 700 "$ssh_dir"
sudo chmod 600 "$ssh_dir/id_ed25519"
sudo chmod 644 "$ssh_dir/id_ed25519.pub"
echo "SSH identity keys installed to $ssh_dir"
fi
# Ensure root SSH config has git.hanson.xyz entry
if ! grep -q "Host git.hanson.xyz" "$ssh_dir/config" 2>/dev/null; then
sudo tee -a "$ssh_dir/config" > /dev/null <<'SSHCFG'
Host git.hanson.xyz
IdentityFile /root/.ssh/id_ed25519
StrictHostKeyChecking no
SSHCFG
sudo chmod 600 "$ssh_dir/config"
echo "SSH config updated for git.hanson.xyz"
fi
# Always ensure WiFi radio is on
nmcli radio wifi on 2>/dev/null
# Provision (packages, git pull, build) if no quick_boot flag
if [ ! -f /data/quick_boot ]; then
sudo bash /data/openpilot/system/clearpilot/provision.sh
fi
# Reverse ssh if brianbot dongle id
bash /data/openpilot/system/clearpilot/dev/on_start.sh
-77
View File
@@ -1,77 +0,0 @@
#!/bin/bash
# ClearPilot provision script
# Runs on first boot (no /data/quick_boot) when internet is available.
# Installs packages, pulls latest code, and builds.
# SSH is handled by on_start.sh before this runs.
# Output is displayed on screen via qt_shell.
mount -o rw,remount /
# 1. Wait for internet connectivity
echo "Waiting for internet connectivity (up to 30s)..."
ONLINE=0
for i in $(seq 1 30); do
if nmcli networking connectivity check 2>/dev/null | grep -q "full"; then
echo "Online after ${i}s"
ONLINE=1
break
fi
sleep 1
done
if [ "$ONLINE" -eq 0 ]; then
echo "No internet after 30s, skipping packages and updates"
sleep 3
exit 0
fi
# 3. Install packages
echo "Remounting / read-write..."
sudo mount -o remount,rw /
echo "Installing packages..."
sudo apt-get update -qq
sudo apt-get install -y openvpn curl ccrypt
#echo "Installing Node.js 20..."
#curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
mount -o rw,remount /
echo "Installing Claude Code..."
curl -fsSL https://claude.ai/install.sh | bash
cat > /usr/local/bin/claude <<'WRAPPER'
#!/bin/bash
sudo mount -o rw,remount /
exec /root/.local/bin/claude "$@"
WRAPPER
chmod +x /usr/local/bin/claude
echo "Packages installed"
# 4. Ensure git remote uses SSH (not HTTPS)
cd /data/openpilot
EXPECTED_REMOTE="git@git.hanson.xyz:brianhansonxyz/clearpilot.git"
CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null)
if [ "$CURRENT_REMOTE" != "$EXPECTED_REMOTE" ]; then
echo "Fixing git remote: $CURRENT_REMOTE -> $EXPECTED_REMOTE"
git remote set-url origin "$EXPECTED_REMOTE"
fi
# 5. Pull latest from remote (remote always wins)
echo "Checking for updates..."
git fetch origin clearpilot
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/clearpilot)
if [ "$LOCAL" != "$REMOTE" ]; then
echo "Updating: $(git log --oneline -1 HEAD) -> $(git log --oneline -1 origin/clearpilot)"
git reset --hard origin/clearpilot
sudo chown -R comma:comma /data/openpilot
echo "Update complete"
else
echo "Already up to date"
fi
# 5. Build
echo ""
sudo chown -R comma:comma /data/openpilot
touch /data/quick_boot
echo "Provision complete"
sleep 2
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
exec bash /data/openpilot/system/clearpilot/provision.sh 2>&1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

@@ -3,9 +3,8 @@
# Create a 2160x1080 true color bitmap canvas with black background
convert -size 2160x1080 canvas:black /tmp/black_canvas.png
# Scale logo 140% then center on canvas
convert /data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/boot_logo.png -resize 140% /tmp/scaled_logo.png
composite -gravity center /tmp/scaled_logo.png /tmp/black_canvas.png /tmp/centered_image.png
# Place the image in the center of the canvas, blending the transparent background
composite -gravity center /data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/boot_logo.png /tmp/black_canvas.png /tmp/centered_image.png
# Rotate the image clockwise 90 degrees
convert /tmp/centered_image.png -rotate 90 /tmp/rotated_image.png
@@ -14,4 +13,4 @@ convert /tmp/centered_image.png -rotate 90 /tmp/rotated_image.png
convert /tmp/rotated_image.png -quality 95 /data/openpilot/system/clearpilot/startup_logo/bg.jpg
# Clean up temporary files
rm /tmp/black_canvas.png /tmp/scaled_logo.png /tmp/centered_image.png /tmp/rotated_image.png
rm /tmp/black_canvas.png /tmp/centered_image.png /tmp/rotated_image.png
@@ -3,13 +3,6 @@
set -x
# CLEARPILOT: regenerate bg.jpg if boot_logo.png is newer (handles logo changes)
BOOT_LOGO="/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/boot_logo.png"
GENERATED_BG="/data/openpilot/system/clearpilot/startup_logo/bg.jpg"
if [ "$BOOT_LOGO" -nt "$GENERATED_BG" ] 2>/dev/null; then
bash /data/openpilot/system/clearpilot/startup_logo/generate_logo.sh
fi
# Check if md5sum of /usr/comma/bg.jpg is not equal to md5sum of /data/openpilot/system/clearpilot/startup_logo/bg.jpg
if [ "$(md5sum /usr/comma/bg.jpg | awk '{print $1}')" != "$(md5sum /data/openpilot/system/clearpilot/startup_logo/bg.jpg | awk '{print $1}')" ]; then
bash /data/openpilot/system/clearpilot/startup_logo/generate_logo.sh
+3 -6
View File
@@ -10,11 +10,8 @@ fi
src="$1"
dest="$2"
# Use hardware serial as decryption key
serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline)
keyfile=$(mktemp)
echo -n "$serial" > "$keyfile"
# Read DongleId for decryption key
dongle_id=/data/params/d/DongleId
# Decrypt the file
cat "$src" | ccrypt -d -k "$keyfile" > "$dest"
rm -f "$keyfile"
cat "$src" | ccrypt -d -k "$dongle_id" > "$dest"
+3 -6
View File
@@ -10,11 +10,8 @@ fi
src="$1"
dest="$2"
# Use hardware serial as encryption key
serial=$(sed 's/.*androidboot.serialno=\([^ ]*\).*/\1/' /proc/cmdline)
keyfile=$(mktemp)
echo -n "$serial" > "$keyfile"
# Read DongleId for encryption key
dongle_id=/data/params/d/DongleId
# Encrypt the file
cat "$src" | ccrypt -e -k "$keyfile" > "$dest"
rm -f "$keyfile"
cat "$src" | ccrypt -e -k "$dongle_id" > "$dest"
-153
View File
@@ -1,153 +0,0 @@
#!/usr/bin/bash
# VPN monitor — connects OpenVPN when internet is up, disconnects when down.
# Drops and reconnects when WiFi SSID changes (stale tunnel prevention).
# On non-home networks, resolves VPN hostname via 8.8.8.8 and passes IP directly.
# Keepalive: pings gateway through tunnel, two failures 10s apart = reconnect.
# SIGTERM: gracefully stops tunnel and exits.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONF="$SCRIPT_DIR/vpn.ovpn"
VPN_HOST="vpn.hanson.xyz"
VPN_PORT="1194"
HOME_SSID="risa"
VPN_GW="192.168.69.1"
CHECK_HOST="1.1.1.1"
INTERVAL=30
CONNECT_TIMEOUT=30
MAX_FAILURES=3
PREV_SSID=""
FAIL_COUNT=0
ACTIVE_VPN_IP=""
kill_vpn() {
killall openvpn 2>/dev/null
# Clean up host route to VPN server
if [ -n "$ACTIVE_VPN_IP" ]; then
ip route del "$ACTIVE_VPN_IP/32" 2>/dev/null
ACTIVE_VPN_IP=""
fi
}
get_default_gw() {
ip route show default | awk '/via/ {print $3; exit}'
}
resolve_vpn() {
if [ "$CURR_SSID" != "$HOME_SSID" ]; then
dig +short @8.8.8.8 "$VPN_HOST" 2>/dev/null | tail -1
else
dig +short "$VPN_HOST" 2>/dev/null | tail -1
fi
}
# Graceful shutdown on SIGTERM
shutdown() {
echo "$(date): SIGTERM received, stopping vpn and exiting"
kill_vpn
exit 0
}
trap shutdown SIGTERM SIGINT
# Kill other instances of this script and wait for graceful shutdown
for pid in $(pgrep -f 'vpn-monitor.sh' | grep -v $$); do
kill "$pid" 2>/dev/null
done
sleep 5
# Force kill any that didn't exit
for pid in $(pgrep -f 'vpn-monitor.sh' | grep -v $$); do
kill -9 "$pid" 2>/dev/null
done
# Kill any existing VPN and clean up
kill_vpn
sleep 1
while true; do
CURR_SSID="$(iwgetid -r 2>/dev/null)"
# Detect SSID change (only when switching between two known networks)
if [ -n "$PREV_SSID" ] && [ -n "$CURR_SSID" ] && [ "$PREV_SSID" != "$CURR_SSID" ]; then
echo "$(date): wifi changed from '$PREV_SSID' to '$CURR_SSID', dropping vpn"
kill_vpn
FAIL_COUNT=0
sleep 5
fi
PREV_SSID="$CURR_SSID"
if ping -c 1 -W 3 "$CHECK_HOST" > /dev/null 2>&1; then
# Internet is up — check tunnel health if connected
if ip link show tun0 > /dev/null 2>&1; then
# Keepalive: ping gateway through tunnel, two failures 10s apart = dead
if ! ping -c 1 -W 3 -I tun0 "$VPN_GW" > /dev/null 2>&1; then
sleep 10
if ! ping -c 1 -W 3 -I tun0 "$VPN_GW" > /dev/null 2>&1; then
echo "$(date): keepalive failed twice, dropping vpn"
kill_vpn
sleep 5
continue
fi
fi
fi
# Start VPN if not running
if ! ip link show tun0 > /dev/null 2>&1; then
if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then
# Back off after repeated failures — just wait for next interval
sleep "$INTERVAL"
continue
fi
# Resolve VPN server IP (via 8.8.8.8 on non-home networks)
RESOLVED_IP="$(resolve_vpn)"
if [ -z "$RESOLVED_IP" ]; then
echo "$(date): failed to resolve $VPN_HOST (ssid=$CURR_SSID)"
FAIL_COUNT=$((FAIL_COUNT + 1))
sleep "$INTERVAL"
continue
fi
# Add host route to VPN server via current default gateway
# so VPN traffic survives tun0 coming up
GW="$(get_default_gw)"
if [ -n "$GW" ]; then
ip route replace "$RESOLVED_IP/32" via "$GW"
echo "$(date): host route $RESOLVED_IP via $GW"
fi
ACTIVE_VPN_IP="$RESOLVED_IP"
echo "$(date): starting openvpn -> $RESOLVED_IP (attempt $((FAIL_COUNT + 1))/$MAX_FAILURES, ssid=$CURR_SSID)"
nice -n 19 openvpn --config "$CONF" --remote "$RESOLVED_IP" "$VPN_PORT" --daemon --log-append /tmp/openvpn.log
# Wait for tunnel to come up
CONNECTED=0
for i in $(seq 1 "$CONNECT_TIMEOUT"); do
if ip link show tun0 > /dev/null 2>&1; then
CONNECTED=1
break
fi
sleep 1
done
if [ "$CONNECTED" -eq 1 ]; then
echo "$(date): vpn connected (took ${i}s)"
FAIL_COUNT=0
else
echo "$(date): vpn failed to connect within ${CONNECT_TIMEOUT}s, killing"
kill_vpn
FAIL_COUNT=$((FAIL_COUNT + 1))
if [ "$FAIL_COUNT" -ge "$MAX_FAILURES" ]; then
echo "$(date): $MAX_FAILURES consecutive failures, backing off"
fi
fi
fi
else
# Internet is down — kill VPN if running
if ip link show tun0 > /dev/null 2>&1; then
echo "$(date): internet down, stopping openvpn"
kill_vpn
fi
FAIL_COUNT=0
fi
sleep "$INTERVAL"
done
-76
View File
@@ -1,76 +0,0 @@
client
dev tun
proto udp
remote vpn.hanson.xyz 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
verb 3
pull-filter ignore "redirect-gateway"
# pull-filter ignore "route "
<ca>
-----BEGIN CERTIFICATE-----
MIIB2jCCAX+gAwIBAgIUFVGjbK1Qb5d3RkkoNPMsXeI/xVAwCgYIKoZIzj0EAwIw
HjEcMBoGA1UEAwwTT3BlblZQTi1JbnRlcm5hbC1DQTAgFw0yNjAyMDcwODQ3Mzda
GA8yMTI2MDExNDA4NDczN1owHjEcMBoGA1UEAwwTT3BlblZQTi1JbnRlcm5hbC1D
QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGb6RWOFnCJ9t7X5q6fqpv0y3Hg/
dTU3ky+MAjfPRYfUWfiM7wVKubYOCc+pUHsJXWaghqu7nQoCeSzVDcPXlWGjgZgw
gZUwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUNThAWabF1zsqNE19iCKuZMjHBIUw
WQYDVR0jBFIwUIAUNThAWabF1zsqNE19iCKuZMjHBIWhIqQgMB4xHDAaBgNVBAMM
E09wZW5WUE4tSW50ZXJuYWwtQ0GCFBVRo2ytUG+Xd0ZJKDTzLF3iP8VQMAsGA1Ud
DwQEAwIBBjAKBggqhkjOPQQDAgNJADBGAiEA2mPwEK8G4HXlRu6WZVSRdqyCPYYd
KffYalCXgw3pZ/sCIQC9qPNckHtubycu8kq4iM8Vl1vYMVEorn7DUFdXJCvtcg==
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
MIIB2TCCAYCgAwIBAgIRALuRBSB68/ccWM8SASfEIV0wCgYIKoZIzj0EAwIwHjEc
MBoGA1UEAwwTT3BlblZQTi1JbnRlcm5hbC1DQTAgFw0yNjA0MTIwMDA1NDhaGA8y
MTI2MDMxOTAwMDU0OFowEDEOMAwGA1UEAwwFY29tbWEwWTATBgcqhkjOPQIBBggq
hkjOPQMBBwNCAAQ/jN83Z2Ikk+IWVPGxN0CNFCh74Yrb3W6VXAjGWa+ppVxSbdeq
YVBWjJl6qSg6n2ZMDivQ5NcKgsxMcY9ly/LEo4GqMIGnMAkGA1UdEwQCMAAwHQYD
VR0OBBYEFDIulLc8hAwTkGHq+z8K8eBBM0vVMFkGA1UdIwRSMFCAFDU4QFmmxdc7
KjRNfYgirmTIxwSFoSKkIDAeMRwwGgYDVQQDDBNPcGVuVlBOLUludGVybmFsLUNB
ghQVUaNsrVBvl3dGSSg08yxd4j/FUDATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNV
HQ8EBAMCB4AwCgYIKoZIzj0EAwIDRwAwRAIgR/ssLDNLmt1s0WXwGLszBUrlstUu
9nhP2PcmdnsOit4CIECFbQ7RHEZLQJWsL2DvKowCCzDtA6ZGDILTVfHwNyDn
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYolghDmo5ISWxjQy
sayXuFRSW5fkiIXJ1SGvSRLnmBmhRANCAAQ/jN83Z2Ikk+IWVPGxN0CNFCh74Yrb
3W6VXAjGWa+ppVxSbdeqYVBWjJl6qSg6n2ZMDivQ5NcKgsxMcY9ly/LE
-----END PRIVATE KEY-----
</key>
<tls-crypt>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
5d6fedacbb44013958eef494b179f21d
51b158484c08cb125b8ddd2a919ed44f
5cae951b1f85f483f0108b1000fac1e6
334ab5b2f3c7352c3a53e814e2e4cdc7
f401d5eb2e13449539313f18de53563d
a72318979c31ef76caad86317064aede
940ab3d799886b9667f4deabb8b159c2
12bd7f27c91a7bfd3b9a315dbac3391d
fb3c354b7955627937fd6163c1683705
e46b252ee9c383507b5a4496462f3d67
25dc48bbca8170574efa22b3c37c4bcc
ad30e92d39aae5326c59a4484302d388
7836837bd5098faeda430aa6db69d8df
fe62aeed2bef6afb7c0c742fe8644040
3c4e46deb3915467c351018592c58545
5b5d7b8c204d37104f9848573d8eb73b
-----END OpenVPN Static key V1-----
</tls-crypt>
+4
View File
@@ -50,6 +50,7 @@ public:
}
static void reboot() { std::system("sudo reboot"); }
static void soft_reboot() {
const std::vector<std::string> commands = {
"rm -f /tmp/safe_staging_overlay.lock",
@@ -67,7 +68,9 @@ public:
}
}
}
static void poweroff() { std::system("sudo poweroff"); }
static void set_brightness(int percent) {
std::string max = util::read_file("/sys/class/backlight/panel0-backlight/max_brightness");
@@ -77,6 +80,7 @@ public:
brightness_control.close();
}
}
static void set_display_power(bool on) {
std::ofstream bl_power_control("/sys/class/backlight/panel0-backlight/bl_power");
if (bl_power_control.is_open()) {
+1 -98
View File
@@ -8,15 +8,11 @@ from openpilot.system.loggerd.config import get_available_bytes, get_available_p
from openpilot.system.loggerd.uploader import listdir_by_creation
from openpilot.system.loggerd.xattr_cache import getxattr
# CLEARPILOT: increased from 5 GB to 9 GB to reserve space for screen recordings
MIN_BYTES = 9 * 1024 * 1024 * 1024
MIN_BYTES = 5 * 1024 * 1024 * 1024
MIN_PERCENT = 10
DELETE_LAST = ['boot', 'crash']
# CLEARPILOT: screen recorder video directory
VIDEOS_DIR = '/data/media/0/videos'
PRESERVE_ATTR_NAME = 'user.preserve'
PRESERVE_ATTR_VALUE = b'1'
PRESERVE_COUNT = 5
@@ -48,103 +44,12 @@ def get_preserved_segments(dirs_by_creation: list[str]) -> list[str]:
return preserved
def delete_oldest_video():
"""CLEARPILOT: delete oldest dashcam footage when disk space is low.
Trip directories are /data/media/0/videos/YYYYMMDD-HHMMSS/ containing .mp4 segments.
Deletes entire oldest trip directory first. If only one trip remains (active),
deletes individual segments oldest-first within it. Also cleans up legacy flat .mp4 files."""
try:
if not os.path.isdir(VIDEOS_DIR):
return False
# Collect legacy flat mp4 files and trip directories
legacy_files = []
trip_dirs = []
for entry in os.listdir(VIDEOS_DIR):
path = os.path.join(VIDEOS_DIR, entry)
if os.path.isfile(path) and entry.endswith('.mp4'):
legacy_files.append(entry)
elif os.path.isdir(path):
trip_dirs.append(entry)
# Delete legacy flat files first (oldest by name)
if legacy_files:
legacy_files.sort()
delete_path = os.path.join(VIDEOS_DIR, legacy_files[0])
cloudlog.info(f"deleting legacy video {delete_path}")
os.remove(delete_path)
return True
if not trip_dirs:
return False
trip_dirs.sort() # sorted by timestamp name = chronological order
# If more than one trip, delete the oldest entire trip directory
if len(trip_dirs) > 1:
delete_path = os.path.join(VIDEOS_DIR, trip_dirs[0])
cloudlog.info(f"deleting trip {delete_path}")
shutil.rmtree(delete_path)
return True
# Only one trip left (likely active) — delete oldest segment within it
trip_path = os.path.join(VIDEOS_DIR, trip_dirs[0])
segments = sorted(f for f in os.listdir(trip_path) if f.endswith('.mp4'))
if not segments:
return False
delete_path = os.path.join(trip_path, segments[0])
cloudlog.info(f"deleting segment {delete_path}")
os.remove(delete_path)
return True
except OSError:
cloudlog.exception(f"issue deleting video from {VIDEOS_DIR}")
return False
# CLEARPILOT: max total size for /data/log2 session logs
LOG2_MAX_BYTES = 4 * 1024 * 1024 * 1024
def cleanup_log2():
"""Delete oldest session log directories until /data/log2 is under LOG2_MAX_BYTES."""
log_base = "/data/log2"
if not os.path.isdir(log_base):
return
# Get all session dirs sorted oldest first (by name = timestamp)
dirs = []
for entry in sorted(os.listdir(log_base)):
if entry == "current":
continue
path = os.path.join(log_base, entry)
if os.path.isdir(path) and not os.path.islink(path):
size = sum(f.stat().st_size for f in os.scandir(path) if f.is_file())
dirs.append((entry, path, size))
total = sum(s for _, _, s in dirs)
# Also count current session
current = os.path.join(log_base, "current")
if os.path.isdir(current):
total += sum(f.stat().st_size for f in os.scandir(current) if f.is_file())
# Delete oldest until under quota
while total > LOG2_MAX_BYTES and dirs:
entry, path, size = dirs.pop(0)
try:
cloudlog.info(f"deleting log session {path} ({size // 1024 // 1024} MB)")
shutil.rmtree(path)
total -= size
except OSError:
cloudlog.exception(f"issue deleting log {path}")
def deleter_thread(exit_event):
while not exit_event.is_set():
out_of_bytes = get_available_bytes(default=MIN_BYTES + 1) < MIN_BYTES
out_of_percent = get_available_percent(default=MIN_PERCENT + 1) < MIN_PERCENT
if out_of_percent or out_of_bytes:
# CLEARPILOT: try deleting oldest video first, then fall back to log segments
if delete_oldest_video():
exit_event.wait(.1)
continue
dirs = listdir_by_creation(Paths.log_root())
# skip deleting most recent N preserved segments (and their prior segment)
@@ -168,8 +73,6 @@ def deleter_thread(exit_event):
cloudlog.exception(f"issue deleting {delete_path}")
exit_event.wait(.1)
else:
# CLEARPILOT: enforce log2 size quota even when disk space is fine
cleanup_log2()
exit_event.wait(30)