Numass control room global update

This commit is contained in:
Alexander Nozik 2017-05-31 16:53:51 +03:00
parent c160fcdea4
commit 0bc03b6053
13 changed files with 144 additions and 433 deletions

View File

@ -16,7 +16,7 @@ dependencies {
task debug(dependsOn: classes, type: JavaExec) { task debug(dependsOn: classes, type: JavaExec) {
main mainClass main mainClass
args "--config.resource=/config-debug/devices.xml" args = ["--config.resource=/config-debug/devices.xml"]
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
description "Start application in debug mode with default virtual port" description "Start application in debug mode with default virtual port"
group "debug" group "debug"
@ -24,7 +24,7 @@ task debug(dependsOn: classes, type: JavaExec) {
task testRun(dependsOn: classes, type: JavaExec) { task testRun(dependsOn: classes, type: JavaExec) {
main mainClass main mainClass
args(["--config=D:/temp/test/numass-devices.xml", "--device=thermo-1"]) args = ["--config=D:/temp/test/numass-devices.xml", "--device=thermo-1"]
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
description "Start application using real device" description "Start application using real device"
group "debug" group "debug"

View File

@ -1,53 +0,0 @@
/*
* Copyright 2015 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package inr.numass.control.cryotemp;
import hep.dataforge.control.devices.DeviceFactory;
import hep.dataforge.meta.Meta;
import inr.numass.control.DeviceViewConnection;
import inr.numass.control.NumassControlApplication;
import javafx.stage.Stage;
import java.util.Objects;
/**
* @author darksnake
*/
public class PKT8App extends NumassControlApplication<PKT8Device> {
@Override
protected DeviceViewConnection<PKT8Device> buildView(PKT8Device device) {
return PKT8ViewConnection.build(device.getContext());
}
@Override
protected DeviceFactory getDeviceFactory() {
return new PKT8DeviceFactory();
}
@Override
protected void setupStage(Stage stage, PKT8Device device) {
stage.setTitle("Numass temperature view " + device.getName());
stage.setMinHeight(400);
stage.setMinWidth(400);
}
@Override
protected boolean acceptDevice(Meta meta) {
return Objects.equals(meta.getString("type"), "PKT8");
}
}

View File

@ -22,6 +22,6 @@ public class PKT8DeviceFactory implements DeviceViewFactory {
@Override @Override
public DeviceViewConnection buildView(Device device) { public DeviceViewConnection buildView(Device device) {
return PKT8ViewConnection.build(device.getContext()); return new PKT8ViewConnection();
} }
} }

View File

@ -1,35 +0,0 @@
package inr.numass.control.cryotemp;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.fx.fragments.FXFragment;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import java.io.IOException;
/**
* Created by darksnake on 07-Oct-16.
*/
public class PKT8PlotFragment extends FXFragment {
private PKT8PlotView plotController;
public PKT8PlotFragment(PKT8Device device) {
super("PKT8 cryogenic temperature viewer", 600, 400);
try {
FXMLLoader loader = new FXMLLoader(device.getContext().getClassLoader().getResource("fxml/PKT8Plot.fxml"));
loader.setClassLoader(device.getContext().getClassLoader());
loader.load();
plotController = loader.getController();
device.connect(plotController, Roles.VIEW_ROLE);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected Parent buildRoot() {
return plotController.getPane();
}
}

View File

@ -1,144 +0,0 @@
/*
* Copyright 2015 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package inr.numass.control.cryotemp;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.MeasurementListener;
import hep.dataforge.meta.Meta;
import hep.dataforge.plots.PlotUtils;
import hep.dataforge.plots.data.TimePlottable;
import hep.dataforge.plots.data.TimePlottableGroup;
import hep.dataforge.plots.fx.FXPlotFrame;
import hep.dataforge.plots.fx.PlotContainer;
import hep.dataforge.plots.jfreechart.JFreeChartFrame;
import inr.numass.control.DeviceViewConnection;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.ResourceBundle;
/**
* FXML Controller class
*
* @author darksnake
*/
public class PKT8PlotView extends DeviceViewConnection<PKT8Device> implements Initializable, MeasurementListener {
private FXPlotFrame plotFrame;
private TimePlottableGroup plottables;
@FXML
private BorderPane root;
@FXML
private ToggleButton rawDataButton;
@FXML
private AnchorPane plotArea;
/**
* Initializes the controller class.
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
}
@Override
public void open(PKT8Device device) throws Exception {
super.open(device);
rawDataButton.selectedProperty().addListener(observable -> {
if (plotFrame != null) {
setupPlotFrame(plotFrame.getConfig());
if (device != null) {
setupChannels(device);
}
}
});
setupPlotFrame(device.meta().getMetaOrEmpty("plot.frame"));
setupChannels(device);
}
@Override
public void close() throws Exception {
super.close();
}
/**
* Set o reset plot area
*/
private synchronized void setupPlotFrame(Meta plotFrameMeta) {
plottables = new TimePlottableGroup();
plottables.setMaxAge(Duration.parse(plotFrameMeta.getString("maxAge", "PT2H")));
plotArea.getChildren().clear();
plotFrame = new JFreeChartFrame(plotFrameMeta);
PlotUtils.setXAxis(plotFrame, "timestamp", null, "time");
PlotContainer container = PlotContainer.anchorTo(plotArea);
container.setPlot(plotFrame);
}
private void setupChannels(PKT8Device device) {
Collection<PKT8Channel> channels = device.getChanels();
//plot config from device configuration
//Do not use view config here, it is applyed separately
channels.stream()
.filter(channel -> !plottables.has(channel.getName()))
.forEachOrdered(channel -> {
//plot config from device configuration
TimePlottable plottable = new TimePlottable(channel.getName());
plottable.configure(channel.meta());
plottables.add(plottable);
plotFrame.add(plottable);
});
if (device.meta().hasMeta("plotConfig")) {
plottables.applyConfig(device.meta().getMeta("plotConfig"));
plottables.setMaxItems(1000);
plottables.setPrefItems(400);
}
// getPlottables.applyConfig(plotFrame.getConfig());
}
@Override
public synchronized void onMeasurementResult(Measurement measurement, Object result, Instant time) {
PKT8Result res = PKT8Result.class.cast(result);
//PENDING replace by connection?
if (rawDataButton.isSelected()) {
plottables.put(res.getChannel(), res.getRawValue());
} else {
plottables.put(res.getChannel(), res.getTemperature());
}
}
@Override
public void onMeasurementFailed(Measurement measurement, Throwable exception) {
}
@Override
public Node getFXNode() {
return root;
}
}

View File

@ -1,150 +0,0 @@
package inr.numass.control.cryotemp;
import hep.dataforge.context.Context;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.MeasurementListener;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.exceptions.MeasurementException;
import hep.dataforge.fx.fragments.FragmentWindow;
import hep.dataforge.fx.fragments.LogFragment;
import inr.numass.control.DeviceViewConnection;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URL;
import java.time.Instant;
import java.util.Comparator;
import java.util.ResourceBundle;
/**
* Created by darksnake on 07-Oct-16.
*/
public class PKT8View extends DeviceViewConnection<PKT8Device> implements Initializable, MeasurementListener {
public static PKT8ViewConnection build(Context context) {
try {
FXMLLoader loader = new FXMLLoader(context.getClassLoader().getResource("fxml/PKT8Indicator.fxml"));
loader.setClassLoader(context.getClassLoader());
loader.load();
return loader.getController();
} catch (IOException e) {
throw new Error(e);
}
}
private LogFragment logFragment;
private PKT8PlotFragment plotFragment;
@FXML
private BorderPane root;
@FXML
private ToggleButton startStopButton;
@FXML
private ToggleButton storeButton;
@FXML
private ToggleButton consoleButton;
@FXML
private ToggleButton plotButton;
@FXML
private Label lastUpdateLabel;
@FXML
private TableView<PKT8Result> table;
@FXML
private TableColumn<TableView<PKT8Result>, String> sensorColumn;
@FXML
private TableColumn<TableView<PKT8Result>, Double> resColumn;
@FXML
private TableColumn<TableView<PKT8Result>, String> tempColumn;
@Override
public void initialize(URL location, ResourceBundle resources) {
sensorColumn.setCellValueFactory(new PropertyValueFactory<>("channel"));
resColumn.setCellValueFactory(new PropertyValueFactory<>("rawString"));
tempColumn.setCellValueFactory(new PropertyValueFactory<>("temperatureString"));
}
@Override
public void open(@NotNull PKT8Device device) throws Exception {
super.open(device);
this.logFragment = new LogFragment();
logFragment.addRootLogHandler();
new FragmentWindow(logFragment).bindTo(consoleButton);
plotFragment = new PKT8PlotFragment(device);
startStopButton.selectedProperty().setValue(getDevice().isMeasuring());
new FragmentWindow(plotFragment).bindTo(plotButton);
bindBooleanToState("storing", storeButton.selectedProperty());
}
@Override
public void close() throws Exception {
super.close();
logFragment = null;
plotFragment = null;
}
@Override
public void onMeasurementResult(Measurement<?> measurement, Object result, Instant time) {
PKT8Result res = PKT8Result.class.cast(result);
Platform.runLater(() -> {
lastUpdateLabel.setText(time.toString());
table.getItems().removeIf(it -> it.getChannel().equals(res.getChannel()));
table.getItems().add(res);
table.getItems().sort(Comparator.comparing(PKT8Result::getChannel));
});
}
@Override
public void onMeasurementFailed(Measurement measurement, Throwable exception) {
}
private void startMeasurement() throws MeasurementException {
getDevice().startMeasurement();
}
private void stopMeasurement() throws MeasurementException {
if (getDevice().isMeasuring()) {
getDevice().stopMeasurement(false);
}
}
@FXML
private void onStartStopClick(ActionEvent event) {
if (getDevice() != null) {
try {
if (startStopButton.isSelected()) {
startMeasurement();
} else {
//in case device started
stopMeasurement();
}
} catch (ControlException ex) {
getDevice().getLogger().error("Failed to start or stop device", ex);
}
}
}
@Override
public Node getFXNode() {
return root;
}
}

View File

@ -27,7 +27,7 @@ public class PKT8VirtualPort extends VirtualPort implements Metoid {
} }
@Override @Override
protected void evaluateRequest(String request) { protected synchronized void evaluateRequest(String request) {
switch (request) { switch (request) {
case "s": case "s":
String[] letters = {"a", "b", "c", "d", "e", "f", "g", "h"}; String[] letters = {"a", "b", "c", "d", "e", "f", "g", "h"};
@ -48,16 +48,15 @@ public class PKT8VirtualPort extends VirtualPort implements Metoid {
() -> { () -> {
double res = average + generator.nextGaussian() * sigma; double res = average + generator.nextGaussian() * sigma;
//TODO convert double value to formatted string //TODO convert double value to formatted string
return letter + "000120000\n"; return String.format("%s000%d", letter, (int) (res * 100));
}, },
Duration.ZERO, Duration.ofMillis(200), letter, "measurement" Duration.ZERO, Duration.ofMillis(500), letter, "measurement"
); );
} }
return; return;
case "p": case "p":
cancelByTag("measurement"); cancelByTag("measurement");
this.recievePhrase("stopped\n\r"); this.receivePhrase("Stopped");
return;
} }
} }

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package inr.numass.control.cryotemp
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication
import javafx.stage.Stage
/**
* @author darksnake
*/
class PKT8App : NumassControlApplication<PKT8Device>() {
override fun buildView(device: PKT8Device): DeviceViewConnection<PKT8Device> {
return PKT8ViewConnection().apply {
device.connect(this, Roles.VIEW_ROLE)
}
}
override val deviceFactory: DeviceFactory = PKT8DeviceFactory()
override fun setupStage(stage: Stage, device: PKT8Device) {
stage.title = "Numass temperature view " + device.name
stage.minHeight = 400.0
stage.minWidth = 400.0
}
override fun acceptDevice(meta: Meta): Boolean {
return meta.getString("type") == "PKT8"
}
}

View File

@ -13,11 +13,13 @@ import hep.dataforge.plots.fx.FXPlotFrame
import hep.dataforge.plots.fx.PlotContainer import hep.dataforge.plots.fx.PlotContainer
import hep.dataforge.plots.jfreechart.JFreeChartFrame import hep.dataforge.plots.jfreechart.JFreeChartFrame
import inr.numass.control.DeviceViewConnection import inr.numass.control.DeviceViewConnection
import inr.numass.control.bindView
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.binding.ListBinding
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.collections.ListChangeListener import javafx.collections.MapChangeListener
import javafx.collections.transformation.SortedList import javafx.collections.ObservableList
import javafx.geometry.Orientation import javafx.geometry.Orientation
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.Parent import javafx.scene.Parent
@ -33,10 +35,11 @@ import java.time.Instant
* Created by darksnake on 30-May-17. * Created by darksnake on 30-May-17.
*/ */
class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListener { class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListener {
private val view by lazy { CryoView() } private val cryoView by lazy{ CryoView()}
internal val table = SortedList(FXCollections.observableArrayList<PKT8Result>()) { r1, r2 -> private val plotView by lazy { CryoPlotView()}
r1.channel.compareTo(r2.channel)
} internal val table = FXCollections.observableHashMap<String, PKT8Result>()
val lastUpdateProperty = SimpleObjectProperty<String>("NEVER") val lastUpdateProperty = SimpleObjectProperty<String>("NEVER")
@ -48,23 +51,21 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
} }
override fun getFXNode(): Node { override fun getFXNode(): Node {
return view.root; if (!isOpen) {
throw RuntimeException("Not connected!")
}
return cryoView.root;
} }
override fun onMeasurementFailed(measurement: Measurement<*>, exception: Throwable) { override fun onMeasurementFailed(measurement: Measurement<*>, exception: Throwable) {
throw exception;
} }
override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant) { override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant) {
if (result is PKT8Result) { if (result is PKT8Result) {
Platform.runLater { Platform.runLater {
lastUpdateProperty.set(time.toString()) lastUpdateProperty.set(time.toString())
val item = table.find { it.channel == result.channel }; table.put(result.channel, result);
if (item == null) {
table.add(result);
} else {
table[table.indexOf(item)] = result
}
} }
} }
} }
@ -74,9 +75,11 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
top { top {
toolbar { toolbar {
togglebutton("Measure") { togglebutton("Measure") {
isSelected = false
bindBooleanToState(Sensor.MEASURING_STATE, selectedProperty()) bindBooleanToState(Sensor.MEASURING_STATE, selectedProperty())
} }
togglebutton("Store") { togglebutton("Store") {
isSelected = false
bindBooleanToState("storing", selectedProperty()) bindBooleanToState("storing", selectedProperty())
} }
separator(Orientation.VERTICAL) separator(Orientation.VERTICAL)
@ -85,9 +88,11 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
} }
separator(Orientation.VERTICAL) separator(Orientation.VERTICAL)
togglebutton("Plot") { togglebutton("Plot") {
FragmentWindow(CryoPlotView().root).bindTo(this) isSelected = false
bindView(plotView)
} }
togglebutton("Log") { togglebutton("Log") {
isSelected = false
FragmentWindow(LogFragment().apply { FragmentWindow(LogFragment().apply {
addLogHandler(device.logger) addLogHandler(device.logger)
}).bindTo(this) }).bindTo(this)
@ -95,12 +100,24 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
} }
} }
center { center {
tableview(table) { tableview<PKT8Result> {
items = object : ListBinding<PKT8Result>() {
init {
bind(table)
}
override fun computeValue(): ObservableList<PKT8Result> {
return FXCollections.observableArrayList(table.values).apply {
sortBy { it.channel }
}
}
}
column("Sensor", PKT8Result::channel); column("Sensor", PKT8Result::channel);
column("Resistance", PKT8Result::rawValue).cellFormat { column("Resistance", PKT8Result::rawValue).cellFormat {
text = String.format("%.2f", it) text = String.format("%.2f", it)
} }
column("Resistance", PKT8Result::temperature).cellFormat { column("Temperature", PKT8Result::temperature).cellFormat {
text = String.format("%.2f", it) text = String.format("%.2f", it)
} }
} }
@ -116,7 +133,7 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
} }
} }
inner class CryoPlotView : View() { inner class CryoPlotView : View("PKT8 temperature plot") {
val plotFrameMeta: Meta = device.meta.getMetaOrEmpty("plotConfig") val plotFrameMeta: Meta = device.meta.getMetaOrEmpty("plotConfig")
val plotFrame: FXPlotFrame by lazy { val plotFrame: FXPlotFrame by lazy {
@ -130,27 +147,24 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
val plottables: TimePlottableGroup by lazy { val plottables: TimePlottableGroup by lazy {
TimePlottableGroup().apply { TimePlottableGroup().apply {
setMaxAge(Duration.parse(plotFrameMeta.getString("maxAge", "PT2H"))) setMaxAge(Duration.parse(plotFrameMeta.getString("maxAge", "PT2H")))
table.addListener(ListChangeListener { change ->
while (change.next()) {
change.addedSubList.forEach {
if (rawDataButton.isSelected()) {
plottables.put(it.channel, it.rawValue)
} else {
plottables.put(it.channel, it.temperature)
}
}
}
})
} }
} }
override val root: Parent = borderpane { override val root: Parent = borderpane {
prefWidth = 800.0
prefHeight = 600.0
PlotContainer.centerIn(this).plot = plotFrame PlotContainer.centerIn(this).plot = plotFrame
bottom { top {
rawDataButton = togglebutton("Raw data") { toolbar {
action { rawDataButton = togglebutton("Raw data") {
plottables.forEach { isSelected = false
it.clear() action {
clearPlot()
}
}
button("Reset") {
action {
clearPlot()
} }
} }
} }
@ -158,7 +172,7 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
} }
init { init {
val channels = device.chanels val channels = device.chanels
//plot config from device configuration //plot config from device configuration
//Do not use view config here, it is applyed separately //Do not use view config here, it is applyed separately
@ -176,6 +190,23 @@ class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListen
plottables.setMaxItems(1000) plottables.setMaxItems(1000)
plottables.setPrefItems(400) plottables.setPrefItems(400)
} }
table.addListener(MapChangeListener { change ->
if (change.wasAdded()) {
change.valueAdded.apply {
if (rawDataButton.isSelected()) {
plottables.put(this.channel, this.rawValue)
} else {
plottables.put(this.channel, this.temperature)
}
}
}
})
}
fun clearPlot() {
plottables.forEach {
it.clear()
}
} }
} }
} }

