Monitorの製作 (02) <メールと人感センサー>

今回は前回製作したサーバーに人感センサーを取り付け訪問者が来たら写真を撮影しメール送信する機能を追加して行きたいと思います。

メールを送信する機能の追加

ESP32でのメール送信は添付ファイルを付けてメールを送るで説明しています。これを参考にすれば簡単に追加出来ます。またESP32にはNTPサーバーから現在の時間を読み取る関数が有り、これを使えば訪問時間をメールに反映する事が出来ます。

先ずはメール関係

人感センサーはハードの変更が必要なので変更の要らないメール関係のソフトから始めます。

必要なプログラムは3種類。それらに対し、下記の処置を行っています。

  • monitor.ino:  メインプログラム      ー> メール送信用の部分を追加
  • monitor.html: サーバーのHTMLファイル  ー> メール送信用のボタンを追加
  • monitor.css:  サーバーのCSSファイル   ー> 変更無し

monitor.ino

monitor.ino
#include "esp_camera.h"
#include "Arduino.h"
#include <SPIFFS.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32_MailClient.h>
#include "time.h"

// 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

#define Ready_LED           33

WebServer server(80);
WebServer st_server(81);

const char *SSID = "your SSID";
const char *PASSWORD = "your password";

int cam_state[5]={0};
#define ope_stat            1

IPAddress ip(192, 168, 3, 200);         // IP Address
IPAddress gateway(192,168, 3, 1);       // Gateway Address
IPAddress subnet(255, 255, 255, 0);     // Subnet Address
IPAddress DNS(192, 168, 3, 1);          // Subnet Address

#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465

/* The sign in credentials */
#define AUTHOR_EMAIL "xxxxxx@gmail.com"
#define AUTHOR_PASSWORD "yyyyyyyyyyyyyy"

/* Recipient's email*/
#define RECIPIENT_EMAIL "zzzzzzzzzzzz"

/* The SMTP Session object used for Email sending */
SMTPData smtpData;

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 9 * 3600;
const int   daylightOffset_sec = 0;

void setup() 
{
    sensor_t * cam_s;
    
    Serial.begin(115200);
    delay(100);

    Serial.println("Connecting to WiFi");
    WiFi.disconnect(true);
    WiFi.softAPdisconnect(true);
    delay(500);

    WiFi.mode(WIFI_STA);
    WiFi.config(ip, gateway, subnet, DNS);
    WiFi.begin(SSID, PASSWORD);
    delay(1000);

    // Try forever
    while (WiFi.status() != WL_CONNECTED) 
    {
        Serial.println("...Connecting to WiFi");
        delay(1000);
    }
    Serial.println("Connected");
    Serial.println(SSID);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    server.begin();
    server.on("/", handleRoot);
    server.onNotFound(handleWebRequests);
    Serial.println("HTTP server started");

    st_server.begin();
    st_server.on("/", st_handleRoot);

    pinMode(Ready_LED, OUTPUT);
    digitalWrite (Ready_LED, LOW) ;

    init_cam();
    cam_s = esp_camera_sensor_get();
    cam_state[ope_stat] = 0;
    cam_state[0] = cam_s->status.framesize;

    SPIFFS.begin();  

    //init and get the time
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

}

void loop() 
{
    server.handleClient();
    st_server.handleClient();
}

void send_mail()
{
    struct tm timeinfo;
    char m_title[80];
    
    //send email
    Serial.println("Sending email...");
    smtpData.setLogin("smtp.gmail.com", 465, AUTHOR_EMAIL, AUTHOR_PASSWORD);        //Set the Email host, port, account and password
    smtpData.setSender("ESP32-CAM", AUTHOR_EMAIL);                                  //Set the sender name and Email
    smtpData.setPriority("Normal");                                                 //Set Email priority or importance High, Normal, Low or 1 to 5 (1 is highest)
    getLocalTime(&timeinfo);                                                        
    sprintf(m_title,"%04d_%02d%02d_%02d:%02d:%02d",timeinfo.tm_year+1900,timeinfo.tm_mon+1,timeinfo.tm_mday,timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec);
    smtpData.setSubject(m_title);                                                   //Set the subject
    smtpData.setMessage("Image captured and attached.", false);                     //Set the message - normal text or html format
    smtpData.addRecipient(RECIPIENT_EMAIL);                                         //Add recipients, can add more than one recipient                                
    smtpData.addAttachFile("/data.jpg");                                            //Add attach files from SD card
//    smtpData.setFileStorageType(MailClientStorageType::SD);
    smtpData.setFileStorageType(MailClientStorageType::SPIFFS);                     //Set the storage types to read the attach files (SD is default)
    smtpData.setSendCallback(sendCallback);
    
    if (!MailClient.sendMail(smtpData))                                             //Start sending Email, can be set callback function to track the status
        Serial.println("Error sending Email, " + MailClient.smtpErrorReason());
    smtpData.empty();                                                               //Clear all data from Email object to free memory
}

