在前一個章節中,我們建構了一個標準照片擷取、傳送與儲存的按全監控裝置,不過假如我們使用的場域中有許多的地方都必須按裝這類的裝置時,例如在一個有許多門、窗的家庭或辦公室,由於我們的系統使用無線WiFi作為信號傳輸之用,所以信號的傳輸除非裝置離WiFi分享器太遠,否則安裝上應該不會是問題,可是這些裝置所需使用的電源尤其佈線的部分就會是一個麻煩的問題了!當然最簡單的方式就是使用電池做為系統的電源,可是大家應該也知道,ESP32-CAM這個模組耗電是有名的,為了解決這個問題,在本範例中我們將示範如何使用ESP32-CAM建構一低功耗的照片擷取、傳送與儲存裝置。
此裝置將使用電池做為供應電源,擷取照片的動作同樣以常見的焦電型紅外線PIR移動偵測模組來觸發,此外也可以用磁簧開關、微動開關或一般的按鈕來啟動。在待機狀態時,系統是處於睡眠的省電模式,當系統被觸發之後,會先將擷取到的照片儲存起來,並立刻連上預設的WiFi分享器,然後將擷取到的照片傳送到使用者的Line Notify群組上,以提醒使用者有人或物進入系統的警戒範圍;在完成上述的兩項動作之後,ESP32-CAM模組便會再次進入深度的睡眠模式(deep-sleep mode),等待下次的觸發信號到來以節省電源的消耗。
◎ 功能與動作說明:
在我們的系統中將具備下面的動作與功能:
本系統使用電池(5V)做為系統的電源,平常系統在待機狀態時,是處於睡眠的省電模式,當測到外部的輸入觸發信號時,便會啟動ESP32-CAM拍攝照片的功能,除了會將所拍攝到的照片儲存到ESP32-CAM上內建的Micro SD讀寫模中所放置的Micro SD卡上之外,還會立刻將照片傳送到使用者的Line Notify群組上。
此外部觸發輸入(輸入腳為GPIO13)為一高態的脈波信號,每一次的脈波輸入會啟動一次拍攝和儲存及傳送照片的動作。此觸發信號可由常見的焦電型紅外線PIR移動偵測模組來提供,此外也可以用磁簧開關、微動開關等機械接點的元件來執行。
當系統將照片傳送到使用者的Line Notify群組上之後,便會再次進入睡眠省電的待機狀態,以節省電池電源的消耗。
◎電路圖:
在本範例中會使用到下列零組件:
1. 一塊ESP32-CAM模組
2. 一個5V的電池電源
3. 一個編號為HC-SR501的人體紅外線移動感測器
4. 一顆NPN電晶體(一般常用的小信號電晶體即可)
5. 2顆10K電阻
6. 磁簧開關(選用)
7. 微動開關(選用)
下面的電路便是本次範例的電路圖:
本裝置所使用的5V電源可用4顆鹼性電池(實際的電壓為1.2V X 4 = 4.8V),或是一般手機充電用的行動電源(輸出正好5V),至於編號為HC-SR501的人體紅外線移動感測器,則建議使用ESP32-CAM模組所輸出的3.3V電壓,以節省電池電力的消耗,所以不要忘記把HC-SR501的工作電壓改接到3.3V的電壓端子上。
在這個範例中,我們是讓ESP32-CAM模組進入睡眠模式後,再想辦法讓它過醒來執行動作;一般較常使用的方法有觸發它的重置腳(Reset)、使用看門狗計時器(Watch Dog)或是輸入腳位喚醒(Wake-Up)等,由於系統觸發信號的來源是隨機不固定的,所以在此是使用輸入腳位喚醒(Wake-Up)的方式來喚醒ESP32。
然而在ESP32-CAM模組板上,由於ESP32必須接上CAM模組及Micro SD模組板,所以剩下可用的I/O腳位並不多;在前一個範例中我們使用了ESP32-CAM模組上的GPIO16腳作為外來信號的觸發輸入腳,可是這根腳位並不具有輸入腳位喚醒(Wake-Up)的功能;而GPIO00雖然有,可是它還有其他的用途(會用在相機的腳位功能上),所以幾乎是沒甚麼腳位可以用了!不過網路上有人建議可以拿GPIO13來作輸入腳位喚醒(Wake-Up)之用,但是這隻腳也同時被用在Micro SD模組板上,所以要用硬體作一些適當的隔離,這也就是我們電路要加上一顆NPN電晶體當作開關的原因,這顆電晶體只要是一般小信號的NPN電晶體都可以。
如果想用將本裝置接在門或窗戶附近作監控時,可改用磁簧開關、微動開關等機械接點的元件,取代HC-SR501的人體紅外線移動感測器,此時只要將這些開關一端接在3.3V電壓上,一端接在原來HC-SR501的人體紅外線移動感測器的信號輸出腳,也就是接在NPN電晶體的B極上10K電阻另一端即可。
◎ 程式列表與說明:
以下便是本範例所使用的完整程式表:
#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include "soc/soc.h" // Disable brownour problems
#include "soc/rtc_cntl_reg.h" // Disable brownour problems
#include "driver/rtc_io.h"
#include <EEPROM.h> // read and write from flash memory
#include <TridentTD_LineNotify.h>
// define the number of bytes you want to access
#define EEPROM_SIZE 1
#define WIFI_SSID "Your_WiFi_SSID"
#define WIFI_PASSWORD "Your_WiFi_Password"
#define LINE_TOKEN "Your_Line_Token"
// Pin definition for CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
int pictureNumber = 0;
const byte shootPin = 16, picLED=33;
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
Serial.begin(115200);
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
pinMode(4,INPUT);
digitalWrite(4,LOW);
rtc_gpio_hold_dis(GPIO_NUM_4);
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Init Camera
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("ESP32 照相機初始化失敗! 錯誤代碼為: 0x%x", err);
return;
}
//Serial.println("Starting SD Card");
if(!SD_MMC.begin()){
Serial.println("SD 卡掛載失敗!");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("找不到 SD卡!");
return;
}
// 使用相機抓取照片
camera_fb_t * fb=esp_camera_fb_get();
if(!fb)
{
Serial.println("相機抓取錯誤!");
return;
}
// initialize EEPROM with predefined size
EEPROM.begin(EEPROM_SIZE);
pictureNumber = EEPROM.read(0)+1 ;
if (pictureNumber > 100)
pictureNumber = 1;
// Path where new picture will be saved in SD Card
String path = "/picture" + String(pictureNumber) +".jpg";
fs::FS &fs = SD_MMC;
Serial.printf("Picture file name: %s\n", path.c_str());
File file = fs.open(path.c_str(), FILE_WRITE);
if(!file){
Serial.println("Failed to open file in writing mode");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.printf("Saved file to path: %s\n", path.c_str());
EEPROM.write(0, pictureNumber);
EEPROM.commit();
}
file.close();
Serial.print("連線到 Wifi SSID ");
Serial.print(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
bool outState = false;
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
outState = !outState;
if (outState)
digitalWrite(picLED, 0);
else
digitalWrite(picLED, 1);
delay(500);
}
digitalWrite(picLED, 0);
Serial.println();
Serial.print("\nWiFi 連線成功! IP address: ");
Serial.println(WiFi.localIP());
LINE.setToken(LINE_TOKEN);
if (pictureNumber == 1) // 第一次使用會送出提示訊息
{
Serial.println("程式開始! 先送出Line的提示訊息與貼圖...");
LINE.notify("你好,我是ESP32-CAM! ");
LINE.notifySticker("歡迎使用Line抓圖傳送系統! ",11539,52114110);
LINE.notifySticker("系統已經準備好,可以開始使用了! ",11537,52002740);
}
Serial.println("送出照片到Line Notify....");
LINE.notifySticker("ㄚ! 有人入侵!! ",11538,51626511);
LINE.notifyPicture( "相機抓圖已啟動!"+path, fb->buf, fb->len);
esp_camera_fb_return(fb);
// Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
rtc_gpio_hold_en(GPIO_NUM_4);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_13,0);
delay(1000);
Serial.println("ESP32-CAM要開始睡覺了!");
delay(2000);
esp_deep_sleep_start();
Serial.println("這行程式應該是看不到了!");
}
void loop() {
}
程式名稱:ESP_Line_CameraCapture_Notify_LP1.ino
本範例的程式和上一章節差異不大,在此僅就比較明顯不同的部分加以說明。在程式開始的1~9行是本範例程式引用到的函式庫,讀者們應該都不陌生才是,在此就不多做說明。這次新增的一個函式庫<EEPROM.h>是Arduino IDE中,將模組板上內建的Flash記憶體模擬成EEPROM之用的函式庫。
#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include "soc/soc.h" // Disable brownour problems
#include "soc/rtc_cntl_reg.h" // Disable brownour problems
#include "driver/rtc_io.h"
#include <EEPROM.h> // read and write from flash memory
#include <TridentTD_LineNotify.h>
如果我們用ESP32中的RAM去儲存拍攝照片的編號,由於系統每次被叫醒等於是重新開始執行程式,這個號碼在每次重新執行時都會被定義成同樣的數字;為了避免這個問題,所以我們將每次拍攝照片的號碼放在EEPROM上,然後再把這個數字加1,這樣就不會都是出現同一個數字了!下面這行程式會將模組板上內建的Flash記憶體模擬成EEPROM,我們的系統為了避免長時間使用後儲存的照片量太大,以致超過SD卡的容量而發生錯誤,所以將會限制只保存最後的100張照片,超過之後就會被新的覆蓋掉。由於100這個數字小於255,所以只需要一個byte的記憶體就可以儲存了。
#define EEPROM_SIZE 1
下面這三行程式碼請依序修改成你自己所使用的WiFi分享器名稱(SSID)與密碼(Password),並在「LINE_TOKEN」後面填上你申請到的Line群組通訊用憑證(Token)。
#define WIFI_SSID "Your_WiFi_SSID"
#define WIFI_PASSWORD "Your_WiFi_Password"
#define LINE_TOKEN "Your_Line_Token"
接著的程式是屬於初始化(setup())程式區的部分,ESP32-CAM模組板每次在拍照時都會啟動內建的高亮度LED燈,在此我們使用下列程式先將這個燈熄滅,以節省電源(此超高亮度LED是接在模組板的GPIO4上)。
pinMode(4,INPUT);
digitalWrite(4,LOW);
rtc_gpio_hold_dis(GPIO_NUM_4);
其實我們所有的程式碼都只放在於初始化(setup())程式區而已,接下來程式81~97行的動作一如以往,就是先🡪初始化CAM相機🡪掛載Micro SD卡模組🡪測試SD卡是否插入等,再來就是啟動相機拍攝照片。
// 使用相機抓取照片
camera_fb_t * fb=esp_camera_fb_get();
if(!fb)
{
Serial.println("相機抓取錯誤!");
return;
}
要將Flash記憶體模擬成EEPROM,必須先初始化這個記憶區,然後才能讀出上次儲存在這個單一byte位址的記憶值,加1之後就是這次所拍攝照片的流水編號。為了避免長時間的使用以致於SD記憶卡的容量爆棚,所以在我們的系統中只會保留最後100張的照片內容,因此每次照片編號值加1之後,必須判斷是否已經到達100,如果是,就會讓這個流水編號回到1的值。下面程式的最後一行會依目前的照片編號組合成照片在SD卡上存檔的檔案名稱。
EEPROM.begin(EEPROM_SIZE);
pictureNumber = EEPROM.read(0)+1 ;
if (pictureNumber > 100)
pictureNumber = 1;
// Path where new picture will be saved in SD Card
String path = "/picture" + String(pictureNumber) +".jpg";
當ESP32-CAM模組抓取照片成功,且照片的存檔名稱也確定了,當然就是該把抓到的照片資料寫入SD卡內,所以下面的程式依序在做:🡪開啟檔案🡪寫入檔案🡪關閉檔案的動作。除此之外,我們還必須把目前已經使用到的照片流水號碼儲存到ESP32-CAM模組上的模擬EEPROM內,由於它不是真正的EEPROM記憶體,所以光執行寫入的動作(即指令“EEPROM.write()”)還不夠,ESP32並不會真正的把資料寫入到FLASH上,最後還必須使用”EEPROM.commit()”這個指令才算功德圓滿!
fs::FS &fs = SD_MMC;
Serial.printf("Picture file name: %s\n", path.c_str());
File file = fs.open(path.c_str(), FILE_WRITE);
if(!file){
Serial.println("Failed to open file in writing mode");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.printf("Saved file to path: %s\n", path.c_str());
EEPROM.write(0, pictureNumber);
EEPROM.commit();
}
file.close();
由於開啟WiFi連線的動作會比較耗電,所以本範例把連線的動作放在後面才進行,程式131~148行便是連線WiFi的動作程式。
在連線成功之後,我們先必須初始化Line Notify,由於每次發送完抓取到的照片之後,系統就會再次進入省電的睡眠模式,為了提醒使用者,我們的程式會在每次送出編號為1的照片時,同時也送出像上一個範例中一開始的Line的提示訊息與貼圖給使用者。
在我們發送給使用者的警示訊息與抓取到的照片時,也會順便送出該張照片的名稱與編號,以供使用者在需要時可以從SD卡中讀取出來做比對之用。
LINE.setToken(LINE_TOKEN);
if (pictureNumber == 1) // 第一次或送出編號為1的照片會送出提示訊息
{
Serial.println("程式開始! 先送出Line的提示訊息與貼圖...");
LINE.notify("你好,我是ESP32-CAM! ");
LINE.notifySticker("歡迎使用Line抓圖傳送系統! ",11539,52114110);
LINE.notifySticker("系統已經準備好,可以開始使用了! ",11537,52002740);
}
Serial.println("送出照片到Line Notify....");
LINE.notifySticker("ㄚ! 有人入侵!! ",11538,51626511);
LINE.notifyPicture( "相機抓圖已啟動!"+path, fb->buf, fb->len);
esp_camera_fb_return(fb);
當照片傳送完之後,最好還是先把曝光用的超高亮度LED關閉,以節省電力。
// Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
rtc_gpio_hold_en(GPIO_NUM_4);
接著用下面的程式設定GPIO13腳為輸入腳位喚醒(Wake-Up)之用,而且使用低態位準(‘0’)作為喚醒的輸入信號。
esp_sleep_enable_ext0_wakeup(GPIO_NUM_13,0);
最後就是讓ESP32進入深度的睡眠模式了,由於ESP32已經進入了睡眠狀態,所以最後一行程式的內容就如其文,使用者是不可能在Arduino IDE的監控視窗中看到該筆訊息的,純粹只是裝飾用!
delay(1000);
Serial.println("ESP32-CAM要開始睡覺了!");
delay(2000);
esp_deep_sleep_start(); // 令ESP32進入深度的睡眠模式
Serial.println("這行程式應該是看不到了!");
由於所有的程式在初始化(setup())程式區就已經全部結束,所以主迴圈程式部分是空白的就可以了。
void loop() {
}
◎ 執行結果:
下面的畫面是系統啟動時會在Arduino IDE的監控視窗上看到的內容,前面的部分是ESP32-CAM模組上內建的啟動輸出訊息,標記1的提示訊息表示抓取到的照片已經成功寫入到SD卡上了。至於標記2是連線WiFi分享器過程的提示訊息,及連線成功後ESP32-CAM所分配到的本地IP位址。接著的標記3則是表示已經將抓取到的照片及警示訊息傳送給使用者的Line Notify 群組上了,最後的標記4則是告知系統即將再次進入省電的睡眠狀態了。
在我們的程式中會送出兩種訊息給使用者的Line Notify 群組,一是系統第一次開機或是送出編號為1的照片時的提示訊息與貼圖,就如上一個範例中一開始發送給的Line的提示信息那樣,也就是下面這張圖片。
再來就是有外來的信號觸發了系統抓圖與傳送片的功能,這時會送出另一種格式的貼圖與訊息,其內容如下所示,和上一個範例的結果差不多,主要是訊息中多了該張照片的名稱與編號,樣當使用者想去找SD卡中的資料比對時可以有個依據。
沒有留言:
張貼留言