工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

ESP32-WROVER-Eを使って、OV2640使用200万画素カメラを動かす

概要

ESP32-WROVER-Eを使って、OV2640使用200万画素カメラで撮ったUXGAの画像をクラウドストレージに送信できた。



背景と目的

以前、ESP-WROOM-32を使って、OV2640使用200万画素カメラを動かしたのだが、ESP-WROOM-32のRAM不足により最大解像度UXGAでの撮影がうまくいかなかった。そこで、RAMが増強されたESP32=WROVER-Eを使って、UXGAでの撮影を試す。



詳細

0. やること

UXGAで写真を撮影し、クラウド上にアップロードする。

1. ESP32-WROVER-E

ESP32-WROVER-Eは、こちらに書いた通り、ESP-WROOM-32にPSRAMと呼ばれる外付けのRAMが備わったもの。

2. 撮影

2.1 ピン設定で間違えてハマる

今回の撮影部分のプログラムは、以前の記事に挙げてあるesp_cameraのサンプルを少し修正したプログラムをそのまま使ったのだが、実はピン設定がおかしかったことに気づいたので修正。PWDN_GPIO_NUM、RESET_GPIO_NUMは接続なしとする必要があった。実は、この間違いに全く気付くまで、プログラムの途中でおかしな再起動が多発し、かなりの時間を費やしてしまった。再起動が起こったある箇所を修正すると、今度は影響がないはずの前方の箇所で再起動が発生するという不思議な現象が起きていた。結局、このあたりからヒントを得て、どうにか修正にたどり着いた。

// ピン設定
#define Y2_GPIO_NUM 32
#define Y3_GPIO_NUM 35
#define Y4_GPIO_NUM 34
#define Y5_GPIO_NUM 5
#define Y6_GPIO_NUM 39
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 36
#define Y9_GPIO_NUM 19
#define XCLK_GPIO_NUM 27
#define PCLK_GPIO_NUM 23
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 22
#define PWDN_GPIO_NUM -1 // 変更
#define RESET_GPIO_NUM -1 // 変更

2.2 カメラ設定

UXGAで撮るには、config.frame_sizeを変更。

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if (psramFound()) {
    Serial.println("PSRAM found.");
    config.frame_size = FRAMESIZE_UXGA; // UXGAを選択
    config.jpeg_quality = 5;
    config.fb_count = 1;
  } else {
   :

2.3 撮影部分

esp_camera_fb_get関数で実行するだけ。pixel_formatをPIXFORMAT_JPEG(サンプルそのまま)なので、fb->bufには、jpgのバイナリデータが格納される。

  // 撮影
  camera_fb_t * fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return fb;
  }


3. アップロード

今回の送信先は、AWSのAPIGatewayのエンドポイント。JSONで受けて、バックエンドのLambdaでS3に保存する。なので、送信元はjpgバイナリデータをいったんbase64エンコードしてテキストに変換しJSONに格納する。(本当はapplication/octet-streamでバイナリを送った方がデータ量が少なくて済むが、クラウド側ができてしまっているので今回は不採用)

3.1 base64エンコード

extern "C" {
#include "libb64/cdecode.h"
#include "libb64/cencode.h"
}

以下のような関数を用意して呼び出して使う。

// base64エンコード
void base64_encode(uint8_t * packet, int packet_len, char * encoded) {
  
  base64_encodestate _state;
  base64_init_encodestate(&_state);
  int len = base64_encode_block((const char *) &packet[0], packet_len, &encoded[0], &_state);
  len = base64_encode_blockend((encoded + len), &_state);  

}

なお、呼び出し側で用意するencodedというエンコード結果格納用配列は、ps_callocを使ってPSRAM上にメモリ確保している。(またはps_malloc)UXGAのjpgバイナリは200kB程度があり、ESP32内部のRAMでは確保できないためだ。PSRAMがあると、できることが増えるというのを実感した。

  char * encoded = (char *) ps_calloc(size, sizeof(char));
  if (encoded == NULL) {
    Serial.printf("encoded=NULL!\n");
    while (1) {}
  }
  encoded[0] = '\0';
  Serial.printf("ps_calloc encoded at %x, size=%lu\n", &encoded[0], (unsigned long) size);

