iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0
自我挑戰組

IT工具與自我學IT的過程分享系列 第 24

Day 4|ESP32CAM網路應用:HTTP / MQTT / LINE Notify(把相機變會講話的同事)

  • 分享至 

  • xImage
  •  

Day 4|ESP32CAM網路應用:HTTP / MQTT / LINE Notify(把相機變會講話的同事)

今天的主題是「會通報、會上雲、別亂刷通知」。
我們把 ESP32-CAM 做成一台有 API的相機,接上 MQTT 當事件匯流排,再用 LINE Notify(或 Telegram)把「有人靠近」的瞬間送到你手機。
全文可直接貼部落格(純 Markdown),附 ASCII 圖/表,在任何平台都能顯示。


0) TL;DR

  • ESP32-CAM 提供 HTTP API/shot/burst/status/led?on=1
  • 事件走 MQTT(主題:esp32cam/<id>/event.../health.../snapshot
  • Pi 5 訂閱事件 → 抓圖 → 上傳雲端 / LINE Notify 通知
  • 冷卻時間、夜間模式失敗重試離線備援(SD 卡)

A. 把 ESP32-CAM 變成「有 API 的相機」

A-1 最小可用 API(整合到你的 setup() / loop()

#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"

WebServer srv(80);

void handleShot(){ // 回傳 JPEG
  camera_fb_t* fb = esp_camera_fb_get();
  if (!fb) { srv.send(500, "text/plain", "fail"); return; }
  srv.sendHeader("Content-Type", "image/jpeg");
  srv.send_P(200, "image/jpeg", (const char*)fb->buf, fb->len);
  esp_camera_fb_return(fb);
}

void handleBurst(){ // 連拍 3 張到 SD
  for (int i=0;i<3;i++){ /* 呼叫你 Day3 的 saveShot("burst") */ delay(120); }
  srv.send(200, "text/plain", "ok");
}

void handleStatus(){ // 回 JSON
  String json = "{\"model\":\"ESP32-CAM\",\"uptime_ms\":"+String(millis())+"}";
  srv.send(200, "application/json", json);
}

void handleLed(){ // /led?on=1
  bool on = (srv.hasArg("on") && srv.arg("on")=="1");
  digitalWrite(4, on ? HIGH : LOW); // 多數板子 LED=GPIO4
  srv.send(200, "text/plain", on?"LED ON":"LED OFF");
}

void setup(){
  // Wi-Fi 與 camera_config_t 初始化(略)
  pinMode(4, OUTPUT); // LED
  srv.on("/shot",   handleShot);
  srv.on("/burst",  handleBurst);
  srv.on("/status", handleStatus);
  srv.on("/led",    handleLed);
  srv.begin();
}
void loop(){ srv.handleClient(); }

A-2 一分鐘測試(cURL)

curl -o test.jpg http://<ESP32-IP>/shot
curl "http://<ESP32-IP>/led?on=1"
curl http://<ESP32-IP>/status

API 封裝建議(命名 & 回應)

路徑 方法 回應 用途
/shot GET image/jpeg 拍一張回傳
/burst POST text/plain 連拍數張到 SD/雲端
/status GET application/json 心跳、韌體版本、PSRAM 狀態
/led?on=1 GET text/plain 簡易光源控制

B. 事件匯流排:用 MQTT 把「有事」傳出去

B-1 架構

[ESP32-CAM] -- publish -->  [MQTT Broker( mosquitto )]  <-- subscribe -- [Raspberry Pi 5]
     |                             ↑                                     ├─ LINE/Telegram 推播
     └─ HTTP /shot  <--- Pi 5 -----┘                                     └─ 雲端/硬碟 存證

B-2 Mosquitto(Docker)

# /srv/mqtt/docker-compose.yml
services:
  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mqtt
    restart: unless-stopped
    ports:
      - "1883:1883"
    volumes:
      - ./conf:/mosquitto/config
      - ./data:/mosquitto/data
      - ./log:/mosquitto/log

./conf/mosquitto.conf(最簡)

listener 1883
allow_anonymous true
persistence true

之後可改帳密、TLS;內網先跑起來最重要。

B-3 ESP32-CAM 發佈事件(Arduino,PubSubClient)

#include <PubSubClient.h>
#include <WiFi.h>

WiFiClient wifi;
PubSubClient mqtt(wifi);
const char* BROKER="192.168.1.50";
const char* ID="cam-porch";

void mqttSend(const char* topic, const String& payload, bool retain=false){
  mqtt.publish(topic, payload.c_str(), retain);
}

void setup(){
  // Wi-Fi init...
  mqtt.setServer(BROKER, 1883);
  mqtt.connect(ID);
  mqttSend("esp32cam/cam-porch/health", "{\"online\":true}", true); // retained
}

void loop(){
  if(!mqtt.connected()) mqtt.connect(ID);
  mqtt.loop();

  // 例:每 60 秒送一次心跳
  static unsigned long t0=0;
  if(millis()-t0>60000){
    mqttSend("esp32cam/cam-porch/health", "{\"online\":true,\"ts\":"+String(millis())+"}", true);
    t0=millis();
  }

  // 偵測到事件就送通知(Day3 的觸發條件)
  // mqttSend("esp32cam/cam-porch/event", "{\"type\":\"motion\"}", false);
}

C. Pi 5:訂閱事件 → 抓圖 → LINE Notify

C-1 安裝套件

sudo apt install -y python3-pip
pip3 install paho-mqtt requests opencv-python

C-2 事件處理器(Python)

# save as notifier.py
import cv2, requests, time, io
import paho.mqtt.client as mqtt

ESP_SHOT = "http://<ESP32-IP>/shot"
LINE_TOKEN = "你的_LINE_TOKEN"
BROKER = "192.168.1.50"
TOPIC_EVENT = "esp32cam/cam-porch/event"

def line_notify(msg, img_bytes=None):
    h={"Authorization":f"Bearer {LINE_TOKEN}"}
    d={"message":msg}
    f={"imageFile":("shot.jpg", img_bytes, "image/jpeg")} if img_bytes else None
    requests.post("https://notify-api.line.me/api/notify", headers=h, data=d, files=f)

def grab_jpeg():
    # 方式1:requests 直接抓(快速)
    r = requests.get(ESP_SHOT, timeout=6)
    r.raise_for_status()
    return r.content

cooldown=20
last_sent=0

def on_msg(client, userdata, msg):
    global last_sent
    if time.time() - last_sent < cooldown: return
    try:
        jpg = grab_jpeg()
        line_notify("門口可能有人(來自 ESP32-CAM)", jpg)
        last_sent = time.time()
    except Exception as e:
        print("notify failed:", e)

mqttc = mqtt.Client()
mqttc.on_message = on_msg
mqttc.connect(BROKER, 1883, 60)
mqttc.subscribe(TOPIC_EVENT, qos=1)
mqttc.loop_forever()

冷卻/夜間模式(反垃圾通知)

ACTIVE_HOURS=(23,6)
def in_active_hours(h0,h1):
    now=time.localtime().tm_hour
    return (now>=h0) or (now<=h1)

# 在 on_msg 裡
if not in_active_hours(*ACTIVE_HOURS): return

D. 圖:資料流小抄

1) 事件通知流程

[ESP32-CAM 偵測] → publish {motion} → [MQTT]
                                   → [Pi 5] → GET /shot → LINE 通知 + 存證

2) 健康檢查(LWT/retained)

