ESP32とOV2640でJPEG画像をLINE通知【④プログラミング】

概要

ESP32ESP32とOV2640(もしくはM5Stack Unit Cam)を用いて、カメラで取得したJPEG画像をLINEに通知する電子工作とプログラムを紹介します。

本投稿では、ArduinoIDEにプログラミングを作成しESP32開発ボードに書き込みします。これで全ての作業が完了ですので実際にOV2640で撮影した画像をLINEに通知してみたいと思います。

また、ESP32とOV2640、もしくはM5Stack Unit Camのどちらを利用してもポートの配線が同じですのでプログラム(スケッチ)は全く同一ですので同様に利用可能です。

【参考】ライブラリが公開されているので利用した方が簡単にプログラムできるかもしれませんが、利用しなくてもそんなに難しいプログラムではないため学習のため利用せずにプログラムしています。
★ライブラリ★ESP-LINE-Notify

プログラミング開発環境

プログラミング開発環境にArduinoを用いています。Arduinoは主に電子工作を学習する人向けの世界標準の開発環境で無料で利用できます。学習しやすいのでそちらを利用しましょう。

下図のようにパソコンにUSBケーブル(マイクロUSBケーブル)でESP32開発ボードと接続できますので接続して利用します。

パソコンはArduinoIDEが動作するWindowsやMac、Linux端末をご利用下さい。また、ArduinoIDEは以下からダウンロードできます。(Downloadsからダウンロードできます。)

Software
Open-source electronic prototyping platform enabling users to create interactive electronic objects.

ESP32を利用するためのArduino環境構築の詳細はこちらを参照ください。ArduinoIDEの設定は以下のようにしてください。特にボードを「ESP32 Dev Module」にすることと、シリアルポートを接続したESP32開発ボードに設定されていることを確認ください。

※ボードの「ESP32 Dev Module」についてはVer1.0.6を利用して下さい。2022.4現在は、2.x.xは動作安定しなかったので推奨しません。

プログラム(スケッチ)

ArduinoIDEで以下のファイル(1つ)を作成します。

ファイル:「ESP32CamLine.ino」

//*************************************************************************
//  ESP32CamLine Ver2022.5.9
//  Arduino Board : ESP32 by Espressif Systems ver 1.0.6
//  Written by IT-Taro
//***********************************************************************
#include <WiFiClientSecure.h>
#include "esp_camera.h"

// ##################### Line,Wi-Fi設定(環境設定) #####################
String lineToken          = "PrfGCWQT2FB(省略)2g2s6OftxxC";   // Lineトークン【★変更要】

const char *ssid        = "##### SSID #####"; // 【★変更要】
const char *password    = "### PASSWORD ###"; // 【★変更要】
// ###################################################################
const char* lineServer = "notify-api.line.me";

// ピン配置
const byte LED_PIN      = 4;  // LED緑

// CAMERA_MODEL_M5_UNIT_CAM
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

WiFiClientSecure httpsClient;
camera_fb_t * fb;
bool ledFlag          = true; // LED制御フラグ
int waitingTime      = 30000; //Wait 30 seconds to response.

void setup() {
  // ####### 処理開始 #######
  Serial.begin(115200);
  Serial.println();
  Serial.println("Start!"); 

  // ####### カメラ設定 #######
  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;  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
  config.jpeg_quality = 10;
  config.fb_count = 1;
  // Newly Added! for "No PSRAM" Board "ESP32 ver 2.0.2" and "M5Stack-Unit-Cam"
  //config.fb_location = CAMERA_FB_IN_DRAM;
  // 2.0.2ではエラーが出るので上記記載が必要だがエラーが出なくなってもPingでロスが多発するため
  // 1.0.6(1.0.4)では上記記述は不要で安定動作する(1.0.xではPing確認でも安定動作)
  
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(3000);
    ESP.restart();
  }

  // ####### PIN設定開始 #######
  pinMode ( LED_PIN, OUTPUT );

  // ####### 無線Wi-Fi接続 #######
  WiFi.begin ( ssid, password );
  while ( WiFi.status() != WL_CONNECTED ) { // 接続するまで無限ループ処理
    // 接続中はLEDを1秒毎に点滅する
    ledFlag = !ledFlag;
    digitalWrite(LED_PIN, ledFlag);
    delay ( 1000 );
    Serial.print ( "." );
  }
  // Wi-Fi接続完了(IPアドレス表示)
  Serial.print ( "Wi-Fi Connected! IP address: " );
  Serial.println ( WiFi.localIP() );
  Serial.println ( );
  // Wi-Fi接続時はLED点灯(Wi-Fi接続状態)
  digitalWrite ( LED_PIN, true );

  // ####### HTTPS証明書設定 #######
  // Serverの証明書チェックをしない(1.0.5以降で必要)【rootCAでルート証明書を設定すれば解決できる】
  httpsClient.setInsecure();//skip verification
  //httpsClient.setCACert(rootCA);// 事前にWebブラウザなどでルート証明書を取得しrootCA設定も可

  // ####### 画像取得 #######
  Serial.println("Start get JPG");
  getCameraJPEG();
  // ####### LineへPost #######
  Serial.println("Start Post Line");
  postLine();

  Serial.println("Line Completed!!!");
}

// Loop処理
void loop() {
  delay(100);
}