3.2 POST

実は、ここでもかなり悩まされてしまった。 ESP32でJSONをPOSTするために以前より自作して使用していた関数を今回も使ったところ、JSON BodyをWiFiClientSecure.printlnした直後に固まってしまった。いろいろ試行錯誤した結果、どうやらBodyのサイズが大きいと固まるらしく、Bodyを分割して複数回WiFiClientSecure->writeするとうまくいった。(POST POST自体を複数回行うという手もやってみたが、あまりに送信に時間がかかるのでボツ)

なので、以下のように、Bodyを分割してwriteするように関数を修正した。 なお、WiFiClientSecureのインスタンス化は、インスタンスをPSRAM上に確保するためにややこしい処理になっている。clientという変数は、WiFiClientSecureインスタンスではなくそのポインタ。そのせいで、各呼び出しが、client.***ではなく、client->になっている。(上述のBody送信後フリーズはが、WiFiClientSecureのインスタンスがRAM不足に起因すると思ってPSRAM上に確保してみたのだが、WiFiClientSecureのインスタンス自体はInternal RAMでも実は問題ない気もする。。。あとで余裕があれば試す。)

#include <WiFiClientSecure.h> // Wi-Fiに接続して、HTTPS通信するためのライブラリ

const int TIMEOUT_IN_MSEC = 60000; // タイムアウト[msec]

// POSTリクエストを送信する
// payload: 送信したいJSON Bodyのテキスト
void post_request(const char* host, const char* path, const char* payload) {

  // PSRAM上にインスタンスを確保するためのps_mallocとnew
  char * ptr = (char *) ps_malloc(sizeof(WiFiClientSecure));
  WiFiClientSecure* client = new (ptr) WiFiClientSecure;
  Serial.printf("client at %x\n", client);
  
  Serial.printf("Trying to connect to %s...\n", host);
  if (!client->connect(host, 443)) { // 接続にトライ
    // 接続失敗
    Serial.println("Connection failed!");
  } else {
    // 接続成功
    Serial.println("Connection success!");

    size_t contentLength = strlen(payload);
    Serial.printf("Content-Length: %d, Body=\"%c ... %c\"\n", contentLength, payload[0], payload[contentLength - 1]);
    
    // ヘッダ
    client->printf("POST %s HTTP/1.1\n", path); // POSTメソッド
    client->printf("Host: %s\n", host); // HTTP1.1で必須のヘッダ, アクセス先サーバーとしておく(そうしないとクロスドメインアクセスになる)
    client->println("Connection: close");
    client->println("Content-Type: application/json");
    client->printf("Content-Length: %d\n", contentLength);
    client->println(); // ヘッダの最後は空行
    
    // ボディ
    // 一度に大きなデータをprintfしたりwriteするとうまくいかない
    // mバイトに分割してwriteする
    int m = 1024;
    int i, j = 0;
    uint8_t * p = (uint8_t *) ps_malloc(m);
    while (true) {
      for (i = 0; i < m; i++) {
        p[i] = payload[i + j * m];
        if (i + j * m == contentLength - 1) break;
      }
      Serial.printf("j=%d, length=%d\n", j, i);
      client->write(p, m);
      client->flush();
      if (i + j * m == contentLength - 1) break;
      j++;
    }
    client->println();
    client->println(); // 空行を最後に

    // レスポンス待ち
    Serial.println("Waiting for reponse...");
    unsigned long curtime = millis();
    while (client->available() == 0) {
      if (millis() - curtime > TIMEOUT_IN_MSEC) {
        // タイムアウトのときは終了
        Serial.println(">>> Client Timeout !");
        client->stop();
        return;
      }
    }
    
    // レスポンス受信
    if (client->connected()) {
      while (client->available()) {
        char c = client->read();
        Serial.write(c); // 1文字ずつ出力
      }
      Serial.println("");
    }
    
    // 接続終了
    client->stop();
  }

}


4. 動作確認

無事、S3上に保存できた。



まとめと今後の課題

とにかく、ピン設定のミスと、Bodyの分割writeでハマってしまい非常に苦労した。ESP32-CAMのような出来上がっている基板を買えばもっとずっと楽だっただろう。とはいえ、ここまでたどり着けたので良しとする。


AWS IoT Device SDK v2でpublishするときのackCallbackとpacket idについて

概要

AWS IoT Device SDK v2でのpublishで、ackCallbackとpacket idについて旧版との違いがわかったのでメモした。



背景と目的

AWS IoT Device SDK v2を使う必要が出た。APIの仕様が旧バージョンから変わっているので、ちょっと動かして確認する。



詳細

0. 参考URL


1. インストール

pip install aws-iot-sdk

Windows PCでは、これだけでよかったが、他のあるLinux環境では、参考URLに書いてある通りwheelが取得できず、まず以下をやってからpipでのインストールが必要だった。

sudo apt-get update
sudo apt-get install cmake
sudo apt-get install libssl-dev


2. 動かして気づいたことをメモ

とりあえず、pubsubのサンプルを動かしてみた。

2.1 旧版のMQTTのClientオブジェクトに相当するもの

  • 旧: AWSIoTPythonSDK.MQTTLib.AWSIoTMQTTClient
  • v2: awscrt.mqtt.Connection

だいたい同じようなメソッドが並んでいる。

2.2 publishメソッドは非同期

  • 旧版: publish=同期、publishAsync=非同期
  • v2: publish=非同期、publishAsync=ない

awscrt.mqtt.Connectionのpublishメソッドには、旧版のpublishAsyncの引数ackCallbackに相当するものがない。旧版のackCallbackはどうやって実装すればいいのか?

2.3 ackCallbackに相当するもの

こちらにある通り、publishメソッドの戻り値タプルの1つ目concurrent.futures.Futureオブジェクトなので、concurrent.futures.Futureオブジェクトのadd_done_callbackメソッドで、ackCallbackを割り当てればよい。

def ackCallback(f):
    # PUBACK受信後の処理を記述
    :

# publish
future, packetId = mqtt_connection.publish(...)

# 戻り値futureにackCallbackを割り当て
future.add_done_callback(ackCallback)
:

2.4 packetIdは旧版と同じではない?

旧版では、publishAsyncの戻り値packetIdは、ackCallbackの引数midと対応していた。publishごとにpacketIdがインクリメントされるので、どのpublishのACKかということが、midで判断できた。

v2では、APIドキュメントを見る限りpublish戻り値タプル2個目packet_idに見えるので、サンプルコードに以下を追記して動かしてみたが、publishごとにpacket_idが変化してくれない。ずっと1のまま。

def ackCallback(f):
    # f.result()にpacket_idが入っている
    print(f.result())

future, packetId = mqtt_connection.publish(...)
print(packetId) # publishのpacket_id
future.add_done_callback(ackCallback)

旧版でできたのにv2でできないのは、おかしい気がする。何か考え方間違えているのか? 自分でpublish時に管理用のカウンタみたいなものを用意するしかないのか?



まとめと今後の課題

旧版とv2の違いがわかった。packet idがpublishごとにインクリメントされないのがいまいち解せない。


Raspberry Pi Picoを初起動

概要

Raspberry Pi Picoを動かした。



背景と目的

最近、Raspberry Pi Picoと名乗るマイコンボードが出た。基板サイズはArduino Nanoくらい。RAMやクロック周波数を見ると性能は全然こちらのほうが高そう。そして550円とかなり安いので、とりあえず試したくなって買ってしまった。(ESP32など同価格帯でもっと高性能なおもちゃは手元にあるのだが・・・) というわけで、ひとまず動かしてみる。



0.実施環境

  • Windows 10 PCを使用
  • MicroPythonを使用

1.調達

