從ESP32 / ESP8266開發板傳送電子郵件(二):附加ESP32-CAM拍攝的照片

延續上一篇貼文,本文將介紹觸發ESP32-CAM開發板拍照之後,把影像附加在e-mail中傳遞的程式寫法。

筆者把寄送郵件的程式寫成名叫mailPhoto()的函式,底下的版本將寄送HTML格式的訊息,函式開頭是令ESP32-CAM執行拍攝影像的敘述,相關說明請參閱「ESP32-CAM開發板(二):esp32-camera程式庫與縮時攝影程式」貼文。

void mailPhoto() {
  camera_fb_t *fb = esp_camera_fb_get();   // 拍照、將影像存入變數fb

  if (!fb) {
    Serial.println("無法取得影像資料…");
    delay(1000);
    ESP.restart();  // 重新啟動
  }

  Serial.println("準備寄送郵件…");

  ESP_Mail_Session session;  // 宣告SMTP郵件伺服器連線物件

  /* 設定SMTP郵件伺服器連線物件 */
  session.server.host_name = SMTP_HOST;      // 設定寄信的郵件伺服器名稱
  session.server.port = SMTP_PORT;           // 郵件伺服器的埠號
  session.login.email = AUTHOR_EMAIL;        // 你的帳號
  session.login.password = AUTHOR_PASSWORD;  // 密碼
  /* 設置時區 */
  session.time.ntp_server = F("pool.ntp.org,time.nist.gov");
  session.time.gmt_offset = 8;        // 台北時區
  session.time.day_light_offset = 0;  // 無日光節約時間

  SMTP_Message message;  // 宣告SMTP訊息物件

  /* 設定郵件標頭 */
  message.sender.name = SENDER_NAME;    // 寄信人的名字
  message.sender.email = AUTHOR_EMAIL;  // 寄信人的e-mail
  message.subject = MAIL_SUBJECT;       // 信件主旨
  message.addRecipient(RECIPIENT, RECIPIENT_EMAIL);  // "收信人的名字", "收信人的e-mail"

  // 設定郵件內容(HTML格式訊息)
  String htmlMsg = "<div style=\"color:#ff3300\"">"<h1>鐵證如山!"</h1>"<p>- 從ESP32開發板傳送"</p>"</div>";
  message.html.content = htmlMsg.c_str();  // 設定信件內容
  message.html.charSet = "utf-8";          // 設定訊息文字的編碼

  // 這裡將加入附加影像檔的敘述

  if (!smtp.connect(&session))  // 連線到郵件伺服器
    return;

  if (!MailClient.sendMail(&smtp, &message))  // 開始寄信
    Serial.println("寄信時出錯了:" + smtp.errorReason());
}

透過SMTP_Attachment物件設定信件的附件

“ESP Mail Client”(ESP郵件用戶端)程式庫定義的SMTP_Attachment類別,用於設定附件檔案的名稱、類型和檔案內容。底下的程式片段宣告一個名叫att的「附件物件」:

SMTP_Attachment att;  // 宣告附件物件

att.descr.filename = "photo.jpg";  // 設定附件的檔名
att.descr.mime = "image/jpeg";     // 設定附件的MIME類型(JPEG影像)
att.blob.data = fb->buf;           // 取得影像檔的資料內容
att.blob.size = fb->len;           // 取得影像檔的位元組大小

message.addAttachment(att);        // 把附件檔加入信件

如「ESP32-CAM開發板(二):esp32-camera程式庫與縮時攝影程式」貼文的說明,ESP32-CAM拍攝的影像資料和大小,分別存在fb結構體的buf和len成員,所以上面的程式將把拍攝影像附加到信件。

把ESP32-CAM拍攝照片附加到e-mail並傳送的完整程式碼

完整的程式碼如下,請自行修改其中的一些變數值:

#include <WiFi.h>
#include <ESP_Mail_Client.h>  // 負責寄信的程式庫
#include <esp_camera.h>       // ESP32-CAM拍攝影像的程式庫

// 採用Gmail的郵件伺服器
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 587

#define AUTHOR_EMAIL "你的帳號@gmail.com"
#define AUTHOR_PASSWORD "上文產生的應用程式密碼"
#define SENDER_NAME "寄信人的名字"
#define MAIL_SUBJECT "信件的主旨"

#define RECIPIENT "收信人的名字"
#define RECIPIENT_EMAIL "收信人的e-amil"

const char* ssid = "你的網路名稱";
const char* password = "網路密碼";

/* 宣告用於寄信的SMTP Session物件 */
SMTPSession smtp;

// 宣告寄信的回呼函式
void smtpCallback(SMTP_Status status);

WiFiClient client;

