Wireless・のおと

サイレックスの無線LAN 開発者が語る、無線技術についてや製品開発の秘話、技術者向け情報、新しく興味深い話題、サイレックスが提供するサービスや現状などの話題などを配信していきます。

前の記事:「SX-580 WiFi 電気スタンド製作記(2)」へ

SX-580 WiFi 電気スタンド製作記(3)

2014年9月26日 14:00
YS
前回は「hostapd と udhcpd を使って iPhone が接続できる AP を作る」ところまで進みました。今回は WEB サーバと CGI の実装です。


WEB サーバを動かす
SX-580 SDK の busybox はデフォルトで WEB サーバ機能が実装されています。httpd --help で簡単な使い方が出てきます。

# httpd --help

BusyBox v1.19.4 (2014-08-05 19:14:14 PDT) multi-call binary.


Usage: httpd [-ifv[v]] [-c CONFFILE] [-p [IP:]PORT] [-r REALM] [-h HOME]
or httpd -d/-e STRING

Listen for incoming HTTP requests

        -i              Inetd mode
        -f              Don't daemonize
        -v[v]           Verbose
        -p [IP:]PORT    Bind to IP:PORT (default *:80)
        -r REALM        Authentication Realm for Basic Authentication
        -h HOME         Home directory (default .)
        -c FILE         Configuration file (default {/etc,HOME}/httpd.conf)
        -e STRING       HTML encode STRING
        -d STRING       URL decode STRING

とりあえずホームディレクトリ(/root)に index.html を作成し、

<html>
<body>
This is SX-580 SDK HTTP test<br>
</body>
</html>

Shell から httpd を起動し、"ps" で httpd が動作していることを確認してみます。

# httpd
# ps
  PID USER       VSZ STAT COMMAND
    1 root      1204 S    init
    2 root         0 SW   [kthreadd]
    3 root         0 SW   [ksoftirqd/0]
    .
    .
    .
  316 root      2224 S    hostapd -B /etc/hostapd.conf
  322 root      1192 S    udhcpd /etc/udhcpd.conf
  325 root      1196 S    inetd /tmp/inetd.conf
  326 root      1200 S    -sh
  335 root      1188 S    httpd
  341 root      1192 R    ps

この状態で iPhone を SSID IMAPP-SDK に接続し、ブラウザで "192.168.99.1" を開けば先ほどの index.html が表示されます(デフォルトではやたら字が小さいので、拡大しなければ読みにくいかも知れません)。

iPhone-http1.jpg WEB サーバのテスト (表示)

以上のように、「読むだけ」の WEB サーバを作るのは呆気ないほど簡単です。しかし今回のテーマは「iPhone から WiFi で操作できる電気スタンド」ですので、WEB ブラウザからの操作を受け付けなければなりません。WEB ブラウザからの操作は「フォーム」と「CGI」という2つの要素によって構成されます。


フォームについて
フォームは HTML の文法で、<form method=METHOD action=URL> で始まり </form> で終わるブロックのなかに <input> や <select> <button> などの入力タグが並んだものです。<input> は type= 属性で text, password, radio, checkbox, file, submit, image, button などが指定されます。フォームタグの解説は巷にたくさん出ていますので、詳細には触れません。

form タグの method= 属性は get または post を指定します。名前が紛らわしいのですが「get が受信で post が送信」という訳ではなく、基本的な動作はどちらも同じです。form タグの action= 属性は URL を指定します。通常はここに CGI の URL が入ります。


HTTP の動作は常に「クライアント(ブラウザ)がサーバに要求を送る」→「サーバがクライアントに情報を返す」というシーケンスで行われます。「要求」は HTTP リクエストと呼ばれ、プロトコル上では

GET /index.html HTTP/1.1
Host: 192.168.99.100
User-Agent: Mozilla/5.0
Accept: text/html, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive

