2008年1月21日月曜日

JavaFX SCADA UI:警報を発する(2)

Javaプログラムで警報音を鳴動させる準備が整いましたので、JavaFXのプログラムで警報文の表示と警報音の鳴動開始/停止を制御させます。
依頼者プログラムから模擬計測値を入力したところで、その値と現在警報を発生中か否かを判定して、警報音の鳴動と警報文の表示制御を行っています。
今回、警報文の表示には、JavaFXのConfirmDialogクラスを使ってみました。表示された対話窓にある「了解」ボタンを押すと、onYes属性に定義した操作を実行する仕組みです。
企画を構築して実行すると枠付き窓が表示されます。模擬計測値が90.0を超えると、下図のような警報文の対話窓が表示されます。模擬計測値が設定値を超えている限り、了解ボタンを押しても何度でも警報が発生するようにしています。

プログラム例では、表示速度を3倍に速くしています。:-P)




package client;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import client.ScadaClient;

class ScadaDataModel {
attribute presenting: Boolean;
attribute alarting: Boolean;
attribute timingGenerator: Number;
attribute numericDatum: Number;
attribute numericData: Number*;
attribute sc: ScadaClient;
function formatNumber(value:Number): String;
operation getDisplayColor(value:Number): Color;
}

attribute ScadaDataModel.timingGenerator
= bind [1..180] dur 60000 linear continue if presenting;

attribute ScadaDataModel.numericData
= foreach (i in [0..180]) 0;

function ScadaDataModel.formatNumber(value:Number): String {
return value format as <<##0.00>>;
}

operation ScadaDataModel.getDisplayColor(value:Number): Color {
var c:Color = green;
if (value > 90.0) {
c = red;
} else if (value > 75.0) {
c = Color {red: 1.0, green: 0.5, blue: 0.0};
}
return c;
}

trigger on new ScadaDataModel {
sc = new ScadaClient();
}

trigger on ScadaDataModel.timingGenerator = value {
numericDatum = sc.receiveData();
delete numericData[0];
insert numericDatum as last into numericData;
System.out.println("受信生データ: {numericDatum}");
System.out.println("書式化データ: {formatNumber(numericDatum)}");
if ((numericDatum > 90.0) and (not alarting)) {
alarting = true;
sc.startAlarm();
ConfirmDialog {
title: "警報"
message: "流量が上上限値を超過しました!"
visible: true
onYes:
operation() {
sc.stopAlarm();
alarting = false;
}
}
}
}

Frame {
var scadaData = ScadaDataModel {presenting: true, alarting: false}
title: "JavaFX SCADA UI"
centerOnScreen: true
width: 800
height: 600
background: Color {red: 0.9, green: 0.9, blue: 0.9}
onClose: operation() {System.exit(0);}
content: BorderPanel {
center: Canvas {
content:
[Group {
transform: translate(50, 50)
content:
[Rect {
height: 18
width: 50
arcHeight: 4
arcWidth: 4
stroke: blue
fill: white
},
Text {
content: bind scadaData.formatNumber(scadaData.numericDatum)
font: new Font("Monospaced", "PLAIN", 14)
x: 47
y: 4
halign: TRAILING
fill: bind scadaData.getDisplayColor(scadaData.numericDatum)
},
Text {
content: "m3/s"
x: 54
y: 6
}
]
},
Group {
transform: translate(200, 50)
content:
[
Rect {
height: 120
width: 55
stroke: blue
fill: white
},
Rect {
x: 10
y: 10
height: 100
width: 10
fill: LinearGradient {
x1: 0, y1: 0, x2: 0, y2: 1
stops:
[Stop {
offset: 0.1
color: red
},
Stop {
offset: 0.2
color: Color {red: 1.0, green: 0.5, blue: 0.0}
},
Stop {
offset: 0.4
color: Color {red: 0.5, green: 1.0, blue: 0.0}
}]
spreadMethod: PAD
}
},
Rect {
x: 10
y: 10
height: bind 100 - scadaData.numericDatum
width: 10
fill: Color {red: 0.5, green: 0.5, blue: 0.5}
},
Line {
x1: 23
y1: 10
x2: 23
y2: 110
stroke: black
},
Group {
transform: translate(23, 10)
content: foreach (i in [0,20..100])
Line {
transform: [translate(0, i)]
x1: 0
y1: 0
x2: 3
y2: 0
stroke: black
}
},
Group {
transform: translate(32, 10)
content: foreach (i in [0,20..100])
Text {
transform: [translate(0, 100 - i)]
content: "{i}"
font: new Font("Monospaced", "PLAIN", 8)
valign: MIDDLE
halign: LEADING
}
}
]
},
Group {
transform: translate(50, 200)
content:
[
Rect {
height: 120
width: 230
stroke: blue
fill: white
},
Rect {
x: 10
y: 35
height: 76
width: 181
fill: black
},
Rect {
x: 10
y: 20
height: 15
width: 181
fill: Color {red: 0.3, green: 0.3, blue: 0.3}
},
Rect {
x: 10
y: 10
height: 10
width: 181
fill: Color {red: 0.5, green: 0.5, blue: 0.5}
},
Line {
x1: 194
y1: 10
x2: 194
y2: 110
stroke: black
},
Group {
transform: translate(194, 10)
content: foreach (i in [0,20..100])
Line {
transform: [translate(0, i)]
x1: 0
y1: 0
x2: 3
y2: 0
stroke: black
}
},
Group {
transform: translate(203, 10)
content: foreach (i in [0,20..100])
Text {
transform: [translate(0, 100 - i)]
content: "{i}"
font: new Font("Monospaced", "PLAIN", 8)
valign: MIDDLE
halign: LEADING
}
},
Group {
transform: translate(10, 10)
content: foreach (i in [0..179])
Line {
transform: [translate(i, 0)]
x1: 0
y1: bind 100 - scadaData.numericData[i]
x2: 1
y2: bind 100 - scadaData.numericData[i + 1]
stroke: bind scadaData.getDisplayColor(scadaData.numericData[i + 1])
strokeWidth: 2
}
}
]
}
]
}
}
visible: true
}

