概要
ここで製作したESP32-camの続きです。 外部からのアクセスに対しTailscaleは非常に便利なのですがESP32-CAMにTailscaleは使えません。 そこでTailscaleに登録出来るサーバを通して外部よりカメラにアクセスしようと考えています。 さらにそのサーバがカメラを複数台管理出来ればカメラの数に関係無くサーバ1台のみのTailscaleへ 登録で全てのカメラにアクセス出来ます。今回はこの要求に対応したESP32-CAMサーバの製作です。
ESP32-CAMとカメラ管理サーバとTailscale
現状のESP32-CAMサーバ単体、管理サーバを通した場合、最後にTailscaleを使用した場合の通信状態の理解。
現状のESP32-CAM
- クライアントがカメラサーバ(例192.168.x.x)にアクセスし、操作HPを取得
- 操作HPがカメラサーバとデータのやり取りを行いカメラを操作
カメラ管理サーバを通した場合
- ESP32-CAMを複数台製作(例:192.168.a,b,c...)
- 同じLAN内に管理用サーバ(例192.168.x.x)を上げる。管理サーバは
- ESP32-CAMを登録する機能(画面左の小窓に登録)
- 選んだカメラを表示する機能(右の大きい窓に表示)
- "<iframe>"を使って管理サーバの中にカメラサーバをそのまま取り込む。
- 管理サーバ側のHPで例えば< iframe.src = "http://192.168.x.a"><iframe>と指定しカメラサーバのHTMLを読み込む。
- これにより管理サーバの中でカメラHPが独自に動く。
この状態で管理サーバにTailscaleを使用すれば
- ESP32-CAMの数に関係無くTailscaleに登録するのは管理サーバ1つのみ
- Tailscaleに登録出来ないESP32-CAMを外部からアクセス出来る
理想的と思ったのですがそうでは無かったです。
この状態でカメラ管理サーバにTailscaleを使用した場合
サーバにTailscaleを適応したら "<iframe>"部分が真っ黒になってしまいました。
これは
- Tailscaleにより管理サーバは仮想環境のIP(例:100.x.x.x)になる。
- ESP32−CAMサーバにTailscaleを適応していないのでカメラHPは自分のサーバ(例192.168.x.a,b,c)にアクセスしようとする
- 仮想環境にESP32−CAMのIP(例192.168.x.a,b,c)は無く、データが取れず画像を表示出来ない。(真っ暗になる)。
ちょっとがっかり。でも対策が有りました。
中継サーバを使う
Tailscale下でもサーバ同士ならデータのやり取りが出来る事が分かりました。 Tailscale下となった管理サーバは仮想環境IP(例:100.x.x.x)とローカルLAN内IP(例192.168.x.x)で機能するようです。
- "<iframe>"内のカメラHPがカメラサーバにアクセスする時に一度管理サーバを通す。
- これはカメラHPのカメラサーバアクセスURLの先頭に目印(例:/xyz/)をつければ可能
- リクエストを受けた管理サーバは目印を取って実際のカメラサーバにアクセスしデータを取得。
- 取得したデータをそのまま"<iframe>"内のカメラHPに返す。
- "<iframe>"内のカメラHPがそのデータを表示する。
これでTailscale下でカメラの表示が出来る様になったのですが新たな問題が。
カメラ側HPは2つ必要?
元々このカメラサーバは中継サーバを通さずに直接アクセス出来ていました。 当然ですが先頭に目印をつければそれが邪魔をして直接アクセスは出来ません。 直接、サーバ両方のアクセスに対応するにはカメラHPに呼び出された時に 直接かサーバーからかを判断する機能が必要です。実はこれ、"window.location" を使えば 判断出来る事が分かりました。
両方のアクセス(直接、サーバ)に対応するカメラ側HPの製作
カメラ側HPの冒頭で下記を実行しアクセス先を判断します。直接アクセスする場合は、IPアドレスで(例:"192.168.x.a") サーバからアクセスする時には"/proxy/?targetUrl=192.168.x.a"とアクセスすれば下記の箇所で両者を判別出来ます。
<script>
let timerId = null;
let para, sv_para;
window.proxyBaseUrl = "";
if(window.location.search.includes("targetUrl") || window.location.pathname.includes("/proxy/")){
const urlParams = new URLSearchParams(window.location.search);
targetUrl = urlParams.get("targetUrl");
if (targetUrl) {
try {
const parsedUrl = new URL(targetUrl);
window.proxyBaseUrl = "http://" + parsedUrl.hostname;
} catch (e) {
console.log("URLの解析に失敗しました: ", e);
}
}
}
- 145行:グローバル変数 window.proxyBaseUrl = "";を宣言
- 147行:window.locationに"targetUrl"または"/proxy/"の有無を判別
- 有る場合:サーバからのアクセス。
- window.proxyBaseUrl にカメラIPを保存("http://" + parsedUrl.hostname;)
- ない場合:直接アクセス。
- 何もしない。window.proxyBaseUrl は空。
- 有る場合:サーバからのアクセス。
- グローバル変数 window.proxyBaseUrlには直接アクセスなら空。サーバからのアクセスなら接続先のカメラIPが保存されます。
function getProxyUrl(originalPath) {
if (window.proxyBaseUrl) {
let decodedBase = decodeURIComponent(window.proxyBaseUrl);
let cameraIp = decodedBase.replace("/proxy/?targetUrl=", "").replace(/\/+$/, "");
let finalTarget = cameraIp + originalPath;
return "/proxy/?targetUrl=" + encodeURIComponent(finalTarget);
} else {
return originalPath;
}
}
document.getElementById("10").addEventListener('change', function (event) {
let targetElement = event.target || event.srcElement;
let val = "1," + targetElement.value;
let originalPath = '/?2=' + encodeURIComponent(val);
let finalUrl = getProxyUrl(originalPath);
// サーバに解像度変更コマンドを送信してリロード
let xhr = new XMLHttpRequest();
xhr.open('GET', finalUrl);
xhr.onload = function() {
if (xhr.status === 200) {
console.log("解像度が変更されました");
item_setup();
カメラHPがカメラサーバにアクセスする時に
- 189行:これがカメラHPがカメラサーバにアクセスする為のURL
- 190行:アクセスする前に関数getProxyUrl()でURLを変換する
- 174行:getProxyUrl(originalPath)関数
- window.proxyBaseUrl
- 空:直接アクセスー>変更無し
- 有:サーバアクセスー>頭にwindow.proxyBaseUrlを追加
- window.proxyBaseUrl
- 変換後のURLで直接、サーバからのアクセスに関わらず正しくカメラにアクセス出来る。
追加のESP32-CAM
手持ちに”WROVER_KIT”、”ESP32S3_EYE”が有ったのでそれらもStreamingカメラとして追加しました。
今回追加した2機種はボードの大きさがAI_THINKERより大きく、2軸のステージに乗せるのを諦めて固定型にしました。 スマホフォルダーを使って下記に様にしています。首振り機能は無いですが魚眼レンズを使用すれば影範囲は広がります。
各機種の回路は下記の通り。左が2軸ステージに載せた場合。右が固定の場合の回路です。
実行する前に
今回使用したコードをここに保存しました。 実行する前に下記の準備を行って下さい。
- "wifi_config.txt" の修正
- このファイルをエディターで開くと下記の様になっています。
- 6個のパラメタをカンマ "," で区切った文字列。各パラメタに下記の値をセットします。
パラメタ 説明 SSID ルータのSSID PASSWORD ルータのパスワード aaa Gatewayアドレス3番めの値 例:192.168.aaa.bbb bbb Gatewayアドレス4番めの値 例:192.168.aaa.bbb xxx カメラアドレス4番めの値 例:192.168.aaa.xxx mail_addr メールの送り先アドレス - Gatewayアドレスはルータの機種で違いますが、192.168.aaa.bbbでカバーできるはずです。
- カメラのハンドリングを考えてIPアドレスを固定します。IPアドレスは "192.168.aaa.xxx" になります。
- プログラムで改行を目印にこの文字列を読み込んでいます。必ず改行を入れてファイルを保存して下さい。
SSID,PASSWORD,aaa,bbb,xxx,mail_addr
- "esp32_net_camera.ino"の修正
- 72行:#define AUTHOR_EMAIL "xxxxxxxx@gmail.com" //Gmail 貴方のメールアドレス
- ここに自分のGメールアドレスを指定
- 73行:#define AUTHOR_PASSWORD "yyyyyyyyyyyyyyyy" //16桁のアプリパスワード
- ここにGmailで取得した16桁のアプリパスワードを指定
- 76行:String recipient_email = "zzzzzzzzzzzzz";
- ここにメールの送信先アドレスを指定して下さい。
- 221行:WiFi.softAP("ESP32CAM-SETUP", "abcdefg"); // SSID & Passowrd
- ESP32-CAMがAPとして立ち上がる時のSSIDとPassowrdを指定します
- SSID:"ESP32CAM-SETUP" と設定していますが、必要に応じて変更下さい。
- Passowrd:"abcdefg" と設定していますが、必要に応じて変更下さい。
esp32_net_camera.ino arduino// --- Gmail設定 --- #define SMTP_HOST "smtp.gmail.com" #define SMTP_PORT 465 /* The sign in credentials */ #define AUTHOR_EMAIL "xxxxxxxx@gmail.com" #define AUTHOR_PASSWORD "yyyyyyyyyyyyyyyy" /* Recipient's email*/ String recipient_email = "zzzzzzzzzzzzz";esp32_net_camera.ino arduinoif(digitalRead(mode_pin)){ Serial.println("Starting in AP Mode..."); WiFi.softAP("ESP32CAM-SETUP", "abcdefg"); // SSID & Passowrd Serial.print("AP IP Address: "); Serial.println(WiFi.softAPIP()); httpd_handle_t wifi_server = NULL; - 72行:#define AUTHOR_EMAIL "xxxxxxxx@gmail.com" //Gmail 貴方のメールアドレス
- カメラ、HPをコード "esp32-net-camera.ino" の冒頭で指定します。
- 13(AI_THINKER), 14(WROVER_KIT), 15(ESP32S3_EYE)行でボードを選択
- 19(2軸用), 20(固定用)行でHPの種類を選択します
- 固定用のHPは2軸用からカメラ移動用の十字キーを除いています。
esp32-net-camera.ino arduino//#define CAMERA_MODEL_AI_THINKER // Has PSRAM //#define CAMERA_MODEL_WROVER_KIT // Has PSRAM #define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM #include "pin_define.h" //#define index_2ax #define index_fix #if defined(index_2ax) #define main_html "/index_2ax.html" #elif defined(index_fix) #define main_html "/index_fix.html" #endif
最後に
直接、サーバからアクセス対応のESP32-CAMサーバが出来ました。 これに対応したサーバはここで説明しています。 このサーバとの組み合わせならTailscaleに対応します。ESP32-CAMに外部からアクセス出来ます。