25. リモートコンソールの作成

 何やら残す課題が大きくてプレッシャーを感じています。しかしここまで来たのですから、もう一番、暑さを跳ね返して頑張ることにしましょう。
 今回から2回にわたって、HAT21のリモート制御に取り組みます。目標は、スマートフォンやパソコンからHAT21の音量調整やイコライザー感度調整を簡単にできるようにすることです。まずは、どのような仕組みで制御するかを検討して、制御する側のソフト「リモートコンソール」を作成します。



1.リモート制御の仕組み

①前提要件

 リモートコンソールの部分は、できるだけ簡単に開発できて機種を選ばないことを最優先することにします。また、HAT21はすでに完成していることから、リモートコンソール連携にともなう追加や変更を、いかに局所的なものにとどめるかが課題になります。


②基本方針

 手っ取り早く開発できて比較的使用機種を選ばないということになると、コンソール側はWebアプリケーションにするのがもっとも現実的でしょう。スマートフォンなどから、WiFi経由でHAT21にコンタクトしてコンソールを操作します。ブラウザーの種類やバージョンによる違いはありますが、できるだけ特殊な使い方は避けるようにします。ただし、HAT21本体側はひと工夫が必要になります。
 HAT21とのインターフェイスは、ドキュメントルート(/var/www/htmlフォルダー)に共用データファイル'comdata'を設置して、データ交換させることにします。'comdata'の最初の作成(書き込み)はHAT21側で行い、コンソール側はその情報にしたがって(設定済みの諸条件を反映して)コンソール画面を作成します。
 その後は、コンソール画面で音量などを変更すると、その都度、変更の状態を共用データファイルに書き込みます。HAT21はcomdataの変更の発生を監視していて、変更が発生すればその内容を取り込んで動作に(音量などに)反映させます。既存のHAT21のロジックにはできるだけ手を加えることなく、「変更発生の監視・変更内容の把握・既存ロジックへのコンタクト」に絞って開発します。
 なお、コンソールの機能は次のように必要最小なものに限定します。
   ・フィルター種別(モード)の選択
   ・イコライザーモード選択時の周波数帯域別感度調整
   ・音量調整
   ・ノイズカットの指定
   ・設定状態でHAT21に基本情報ファイルのアップデートを指示
   ・HAT21の終結処理と電源オフを指示


③アプリケションの構成

 Webアプリケーションなので、構成プログラム類はすべてドキュメントルートに配置します。第3項以降で個別プログラムについて解説しますが、これらは次のように配置されています。
   /var/www/html  (ドキュメントルート)
     +--- /js    (JavaScriptフォルダー)
     |   +--- hat21sub.js  JavaScript関数群ファイル
     +--- hat21.html    リモートコンソール
     +--- hiddenpage.html  共用データ更新処理
     +--- finalpage.html  終了画面


2.必要な環境と利用技術

 このプロジェクトでは初めてのWebアプリケーション開発です。第5章「OS(Linux)のインストール」にしたがっていれば新たなインストールは不要ですが、念のためにPHPとApach2の状態を確認しておきましょう。
①PHPとApacheのバージョン確認

 次のようにPHPとApache2のバージョンを確認します。細かい情報は別にして、PHP 7とApache2がインストールされていればOKです。インストールされてなければ、第5章を参考にしてインストールしてください。
$ php -v PHP 7.3.14-1~deb10u1 (cli) (built: Feb 16 2020 15:07:23) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.14, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.3.14-1~deb10u1, Copyright (c) 1999-2018, by Zend Technologies $ apache2 -v Server version: Apache/2.4.38 (Raspbian) Server built: 2019-10-15T19:53:42


②Apach2とPHPの動作確認

 ブラウザーのアドレスバーにRaspberry Pi Zeroのアドレスを入力してアクセスします。次のような画面が表示されたらApache2が正常に動作していることがわかります。

 PHPが正しく動作しているか確認するために、ドキュメントルート(/var/www/html)にphpinfo.phpファイルを作成します。
$ sudo nano /var/www/html/phpinfo.php
 そして次の内容を記述します。
<?php phpinfo(); ?>

 ブラウザーのアドレスバーに「http://192.168.0.38/phpinfo.php」と入力してアクセスします(192.168.~の部分はRaspberry Pi ZeroのIPアドレスを入力)。以下のようにPHPに関する情報が表示されれば、apache2とphp7が連係して動作していることが確認できます。これが確認できれば次の③の作業は不要です。