のような一連の文字列(行終端は CR+LF)として送信されます。1行目の最初のワードが「リクエスト」であり、HTTP 上では "OPTIONS" "GET" "HEAD" "POST" "PUT" "DELETE" "TRACE" "CONNECT" が定義されていますが、通常使われるのは "GET" と "POST" の2つだけです。GET も POST もプロトコル上の形式は殆ど同じで、異なるのは GET には "Content-Length:" ヘッダが無く空行(CR+LF)をもってリクエストの終端となるのに対し、POST には空行に続いて "Content-Length:" で指定された長さのデータが続くことです。

フォームで使われる場合、GET も POST もフォーム内の入力タグの情報が付加されたリクエストがサーバに送られます。例えば

<form method="xxx" action="/cgi-bin/test.cgi">
<input type="hidden" name="arg1" value="abcdefg">
<input type="hidden" name="arg2" value="this is test+">
<input type="submit">
</form>

のようなフォームで Submit ボタンを押すと、

arg1=abcdefg&arg2=this+is+test%2b

という文字列がサーバに渡されます。name=value&name=value&... の繰り返しで、value 部分の空白文字(' ')は '+' に置き換えられ、それ以外の「URL に含められない文字(※註)」は %xx のかたちで 16 進数表記されます。この規則を「URLencode」と呼んでいます。

(※註)オフィシャルには ", <, >, %, # および制御文字(0x00~0x1F, 0x7F)が「URL に含められない文字」と規定されています。0x80 以上を使う漢字コードも通常は %xx で置き換え、URL パラメータの区切り文字に使う '&', '+', '=' も %26, %2b, %3d で置き換えます。ブラウザによってはチルド文字 '~' を %7e で置き換えるものもあり、この辺の詳細については実装依存があります。

method=get の場合、文字列は URL の末尾に ? を挟んで付加された状態でサーバに渡されます。プロトコル上は下記のようになります。

GET /cgi-bin/test.cgi?arg1=abcdefg&arg2=this+is+test%2b HTTP/1.1
Host: 192.168.99.100
User-Agent: Mozilla/5.0
Accept: text/html, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive

method=post の場合、文字列は HTTP データとしてサーバに渡されます。プロトコル上は下記のようになります。

POST /cgi-bin/test.cgi HTTP/1.1
Host: 192.168.99.100
User-Agent: Mozilla/5.0
Accept: text/html, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-length: 33

arg1=abcdefg&arg2=this+is+test%2b


一般的に GET のほうが実装が(少々)容易ですが、URL 全長に制限がある(古いブラウザでは 256 文字、新しいものでは 1024 文字)のであまり多くのパラメータを渡せません。またパラメータが URL に「見えて」しまうので、ブラウザの URL 窓にごちゃごちゃ表示されて見た目が悪いという欠点もあります。
POST は GET より多くのパラメータを渡すことができ、URL の表示もすっきりしていますが、Content-Length を解析して受信データをパースする CGI の実装が少々面倒くさい欠点があります。


CGI について
CGI は Common Gateway Interface の略で、WEB サーバが外部プログラムを呼び出すための仕組みです。フォームで送信されるデータを受信解析し何らかのアクションを起こすためには、一般的に CGI が用いられます。
CGI の原理は単純で、「特定の条件を満たすリクエスト」に対して WEB サーバが外部プログラムを呼び出し、その実行結果を HTTP リプライとして返す、というものです。ただし、その「特定の条件」をどうやって設定するか、どこまで柔軟に設定できるかは WEB サーバの実装によってマチマチです。busybox httpd の場合は /cgi-bin で始まる URL パスを CGI として解釈します。

CGI は Unix 系 OS の実装に深く依存しており(※註)、CGI は WEB サーバから「子プロセス」として fork され、その標準入力には HTTP 受信データ(リクエストヘッダ終端の空行以降)が与えられ、標準出力は HTTP 送信データ(リプライヘッダを含む)として扱われます。WEB サーバから CGI プロセスへのパラメータは「環境変数」として渡され、これも詳細仕様は WEB サーバの実装によってマチマチなのですが、フォームデータの受信に関わる

