簡単なStreamingと写真撮影(02)

簡単なStreamingと写真撮影。撮影した画像をSDカードに保存するプログラムを書いてみました。

回路

今回のESP32にはSDカードが無いのでSDカードを追加。回路は前回と同じ

プログラムを作製

サンプルスケッチを元に書いたStreamingと写真撮影、そしてそのデータをSDカードに保管するプログラムです。

sample_02.in0

#include "esp_camera.h"
#include "Arduino.h"
#include "SD.h"
#include "FS.h"

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>

// Pin definition for CAMERA_MODEL_WROVER_KIT
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    21
#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      19
#define Y4_GPIO_NUM      18
#define Y3_GPIO_NUM       5
#define Y2_GPIO_NUM       4
#define VSYNC_GPIO_NUM   25
#define HREF_GPIO_NUM    23
#define PCLK_GPIO_NUM    22

// Pin definition for SD Card
#define sd_sck  13
#define sd_mosi 15
#define sd_ss   12
#define sd_miso 14

WebServer server(80);
const char *SSID = "xxxxxxxxxx";
const char *PASSWORD = "yyyyyyyyyy";

// Take Picture with Camera
camera_fb_t * fb = NULL;
int c_state = 1;

String index_00 =
                  "<!DOCTYPE html>\n<html>\n<head>\n"
                  "<title>ESP32CAM</title>\n"
                  "<style type='text/css'>\n"
                  "button.bt1 {background:#90ee90;font-size:50px;padding:10px;margin:20px;border-radius:30px;box-shadow:4px 4px #555;}\n"
                  "</style>\n</head>\n"
                  "<body>\n<center>\n"
                  "<div style='width:700px; background-color:#d8a373; border-style:solid; border-radius:30px; border-color:#6b3f31'>\n"
                  "<p style='font-size:70px'><b><i><u>ESP32CAM</u></i></b></p>\n";
                  
void setup() 
{

    Serial.begin(115200);
    delay(1000);

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

    WiFi.mode(WIFI_STA);
    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());

    if (MDNS.begin("esp32cam")) 
    {
        Serial.println("MDNS responder started");
    }

    init_cam();
    
    SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss);
    SD.begin(sd_ss);

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

void loop() {

  server.handleClient();

}

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 handleRoot() 
{
  String index_dt,cmd;
  File dataFile;

    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1:   // Take photo
                if(c_state)
                {
                    fb = esp_camera_fb_get();
                    dataFile = SD.open("/data.jpg", FILE_WRITE);
                    dataFile.write(fb->buf, fb->len); // payload (image), payload length
                    dataFile.close();
                    esp_camera_fb_return(fb);
                    Serial.printf("Take a photo.\n");
                }
                c_state = !c_state;
                break;
    }

    index_dt = index_00;
    cmd = "<img src='/data.jpg'>\n"
          "<form method='get'>\n<button type='submit' name='1' class='bt1'>Stream</button>\n";
    if(c_state)
    {
        cmd = "<img src='/data.strm'>\n"
              "<form method='get'>\n<button type='submit' name='1' class='bt1' onClick='onBtnStearm()'>Photo</button>\n";
    }
              
    index_dt += cmd;
    index_dt += "</form>\n</div>\n</center>\n<script>\nfunction onBtnStearm() {\n window.stop();\n}\n</script>\n</body>\n</html>";
                        
    server.send(200, "text/html", index_dt);
}

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

    a = 1;
    path = server.uri();
    if(path.endsWith(".txt")) dataType = "text/plain";
    else if(path.endsWith(".css")) dataType = "text/css";
    else if(path.endsWith(".js")) dataType = "application/javascript";
    else if(path.endsWith(".png")) dataType = "image/png";
    else if(path.endsWith(".gif")) dataType = "image/gif";
    else if(path.endsWith(".jpg")) dataType = "image/jpg";
    else if(path.endsWith(".strm"))
    {
      client = server.client();
      String response = "HTTP/1.1 200 OK\r\n";
      response += "Content-Type: multipart/x-mixed-replace; boundary=--frame\r\n\r\n";
      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";
        server.sendContent(response);

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

        if (!client.connected()) break;
      }
      a=0;
    }

    delay(5);

    if(a)
    {
        dataFile = SD.open(path.c_str(), "r");
        server.streamFile(dataFile, dataType);
        dataFile.close();
        delay(5);
    }
}
  • 10−27行: ピンの定義
    • ピンは、” CAMERA_MODEL_WROVER_KIT // Has PSRAM” 用です。
    • サンプルスケッチから引用。
  • 29−33行: SD Card ピンの定義
  • 36−37行: ルータのSSIDとPassword
    • 今回はESP32にWebサーバーを立ち上げます。
    • ルーターにつなぐ為、ルーターのSSIDとPasswordをここに入力します。
  • 40行: camera_fb_t * fb = NULL
    • この変数は写真撮影時に使用します。
  • 53行: void setup()
    • 76行まで: WiFiに接続
    • 78−81行: コンピュータ名を”esp32cam”と設定。
      • WebサーバーへのアクセスはIPアドレスでは無くコンピュータ名で行います。
    • 83行: カメラの初期化。
      • 解像度をVGA指定(124行)。それ以外はサンプルスケッチと同じ
      • 設定したConfigのポインターを引数に、esp_camera_init(&config);を実行すれば初期化完了
    • 85−86行: SDカードの初期化
    • 88行: Webサーバーの起動
    • 91行: クライアント要求の対応先の指定。
      • クライアントからの要求は、handleRoot 関数で行う
  • 132行: void handleRoot()
    • 写真の保存
      • fb = esp_camera_fb_get();(143行)写真の撮影。
      • 144行:保存用ファイルの作製。
      • 145行:でーたの書込
      • 146行:書込終了。
      • 147行:esp_camera_fb_return(fb);。esp_camera_fb_get();とセットで使う様です。
    • モードの切り替え
      • WebサーバーのHPで表示されるボタンを押すたびモードを切り替えています。
  • Streamingはvoid handleWebRequests()で実行
    • 186−213行:クライアントからStreaming要求があったらここで対応しています。
    • カメラが撮影した画面(静止画)をひたすらクライアントに送信しています。
    • クライアントの接続の有無を終了判定にしています。
      • 判定としてはちょっと不適切に思いますが

プログラムの実行

プログラムをコンパイルして実行。PCでWebブラウザーをあげて下さい。ブラウザーはFire Foxを推奨します。どのブラウザーでも動作すると思うのですが他のブラウザーでは動作を確認していません。

  1. アドレス欄に、”esp32cam.local”と入力すると下記の様な画面が表示されます。
  2. この時、画像は静止がでは無く動画です。
  3. ”Photo”ボタンをクリックすると表示されていた画面が”data.jpg”としてSDカードに保存されます。
  4. ボタンが”Streaming”に代わり、画面に保存された画像が表示されます。(この時は静止画)
  5. この状態で”Streaming”を押せば、ボタンが”Photo”に代わり、Streamingが始まります。

次回は

設定をちゃんと行えば、fb = esp_camera_fb_get(); で画像の撮影が出来る事が分かりました。 また、これをSDカードに保存する方法も分かりました。

これにメール機能(指定先にメールを送る)を付けて訪問者チェックに使えないかと思っています。次回はメール機能を付けたいと思います。