2008年1月17日木曜日

IBMのdeveloperWorksでJavaFXが紹介されました

ついに、IBMのdeveloperWorksにもJavaFXについての手引が載りました。これを機会に、JavaFXがより普及するとうれしいのですが。

2008年1月15日火曜日

JavaFX SCADA UI:警報を発する

次に、SCADA情報として入力した模擬計測値が、あらかじめ設定した値を超えたら警報を発するようにしてみます。
まず、
Javaで記述した模擬依頼者プログラムにコードを追加し、警報音の鳴動開始と、同停止手続きを追加します。
警報音のファイルは、構築子を定義してその中で読み込むようにしたかったのですが、構築子を定義すると、なぜかJavaFXのプログラムからJavaプログラムの手続きを呼び出さなくなってしまいます(誤りが発生するわけでもありません)。仕方ないので、実体変数の定義時に初期化を行って、音声ファイルを読み込むようにしました。
私は、警報音ファイルとして、自分のPCにあった「ALERT.WAV」を使用しました。Javaのプログラムでの音声ファイルの扱いについては、この投稿を参照してください(今回、警報文は、JavaFXのプログラムで表示することにしました。また、
鳴動開始と停止を別の手続きにしています)。
音声ファイルを「企画名\build\classes\包み名」ディレクトリに置きます。そして、NetBeansの「プロジェクト」窓でJavaファイルをマウスで右押しし、「ファイルをコンパイル」を選択実行して、Javaファイルを一括変換しておきます。
最後に、Javaプログラムのコードを載せておきます。


package client;

import java.applet.Applet;
import java.applet.AudioClip;

public class ScadaClient {

private AudioClip ac = Applet.newAudioClip(ScadaClient.class.getResource("ALERT.WAV"));
// 模擬SCADAデータ生成用作業変数。
private int i = -1;

public double receiveData() {
// 模擬SCADAデータを生成して返す。
i += 1;
if (i >= 360) {
i = 0;
}
return (double)(Math.abs(Math.sin(i * Math.PI / 180.0) * 100));
}

public void startAlarm() {
ac.loop();
}
public void stopAlarm() {
ac.stop();
}

public static void main(String[] args) {
ScadaClient sc = new ScadaClient();
for (int i = 0; i < 5; i++) {
System.out.println(sc.receiveData());
}
}
}

2008年1月11日金曜日

JavaFX SCADA UI:記録計を作る

前回の棒図式指示計に加えて、記録計を作ります。
表示装置上の記録計は、常に最新の情報を何点分か配列変数に保持しておき、その情報に基づいて線図式を描画することで実現できます。
Java言語等で配列変数を扱う場合、最新の情報が得られるたびに、既存の配列要素をずらして新たな情報を追加するという操作を行う必要があります。単純な処理ですが、あまり美しいものではありません。
JavaFXでは、単に、配列から最も古い情報を削除し、新たな情報を所要の位置に挿入するだけで済みます。添え字の付け替え操作が不要なため、簡潔で無駄のない記述が可能です。
プログラムの例では、模擬依頼者から新たな情報を取得するたびに、配列の先頭要素を削除し、取得した情報を配列の末尾に挿入しています。
後は、保持している情報を基にして、線図式を描画するだけです。
企画を構築して実行すると枠付き窓が表示され、窓の左下に右図のような記録計が表示されます。














package client;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import client.ScadaClient;