[ESP32-CAM 上線] → publish retained "online:true"
[ESP32-CAM 斷線] → Broker 發 LWT "online:false" → Pi 5 接到 → Slack/LINE 報警

E. 可靠度 & 安全清單

可靠度

  • 失敗重試:抓圖 or 推播失敗 → 退避 2、4、8 秒重試
  • 離線備援:網路斷線時,ESP32-CAM 先存 SD 卡;恢復後由 Pi 5 把缺圖補回
  • 心跳:每 60 秒發 .../healthretained),Pi 5 訂閱做儀表板

安全

  • 只在內網運作(或用 Tailscale/ZeroTier VPN)
  • API 加簡單 Token:/shot?tk=...(至少擋掉隨手亂掃)
  • MQTT 設帳密/TLS(之後上線再加,不耽誤今天練兵)

F. 觀測與圖表

事件次數(每小時)

時段   | 次數  | 迷你圖
00-03 |   1   | ▁
03-06 |   0   | 
06-09 |   4   | ▂▂▂▂
09-12 |   7   | ▃▃▃▃▃▃▃
12-15 |  12   | ████████████
15-18 |   9   | █████████
18-21 |  15   | ███████████████
21-24 |   5   | █████

管道成功率(近 24h)

抓圖(/shot)成功率:96%
LINE API 成功率:98%
MQTT 重連次數:1
平均冷卻間隔:20 s

G. 進階玩法(你會喜歡)

  1. Telegram Bot:和 LINE 並行,一個出事另一個頂上。
  2. 反向代理:用 Caddy/Nginx 幫 /shot 加基本認證、TLS。
  3. 事件篩選:白天只存證、不通知;晚上才通知。
  4. 多相機esp32cam/frontdoor.../backyard.../garage,Pi 5 同時訂閱與分流存檔。

H. 今日任務清單 ✅

  • [ ] ESP32-CAM API 跑起來(/shot、/status、/led)
  • [ ] Mosquitto 跑起來(Pi 5 或 NAS)
  • [ ] 事件能從 ESP32-CAM → MQTT → Pi 5 → LINE 手機
  • [ ] 設好冷卻與夜間模式,不再被通知轟炸
  • [ ] 做一張「事件次數表」,看看你家哪個時段最熱鬧

I. 明天預告(Day 5)

  • ESP32-CAM × Pi 5 合體:OpenCV/輕量 AI(TFLite/YOLO),移動偵測、人形框、NVMe 高速存證,還有「每晚自動生成縮時影片」!

上一篇
Day 3|ESP32-CAM拍照 / 錄影 / SD 卡 × 低延遲調校 × API(把相機變「會工作的相機」)
系列文
IT工具與自我學IT的過程分享24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言