ottijp blog

ESP32カメラWebサーバで家の外から猫さんの様子を見られるようにする

2022-04-24ESP32nginxArduino

1日家を空けるときに猫さんの様子が気になるので,カメラ付きESP32を使い,外から映像を確認できる仕組みを作りました.

自宅は固定IPではないのでDDNSが必要だったり,ESP32カメラサーバのサンプルスケッチはストリーム用のポートだけ81番だったりして手こずったので, やったことを記録しておきます.

環境

  • ESP32: ESP32-WROVER-DEV
  • カメラ: OV2640
  • Raspbian: 10 (buster)
  • nginx: 1.14
  • macOS: 12.3 (Monterey)
  • Arudino IDE: 1.8.19

構成

structure

やったこと

  • ESP32カメラWebサーバのセットアップ
  • MyDNSで自宅ネットワークへのドメインを作成
  • ホストへの固定IP設定とポートフォワーディング
  • フロントにnginxをおいて,ESP32カメラWebサーバにプロキシ

ESP32カメラWebサーバのセットアップ

Arudino IDEのBoard Managerでesp32をインストールし, サンプルスケッチをコンパイルして書き込みました.(File > Examples > ESP32 > Camera > CameraWebServer)

ssid, password変数だけ自宅WiFiのものに変更しました.

WiFiモジュールがArduinoビルトインのものと競合してコンパイルエラーになったため, Arduinoビルトインのもの(/Applications/Arduino.app/Contents/Java/libraries/WiFi)を削除しました.

MyDNSで自宅ネットワークへのドメインを作成

MyDNSを使って自宅IPを動的にDNSレコードに反映するようにしました. 私の場合はすでに持っていたドメインを使いたかったので,使いたいドメインのCNAMEにMyDNSのドメインを設定しましたが, MyDNSのドメインでアクセスする場合は,MyDNSだけでOKです.

MyDNSにアカウントを作ってドメイン名を決めたあとで, 自宅ネットワーク内にあるRaspberryPiに,定期的にDNSレコードを更新するサービスを登録しました.

/etc/systemd/system/mydns.service
[Unit]
Description=MyDNS IP Update

[Service]
Type=oneshot
EnvironmentFile=/home/pi/.mydns/env
ExecStart=/bin/sh -c 'curl -u ${MASTERID}:${PASSWORD} https://ipv4.mydns.jp/login.html'
/etc/systemd/system/mydns.timer
[Unit]
Description=Run mydns.service every 30min

[Timer]
OnBootSec=10min
OnUnitActiveSec=30m

[Install]
WantedBy=timers.target
/home/pi/.mydns/env
MASTERID=<your master id>
PASSWORD=<your password>
$ sudo systemctl enable mydns.timer
$ sudo systemctl start mydns.timer

これで30分間隔でDNSレコードの更新リクエストが送られるようになります.

ホストへの固定IP設定とポートフォワーディング

nginxを動かすRaspberryPiとESP32に固定IPが割り当てられるようにルータの設定を変更しました. また,外からのtcp/80とtcp/443をRaspberryPiへポートフォワーディングするようにルータの設定を変更しました.

nuroを使っていて,ルータ一体型ONU(F660A)の後段で自前のルータも使っています. 本当はルータ機能は自前のルータだけで行いたいのですが,F660Aのルータ機能はOFFにできないので, F660Aの設定でDMZを有効化し,自前ルータを設定しています. 同じようなルータ構成の場合,前段のルータに対して同様にDMZやポートフォワーディングの設定が追加で必要かもしれないので注意してください.

フロントにnginxをおいて,ESP32カメラWebサーバにプロキシ

ESP32カメラWebサーバはSSLや認証の機能がないので,フロントにnginxをおいてプロキシを構成しました.