③Apach2とPHP7の連携
 PHPの関連情報が表示されない場合は、以下のようにして連携をとります。

 パッケージ管理コマンドdpkgを使って、Apacheとマッチするパッケージ一覧を表示させます。
$ sudo dpkg -l | grep apache ii apache2 2.4.38-3+deb10u3 armhf Apache HTTP Server ii apache2-bin 2.4.38-3+deb10u3 armhf Apache HTTP Server (modules and other binary files) ii apache2-data 2.4.38-3+deb10u3 all Apache HTTP Server (common files) ii apache2-utils 2.4.38-3+deb10u3 armhf Apache HTTP Server (utility programs for web servers)
 libapache2-mod-php~のようなユーティリティーが含まれていないので、PHPとの連携がとれていないことがわかります。そこで、まずCGIを有効にしてApache2を再起動します。
$ sudo a2enmod cgid Module cgid already enabled
 次に、Apacheの起動設定ファイルに問題が無いかチェックします。
$ apache2ctl configtest AH00526: Syntax error on line 14 of /etc/apache2/sites-enabled/000-default.conf: Illegal override option # Action 'configtest' failed. The Apache error log may have more information.
 手元のPiでは、000-default.confに問題があることがわかります。次のようにnanoで内容を表示させます。
$ sudo nano /etc/apache2/sites-enabled/000-default.conf
 以下はnanoで表示された内容の一部です。<Directory></Directory>で囲まれた部分がコメントになっていれば外して書き戻してください。
<VirtualHost *:80> # The ServerName directive sets the request scheme, hostname and port that # the server uses to identify itself. This is used when creating # redirection URLs. In the context of virtual hosts, the ServerName # specifies what hostname must appear in the request's Host: header to # match this virtual host. For the default virtual host (this file) this # value is not decisive as it is used as a last resort host regardless. # However, you must set it for any further virtual host explicitly. #ServerName www.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www/html <Directory "/var/www/html"> Options FollowSymLinks AllowOverride All Options +ExecCGI     # <=== CGIスクリプトの実行を許可する Require all granted </Directory> : : (以下省略)

 再度dpkgを実行して、以下のように「libapache2-mod-php7.x」が含まれていればOKです。
$ sudo dpkg -l | grep apache ii apache2 2.4.38-3+deb10u3 armhf Apache HTTP Server ii apache2-bin 2.4.38-3+deb10u3 armhf Apache HTTP Server (modules and other binary files) ii apache2-data 2.4.38-3+deb10u3 all Apache HTTP Server (common files) ii apache2-utils 2.4.38-3+deb10u3 armhf Apache HTTP Server (utility programs for web servers) ii libapache2-mod-php7.3 7.3.27-1~deb10u1 armhf server-side, HTML-embedded scripting language (Apache 2 module)

 もしこれでもダメなようなら、ドキュメントルート(/var/www/html/)に、次の記述をした'.htaccess'ファイルを設置してください。
AddType application/x-httpd-cgi .php

 で、それでもダメな場合は、現在のPHPをアンインストールして最新版をインストールしてみましょう(こうなると、もう力尽くですね)。手順は以下のとおりです。
○PHPをアンインストールする(バージョンが php7.3.~として)
$ sudo apt-get purge php7.3 $ sudo apt-get purge php7.3-common
○PHPの最新版をインストールします(版数はインストーラー任せで)
$ sudo apt-get update $ sudo apt-get install php
 これで、再度「http://192.168.0.38/phpinfo.php」でapache2とphp7が連係動作することを確認します。


④利用技術

 Webアプリケーションの開発には次の技術を利用します。いずれも基礎的なレベルだけですから、ソースコードを一読すれば理解できると思います。ただしエイヤッと書き流したので、コードの読み難い点はご勘弁ください。
   ・HTML5
   ・CSS
   ・PHP7
   ・JavaScript


⑤テスト環境

 今回と次回のリモートコンソール側のテストは、Windows10 PCおよびAndroidスマートフォン上のGoogle Chrome(version 91.0.4472)で行っています。


3.画面のデザインとイベント

 リモートコンソール画面は必要最小限のパーツ(オブジェクト)で構成しています。上部の2段はタイトルですが、その下の〔 〕で囲まれた部分には共用データを表示しています。これはテスト用で、完成時には削除します。下図はイコライザーがイネーブルとディセーブルの状態です。