/* Callback function to get the Email sending status */
void sendCallback(SendStatus msg)
{
    //Print the current status
    Serial.println(msg.info());

    //Do something when complete
    if (msg.success())
    {
        Serial.println("----------------");
    }
}

void handleRoot() 
{
    String cmd;
    int a,fl;
    File dataFile;
    camera_fb_t * fb = NULL;
    sensor_t * cam_s;

    fl=1;
    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1: // Get Still
                fb = esp_camera_fb_get();
                dataFile = SPIFFS.open("/data.jpg", FILE_WRITE);
                dataFile.write(fb->buf, fb->len); // payload (image), payload length
                dataFile.close();
                esp_camera_fb_return(fb);
                Serial.println("Take a photo.");
                cam_state[ope_stat] = 2;
                break;

      case 2: //Set Camera Parameter
                Serial.println("Set Camera Parameter");
                cmd=server.arg("2");
                cam_state[0] = cmd.toInt();
                cam_s = esp_camera_sensor_get();
                cam_s->set_framesize(cam_s,(framesize_t)cam_state[0]);        //framesize
                break;

      case 3: //  Stream Start 
                cam_state[ope_stat] = 1;
                break;
                
      case 4: //  Stream Stop 
                Serial.println("Stream Stop.");
                cam_state[ope_stat] = 0;
                break;

      case 5: //  Send Mail
                send_mail();
                break;
                
      case 80: // Send camera parameter
                cmd="";
                for(a = 0; a < 2; a ++) cmd += (String(cam_state[a]) + ',');
                server.send(200, "text/plain", cmd);
                fl=0;
                break;
    }

    if(fl)
    {
        dataFile = SPIFFS.open("/monitor.html", FILE_READ);
        server.streamFile(dataFile,"text/html");
        dataFile.close();
    }
}

void init_cam()
{
    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;
    config.frame_size = FRAMESIZE_VGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 10;
    config.fb_count = 2;

    // Init Camera
    esp_camera_init(&config);
}

void st_handleRoot() 
{
    WiFiClient client;
    camera_fb_t * fb = NULL;

    Serial.println("HTTP st_handleRoot started");
    client = st_server.client();
    String response = "HTTP/1.1 200 OK\r\n";
    response += "Content-Type: multipart/x-mixed-replace; boundary=--frame\r\n\r\n";
    st_server.sendContent(response);
    while (1)
    {
      fb = esp_camera_fb_get();
      
      if (!client.connected())
      {
        esp_camera_fb_return(fb);
        break;
      }

      response = "--frame\r\n";
      response += "Content-Type: image/jpeg\r\n\r\n";
      st_server.sendContent(response);

      client.write(fb->buf, fb->len);
      st_server.sendContent("\r\n");
      esp_camera_fb_return(fb);

      if (!client.connected()) break;
    }
}