bool initCamera() {
  // 設定攝像頭的接腳和影像格式與尺寸
  static camera_config_t camera_config = {
    .pin_pwdn       = 32,  // 斷電腳
    .pin_reset      = -1,  // 重置腳
    .pin_xclk       = 0,   // 外部時脈腳
    .pin_sscb_sda   = 26,  // I2C資料腳
    .pin_sscb_scl   = 27,  // I2C時脈腳
    .pin_d7         = 35,  // 資料腳
    .pin_d6         = 34,
    .pin_d5         = 39,
    .pin_d4         = 36,
    .pin_d3         = 21,
    .pin_d2         = 19,
    .pin_d1         = 18,
    .pin_d0         = 5,
    .pin_vsync      = 25,   // 垂直同步腳
    .pin_href       = 23,   // 水平同步腳
    .pin_pclk       = 22,   // 像素時脈腳
    .xclk_freq_hz   = 20000000,       // 設定外部時脈:20MHz
    .ledc_timer     = LEDC_TIMER_0,   // 指定產生XCLK時脈的計時器
    .ledc_channel   = LEDC_CHANNEL_0, // 指定產生XCLM時脈的通道
    .pixel_format   = PIXFORMAT_JPEG, // 設定影像格式:JPEG
    .frame_size     = FRAMESIZE_SVGA, // 設定影像大小:SVGA
    .jpeg_quality   = 10,  // 設定JPEG影像畫質,有效值介於0-63,數字越低畫質越高。
    .fb_count       = 1    // 影像緩衝記憶區數量
  };

  // 初始化攝像頭
  esp_err_t err = esp_camera_init(&camera_config);
  if (err != ESP_OK) {
    Serial.printf("攝像頭出錯了,錯誤碼:0x%x", err);
    return false;
  }

  return true;
}

void mailPhoto() {
  camera_fb_t *fb = NULL;    // 宣告儲存影像結構資料的變數
  fb = esp_camera_fb_get();  // 拍照

  if (!fb) {
    Serial.println("無法取得影像資料…");
    delay(1000);
    ESP.restart();  // 重新啟動
  }

  Serial.println("準備寄送郵件…");

  ESP_Mail_Session session;  // 宣告SMTP郵件伺服器連線物件

  /* 設定SMTP郵件伺服器連線物件 */
  session.server.host_name = SMTP_HOST;      // 設定寄信的郵件伺服器名稱
  session.server.port = SMTP_PORT;           // 郵件伺服器的埠號
  session.login.email = AUTHOR_EMAIL;        // 你的帳號
  session.login.password = AUTHOR_PASSWORD;  // 密碼
  /* 設置時區 */
  session.time.ntp_server = F("pool.ntp.org,time.nist.gov");
  session.time.gmt_offset = 8;        // 台北時區
  session.time.day_light_offset = 0;  // 無日光節約時間

  SMTP_Message message;  // 宣告SMTP訊息物件

  /* 設定郵件標頭 */
  message.sender.name = SENDER_NAME;    // 寄信人的名字
  message.sender.email = AUTHOR_EMAIL;  // 寄信人的e-mail
  message.subject = MAIL_SUBJECT;       // 信件主旨
  message.addRecipient(RECIPIENT, RECIPIENT_EMAIL);  // "收信人的名字", "收信人的e-mail"

  /* 設定郵件內容(HTML格式訊息) */
  String htmlMsg = "<div style=\"color:#ff3300\"><h1>鐵證如山!</h1><p>- 從ESP32開發板傳送</p></div>";
  message.html.content = htmlMsg.c_str();  // 設定信件內容
  message.html.charSet = "utf-8";          // 設定訊息文字的編碼

  SMTP_Attachment att;  // 宣告附件物件

  att.descr.filename = "photo.jpg";  // 設定附件的檔名
  att.descr.mime = "image/jpeg";   // 設定附件的MIME類型(JPEG影像)
  att.blob.data = fb->buf;  // 取得影像檔的資料內容
  att.blob.size = fb->len;  // 取得影像檔的位元組大小

  message.addAttachment(att);  // 把附件檔加入信件

  if (!smtp.connect(&session))  // 連線到郵件伺服器
    return;

  if (!MailClient.sendMail(&smtp, &message))  // 開始寄信
    Serial.println("寄信時出錯了:" + smtp.errorReason());
}

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("\n\n連接Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print("\nIP位址:");
  Serial.println(WiFi.localIP());

  bool cameraReady = initCamera();  // 初始化攝像頭
  if (!cameraReady) {
    Serial.println("攝像頭出錯了…");
    delay(1000);
    ESP.restart();  // 重新啟動ESP32
  }

  /** 是否在序列埠監控視窗顯示除錯訊息
     0代表不要
     1代表顯示簡單除錯訊息
  */
  smtp.debug(1);

  /* 設定取得寄信狀態的回呼函式 */
  smtp.callback(smtpCallback);

  mailPhoto();   // 寄送拍攝影像
}

void loop() {
}

