IO設計2015年10月04日 10:11

IO設計


ジャバスクリプトで、パラメーターを指定するにはどうしたらいいかを探っている。

なるべくシンプルにして、ネット接続できない環境(ダイビングでは、実はそういう環境が多い)でも実行でき、インストールとかせずに(スクリプトを走らせるのもヤバイですが、(ヘタレな)ソース公開ということでご容赦を!)、出来れば実行の度にパラメーターを何度も入れなおすことなく、実行環境に保存して置ける方法はないかと欲張っている。

ローカルに設定を保存するテキストファイルを置いて、やり取りする方法もあり、ベーシックでならこれで実装するところだが、ジャバスクリプトの場合はセキュリティの関係もあって、しっくりくる仕掛けが見つからなかった。

最近流行の、HTML5という仕様では、クッキーの代わりにローカルでデータを保持する仕組みが備わっているらしい。

(ブラウザでストレージ? Web Storageを使いこなそう (1/3))
http://www.atmarkit.co.jp/ait/articles/1108/12/news093.html

「HTML5では、クッキーに代わるデータ保存の仕組みとして、「Web Storage」と呼ばれる機能を利用できる。Web Storageは、ブラウザ側でKey-Value型でデータを保存する機能のこと」

おお、これこそ浮沈子が探していた仕掛けだと感じて(フィーリング、大事です)、いろいろ勉強を始めた。

データ形式としては、JSONを利用したいと思っている。

(html5のlocalStorageで複数のデータを保存するには?)
http://the-zombis.sakura.ne.jp/wp/blog/2012/05/12/post-1362/

「前回はキーに対してデータを一つだけ保存したのですが、今回は複数のデータを保存する方法として複数のデータをJSONデータとして扱えば可能になります。」

この時点の実装では、保存できるデータは文字列になるので、それを数値として活用するには出し入れの際の変換が必要だが、それは一度コードを書いてしまえばコンピューター側でやってくれるので、使用上は問題ない。

この辺を探っていけば、一度入力したパラメーター(潜降速度、浮上速度、作業水深、作業時間、窒素濃度、ヘリウム濃度、大気圧、安全率など)を、次回も表示させることができ、必要なところだけを書き換えて計算させるという、まるでクライアント・アプリケーションのような動作をさせられるのではないかと期待している(人任せ・・・?)。

もう、コアな計算部分は出来てしまったので、最悪、パラメーターの値をシコシコとコード上で書き換えても問題はなく(あるよ!)、ここからは趣味として楽しみながら取り組むことができる。

この減圧ソフト作成の意義は、あくまでも厚労省が定めた減圧方法を基に、いちいち電卓で計算することなく、あるいは、表計算ソフトに依存することなく、手軽に減圧スケジュールに変換して、潜水士が業務上指示された減圧スケジュールと比較して、適法であることを確認できるというのがメインだ。

だから、使い勝手のいい巷の減圧ソフトをいろいろ調べて、保守性を与えない生の計算数値に近い条件で算出した減圧プランと比較して、ピッタリくるのがあればそれを適用してもいいんだが、やはりお上が定めた方法に準じて、自分で計算し、検討会報告書の手順に則って算出した値を使う方が確かだ(報告書添付の資料と突合して、確認はします)。

そのために、慣れないプログラムも書き、初めての言語であるジャバスクリプトも勉強している。

既に、ソースコードを書き換える方法では、M値を割り返して保守性を高めるという「安全率」という考え方を導入することも可能なようになっている(コメントアウトしてますが)。

この値を、任意の深度で書き換えれば、報告書資料のような減圧表を導出することも出来るだろう。

また、酸素減圧についても、パラメーターを与える方法を工夫して、特定の深度より浅い場合にガスの濃度(混合比)を変更することで実装可能だ(表計算ソフトの場合は、減圧過程が明らかになった後に、手動で濃度を変更して再計算する方法を採った)。

プログラム上で、この辺りをどうやって実装するかは、まだ具体的に検討はしていない。

まずは、その前に、パラメーターの入力方法を確立しようとしている。