スイッチサイエンスで売ると書いてあったので入荷通知を待っていたのだが、通知があった数時間後に売り切れになっていた。これは入手に手間取りそうだと思っていたが、その数日後、秋月のネットショップを何気なく見たら、普通に売っていた。というわけで、秋月で購入。


2.マニュアル類を読む


3.実物を触る

3.1 MicroPython本体を書き込む

Raspberry Pi Picoには、標準で用意されたMicroPythonインタプリタ以外に、こちらのような独自拡張したMicroPythonを作って入れることもできるが、それは必要になったらおいおいやるとして、今回はとりあえず標準のMicroPythonを使用する。

https://www.raspberrypi.org/documentation/pico/getting-started/

まず、上記から、.uf2というRaspberry Pi Pico用MicroPython本体をダウンロードする。

次に、マニュアルに従い、BOOTSELボタンを押しながら、USBケーブルを接続すると、RPI-RP2というストレージが表示された。uf2ファイルをドラッグアンドドロップすると、ストレージが消える。これでMicroPython本体は書き込めたらしい。

Tera Termでシリアルポートを開くと以下のようになった。とりあえず、動きそう。

f:id:dekuo-03:20210219004249p:plain

3.2 LEDを点灯させる

SDKのドキュメントを見ながら、基板に載っているLEDをON/OFFしてみた。 Tera Termで直接入力したところ、LEDがON/OFFできた。成功。 GPIOを直接触るには、machineモジュールのPinを使う。Pinの引数modeにOUT/INなどを設定して入出力を設定する。オープンドレインやALTという周期動作をするモードもあるらしい。SDKのドキュメントには詳しく描いてあるので、そのうち試す。

from machine import Pin

led = Pin(25, mode=Pin.OUT)

# 点灯
led.on()

# 消灯
led.off()

3.3 MicroPython IDE Thonny

ターミナルに直接入力していてもしょうがないので、IDEでコードを作成してpyファイルを実行させたい。 Raspberry Pi Pico Python SDKのドキュメントにあるMicroPython IDE Thonnyを使うことにした。

https://thonny.org/

上記URLから、Windows用をダウンロードしてインストール。

マニュアルに従って、インタプリタの設定を行う。

f:id:dekuo-03:20210219005911p:plain

f:id:dekuo-03:20210219005920p:plain

いよいよ、コードを書く。

f:id:dekuo-03:20210219005942p:plain

実行ボタンを押したところ、1秒周期で点滅した。成功。


まとめと今後の課題

Raspberry Pi Picoを動かした。次は、すぐに何かしたいわけではないが、ADCあたりを使ってみるか。


ESP32のRAM周りの理解のなさを反省、カメラを使うならRAM増強が必要

背景

ここ最近、ESP32とカメラモジュールを繋いでをいじっていたのだが、RAM周りについて理解が深まったというか、そもそも理解が足らなかったと痛感した。

わかったこと

1. ユーザープログラム上の変数データで使えるRAMは120数kB

私の使っているESP-WROOM-32 DevKitCには、ESP-WROOM-32が載っていて、SRAMが520kBとあり、この分がすべて使えそうである。しかし、実際にユーザープログラム上の変数データで使えるのは、マニュアルにある通り、SRAM2の領域200kBのうちの一部のようだ。(ただし、あらゆる方法を試したわけではない。下記の方法で調べた限りで。)

以下のような全く何もしないArduinoプログラムを書き込んだ場合、

void setup(){

}

void loop (){

}

Arduino IDEの書き込みログで、約307kB使えると表示される。

最大327680バイトのRAMのうち、グローバル変数が13228バイト(4%)を使っていて、ローカル変数で314452バイト使うことができます。

一方、こちらを使って残りメモリを求めると、setup関数スタート時点で、

127168

となる。結局使えるのは124kB程度。背景で動いているFree RTOSなのか、Arduinoのせいなのか細かいことはわからないが。

以下のように、各変数の配置先アドレスを調べてみる。