①フィルター種別選択

 四角の枠線で囲んだ部分は「フィルター種別の選択」で、5つのラジオボタンを配置します。どれか一つを選ぶことができて、イコライザー(Equalizer)を選択すると中央部の感度調整を使用可能にし、それ以外を選択すると使用不可にします。
 ラジオボタンをクリックすると、onChangeイベントでモードの変更を認識させます。


②イコライザー感度調整

 左右にそれぞれに、周波数帯域(バンド)ごとに感度を調整できる9段のスライドバーを配置します。各スライドバーは0~9までの10レベルの値を調整でき、バーの移動に伴うonChangeイベントにより、変更されたスライドバーを特定して変更値を取得します。


③音量調整

 下部の左に少し大きめのスライドバーを設置します。イコライザーの感度調整と同様に0~9までの10レベルを調整でき、onChangeイベントで変更後の値を取り込みます。


④ノイズカット

 低レベルの音声信号(ノイズ)をカットするかどうかを指定するチェックボックスです。チェックのON/OFFをonChangeイベントで取り込みます。


⑤更新指示

 [Update]ボタンを設置し、クリックするとonClickイベントの発生を受けて動作します。現在のコンソールの状態を、HAT21の基本情報に書き出すようHAT21に指示します。


⑥電源オフ

 [Power Off]ボタンをクリックすると確認ダイアログボックスを表示します。[OK]ボタンを押すとHAT21に終結処理をして電源をオフにするよう指示します。

 電源オフを実行すると、コンソールに「終了画面」を表示します。


 以下にHTML5で記述したコンソールのコードを掲げます。HTML5の部分はきわめて少なくタイトル表示部分だけです。コンソール画面の大半はPHPで作成しています。共用データファイルとデータの詳細については、次項で説明します。

<!DOCTYPE HTML>
<html lang="ja">
<head>
<title>HAT21 Console</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  html { font-family: Helvetica; display: inline-block; margin: 0px auto;text-align: center; line-height:18px;} 
  h1 {font-size:24px; margin-top: 10; margin-bottom: 0px;} 
  h2 {font-size:16px; margin-top: 10; margin-bottom: 0px;} 
  .slider { width: 120px; margin-top:14px; height:6px;}
  .volume { width: 160px; margin-top:0px; height:6px;}

  .write { width: 100px; height:30px;
  border-radius: 5%;
  font-size: 13pt;
  text-align: center;
  background: #d3d3d3;
  color: #0000ff;
  box-shadow: 3px 3px 2px #666666;
  border: 2px solid #000088;
  }
.write:hover {
  box-shadow: none;
  color: #000066;
  background: #eeeeee;
  }
  .power {
  width: 100px; height:30px;
  border-radius: 5%;
  font-size: 12pt;
  text-align: center;
  background: #d3d3d3;
  color: #dc143c;
  box-shadow: 3px 3px 2px #666666;
  border: 2px solid #000088;
  }
.power:hover {
  box-shadow: none;
  color: #000066;
  background: #eeeeee;
  }
</style>
<script src="js/hat21sub.js"></script>
</head>

<body>
	<h1>HAT21 </h1>
	<p>Hearing Assist Tool 2021 (Version 1.00)</p>