void handleWebRequests()
{
    String dataType = "text/plain";
    String path;
    File dataFile;
    camera_fb_t * fb;

    path = server.uri();
    if(path.endsWith(".txt")) dataType = "text/plain";
    else if(path.endsWith(".jpg")) dataType = "image/jpeg";
    else if(path.endsWith(".css")) dataType = "text/css";
    else if(path.endsWith(".js")) dataType = "application/javascript";
    else if(path.endsWith(".html")) dataType = "text/html";
    else if(path.endsWith(".ico")) dataType = "image/html";

    server.sendHeader("Cache-Control", "public, max-age=86400");
    
    dataFile = SPIFFS.open(path.c_str(), "r");
    server.streamFile(dataFile, dataType);
    dataFile.close();
    delay(5);
}
  • 6行:#include <ESP32_MailClient.h> メール送信に必要なヘッダーファイル。
  • 7行:#include “time.h” 時間操作に必要なヘッダーファイル。
  • 33,34行: ルーターのSSID Passwordを設定して下さい。
  • 39から42行: IPアドレス固定に必要なパラメタ。今回はDNSも指定する必要が有りました。NTPサーバにアクセスする関係と思われますが確認は出来ていません。
  • 44から55行: メールを送信する為に必要なパラメータ。各項目の説明ー>添付ファイルを付けてメールを送る
  • 57から59行: NTPサーバーにアクセスするために必要はパラメータです。
  • 74行:WiFi.config(ip, gateway, subnet, DNS);ー>前回はDNSの指定はなかったのですが、NTPサーバーにアクセスするには指定する必要が有る様です。
  • 108行: ここで時間の初期化を行っています。
  • 118から154行: メールを送信する関数です。
    • 128行: メールを送る直前にNTPサーバーにアクセスして現在の時間を読み取っています。
    • 129行: 現在の時間をメールの題名に変換しています。これで受取側がメールの送信時間が分かります。
    • 130行: ここでメールの題名にセット
    • 135行: ファイルはSPIFFSで保存されているので、それを指定。
  • 156から213行: サーバー側の処理
    • 195行: ここにメールを送る部分を追加しています。

monitor.html

momitor.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link rel='stylesheet' type='text/css' href='monitor.css' >
        <title>ESP32 Monitor</title>
    </head>
    <body>
        <section class="main">
            <div style='font-size:40px'><b><i><u>Monitor</u></i></b><br></div>
            <div id="content">
                    <nav id="menu">
                    	<form method='get'>
                    		<sp>
     	                    <button type='submit' name='3'>Start</button><br>
    	                    <button type='submit' name='4' id="st_stop">Stop</button><br>
    	                    <button type='submit' name='1'>Take</button><br>
    	                    <div style = "margin-left: 25%;">
		            		<a style="color:#ffff00;cursor: pointer;" href='./data.jpg' download='data.jpg'>Download</a>
		            		</div><br>
    	                    <button type='submit' name='5'>Mail</button><br>
							</sp>
    	                </form>
                        <div class="input-group">
                        	<br><br>
                            <select id="1">
                                <option value='0' style="display:none">QQVGA(160x120)</option>
                                <option value='1'>QQVGA(160x120)</option>
                                <option value='2'>QCIF(176x144)</option>
                                <option value='3'>HQVGA(240x176)</option>
                                <option value='4'>QVGA(320x240)</option>
                                <option value='5'>CIF(400x296)</option>
                                <option value='6'>VGA(640x480)</option>
                                <option value='7'>SVGA(800x600)</option>
                                <option value='8' selected >XGA(1024x768)</option>
                                <option value='9'>SXGA(1280x1024)</option>
                                <option value='10'>UXGA(1600x1200)</option>
                            </select>
                        </div>
                    	<form method='get' id='abc'>
                        	<input name="2" id='123' style="display:none">
                    	</form>
                  </nav>
                <figure> <img id="stream" src="black.jpg" alt="photo"> </figure>
            </div>
        </section>
      <script>
      	var para = Array(4);
		document.addEventListener('change', function (event) 
		{
    		var targetElement = event.target || event.srcElement;
			document.getElementById('123').value = targetElement.value;
			document.getElementById('abc').submit();
		}
    	,false);

		document.addEventListener('DOMContentLoaded', function (event) 
		{
    		var url = "http://192.168.3.200/?80=";
    		var xhr = new XMLHttpRequest();
			var a,b,str;
    		xhr.open('GET', url);
    		xhr.send();
    		xhr.onreadystatechange = function()
    		{
      			if(xhr.readyState === 4 && xhr.status === 200)
      			{
					console.log( xhr.responseText );
			        b=0;
        			for(a = 0; a < 2; a++)
        			{
			            para[a] = '';
        			    while( xhr.responseText[b] != ',')
        			    {
        			        para[a] += xhr.responseText[b];
        			        b ++;
        			    }
        			    b ++;
        			}
        			
					document.getElementById("1").selectedIndex=Number(para[0]);
					
					switch(Number(para[1]))
					{
						case 0:	document.getElementById("stream").src = `black.jpg`;
								break;
						case 1:	
								document.getElementById("stream").src = `http://192.168.3.200:81/`;
								break;
						case 2:	document.getElementById("stream").src = `data.jpg`;
								break;
					}
    			}	
  			}
		});

		function stream_stop() 
		{
			window.stop();
    		var url = "http://192.168.3.200?4=";
    		var xhr = new XMLHttpRequest();
    		xhr.open('GET', url);
    		xhr.send();
		}
		
		//history.pushState(null,null,'/');
		
      </script>
    </body>
