2022年1月29日 星期六

三、使用Line Notify傳送照片之安全監控系統之二---低功耗篇



在前一個章節中我們建構了一個標準照片擷取傳送與儲存的按全監控裝置不過假如我們使用的場域中有許多的地方都必須按裝這類的裝置時例如在一個有許多門窗的家庭或辦公室由於我們的系統使用無線WiFi作為信號傳輸之用所以信號的傳輸除非裝置離WiFi分享器太遠否則安裝上應該不會是問題可是這些裝置所需使用的電源尤其佈線的部分就會是一個麻煩的問題了!當然最簡單的方式就是使用電池做為系統的電源是大家應該也知道ESP32-CAM這個模組耗電是有名的為了解決這個問題在本範例中我們將示範如何使用ESP32-CAM建構一低功耗的照片擷取傳送與儲存裝置

此裝置將使用電池做為供應電源擷取照片的動作同樣以常見的焦電型紅外線PIR移動偵測模組來觸發,此外也可以用磁簧開關、微動開關或一般的按鈕來啟動。在待機狀態時系統是處於睡眠的省電模式當系統被觸發之後會先將擷取到的照片儲存起來立刻連上預設的WiFi分享器然後將擷取到的照片傳送到使用者的Line Notify群組上以提醒使用者有人或物進入系統的警戒範圍;在完成上述的兩項動作之後,ESP32-CAM模組便會再次進入深度的睡眠模式(deep-sleep mode)等待下次的觸發信號到來以節省電源的消耗


功能與動作說明

在我們的系統中將具備下面的動作與功能:

  1. 本系統使用電池(5V)做為系統的電源平常系統在待機狀態時是處於睡眠的省電模式當測到外部的輸入觸發信號時,便會啟動ESP32-CAM拍攝照片的功能,除了會將所拍攝到的照片儲存到ESP32-CAM上內建的Micro SD讀寫模中所放置的Micro SD卡上之外還會立刻將照片傳送到使用者的Line Notify群組上

  2. 此外部觸發輸入(輸入腳為GPIO13)為一高態的脈波信號,每一次的脈波輸入會啟動一次拍攝和儲存及傳送照片的動作。此觸發信號可由常見的焦電型紅外線PIR移動偵測模組來提供,此外也可以用磁簧開關、微動開關等機械接點的元件來執行

  3. 當系統將照片傳送到使用者的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電阻另一端即可


程式列表與說明