<?php
	// Read common data
	$fp = fopen("/var/www/html/comdata", 'r');
	$data = fgets($fp);
	fclose($fp);
	$arr = str_split($data);
	echo "〔 " . $data . " 〕<br><br>";

	// modeによってラジオボタンをチェックする
	$rd = array(0=>'','','','','');
	$da = ' disabled';
	switch ($arr[3]) {
		case 'e':	$rd[0] = ' checked';	$da = ' ';	break;
		case 'x':	$rd[1] = ' checked';				break;
		case 'l':	$rd[2] = ' checked';				break;
		case 'h':	$rd[3] = ' checked';				break;
		case 'b':	$rd[4] = ' checked';				break;
	}
	$noiseCut = "";
	if ($arr[4] != 0)	$noiseCut = " checked";

	$w = "<table border='1' align='center'><tr><td>"
	  	. "<table align='center' width='220'><tr><td>"
		. "<input type='radio' name='mode' value='EQU' onchange='modeChange()' id='idequ'" . $rd[0] . "> Equalizer   "
		. "<input type='radio' name='mode' value='NON' onchange='modeChange()' id='idnon'" . $rd[1] . "> NON   "
	  	. "</td></tr></table><table align='center'><tr><td>"
		. "<input type='radio' name='mode' value='LPF' onchange='modeChange()' id='idlpf'" . $rd[2] . "> LPF  "
		. "<input type='radio' name='mode' value='HPF' onchange='modeChange()' id='idhpf'" . $rd[3] . "> HPF  "
		. "<input type='radio' name='mode' value='BPF' onchange='modeChange()' id='idbpf'" . $rd[4] . "> BPF"
		. "</td></tr></table></td></tr></table><br>"
    	. "<h2>Equalizer</h2>" 
		. "<table align='center'><tr><td>"
		. "<table align='left'><tr><td>"
		. "   ━━ Left ━━<br>"
		. "   (low)   (high)<br>"
	    . "1 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL1' value='" . $arr[5] . "' onchange='sensChange(1)'" . $da . " /><br>"
	    . "2 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL2' value='" . $arr[6] . "' onchange='sensChange(2)'" . $da . " /><br>"
	    . "3 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL3' value='" . $arr[7] . "' onchange='sensChange(3)'" . $da . " /><br>"
	    . "4 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL4' value='" . $arr[8] . "' onchange='sensChange(4)'" . $da . " /><br>"
	    . "5 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL5' value='" . $arr[9] . "' onchange='sensChange(5)'" . $da . " /><br>"
	    . "6 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL6' value='" . $arr[10] . "' onchange='sensChange(6)'" . $da . " /><br>"
	    . "7 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL7' value='" . $arr[11] . "' onchange='sensChange(7)'" . $da . " /><br>"
	    . "8 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL8' value='" . $arr[12] . "' onchange='sensChange(8)'" . $da . " /><br>"
	    . "9 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarL9' value='" . $arr[13] . "' onchange='sensChange(9)'" . $da . " /><br>"
		. "</td></tr></table>"
		. "<table align='left'><tr><td> </td></tr></table>"
		. "<table><tr><td>"
		. "   ━━ Right ━━<br>"
		. "   (low)   (high)<br>"
	    . "1 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR1' value='" . $arr[15] . "' onchange='sensChange(10)'" . $da . " /><br>"
	    . "2 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR2' value='" . $arr[16] . "' onchange='sensChange(11)'" . $da . " /><br>"
	    . "3 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR3' value='" . $arr[17] . "' onchange='sensChange(12)'" . $da . " /><br>"
	    . "4 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR4' value='" . $arr[18] . "' onchange='sensChange(13)'" . $da . " /><br>"
	    . "5 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR5' value='" . $arr[19] . "' onchange='sensChange(14)'" . $da . " /><br>"
	    . "6 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR6' value='" . $arr[20] . "' onchange='sensChange(15)'" . $da . " /><br>"
	    . "7 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR7' value='" . $arr[21] . "' onchange='sensChange(16)'" . $da . " /><br>"
	    . "8 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR8' value='" . $arr[22] . "' onchange='sensChange(17)'" . $da . " /><br>"
	    . "9 <input type='range' min='0' max='9' step='1' class='slider' id='ssBarR9' value='" . $arr[23] . "' onchange='sensChange(18)'" . $da . " /><br>"
		. "</td></tr></table>"
	. "</td></tr></table>"
	. "<table align='center'><tr><td>"
    . "<p align='left'>       Volume control    Noise cut "
	. "    <input type='checkbox' id='noiseCut' onchange='noiseCut()'" . $noiseCut . "></p>"
	. "    <input type='range' min='0' max='9' step='1' class='volume' id='vlmBar' value='" . $arr[24] ."' onchange='volumeChange()' />       </td></tr><tr><td><br>"
	. "    <input type='button' value='Update' class='write' onClick='writeCondition()' />"
	. "    <input type='button' value='Power Off' class='power' onClick='powerOff()' />"
	. "</td></tr></table>";

	echo "$w";
?>
  </body>
</html>

