dashcamd v3: trip directories, state machine, graceful shutdown

Dashcam recording now organized by trip in /data/media/0/videos/YYYYMMDD-HHMMSS/.
Starts recording immediately on launch (with 10-min idle timer), transitions to
continuous recording when drive gear detected. New trip on every ignition cycle.
Graceful shutdown via DashcamShutdown param with 15s ack timeout in thermald.

- Bitrate reduced to 2500 kbps (was 4 Mbps)
- Trip state machine: IDLE → RECORDING ↔ IDLE_TIMEOUT → TRIP_ENDED
- Deleter: trip-aware deletion (oldest trip first, then segments within active trip)
- camerad changed to always_run (was driverview) so dashcam works offroad
- DashcamShutdown param for graceful close before device power-off

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 05:19:41 +00:00
parent 4f16a8953a
commit 86bd2e25b9
6 changed files with 262 additions and 65 deletions

View File

@@ -49,18 +49,51 @@ def get_preserved_segments(dirs_by_creation: list[str]) -> list[str]:
def delete_oldest_video():
"""CLEARPILOT: delete the oldest screen recording MP4 when disk space is low."""
"""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
videos = sorted(
(f for f in os.listdir(VIDEOS_DIR) if f.endswith('.mp4')),
key=lambda f: os.path.getctime(os.path.join(VIDEOS_DIR, f))
)
if not videos:
# 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
delete_path = os.path.join(VIDEOS_DIR, videos[0])
cloudlog.info(f"deleting video {delete_path}")
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: