Control to Kotlin!

This commit is contained in:
Alexander Nozik 2017-05-31 09:53:21 +03:00
parent abb80ba1aa
commit c160fcdea4
27 changed files with 603 additions and 469 deletions

View File

@ -1,11 +1,35 @@
buildscript {
ext.kotlin_version = '1.1.2-2'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects{
apply plugin: "kotlin"
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
javaParameters = true
}
}
}
dependencies {
compile project(':numass-client')
compile "hep.dataforge:plots-jfc" // project(':dataforge-plots:plots-jfc')
compile "hep.dataforge:dataforge-control" //project(':dataforge-control')
compile "hep.dataforge:kodex"
// https://mvnrepository.com/artifact/org.controlsfx/controlsfx
compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.12'
//graphics
compile 'org.controlsfx:controlsfx:8.40.12'
compile "no.tornado:tornadofx:1.7.4"
}
task installAll(type: Copy) {

View File

@ -1,9 +1,9 @@
plugins{
id "org.jetbrains.kotlin.jvm" version '1.1.2-2'
id "application"
id 'com.github.johnrengelman.shadow' version '2.0.0'
}
if (!hasProperty('mainClass')) {
ext.mainClass = 'inr.numass.control.ServerApp'//"inr.numass.viewer.test.TestApp"
}
@ -21,11 +21,6 @@ configurations{
}
dependencies {
//graphics
compile 'org.controlsfx:controlsfx:8.40.12'
compile "no.tornado:tornadofx:1.7.4"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8"
//DataForge dependencies
compile project(':numass-control')
compile project(':numass-server')

View File

@ -1,12 +1,6 @@
package inr.numass.control
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.devices.Sensor
import hep.dataforge.fx.fragments.FXFragment
import hep.dataforge.fx.fragments.FragmentWindow
import hep.dataforge.storage.filestorage.FileStorage
import inr.numass.control.NumassControlUtils.getDFIcon
import javafx.geometry.Orientation
import javafx.geometry.Pos
import javafx.scene.control.Hyperlink
@ -17,7 +11,7 @@ import tornadofx.*
/**
* Created by darksnake on 11-May-17.
*/
class BoardView : View("Numass control board", ImageView(getDFIcon())) {
class BoardView : View("Numass control board", ImageView(dfIcon)) {
private val controller: BoardController by inject();
override val root = borderpane {
@ -87,23 +81,11 @@ class BoardView : View("Numass control board", ImageView(getDFIcon())) {
vbox {
prefHeight = 40.0
bindChildren(controller.devices) { connection ->
titledpane(title = "Device: " + connection.device.name, collapsible = true) {
hbox {
alignment = Pos.CENTER_LEFT
vgrow = Priority.ALWAYS;
deviceStateIndicator(connection, Device.INITIALIZED_STATE)
deviceStateIndicator(connection, PortSensor.CONNECTED_STATE)
deviceStateIndicator(connection, Sensor.MEASURING_STATE)
deviceStateIndicator(connection, "storing")
pane {
hgrow = Priority.ALWAYS
}
togglebutton("View") {
isSelected = false
FragmentWindow(FXFragment.buildFromNode(connection.device.name) { connection.fxNode }).bindTo(this)
}
}
}
titledpane(
title = "Device: " + connection.device.name,
collapsible = true,
node = connection.getBoardView()
)
}
}
}

View File

@ -29,7 +29,7 @@ import java.util.Objects;
public class PKT8App extends NumassControlApplication<PKT8Device> {
@Override
protected DeviceViewConnection<PKT8Device> buildView(PKT8Device device) {
return PKT8View.build(device.getContext());
return PKT8ViewConnection.build(device.getContext());
}
@Override

View File

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

View File

@ -125,9 +125,9 @@ public class PKT8PlotView extends DeviceViewConnection<PKT8Device> implements In
PKT8Result res = PKT8Result.class.cast(result);
//PENDING replace by connection?
if (rawDataButton.isSelected()) {
plottables.put(res.channel, res.rawValue);
plottables.put(res.getChannel(), res.getRawValue());
} else {
plottables.put(res.channel, res.temperature);
plottables.put(res.getChannel(), res.getTemperature());
}
}

View File

@ -33,7 +33,7 @@ import java.util.ResourceBundle;
*/
public class PKT8View extends DeviceViewConnection<PKT8Device> implements Initializable, MeasurementListener {
public static PKT8View build(Context context) {
public static PKT8ViewConnection build(Context context) {
try {
FXMLLoader loader = new FXMLLoader(context.getClassLoader().getResource("fxml/PKT8Indicator.fxml"));
loader.setClassLoader(context.getClassLoader());
@ -104,9 +104,9 @@ public class PKT8View extends DeviceViewConnection<PKT8Device> implements Initia
PKT8Result res = PKT8Result.class.cast(result);
Platform.runLater(() -> {
lastUpdateLabel.setText(time.toString());
table.getItems().removeIf(it -> it.channel.equals(res.channel));
table.getItems().removeIf(it -> it.getChannel().equals(res.getChannel()));
table.getItems().add(res);
table.getItems().sort(Comparator.comparing(o -> o.channel));
table.getItems().sort(Comparator.comparing(PKT8Result::getChannel));
});
}

View File

@ -14,32 +14,14 @@
* limitations under the License.
*/
package inr.numass.control.cryotemp;
package inr.numass.control.cryotemp
/**
* Created by darksnake on 28-Sep-16.
*/
public class PKT8Result {
data class PKT8Result(var channel: String, var rawValue: Double, var temperature: Double) {
public String channel;
public double rawValue;
public double temperature;
val rawString: String = String.format("%.2f", rawValue)
public PKT8Result(String channel, double rawValue, double temperature) {
this.channel = channel;
this.rawValue = rawValue;
this.temperature = temperature;
}
public String getChannel() {
return channel;
}
public String getRawString() {
return String.format("%.2f", rawValue);
}
public String getTemperatureString() {
return String.format("%.2f", temperature);
}
val temperatureString: String = String.format("%.2f", temperature)
}

View File

@ -0,0 +1,182 @@
package inr.numass.control.cryotemp
import hep.dataforge.control.devices.Sensor
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.measurements.MeasurementListener
import hep.dataforge.fx.fragments.FragmentWindow
import hep.dataforge.fx.fragments.LogFragment
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.application.Platform
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections
import javafx.collections.ListChangeListener
import javafx.collections.transformation.SortedList
import javafx.geometry.Orientation
import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.control.ToggleButton
import javafx.scene.layout.Priority
import javafx.scene.layout.VBox
import javafx.scene.text.Font
import tornadofx.*
import java.time.Duration
import java.time.Instant
/**
* Created by darksnake on 30-May-17.
*/
class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListener {
private val view by lazy { CryoView() }
internal val table = SortedList(FXCollections.observableArrayList<PKT8Result>()) { r1, r2 ->
r1.channel.compareTo(r2.channel)
}
val lastUpdateProperty = SimpleObjectProperty<String>("NEVER")
override fun getBoardView(): Parent {
return VBox().apply {
this += super.getBoardView()
}
}
override fun getFXNode(): Node {
return view.root;
}
override fun onMeasurementFailed(measurement: Measurement<*>, exception: Throwable) {
throw exception;
}
override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant) {
if (result is PKT8Result) {
Platform.runLater {
lastUpdateProperty.set(time.toString())
val item = table.find { it.channel == result.channel };
if (item == null) {
table.add(result);
} else {
table[table.indexOf(item)] = result
}
}
}
}
inner class CryoView() : View() {
override val root = borderpane {
top {
toolbar {
togglebutton("Measure") {
bindBooleanToState(Sensor.MEASURING_STATE, selectedProperty())
}
togglebutton("Store") {
bindBooleanToState("storing", selectedProperty())
}
separator(Orientation.VERTICAL)
pane {
hgrow = Priority.ALWAYS
}
separator(Orientation.VERTICAL)
togglebutton("Plot") {
FragmentWindow(CryoPlotView().root).bindTo(this)
}
togglebutton("Log") {
FragmentWindow(LogFragment().apply {
addLogHandler(device.logger)
}).bindTo(this)
}
}
}
center {
tableview(table) {
column("Sensor", PKT8Result::channel);
column("Resistance", PKT8Result::rawValue).cellFormat {
text = String.format("%.2f", it)
}
column("Resistance", PKT8Result::temperature).cellFormat {
text = String.format("%.2f", it)
}
}
}
bottom {
toolbar {
label("Last update: ")
label(lastUpdateProperty) {
font = Font.font("System Bold")
}
}
}
}
}
inner class CryoPlotView : View() {
val plotFrameMeta: Meta = device.meta.getMetaOrEmpty("plotConfig")
val plotFrame: FXPlotFrame by lazy {
JFreeChartFrame(plotFrameMeta).apply {
PlotUtils.setXAxis(this, "timestamp", null, "time")
}
}
var rawDataButton: ToggleButton by singleAssign()
val plottables: TimePlottableGroup by lazy {
TimePlottableGroup().apply {
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 {
PlotContainer.centerIn(this).plot = plotFrame
bottom {
rawDataButton = togglebutton("Raw data") {
action {
plottables.forEach {
it.clear()
}
}
}
}
}
init {
val channels = device.chanels
//plot config from device configuration
//Do not use view config here, it is applyed separately
channels.stream()
.filter { channel -> !plottables.has(channel.name) }
.forEachOrdered { channel ->
//plot config from device configuration
val plottable = TimePlottable(channel.name)
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)
}
}
}
}

View File

@ -4,7 +4,7 @@
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<BorderPane fx:id="root" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="400.0"
xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8View">
xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8ViewConnection">
<center>
<TableView fx:id="table" BorderPane.alignment="CENTER">
<columns>
@ -31,14 +31,12 @@
</top>
<bottom>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<Label text="Last update: "/>
<Label fx:id="lastUpdateLabel" text="NONE">
<font>
<Font name="System Bold" size="12.0"/>
</font>
</Label>
</items>
<Label text="Last update: "/>
<Label fx:id="lastUpdateLabel" text="NONE">
<font>
<Font name="System Bold" size="12.0"/>
</font>
</Label>
</ToolBar>
</bottom>
</BorderPane>

View File

@ -27,12 +27,10 @@ limitations under the License.
</center>
<top>
<ToolBar BorderPane.alignment="CENTER">
<items>
<ToggleButton fx:id="rawDataButton" mnemonicParsing="false" text="Raw data"/>
<Separator orientation="VERTICAL"/>
<Pane HBox.hgrow="ALWAYS"/>
<Separator orientation="VERTICAL"/>
</items>
<ToggleButton fx:id="rawDataButton" mnemonicParsing="false" text="Raw data"/>
<Separator orientation="VERTICAL"/>
<Pane HBox.hgrow="ALWAYS"/>
<Separator orientation="VERTICAL"/>
</ToolBar>
</top>
</BorderPane>

View File

@ -49,7 +49,7 @@ public class MagnetControllerApp extends Application {
List<SafeMagnetController> controllers = new ArrayList<>();
@Override
public void start(Stage primaryStage) throws IOException, ControlException {
public void start(Stage stage) throws IOException, ControlException {
Locale.setDefault(Locale.US);// чтобы отделение десятичных знаков было точкой
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
@ -108,10 +108,10 @@ public class MagnetControllerApp extends Application {
Scene scene = new Scene(vbox, width, height);
primaryStage.setTitle("Numass magnet view");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
stage.setTitle("Numass magnet view");
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
@Override

View File

@ -1,31 +0,0 @@
package inr.numass.control;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.DeviceListener;
import hep.dataforge.fx.fragments.FXFragment;
import javafx.scene.Parent;
/**
* Created by darksnake on 20-Oct-16.
*/
public abstract class DeviceFragment<T extends Device> extends FXFragment implements DeviceListener {
private final T device;
protected DeviceFragment(T device) {
this.device = device;
}
@Override
protected Parent buildRoot() {
return buildRoot(device);
}
protected abstract Parent buildRoot(T device);
@Override
public void evaluateDeviceException(Device device, String message, Throwable exception) {
//do something pretty
}
}

View File

@ -1,72 +0,0 @@
package inr.numass.control;
import hep.dataforge.control.connections.DeviceConnection;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.DeviceListener;
import hep.dataforge.fx.FXObject;
import hep.dataforge.values.Value;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import java.util.HashMap;
import java.util.Map;
/**
* Created by darksnake on 14-May-17.
*/
public abstract class DeviceViewConnection<D extends Device> extends DeviceConnection<D> implements DeviceListener, FXObject {
private Map<String, ObjectBinding<Value>> bindings = new HashMap<>();
/**
* Get binding for a given device state
*
* @param state
* @return
*/
public ObjectBinding<Value> getStateBinding(String state) {
return bindings.computeIfAbsent(state, stateName ->
new ObjectBinding<Value>() {
@Override
protected Value computeValue() {
if(isOpen()) {
return getDevice().getState(stateName);
} else {
return Value.NULL;
}
}
}
);
}
/**
* Bind existing boolean property to writable device state
*
* @param state
* @param property
*/
protected void bindBooleanToState(String state, BooleanProperty property) {
getStateBinding(state).addListener((observable, oldValue, newValue) -> {
if (isOpen() && oldValue != newValue) {
property.setValue(newValue.booleanValue());
}
});
property.addListener((observable, oldValue, newValue) -> {
if (isOpen() && oldValue != newValue) {
getDevice().setState(state, newValue);
}
});
}
@Override
public void notifyDeviceStateChanged(Device device, String name, Value state) {
if (bindings.containsKey(name)) {
bindings.get(name).invalidate();
}
}
// /**
// * The small view for
// * @return
// */
// public abstract Optional<Parent> getBoardView();
}

View File

@ -1,12 +0,0 @@
package inr.numass.control;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.DeviceFactory;
public interface DeviceViewFactory extends DeviceFactory {
/**
* Create but do not connect view connection for the device
* @return
*/
DeviceViewConnection buildView(Device device);
}

View File

@ -1,94 +0,0 @@
package inr.numass.control;
import ch.qos.logback.classic.Level;
import hep.dataforge.context.Context;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.DeviceFactory;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.slf4j.LoggerFactory;
import java.util.Locale;
/**
* Created by darksnake on 14-May-17.
*/
public abstract class NumassControlApplication<D extends Device> extends Application {
private D device;
@Override
public void start(Stage primaryStage) throws Exception {
Locale.setDefault(Locale.US);// чтобы отделение десятичных знаков было точкой
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.INFO);
device = setupDevice();
DeviceViewConnection<D> controller = buildView(device);
Scene scene = new Scene(controller.getPane());
primaryStage.setScene(scene);
Platform.runLater(() -> {
device.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE);
});
primaryStage.show();
setupStage(primaryStage, device);
NumassControlUtils.setDFStageIcon(primaryStage);
}
/**
* Build a view connection
*
* @return
*/
protected abstract DeviceViewConnection<D> buildView(D device);
/**
* Get a device factory for given device
*
* @return
*/
protected abstract DeviceFactory getDeviceFactory();
protected abstract void setupStage(Stage stage, D device);
protected abstract boolean acceptDevice(Meta meta);
private D setupDevice() {
Meta config = NumassControlUtils.getConfig(this)
.orElseGet(() -> NumassControlUtils.readResourceMeta("/config/devices.xml"));
Context ctx = NumassControlUtils.setupContext(config);
Meta deviceConfig = NumassControlUtils.findDeviceMeta(config, this::acceptDevice)
.orElseThrow(() -> new RuntimeException("Device configuration not found"));
try {
@SuppressWarnings("unchecked") D d = (D) getDeviceFactory().build(ctx, deviceConfig);
d.init();
NumassControlUtils.connectStorage(d, config);
return d;
} catch (ControlException e) {
throw new RuntimeException("Failed to build device", e);
}
}
@Override
public void stop() throws Exception {
super.stop();
if (device != null) {
device.shutdown();
device.getContext().close();
}
}
}

View File

@ -1,115 +0,0 @@
package inr.numass.control;
import hep.dataforge.context.Context;
import hep.dataforge.context.Global;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.Device;
import hep.dataforge.exceptions.StorageException;
import hep.dataforge.io.MetaFileReader;
import hep.dataforge.io.XMLMetaReader;
import hep.dataforge.meta.Meta;
import hep.dataforge.storage.api.Storage;
import hep.dataforge.storage.commons.StorageFactory;
import hep.dataforge.storage.commons.StorageManager;
import inr.numass.client.ClientUtils;
import javafx.application.Application;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.Optional;
import java.util.function.Predicate;
/**
* Created by darksnake on 08-May-17.
*/
public class NumassControlUtils {
public static final String DEFAULT_CONFIG_LOCATION = "./numass-control.xml";
/**
* Create a single or multiple storage connections for a device
*
* @param device
* @param config
*/
public static void connectStorage(Device device, Meta config) {
//TODO add on reset listener
if (config.hasMeta("storage") && device.acceptsRole(Roles.STORAGE_ROLE)) {
String numassRun = ClientUtils.getRunName(config);
config.getMetaList("storage").forEach(node -> {
device.getContext().getLogger().info("Creating storage for device with meta: {}", node);
//building storage in a separate thread
new Thread(() -> {
Storage storage = StorageFactory.buildStorage(device.getContext(), node);
if (!numassRun.isEmpty()) {
try {
storage = storage.buildShelf(numassRun, Meta.empty());
} catch (StorageException e) {
device.getContext().getLogger().error("Failed to build shelf", e);
}
}
device.connect(new StorageConnection(storage), Roles.STORAGE_ROLE);
}).start();
});
}
}
public static Meta readResourceMeta(String path) {
try {
return new XMLMetaReader().read(NumassControlUtils.class.getResourceAsStream(path));
} catch (IOException | ParseException e) {
throw new RuntimeException(e);
}
}
public static Optional<Meta> getConfig(Application app) {
String debugConfig = app.getParameters().getNamed().get("config.resource");
if (debugConfig != null) {
return Optional.ofNullable(readResourceMeta(debugConfig));
}
String configFileName = app.getParameters().getNamed().get("config");
Logger logger = LoggerFactory.getLogger(app.getClass());
if (configFileName == null) {
logger.info("Configuration path not defined. Loading configuration from {}", DEFAULT_CONFIG_LOCATION);
configFileName = DEFAULT_CONFIG_LOCATION;
}
File configFile = new File(configFileName);
if (configFile.exists()) {
try {
Meta config = MetaFileReader.read(configFile).build();
return Optional.of(config);
} catch (IOException | ParseException e) {
throw new RuntimeException(e);
}
} else {
logger.warn("Configuration file not found");
return Optional.empty();
}
}
public static Optional<Meta> findDeviceMeta(Meta config, Predicate<Meta> criterion) {
return config.getMetaList("device").stream().filter(criterion).findFirst().map(it -> it);
}
public static Context setupContext(Meta meta) {
Context ctx = Global.getContext("NUMASS-CONTROL");
ctx.pluginManager().getOrLoad(StorageManager.class);
return ctx;
}
public static void setDFStageIcon(Stage stage) {
stage.getIcons().add(getDFIcon());
}
public static Image getDFIcon(){
return new Image(NumassControlUtils.class.getResourceAsStream("/img/df.png"));
}
}

View File

@ -1,51 +0,0 @@
package inr.numass.control;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.AbstractDevice;
import hep.dataforge.exceptions.StorageException;
import hep.dataforge.storage.api.PointLoader;
import hep.dataforge.tables.DataPoint;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* A helper to store points in multiple loaders
* Created by darksnake on 16-May-17.
*/
public class StorageHelper implements AutoCloseable {
private final AbstractDevice device;
private final Map<StorageConnection, PointLoader> loaderMap = new HashMap<>();
private final Function<StorageConnection, PointLoader> loaderFactory;
public StorageHelper(AbstractDevice device, Function<StorageConnection, PointLoader> loaderFactory) {
this.device = device;
this.loaderFactory = loaderFactory;
}
public void push(DataPoint point) {
if (!device.hasState("storing") || device.getState("storing").booleanValue()) {
device.forEachConnection("storage", StorageConnection.class, connection -> {
PointLoader pl = loaderMap.computeIfAbsent(connection, loaderFactory);
try {
pl.push(point);
} catch (StorageException ex) {
device.getLogger().error("Push to loader failed", ex);
}
});
}
}
@Override
public void close() {
loaderMap.values().forEach(it -> {
try {
it.close();
} catch (Exception ex) {
device.getLogger().error("Failed to close Loader", ex);
}
});
}
}

View File

@ -0,0 +1,89 @@
package inr.numass.control
import hep.dataforge.control.connections.DeviceConnection
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceListener
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.devices.Sensor
import hep.dataforge.fx.FXObject
import hep.dataforge.fx.fragments.FXFragment
import hep.dataforge.fx.fragments.FragmentWindow
import hep.dataforge.values.Value
import javafx.beans.binding.ObjectBinding
import javafx.beans.property.BooleanProperty
import javafx.geometry.Pos
import javafx.scene.Parent
import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import tornadofx.*
import java.util.*
/**
* Created by darksnake on 14-May-17.
*/
abstract class DeviceViewConnection<D : Device> : DeviceConnection<D>(), DeviceListener, FXObject {
private val bindings = HashMap<String, ObjectBinding<Value>>()
/**
* Get binding for a given device state
* @param state
* *
* @return
*/
fun getStateBinding(state: String): ObjectBinding<Value> {
return bindings.computeIfAbsent(state) { stateName ->
object : ObjectBinding<Value>() {
override fun computeValue(): Value {
if (isOpen) {
return device.getState(stateName)
} else {
return Value.NULL
}
}
}
}
}
/**
* Bind existing boolean property to writable device state
* @param state
* *
* @param property
*/
protected fun bindBooleanToState(state: String, property: BooleanProperty) {
getStateBinding(state).addListener { observable, oldValue, newValue ->
if (isOpen && oldValue !== newValue) {
property.value = newValue.booleanValue()
}
}
property.addListener { observable, oldValue, newValue ->
if (isOpen && oldValue != newValue) {
device.setState(state, newValue)
}
}
}
override fun notifyDeviceStateChanged(device: Device, name: String, state: Value) {
bindings[name]?.invalidate()
}
open fun getBoardView(): Parent {
return HBox().apply {
alignment = Pos.CENTER_LEFT
vgrow = Priority.ALWAYS;
deviceStateIndicator(this@DeviceViewConnection, Device.INITIALIZED_STATE)
deviceStateIndicator(this@DeviceViewConnection, PortSensor.CONNECTED_STATE)
deviceStateIndicator(this@DeviceViewConnection, Sensor.MEASURING_STATE)
deviceStateIndicator(this@DeviceViewConnection, "storing")
pane {
hgrow = Priority.ALWAYS
}
togglebutton("View") {
isSelected = false
FragmentWindow(FXFragment.buildFromNode(device.name) { fxNode }).bindTo(this)
}
}
}
}

View File

@ -0,0 +1,12 @@
package inr.numass.control
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
interface DeviceViewFactory : DeviceFactory {
/**
* Create but do not connect view connection for the device
* @return
*/
fun buildView(device: Device): DeviceViewConnection<*>
}

View File

@ -0,0 +1,89 @@
package inr.numass.control
import ch.qos.logback.classic.Level
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import javafx.application.Platform
import javafx.scene.Scene
import javafx.stage.Stage
import org.slf4j.LoggerFactory
import tornadofx.*
import java.util.*
import java.util.function.Predicate
/**
* Created by darksnake on 14-May-17.
*/
abstract class NumassControlApplication<D : Device> : App() {
private var device: D by singleAssign()
override fun start(stage: Stage) {
Locale.setDefault(Locale.US)// чтобы отделение десятичных знаков было точкой
val rootLogger = LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME) as ch.qos.logback.classic.Logger
rootLogger.level = Level.INFO
device = setupDevice()
val controller = buildView(device)
val scene = Scene(controller.pane)
stage.scene = scene
Platform.runLater { device.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) }
stage.show()
setupStage(stage, device)
setDFStageIcon(stage)
}
/**
* Build a view connection
* @return
*/
protected abstract fun buildView(device: D): DeviceViewConnection<D>
/**
* Get a device factory for given device
* @return
*/
protected abstract val deviceFactory: DeviceFactory
protected abstract fun setupStage(stage: Stage, device: D)
protected abstract fun acceptDevice(meta: Meta): Boolean
private fun setupDevice(): D {
val config = getConfig(this)
.orElseGet { readResourceMeta("/config/devices.xml") }
val ctx = setupContext(config)
val deviceConfig = findDeviceMeta(config, Predicate<Meta> { this.acceptDevice(it) })
.orElseThrow { RuntimeException("Device configuration not found") }
try {
val d = deviceFactory.build(ctx, deviceConfig) as D
d.init()
connectStorage(d, config)
return d
} catch (e: ControlException) {
throw RuntimeException("Failed to build device", e)
}
}
override fun stop() {
super.stop()
device.shutdown()
device.context.close()
}
}

View File

@ -0,0 +1,117 @@
package inr.numass.control
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.control.devices.Device
import hep.dataforge.exceptions.StorageException
import hep.dataforge.io.MetaFileReader
import hep.dataforge.io.XMLMetaReader
import hep.dataforge.meta.Meta
import hep.dataforge.storage.commons.StorageFactory
import hep.dataforge.storage.commons.StorageManager
import inr.numass.client.ClientUtils
import javafx.application.Application
import javafx.scene.image.Image
import javafx.stage.Stage
import org.slf4j.LoggerFactory
import java.io.File
import java.io.IOException
import java.text.ParseException
import java.util.*
import java.util.function.Predicate
/**
* Created by darksnake on 08-May-17.
*/
val DEFAULT_CONFIG_LOCATION = "./numass-control.xml"
val dfIcon: Image = Image(Global::class.java.getResourceAsStream("/img/df.png"))
/**
* Create a single or multiple storage connections for a device
* @param device
* *
* @param config
*/
fun connectStorage(device: Device, config: Meta) {
//TODO add on reset listener
if (config.hasMeta("storage") && device.acceptsRole(Roles.STORAGE_ROLE)) {
val numassRun = ClientUtils.getRunName(config)
config.getMetaList("storage").forEach { node ->
device.context.logger.info("Creating storage for device with meta: {}", node)
//building storage in a separate thread
Thread {
var storage = StorageFactory.buildStorage(device.context, node)
if (!numassRun.isEmpty()) {
try {
storage = storage.buildShelf(numassRun, Meta.empty())
} catch (e: StorageException) {
device.context.logger.error("Failed to build shelf", e)
}
}
device.connect(StorageConnection(storage), Roles.STORAGE_ROLE)
}.start()
}
}
}
fun readResourceMeta(path: String): Meta {
try {
return XMLMetaReader().read(Global::class.java.getResourceAsStream(path))
} catch (e: IOException) {
throw RuntimeException(e)
} catch (e: ParseException) {
throw RuntimeException(e)
}
}
fun getConfig(app: Application): Optional<Meta> {
val debugConfig = app.parameters.named["config.resource"]
if (debugConfig != null) {
return Optional.ofNullable(readResourceMeta(debugConfig))
}
var configFileName: String? = app.parameters.named["config"]
val logger = LoggerFactory.getLogger(app.javaClass)
if (configFileName == null) {
logger.info("Configuration path not defined. Loading configuration from {}", DEFAULT_CONFIG_LOCATION)
configFileName = DEFAULT_CONFIG_LOCATION
}
val configFile = File(configFileName)
if (configFile.exists()) {
try {
val config = MetaFileReader.read(configFile).build()
return Optional.of(config)
} catch (e: IOException) {
throw RuntimeException(e)
} catch (e: ParseException) {
throw RuntimeException(e)
}
} else {
logger.warn("Configuration file not found")
return Optional.empty<Meta>()
}
}
fun findDeviceMeta(config: Meta, criterion: Predicate<Meta>): Optional<Meta> {
return config.getMetaList("device").stream().filter(criterion).findFirst().map { it -> it }
}
fun setupContext(meta: Meta): Context {
val ctx = Global.getContext("NUMASS-CONTROL")
ctx.pluginManager().getOrLoad(StorageManager::class.java)
return ctx
}
fun setDFStageIcon(stage: Stage) {
stage.icons.add(dfIcon)
}

View File

@ -0,0 +1,41 @@
package inr.numass.control
import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.control.devices.AbstractDevice
import hep.dataforge.exceptions.StorageException
import hep.dataforge.storage.api.PointLoader
import hep.dataforge.tables.DataPoint
import java.util.*
import java.util.function.Function
/**
* A helper to store points in multiple loaders
* Created by darksnake on 16-May-17.
*/
class StorageHelper(private val device: AbstractDevice, private val loaderFactory: Function<StorageConnection, PointLoader>) : AutoCloseable {
private val loaderMap = HashMap<StorageConnection, PointLoader>()
fun push(point: DataPoint) {
if (!device.hasState("storing") || device.getState("storing").booleanValue()) {
device.forEachConnection("storage", StorageConnection::class.java) { connection ->
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
try {
pl.push(point)
} catch (ex: StorageException) {
device.logger.error("Push to loader failed", ex)
}
}
}
}
override fun close() {
loaderMap.values.forEach { it ->
try {
it.close()
} catch (ex: Exception) {
device.logger.error("Failed to close Loader", ex)
}
}
}
}

View File

@ -25,7 +25,7 @@ public class TestVac extends Application {
VacCollectorView controller;
@Override
public void start(Stage primaryStage) {
public void start(Stage stage) {
try {
Sensor<Double> sensor1 = Virtual.randomDoubleSensor("vac1", Duration.ofMillis(200), 1e-5, 2e-6);
Sensor<Double> sensor2 = Virtual.randomDoubleSensor("vac2", Duration.ofMillis(200), 2e-5, 2e-6);
@ -65,9 +65,9 @@ public class TestVac extends Application {
Scene scene = new Scene(loader.getRoot(), 800, 600);
primaryStage.setTitle("Vacuum measurement test");
primaryStage.setScene(scene);
primaryStage.show();
stage.setTitle("Vacuum measurement test");
stage.setScene(scene);
stage.show();
} catch (Exception ex) {
throw new Error(ex);
}

View File

@ -67,7 +67,7 @@ public class TestModels {
double A = meta.getDouble("resolution", meta.getDouble("resolution.width", 8.3e-5));//8.3e-5
double from = meta.getDouble("from", 13900d);
double to = meta.getDouble("to", 18700d);
context.getLog().report("Setting up tritium model with real transmission function");
context.getChronicle().report("Setting up tritium model with real transmission function");
BivariateFunction resolutionTail;
if (meta.hasValue("resolution.tailAlpha")) {
resolutionTail = ResolutionFunction.getAngledTail(meta.getDouble("resolution.tailAlpha"), meta.getDouble("resolution.tailBeta", 0));
@ -78,7 +78,7 @@ public class TestModels {
RangedNamedSetSpectrum beta = new BetaSpectrum();
ModularSpectrum sp = new ModularSpectrum(beta, new ResolutionFunction(A, resolutionTail), from, to);
if (meta.getBoolean("caching", false)) {
context.getLog().report("Caching turned on");
context.getChronicle().report("Caching turned on");
sp.setCaching(true);
}
//Adding trapping energy dependence

View File

@ -31,7 +31,7 @@ import java.io.IOException;
public class TestDirectoryViewer extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
public void start(Stage stage) throws IOException {
new StorageManager().startGlobal();
NumassDataLoader reader = NumassDataLoader.fromLocalDir(null, new File("C:\\Users\\darksnake\\Dropbox\\PlayGround\\data-test\\20150703143643_1\\"));
@ -49,13 +49,13 @@ public class TestDirectoryViewer extends Application {
Scene scene = new Scene(comp.getRoot(), 800, 600);
primaryStage.setTitle("Detector Visualisation test");
primaryStage.setScene(scene);
primaryStage.setMinHeight(600);
primaryStage.setMinWidth(800);
stage.setTitle("Detector Visualisation test");
stage.setScene(scene);
stage.setMinHeight(600);
stage.setMinWidth(800);
// primaryStage.setResizable(false);
primaryStage.show();
stage.show();
}
/**