const int a = 1; // constはFlashメモリに配置される
int c[256 * 108 + 185] = {0}; // グローバル変数としてRAMが消費される
int b = 2; // グローバル変数としてRAMが消費される

void setup() {

  int i = 0;

  Serial.begin(115200);
  Serial.printf("a, address=%x\n", &a);
  Serial.printf("i, address=%x\n", &i);
  Serial.printf("b, address=%x\n", &b);
  Serial.printf("c, address=%x - %x\n", &c[0], &c[256 * 108 + 185 - 1]);

}

void loop (){

}

マニュアルと照らし合わせると、aは、constなのでFlashメモリに配置されている。i, b, cこれはSRAM2の領域だ。他にもいろいろやってみたが、SRAM1への配置はされなかった。どうやら、SRAM2の120kB強くらいしか使えないらしい。

a, address=3f400178
i, address=3ffb878c
b, address=3ffbdbb4
c, address=3ffbfdd0 - 3ffdb0b0


2. PSRAMを外付け(External)することでRAMが増強できる

マニュアルを読むと、External Memoryという項目が確認できる。 SPI接続のSRAMを外付けすることで、4MBまでRAMが増強できるらしい。

ESP-IDF Programming Guideには、より詳しくプログラムから利用する方法がかかれている。 やってはいないが、Arduino IDEのメニューバー>ツールの中にPSRAMという項目があってこれを有効化すれば、外付けSRAMが使えそうだ。

f:id:dekuo-03:20210214220004p:plain


3. ESP32-CAMはPSRAMが増強されている

最近、ESP32にカメラを繋いでいじっていたときに、最高解像度設定(UXGA)で撮影したところ、一見ちゃんと取れたかに見えたのに、実は画像の一部が欠けて表示されていなかった。プログラムをいろいろいじってわかったのだが、上述の使用可能メモリ量のせいで、画像全体のデータが保持できていないことがわかった。

じゃあ、Amazonなどで格安で売っているESP32-CAMはどうしてちゃんと撮れるのか?というと、上述のPSRAMが増強されているから、ということがわかった。

ESP32-CAMの基板の写真をよく見ると、IPS6404という8本足のICが載っていて、調べたらこれがまさに外付けSRAMだった。まあ、カメラモジュール基板として作られているので必要なRAM容量が確保されているのは当たり前ではあるが、改めてちゃんと見ると納得。


3. ESP32-CAMが使えないとすると、PSRAMが一緒に載っているモジュールESP32-WROVER-Eを使って、カメラを繋げばいい

とはいえ、Amazonなどで売られているものの写真を見る限り、ESP32-CAMは技適をクリアしていない。(ように見える。)技適マークのあるものは見つからない。米国のFCCなどの認証はクリアしている。 総務省の説明によれば、FCC FCC認証OKのものは持ち込んでから90日は使えるとあるが、主に観光で来た外国人の持ち物を想定した話なので、ESP32-CAMをAmazonで買ったとしてそこから90日という風に適用されるのだろうか? また、技適未取得のものでも申請すれば一定期間使えるというルールの運用が始まった?のか、始まるのかわからないが、申請はしないといけない。

参考1 参考2

なので、結局ESP32-CAMが日本で大っぴらに使える状況なのかどうか、いまいち自信が持てないので、買うのを避けた。

じゃあ、ESP32-CAMが使えないとすると、手持ちのESP32に外付けSRAMをつければいいのだが、上述のIPS6404も、ESP-IDF Programming GuideにあるESP-PSRAM32も、ネットで探しても全く入手ルートがなさそうだった。ニーズがあまりないんだろうなあ。と、悩んでいたが、色々調べた結果、ESP32-WROVER-EというモジュールにはPSRAMが載っていて、技適マークも付いていて、すぐに使える開発ボードの形で秋月に売られてる

akizukidenshi.com

ことがわかった。 なので、これを買えばよい。もともと、ESP32-WROVER-Eというものがあることは知っていたのだが、手持ちのESP-WROOM-32で間に合ってしまったので全然気にしていなかった。やっと存在意義を知ることができた。