/* 取得信件寄送狀態的回呼函式本體 */
void smtpCallback(SMTP_Status status) {
  Serial.println(status.info());  // 顯示目前的狀態

  if (status.success()) {  // 顯示傳送結果
    Serial.println("----------------");
    ESP_MAIL_PRINTF("訊息傳送成功:%d\n", status.completedCount());
    ESP_MAIL_PRINTF("訊息傳送失敗:%d\n", status.failedCount());
    Serial.println("----------------\n");
    struct tm dt;

    for (int i = 0; i < smtp.sendingResult.size(); i++) {
      SMTP_Result result = smtp.sendingResult.getItem(i);
      time_t ts = (time_t)result.timestamp;

      ESP_MAIL_PRINTF("訊息編號:%d\n", i + 1);
      ESP_MAIL_PRINTF("狀態:%s\n", result.completed ? "成功" : "失敗");
      ESP_MAIL_PRINTF("日期/時間:%s\n", asctime(localtime(&ts)));
      ESP_MAIL_PRINTF("收信人:%s\n", result.recipients.c_str());
      ESP_MAIL_PRINTF("主旨:%s\n", result.subject.c_str());
    }
    Serial.println("----------------\n");
    smtp.sendingResult.clear();
  }
}

編譯並上傳程式碼到ESP32-CAM開發板,在Yahoo信箱收到的訊息內容如下:

包含附件的電郵

將ESP32-CAM拍攝的影像插入信件內文

上一節的程式透過message(SMTP訊息)物件addAttachment(直譯為「加入附件」)方法,把影像檔附加在信件末尾,本節將透過「訊息」物件的另一個addInlineImage(直譯為「新增行內影像」)方法,把拍攝到的影像直接顯示在訊息內文,像這樣:

包含行內影像的電郵

首先要修改訊息內文,在其中加入<img>影像標籤並且選擇性地設定影像的替代(說明)文字以及影像的顯示大小(此例設成400 × 300像素),重點是,影像檔的來源(src)屬性要用“cid:識別名稱”的格式,指定影像來源。

附加行內影像的HTML和Arduino程式

附加檔案的att物件程式,要透過content_id設置自訂的影像識別名稱,此例設成“espCAM”。最後,透過addInlineImage()方法附加「行內影像」。

這裡有一個小細節,e-mail的附件檔案預設採用”7bit”編碼,嵌入信件內文的影像要用“base64”編碼(“base64”字串值可改寫成Content_Transfer_Encoding::enc_base64)。

修改之後的e-mail訊息內容以及附加檔案的程式片段如下:

/* 設定郵件內容(HTML格式訊息) */
String htmlMsg = "<div style=\"color:#ff3300\"><h1>鐵證如山!</h1><img src=\"cid:espCAM\" alt=\"拍到鹹魚犯~\"  width=\"400\" height=\"300\"><p>- 從ESP32開發板傳送</p></div>";
message.html.content = htmlMsg.c_str();  // 設定信件內容
message.html.charSet = "utf-8";          // 設定訊息文字的編碼

SMTP_Attachment att;   // 宣告附件物件

// 加上附件
att.descr.content_id = "espCAM";  // 設定影像的識別名稱
att.descr.filename = "photo.jpg";
att.descr.mime = "image/jpeg";
att.descr.transfer_encoding = "base64";  // 內文影像要用“base64”編碼
att.blob.data = fb->buf;      // 取得影像檔的內容
att.blob.size = fb->len;      // 取得影像檔的位元組大小

/* 把影像檔加入信件內文 */
message.addInlineImage(att);

解決Brownout detector was triggered(電力不足)錯誤

筆者在ESP32-CAM開發板上測試寄送相片郵件的程式碼時,發生如下的錯誤訊息,代表ESP32的電力不足,無法正常運作

Brownout detector was triggered(電力不足)錯誤

總結網路上的各個前輩的經驗,根本的解決方法是確保電力充足,換USB線、換電源供應板、測試時改接電腦的Type-C介面(因為Type-C介面的輸出電流遠大於USB 2.0介面的0.5A,詳閱維基百科的《USB-C條目》)。

另外,根據ESP32晶片原廠樂鑫科技的ESP32硬體瑕疵修正文件指出,第一批生產的ESP32晶片的Brown-out Reset(直譯為「電源低壓重置」)功能不正常,無法在發生電力不足錯誤之後重新啟動。

筆者採用FT232RL USB序列埠轉換板燒錄ESP32-CAM程式(參閱「ESP32-CAM開發板(一):簡介與燒錄程式」貼文),原本使用3.3V供電,後來改在USB插座的5V接點焊接一個排針,提供5V電源給ESP32-CAM開發板,並將它插在筆電的Type-C插孔。

FT232RL USB序列埠轉換板

在程式開發測試階段,也可以先關閉ESP32晶片內部的電源低壓偵測功能,方法是引用這兩個程式庫:

#include <soc/soc.h>
#include <soc/rtc_cntl_reg.h>

然後在setup()函式開頭輸入「停止低電壓偵測」的敘述,這樣也可以暫時避免引發「電力不足」的錯誤。

void setup() {
   WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // 停止ESP32晶片內部的低壓偵測功能 
          :  略 
 }
Posts created 483

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top