ESP32カメラWebサーバで家の外から猫さんの様子を見られるようにする
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
構成
やったこと
- 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レコードを更新するサービスを登録しました.
[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'
[Unit]
Description=Run mydns.service every 30min
[Timer]
OnBootSec=10min
OnUnitActiveSec=30m
[Install]
WantedBy=timers.target
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
- 変更前のストリーム用のURL:
- 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の設定は次のようにしました.
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_certificate
とssl_certificate_key
はcertbotにより作成されたものです.
(知らなかったのですが,最近のcertbotのインストールはsnapcraftというディストリビューション非依存のパッケージマネージャを使うようになったようですね.)
Lets’ encryptによるリクエストを受け付けるために,/.well-known
だけは許可し,
それ以外のtcp/80へのアクセスはtcp/443へリダイレクトしています.
映像
このような感じで,外から猫さんの様子が見られるようになりました.
課題
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つカメラを用意したいです.