M: i asked another silo of yours to explain what's going on here...:
Perfect—here’s a compact, copy-paste “context pack” you can give the other silo along with your last working Python file.
arecord) recorded in parallel.libcamera-vid ok; Picamera2
works).ShowImage requires exactly 240×240 RGB; we
rotate 270° and push).Python 3.11.2
pip 23.0.1
# libs (already installed)
cv2 4.13.0-dev
numpy 1.24.2
Pillow 9.4.0
picamera2 0.3.30
spidev 3.5
libcamera 0.5.1 (with python bindings)
rpicam-apps 1.8.1 (libcamera-vid/still present)
Working dir for scripts: /home/pi/
Videos output dir: /home/pi/videos/ (create if
needed)
Waveshare lib path added at runtime:
sys.path.append("/home/pi/LCD_Module_RPI_code/RaspberryPi/python")
from lib.LCD_1inch3 import LCD_1inch3LCD rotation used in code: 270°; backlight PWM around 80%.
motion_record_ten.py (the “last known good” script):
arecord
in parallel: device plughw:1,0 is typical, confirm with
arecord -l.THRESH (default ~20–30): raise to reduce sensitivity to
flicker.MIN_AREA (default ~200–800): raise to ignore tiny
contours.EMA_ALPHA (~0.03–0.05): background update speed.POST_MOTION_SEC (20): tail length.ANALYSIS_FPS (~10), PREVIEW_FPS (~2).ShowImage.mkdir -p /home/pi/videos
python3 /home/pi/motion_record_ten.py
# Watch the LCD top-left debug text: state/area; clips appear in /home/pi/videos[REC] START stem=... and
[REC] STOP saved=...H264Encoder +
FfmpegOutput per clip and fully release them on
stop (this has helped).If you wire audio now, use this pattern (PCM WAV, near-zero CPU):
# start: arecord -D plughw:1,0 -f S16_LE -r 48000 -c 1 -t wav /home/pi/videos/{stem}.wav
# stop: send SIGINT so WAV header finalizesWe’ll mux later with:
ffmpeg -i {stem}.mp4 -i {stem}.wav -c:v copy -c:a aac -b:a 96k -shortest out.mp4If you need anything else from me (e.g., current constants in my script, a short log excerpt, or a tiny test clip), say the word and I’ll include it.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: TL;DR: Your context pack is excellent and matches the working script’s design. The only real mismatches are (1) your spec says 20s tail but the code uses 10s, (2) audio is enabled by default in the script though your pack frames it as “later,” and (3) there’s no explicit hysteresis or max-clip failsafe. A tiny patch below aligns those, improves re-arm reliability, and tidies resource cleanup. fileciteturn0file0
POST_MOTION_SEC = 10. Bump it to 20 to match expectations.
fileciteturn0file0arecord by default
(ALSA_DEVICE = "plughw:1,0"). If the mic/device isn’t
present, you’ll silently spin up a failing child process. Either set
ALSA_DEVICE = None for now or keep it and verify with
arecord -l. fileciteturn0file0H264Encoder + FfmpegOutput per clip
and stops/starts cleanly, so the “one-shot”/“never-stop” issues likely
came from the other rewrite. Adding hysteresis, a
cooldown (you already have 2s), and an optional
max clip length removes edge-cases like flicker-loops
and leaked encoders. fileciteturn0file0Paste these diffs into your current file. They: set 20s tail, add start/stop hysteresis, add a 30-minute safety cap, and explicitly release encoder/output on stop.
--- a/motion_record_ten.py
+++ b/motion_record_ten.py
@@
-POST_MOTION_SEC = 10 # keep recording this long after motion stops
+POST_MOTION_SEC = 20 # keep recording this long after motion stops (was 10)
+FRAMES_TO_START = 3 # hysteresis: consecutive motion frames required to start
+FRAMES_TO_STOP = 5 # hysteresis: consecutive quiet frames required to stop
+MAX_CLIP_SEC = 1800 # fail-safe: 30 min max clip length
@@ def main():
- encoder = H264Encoder(bitrate=BITRATE)
+ encoder = None
+ output = None
@@
- recording = False
+ recording = False
stem = None
mp4_path = None
+ last_stop_time = 0.0
+ motion_count = 0
+ quiet_count = 0
+ clip_start_time = 0.0
@@
- if motion:
+ if motion:
last_motion_time = now
# One-time debug pulse
if not recording:
print(f"[MOTION] area={int(motion_area)} at {datetime.datetime.now().strftime('%H:%M:%S')}")
+ motion_count = min(motion_count + 1, FRAMES_TO_START + 1)
+ quiet_count = 0
+ else:
+ quiet_count = min(quiet_count + 1, FRAMES_TO_STOP + 1)
+ motion_count = 0
@@
- COOLDOWN_SEC = 2.0 # short pause after a stop before arming again
- if 'last_stop_time' not in locals():
- last_stop_time = 0.0
+ COOLDOWN_SEC = 2.0 # short pause after a stop before arming again
# start?
- if motion and not recording and (now - last_stop_time >= COOLDOWN_SEC):
+ if (motion_count >= FRAMES_TO_START) and (not recording) and ((now - last_stop_time) >= COOLDOWN_SEC):
stem = ts_base()
mp4_path = os.path.join(VIDEOS_DIR, f"{stem}.mp4")
print(f"[REC] START stem={stem}")
# Fresh encoder + output each time
- encoder = H264Encoder(bitrate=BITRATE)
- output = FfmpegOutput(mp4_path)
+ encoder = H264Encoder(bitrate=BITRATE)
+ output = FfmpegOutput(mp4_path)
# (optional) audio
if audio:
audio.start(stem)
picam2.start_recording(encoder, output)
recording = True
# make sure last_motion_time is "now" so tail timing is correct
last_motion_time = now
+ clip_start_time = now
# stop?
- if recording and (now - last_motion_time >= POST_MOTION_SEC):
+ should_tail_stop = (now - last_motion_time >= POST_MOTION_SEC) and (quiet_count >= FRAMES_TO_STOP)
+ should_timeout = (now - clip_start_time >= MAX_CLIP_SEC)
+ if recording and (should_tail_stop or should_timeout):
print(f"[REC] STOP saved={mp4_path}")
try:
picam2.stop_recording()
except Exception as e:
print("[REC] stop_recording error:", e)
if audio:
audio.stop()
recording = False
last_stop_time = now
stem = None
mp4_path = None
+ # be tidy: release encoder/output explicitly
+ try:
+ if hasattr(encoder, "close"): encoder.close()
+ except Exception: pass
+ try:
+ if hasattr(output, "close"): output.close()
+ except Exception: pass
+ encoder = None
+ output = None
@@
if recording:
picam2.stop_recording()
if audio:
audio.stop()
+ try:
+ if hasattr(encoder, "close"): encoder.close()
+ except Exception: pass
+ try:
+ if hasattr(output, "close"): output.close()
+ except Exception: passALSA_DEVICE = None to
disable audio for now; it lowers noise in debugging and avoids zombie
arecord processes. fileciteturn0file0THRESH a bit or
MIN_AREA (e.g., +25% each) before touching
EMA_ALPHA. Stabilizing thresholds first will keep the EMA
from “chasing” slow lighting drift. fileciteturn0file0ShowImage; that’s the right guard for the
Waveshare library’s strict input. No change needed.
fileciteturn0file0inv). That avoids the classic smear/ghosting pitfall, so
leave it as is. fileciteturn0file0If you hand the other silo this patch plus your context pack and the working file, they’ll have everything needed to fix the re-arming cleanly and keep it robust going forward.