UIOUSB (USB接続のI/O装置)をスマートフォン(iPod touch)から操作する

今回は、オールブルーシステムで提供している USB 経由で操作する I/O 装置(UIOUSB) を、スマートフォン等(iPod touch)から操作するWeb アプリケーションについて説明したいと思います。

DeviceServer をインストールすると、PC のWeb ブラウザ上から UIOUSB デバイスを GUI で操作するためのFlash アプリがインストールされます。(下記のキャプチャ画像参照)

今回は、この Flash アプリの デジタル I/O ポート操作部分を、Web アプリ(HTML + JavaScript) で作成した例を紹介します。スマートフォン等では Flash が動作しない場合が多いので、今回のように HTML5 Web アプリ(HTML + JavaScript) で作成することで、iPhone や iPad, Android など端末やOS の種類を問わずに操作できるようになります。Web アプリを起動すると、最初にログイン画面が表示されます。

ユーザー名とパスワードを入力して Login ボタンを押すとユーザー認証を行います。DeviceServer 側でユーザー認証に成功すると、I/O ポートの操作画面が表示されます。

チェックボックスを操作することで、UIOUSB デバイスの DIO#0 から DIO#7 までのデジタルポートをリアルタイムに ON/OFF に切り替えることができます。予めUIOUSBデバイスの I/O ポートは出力モードに設定しておく必要があります。タイトルバー上の “Logout”  ボタンを押すと確認画面の後、操作を終了してログアウトします。

ここから、今回のWeb アプリについて説明します。この記事で紹介した Web アプリのファイルはここからダウンロードできます。Web アプリは、HTML ファイルと JavaScript ファイル、jQuery 等のライブラリから構成されています。Web アプリの配信と Web API 機能は、DeviceServer 内蔵の HTTP サーバー機能を使用しています。DeviceServer で設定された HTTP サーバールートディレクトリ(“C:\Program Files\AllBlueSystem\WebRoot”) の下に “uiousb_io” ディレクトリを作成して、その中に今回の Web アプリのファイルを格納します。Web アプリのファイルを格納したディレクトリをエキスプローラで表示すると下記の様になっています。

DeviceServer の”サーバー設定プログラム” で HTTP サーバー機能と Web API 機能を有効に設定すると、このWebアプリが使用できるようになります。iPod touch 等から http://<DeviceServerの動作するPC ホスト名またはIP アドレス>/uiousb_io/index.html にアクセスすると Web アプリが起動されます。

Web アプリ中の index.html ファイルの内容は下記の様になっています。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>UIOUSB DIGITAL I/O</title>
        <link rel="stylesheet" href="./css/themes/default/jquery.mobile-1.2.0.css" />
        <link rel="stylesheet" href="my.css" />
        <script src="./js/jquery.js"></script>
        <script src="./js/jquery.mobile-1.2.0.js"></script>
        <script src="./device_server/webapi.js"></script>
    </head>
    <body>

        <div data-role="page" id="login" data-theme="a">
            <div data-theme="a" data-role="header" data-position="inline">
                <h3>ユーザー認証</h3>
            </div>

			<ul data-role="listview">
				<li data-role="fieldcontain">
                    <fieldset data-role="controlgroup">
  						<div><h1>&nbsp</h1></div>
                    </fieldset>
                    <fieldset data-role="controlgroup">
                        <label for="textinput1">Name</label>
                        <input id="login_name" placeholder="" value="" type="text" />
                    </fieldset>
                    <fieldset data-role="controlgroup">
                        <label for="textinput2">Password</label>
                        <input id="login_password" placeholder="" value="" type="password" />
                    </fieldset>
                    <fieldset data-role="controlgroup">
  						<div><h1>&nbsp</h1></div>
                    </fieldset>
                    <fieldset data-role="controlgroup">
						<div class="ui-grid-b">
							<div class="ui-block-a"></div>
							<div class="ui-block-b">
		                		<a data-role="button" data-inline="true" data-theme="a" data-icon="check" data-iconpos="left" id="login_btn" >Login</a>
						    </div>
							<div class="ui-block-c"></div>
						</div>
                    </fieldset>
				</li>
			</ul>

            <div data-theme="a" data-role="footer">
                <h3>ABS-9000 DeviceServer</h3>
            </div>
        </div>

        <div data-role="page" id="page1">
            <div data-theme="a" data-role="header">
				<a data-icon="home" id="logout_btn" href="#logout_caution" data-rel="dialog" data-transition="pop">Logout</a>
                <h3>UIOUSB 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">
                                <input name="checkbox0" id="checkbox0" type="checkbox" class="pin" pin="0"/>
                                <label for="checkbox0">
                                    DIO#0
                                </label>
                                <input name="checkbox1" id="checkbox1" type="checkbox" class="pin" pin="1"/>
                                <label for="checkbox1">
                                    DIO#1
                                </label>
                                <input name="checkbox2" id="checkbox2" type="checkbox" class="pin" pin="2"/>
                                <label for="checkbox2">
                                    DIO#2
                                </label>
                                <input name="checkbox3" id="checkbox3" type="checkbox" class="pin" pin="3"/>
                                <label for="checkbox3">
                                    DIO#3
                                </label>
                                <input name="checkbox4" id="checkbox4" type="checkbox" class="pin" pin="4"/>
                                <label for="checkbox4">
                                    DIO#4
                                </label>
                                <input name="checkbox5" id="checkbox5" type="checkbox" class="pin" pin="5"/>
                                <label for="checkbox5">
                                    DIO#5
                                </label>
                                <input name="checkbox6" id="checkbox6" type="checkbox" class="pin" pin="6"/>
                                <label for="checkbox6">
                                    DIO#6
                                </label>
                                <input name="checkbox7" id="checkbox7" type="checkbox" class="pin" pin="7"/>
                                <label for="checkbox7">
                                    DIO#7
                                </label>
                            </fieldset>
                        </div>
                    </div>
                </div>
            </div>
        </div>

		<div data-role="page" id="login_error_dialog">
			<div data-role="header" data-theme="e">
				<h3>*ERROR*</h3>
			</div>
			<div data-role="content" data-theme="e">
				<h2>ログインに失敗しました</h2>
				<p>ユーザー名またはパスワードが間違っています。システムのログイン制限により失敗している場合があります</p>
				<p><a href="#login" data-role="button" data-inline="true" data-icon="check" data-theme="c">戻る</a></p>
			</div>
		</div>

		<div data-role="page" id="logout_caution">
			<div data-role="header" data-theme="e">
				<h3>*WARNING*</h3>
			</div>
			<div data-role="content" data-theme="e">
				<h2>ログアウトしますか?</h2>
				<p>ログアウト操作を行う場合には "OK" を押してください。"キャンセル" で元の画面に戻ります</p>
				<p><a data-role="button" data-inline="true" data-icon="back" data-rel="back" data-theme="c">キャンセル</a>
				   <a data-role="button" data-inline="true" data-icon="check" id="logout_ok_btn" data-theme="c">OK</a></p>
			</div>
		</div>

		<div data-role="page" id="error_quit_dialog">
			<div data-role="header" data-theme="e">
				<h3>*ERROR*</h3>
			</div>
			<div data-role="content" data-theme="e">
				<h2>エラーが発生しました</h2>
				<p>サーバー処理中にエラーが発生しました。現在のセッションが無効になっている場合があります。再ログイン操作を行ってください</p>
				<p><a data-role="button" data-inline="true" data-icon="check" id="server_error_ok_btn" data-theme="c">OK</a></p>
			</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>