REQUEST_METHOD     HTTP リクエストの種類。"GET" または "POST"。
QUERY_STRING       URL 最初の & 以降に付加された添付データ。
CONTENT_LENGTH     POST に付けられた添付データの長さ。
CONTENT_TYPE       POST に付けられた添付データのフォーマット。

という4つについては大抵の WEB サーバ実装で共通です。

(※註) 子プロセスとか環境変数という概念を持たないリアルタイム OS (RTOS) では、CGI の仕組みは全く異なります。各 RTOS ごと・各 HTTP サーバごとにバラバラと言ってもよく、ちっとも「Common」ではありません。

世間的に CGI は PerlPHP などのインタプリタ言語で実装される場合が多いですが、子プロセスとして実行可能な形式であれば何でも構いません。C 言語でバイナリを組んでも良いですし、Shell スクリプトでも実装可能です。

では試しに、簡単な CGI を Shell スクリプトで書いてみましょう。/root 下に mkdir で cgi-bin ディレクトリを作り、以下の test.cgi ファイルを作成します。chmod a+x で実行属性を与えておいてください。

#!/bin/sh
echo "HTTP/1.0 200 OK"
echo "Content-Type:text/plain"
echo
echo "This is SX-580 CGI test"
echo script_name = $SCRIPT_NAME
echo request_method = $REQUEST_METHOD
echo query_string = $QUERY_STRING
echo content_length = $CONTENT_LENGTH
echo content_type = $CONTENT_TYPE

この状態で http://192.168.99.1/cgi-bin/test.cgi にアクセスすると、次のような画面が表示されるはずです。

iPhone-cgi1.jpg CGI のテスト (表示)

ブラウザの URL を編集して http://192.168.99.1/cgi-bin/test.cgi?arg1=abcdefg としてアクセスすると、今度は「QUERY_STRING」に「arg1=abcdefg」が渡されているのが見えるはずです。

iPhone-cgi2.jpg CGI に引数を渡すテスト (表示)

このように CGI の原理は単純なのですが、実装となると色々なノウハウが必要になってきます。環境変数 CONTENT_LENGTH で渡されたバイト数のパラメータを標準入力から受信したり(POST の場合)、URLencode されたパラメータを通常の文字列に戻したうえで「名前=値」のリストに分解して格納したり(GET/POST 共通)するのは Shell スクリプトでは少々困難です。「世間的に CGI は Perl か PHP で実装される場合が多い」というのは、これらのインタプリタ言語が CGI 実装に充分な機能を備えており、また Linux (Perl) や Windows (PHP) では標準的に実装されているという理由によります。

SX-580 SDK に Perl は含まれていません。Perl を実装することも不可能ではありませんが、Perl のクロスコンパイルはそれだけで記事が一本書けそうなくらいの難物ですし、巨大で処理の重いスクリプト言語である Perl は ARM9 454MHz の SX-580 には少々荷が重いと思います。今回は Perl は使わず Shell スクリプトで頑張り、必要なときは C 言語を使う方針で進めます。


CGI から GPIO を操作する
さて本連載の目的は「iPhone から WiFi で操作できる電気スタンド」です。電球を点灯/消灯するためには AC 100V のスイッチを ON/OFF する必要があり、電磁石式のリレーを使うにせよサイリスタを使った SSR (Solid State Relay) を使うにせよ、何らかの I/O 端子で信号を操作しなければなりません。SX-580 には 11 本の GPIO (General Perpose I/O) が備わっているので、そのうち1本を使うことにします。

リレーユニットを制作する前に、Linux における GPIO の使い方を確認しておきましょう。SX-580 SDK のデベロッパーズガイドには sxgpio_drv.ko を使った GPIO 操作のサンプルが掲載されていますが、実は SX-580 の Linux カーネルには "Kernel mode GPIO driver" が実装されており、IOCTL を使用しなくても GPIO を操作することができます。
SX-580 SDK デベロッパーズガイド表 6-1 にあるように、SX-580 には 11 本の GPIO が備わっており、そのうち GPIO 6, 7, 8, 9, 10 の5本は基盤上の LED に接続されています。GPIO0_6 とか GPIO1_24 とあるのは GPIO のポート番号(0~3)とビット番号(0~31) を示しており、Linux 上では「ポート番号 * 32 + ビット番号」の通し番号として扱われます。例えば GPIO10 は GPIO1_24 であり、1*32+24=56 番となります。

