玄関の呼び鈴代わりの鉄腕アトムIPカメラ
うちの玄関にはIPカメラを設置している(図)。これは確か “B Series Astro Boy Model” と呼ばれていた,pan, tilt機能を持つもの。 “Astro Boy” (=鉄腕アトム)なのはその形状が鉄腕アトムの特徴的な頭部を想起させるからだろう。確かなところはわからないが,2012年なり2013年なりに購入したのだと思う。動画はMJPEG形式,画像はVGA解像度までなので,HDでの動画・写真撮影が当たり前の最近の機種からすると時代遅れと言われてもしかたがない。が,まだまだ使い続けたい。
1つの理由がアラーム端子を入力用,出力用の2セットとも備えていること。以前はそういうスペックのIPカメラは一定の割合であったように思うが最近見かけない。この鉄腕アトムカメラにはアラーム入力端子にボタンを繋いであるので,ボタンが押されると短絡された状態になり,アラームがトリガーされたものとIPカメラは認識する。それに対するアクションで住人に通知が行くようにして,そのボタンを呼び鈴代わりにしたかった。出力用アラーム端子には圧電ブザーを取り付けてあり,ボタンを押したらブザーが鳴るようにすることで,ボタンを押した人にはオーディオフィードバックがかかるようにした。
当時は父と二人暮らしだったのだが,本来の呼び鈴では1Fの狭いエリア内にいるときでないと聞こえず,2Fで寝起きや仕事をしてた私には聞こえなかったり,父も離れに行っていると聞こえず,2人とも在宅中なのにも関わらず配達を受け取りそこねるといったことがたびたび起こっていた。その問題を解消したかった。
アラーム通知機能を利用してボタンが押されたことを通知
さて,設定によりアラームがトリガーされたときいろいろなアクションを取らせることができる。FTPサーバに画像をアップロードする,指定アドレスに画像とともにメールを送る,MSNメッセンジャー(!!!)で通知するなど。
そのアラームがトリガーされたときに起こせるアクションの1つが,「アラームサーバ」に通知すること。といっても,要はHTTPで以下のリクエストが送られるだけ(マニュアル,そのスナップショット)。
GET /api/alarm.asp? username=username& userpwd=password& rea=alarm type (1=Motion Detection, 2 =Alarm from Alarm in port)& io=0
実際には途中の改行はない。また行末に ” HTTP/1.0″ がつく(1.1ですらない)。
メールでの通知は,写真を6枚添付してから送ってくることもあり,メールが届くまでに遅延があったため,送られてきたのに気づいたときにはもう来客・配達人は帰ってしまったりしていた。しかも,時間のかかる写真添付をやめさせるように設定するすべも用意されていない。そうなると,実際にアラームに気づくまでの時間を短縮するにはこのアラームサーバへの通知機能を利用するしかない。
その路線で以前も努力したときの経緯は “OpenWrt BB rc3上のuhttpdのCGIが思うように動作しない” にある。当時もOpenWrtをインストールしたルータTP-Link TL-WR703N(ただしブリッジとして運用)で通知を受けようとした。OpenWrtのWeb設定システムLuCIのためにWebサーバはいずれにせよ走っているのでそのCGIとして実現しようとし,実際うまくいった。ところが,OpenWrtのバージョンを上げるとそれがなぜかうまくいかなくなり,しょうがなくNcatで「なんちゃってアラームサーバ」を作り,それで対処していた。そのときはなぜCGIとして実現できなくなったのか理由はわからなかった。
再度挑戦してわかったこと
このほど,ドコモの固定回線置き換え用ホームルータHome 5G HR01を導入し,それを中心にホームLANを構成し直したこともあり,最新版OpenWrtをインストールしたNexx WT3020F(やはりブリッジ運用)で再挑戦することにした。
uHTTPdのCGIスクリプトとしてはalarm.aspは起動もされない
やはりOpenWrtで走るWebサーバuHTTPdのCGIとして実装しても(そのためには以下が必要),実際鉄腕アトムカメラに接続したボタンを押したときにはCGI alarm.asp は起動すらされない。起動されてすぐkillされているのではない。ルータ上でtcpdumpを動かしそれをPC上のWiresharkで眺めてみても,cURL コマンドの場合とIPカメラがアラーム通知をする場合とで差があるように思えないし,GETリクエストがWT3020Fに伝わっていることは確実なのに,だ。ブラウザから http://鉄腕アトムカメラのIPアドレス/api/alarm.asp?user=… を開いたり,cURLでそのGETのリクエストと等価なはずのcURLコマンド( “OpenWrt BB rc3上のuhttpdのCGIが思うように動作しない” 参照)をPC上で実行したときには普通に alarm.asp は起動されるのに。
/etc/config/uhttpdに以下を入れる(代わりにluci-app-uhttpdパッケージをインストールしてLuCI上で同様の設定をしても良い)。
list interpreter '.asp=/bin/ash'オプションcgi_prefixは/cgi-binと設定してあるのにも関わらず /api/alarm.asp に対応してくれる。
途中でkillされてしまうにしても一旦は起動されるのであればnohupと併用すればなんとかなるだろうがその手は使えない。起動もされていないということに気づく前はバッファリングが理由ではないかとstdbufコマンドやAwk内であればfflush()を使ってみたりもしたが,そこが理由ではないので当然うまくいかない。
“[OpenWrt Wiki] uHTTPd webserver” や “[OpenWrt Wiki] uHTTPd Web Server Configuration” を読んでもヒントは見当たらない。さんざん時間をかけて試行錯誤した結果わかったことは,鉄腕アトムカメラは,GETに対するレスポンス(”HTTP/1.0 200 OK”)までは受け取るが,どうやらその後ヘッダの残りやボディを受け取らず即ディスコネクトしてしまうようであること。ステータスコードを返すまではCGIの仕事ではない。uHTTPdが alarm.asp “CGI” を起動する前にクライアント(鉄腕アトムカメラ)がディスコネクトしてしまうので,それを検知してuHTTPdはCGI起動をしないのではないかと想像する。ちなみに,ステータスコードを返さないとCGIとしては “Bad Gateway” と表示する。
ListenモードのNcatによる「なんちゃってWebサーバ」( “OpenWrt BB rc3上のuhttpdのCGIが思うように動作しない” にあげたものと本質的には変わらない)の場合,そういったHTTPのセマンティクスには無頓着。なので少なくとも –sh-execで指定したスクリプトの起動は行われる。それで alarm.asp は起動できる。ただし,alarm.asp がデバッグやテストのためにCGIライクに振る舞えるためには,alarm.asp の起動以前にステータスコードは返されていなくてはならない。また,CGIならば起動時にセットされる環境変数(QUERY_STRING,REMOTE_ADDRなど)は,Ncatのスクリプトでセットしてやってからalarm.aspを起動する必要がある。
リクエストの処理はGawkのスクリプトでやるが,以下ではうまくいかない。そこでブロックしてしまう模様。
printf “HTTP/1.0 200 OK\r\nConnection: close\r\n” > “/dev/stdout” ;
なのに以下ではうまくいく。なぜ???今だにわからない…。
system ( “echo -ne \”HTTP/1.0 200 OK\r\nConnection: close\r\n\”” )
“CGI”スクリプトの alarm.asp で標準出力等に出力があると固まる
上の理由でuHTTPd Webサーバからalarm.aspをCGIスクリプトとして起動できないことはわかったが,Ncatによる「なんちゃってWebサーバ」ではもう問題がないかというと,別の問題があった。
テストのためにブラウザからアクセスしたり,PCからcURLコマンドでGETしてみたりしたときに,デバッグ目的に標準出力にいろいろ文字出力をしていた。ブラウザからアクセスした場合はそれがWebページとして表示されるのでわかりやすいと思ったからだ。しかし,実際に鉄腕アトムカメラからアラーム通知があったときにはその文字出力でブロックしてしまうようだった。実際にIPカメラのボタンを押したときにだけに起こる以下のエラーもヒントになった。
curl: (23) Failure writing output to destination
Ncat版「なんちゃってWebサーバ」はアクセスされたときスクリプトを起動するのだが,アクセスした側が入力をすかさず閉じてしまうので,スクリプト内で標準出力に出力しようとしてもブロックしてしまうのだろう。
もちろん標準出力への出力を一切止めてしまうというのも1つの手であったが,出力を全てバックグラウンド実行させることでしのぐことにした。鉄腕アトムカメラから実際にアラーム通知があった際には,このサブプロセスはブロックしてしまう。だが,最終的にスクリプトが終了する段階でこれらサブプロセスはいずれにせよ合わせて終了される。デバッグ目的にWebブラウザやcURLからアクセスする際には出力は成功する。
Bashスクリプトでは exec &>/dev/null
でそこから後の stdout, stderr への出力を /dev/null にリダイレクトできる。これはBashismのようでAshではどうせ使えない方法だったが,ファイルディスクリプタが閉じているかどうか判断して,標準出力が閉じているようなら/dev/nullにリダイレクトすることも考えた。試してみると,標準出力に実際出力すればブロックする状況でも,閉じてないと判断されてしまってこの手はいずれにせよ使えない。Sleepコマンドを使ってタイムアウトを実現するやり方も試したが(下),単純にバックグラウンド実行させてブロックするならブロックさせてほっておく方法の方が楽。
echo -ne "Content-Type: text/plain; charset=utf-8\r\n\r\n" & PID=${!}; sleep 1; kill ${PID} ; ESTAT=${?} ; echo "Kill exit status (1 for success before timeout): $ESTAT" >&2
Ncatの動作のために明示的なポート開けが必要
uHTTPdとNcatによる「なんちゃってWebサーバ」は並行して走る必要があるので,後者は80以外のポートを使う必要がある。そのためには,OpenWrtのBarrier Breakerでは必要なかった明示的なポート開けが必要だった。「なんちゃってWebサーバ」用にポート8081を選択。
“Network” → “Firewall” → “Traffic Rules” タブ でポート8081を開ける。
設定では Destination Zone として Device を選択するのがキー。
DNSの名前解決は問題
アラーム通知があったことをAndroid端末に伝えるための方法の1つとして,Automateのcloud messaging機能を利用するのだが,それが以下のようなエラーを時々吐いていた。cURLのタイムアウトに対する対策は講じていたのに。
curl: (6) Could not resolve host: llamalab.com
その後LAN内全般のDNS体制を見直したのでそれで解決することを期待したい。
やはりEmacsは快適
ルータ上でごくごく基本的な機能しかないZileで編集していたが,Windows上でCygwin版Emacsを起動し,Trampライブラリを使って遠隔編集。ああ,凄まじく快適。
その他
- “macos – Why does cURL return error “(23) Failed writing body”? – Stack Overflow” –no-buffer オプションをつけてみたがエラーは解消しない。
- “docker – Is there a way to decide what CURL should consider a success? – Stack Overflow“