CameraWebServerに外付けLED制御用ボタンを追加する

「Unit Cam Wi-Fiカメラ」用のサンプルスケッチに「CameraWebServer」というものがあります。
これは、「Unit Cam Wi-Fiカメラ」をWebサーバにし、パソコンやスマホのWebブラウザからアクセスすることで、「Unit Cam Wi-Fiカメラ」で撮影したストリーミング画像をスマホなどで見ることができるというものです。

今回は、この「CameraWebServer」を編集してみたいと思います。
具体的には以下のようなものをつくります。

  • オリジナルのスケッチには画質調整用のボタンやスライダーがたくさんあるが、それらを全て削除する(ストリーミングの開始・終了のみできるようにする)。
  • 「Unit Cam Wi-Fiカメラ」に外付けLEDを接続し、そのLEDの明るさをスマホなどで変更できるようにする。

オリジナルのスケッチでは、Webブラウザで表示される内容は以下のとおりです。

これを以下のように変更します。

編集内容

Arduino IDEでサンプルスケッチ「ファイル」>「スケッチ例」>「ESP32」>「Camera」>「CameraWebServer」を開き、これを編集します。
中には以下の4つのファイルがあります。これらをそれぞれ編集することになります。

  • CameraWebServer.ino
  • camera_pins.h
  • app_httpd.cpp
  • camera_index.h

CameraWebServer.ino

以下を変更します。

  • カメラモデルを選択(「CAMERA_MODEL_M5STACK_ESP32CAM」を選択する)
  • Wi-Fi設定(Wi-FiネットワークのSSID、パスワードを設定する)
  • 画質の設定(画質を「31」に変更する)
        : 省略
// Select camera model
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
//#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM

#include "camera_pins.h"

const char* ssid = "XXXXXXXX";
const char* password = "XXXXXXXX";

void startCameraServer();

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  camera_config_t config;
        : 省略
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 31;
    config.fb_count = 1;
  }
        : 省略

camera_pins.h

以下を変更します。

  • ピン番号を変更(カメラモデル「CAMERA_MODEL_M5STACK_ESP32CAM」の「Y2_GPIO_NUM」の値を「17」から「32」に変更する)
        : 省略
#elif defined(CAMERA_MODEL_M5STACK_ESP32CAM)
#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
        : 省略

app_httpd.cpp

以下を変更します。

  • 「PWM_VALUE」変数の定義を追加
  • 「startCameraServer」関数を編集(LED点灯用信号についてPWM信号出力用の定義を行う)
  • 「cmd_handler」関数を編集(ボタンが押されたときにPWM値を変更してPWM信号を出力する)
        : 省略
int PWM_VALUE = 0;

static esp_err_t cmd_handler(httpd_req_t *req){
        : 省略
    else if(!strcmp(variable, "face_detect")) {
        detection_enabled = val;
        if(!detection_enabled) {
            recognition_enabled = 0;
        }
    }
    else if(!strcmp(variable, "face_enroll")) is_enrolling = val;
    else if(!strcmp(variable, "face_recognize")) {
        recognition_enabled = val;
        if(recognition_enabled){
            detection_enabled = val;
        }
    }
    else if(!strcmp(variable, "pwm-up")) {
      PWM_VALUE = PWM_VALUE + 5;
      if(PWM_VALUE > 100) PWM_VALUE = 100;
      Serial.printf("PWM=%d\n", PWM_VALUE);
      ledcWrite(7, PWM_VALUE);
    }
    else if(!strcmp(variable, "pwm-dn")) {
      PWM_VALUE = PWM_VALUE - 5;
      if(PWM_VALUE < 0) PWM_VALUE = 0;
      Serial.printf("PWM=%d\n", PWM_VALUE);
      ledcWrite(7, PWM_VALUE);
    }
    else {
        res = -1;
    }

    if(res){
        return httpd_resp_send_500(req);
    }

    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    return httpd_resp_send(req, NULL, 0);
}
        : 省略