ArduinoでRAMの残りを求める関数を高速化する

Arudinoで、RAMの残りを求める関数として、以下のサイトにavailableMemoryというものが紹介されている。

https://playground.arduino.cc/Code/AvailableMemory/

これは、mallocの戻り値がNULLでなくなる=確保できるまで1バイトずつサイズを小さくして試すという仕組み。 原理が単純で分かりやすいのだが、ESP-WROOM-32などRAM容量の大きなものに対してそのまま使うと、非常に動作が遅い。また、残り容量次第で計算時間が大きく変化する。

そこで、高速化をしてみた。

mallocの戻り値がNULLでなくなるごとにstepという変数の値を半分に小さくしていく。つまり、バイナリサーチだ。size=1MB、step=128kBであれば、最悪でも40回程度で探索が終わってくれる。運が良ければ16回。

size_t availableMemory() {
  
  size_t size = 1024 * 1024; // 最大サイズ
  size_t step = 128 * 1024; // 探索ステップ
  
  while (true) {
    byte *buf;
    while ((buf = (byte *) malloc(size)) == NULL) {
      size -= step;
    }
    free(buf);
    //Serial.printf("step=%d, size=%d\n", step, size);
    step /= 2;
    if (step == 0) break;
    size += step * 2;
  }

  return size;
}

実際、使ってみると、非常に素早く終了する。デバッグ用に、プログラムの各所に入れても、それほど動作に影響を与えないだろう。

秋月で売っていたOV2640使用200万画素カメラをESP32につないで動かす

概要


秋月で売っているOV2640使用200万画素カメラをESP-WROOM-32につないで動作確認をした。

背景と目的


以前、こちらの記事で、カメラで成長記録を自動的に撮りたいと書きながら全く手がつかず2年近くたってしまったが、最近ようやく実行に移せそうな状況になった。そこで、自動水やり器のオプションとして追加できそうなカメラがないか探したところ、秋月で売っているOV2640使用200万画素カメラが、価格も手ごろで解像度もまずまずだったので、試しにESP-WROOM-32につないで撮影してみることにした。

詳細


0. 環境


1. 配線

秋月のサイトに載っている仕様書を見ても、いまいちわからかなったのだが、

https://www.mgo-tec.com/blog-entry-esp32-ov2640-ssd1331-test1.htmlhttps://www.mgo-tec.com/blog-entry-esp32-ov2640-ssd1331-test1.html/2

を参考にさせていただき、というか、まるまる同じ配線を行った。ブレッドボードに挿せるように20本をピンヘッダ、ピンフレームにはんだ付けするのはとても面倒だったが、まあ仕方ない。

f:id:dekuo-03:20210131013640j:plain


2. ソフトウェア

2.1 スケッチ

Arduino core for the ESP32のサンプルスケッチの中に、CameraWebServerというものがある。これは、ESP32上にWebServerを立て、ESP32と同じワイヤレスLANに接続されたスマホやPCでカメラの画像が見られるので、動作確認がしやすい。そこで、これを基に以下のような感じでコードを書いた。 Select camera modelという項目とcamera_pins.hのインクルードは不要なので削除し、その代わりにピン設定を定義。

#include "esp_camera.h"
#include <WiFi.h>

// ピン設定
#define Y2_GPIO_NUM 32
#define Y3_GPIO_NUM 35
#define Y4_GPIO_NUM 34
#define Y5_GPIO_NUM 5
#define Y6_GPIO_NUM 39
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 36
#define Y9_GPIO_NUM 19
#define XCLK_GPIO_NUM 27
#define PCLK_GPIO_NUM 23
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 22
#define PWDN_GPIO_NUM -1 // 2021/2/27 GNDに接続なので割り当てなし修正。
#define RESET_GPIO_NUM -1 // 2021/2/27 割り当てなしに修正。

// 接続先Wi-Fiアクセスポイント
const char* ssid = "*******";
const char* password = "*******";

:

// 以下、サンプルと同じため省略

2021/2/27 こちらの記事に従い修正。


