Arduino I/O をスマートフォン(iPod touch) から操作する

前回の記事では Arduino I/O を Web API で操作する方法について説明しました。今回はそのWeb API を利用して、スマートフォンから Arduino I/O を操作するWebアプリを紹介します。このアプリは、HTML(+CSS) と JavaScriptだけで作成していますので、対応する Webブラウザであれば様々な端末から操作できると思います。下記はこのWebアプリを iPod touch で動作させたときの表示画面です。

このWeb アプリでは、Arduino I/O ピンの出力をチェックボックスで ON/OFF に設定できます。画面下の CLEAR ボタンを押すとポート全体の出力を全て OFF にすることができます。 Webアプリを起動した時には、現在の Arduino のポートの出力値がチェックボックスに既に反映されています。Web アプリを複数の Web ブラウザから同時に操作した場合には、Reload ボタンを押して現在の I/O値を再ロードすることができます。

Web アプリで使用するファイルについて説明します。HTML ファイルと CSSファイル、JavaScript ファイルの3つのファイルで構成されています。Webブラウザ側にこれらのファイルを配信するために、DeviceServer に搭載されている HTTP サーバー機能を利用します。Web API を実行する DeviceServer のPC とHTTPサーバーを別のPC に分けて設置することもできます。”C:\Program Files\AllBlueSystem\WebRoot” フォルダがDeviceServer に搭載されている HTTP サーバーのデフォルトのルートディレクトリになります。そのフォルダの下に “arduino_io” フォルダを作成して以下の3つのファイルを配置します。これらのファイルはここからダウンロードすることができます。

HTML ファイル(index.html)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Arduino I/O コントロール</title>
        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
        <link rel="stylesheet" href="my.css" />
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
        </script>
        <script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js">
        </script>
    </head>
    <body>
        <div data-role="page" id="page1">
            <div data-theme="a" data-role="header">
				<a data-icon="refresh" id="reloadBtn">Reload</a>
                <h3>Arduino I/O</h3>
            </div>
            <div data-role="content" data-theme="a">
                <div class="ui-grid-a">
                    <div class="ui-block-a" data-theme="a">
                        <div data-role="fieldcontain">
                            <fieldset data-role="controlgroup" data-type="vertical">
                                <legend>
                                    PORT 0
                                </legend>
                                <input name="checkbox1" id="checkbox1" type="checkbox" class="pin" pin="2"/>
                                <label for="checkbox1">
                                    DIO#2
                                </label>
                                <input name="checkbox2" id="checkbox2" type="checkbox" class="pin" pin="3"/>
                                <label for="checkbox2">
                                    DIO#3
                                </label>
                                <input name="checkbox3" id="checkbox3" type="checkbox" class="pin" pin="4"/>
                                <label for="checkbox3">
                                    DIO#4
                                </label>
                                <input name="checkbox4" id="checkbox4" type="checkbox" class="pin" pin="5"/>
                                <label for="checkbox4">
                                    DIO#5
                                </label>
                                <input name="checkbox5" id="checkbox5" type="checkbox" class="pin" pin="6"/>
                                <label for="checkbox5">
                                    DIO#6
                                </label>
                                <input name="checkbox6" id="checkbox6" type="checkbox" class="pin" pin="7"/>
                                <label for="checkbox6">
                                    DIO#7
                                </label>
                            </fieldset>
                        </div>
                        <a data-role="button" class="port" port="0">
                            CLEAR
                        </a>
                    </div>
                    <div class="ui-block-b" data-theme="a">
                        <div data-role="fieldcontain">
                            <fieldset data-role="controlgroup" data-type="vertical">
                                <legend>
                                    PORT 1
                                </legend>
                                <input name="checkbox7" id="checkbox7" type="checkbox" class="pin" pin="8"/>
                                <label for="checkbox7">
                                    DIO#8
                                </label>
                                <input name="checkbox8" id="checkbox8" type="checkbox" class="pin" pin="9"/>
                                <label for="checkbox8">
                                    DIO#9
                                </label>
                                <input name="checkbox9" id="checkbox9" type="checkbox" class="pin" pin="10"/>
                                <label for="checkbox9">
                                    DIO#10
                                </label>
                                <input name="checkbox10" id="checkbox10" type="checkbox" class="pin" pin="11"/>
                                <label for="checkbox10">
                                    DIO#11
                                </label>
                                <input name="checkbox11" id="checkbox11" type="checkbox" class="pin" pin="12"/>
                                <label for="checkbox11">
                                    DIO#12
                                </label>
                                <input name="checkbox12" id="checkbox12" type="checkbox" class="pin" pin="13"/>
                                <label for="checkbox12">
                                    DIO#13
                                </label>
                            </fieldset>
                        </div>
                        <a data-role="button" class="port" port="1">
                            CLEAR
                        </a>
                    </div>
                </div>
            </div>
        </div>

		<div data-role="page" id="error_back_dialog">
			<div data-role="header" data-theme="e">
				<h3>script error</h3>
			</div>
			<div data-role="content" data-theme="e">
				<h3>エラーが発生しました</h3>
				<p>サーバー処理中にエラーが発生しました。現在のセッションが無効になっている場合があります。サーバー側にログが残されていますので確認して下さい</p>
				<p><a data-role="button" data-inline="true" data-icon="check" data-rel="back" data-theme="e">OK</a></p>
			</div><!-- /content -->
		</div>

		<script src="main.js"></script>

    </body>
