電子工作/IoT連載 の2本目です。
背景、はじめに。 みなさんこんにちは。フューチャーの矢野です。
もう春ですね。この季節になると実家の壁に雀が巣を作ることがあります。
今年も雀が巣を作ったなら、それを見守れたら楽しいなと思います。
そこで、雀を見守るためのカメラを試作してみました。
概要 作ったのがこれです。(デモ )
今回実現したのは下記です。
LAN内ならカメラの映像が確認できる
雀がいない間はカメラの電源を落とすことで節電
カメラが起動したら通知が届く
かわいい雀の写真がLINEに通知される
カメラの設定はスマホからできる
フロー図
人感センサーに反応があると、カメラに電源が入ります。 カメラは起動するとまずWi-Fiに接続を試みて、失敗したらAPモードになり、APに接続することでWi-Fi設定などができるようになります。設定が終了したら、再起動して、またWi-Fiの接続を試みます。Wi-Fiの接続に成功したらば、まずLINEに通知を飛ばします。飛ばせない場合は、黙ってカメラのサーバーを立ち上げます。30秒以上センサーに反応がない場合は、カメラの電源が落ちます。ここのロジックはATTiny202のマイコン側でやっていますが、ESP32-CAMだけで実現できると思います。
使用の想定
カメラがアクセスするWi-Fiなどの設定はスマホからできるようにしました。カメラへのアクセスはLAN内に限定しました。インターネット越しに雀を見守ることはできません。
今回の想定は、ユーザー(私)がLINEの通知をみたあと、同じWi-Fi内にいるにスマホでカメラに接続する想定です。
回路図
常時人感センサーだけ給電されていて、人感センサーに反応があると、ほかのモジュールにも電源がいくようになっています。NchMOSFETとPchMOSFETを組み合わせて電源を入れる処理は実現しました。電源が入ったATTiny202がQ2のゲートを開けます。
これによって電源が切れないようなります。電源を切るときはQ2のゲートを閉じます。
ブレッドボードだとこうなります。 一枚のブレッドボードでサクッと作れるのは気持ちがいいですね。
パーツリスト 今回の工作で使ったパーツと、購入できるページを載せておきます。 ブレッドボードやジャンパーピンは省略します。
MOSFETやATTiny202などの表面実装パッケージをブレッドボードに差し込むために下記の基板を使いました。
ソースコード ソースコードは下記です。 ESP32のスケッチ例、CameraWebServerに下記のような変更を加えたものです。
Wi-Fi接続時にWiFiManager を用いることで、Wi-Fi設定をスマホからできるようにした。
HTTPClientを用いて、LINE NotifyのAPIを叩くようにした。
画像をHTTPClientを用いてLINEにPOSTするのですが、この記事 が大変参考になりました。
camera_server.ino #include "esp_camera.h" #include <WiFi.h> #include <DNSServer.h> #include <WebServer.h> #include <WiFiManager.h> #include <HTTPClient.h> #define CAMERA_MODEL_AI_THINKER #include "camera_pins.h" void startCameraServer () ;#define LINE_ACCESS_TOKEN "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" #define BOUNDARY "CHUNCHUNNOTICE20220327" HTTPClient http; int32_t linePost ( camera_fb_t *fb ) { String RequestURL="https://notify-api.line.me/api/notify" ; http.begin(RequestURL); if (fb) { String stConType ="multipart/form-data; boundary=" ; stConType+=BOUNDARY; http.addHeader("Content-Type" , stConType); String authorization = "Bearer " ; authorization += LINE_ACCESS_TOKEN; http.addHeader("Authorization" , authorization); String stMHead="--" ; stMHead += BOUNDARY; stMHead += "\r\n" ; stMHead += "Content-Disposition: form-data; name=\"message\";\r\n\r\n" ; stMHead += "Camera: http://" ; stMHead += WiFi.localIP().toString(); stMHead += "\r\n" ; stMHead += "--" ; stMHead += BOUNDARY; stMHead += "\r\n" ; stMHead += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"./a.jpg\" \r\n" ; stMHead += "Content-Type: image/jpeg \r\n" ; stMHead += "\r\n" ; uint32_t iNumMHead = stMHead.length(); String stMTail="\r\n--" ; stMTail += BOUNDARY; stMTail += "--\r\n\r\n" ; uint32_t iNumMTail = stMTail.length(); uint32_t iNumTotalLen = iNumMHead + iNumMTail + fb->len; uint8_t *uiB = (uint8_t *)malloc (sizeof (uint8_t )*iNumTotalLen); for (int uilp=0 ;uilp<iNumMHead;uilp++) uiB[0 +uilp]=stMHead[uilp]; for (int uilp=0 ;uilp<fb->len;uilp++) uiB[iNumMHead+uilp]=(fb->buf)[uilp]; for (int uilp=0 ;uilp<iNumMTail;uilp++) uiB[iNumMHead+fb->len+uilp]=stMTail[uilp]; int32_t httpResponseCode = (int32_t )http.POST(uiB,iNumTotalLen); http.end(); free (uiB); Serial.print("Response Code:" ); Serial.println(httpResponseCode); Serial.print("Response Body:" ); Serial.println(http.getString()); return (httpResponseCode); } } void takePhoto () { camera_fb_t * fb = NULL ; fb = esp_camera_fb_get(); if (!fb) Serial.println("Camera capture failed" ); linePost(fb); esp_camera_fb_return(fb); } 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; 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 ; } #if defined(CAMERA_MODEL_ESP_EYE) pinMode(13 , INPUT_PULLUP); pinMode(14 , INPUT_PULLUP); #endif 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(); if (s->id.PID == OV3660_PID) { s->set_vflip(s, 1 ); s->set_brightness(s, 1 ); s->set_saturation(s, -2 ); } s->set_framesize(s, FRAMESIZE_QVGA); #if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM) s->set_vflip(s, 1 ); s->set_hmirror(s, 1 ); #endif WiFiManager wifiManager; wifiManager.setConfigPortalTimeout(30 ); if (!wifiManager.autoConnect("BirdWatcher" ,"hogehoge" )) { Serial.println("failed to connect and hit timeout" ); } takePhoto(); startCameraServer(); Serial.print("Camera Ready! Use 'http://" ); Serial.print(WiFi.localIP()); Serial.println("' to connect" ); } void loop () { delay(10000 ); }
ATTINY202側のコードです。人感センサーが反応してから30秒間電源が落ちないように0pin(NchMOSFETに繋がっている)をオンにします。こちらもArduinoのブートローダーを書き込んでArduinoIDEで開発してます。
attiny202.ino bool keepPassive (int digiin,int interval) { for (int i=0 ; i<interval; i++) { if (digitalRead(digiin) == HIGH) return false ; delay(1000 ); } return true ; } void setup () { pinMode(0 , OUTPUT); pinMode(1 , INPUT); digitalWrite(0 , HIGH); } void loop () { if ( digitalRead(1 )==LOW && keepPassive(1 , 30 ) ) digitalWrite(0 , LOW); }
デモ
消費電力はテスターで測れる範囲で測った結果が下記です。電源の電圧は5Vです。
上記の通り、カメラを常時起動させているよりは電気を節約できていそうですね。
まとめ これでかわいい雀が巣を作っても見守れますね。今回はカメラ部分だけ作りました。
実際に雀を見守るとなると、巣箱と、カメラを動かす電源が必要になります。
電源にはソーラーパネルと鉛蓄電池を使おうかなと思っています。
ありがとうございました。
利用ツール・参考
diagrams.net(フロー図、利用想定図作成)
KiCad(回路図作成)
fritzing(ブレッドボードの図作成)
ffmpeg(デモ動画変換)
QuickTime Player + iPhone(デモ動画撮影)
Arduino HTTPClientでファイルのバイナリ送信
LINE Notify API Document([POST] /api/notify)