</html>
  • 前回からMailボタンの追加しました。

プロジェクトの構成

プロジェクトの構成は以下の通り。

  • プロジェクトのルート:
    • monitor.ino: Arduino ファイル
    • data:     フォルダー (SPIFFS用)
      • monitor.html
      • monitor.css
      • favicon.ico: カメラのマークのファビコファイル。(今回追加)
      • black.jpg: 画像指定無い時に使用。(今回追加)

コンパイルしてSPIFFS用ファイルをUploadして実行して下さい。ちゃんと写真が添付されたメールが送信出来ます。

人感センサー

今回人感センサーは、人感センサー(小型)を使います。電源とGNDと信号ピンの3ピン構成。信号は通常がLowで、人を検出するHighになる様です。

ESP32CAM側のピン配置は以下の通り。

今回はSDカードを諦めているので、GPIO4,13,16が使用可能です。ちなみに、もしGPIOを1つしか使わないならGPIO16を使用しSDカードを諦める必要は有りません。

割り込みに関しての説明は外部信号割り込みに有ります。下記の様にセンサーを配線し割り込み部分のみプログラムを書いて動作を確認しました。確かに検知すると信号が約2秒位Highになりました。問題無く使えそうです。

割り込みを入れると動かなくなります

今度はサーバーのプログラムにこの割り込み処理を追加して行きます。割り込みの条件は以下様に設定しています。

  • 割り込み入力GPIO: GPIO13
  • 信号のMode   : 信号の立ち上がり

割り込み後実行される関数はボード上のLEDが点灯消灯するものです。

monitor.ino

    //init and get the time
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    pinMode(Ready_LED, OUTPUT);
    digitalWrite (Ready_LED, LOW) ;

    pinMode(int_pin, INPUT_PULLDOWN);
    attachInterrupt(int_pin, blink_LED, RISING);
}

void blink_LED()
{
    digitalWrite (Ready_LED, !digitalRead(Ready_LED)) ;
}
  • 5,6行: 点灯消灯させるLEDの準備
  • 8行: 割り込み用ポートの設定。通常はLowなのでプルダウン付きの入力に設定。
  • 9行: 割り込みの設定。
  • 12から15行: 割り込み処理関数の定義。割り込みがかかる度にLEDの値を反転しています。

コンパイルは問題無く完了するのですが、実行するとひたすらRebootを繰り返します。シリアルモニタの出力を見ると、

  • WiFiの接続は完了している。
  • HTTP Serverは開始している。
  • Guru Meditation Error: Core 1 panic’ed (LoadProhibited). Exception was unhandled.と表示しRebootを繰り返す。

理由が分からなかったので割り込みを諦めてひたすらGPIO13をモニターしようとも考えました。でもWebで検索するとException while initialising camera when using interrupts #256を見つけました。割り込みの方法をArduino IDEでは無くESP32 IDF方式にすれば良いと書かれています。関係する部分を書き出すと以下の様になります。


..................
............
...........

#define Ready_LED           33
#define int_pin             GPIO_NUM_13

void IRAM_ATTR ISR_HANDLER(void* arg) 
{
    gpio_intr_disable(int_pin);
    digitalWrite (Ready_LED, !digitalRead(Ready_LED)) ;
    gpio_intr_enable(int_pin);
}

void setup() 
{
  // put your setup code here, to run once:

    ..................
    ...........
    ............

    pinMode(Ready_LED, OUTPUT);
    digitalWrite (Ready_LED, LOW) ;

    pinMode(int_pin, INPUT_PULLDOWN);
    gpio_set_intr_type(int_pin, GPIO_INTR_POSEDGE);
    gpio_isr_handler_add(int_pin, ISR_HANDLER, NULL);
    gpio_intr_enable(int_pin);    

}
  • 6行: 割り込みに使うGPIOの定義。この様にGPIO_NUM_XXの形式で指定します。
  • 8行: 割り込みで実行される関数。ここでは割り込みがかかる度にLEDを点灯消灯しています。
  • 26行: 割り込み用GPIOの初期設定
  • 27行: 割り込みモードの設定
  • 28行: 割り込みがかかった時に実行する関数の設定
  • 29行: 割り込みの開始。

