安定稼働する定点カメラの作り方(M5CameraとNASを使用) その1

はじめに

お久しぶりです。寒い日が続きますが、お元気でお過ごしでしょうか?
今年の冬はニンニクを栽培しようとチャレンジしています。 実は一昨年ころたん(メロン)を育てた時にRaspberry Piを用いて定点撮影していたのですが、赤外線カメラだったばっかりに写真の色が変だったので、 今回は普通のカメラで定点撮影をしたくなりました。 そこでM5Cameraを用いて定点カメラを作り、実際に4か月以上連続稼働させて問題がなかったので共有します。

目次

ニンニク撮影システムの全体像と使用したもの

M5Cameraを使ってニンニクを撮影し、撮影した画像をNAS上に格納するシステムを作りました。
全体の概要図は以下となります。※WiFiはあくまでも家庭内で閉じています。 f:id:GypsophilaRupi:20220205180035p:plain

M5Cameraはカメラ付きマイコン(ESP32)です。 ArduinoIDEでM5Cameraにプログラムを書き込むことで、撮った写真をネットワーク経由で転送が可能となります。

www.switch-science.com

NASとはネットワークにアクセスできるハードウェアです。そのため、M5Cameraから転送された画像を格納することができます。
加えてNAS上のタスクマネージャーを使えば定期的にNAS上のPython起動することができます。NAS上のPythonは、M5Cameraに写真を撮る指令を送り、撮った写真をNAS上に格納するソフトウェアにしました。
ちなみに今回使ったNASはSynologyのNAS DS118です。

DS118はHDDが1つしかささらないので、HDD交換のことを考えると、HDDが2つささるDS218方がお勧めです。(買いなおしたいという声がどこからともなく聞こえてきます笑)
ただし、他のNASでもPythonが走れば何でもいいと思います。

やったこと

M5Cameraをサーバーにする

M5Cameraをサーバーにすべく、Arduinoでプログラムを編集・書き込みます。

Arduinoサンプルプログラムの準備
  1. Arduino IDEのボード選択
    ツールからArduino Wrover Moduleを選択します。 f:id:GypsophilaRupi:20211205230302p:plain

  2. Arduino サンプルプログラムの入手
    今回編集するサンプルプログラムはCameraWebServerです。ファイルースケッチ例ーESP32ーCameraから入手します。 f:id:GypsophilaRupi:20220205183618p:plain

Arduinoサンプルプログラムの編集

サンプルプログラム(CameraWebServer)からの変更点は以下です。 左がサンプルプログラムで右が編集後で、色がついているところが変更になっているところです。

  1. 環境設定
    4.のホスト名を指定するためにincludeを追加します。 M5Cameraを使うので、下図の通り、defineの内容を変更します。
    また、WifiのID等の変更します。 f:id:GypsophilaRupi:20220123213727p:plain

  2. 写真サイズの変更
    写真サイズをUXGA(1600×1200)に変更します。 f:id:GypsophilaRupi:20220123214452p:plain

  3. Wifiがつながらない間は10秒に1回再起動するように設定
    M5CameraをWifiを確実に繋げるために、Wifiがつながらない間はM5Cameraをソフト的に再起動をし続ける設定にしています。
    f:id:GypsophilaRupi:20220129195101p:plain

  4. ESP32のホスト名の指定
    M5StackカメラはWifiを繋ぎなおす度にIPアドレスが変わるので、ホスト名「http://esp32.local/capture」で写真を撮れるようにしました。
    ただまだこのURLでNASからはアクセスができないので、次回以降の記事でこの辺を改良出来たらと思います。 f:id:GypsophilaRupi:20220129195308p:plain

  5. LEDチカチカと5分に1回の再起動
    ESP32は安定動作ができないイメージがあるので、定期的にM5Cameraの再起動をするようにしています。 また、この解決策に至る過程での苦肉の策として、10秒に1回LEDを点滅させています(必要なのかわかりませんが、気休めに今もチカチカさせています。)
    ピンモードの設定をします。 f:id:GypsophilaRupi:20220123214134p:plain 10秒に1回LEDを点滅させ、5分に1回再起動するようにしています。 f:id:GypsophilaRupi:20220123215106p:plain

以上のことを行ったプログラムCameraWebServerが以下です。

#include "esp_camera.h"
#include <WiFi.h>
#include <ESPmDNS.h>
//
// WARNING!!! Make sure that you have either selected ESP32 Wrover Module,
//            or another board which has PSRAM enabled
//

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
//#define CAMERA_MODEL_AI_THINKER