完全に、趣味の世界に入ってるなあ・・・。

まあ、どうでもいいんですが。

ジャバスクリプトに拘らなければ、いろいろ方法はあるんだろうが、ブラウザー上で計算できるというのは、実行環境としては最も汎用的といえる。

アンドロイドアプリとして移植することも考えていたが、ローカルファイルとして置いておいて、それをブラウザーで開くだけでいいなら、移植するまでもない。

が、まあ、そっちは気が向いたらやるかもしれない。

パソコンなら、今時ブラウザーが入っていないパソコンはないだろうし、このブログからソースのスクリプトをコピペして、拡張子がhtmlのテキストファイルを作ることができないユーザーは限られているだろうから、誰もが広く活用でき、ネット環境がなくても使えるという意味では、ジャバスクリプトをローカルで走らせるというのが一番いいように感じる(フィーリング、大事です)。

というわけで、今日は言い訳だけして、お終いにしよう。

昨夜、今まで書いたソースコードに、バグがあったことに気が付いて(プリントすると、先頭にスペースが入ってしまう)、元ネタからコピペした部分に紛れ込んでいた隠れ文字コードを削除してプリントアウトもきれいに出るようになった。

スッキリ宿便が出た感じだ(お食事中の方、スイマセン・・・)。

(ヘリウム中間報告)
http://kfujito2.asablo.jp/blog/2015/10/03/7823200

このページのソースコードにも入っていたが、ちゃんと消した(「<!DOCTYPE html>」の前に、ゴミがあったようだ)。

(移植完了!)
http://kfujito2.asablo.jp/blog/2015/09/27/7813894

この中には、入っていなかった。

今後、気を付けなければならんな。

安易なコピペは、慎もう(・・・無理だな)。

コピペの力!2015年10月04日 21:34

コピペの力!
コピペの力!


今回は、入力関係の作成。

パラメーターをいちいち入れるのは面倒なので、HTML5の機能であるローカルストレージというのをやってみた。

IEとかでは動かないので、どうしようかと思っている(たぶん、コードの書き方が違うのかもしれない)。

やっぱ、ファイルのやり取りをしなければいけないんだろうか?。

そこにハマると、抜け出せない気がしたので、とりあえずコピペして動いたグーグルクローム(バージョン 45.0.2454.101 m)で試してみる。

「<html lang="ja">
<head>
<meta charset="utf-8">
<title>JavaScript</title>
</head>
<body onload="viewStorage();">



<script type="text/javascript">



/*
// (1)Web Storageの実装確認
if (typeof localStorage === 'undefined') {
window.alert("このブラウザはlocalStorage機能が実装されていません");
} else {
window.alert("このブラウザはlocalStorage機能を実装しています");
}
*/
var storage = window.localStorage;

// (2)localStorageへの格納
function setlocalStorage() {

var key = document.getElementById("textkey").value;
var value = document.getElementById("textdata").value;

// 値の入力チェック
if (key && value) {
window.localStorage.setItem(key, value);
}

key = "";
value = "";

viewStorage();
}

// (3)localStorageからのデータの取得と表示
function viewStorage() {

var list = document.getElementById("list")
while (list.firstChild) list.removeChild(list.firstChild);

// localStorageすべての情報の取得
for (var i=0; i < storage.length; i++) {
var _key = storage.key(i);

// localStorageのキーと値を表示
var tr = document.createElement("tr");
var td1 = document.createElement("td");
var td2 = document.createElement("td");
list.appendChild(tr);
tr.appendChild(td1);
tr.appendChild(td2);
td1.innerHTML = _key;
td2.innerHTML = storage.getItem(_key);
}
}

// (4)localStorageから削除
function removeStorage() {
var key = document.getElementById("textkey").value;
storage.removeItem(key);
key = "";
viewStorage();
}
/*
// (5)localStorageからすべて削除
function removeallStorage() {
storage.clear();
viewStorage();
}
*/

</script>


