Grundig Raspberry Cameras View - Part 2
Introduction
I shelved this project thinking it was complete. After a few days, I noticed the stream would crash after several hours of playback. The second major issue was that when the TV was disconnected from power and then reconnected (via a programmable timer), it would display a blank screen—requiring manual intervention with either the TV button or remote control.
To make matters worse, the SD card became physically damaged and shorted out. When I powered the Raspberry Pi with the damaged card still in the slot, the card got extremely hot. I had to rebuild the entire project from scratch anyway.
Solution
I modified the project to address these issues as follows:
1. Replace Flash Journal with RAM Journal
Disable Swap Service
sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall
sudo systemctl disable dphys-swapfileDownload and set the zram for swap compression and save into RAM.
sudo apt install zram-tools
sudo nano /etc/default/zramswap # set ALGO=zstd and PERCENT=50
sudo systemctl restart zramswap
swapon —show
Unblock Wi-Fi Radio
sudo raspi-config nonint do_wifi_country PL
sudo rfkill unblock wifiConfigure Protocol Explicitly via nmcli
nmcli con add type wifi ifname wlan0 con-name "YourWiFiName" ssid "YourWiFiName"
nmcli con modify "YourWiFiName" wifi-sec.key-mgmt wpa-psk
nmcli con modify "YourWiFiName" wifi-sec.psk "YourPassword"
nmcli con up "YourWiFiName"Configure Systemd Journal
Open /etc/systemd/journald.conf and set the following under the [Journal] section:
Storage=volatile
RuntimeMaxUse=50MMount /var/log to RAM
Open /etc/fstab and add this line to the end:
tmpfs /var/log tmpfs defaults,noatime,nosuid,mode=0755,size=50m 0 0
Enable and Start the Service
sudo systemctl daemon-reload
sudo systemctl enable cctv-stream.service
sudo systemctl start cctv-stream.service
sudo systemctl status cctv-stream.service1.1 Auto-Login Configuration
This feature is very useful for unattended operation.
- Run
sudo raspi-config - Navigate to System Options → Boot / Auto Login
- Select Console Autologin (automatically logs in as the default user)
- Select Finish and reboot
2. Remote Power Button Control
The following approaches were attempted:
-
CEC via Pin 13 was not supported on this older TV model -
Attempted to mount a capacitor to the power button, but it charged too quickly even with a 100µF capacitor -
Connected a 2N2222 transistor and added code to the streamer.sh script. This requires a common ground between the TV and the 2N2222 emitter:
bash~~ ~~python3 -c "import RPi.GPIO as g, time; g.setmode(g.BCM); g.setup(17, g.OUT); g.output(17, 1); time.sleep(0.5); g.output(17, 0); g.cleanup()"~~ ~~
Initially this worked, but the common ground connection likely introduced electrical noise from the TV, causing Wi-Fi jamming. -
Recommended solution: Use the PC817 Optocoupler
With Optocoupler and short Python script that launches after the cctv-stream.service in separate tv-button.service everything works well
import RPi.GPIO as GPIO
import time
try:
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT, initial=GPIO.LOW)
time.sleep(8)
GPIO.output(17, GPIO.HIGH)
time.sleep(0.5) # Holds line HIGH for 500ms
GPIO.output(17, GPIO.LOW)
finally:
GPIO.cleanup()
```
tv-button.service
[Unit]
Description=Press TV power button via optocoupler
Requires=cctv-stream.service
Wants=multi-user.target
[Service]
Type=oneshot
User=raspberry
WorkingDirectory=/home/raspberry
ExecStart=/usr/bin/python3 /home/raspberry/button.py
[Install]
WantedBy=multi-user.target
3. Crashing after time
a) Your network timeout is probably not being applied at all. stimeout was deprecated in FFmpeg 5.0 and removed in favour of timeout for the RTSP demuxer. You’re on nano 8.4, which means Debian trixie and FFmpeg 7.x — so --stream-lavf-o=stimeout=5000000 is very likely being silently rejected. That means when the NVR stops sending data without closing the TCP socket, mpv waits forever. Check it:
bash
mpv --stream-lavf-o=stimeout=5000000 --no-config rtsp://invalid 2>&1 | head -20If you see an option error, that’s your hang. The fix is mpv’s own --network-timeout=10, which is version-stable and maps to the right lavf option automatically.
b) --length=15 is not a wall-clock timer. It measures playback position from stream PTS. If the RTSP stream stalls, playback position stops advancing, and the 15-second limit is never reached — mpv sits there indefinitely. --untimed makes this worse by decoupling output from real time. A wall-clock timeout wrapper is the only reliable bound here.
c) Hard kills leave RTSP sessions open on the NVR. Reolink NVRs cap concurrent RTSP sessions. If mpv is SIGKILLed it never sends TEARDOWN, and the NVR holds that slot until its own timeout. Do that a few times an hour and the NVR eventually refuses new connections — which looks exactly like “it crashes after two hours.” So: SIGTERM first with a real grace period, SIGKILL only as a last resort.
To resolve the issue I made several updates to streamer.sh
set -uo pipefail
# Configuration Variables
NVR_IP="" # Ensure this matches your NVR's static IP
USER="" # URL-encode special characters if necessary
PASS=""
# Solar Camera Power Management
# Set how often to wake the battery camera (in seconds).
# 1800 seconds = 30 minutes.
SOLAR_WAKE_INTERVAL=1800
LAST_WAKE_TIME=0
# Enable the Mesa GPU HUD globally for all mpv instances
export GALLIUM_HUD=".x950.y40fps,GPU-load"
# Clean array containing your specific GPU setup + the freeze watchdogs
MPV_FLAGS=(
--vo=gpu
--gpu-context=drm
--hwdec=no
--video-zoom=-0.07
--fs
--ontop
--no-osc
--no-osd-bar
--profile=low-latency
--cache=no
--no-audio
--rtsp-transport=tcp
--length=15
--network-timeout=10
--msg-level=all=warn
)
#FUNCTION
run_mpv() {
local limit="$1"; shift
timeout --signal=TERM --kill-after=15 "$limit" mpv "${MPV_FLAGS[@]}" "$@"
}
#Safety kill
pkill -x mpv 2>/dev/null && sleep 3
# The Master Time Loop
while true; do
# 1. Network Pre-Flight (Wait until NVR is reachable)
while ! ping -c 1 -W 2 "$NVR_IP" &> /dev/null; do
sleep 2
done
# 2. Check if the Solar Camera has rested long enough
CURRENT_TIME=$(date +%s)
if (( CURRENT_TIME - LAST_WAKE_TIME >= SOLAR_WAKE_INTERVAL )); then
# Stream the battery camera for a single 15-second block
run_mpv 45 "rtsp://${USER}:${PASS}@${NVR_IP}:554/h264Preview_05_sub"
# Log the exact time it went back to sleep
LAST_WAKE_TIME=$(date +%s)
fi
# 3. Stream the hardwired cameras (Cams 1-4)
# --loop-playlist=20 loops the 4 wired cameras 20 times.
# 4 cameras * 15 seconds = 1 minute per loop. 20 loops = ~20 minutes of continuous rotation.
START=$(date +%s)
run_mpv 1800 --loop-playlist=20 \
"rtsp://${USER}:${PASS}@${NVR_IP}:554/h264Preview_01_sub" \
"rtsp://${USER}:${PASS}@${NVR_IP}:554/h264Preview_02_sub" \
"rtsp://${USER}:${PASS}@${NVR_IP}:554/h264Preview_03_sub" \
"rtsp://${USER}:${PASS}@${NVR_IP}:554/h264Preview_04_sub"
rc=$?
ELAPSED=$(( $(date +%s) - START ))
# Give the NVR a moment to release the RTSP session before reconnecting
if (( rc != 0 )); then
pkill -x mpv 2>/dev/null
# Failed fast means the NVR is refusing connections, not a
# mid-stream stall. Back off hard so its session table can drain.
if (( ELAPSED < 60 )); then
sleep 60
else
sleep 5
fi
fi
done
It didn’t helped. I had to add the crash exception in case