注意点として,ESP32カメラWebサーバはtcp/80とtcp/81の2つのポートをリスンしていて, カメラ映像のストリームはtcp/81で行っていたので,以下のように工夫しました.

  • html中にあるストリーム用のURLを書き換え,nginxの静的ファイルとして配置する.

    • 変更前のストリーム用のURL: <host>:81/stream
    • 変更後のストリーム用のURL: <host>/stream
  • nginxへの<host>/streamへのアクセスを,ESP32カメラWebサーバの<host>:81/streamへプロキシする.

htmlはESC32カメラWebサーバから返ってくるものを以下のように変更して保存しました.

$ sudo mkdir /var/www/catcam
$ sudo curl -o - http://<ESP32 host> | gunzip | sed "s/var streamUrl = baseHost + ':81'/var streamUrl = baseHost/" > /var/www/catcam/index.html

さらに,ベーシック認証を有効にするために,.htpasswdを作成しました.

cf. Restricting Access with HTTP Basic Authentication | NGINX Plus

nginxの設定は次のようにしました.

/etc/nginx/sites-enabled/catcam
server {
        listen 443;
        listen [::]:443;

        # auth
        auth_basic "Auth";
        auth_basic_user_file /etc/apache2/.htpasswd;

        server_name <your FQDN>;

        root /var/www/catcam;
        index index.html;

        location / {
                try_files $uri $uri/ =404;
        }
        location /stream {
                proxy_pass http://<ESP32 host>:81/stream;
        }
        location /status {
                proxy_pass http://<ESP32 host>/status;
        }
        location /control {
                proxy_pass http://<ESP32 host>/control;
        }

        ssl_certificate /etc/letsencrypt/live/<your FQDN>/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/<your FQDN>/privkey.pem; # managed by Certbot
}

server {
        listen 80;
        listen [::]:80;

        location / {
            return 301 https://$host$request_uri;
        }

        location /.well-known {
            allow all;
        }
}

ssl_certificatessl_certificate_keycertbotにより作成されたものです. (知らなかったのですが,最近のcertbotのインストールはsnapcraftというディストリビューション非依存のパッケージマネージャを使うようになったようですね.)

Lets’ encryptによるリクエストを受け付けるために,/.well-knownだけは許可し, それ以外のtcp/80へのアクセスはtcp/443へリダイレクトしています.

映像

このような感じで,外から猫さんの様子が見られるようになりました.

video

課題

Let’s encryptの証明書更新の自動実行

Let’s encryptの証明書は3ヶ月で切れるので,自動で更新するようにsysmtedにサービスとして登録したいです.


2022-05-02 追記

cerbotを実行したときにsystemdに登録されていたようで,特にやることはありませんでした.

$ systemctl list-timers | grep certbot
Tue 2022-05-03 06:56:00 JST  15h left   Mon 2022-05-02 13:39:31 JST  2h 8min ago snap.certbot.renew.timer     snap.certbot.renew.service

映像確認用の照明

部屋が暗いと映らないので,照明を設置して,カメラで使っていないESP32のGPIOポートを使ってコントロールできるようにしたいです.

chromeでアクセスすると431が出ることがある

chromeでアクセスすると431 Request Header Fields Too Largeになることがあります. nginxで不要なヘッダを落とすなどする必要があるかもしれません.


2022-05-02 追記

nginxのServerセクションに以下を追加し,いくつかヘッダをプロキシ先に渡さないことでとりえあず問題解消しました.

proxy_set_header Accept-Language "";
proxy_set_header Cookie "";
proxy_set_header sec-ch-ua "";
proxy_set_header sec-ch-ua-mobile "";
proxy_set_header sec-ch-ua-platform "";
proxy_set_header Sec-Fetch-Dest "";
proxy_set_header Sec-Fetch-Mode "";
proxy_set_header Sec-Fetch-Site "";
proxy_set_header User-Agent "";

ちょっと画角が狭い

ケージが2階建てなんですが,2階分を移すには画角が足りなかったので,1階用と2階用の2つカメラを用意したいです.

refs


ottijp
Satoshi SAKAO (@ottijp)

都内でアプリケーションエンジニアをしています

...