〔ポイント〕
  15行目(~51行):2つのボタンのデザインをCSS3で直書きしています。
  52行目: ここでJavaScript関数群のファイルhat21sub.jsを組み込んでいます。
  61~63行:HAT21の状態を共用データから読み込みます。これがコンソール作成の基礎情報になります。
  64行目: 読み込んだ文字列データを文字配列に変換します。
  65行目: 文字列データをデバッグ用窓に表示します。
  69行目: イコライザー感度調整を無効にするように、定数$daに' disabled'をセットしておきます。
  70行目: 配列[3]のmode情報を判定して該当するラジオボタンをチェック状態にします。
       イコライザーが選択されていれば、' disabled'をセットした定数$daを空白に置き換えます。
  82行目: ラジオボタンは「type='radio'」で設定、それぞれ3文字の値と5文字のidを割り付けます。
       チェックの状態は配列$rd[]で決定、onChangeイベントでJavaScript関数modeChange()を実行。
  94行目: スライドバーは「type='range'」で設定、左チャンネルを表すid「ddBarL1~9」を割り当てます。
       各スライドバーのツマミの位置は、共用データ配列位置5~13で設定します。
       onChangeイベントでJavaScript関数sensChange(1~9)を実行。定数$daで有効/無効が決まります。
  108行目: 右チャンネルを表すid「ddBarR1~9」を割り当てます。
       各スライドバーのツマミの位置は、共用データ配列位置15~23で設定します。
       onChangeイベントでJavaScript関数sensChange(10~18)を実行します。
  121行目: ノイズカットを選択するチェックボックスです。
       onChangeイベントでJavaScript関数noiseCut()を実行します。
  122行目: 音量調整スライドバーです。ツマミ位置は共用データ配列位置24で設定します。
       ツマミが移動すると、onChangeイベントでJavaScript関数volumeChange()を実行します。
  123行目: ボタンは「type='button'」で設定、クリックするとJavaScript関数writeCondition()を実行します。
  124行目: ボタンは「type='button'」で設定、クリックするとJavaScript関数powerOff()を実行します。
  127行目: フィルター種別選択ラジオボタンから始まる一連のHTML文を、このecho文で一気に出力します。


4.共用データの更新処理

 共用データは26文字の文字列です。上記のHTMLコードに見るように、$dataに読み込んだデータは、PHPのstr_split関数を使って、
   $arr = str_split(string $data);
のように簡単に文字配列に変換できます(61~65行)。また逆に、文字配列をimplode関数で文字列に連結することもできます(下記 hiddenpage.htmlコードの67行目)。
   $data = implode(array $arr);
 共用データファイルから読み込んだ文字列データは文字配列に変換して、各配列位置に対応したパーツを描かせてコンソール画面を作成します。パーツを操作すると、対応するJavaScript関数がその値を読み取って、該当位置の配列データの値を更新して文字列に変換して共用データファイルに書き戻す。そんな方法でHAT21とのコミュニケーションをとることにします。

①共用データの構造

 共用データは次のような構成になっています。最初の[ ]は配列位置、続いてフィールド略称とコメントです。