<section id="main">
保存するKey:<input id="textkey" type="text" />
保存する値:<input id="textdata" type="text" />
<button id="button" onclick="setlocalStorage()">保存</button>
<button id="button" onclick="removeStorage()">削除</button>
<!--
<button id="button" onclick="removeallStorage()">全て削除</button>
-->
<table border="1">
<tr>
<th>キー</th><th>値</th>
</tr>
<tbody id="list">
</tbody>
</table>

<input type="button" value="値を変更したら、このボタンを押して再計算!" onclick="location.reload();" />

</section>


<script type="text/javascript">
function print(str){
document.write(str + "<br />");
}

var a=parseInt(storage.getItem("a"));
var d=parseInt(storage.getItem("d"));
var nhe=parseInt(storage.getItem("nhe"));
var nn2=parseInt(storage.getItem("nn2"));
var pa=parseInt(storage.getItem("pa"));
var pb=parseInt(storage.getItem("pb"));
var qhe=parseInt(storage.getItem("qhe"));
var qn2=parseInt(storage.getItem("qn2"));
var r=parseInt(storage.getItem("r"));
var r0=parseInt(storage.getItem("r0"));
var t1=parseInt(storage.getItem("t1"));


/*
print(a);
print(d);
print(nhe);
print(nn2);
print(pa);
print(pb);
print(qhe);
print(qn2);
print(r);
print(r0);
print(t1);
*/

/*
<<<<潜降>>>>
*/
// var pa = 100;
// var pb = 0;
//var nn2 = 0.79;
var nn2 = nn2/100;//窒素濃度
var nhe = nhe/100;//ヘリウム濃度
var a = a/10;//安全率


var r0;//潜降速度(m/分)
//r0=10;//r0=prompt("senkou_sokudo(m/min) ? ","");
//if ( (r0 == "") || (r0 == null) ) r0 = "0";
r0 = 10*r0;
var d;//作業深度(m)
//d=40;//d=prompt("sagyou_sindo(m) ? ","");
//if ( (d == "") || (d == null) ) d = "0";
var t = 10*d/r0;
var qn2 = qn2/10000//74.5207;
//var qhe=0
var half_time_n2=[5,8,12.5,18.5,27,38.3,54.3,77,109,146,187,239,305,390,498,635];
var half_time_he=[1.887,3.019,4.717,6.981,10.189,14.453,20.491,29.057,41.132,55.094,70.566,90.189,115.094,147.170,187.925,239.623]

var kn2=new Array(15);
var bunatu_n2=new Array(15);
for (var i = 0; i < 16;i++){
kn2[i] = Math.log(2)/half_time_n2[i];
bunatu_n2[i] = (pa+pb)*nn2+r0*nn2*(t-1/kn2[i])-((pa+pb)*nn2-qn2-r0*nn2/kn2[i])*Math.exp(-kn2[i]*t);
}

var khe=new Array(15);
var bunatu_he=new Array(15);


for (var i = 0; i < 16;i++){
khe[i] = Math.log(2)/half_time_he[i];



bunatu_he[i] = (pa+pb)*nhe+r0*nhe*(t-1/khe[i])-((pa+pb)*nhe-qhe-r0*nhe/khe[i])*Math.exp(-khe[i]*t);
}



/*
<<<<作業>>>>
*/
var pb = 10*d;
var qn2=new Array(15);
var qhe=new Array(15);
var t1;//作業時間(分)
// t1=60;//t=prompt("sagyou_jikan(min) ? ","");
//if ( (t1 == "") || (t1 == null) ) t1 = "0";
for (var i = 0; i < 16;i++){
qn2[i] = bunatu_n2[i];
bunatu_n2[i] = (pa+pb)*nn2-((pa+pb)*nn2-qn2[i])*Math.exp(-kn2[i]*t1);
qhe[i] = bunatu_he[i];
bunatu_he[i] = (pa+pb)*nhe-((pa+pb)*nhe-qhe[i])*Math.exp(-khe[i]*t1);
}




/*
<<<<浮上開始時の分圧>>>>
*/
var btm_bunatu_n2=new Array(15);
for (var i = 0; i < 16;i++){
btm_bunatu_n2[i] = bunatu_n2[i]
}
var btm_bunatu_he=new Array(15);
for (var i = 0; i < 16;i++){
btm_bunatu_he[i] = bunatu_he[i]
}

