之前文章 ESP32 Grbl Controller
一、架構總覽
整套系統有四個常駐服務,全跑在 Pi4 上:
| 服務 | 作用 |
|---|---|
mediamtx |
串流伺服器,把一路 RTSP 來源重新打包成 WebRTC / RTSP / HLS |
cam-push |
ffmpeg 從相機抓幀,常駐推到 MediaMTX |
cnc-monitor |
Python 腳本盯 FluidNC 狀態:Run 時開錄、Idle 自動收檔 |
cnc-sync.timer |
每 2 分鐘把新錄好的影片 scp 回主機 |
訊號流:
C310 /dev/video0
→ ffmpeg (H.264 720p15, zerolatency)
→ MediaMTX
① WebRTC 瀏覽器 <300ms 低延時
② RTSP 手機 VLC
③ HLS 瀏覽器備用(有延遲)
→ cnc-monitor 偵測 FluidNC 狀態 → Run 時再錄一份 mp4 → 自動回傳
二、硬體與前置
- Raspberry Pi 4(64 位 Raspberry Pi OS,本次用 Debian 13 版),有 USB 2.0 口
- USB Webcam 一支(實測 Logitech C310,C922 也可;720p 等級足夠)
- FluidNC 韌體的 ESP32(wifi 版,同一台 AP 或同一 tailnet 內可互達)
- 相機建議斜上方 45 度拍機頭,距離 50–80 cm,避免正對刀頭(反光且看錯切削點)
三、確認相機
插上去之後先確認 OS 認得到:
lsusb | grep -i logitech # 確認 UVC 相機在線
ls /dev/video* # 會有 video0(主流)video1(副流)
v4l2-ctl -d /dev/video0 --info # 卡型/驅動確認
抓一幀確認能出圖:
ffmpeg -f v4l2 -video_size 1280x720 -i /dev/video0 -frames:v 1 /tmp/t.jpg
C922 的 video0/video1 是雙路串流(一路 1080p、一路 720p),C310 是 1280×720 單路。
四、安裝 MediaMTX
MediaMTX 是輕量串流伺服器,單檔二進位即可,aarch64 有官方包:
mkdir -p ~/cnc-monitor && cd ~/cnc-monitor
curl -fsSL https://github.com/bluenviron/mediamtx/releases/download/v1.21.2/mediamtx_v1.21.2_linux_arm64.tar.gz | tar xz
./mediamtx -h # 會自動產生默認設定與憑證
最小設定檔 mediamtx.yml(注意欄位要寫對,別亂加會 unmarshal 失敗):
logLevel: info
rtspAddress: :8554
webrtcAddress: :8889
hlsAddress: :8888
paths:
cnc:
source: publisher
做成 user systemd 服務:
# ~/.config/systemd/user/mediamtx.service
[Unit]
Description=MediaMTX (CNC monitor streaming)
After=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/cnc-monitor
ExecStart=%h/cnc-monitor/mediamtx %h/cnc-monitor/mediamtx.yml
Restart=on-failure
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now mediamtx
五、常駐推播(cam-push)
# ~/cnc-monitor/push_cam.sh
#!/bin/bash
while true; do
ffmpeg -loglevel warning -f v4l2 -video_size 1280x720 -framerate 15 -i /dev/video0 \
-c:v libx264 -tune zerolatency -preset ultrafast -pix_fmt yuv420p -b:v 2500k -g 30 \
-f rtsp -rtsp_transport tcp rtsp://localhost:8554/cnc
echo "push ended, reconnect in 2s"; sleep 2
done
重點參數:
– -tune zerolatency -preset ultrafast:把編碼延時壓到最低,4 核 Pi 推 720p15 沒壓力
– -rtsp_transport tcp:避免 UDP 在 AP/WiFi 環境掉幀
– 外層 while true 做斷線自動重推
同樣做成 user 服務,記得 After=mediamtx.service、Requires=mediamtx.service。
六、三條收看路徑
| 用途 | 位址(把 IP 換成你的 Pi) |
|---|---|
| PC 瀏覽器(WebRTC,延時最低) | http://<PI_IP>:8889/cnc/ |
| 手機 VLC(RTSP,轉運選 TCP) | rtsp://<PI_IP>:8554/cnc |
| 瀏覽器備用(HLS,有秒級延遲) | http://<PI_IP>:8888/cnc/index.m3u8 |
Pi 在 AP 的 WiFi 網段內直連即可;跨網段或在外就用 Tailscale IP(tailnet 內 100.x 位址,三端口都能通,不需穿透)。
七、自動錄影(cnc-monitor)
這支腳本是整套裡最吃螺絲的一部分,把實踩的坑都寫出來。
FluidNC 的 WebSocket 協定(最大的坑)
- Endpoint:
ws://<FLUIDNC_IP>(port 80,不是 81),subprotocol 要帶arduino - 送 G-code 指令,字串結尾一定要帶換行
\n——FluidNC 的 WSChannel 對沒有\n的輸入會緩存不執行,你只收得到PING永遠等不到ok/error - 狀態行格式:
<Run|MPos:x,y,z|FS:feedrpm>/<Idle|...>,狀態偵測用正規表達式抓<Run|<Idle|<Alarm - 同一 time 只有「最新一個」WebSocket 客戶端是被當 primary terminal 的(它會收到
activeID:push,晚到的客端被踢成 passive,passive 送指令不會回 ok)。所以設計上是:monitor 常駐佔住這個 ws session,其他需要發指令的地方(比如手機端想下指令)走文件轉發:寫一行指令到/home/pi/cnc-monitor/cmd.txt,monitor 看到就往自己的 ws 送出——這是唯一不會搶 session 的方式。UGS 從 PC 走 USB 序列口完全不受影響。
錄影觸發邏輯
- 輪詢
?拿狀態(0.4~1 秒一次) - 連續 6 次收不到任何回應 → 判定 ws 失效,重連(防止 FluidNC 重啟或 session 被搶後卡死)
- 狀態從 Idle 轉 Run/Hold → 啓動 ffmpeg 錄 MediaMTX 的
rtsp://localhost:8554/cnc,檔名run_YYYYmmdd_HHMMSS.mp4,同時寫一個recorder_active標記檔 - 狀態回 Idle 並持續 8 秒→ 收檔(防止 G-code 行與行之間短暫 Idle 就誤收)
- 安全上限:錄超過 10 分鐘強制收檔
- 收檔判斷要依賴「recorder_active 標記」,不要用 state 變數——實測過用 state 變數做
elif state=='Run'的判斷会因第一次 Idle 就把 state 改掉而永遠不走收檔分支,ffmpeg 無限錄下去
ffmpeg 參數(錄製版,比推播版可放寬):
ffmpeg -rtsp_transport tcp -i rtsp://localhost:8554/cnc \
-c:v libx264 -preset veryfast -tune zerolatency -b:v 2500k \
-pix_fmt yuv420p -movflags frag_keyframe+empty_moov run_xxx.mp4
八、自動回傳主機(cnc-sync)
用 user timer 每 2 分鐘掃一次 recordings 目錄:
systemctl --user enable --now cnc-sync.timer
腳本要防三個坑:
1. 檔身還在寫——mp4 錄好前 size 會一直長,要檢查「距上次修改 ≥20 秒」且「兩秒間 size 不變」才動手
2. 傳半截——先 scp 成 xxx.mp4.part,傳完比對远端 size 一致才 mv 改回正式檔名
3. 本地 size 對遠端 size 不一致——以遠端為準重新傳,別直接 mv
回傳目標例:主機的 /home/<user>/cnc_records/(走 Tailscale IP,免開洞)。
九、必做:啟用 lingering
systemctl --user 的服務只活著在登入 session 裡面。Pi 上通常是用 VNC/SSH 登進去操作的,一登出(或重開機)這些服務就都沒了。必跑一次:
sudo loginctl enable-linger pi
loginctl show-user pi -p Linger # 應回 Linger=yes
(注意是 enable-linger 不是 enable-lingering,多一個 g 會報 invalid choice。)
十、注意事项清單
- 相機角度:斜上方拍機頭,避直對刀尖(反光 + 看不到實際切削位置)
- 延時:看走 WebRTC(8889),別走 HLS(8888 有秒級延遲,看切削對位会慢半拍)
- Pi 4 USB 2.0 帶寬:720p 推播 + 720p 錄製并行实测沒問題,但別再堆更多路;若要 1080p 建議 Pi4 升 8GB 或 Pi5
- Webcam 被佔:
/dev/video0同一 time 只能被一個 ffmpeg 讀,cam-push 死掉重連時如果舊 process 還在,新 process 會Device or resource busy——重前用fuser -v /dev/video0查 - FluidNC ws 單一 primary:別同時開 dashboard 和 monitor 又發指令,指令會被 passive 客端吞掉不回 ok;要發指令就走 cmd.txt 轉發
- 指令一定要帶
\n結尾,否則不會執行 - 自動收檔要留 idle buffer(8 秒),不然 G-code 行間的微 Idle 會把影片切得七零八落
- lingering 必開,不然重開/登出後整套服務消失
- 錄製檔要等它「穩」再回傳,size 快照 + .part 暫存 + 遠端比對,三層防護
- 看片與看 FluidNC 狀態分屏:左邊 WebRTC 看切削,右邊 FluidNC dashboard 看 position/feed,跑長 G-code 時比單看影片可靠
十一、故障排查快速表
| 症狀 | 原因 | 解法 |
|---|---|---|
| 瀏覽器連 8889 沒畫面 | cam-push 沒在推 | pgrep -af ffmpeg|cam,重啟 cam-push |
| VLC 連 8554 卡住 | UDP 丢幀 | VLC 轉運改 TCP / 命令加 -rtsp_transport tcp |
發指令只收 PING |
字串沒帶 \n 結尾 |
ws.send(cmd + '\n') |
| 錄影片無止境長大 | 收檔判斷用了 state 變數 | 用 recorder_active 標記檔判 |
| 重開 Pi 後串流沒了 | lingering 沒開 | sudo loginctl enable-linger pi |
相機 Device or resource busy |
舊 ffmpeg 殘留 | fuser -v /dev/video0 找到 pid 殺掉 |
結語
這套東西的最大價值不是「能看」,而是「自動留證據」——每段 Run 的運動都對應一個 run_xxx.mp4,事後回放配 FluidNC 的狀態流,能精確對到是哪一行 G-code 出了問題。整套 Pi4 常駐之後完全免維護,跑完機就回家看影片。
下次接好馬_driver、寫完設定,第一刀跑完就能收到自己的 run_20261013_xxxxxx.mp4。



沒有留言:
張貼留言