HTML ファイルでは、ログイン認証画面と I/Oポート操作画面、エラーや警告のダイアログ画面を定義しています。GUI は jQuery-mobile ライブラリを使用して作成しています。

Web アプリの動作ロジックが記述された main.js ファイルは下記の様になっています。

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

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

// 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;
}

// IO ポート値の配列を受け取ってチェックボックスの値に反映させる
// パラメータフォーマット {"DIGITAL_PORT":"xx"}
function apply_ui(ports){
	var pval;
	var	flag;

	if (ports.DIGITAL_PORT != ""){
		pval = parseInt(ports.DIGITAL_PORT,16);

		flag = ((pval & (1 << 0)) != 0);
		if ($("#checkbox0").checked != flag) $("#checkbox0").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 1)) != 0);
		if ($("#checkbox1").checked != flag) $("#checkbox1").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 2)) != 0);
		if ($("#checkbox2").checked != flag) $("#checkbox2").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 3)) != 0);
		if ($("#checkbox3").checked != flag) $("#checkbox3").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 4)) != 0);
		if ($("#checkbox4").checked != flag) $("#checkbox4").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 5)) != 0);
		if ($("#checkbox5").checked != flag) $("#checkbox5").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 6)) != 0);
		if ($("#checkbox6").checked != flag) $("#checkbox6").attr("checked",flag).checkboxradio("refresh");
		flag = ((pval & (1 << 7)) != 0);
		if ($("#checkbox7").checked != flag) $("#checkbox7").attr("checked",flag).checkboxradio("refresh");

	}

}

// UIOUSB_IO_GET スクリプト実行結果のイベントハンドラ。
function io_get_handler(data){
	if (data.Result != "Success"){
		if(data.ErrorText.match(/CertifyUpdateSession failed/i)) {
			$.mobile.changePage( "#error_quit_dialog", {transition: "pop",role:"dialog"});
		} else {
			$.mobile.changePage( "#error_back_dialog", {transition: "pop",role:"dialog"});
		}
		return;
	}
	apply_ui(data.ResultParams);
}

// UI コンポーネントの初期値ロード
function load_uiousb_dio(){
	var params = {};
	script_exec("UIOUSB_IO_GET",params,"io_get_handler");
}

// メインページが表示された
$( '#page1' ).live( 'pageshow',function(event){
	load_uiousb_dio();
});

// チェックボックスを操作してUIOUSB DIOの値が更新された
$('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("UIOUSB_PIN_SET",params,"script_exec_callback");
	}
});