/*
<<<<M値>>>>
*/
var mvn2a=[126.885,109.185,94.381,82.446,73.918,63.153,56.483,51.133,48.246,43.709,40.774,38.68,34.463,33.161,30.765,29.284];
var mvn2b=[0.5578,0.6514,0.7222,0.7825,0.8126,0.8434,0.8693,0.891,0.9092,0.9222,0.9319,0.9403,0.9477,0.9544,0.9602,0.9653];
var mvhea=[174.247,147.866,127.477,112.400,99.588,89.446,80.059,71.709,66.285,62.049,59.152,58.029,57.586,58.143,57.652,57.208]
var mvheb=[0.4770,0.5747,0.6527,0.7223,0.7582,0.7957,0.8279,0.8553,0.8757,0.8903,0.8997,0.9073,0.9122,0.9171,0.9217,0.9267]

/*
<<<<初回減圧停止深度の決定>>>>
*/
var r;//浮上速度(m/分)
// r=10;//r=prompt("fujyou_sokudo(m/min) ? ","");
//if ( (r == "") || (r == null) ) r = "0";
r = -10*r;
var hantei=new Array(15);
var mv=new Array(15);
var j = 0;
while (j < 20){
var t = Math.abs((d-3*j)*10/r);
var pc = 3*j*10;
for (var i = 0; i < 16;i++){

//窒素の計算
qn2[i] = btm_bunatu_n2[i];
bunatu_n2[i] = (pa+pb)*nn2+r*nn2*(t-1/kn2[i])-((pa+pb)*nn2-qn2[i]-r*nn2/kn2[i])*Math.exp(-kn2[i]*t);

//ヘリウムの計算
qhe[i] = btm_bunatu_he[i];
bunatu_he[i] = (pa+pb)*nhe+r*nhe*(t-1/khe[i])-((pa+pb)*nhe-qhe[i]-r*nhe/khe[i])*Math.exp(-khe[i]*t);

// mv[i] = (pa+pc)/mvn2b[i]+mvn2a[i];//窒素だけの時

//ヘリウム混合の時のM値
mv[i]=(pa+pc)/((mvn2b[i]*bunatu_n2[i]+mvheb[i]*bunatu_he[i])/(bunatu_n2[i]+bunatu_he[i]))+((mvn2a[i]*bunatu_n2[i]+mvhea[i]*bunatu_he[i])/(bunatu_n2[i]+bunatu_he[i]));
var a;
//a=1.1;
mv[i] = mv[i]/a;//安全率

//浮上できるかどうかの判定
if (bunatu_n2[i]+bunatu_he[i] > mv[i]){
hantei[i] = 1;
} else {
hantei[i] = 0;
}
}//for閉じ
var hanteiti = 0;
for (var i = 0; i < 16;i++){
hanteiti = hanteiti+hantei[i];
}//for閉じ
if (hanteiti === 0){
break;
}
j++;
}//while閉じ

/*
<<<<停止+浮上>>>>
*/

if (j === 0){
print("end");
}