以下便是本範例所使用的完整程式表

  1. #include "esp_camera.h"

  2. #include "Arduino.h"

  3. #include "FS.h"                // SD Card ESP32

  4. #include "SD_MMC.h"            // SD Card ESP32

  5. #include "soc/soc.h"           // Disable brownour problems

  6. #include "soc/rtc_cntl_reg.h"  // Disable brownour problems

  7. #include "driver/rtc_io.h"

  8. #include <EEPROM.h>            // read and write from flash memory

  9. #include <TridentTD_LineNotify.h>

  10.  

  11. // define the number of bytes you want to access

  12. #define EEPROM_SIZE 1

  13.  

  14. #define WIFI_SSID        "Your_WiFi_SSID"

  15. #define WIFI_PASSWORD    "Your_WiFi_Password"

  16. #define LINE_TOKEN  "Your_Line_Token"

  17.  

  18. // Pin definition for CAMERA_MODEL_AI_THINKER

  19. #define PWDN_GPIO_NUM     32

  20. #define RESET_GPIO_NUM    -1

  21. #define XCLK_GPIO_NUM      0

  22. #define SIOD_GPIO_NUM     26

  23. #define SIOC_GPIO_NUM     27

  24.  

  25. #define Y9_GPIO_NUM       35

  26. #define Y8_GPIO_NUM       34

  27. #define Y7_GPIO_NUM       39

  28. #define Y6_GPIO_NUM       36

  29. #define Y5_GPIO_NUM       21

  30. #define Y4_GPIO_NUM       19

  31. #define Y3_GPIO_NUM       18

  32. #define Y2_GPIO_NUM        5

  33. #define VSYNC_GPIO_NUM    25

  34. #define HREF_GPIO_NUM     23

  35. #define PCLK_GPIO_NUM     22

  36.  

  37. int pictureNumber = 0;

  38. const byte shootPin = 16, picLED=33;

  39.  

  40. void setup() {

  41.   WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector

  42.   Serial.begin(115200);

  43.   

  44.   camera_config_t config;

  45.   config.ledc_channel = LEDC_CHANNEL_0;

  46.   config.ledc_timer = LEDC_TIMER_0;

  47.   config.pin_d0 = Y2_GPIO_NUM;

  48.   config.pin_d1 = Y3_GPIO_NUM;

  49.   config.pin_d2 = Y4_GPIO_NUM;

  50.   config.pin_d3 = Y5_GPIO_NUM;

  51.   config.pin_d4 = Y6_GPIO_NUM;

  52.   config.pin_d5 = Y7_GPIO_NUM;

  53.   config.pin_d6 = Y8_GPIO_NUM;

  54.   config.pin_d7 = Y9_GPIO_NUM;

  55.   config.pin_xclk = XCLK_GPIO_NUM;

  56.   config.pin_pclk = PCLK_GPIO_NUM;

  57.   config.pin_vsync = VSYNC_GPIO_NUM;

  58.   config.pin_href = HREF_GPIO_NUM;

  59.   config.pin_sscb_sda = SIOD_GPIO_NUM;

  60.   config.pin_sscb_scl = SIOC_GPIO_NUM;

  61.   config.pin_pwdn = PWDN_GPIO_NUM;

  62.   config.pin_reset = RESET_GPIO_NUM;

  63.   config.xclk_freq_hz = 20000000;

  64.   config.pixel_format = PIXFORMAT_JPEG; 

  65.  

  66.   pinMode(4,INPUT);

  67.   digitalWrite(4,LOW);

  68.   rtc_gpio_hold_dis(GPIO_NUM_4);

  69.   

  70.   if(psramFound()){

  71.     config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA

  72.     config.jpeg_quality = 10;

  73.     config.fb_count = 2;

  74.   } else {

  75.     config.frame_size = FRAMESIZE_SVGA;

  76.     config.jpeg_quality = 12;

  77.     config.fb_count = 1;

  78.   }

  79.   

  80.   // Init Camera

  81.   esp_err_t err = esp_camera_init(&config);

  82.   if (err != ESP_OK) {

  83.     Serial.printf("ESP32 照相機初始化失敗! 錯誤代碼為: 0x%x", err);

  84.     return;

  85.   }

  86.   

  87.   //Serial.println("Starting SD Card");

  88.   if(!SD_MMC.begin()){

  89.     Serial.println("SD 卡掛載失敗!");

  90.     return;

  91.   }

  92.   

  93.   uint8_t cardType = SD_MMC.cardType();

  94.   if(cardType == CARD_NONE){

  95.     Serial.println("找不到 SD卡!");

  96.     return;

  97.   }

  98.  

  99.   // 使用相機抓取照片

  100.   camera_fb_t * fb=esp_camera_fb_get();

  101.   if(!fb)

  102.   {

  103.     Serial.println("相機抓取錯誤!");

  104.     return;

  105.   }

  106.  

  107.   // initialize EEPROM with predefined size

  108.   EEPROM.begin(EEPROM_SIZE);

  109.   pictureNumber = EEPROM.read(0)+1 ;

  110.   if (pictureNumber > 100)

  111.     pictureNumber = 1;

  112.  

  113.   // Path where new picture will be saved in SD Card

  114.   String path = "/picture" + String(pictureNumber) +".jpg";

  115.  

  116.   fs::FS &fs = SD_MMC; 

  117.   Serial.printf("Picture file name: %s\n", path.c_str());

  118.   

  119.   File file = fs.open(path.c_str(), FILE_WRITE);

  120.   if(!file){

  121.     Serial.println("Failed to open file in writing mode");

  122.   } 

  123.   else {

  124.     file.write(fb->buf, fb->len); // payload (image), payload length

  125.     Serial.printf("Saved file to path: %s\n", path.c_str());

  126.     EEPROM.write(0, pictureNumber);

  127.     EEPROM.commit();

  128.   }

  129.   file.close();

  130.  

  131.   Serial.print("連線到 Wifi SSID ");

  132.   Serial.print(WIFI_SSID);

  133.   WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  134.   bool  outState = false;

  135.   while (WiFi.status() != WL_CONNECTED)

  136.   {

  137.     Serial.print(".");

  138.     outState = !outState;

  139.     if (outState)

  140.       digitalWrite(picLED, 0);

  141.     else

  142.       digitalWrite(picLED, 1);

  143.     delay(500);

  144.   }

  145.   digitalWrite(picLED, 0);

  146.   Serial.println();

  147.   Serial.print("\nWiFi 連線成功! IP address: ");

  148.   Serial.println(WiFi.localIP());

  149.  

  150.   LINE.setToken(LINE_TOKEN);

  151.   if (pictureNumber == 1) // 第一次使用會送出提示訊息

  152.   {

  153.     Serial.println("程式開始! 先送出Line的提示訊息與貼圖...");

  154.     LINE.notify("你好,我是ESP32-CAM! ");

  155.     LINE.notifySticker("歡迎使用Line抓圖傳送系統! ",11539,52114110);

  156.     LINE.notifySticker("系統已經準備好,可以開始使用了! ",11537,52002740);

  157.   }

  158.  

  159.   Serial.println("送出照片到Line Notify....");  

  160.   LINE.notifySticker("ㄚ! 有人入侵!! ",11538,51626511);

  161.   LINE.notifyPicture( "相機抓圖已啟動!"+path, fb->buf, fb->len);

  162.  

  163.   esp_camera_fb_return(fb); 

  164.   

  165.   // Turns off the ESP32-CAM white on-board LED (flash) connected to GPIO 4

  166.   pinMode(4, OUTPUT);

  167.   digitalWrite(4, LOW);

  168.   rtc_gpio_hold_en(GPIO_NUM_4);

  169.   esp_sleep_enable_ext0_wakeup(GPIO_NUM_13,0);

  170.   

  171.   delay(1000);

  172.   Serial.println("ESP32-CAM要開始睡覺了!");

  173.   delay(2000);

  174.   esp_deep_sleep_start();

  175.   Serial.println("這行程式應該是看不到了!"); 

  176. }

  177.  

  178. void loop() {

  179.   

  180. }

程式名稱: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卡中的資料比對時可以有個依據。


三、使用Line Notify傳送照片之安全監控系統之二---低功耗篇

在前一個章節中 , 我們建構了一個標準照片擷取 、 傳送與儲存的按全監控裝置 , 不過假如我們使用的場域中有許多的地方都必須按裝這類的裝置時 , 例如在一個有許多門 、 窗的家庭或辦公室 , 由於我們的系統使用無線WiFi作為信號傳輸之用 , 所以信號的傳輸除非裝置離WiFi分享...