</html>

今回のWeb アプリでは、GUI コンポーネントに jQuery-mobile を利用していますので <script> タグでこれらのライブラリを最初にロードしています。index.html ではメイン画面のコンポーネントとエラー発生時のダイアログの定義を行っています。メインの動作ロジックはJavaScript ファイル main.js に記述しています。

JavaScript ファイル(main.js)

// 	Webサーバーから、別のPC で実行中の DeviceServerにアクセスする場合の
//	URLホスト名とポート番号部分を設定する
//  DeviceServer 自身の HTTP サーバー機能を使用している場合には "" にする
//
//var server_host_url = "http://your_DeviceServer_public_url:80";
var server_host_url = "";

// DeviceServer セッション認証用トークン文字列
var session_token = "";

// DeviceServer の LogService にログメッセージを出力する
function log(msg,module){
	var url = server_host_url + "/command/log?message=";
	url = url + encodeURIComponent(msg);
	if (module != undefined){
		url = url + "&module=" + encodeURIComponent(module);
	}
	$.get(url,"",function(data){},"text");
}

// DeviceServer のスクリプトを実行する。
// 第2パラメータにスクリプトパラメータを指定する
// callback パラメータを省略するとリターンパラメータを受け取らない
function script_exec(name,params,callback){
	if (callback == undefined){
		var callback = "default_callback";
    	var url = 	server_host_url + "/command/json/script" +
					"?session=" + encodeURIComponent(session_token) +
					"&resultrecords=0" +
					"&name=" + encodeURIComponent(name);
	} else {
    	var url = 	server_host_url + "/command/json/script" +
					"?session=" + encodeURIComponent(session_token) +
					"&name=" + encodeURIComponent(name);
	}

	for(key in params){
		url = url + "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
	}

	$.ajax({"url" : url,
			"dataType" : "jsonp",
			"jsonpCallback" : callback
	});
}

// script_exec() でコールバックを指定しない場合のデフォルトコールバック関数
function default_callback(data){
	if (data.Result != "Success"){
		log("*ERROR* script error!");
		return;
	}
}

// スクリプト実行結果ステータスのみをチェック
function script_exec_callback(data){
	if (data.Result != "Success"){
		$.mobile.changePage( "#error_back_dialog", {transition: "pop",role:"dialog"});
	}
}

// スクリプト実行結果ステータスチェックとリロード
function script_exec_callback_reload(data){
	if (data.Result != "Success"){
		$.mobile.changePage( "#error_back_dialog", {transition: "pop",role:"dialog"});
	}
	load_arduino_io();
}

// UI コンポーネントの xml 属性値を検索取得
function getAttrVal(node,name){
	var val = "";
	var attr =  node.attributes;
	for (var i=0; i<attr.length; i++){
		if (attr[i].nodeName == name){
			val = attr[i].nodeValue;
		}
	}
	return val;
}