View File

@ -18,10 +18,11 @@ package inr.numass.control.magnet;
import hep.dataforge.control.ports.VirtualPort; import hep.dataforge.control.ports.VirtualPort;
import hep.dataforge.exceptions.PortException; import hep.dataforge.exceptions.PortException;
import hep.dataforge.meta.Meta; import hep.dataforge.meta.Meta;
import org.slf4j.LoggerFactory;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.slf4j.LoggerFactory;
/** /**
* *
@ -69,7 +70,7 @@ public class VirtualLambdaPort extends VirtualPort {
evaluateRequest(comand.trim(), value.trim()); evaluateRequest(comand.trim(), value.trim());
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
recievePhrase("FAIL");//TODO какая команда правильная? receivePhrase("FAIL");//TODO какая команда правильная?
LoggerFactory.getLogger(getClass()).error("Request evaluation failure", ex); LoggerFactory.getLogger(getClass()).error("Request evaluation failure", ex);
} }

View File

@ -1,9 +1,12 @@
package inr.numass.control package inr.numass.control
import hep.dataforge.fx.fragments.FXFragment
import hep.dataforge.fx.fragments.FragmentWindow
import hep.dataforge.values.Value import hep.dataforge.values.Value
import javafx.beans.value.ObservableValue import javafx.beans.value.ObservableValue
import javafx.event.EventTarget import javafx.event.EventTarget
import javafx.geometry.Orientation import javafx.geometry.Orientation
import javafx.scene.control.ToggleButton
import javafx.scene.paint.Color import javafx.scene.paint.Color
import javafx.scene.paint.Paint import javafx.scene.paint.Paint
import javafx.scene.shape.Circle import javafx.scene.shape.Circle
@ -96,3 +99,9 @@ fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state:
separator(Orientation.VERTICAL) separator(Orientation.VERTICAL)
} }
} }
fun ToggleButton.bindView(view: View) {
//TODO use view instead of FXFragment
FragmentWindow(FXFragment.buildFromNode(view.title) { view.root }).bindTo(this)
}

View File

@ -67,7 +67,7 @@ abstract class NumassControlApplication<D : Device> : App() {
try { try {
@Suppress("UNCHECKED_CAST")
val d = deviceFactory.build(ctx, deviceConfig) as D val d = deviceFactory.build(ctx, deviceConfig) as D
d.init() d.init()
connectStorage(d, config) connectStorage(d, config)
@ -80,9 +80,14 @@ abstract class NumassControlApplication<D : Device> : App() {
} }
override fun stop() { override fun stop() {
super.stop() try {
device.shutdown() device.shutdown()
device.context.close() } catch (ex: Exception) {
LoggerFactory.getLogger(javaClass).error("Failed to shutdown application", ex);
} finally {
device.context.close()
super.stop()
}
} }

View File

@ -31,7 +31,7 @@ class JFCTest : View("My View") {
override val root = borderpane { override val root = borderpane {
center { center {
container.plot = plot container.plot = plot
add(container.root) add(container.pane)
} }
bottom { bottom {
add(button) add(button)