while (j > 0){
var teisi = j;
var teisi_bunatu_n2=new Array(15);
var teisi_bunatu_he=new Array(15);

//停止分圧を変数に格納して保持
for (var i = 0; i < 16;i++){
teisi_bunatu_n2[i] = bunatu_n2[i];
teisi_bunatu_he[i] = bunatu_he[i];
}//for抜け

var pb = 3*teisi*10;
var pc = 3*(teisi-1)*10;
var l = 1;
while (l < 300){

//停止の計算
var t = l;//1分毎に計算

for (var i = 0; i < 16;i++){

//窒素の計算
qn2[i] = teisi_bunatu_n2[i];
bunatu_n2[i] = (pa+pb)*nn2-((pa+pb)*nn2-qn2[i])*Math.exp(-kn2[i]*t);
qn2[i] = bunatu_n2[i];

//ヘリウムの計算
qhe[i] = teisi_bunatu_he[i];
bunatu_he[i] = (pa+pb)*nhe-((pa+pb)*nhe-qhe[i])*Math.exp(-khe[i]*t);
qhe[i] = bunatu_he[i];

}//for抜け

//浮上の計算
var t = Math.abs(3*10/r);//3m浮上する時間

for (var i = 0; i < 16;i++){

//窒素の計算
bunatu_n2[i] = (pa+pb)*nn2+r*nn2*(t-1/kn2[i])-((pa+pb)*nn2-qn2[i]-r*nn2/kn2[i])*Math.exp(-kn2[i]*t);

//ヘリウムの計算
bunatu_he[i] = (pa+pb)*nhe+r*nhe*(t-1/khe[i])-((pa+pb)*nhe-qhe[i]-r*nhe/khe[i])*Math.exp(-khe[i]*t);

//M値の計算

//ヘリウム混合の時のM値
mv[i]=(pa+pc)/((mvn2b[i]*bunatu_n2[i]+mvheb[i]*bunatu_he[i])/(bunatu_n2[i]+bunatu_he[i]))+((mvn2a[i]*bunatu_n2[i]+mvhea[i]*bunatu_he[i])/(bunatu_n2[i]+bunatu_he[i]));

mv[i] = mv[i]/a;//安全率

//浮上できるかどうかの判定
if (bunatu_n2[i]+bunatu_he[i] > mv[i]){
hantei[i] = 1;
} else {//if抜け
hantei[i] = 0;
}//else抜け
}//for抜け

//判定配列の配列変数の合計
var hanteiti = 0;
for (var i = 0; i < 16;i++){
hanteiti = hanteiti+hantei[i];
}//for抜け

//浮上OKなら抜ける
if (hanteiti === 0){
break;
}//if抜け

l++;//変数「l」のインクリメント

}//内側のwhile抜け

//プリント表示
print(3*j+"m,"+ l +"min");

j--;//変数「j」のデクリメント

}//外側のwhile抜け
</script>
</body>
</html>」

コードは一気に増えて、350行くらいになった。

画像は、安全率の検証のために、検討会報告資料の空気潜水(っていうか、この資料自体が空気潜水だけ:酸素減圧もありますが)の該当するところと比較したもの。

全体に1.1の安全率を適用し、40m滞底時間110分(潜降・浮上速度8m/分なので、潜降時間5分を修正:浮沈子の計算は、作業時間でやっているので)して、減圧プランを出した。

パラメーターを書き換える毎に、再計算ボタンを押すようにしたが、再読み込みしてるだけ・・・。

ローカルストレージで格納された値は、文字列なので、値の方は数値に変換してやる必要がある。

潜降プロセスに入る前に、parseInt()というのを使って、変換している(これは、整数にしか変換できないことが判明して、後日、parseFloat()に書き換えている)。

他の所は、前回調べたページのコピペであるな。

(IO設計)
http://kfujito2.asablo.jp/blog/2015/10/04/7825265

このなかでは、JSONを使うとあるが、ファイルを読み込む仕掛けを使う際にはそれもいいだろうが、ローカルストレージの場合は、画面上で弄れるのでこのままでいい。

垢抜けないというか、悲惨な画面だが、ベタ書きするとこんな感じになっている。

「アイテム一覧:
・保存するKey:(入力窓)
・保存する値:(入力窓)
・保存(ボタン)
・削除(ボタン)
<以下、表( )内は、注釈>
キー:値
・a:11(安全率×10)
・d:40(作業深度:m)
・nhe:0(ヘリウム:パーセント)
・nn2:79(窒素:パーセント)
・pa:100(大気圧:kPa)
・pb:0(水面のゲージ圧:kPa)
・qhe:0(水面のヘリウム分圧:kPa)
・qn2:745207(水面の窒素分圧:kPa×10000)
・r:8(浮上深度:m/分)
・r0:8(潜降速度:m/分)
・t1:105(作業時間:分:滞底時間-潜降時間)
<以上で、表終了>
・値を変更したら、このボタンを押して再計算!(ボタン)
<以下、減圧プラン>
18m,10min
15m,17min
12m,27min
9m,46min
6m,92min
3m,246min」