class ScadaDataModel {
attribute presenting: Boolean;
attribute timingGenerator: Number;
attribute numericDatum: Number;
attribute numericData: Number*;
attribute sc: ScadaClient;
function formatNumber(value:Number): String;
operation displayColor(value:Number): Color;
}

attribute ScadaDataModel.timingGenerator
= bind [1..60] dur 60000 linear continue if presenting;

attribute ScadaDataModel.numericData
= foreach (i in [0..180]) 0;

function ScadaDataModel.formatNumber(value:Number): String {
return value format as <<##0.00>>;
}

operation ScadaDataModel.displayColor(value:Number): Color {
var c:Color = green;
if (value > 90.0) {c = red;}
else if (value > 75.0) {c = Color {red: 1.0, green: 0.5, blue: 0.0};}
return c;
}

trigger on new ScadaDataModel {
sc = new ScadaClient();
}

trigger on ScadaDataModel.timingGenerator = value {
numericDatum = sc.receiveData();
delete numericData[0];
insert numericDatum as last into numericData;
System.out.println("受信生データ: {numericDatum}");
System.out.println("書式化データ: {formatNumber(numericDatum)}");
}

Frame {
var scadaData = ScadaDataModel {presenting: true}
title: "JavaFX SCADA UI"
centerOnScreen: true
width: 800
height: 600
background: Color {red: 0.9, green: 0.9, blue: 0.9}
onClose: operation() {System.exit(0);}
content: BorderPanel {
center: Canvas {
content:
[Group {
transform: translate(50, 50)
content:
[Rect {
height: 18
width: 50
arcHeight: 4
arcWidth: 4
stroke: blue
fill: white
},
Text {
content: bind scadaData.formatNumber(scadaData.numericDatum)
font: new Font("Monospaced", "PLAIN", 14)
x: 47
y: 4
halign: TRAILING
fill: bind scadaData.displayColor(scadaData.numericDatum)
},
Text {
content: "m3/s"
x: 54
y: 6
}
]
},
Group {
transform: translate(200, 50)
content:
[
Rect {
height: 120
width: 55
stroke: blue
fill: white
},
Rect {
x: 10
y: 10
height: 100
width: 10
fill: LinearGradient {
x1: 0, y1: 0, x2: 0, y2: 1
stops:
[Stop {
offset: 0.1
color: red
},
Stop {
offset: 0.2
color: Color {red: 1.0, green: 0.5, blue: 0.0}
},
Stop {
offset: 0.4
color: Color {red: 0.5, green: 1.0, blue: 0.0}
}]
spreadMethod: PAD
}
},
Rect {
x: 10
y: 10
height: bind 100 - scadaData.numericDatum
width: 10
fill: Color {red: 0.5, green: 0.5, blue: 0.5}
},
Line {
x1: 23
y1: 10
x2: 23
y2: 110
stroke: black
},
Group {
transform: translate(23, 10)
content: foreach (i in [0,20..100])
Line {
transform: [translate(0, i)]
x1: 0
y1: 0
x2: 3
y2: 0
stroke: black
}
},
Group {
transform: translate(32, 10)
content: foreach (i in [0,20..100])
Text {
transform: [translate(0, 100 - i)]
content: "{i}"
font: new Font("Monospaced", "PLAIN", 8)
valign: MIDDLE
halign: LEADING
}
}
]
},
Group {
transform: translate(50, 200)
content:
[
Rect {
height: 120
width: 230
stroke: blue
fill: white
},
Rect {
x: 10
y: 35
height: 76
width: 181
fill: black
},
Rect {
x: 10
y: 20
height: 15
width: 181
fill: Color {red: 0.3, green: 0.3, blue: 0.3}
},
Rect {
x: 10
y: 10
height: 10
width: 181
fill: Color {red: 0.5, green: 0.5, blue: 0.5}
},
Line {
x1: 194
y1: 10
x2: 194
y2: 110
stroke: black
},
Group {
transform: translate(194, 10)
content: foreach (i in [0,20..100])
Line {
transform: [translate(0, i)]
x1: 0
y1: 0
x2: 3
y2: 0
stroke: black
}
},
Group {
transform: translate(203, 10)
content: foreach (i in [0,20..100])
Text {
transform: [translate(0, 100 - i)]
content: "{i}"
font: new Font("Monospaced", "PLAIN", 8)
valign: MIDDLE
halign: LEADING
}
},
Group {
transform: translate(10, 10)
content: foreach (i in [0..179])
Line {
transform: [translate(i, 0)]
x1: 0
y1: bind 100 - scadaData.numericData[i]
x2: 1
y2: bind 100 - scadaData.numericData[i + 1]
stroke: bind scadaData.displayColor(scadaData.numericData[i + 1])
strokeWidth: 2
}
}
]
}
]
}
}
visible: true
}