// ARDUINO_IO_GET スクリプト実行結果のイベントハンドラ。
function apply_ui(data){
	var pval;
	if (data.Result != "Success"){
		$.mobile.changePage( "#error_quit_dialog", {transition: "pop",role:"dialog"});
		return;
	}

	if (data.ResultParams.PORT_0 != ""){
		pval = parseInt(data.ResultParams.PORT_0,16);
		$("#checkbox1").attr("checked",((pval & (1 << 2)) != 0)).checkboxradio("refresh");
		$("#checkbox2").attr("checked",((pval & (1 << 3)) != 0)).checkboxradio("refresh");
		$("#checkbox3").attr("checked",((pval & (1 << 4)) != 0)).checkboxradio("refresh");
		$("#checkbox4").attr("checked",((pval & (1 << 5)) != 0)).checkboxradio("refresh");
		$("#checkbox5").attr("checked",((pval & (1 << 6)) != 0)).checkboxradio("refresh");
		$("#checkbox6").attr("checked",((pval & (1 << 7)) != 0)).checkboxradio("refresh");
	}

	if (data.ResultParams.PORT_1 != ""){
		pval = parseInt(data.ResultParams.PORT_1,16);
		$("#checkbox7").attr("checked",((pval & (1 << 0)) != 0)).checkboxradio("refresh");
		$("#checkbox8").attr("checked",((pval & (1 << 1)) != 0)).checkboxradio("refresh");
		$("#checkbox9").attr("checked",((pval & (1 << 2)) != 0)).checkboxradio("refresh");
		$("#checkbox10").attr("checked",((pval & (1 << 3)) != 0)).checkboxradio("refresh");
		$("#checkbox11").attr("checked",((pval & (1 << 4)) != 0)).checkboxradio("refresh");
		$("#checkbox12").attr("checked",((pval & (1 << 5)) != 0)).checkboxradio("refresh");
	}
}

// UI コンポーネントの初期値ロード
function load_arduino_io(){
	script_exec("ARDUINO_IO_GET",{},"apply_ui");
}

// メインページが表示された
$( '#page1' ).live( 'pageshow',function(event){
	// 秘密のセッショントークン文字列を強制的に設定
	session_token = "abc123";

	load_arduino_io();
});

// チェックボックスを操作してArduino ピンの値が更新された
$('input[class="pin"]' ).bind( "change", function(event, ui){
	var params = {};
	var attrVal = getAttrVal(this,"pin");
	if (attrVal != ""){
		params["pin"] = attrVal;
		if (this.checked){
			params["value"] = "1";
		} else {
			params["value"] = "0";
		}
		script_exec("ARDUINO_PIN_SET",params,"script_exec_callback");
	}
});

// ボタンを操作してArduino ポートの値がクリアされた
$('[class="port"]' ).bind( "click", function(event, ui){
	var params = {};
	var attrVal = getAttrVal(this,"port");
	if (attrVal != ""){
		params["value"] = "00";
		params["port"] = attrVal;
		script_exec("ARDUINO_IO_PUT",params,"script_exec_callback_reload");
	}
});

// リロードボタンが操作された
$( "#reloadBtn" ).bind( "click", function(event, ui){
	load_arduino_io();
});

この JavaScript ファイルで Arduino をリモートから操作する Web API をコールしています。index.html ファイルで表示したメイン画面のチェックボックスやボタンをユーザーが操作したときに、この main.js で定義されたイベントハンドラが実行されます。jQuery のセレクタ式で対象となる GUI コンポーネントを選択して各イベントを処理しています。ファイルの最初の部分で、 DeviceServer 側のLua スクリプトを実行するための Web API をコールするscript_exec() 関数を定義しています。この関数では JavaScript の連想配列形式で指定されたスクリプトパラメータを URL パラメータに変換して、Web API をコールしています。

また、サーバー側の認証を省略するために作成したセッショントークン文字列を session_token 変数に設定しています。

前回の記事中の動作確認画面で、Web ブラウザ中に表示された JSON 文字列を参照しながら main.js を見ていただけると、 Web API のパラメータとリターン文字列の処理内容が分かり易くなると思います。

CSS ファイル(my.css)

.text-align-center {
  text-align: center;
}
.text-align-right {
  text-align: right;
}

jQuery-mobile で利用する CSS ファイルです。

以上の3つのファイルを ”C:\Program Files\AllBlueSystem\WebRoot\arduino_io” フォルダに配置すると、Arduino を Web ブラウザから操作できる様になります。また、前回の記事で説明した Arduino 側で実行するスケッチと、Web API で実行するサーバー側の Lua スクリプトファイルは事前に設定しておいて下さい。

Web ブラウザで “http://localhost:8080/arduino_io/index.html” にアクセスすると、Webアプリのメイン画面が表示されて、現在の Arduino I/O の状態がチェックボックスに表示されます。チェックボックスを操作して、Arduino I/O を ON/OFF に設定できます。”localhost” 部分は、スマートフォン等からアクセスする場合には DeviceServer のIP アドレスやホスト名に置き換えて指定して下さい。

また、操作中に何らかの原因でエラーが発生した場合には下記の様なエラーダイアログが表示されます。

以下に、Web アプリを操作した時の動画を載せましたので参照してください。(音量注意)

ここで紹介した DeviceServer の機能は、ABS-9000 DeviceServer インストールキットをダウンロードして、直ぐに使用することができます。(デモライセンスが添付されていますので直ちに使用可能です)

それではまた。