これを今回のプログラムに組み込むと以下の様になります。

monitor02.ino

#include "esp_camera.h"
#include "Arduino.h"
#include <SPIFFS.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32_MailClient.h>
#include "time.h"

// 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

#define Ready_LED           33

WebServer server(80);
WebServer st_server(81);

const char *SSID = "your SSID";
const char *PASSWORD = "your password";

int cam_state[5]={0};
#define ope_stat            1

IPAddress ip(192, 168, 3, 200);         // IP Address
IPAddress gateway(192,168, 3, 1);       // Gateway Address
IPAddress subnet(255, 255, 255, 0);     // Subnet Address
IPAddress DNS(192, 168, 3, 1);          // Subnet Address

#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465

/* The sign in credentials */
#define AUTHOR_EMAIL "xxxxxx@gmail.com"
#define AUTHOR_PASSWORD "yyyyyyyyyy"

/* Recipient's email*/
#define RECIPIENT_EMAIL "zzzzzzzzzz"

/* The SMTP Session object used for Email sending */
SMTPData smtpData;

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 9 * 3600;
const int   daylightOffset_sec = 0;

#define int_pin             GPIO_NUM_13

void IRAM_ATTR ISR_HANDLER(void* arg) 
{
    gpio_intr_disable(int_pin);
    digitalWrite (Ready_LED, !digitalRead(Ready_LED)) ;
    gpio_intr_enable(int_pin);
}

void setup() 
{
    sensor_t * cam_s;
    
    Serial.begin(115200);
    delay(100);

    Serial.println("Connecting to WiFi");
    WiFi.disconnect(true);
    WiFi.softAPdisconnect(true);
    delay(500);

    WiFi.mode(WIFI_STA);
    WiFi.config(ip, gateway, subnet, DNS);
    WiFi.begin(SSID, PASSWORD);
    delay(1000);

    // Try forever
    while (WiFi.status() != WL_CONNECTED) 
    {
        Serial.println("...Connecting to WiFi");
        delay(1000);
    }
    Serial.println("Connected");
    Serial.println(SSID);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    server.begin();
    server.on("/", handleRoot);
    server.onNotFound(handleWebRequests);
    Serial.println("HTTP server started");

    st_server.begin();
    st_server.on("/", st_handleRoot);

    init_cam();
    cam_s = esp_camera_sensor_get();
    cam_state[ope_stat] = 0;
    cam_state[0] = cam_s->status.framesize;

    SPIFFS.begin();  

    //init and get the time
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    pinMode(Ready_LED, OUTPUT);
    digitalWrite (Ready_LED, LOW) ;

    pinMode(int_pin, INPUT_PULLDOWN);
    gpio_set_intr_type(int_pin, GPIO_INTR_POSEDGE);
    gpio_isr_handler_add(int_pin, ISR_HANDLER, NULL);
    gpio_intr_enable(int_pin);    
}

void loop() 
{
    server.handleClient();
    st_server.handleClient();
}

void send_mail()
{
    struct tm timeinfo;
    char m_title[80];
    
    //send email
    Serial.println("Sending email...");
    smtpData.setLogin("smtp.gmail.com", 465, AUTHOR_EMAIL, AUTHOR_PASSWORD);        //Set the Email host, port, account and password
    smtpData.setSender("ESP32-CAM", AUTHOR_EMAIL);                                  //Set the sender name and Email
    smtpData.setPriority("Normal");                                                 //Set Email priority or importance High, Normal, Low or 1 to 5 (1 is highest)
    getLocalTime(&timeinfo);                                                        
    sprintf(m_title,"%04d_%02d%02d_%02d:%02d:%02d",timeinfo.tm_year+1900,timeinfo.tm_mon+1,timeinfo.tm_mday,timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec);
    smtpData.setSubject(m_title);                                                   //Set the subject
    smtpData.setMessage("Image captured and attached.", false);                     //Set the message - normal text or html format
    smtpData.addRecipient(RECIPIENT_EMAIL);                                         //Add recipients, can add more than one recipient                                
    smtpData.addAttachFile("/data.jpg");                                            //Add attach files from SD card
//    smtpData.setFileStorageType(MailClientStorageType::SD);
    smtpData.setFileStorageType(MailClientStorageType::SPIFFS);                     //Set the storage types to read the attach files (SD is default)
    smtpData.setSendCallback(sendCallback);
    
    if (!MailClient.sendMail(smtpData))                                             //Start sending Email, can be set callback function to track the status
        Serial.println("Error sending Email, " + MailClient.smtpErrorReason());
    smtpData.empty();                                                               //Clear all data from Email object to free memory
}