////////////////////////////////////////////////////////////////

// サーバー側でログイン操作が成功したらメイン画面に移動する
function login_callback(data){
	if (data.Result == "Success"){
		session_token = data.SessionToken;
		// dump_login_result(data);
		$.mobile.changePage( "#page1", { transition: "slide"});
	} else {
		$.mobile.changePage( "#login_error_dialog", {transition: "pop",role:"dialog"});
	}
}

// サーバー側でログアウト操作が完了したらログイン画面に戻る
function logout_callback(data){
	session_token = "";
	$("#login_password").val("");
	$.mobile.changePage( "#login", { transition: "pop"});
}

// ログインボタンを押した
$( "#login_btn" ).bind( "click", function(event, ui){
	var user = $("#login_name").val();
	var pass = $("#login_password").val();
	login(user,pass,"login_callback");
});

// ログアウトボタンを押した
$( "#logout_ok_btn" ).bind( "click", function(event, ui){
	logout("logout_callback");
});

// サーバーエラーのダイアログから復帰する場合はログイン画面に戻る
$( "#server_error_ok_btn" ).bind( "click", function(event, ui){
	session_token = "";
	$.mobile.changePage( "#login", { transition: "pop"});
});

// ログインページが表示された
$( '#login' ).live( 'pageshow',function(event){
	// セッショントークンが指定されている場合にはユーザー認証を省略する
	if (session_token != ""){
		$.mobile.changePage( "#page1", { transition: "slide"});
	}
});

main.js (JavaScript) ファイルでは、ログインボタンやDIO#xx チェックボックスを操作した時に実行されるイベントハンドラを定義しています。これらのイベントハンドラでは、DeviceServer 側に配置された Lua スクリプトファイルを Web API 経由で実行しています。Web API 経由で実行する Lua スクリプトは以下の2種類あります。

UIOUSB_IO_GET.lua スクリプトファイル

file_id = "UIOUSB_IO_GET"
log_msg("start..",file_id)

--[[
**************************************************************************************

UIOUSB デバイスの I/O 値を取得する

リターン時に返されるパラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
DIGITAL_PORT	UIOUSB DIO の現在の値(16進数2桁)						"FC"

**************************************************************************************
]]

local stat,ret = uio_command("di")
if stat then
	if not script_result(g_taskid,"DIGITAL_PORT",string.sub(ret,3,5)) then error() end
else
	error()
end

このスクリプトは、Web アプリの I/Oポート操作画面が表示されたときに実行されて、現在の I/O ポート値を UIOUSB デバイスから取得します。

UIOUSB_PIN_SET.lua スクリプトファイル

file_id = "UIOUSB_PIN_SET"
log_msg("start..",file_id)

--[[
**************************************************************************************

UIOUSB デバイスの指定したI/O ピンの値を設定する

スクリプト起動時に渡されるパラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
pin			UIOUSB DIO#番号(0 から 7 までの整数)						"7"

value		pin に指定したI/Oポートに設定する値(0 または 1)				"1"

**************************************************************************************
]]

-------------------------
-- パラメータチェック
-------------------------
if not (g_params["pin"] and g_params["value"]) then
	log_msg("parameter error",file_id)
	error()
end

if (g_params["value"] == "1") then
	if not uio_don(tonumber(g_params["pin"]),true) then error() end
else
	if not uio_don(tonumber(g_params["pin"]),false) then error() end
end

このスクリプトは、Web アプリの DIO#xx のチェックボックスが操作されたときに実行されて、UIOUSB デバイスのI/O ポート値をビット毎に更新します。スクリプトパラメータとしてDIO ポートのビット番号と設定値を指定します。

ここで説明したファイル以外にも、DeviceServer の Web API を実行するためのライブラリ関数やログメッセージ出力関数などがライブラリファイル(webapi.js) として Web アプリのファイルに格納されています。これらのファイルは、ダウンロードして内容を確認して下さい。webapi.js ファイル中で定義されたグローバル変数 ”session_token” に、予め DeviceServer で作成済みのセッショントークン文字列を代入しておくことで、ログイン認証画面をスキップして直接 I/O 操作画面を表示させることもできます。詳しくは webapi.js ファイル中のコメントを参照して下さい。

下記に、Web アプリを動作させた時の動画を添付しますので参考にしてください(音量注意)

この記事で説明した Web アプリは、オールブルーシステムの “ホームセンサーキット”または、”UIOUSB キット” 製品が動作している環境で直ぐに使用することができます。

DeviceServer のセンサーネットワークで管理している全てのデバイスは、今回の記事と同様に、簡単に Web アプリケーションとしてユーザーが作成したGUI から操作することができます。スマートフォンやタブレット端末毎の専用開発環境の準備やアプリの登録審査などは一切不要で、手軽にユーザーが作成することができます。シリアルポートで接続した計測器やI/O 装置、Arduino デバイス、データベースにデータを格納するアクイジション装置などを操作する Web アプリを作成して、外出先からでもスマートフォンやタブレットで簡単に操作できるようになります。

それではまた。