void startCameraServer(){
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        : 省略
    config.server_port += 1;
    config.ctrl_port += 1;
    Serial.printf("Starting stream server on port: '%d'\n", config.server_port);
    if (httpd_start(&stream_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(stream_httpd, &stream_uri);
    }
    // PWM出力用設定
    pinMode(16, OUTPUT);
    ledcSetup(7, 490, 8); // channel0, 490Hz, 8bit
    ledcAttachPin(16, 7); // io16, channel0
}

camera_index.h

Webブラウザから「Unit Cam Wi-Fiカメラ」にアクセスしたときに表示されるHTMLファイルです。
Webブラウザで表示される内容を変えるためにはこのファイルを編集する必要がありますが、このファイルはHEX変換されているので、まずはテキストに戻します。

テキストに戻す手順は以下のとおりです。

  • こちら のサイトにアクセスします。
  • 「Input」欄に「index_ov2640_html_gz[]」の内容( { 〜 } の中身)を貼り付けます。
  • 「Operations」欄から「From Hex」を探し、「Recipe」欄の先頭に配置します。
  • 「Operations」欄から「Gunzip」を探し、「Recipe」欄に追加します。
  • 「Output」欄にHTMLファイルのテキストデータが表示されます。

表示されたHTMLファイル(テキストデータ)を編集します。
編集した結果は以下のとおりです。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>CAMERA CONTROL</title>
<style>
html {
  font-family: sans-serif;
  text-align: center;
}
button {
  color: white;
  background-color: steelblue;
  font-weight: bold;
  border: none;
  padding: 16px 40px;
  font-size: 24px;
  width: 300px;
}
</style>
</head>
<body>

<h1>CAMERA CONTROL</h1>
<figure>
<img id="stream" src="">
</figure>
<p><button id="toggle-stream">CAMERA START</button></p>
<p><button id="pwm-up">UP</button></p>
<p><button id="pwm-dn">DOWN</button></p>

<script>
document.addEventListener('DOMContentLoaded', function (event) {
  var baseHost = document.location.origin
  var streamUrl = baseHost + ':81'

  function updateConfig (el) {
    const query = `${baseHost}/control?var=${el.id}&val=1`
    fetch(query)
      .then(response => { console.log(`request to ${query} finished, status: ${response.status}`) })
  }

  const view = document.getElementById('stream')
  const streamButton = document.getElementById('toggle-stream')
  const pwmupButton = document.getElementById('pwm-up')
  const pwmdnButton = document.getElementById('pwm-dn')

  // Attach actions to buttons
  streamButton.onclick = () => {
    const streamEnabled = streamButton.innerHTML === 'CAMERA STOP'
    if(streamEnabled) {
      window.stop();
      streamButton.innerHTML = 'CAMERA START'
    } else {
      view.src = `${streamUrl}/stream`
      streamButton.innerHTML = 'CAMERA STOP'
    }
  }
  pwmupButton.onclick = () => {
    updateConfig(pwmupButton)
  }
  pwmdnButton.onclick = () => {
    updateConfig(pwmdnButton)
  }
})
</script>

</body>
</html>

編集後のHTMLファイルをHEX変換します。手順は以下のとおりです。

  • こちら にアクセスし、コードをダウンロードします。
  • (Macの場合)ターミナルを開き、ダウンロードしたCプログラムをコンパイルします。
gcc -o filetoarray filetoarray.c
  • (Macの場合)ターミナルで、編集後のHTMLファイルを圧縮します。
gzip [編集後のHTMLファイル]
  • (Macの場合)ターミナルで、先ほどコンパイルした「filetoarray」を使ってHTMLファイルをHEX変換します。
filetoarray [圧縮後のHTMLファイル] > tmp
  • 「tmp」ファイルを開きます。「camera_index.h」ファイルの「#define index_ov2640_html_gz_len」の値を、「tmp」ファイルの「#define [ファイル名]_len」行に書かれている数字に書き換えます。
    また、「camera_index.h」ファイルの「index_ov2640_html_gz[]」の内容を、「tmp」ファイルの「PROGMEM」の内容( { 〜 } の中身)に置き換えます。

動作確認

編集したスケッチを「Unit Cam Wi-Fiカメラ」に書き込みます。
書き込みが完了すると、シリアルモニタに、Webブラウザで「Unit Cam Wi-Fiカメラ」にアクセスする際のアドレスが表示されます。

「Unit Cam Wi-Fiカメラ」のGROVEポートを使って、「Unit Cam Wi-Fiカメラ」とLED、抵抗(47Ω)をつなぎます。
16番ピンとGNDの間にLEDと抵抗を直列につなぎます。

「Unit Cam Wi-Fiカメラ」への電源供給にもGROVEポートを使います。
Ni-MH電池4本(4.8V)をGROVEポートの5V、GNDにつなぎます。

スマホのWebブラウザで「Unit Cam Wi-Fiカメラ」にアクセスします。

「CAMERA START」をタップすると、ストリーミング画像が表示されます。
「UP」をタップするたびに、LEDが少しずつ明るくなります。「DOWN」をタップするたびに少しずつ暗くなります。

これでなんとか、ストリーミング画像を表示しながら、外付けの電子部品を制御できるようになりました。


なお、私がM5Stack、M5StickCの使い方を習得するのにあたっては、以下の書籍を参考にさせていただきました。


ごく基本的なところから、かなり複雑なスケッチや、ネットワーク接続など、比較的高度なものまで、つまづかずに読み進めていけるような構成になっており、大変わかりやすい本です。


このサイトで書いている、M5Stackシリーズ(M5Stack、M5StickCなど)に関するブログ記事を、「さとやまノート」という別のブログページに、あらためて整理してまとめました。

他のM5Stackシリーズの記事にも興味のある方は「さとやまノート」をご覧ください。