/* Callback function to get the Email sending status */
void sendCallback(SendStatus msg)
{
    //Print the current status
    Serial.println(msg.info());

    //Do something when complete
    if (msg.success())
    {
        Serial.println("----------------");
    }
}

void handleRoot() 
{
    String cmd;
    int a,fl;
    File dataFile;
    camera_fb_t * fb = NULL;
    sensor_t * cam_s;

    fl=1;
    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1: // Get Still
                fb = esp_camera_fb_get();
                dataFile = SPIFFS.open("/data.jpg", FILE_WRITE);
                dataFile.write(fb->buf, fb->len); // payload (image), payload length
                dataFile.close();
                esp_camera_fb_return(fb);
                Serial.println("Take a photo.");
                cam_state[ope_stat] = 2;
                break;

      case 2: //Set Camera Parameter
                Serial.println("Set Camera Parameter");
                cmd=server.arg("2");
                cam_state[0] = cmd.toInt();
                cam_s = esp_camera_sensor_get();
                cam_s->set_framesize(cam_s,(framesize_t)cam_state[0]);        //framesize
                break;

      case 3: //  Stream Start 
                cam_state[ope_stat] = 1;
                break;
                
      case 4: //  Stream Stop 
                Serial.println("Stream Stop.");
                cam_state[ope_stat] = 0;
                break;

      case 5: //  Send Mail
                send_mail();
                break;
                
      case 80: // Send camera parameter
                cmd="";
                for(a = 0; a < 2; a ++) cmd += (String(cam_state[a]) + ',');
                server.send(200, "text/plain", cmd);
                fl=0;
                break;
    }

    if(fl)
    {
        dataFile = SPIFFS.open("/monitor.html", FILE_READ);
        server.streamFile(dataFile,"text/html");
        dataFile.close();
    }
}

void init_cam()
{
    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;
    config.frame_size = FRAMESIZE_VGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 10;
    config.fb_count = 2;

    // Init Camera
    esp_camera_init(&config);
}

void st_handleRoot() 
{
    WiFiClient client;
    camera_fb_t * fb = NULL;

    Serial.println("HTTP st_handleRoot started");
    client = st_server.client();
    String response = "HTTP/1.1 200 OK\r\n";
    response += "Content-Type: multipart/x-mixed-replace; boundary=--frame\r\n\r\n";
    st_server.sendContent(response);
    while (1)
    {
      fb = esp_camera_fb_get();
      
      if (!client.connected())
      {
        esp_camera_fb_return(fb);
        break;
      }

      response = "--frame\r\n";
      response += "Content-Type: image/jpeg\r\n\r\n";
      st_server.sendContent(response);

      client.write(fb->buf, fb->len);
      st_server.sendContent("\r\n");
      esp_camera_fb_return(fb);

      if (!client.connected()) break;
    }
}

void handleWebRequests()
{
    String dataType = "text/plain";
    String path;
    File dataFile;
    camera_fb_t * fb;

    path = server.uri();
    if(path.endsWith(".txt")) dataType = "text/plain";
    else if(path.endsWith(".jpg")) dataType = "image/jpeg";
    else if(path.endsWith(".css")) dataType = "text/css";
    else if(path.endsWith(".js")) dataType = "application/javascript";
    else if(path.endsWith(".html")) dataType = "text/html";
    else if(path.endsWith(".ico")) dataType = "image/html";

    server.sendHeader("Cache-Control", "public, max-age=86400");
    
    dataFile = SPIFFS.open(path.c_str(), "r");
    server.streamFile(dataFile, dataType);
    dataFile.close();
    delay(5);
}

今度は割り込みがかかる度にESP32CAM上のLEDが点灯消灯します。割り込みが正常に動作している事が分かります。今回のプロジェクトを下記に添付します。

次回は

メールを送る機能、人感センサーで訪問者を認識する機能を追加したので次回はモニターの管理画面の修正を行いたいと思います。