#include "camera_pins.h"

const char* ssid = "WIFIのSSID";
const char* password = "パスワード";

void startCameraServer();

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


  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;
  //init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  
  pinMode(GPIO_NUM_14, OUTPUT); 
  
#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
  //initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1);//flip it back
    s->set_brightness(s, 1);//up the blightness just a bit
    s->set_saturation(s, -2);//lower the saturation
  }
  //drop down frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_UXGA);

#if defined(CAMERA_MODEL_M5STACK_WIDE)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (millis()>(10000)){
      ESP.restart();
    }
  }
  Serial.println("");
  Serial.println("WiFi connected");
  MDNS.begin("esp32"); // ホスト名 esp32.local
  startCameraServer();
  
  Serial.print("Camera Ready! Use 'http://");
  Serial.print(WiFi.localIP());
  Serial.println("' to connect");
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(GPIO_NUM_14,LOW);
  delay(10000);
  digitalWrite(GPIO_NUM_14,HIGH);
  delay(500);
  if (millis()>(5*60*1000)){
    ESP.restart();
  }
  //Serial.println("OK");
}

M5Cameraの写真をリクエスト&保存するPythonプログラムの作成

写真撮影するURLから得た写真を指定したローカルURL上に名前を付けて保存するようにしたプログラム(Python)は以下の通りです。 工夫した点は、写真撮影するURLが開いて、NAS上に写真を書き込むまで4回チャレンジするようにしたことです。これはM5Cameraが定常運転してくれないことを考えての設定です。

import urllib.error
import urllib.request
import datetime


def download_file(url, dst_path):
    for i in range(1,4):
        try:
            with urllib.request.urlopen(url,timeout=10) as web_file, open(dst_path, 'wb') as local_file:
                local_file.write(web_file.read())
        except urllib.error.URLError as e:
            print(e)
        else:
            return

dt_now = datetime.datetime.now()
print(dt_now.strftime('%Y%m%d%H%M'))
        
url = 'http://192.168.11.3/capture'
dst_path = 'data/test'+dt_now.strftime('%Y%m%d%H%M')+'.jpg'
download_file(url, dst_path)
print('finish')

ちなみに、M5Cameraで撮影した画像のIPアドレス(http://192.168.11.3)はWindowsコマンドプロンプトから以下のように見つけました。

C:\Users\****>ping esp32.local

esp32.local [192.168.11.3]に ping を送信しています 32 バイトのデータ:
192.168.11.3 からの応答: バイト数 =32 時間 =77ms TTL=255
192.168.11.3 からの応答: バイト数 =32 時間 =90ms TTL=255
192.168.11.3 からの応答: バイト数 =32 時間 =89ms TTL=255
192.168.11.3 からの応答: バイト数 =32 時間 =95ms TTL=255

192.168.11.3 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 77ms、最大 = 95ms、平均 = 87ms

NAS上のタスクマネージャーを使ってPythonのプログラムを定期的に動かす

4時から30分ごとに17時まで写真を撮り続けるような指定をしました。

  1. NAS上の任意の場所にPythonのプログラムを格納する
    PythonプログラムをNAS上で動かすために、NAS上の任意の場所にPythonプログラムを保存します。
    今回は\ds118\homes\□□□にGarlic.pyを保存しました。

  2. NASのコントロールパネルからタスクマネージャーを開く
    f:id:GypsophilaRupi:20220129203743p:plain

  3. 左の作成ボタンからユーザー指定のスクリプトを選択
    f:id:GypsophilaRupi:20220129204013p:plain

  4. スケジュール管理する
    f:id:GypsophilaRupi:20220129204212p:plain

  5. タスク設定をする
    1行目ではcdコマンドでpythonプログラムを格納した場所に移動し、2行目では作ったPythonプログラムの実行をしています。ちなみに今回のPythonプログラムの名前はGarlic.pyです笑 f:id:GypsophilaRupi:20220129204513p:plain

おわりに

何故動いているかわからない状態で4か月以上の連続稼働に成功しました笑
今回はこれを記事にすることで少し理解が進んでよかったです。 ちなみにニンニクを育てるという本来の目的は最初微妙に上手くいっていませんでした。 鉢を変える植え替えをしたら枯れてしまったので、台所に転がっていた芽が出ていた中国製のニンニクを代わりに植えています。 ちなみに昨日撮れた写真はこんな感じです。 f:id:GypsophilaRupi:20220206000636p:plain