2.2 書き込み

カメラとWi-Fiを同時に使うだけあってプログラムサイズが大きい。デフォルトの設定では194%となってしまい書き込めなかった。そこで、以下のようにPartition Schemeを変更したらちゃんと書き込めた。

f:id:dekuo-03:20210131012332p:plain


3. 動作確認

手持ちのスマホWebブラウザから、シリアルに表示されたIPアドレスにアクセスしたところ、以下のように設定画面が表示された。

f:id:dekuo-03:20210131013652p:plain

設定画面の一番下に、Start Streamボタンがあるので押したところリアルタイムに映像が表示された。ひとまず成功。しかし、初期設定では、VGA画質で余り画質が良くない。

f:id:dekuo-03:20210131013703p:plain

解像度をUXGAに変更したところ、以下のようにかなりきれいになった。これなら、植物の画像もまあまあきれいに撮れるか。ただし、画角が狭いのは気になる。

f:id:dekuo-03:20210131013716p:plain

というわけで、ESP32のサンプルのおかげで動作確認はすんなり終わってしまった。ひとまず、これでカメラはちゃんと使えそうだということが分かった。ブレッドボードからの線出しでもちゃんと動いたので、ESPから少し離して稼働させるという使い方もできそうだ。


まとめと今後の課題


自動水やり器のオプションとして追加できそうな小型のカメラモジュールをESP32につないで動作させることができた。次は、撮影した画像をクラウド等に保存させてみたい。

Wi-Fiの自動再接続スクリプト

概要


Raspberry Piで、Wi-Fiの接続が切れたときに自動で再接続するスクリプトを作成した。


背景と目的


自宅で使っているRaspberryPiのWi-Fi接続が、電波状況が悪いのかよく切れるので、切れたら自動で再接続するように、簡単なスクリプトを組む。


詳細


0. 参考

1. NetworkManger

NetworkManagerを使いたいので、インストールと設定をする。

1.1 インストール

sudo apt-get install -y network-manager

1.2 設定

/etc/NetworkManager/NetworkManger.confの設定

以下を変更

[ifupdown]
managed=true

/etc/network/interfacesの設定

以下を追記

auto wlan0
iface wlan0 inet dhcp

1.3 再起動

sudo reboot


2. 自動再接続スクリプト

スクリプトを作成して、デーモン化する。

2.1 ソースコード

  • wifi_auto_reconnect.sh
    • nmcli connection show でIP4.ADDRESSという項目が出力されなければ切れているとみなして再接続する。
#!/bin/bash

CONNECTION=$1 #接続名
INTERVAL=$2 # チェック周期[sec]
WAIT=$3 # 起動時待ち時間[sec]

echo "connection=$CONNECTION"
echo "INTERVAL=$INTERVAL sec"

# 起動時待ち時間
# すぐに動かしたくないので適宜入れる
echo "waiting $WAIT sec..."
sleep $WAIT

# 繰り返し
while true
do

    # IPアドレスを取得
    x=`nmcli c show $CONNECTION | grep IP4.ADDRESS`

    if [ "$x" == "" ]; then
        # IPアドレスが空なら接続されていないので接続
        nmcli c up $CONNECTION
    fi

    # 待つ
    sleep $INTERVAL

done

2.2 デーモン化

  • wifi_auto_reconnect.service
    • 開始時のチェック開始待ちは3分。チェック周期は60秒。
[Unit]
Description=Wi-FiAutoReconnectDaemon
After=NetworkManager-wait-online.target

[Service]
Type=simple
ExecStart=/home/pi/wifi_auto_reconnect.sh 接続名 60 180

[Install]
WantedBy=multi-user.target

実行権限を追加

sudo chmod 755 ./wifi_auto_reconnect.sh

配置

sudo cp ./wifi_auto_reconnect.service /etc/systemd/system

有効化

sudo systemctl enable wifi_auto_reconnect

開始

sudo systemctl start wifi_auto_reconnect


まとめと今後の課題


これで、とりあえず切れても復帰してくれそう。