Array (  [ 0]  Proc  // 実行フラグ 'p': 実行指示(操作発生)、 '_': 実行済み  [ 1]  Key  // 操作キー 'm': モード、 'L': 左感度、 'R': 右感度、 'v': 音量、 'w': Update  [ 2]  Value  // 指定された値  0~9(音量)  [ 3]  Mode  // 選択モード 'e': イコライザー、 'x': Non、 'l': LPF、 'h': HPF、 'b': BPF  [ 4]  Noise  // ノイズカット 整数値 [1or0] で[要or不要]  [ 5]  L[1]  // 左感度変更(以下バンド1~9の感度) '0'~'9'  [ 6]  L[2]  [ 7]  L[3]  [ 8]  L[4]  [ 9]  L[5]  [10]  L6[]  [11]  L7[]  [12]  L[8]  [13]  L[9]  [14]  -  // 未使用 '*'  [15]  R[1]  // 右感度変更(以下バンド1~9の感度) '0'~'9'  [16]  R[2]  [17]  R[3]  [18]  R[4]  [19]  R[5]  [20]  R[6]  [21]  R[7]  [22]  R[8]  [23]  R[9]  [24]  Volume // 音量 '0'~'9'  [25]  Mark  // 終端記号 ';' )
 配列先頭のProcはHAT21のトリガーとして使用します。コンソール上で何らかの操作が行われたら(つまり配列のどこかが変更されたら)、ここに'p'をセットして共用データファイルに書き込みます。HAT21はこの位置を監視していて、'p'が現れるとコンソール側に変更があったことを知り、直ちにリセットします('_'をセットして書き戻します)。続いて他の配列の値を参照して、該当する制御を行います。


②共用データ更新処理 'hiddenpage.html'

 共用データファイルの更新処理は、非表示のhtmlファイル'hiddenpage.html'が担当します。コンソール上のパーツを操作してイベントが発生すると、次項で述べるJavaScriptの関数によって操作内容をクッキーに書き込みます。hiddenpage.htmlでは、PHPのコードでその内容を取得し、共用データ文字配列の該当位置に情報を設定してから共用データファイルに書き出します。最後にhat21.htmlを呼び戻しています。コードはシンプルなので特に問題ないと思います。処理詳細は、次節「イベントとJavaScript」で説明します。
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>

<body>
<?php
	// 共用データを取得して配列に展開する
	$fp = fopen("/var/www/html/comdata", 'r+');
	$data = fgets($fp);
	$arr = str_split($data);
	echo "<br>" . $data . "<br>";

	// クッキー情報から種別を取り出す
	$cmd = $_COOKIE['command'];
	$kind = substr($cmd, 0, 4);

	// それぞれの種別に応じて配列情報を変更する
	if ($kind == 'mode') {
		$arr[0] = 'p';
		$arr[1] = 'm';
		$arr[3] = substr($cmd, -1);
	}
	else if ($kind == 'senL') {
		$arr[0] = 'p';
		$arr[1] = 'L';
		$m = (int)substr($cmd, 5, 1);
		$arr[2] = $m;
		$arr[4+$m] = substr($cmd, -1);
	}
	else if ($kind == 'senR') {
		$arr[0] = 'p';
		$arr[1] = 'R';
		$m = (int)substr($cmd, 5, 1);
		$arr[2] = $m;
		$arr[14+$m] = substr($cmd, -1);
	}
	else if ($kind == 'nois') {
		$arr[0] = 'p';
		$arr[1] = 'n';
		$arr[4] = substr($cmd, -1);
	}
	else if ($kind == 'volm') {
		$arr[0] = 'p';
		$arr[1] = 'v';
		$arr[24] = substr($cmd, -1);
	}
	else if ($kind == 'writ') {
		$arr[0] = 'p';
		$arr[1] = 'w';
	}
	else if ($kind == 'poff') {
		$arr[0] = 'p';
		$arr[1] = 'z';
	}

	// 配列データを統合して共用データをファイルに書き出す
	$data2 = implode($arr);
	rewind($fp);
	fwrite($fp, $data2);
	fclose($fp);

	if ($kind == 'poff')
		header("Location: ./finalpage.html");	// 終了ページへ移動する
	else
		header("Location: ./hat21.html");		// 元のページへ戻す
?>
</body>
</html>


5.イベントとJavaScript

 リモートコンソールは画面上のパーツの状態変更で発生するイベントを元に動作しますが、全体の処理の流れが込み入っているので、ここで全体の流れを整理しておきます。
①処理の流れ

 リモートコンソール処理は次の3つのファイルで構成されています。
   ・hat21.html: リモートコンソール画面の描画
   ・hat21sub.js: リモートコンソールで発生するイベントから呼び出されるJavaScript関数群
   ・hiddenpage.html: JavaScript関数の実行結果で共用データファイルを更新する非表示ページ

 これらは次のような流れで動作します。
  1) hat21.htmlが呼び出されると、HAT21の状態が記録された共用データファイルを読み込む。

  2) 共用データに基づいて、現在のHAT21の状態を反映したリモートコンソール画面を描画する。

  3) 画面を構成するパーツ(ラジオボタン、スクロールバーなど)の操作でイベントが発生する。

  4) イベントに対応したJavaScriptの関数が呼ばれて、所定の処理を実行する。

  5) 処理結果はクッキーにcommand変数として記録して、hiddenpage.htmlに制御を移行する。
   〔操作、イベント⇒JavaScript関数、クッキー記録の関係〕
    ・ラジオボタンのクリック:onChange⇒modeChange()
       command=mode#{e|x|l|h|b}   { }内のいずれか1文字で、それぞれ以下を表す。
           e:イコライザー、 x:素通し、 l:LPF、 h:LPF、 h:HPF
        (例) command=mode#x
    ・左スクロールバーの変化:onChange⇒sensChange()
       command=senL#(1~9)(0~9)   前の()はバンドNo.、後の()は感度レベル
        (例) command=senL#25
    ・右スクロールバーの変化:onChange⇒sensChange()
       command=senR#(1~9)(0~9)   前の()はバンドNo.、後の()は感度レベル
        (例) command=senR#10
    ・ノイズカットのチェックボックス:onChange⇒noiseCut()
       command=nois#{1|0}  チェック状態なら整数の1、そうでなければ整数の0
    ・音量スクロールバーの変化:onChange⇒volumeChange()
       command=volm#(0~9)   ()内は音量レベル
    ・[Update]ボタンクリック:onClick⇒writeCondition()
       command=writ#
    ・[Power Off]ボタンクリック:onClick⇒powerOff()
       command=poff#

  6) hiddenpage.htmlは先頭で共用データファイルから文字列データを読み込んで文字配列に変換する。

  7) 続いてクッキーからcommand変数の値を取り出し、
   先頭4文字{mode|senL|senR|nois|volm|writ|poff}で種別を判定する。

  8) 文字配列[0]に'p'、[1]に種別に対応した{m|L|R|n|v|w|z}をセットして、その他の種別固有の値を設定する。

  9) 文字配列を文字列データに変換して共用データファイルに書き出す。

  10) 電源オフ(種別=poff)なら終了画面finalpage.htmlを呼び、その他はhat21.htmlを呼び戻す。