人様に使っていただくには、いささか工夫が必要だが、ブラウザー上で操作するという当初からの目標は達成したことになる。

普及しているIEとかで出来ないのは悔しいが、いくつか他のブラウザーで試してみよう。

コードの不具合だろうが、有名どころのブラウザーでは動かしたいもんだな。

ところで、大事なことに気づいたんだが、スマホのクロームは、スマホ内のファイルを開くことができない。

オペラでは開くことは出来たが、うまく動かなかった。

この辺りにハマると、目も当てられなくなりそうだな。

うーん、どうしようか・・・。

ブラウザーという共通環境で動かないということになれば、やはりアプリを書くしかなくなる。

または、どっかのウェッブサーバーに上げて、閲覧するということになるだろうな。

それも、避けたいところだ。

まあ、インターフェースをどうするかという話なので、あまり深刻には考えていない。

やっぱ、外部ファイルを読み書きするのが確かだろうな。

そうなると、アプリケーションが一番簡単ということになって、環境依存性が高まる。

痛しかゆしだろう・・・。

まあ、そっちの方は、おいおいやるとして、安全率を深度によって使い分ける仕掛けと、酸素減圧をどうするかを考えなければならない。

酸素減圧も、マスクからの漏れを考慮した場合等も考えておかなければならない。

(高気圧作業安全衛生規則の一部を改正する省令の施行等について)
http://www.mhlw.go.jp/file/06-Seisakujouhou-11300000-Roudoukijunkyokuanzeneiseibu/sekoutuutatu.pdf

「呼吸用ガスとしていわゆる純酸素を用いて減圧を行う場合であって、マスクから呼気が漏れるおそれがあるときは、各区間の計算に際し、窒素の濃度(NN2)を 20 パーセント、ヘリウムの濃度(NHe)を0パーセントとしてそれぞれ計算すること。」

深度毎に、パラメーターを変えられるのがいいんだが、表計算では簡単だ(セルに代入するだけでいい)。

プログラムでは、どう実装したらいいんだろうか。

深度を指定する係数と、ガスの構成比や安全率をセットにして、条件式で判定するのがいいだろうとは思っている。

最大12mまでの純酸素減圧を認めているので、それにも対応しなければならないだろう。

こっちは、12m、9m、6m、3mと4パターン作ればいいだろう。

なんか、大変なことになりそうだな。

変数名とか、ワケワカになりそうだ。

ローカルストレージのコードを見て、ローカル変数というのがどういうものかが何となく分かった。

ファンクションの中で定義してしまえば、その外で悪さをすることはない。

ちょっと、参考になったな。

浮沈子のコードは、ベーシックから移植したので、そういう配慮はなかった。

みんなグローバル変数のまんま・・・。

変数名も、計算式の中の変数名を引きずっていて、整理されていない。

このまま、複雑な処理に突っ走ると、悲惨な結果になりそうな予感がしている。

まあいい。

ここまで来れば、後はどうにでもなるだろう。

とりあえずは、直書きしてでも、安全率や酸素減圧のバリエーションを作ることだな。

初回停止深度の値から、整理していけば、十分だろう。

酸素を使い始める深度(何らかの変数を与える)と比較して、その時点(初回減圧停止深度)からのガスを再定義すればいい。

問題なのは、初回停止が酸素使用開始深度より浅くなった場合で、浮上途中で酸素に切り替えるわけにもいかないだろうから、その深度以浅の区切りのいい深度から吸わせることになる。

その判定も意識しないとな。

安全率は、どの程度の柔軟さを与えるか、何種類くらい設定するかで変わって来る。

検討会資料(別添資料4)では、18mから12mまでを1.10、9mを1.35、6mを1.40、水面を1.21として計算して、純酸素は12mから吸わせて6mで完了している。

このパターンは、少なくともクリアしなければならないだろう。

ちょっと、戦略を立て直さなければならないな。

もちろん、酸素の毒性の計算もある。

それこそ、長期戦だ。

年内完了も怪しい・・・。