2026年10月17日 星期六

用 Webcam 監看 FluidNC 運轉:從串流到自動錄影的完整配置

把之前舊玩具重新拿出來用AI 玩, telegram 透過tailscale下指令給 Pi4+ESP32
之前文章 ESP32 Grbl Controller

而這篇文章把 ESP32 的韌體升級到了 FluidNC, 直接官網更新


這次把「眼睛」也接上——用一支 USB Webcam 監視 CNC 的運轉,瀏覽器低延時直播、手機可以看、跑 G-code 時自動錄影,影片自動回传到主機。整套東西架在 Raspberry Pi 4 上,全部開機自啟,不佔用 PC。

一、架構總覽

整套系統有四個常駐服務,全跑在 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 序列口完全不受影響。

錄影觸發邏輯

  1. 輪詢 ? 拿狀態(0.4~1 秒一次)
  2. 連續 6 次收不到任何回應 → 判定 ws 失效,重連(防止 FluidNC 重啟或 session 被搶後卡死)
  3. 狀態從 Idle 轉 Run/Hold → 啓動 ffmpeg 錄 MediaMTX 的 rtsp://localhost:8554/cnc,檔名 run_YYYYmmdd_HHMMSS.mp4,同時寫一個 recorder_active 標記檔
  4. 狀態回 Idle 並持續 8 秒→ 收檔(防止 G-code 行與行之間短暫 Idle 就誤收)
  5. 安全上限:錄超過 10 分鐘強制收檔
  6. 收檔判斷要依賴「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。)

十、注意事项清單

  1. 相機角度:斜上方拍機頭,避直對刀尖(反光 + 看不到實際切削位置)
  2. 延時:看走 WebRTC(8889),別走 HLS(8888 有秒級延遲,看切削對位会慢半拍)
  3. Pi 4 USB 2.0 帶寬:720p 推播 + 720p 錄製并行实测沒問題,但別再堆更多路;若要 1080p 建議 Pi4 升 8GB 或 Pi5
  4. Webcam 被佔:/dev/video0 同一 time 只能被一個 ffmpeg 讀,cam-push 死掉重連時如果舊 process 還在,新 process 會 Device or resource busy——重前用 fuser -v /dev/video0 查
  5. FluidNC ws 單一 primary:別同時開 dashboard 和 monitor 又發指令,指令會被 passive 客端吞掉不回 ok;要發指令就走 cmd.txt 轉發
  6. 指令一定要帶 \n 結尾,否則不會執行
  7. 自動收檔要留 idle buffer(8 秒),不然 G-code 行間的微 Idle 會把影片切得七零八落
  8. lingering 必開,不然重開/登出後整套服務消失
  9. 錄製檔要等它「穩」再回傳,size 快照 + .part 暫存 + 遠端比對,三層防護
  10. 看片與看 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。

沒有留言:

張貼留言