//  画像をPOST処理(送信)
void postLine() {

  Serial.println("Connect to " + String(lineServer));
  if (httpsClient.connect(lineServer, 443)) {
    Serial.println("Connection successful");

    String messageData = "--foo_bar_baz\r\n"
                      "Content-Disposition: form-data; name=\"message\"\r\n\r\n"
                      "ESP32CAM投稿\r\n"; // 表示したいメッセージ
    String startBoundry = "--foo_bar_baz\r\n"
                          "Content-Disposition: form-data; name=\"imageFile\"; filename=\"esp32cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String endBoundry   = "\r\n--foo_bar_baz--";

    unsigned long contentsLength = messageData.length() + startBoundry.length() + fb->len + endBoundry.length();
    String header = "POST /api/notify HTTP/1.0\r\n"
                    "HOST: " + String(lineServer) + "\r\n" +
                    "Connection: close\r\n" +
                    "content-type: multipart/form-data;boundary=foo_bar_baz\r\n" +
                    "content-length: " + String(contentsLength) + "\r\n" +
                    "authorization: Bearer " + lineToken + "\r\n\r\n";

    Serial.println("Send JPEG DATA by API");
    httpsClient.print(header);
    httpsClient.print(messageData);
    httpsClient.print(startBoundry);
    //  JPEGデータは1000bytesに区切ってPOST
    unsigned long dataLength = fb->len;
    uint8_t*      bufAddr    = fb->buf;
    for(unsigned long i = 0; i < dataLength ;i=i+1000) {
      if ( (i + 1000) < dataLength ) {
        httpsClient.write(( bufAddr + i ), 1000);
      } else if (dataLength%1000 != 0) {
        httpsClient.write(( bufAddr + i ), dataLength%1000);
      }
    }
    httpsClient.print(endBoundry);

    Serial.println("Waiting for response.");
    long int StartTime=millis();
    while (!httpsClient.available()) {
      Serial.print(".");
      delay(100);
      if ((StartTime+waitingTime) < millis()) {
        Serial.println();
        Serial.println("No response.");
        break;
      }
    }
    Serial.println();
    while (httpsClient.available()) {
      Serial.print(char(httpsClient.read()));
    }
    /*Serial.println("Recieving Reply");
    while (httpsClient.connected()) {
      String retLine = httpsClient.readStringUntil('\n');
      //Serial.println("retLine:" + retLine);
      int okStartPos = retLine.indexOf("200 OK");
      if (okStartPos >= 0) {
        Serial.println("200 OK");
        break;
      }
    }*/
  } else {
    Serial.println("Connected to " + String(lineServer) + " failed.");
  }
  httpsClient.stop();
}

// OV2640でJPEG画像取得
void getCameraJPEG(){
  //  数回撮影してAE(自動露出)をあわせる
  esp_camera_fb_get();
  esp_camera_fb_get();
  fb = esp_camera_fb_get();  //JPEG取得
  if (!fb) {
    Serial.printf("Camera capture failed");
  }
  // 撮影終了処理
  esp_camera_fb_return(fb);
  Serial.printf("JPG: %uB ", (uint32_t)(fb->len));
}

作成方法は、ArduinoIDEの「ファイル」の「新規ファイル」で作成し、名前を付けて保存で「ESP32CamLine」をつけて以下のコピペします。下図のように作成できます

上記の状態で「ESP32CamGdriveAPI.ino」ファイルの以下の設定を書き換えてください。

String lineToken          = "PrfT2FB(省略)2g2s6xC";   // Lineトークン【★変更要】

const char *ssid        = "##### SSID #####"; // 【★変更要】
const char *password    = "### PASSWORD ###"; // 【★変更要】

上記の設定値については別投稿の「②LINE設定」で取得した情報を設定する必要があります。設定を書き換えた状態で、ESP32に書き込みを実施してください。

注意点(書き込みに関して)

【注意1】“Failed to connect to ESP32: Timed out waiting for packet header”

書き込みを行う場合に上記のエラーなどが発生する場合があります。
ArduinoIDEで書き込みボタンをクリックからコンパイルが完了し「Writing at…」が表示されるまでESP32開発ボードのBootボタンを押し続けて下さい。

——————–【ArduinoIDEのConsole表示内容】——————–
Sketch uses 213253 bytes (16%) of program storage space. Maximum is 1310720 bytes.
Global variables use 15372 bytes (4%) of dynamic memory, leaving 312308 bytes for local variables. Maximum is 327680 bytes.
esptool.py v2.6
Serial port /dev/cu.SLAB_USBtoUART
Connecting…     <= ★★★★★接続開始時にBootボタンの押下している必要があります
(省略)
Writing at 0x0000e000 <= ★★★★★書き込み初めているのでBootボタンは不要です
——————–——————–

UnitCamの場合は下図のようにブレッドボードのG0ポートとGNDポートを配線して書き込みを行ってください。また、ENポートとGNDポートを抜き差しすると再起動できますので、再起動してConsoleに”waiting for download”が表示されてから書き込むことを推奨します。(プログラム書き込み後、起動時は配線を外して買い込みモードで起動しないようにうする必要があります。)

下図のように書き込み時はG0とGNDを配線します(黄色ジャンパ)。ENとGNDを接続して再起動して利用しています(白色ジャンパ)。

動作確認

プログラムの書き込みに成功しESP32を再起動する(ENボタンをクリックで再起動)すると起動時に撮影しLINEに画像が通知されます。(下図参照)

その他(サーバ証明書チェック処理のスキップ)

今回のプログラムではhttps利用時のサーバ証明書をチェックしていません。Board:ESP32 ver1.0.5よりプログラムで以下の記載を行い処理をスキップしないとHTTPS接続時にエラーになります。

httpsClient.setInsecure();

HTTPS利用時のサーバ証明書はアクセスしたURLが間違いなくこのサーバですと証明するもので、主にフィッシング詐欺対策に利用されます。この処理をスキップしても暗号化などは実施されますので、今回はセキュリティ的にも特に問題になるようなことはないと考えて利用していません。

チェックする方はアクセスするサイトのルート証明書をWebブラウザなどで取得し設定下さい。

コメント

タイトルとURLをコピーしました