Kernel mode GPIO driver は /sys/class/gpio 下の仮想ファイル群として実装されています。GPIO 番号を /sys/class/gpio/export に対して送ると /sys/class/gpio/gpioXX という仮想ディレクトリが作られ、その仮想ディレクトリ内のファイルに対する操作で GPIO ポートの操作が可能になります。

ジャンパピン JP21-3 を「GPIO10」側に差して

# echo 56 > /sys/class/gpio/export
# echo out > /sys/class/gpio/gpio56/direction

と操作すれば LED5(橙) が点灯するはずです。LED は負論理で接続されており、ポート出力状態 "0" で点灯するためです。仮想ファイル "value" に対して 0/1 を送ることで消灯/点灯が制御できます。

# echo 1 > /sys/class/gpio/gpio56/value
# echo 0 > /sys/class/gpio/gpio56/value

また value 値を読み出すことで現在の出力(direction=in の場合は入力)状態を知ることもできます。

# cat /sys/class/gpio/gpio56/value
0

GPIO 番号を /sys/class/gpio/unexport に送れば仮想ディレクトリが消滅します。ただし GPIO の入出力設定・出力状態などは再初期化されずにそのまま残ります。

# echo 56 > /sys/class/gpio/unexport

では早速、この仕組みを使って GPIO の現状表示・ON/OFF 操作ができる CGI を書いてみましょう。ファイル名は /root/cgi-bin/gpiotest.cgi とします。

#!/bin/sh
gpio_path=/sys/class/gpio/gpio56
if [ $QUERY_STRING = "SW=ON" ]; then
        echo 0 > $gpio_path/value
fi
if [ $QUERY_STRING = "SW=OFF" ]; then
        echo 1 > $gpio_path/value
fi
led_state=`cat $gpio_path/value`
echo "Content-type:text/html"
echo
echo "<html><head><title>SX-580-2700DM</title></head><body>"
echo "<div align="center">"
echo -n "Current state="
if [ $led_state = "1" ]; then
        echo "OFF<br>"
else
        echo "ON<br>"
fi
echo '<br>'
echo '<form method="get" action="/cgi-bin/gpiotest.cgi">'
echo '<input type="hidden" name="SW" value="ON">'
echo '<input type="submit" value="ON">'
echo '</form><br>'
echo '<form method="get" action="/cgi-bin/gpiotest.cgi">'
echo '<input type="hidden" name="SW" value="OFF">'
echo '<input type="submit" value="OFF">'
echo '</form>'
echo '</div></body></html>'

「Shell では処理が難しい」とした引数の解釈については「引数("SW")は1つしか無い」「SW は "ON" "OFF" どちらかの値しか取らない」という前提で、環境変数の直接比較  (if [ $QUERY_STRING = "SW=ON" ]; then) として実装しています。HTTP の仕様上は "?SW=ON" でも "?SW=%4F%4E" でも同じ動きをしなければならない事になりますが、完全な URLencode には対応しないことにします。

例によって chmod a+x gpiotest.cgi で実行属性を与えておき、iPhone のブラウザから http://192.168.99.1/cgi-bin/gpiotest.cgi を実行すると下記のような画面(拡大しないと無茶苦茶小さいですが)が表示され、「ON」「OFF」のボタン操作に応じて LED5 が点灯・消灯するはずです。

iPhone-cgi-gpio1.jpg GPIO を操作する CGI (表示)


次回はいよいよ半田付け工作でリレー基盤を製作します。また CGI の操作画面もグラフィックを使って「iPhone らしい」デザインに変更しようと思います。

次の記事:「SX-580 WiFi 電気スタンド製作記(4)」へ

最新の記事

カテゴリ

バックナンバー