②JavaScript関数のコード

 以上でイベントとJavaScript関数、共用データ更新の関係が明らかになりました。JavaScript関数群ファイルhat21sub.jsのコードは以下のとおりです。
// モードの変更を取り込む
function modeChange() {
	var mode = document.getElementsByName('mode');
	var result = '';
	for (var i=0; i<mode.length; i++) {
		if (mode[i].checked) {
			result = mode[i].value;
			break;
		}
	}
	switch (result) {
		case 'EQU':	result = 'e';	break;
		case 'NON':	result = 'x';	break;
		case 'LPF':	result = 'l';	break;
		case 'HPF':	result = 'h';	break;
		case 'BPF':	result = 'b';	break;
	}
	document.cookie = 'command=' + 'mode#' + result;
	window.location.href = 'hiddenpage.html';
}

// 感度の変更を取り込む
function sensChange(n) {
	var side = "";

	if (n > 0 && n <= 9)
		side = 'ssBarL' + n;
	else if ( n > 9 && n <= 18)
		side = 'ssBarR' + (n - 9);

	var sense = document.getElementById(side).value;
	var ck = "";
	if (n <= 9)
		ck = 'command=' + 'senL#' + n + sense;
	else
		ck = 'command=' + 'senR#' + (n-9) + sense;
	document.cookie = ck;
	window.location.href = 'hiddenpage.html';
}

// ノイズカットの状態を取り込む
function noiseCut() {
	var stat = 0;
	if (document.getElementById('noiseCut').checked)
		stat = 1;
	document.cookie = 'command=' + 'nois#' + stat;
	window.location.href = 'hiddenpage.html';
}

// 音量の変更を取り込む
function volumeChange() {
	var vlm = document.getElementById('vlmBar').value;
	document.cookie = 'command=' + 'volm#' + vlm;
	window.location.href = 'hiddenpage.html';
}

// HAT21に、設定情報のファイル出力を依頼する
function writeCondition() {
	document.cookie = 'command=' + 'writ#';
	window.location.href = 'hiddenpage.html';
}

// HAT21に、パワーオフを指示する
function powerOff() {
	var ans = window.confirm("HAT21の電源を切ります。よろしいですか?");
	if (ans == true)
		document.cookie = 'command=' + 'poff#';
	else
		document.cookie = 'command=' + 'zzzz#';
	window.location.href = 'hiddenpage.html';
}


 試作であれこれやってみる場合に、PHPやJavaScriptなどのインタープリター言語は手っ取り早く試せるので便利ですね。テキストエディターだけで何でも出来てしまうのも魅力です。HAT21と連係動作するシステムなので、ソースコードのダウンロードは次回に持ち越しです。実際はこの段階で、ダミーの共用データファイルを設定し、JavaScript関数のクッキー設定部分にalert文を挿入するなどして動作確認を行っています。どうやら結果は良好なようなので、引き続きHAT21の改変に取り組むことにします。では、お楽しみに!