Server is now a context plugin

This commit is contained in:
Alexander Nozik 2017-05-13 21:46:12 +03:00
parent 10fc68461f
commit 7749dd31de
21 changed files with 329 additions and 78 deletions

View File

@ -0,0 +1,34 @@
plugins{
id "org.jetbrains.kotlin.jvm" version '1.1.2-2'
id "application"
}
repositories {
mavenCentral()
}
if (!hasProperty('mainClass')) {
ext.mainClass = 'inr.numass.viewer.Viewer'//"inr.numass.viewer.test.TestApp"
}
mainClassName = mainClass
version = "0.1.0"
description = "The control room application for numass slow control"
compileKotlin.kotlinOptions.jvmTarget = "1.8"
dependencies {
compile project(':numass-core')
compile project(':numass-control')
compile 'org.controlsfx:controlsfx:8.40.12'
compile "no.tornado:tornadofx:1.7.4"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:1.1.2-2"
}
apply plugin: 'kotlin'

View File

@ -0,0 +1,33 @@
package inr.numass.control
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.control.devices.Device
import hep.dataforge.meta.Meta
import hep.dataforge.storage.api.Storage
import hep.dataforge.storage.commons.StorageFactory
import inr.numass.client.ClientUtils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.scene.Node
import tornadofx.*
/**
* Created by darksnake on 12-May-17.
*/
class BoardController(val context: Context = Global.instance(), val meta: Meta) : Controller() {
val devices: ObservableList<Pair<Device, Node?>> = FXCollections.observableArrayList<Pair<Device, Node?>>();
val storage: Storage? by lazy {
if (meta.hasMeta("storage")) {
val numassRun = ClientUtils.getRunName(meta)
var storage = StorageFactory.buildStorage(context, meta.getMeta("storage"));
if(! numassRun.isEmpty()){
storage = storage.buildShelf(numassRun, Meta.empty());
}
return@lazy storage;
} else {
return@lazy null;
}
}
}

View File

@ -0,0 +1,22 @@
package inr.numass.control
import javafx.scene.layout.VBox
import tornadofx.*
/**
* Created by darksnake on 11-May-17.
*/
class BoardView : View("Numass control board") {
private var deviceList: VBox by singleAssign();
private val controller: BoardController by inject();
override val root = borderpane {
center {
deviceList = vbox {
bindChildren(controller.devices) { DeviceInfoView(it).root }
}
}
}
}

View File

@ -0,0 +1,46 @@
package inr.numass.control
import hep.dataforge.control.devices.Device
import hep.dataforge.fx.FXObject
import hep.dataforge.fx.fragments.FragmentWindow
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node
import javafx.scene.control.ToggleButton
import tornadofx.*
/**
* A simple device indicator board
* Created by darksnake on 11-May-17.
*/
class DeviceInfoView(val device: Device, node: Node? = null) : Fragment(device.name) {
constructor(pair: Pair<Device, Node?>) : this(pair.first, pair.second);
val deviceNode = SimpleObjectProperty<Node>();
var viewButton: ToggleButton by singleAssign();
override val root = hbox {
label(device.name)
add(Indicator.build(device, Device.INITIALIZED_STATE).fxNode)
viewButton = togglebutton("View") {
disableProperty().bind(deviceNode.isNull);
}
}
init {
FragmentWindow(hep.dataforge.fx.fragments.Fragment.buildFromNode(device.name) { deviceNode.get() })
if (node != null) {
deviceNode.set(node);
} else if (device is FXObject) {
deviceNode.set(device.fxNode)
}
}
fun setDeviceView(node: Node) {
deviceNode.set(node);
}
}

View File

@ -0,0 +1,74 @@
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.scene.Node
import javafx.scene.paint.Color
import javafx.scene.paint.Paint
import javafx.scene.shape.Circle
/**
* Lamp-like indicator
*
* TODO move to general kotlin FX utils
* Created by darksnake on 12-May-17.
*/
open class Indicator(val state: String) : FXObject, DeviceConnection<Device>(), DeviceListener {
private val color = object : ObjectBinding<Paint>() {
override fun computeValue(): Paint {
val value = device.getState(state);
return compute(value);
}
}
private val indicator = Circle();
init {
indicator.fillProperty().bind(color);
}
protected open fun compute(value: Value): Paint {
if (value.booleanValue()) {
return Color.GREEN;
} else {
return Color.GRAY;
}
}
override fun getFXNode(): Node {
return indicator;
}
override fun notifyDeviceStateChanged(device: Device?, name: String?, value: Value?) {
if (name == state) {
color.invalidate();
}
}
companion object {
/**
* Build an indicator
*/
fun build(device: Device, state: String): Indicator {
val indicator = Indicator(state);
device.connect(indicator);
return indicator;
}
/**
* Build an indicator with the custom color builder
*/
fun build(device: Device, state: String, func: (Value)-> Paint): Indicator {
val indicator = object:Indicator(state){
override fun compute(value: Value): Paint {
return func(value);
}
};
device.connect(indicator);
return indicator;
}
}
}

View File

@ -1,7 +1,6 @@
package inr.numass.cryotemp; package inr.numass.cryotemp;
import hep.dataforge.control.connections.DeviceConnection; import hep.dataforge.control.connections.DeviceConnection;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.DeviceListener; import hep.dataforge.control.devices.DeviceListener;
import hep.dataforge.control.measurements.Measurement; import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.MeasurementListener; import hep.dataforge.control.measurements.MeasurementListener;
@ -9,7 +8,6 @@ import hep.dataforge.exceptions.ControlException;
import hep.dataforge.exceptions.MeasurementException; import hep.dataforge.exceptions.MeasurementException;
import hep.dataforge.fx.fragments.FragmentWindow; import hep.dataforge.fx.fragments.FragmentWindow;
import hep.dataforge.fx.fragments.LogFragment; import hep.dataforge.fx.fragments.LogFragment;
import hep.dataforge.values.Value;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -82,22 +80,12 @@ public class PKT8Controller extends DeviceConnection<PKT8Device> implements Init
} }
@Override
public void notifyDeviceStateChanged(Device device, String name, Value state) {
} private void startMeasurement() throws MeasurementException {
@Override
public void evaluateDeviceException(Device device, String message, Throwable exception) {
}
public void start() throws MeasurementException {
getDevice().startMeasurement().addListener(this); getDevice().startMeasurement().addListener(this);
} }
public void stop() throws MeasurementException { private void stopMeasurement() throws MeasurementException {
if (getDevice().isMeasuring()) { if (getDevice().isMeasuring()) {
getDevice().getMeasurement().removeListener(this); getDevice().getMeasurement().removeListener(this);
getDevice().stopMeasurement(false); getDevice().stopMeasurement(false);
@ -110,10 +98,10 @@ public class PKT8Controller extends DeviceConnection<PKT8Device> implements Init
if (getDevice() != null) { if (getDevice() != null) {
try { try {
if (startStopButton.isSelected()) { if (startStopButton.isSelected()) {
start(); startMeasurement();
} else { } else {
//in case device started //in case device started
stop(); stopMeasurement();
} }
} catch (ControlException ex) { } catch (ControlException ex) {
evaluateDeviceException(getDevice(), "Failed to start or stop device", ex); evaluateDeviceException(getDevice(), "Failed to start or stop device", ex);

View File

@ -71,7 +71,8 @@ public class PKT8Device extends PortSensor<PKT8Result> {
public PKT8Device(Context context, Meta meta) { public PKT8Device(Context context, Meta meta) {
setContext(context);
setMetaBase(meta);
} }
@Override @Override

View File

@ -1,13 +1,9 @@
package inr.numass.cryotemp; package inr.numass.cryotemp;
import hep.dataforge.context.Context; import hep.dataforge.context.Context;
import hep.dataforge.control.connections.Connection;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.devices.DeviceFactory; import hep.dataforge.control.devices.DeviceFactory;
import hep.dataforge.meta.Meta; import hep.dataforge.meta.Meta;
import java.util.Objects;
/** /**
* Created by darksnake on 09-May-17. * Created by darksnake on 09-May-17.
*/ */
@ -22,12 +18,4 @@ public class PKT8DeviceFactory implements DeviceFactory<PKT8Device> {
return new PKT8Device(context, meta); return new PKT8Device(context, meta);
} }
@Override
public Connection<PKT8Device> buildConnection(String role, Context context, Meta meta) {
if(Objects.equals(role, Roles.VIEW_ROLE)){
return new PKT8Controller();
} else {
return DeviceFactory.super.buildConnection(role, context, meta);
}
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package inr.numass.control.msp; package inr.numass.control.msp;
import hep.dataforge.context.Context;
import hep.dataforge.control.connections.Roles; import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection; import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.SingleMeasurementDevice; import hep.dataforge.control.devices.SingleMeasurementDevice;
@ -61,6 +62,14 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
private Consumer<MspResponse> responseDelegate; private Consumer<MspResponse> responseDelegate;
private Consumer<Throwable> errorDelegate; private Consumer<Throwable> errorDelegate;
public MspDevice() {
}
public MspDevice(Context context, Meta meta) {
setContext(context);
setMetaBase(meta);
}
// public MspDevice(String name, Context context, Meta config) { // public MspDevice(String name, Context context, Meta config) {
// super(name, context, config); // super(name, context, config);
// } // }

View File

@ -15,9 +15,7 @@ public class MspDeviceFactory implements DeviceFactory<MspDevice> {
@Override @Override
public MspDevice build(Context context, Meta config) { public MspDevice build(Context context, Meta config) {
MspDevice device = new MspDevice(); MspDevice device = new MspDevice(context,config);
device.setContext(context);
device.configure(config);
return device; return device;
} }
} }

View File

@ -348,16 +348,6 @@ public class MspViewController extends DeviceConnection<MspDevice> implements De
} }
} }
@Override
public void notifyDeviceInitialized(Device device) {
}
@Override
public void notifyDeviceShutdown(Device device) {
}
@Override @Override
public void notifyDeviceStateChanged(Device device, String name, Value state) { public void notifyDeviceStateChanged(Device device, String name, Value state) {

View File

@ -5,6 +5,8 @@
*/ */
package inr.numass.readvac.app; package inr.numass.readvac.app;
import hep.dataforge.context.Context;
import hep.dataforge.context.Global;
import hep.dataforge.control.measurements.Sensor; import hep.dataforge.control.measurements.Sensor;
import hep.dataforge.exceptions.StorageException; import hep.dataforge.exceptions.StorageException;
import hep.dataforge.io.MetaFileReader; import hep.dataforge.io.MetaFileReader;
@ -56,26 +58,52 @@ public class ReadVac extends Application {
config = Meta.empty(); config = Meta.empty();
} }
Sensor<Double> p1 = new MKSVacDevice(); Context context = Global.instance();
p1.configure(config.getMeta("p1",
() -> new MetaBuilder("p1").setValue("port", "com::/dev/ttyUSB0"))); Meta p1Meta = config.getMeta("p1",
p1.setName(config.getString("p1.name", "P1")); new MetaBuilder("p1")
Sensor<Double> p2 = new CM32Device(); .setValue("port", "com::/dev/ttyUSB0")
p2.configure(config.getMeta("p2", .setValue("name", "P1")
() -> new MetaBuilder("p2").setValue("port", "tcp::192.168.111.32:4002"))); .build()
p2.setName(config.getString("p2.name", "P2")); );
Sensor<Double> p3 = new CM32Device();
p3.configure(config.getMeta("p3", Sensor<Double> p1 = new MKSVacDevice(context, p1Meta);
() -> new MetaBuilder("p3").setValue("port", "tcp::192.168.111.32:4003")));
p3.setName(config.getString("p3.name", "P3")); Meta p2Meta = config.getMeta("p2",
Sensor<Double> px = new VITVacDevice(); new MetaBuilder("p2")
px.configure(config.getMeta("px", .setValue("port", "tcp::192.168.111.32:4002")
() -> new MetaBuilder("px").setValue("port", "tcp::192.168.111.32:4003"))); .setValue("name", "P2")
px.setName(config.getString("px.name", "Px")); .build()
Sensor<Double> baratron = new MKSBaratronDevice(); );
baratron.configure(config.getMeta("baratron",
() -> new MetaBuilder("baratron").setValue("port", "tcp::192.168.111.33:4004"))); Sensor<Double> p2 = new CM32Device(context,p2Meta);
baratron.setName(config.getString("baratron.name", "Baratron"));
Meta p3Meta = config.getMeta("p3",
new MetaBuilder("p3")
.setValue("port", "tcp::192.168.111.32:4003")
.setValue("name", "P3")
.build()
);
Sensor<Double> p3 = new CM32Device(context, p3Meta);
Meta pxMeta = config.getMeta("px",
new MetaBuilder("px")
.setValue("port", "tcp::192.168.111.32:4003")
.setValue("name", "Px")
.build()
);
Sensor<Double> px = new VITVacDevice(context,pxMeta);
Meta baratronMeta = config.getMeta("baratron",
new MetaBuilder("baratron")
.setValue("port", "tcp::192.168.111.33:4004")
.setValue("name", "Baratron")
.build()
);
Sensor<Double> baratron = new MKSBaratronDevice(context,baratronMeta);
VacCollectorDevice collector = new VacCollectorDevice(); VacCollectorDevice collector = new VacCollectorDevice();
collector.configure(config); collector.configure(config);

View File

@ -5,6 +5,7 @@
*/ */
package inr.numass.readvac.devices; package inr.numass.readvac.devices;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.PortSensor; import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement; import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement; import hep.dataforge.control.measurements.SimpleMeasurement;
@ -13,15 +14,22 @@ import hep.dataforge.control.ports.PortFactory;
import hep.dataforge.control.ports.PortHandler; import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef; import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException; import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
/** /**
*
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@ValueDef(name = "port") @ValueDef(name = "port")
@ValueDef(name = "delay") @ValueDef(name = "delay")
@ValueDef(name = "timeout") @ValueDef(name = "timeout")
public class CM32Device extends PortSensor<Double> { public class CM32Device extends PortSensor<Double> {
public CM32Device() {
}
public CM32Device(Context context, Meta meta) {
setContext(context);
setMetaBase(meta);
}
@Override @Override
protected PortHandler buildHandler(String portName) throws ControlException { protected PortHandler buildHandler(String portName) throws ControlException {

View File

@ -5,20 +5,29 @@
*/ */
package inr.numass.readvac.devices; package inr.numass.readvac.devices;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.PortSensor; import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement; import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement; import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.PortHandler; import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef; import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException; import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
/** /**
*
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@ValueDef(name = "channel") @ValueDef(name = "channel")
public class MKSBaratronDevice extends PortSensor<Double> { public class MKSBaratronDevice extends PortSensor<Double> {
public MKSBaratronDevice() {
}
public MKSBaratronDevice(Context context, Meta meta) {
setContext(context);
setMetaBase(meta);
}
@Override @Override
protected Measurement<Double> createMeasurement() { protected Measurement<Double> createMeasurement() {

View File

@ -5,12 +5,14 @@
*/ */
package inr.numass.readvac.devices; package inr.numass.readvac.devices;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.PortSensor; import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement; import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement; import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.PortHandler; import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef; import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException; import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder; import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder;
@ -26,6 +28,14 @@ import java.util.regex.Pattern;
@ValueDef(name = "powerButton", type = "BOOLEAN", def = "true") @ValueDef(name = "powerButton", type = "BOOLEAN", def = "true")
public class MKSVacDevice extends PortSensor<Double> { public class MKSVacDevice extends PortSensor<Double> {
public MKSVacDevice() {
}
public MKSVacDevice(Context context, Meta meta) {
setContext(context);
setMetaBase(meta);
}
private String talk(String requestContent) throws ControlException { private String talk(String requestContent) throws ControlException {
String answer = getHandler().sendAndWait(String.format("@%s%s;FF", getDeviceAddress(), requestContent), timeout()); String answer = getHandler().sendAndWait(String.format("@%s%s;FF", getDeviceAddress(), requestContent), timeout());

View File

@ -5,11 +5,13 @@
*/ */
package inr.numass.readvac.devices; package inr.numass.readvac.devices;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.PortSensor; import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement; import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement; import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.PortHandler; import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.exceptions.ControlException; import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
@ -17,11 +19,18 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
*
* @author Alexander Nozik * @author Alexander Nozik
*/ */
public class VITVacDevice extends PortSensor<Double> { public class VITVacDevice extends PortSensor<Double> {
public VITVacDevice() {
}
public VITVacDevice(Context context, Meta meta) {
setContext(context);
setMetaBase(meta);
}
@Override @Override
protected PortHandler buildHandler(String portName) throws ControlException { protected PortHandler buildHandler(String portName) throws ControlException {
PortHandler newHandler = super.buildHandler(portName); PortHandler newHandler = super.buildHandler(portName);

View File

@ -45,7 +45,7 @@ import org.apache.commons.math3.util.FastMath;
*/ */
@PluginDef(group = "inr.numass", name = "numass", @PluginDef(group = "inr.numass", name = "numass",
dependsOn = {"hep.dataforge:math", "hep.dataforge:MINUIT"}, dependsOn = {"hep.dataforge:math", "hep.dataforge:MINUIT"},
description = "Numass data analysis tools") info = "Numass data analysis tools")
public class NumassPlugin extends BasicPlugin { public class NumassPlugin extends BasicPlugin {
/** /**

View File

@ -6,11 +6,11 @@
package inr.numass.server; package inr.numass.server;
import freemarker.template.Template; import freemarker.template.Template;
import hep.dataforge.server.ServletUtils;
import hep.dataforge.storage.api.Loader; import hep.dataforge.storage.api.Loader;
import hep.dataforge.storage.api.StateLoader; import hep.dataforge.storage.api.StateLoader;
import hep.dataforge.storage.api.Storage; import hep.dataforge.storage.api.Storage;
import hep.dataforge.storage.commons.JSONMetaWriter; import hep.dataforge.storage.commons.JSONMetaWriter;
import hep.dataforge.storage.servlet.ServletUtils;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ratpack.handling.Context; import ratpack.handling.Context;
import ratpack.handling.Handler; import ratpack.handling.Handler;
@ -19,7 +19,7 @@ import java.io.StringWriter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static hep.dataforge.storage.servlet.StorageRenderer.renderStorage; import static hep.dataforge.server.StorageRenderer.renderStorage;
import static inr.numass.server.HandlerUtils.renderStates; import static inr.numass.server.HandlerUtils.renderStates;
/** /**

View File

@ -7,11 +7,11 @@ package inr.numass.server;
import freemarker.template.Template; import freemarker.template.Template;
import hep.dataforge.meta.MetaBuilder; import hep.dataforge.meta.MetaBuilder;
import hep.dataforge.server.ServletUtils;
import hep.dataforge.server.StorageRatpackHandler;
import hep.dataforge.storage.api.ObjectLoader; import hep.dataforge.storage.api.ObjectLoader;
import hep.dataforge.storage.api.PointLoader; import hep.dataforge.storage.api.PointLoader;
import hep.dataforge.storage.api.Storage; import hep.dataforge.storage.api.Storage;
import hep.dataforge.storage.servlet.ServletUtils;
import hep.dataforge.storage.servlet.StorageRatpackHandler;
import inr.numass.data.NumassData; import inr.numass.data.NumassData;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ratpack.handling.Context; import ratpack.handling.Context;

View File

@ -3,9 +3,15 @@ plugins{
id "application" id "application"
} }
repositories {
mavenCentral()
}
if (!hasProperty('mainClass')) { if (!hasProperty('mainClass')) {
ext.mainClass = 'inr.numass.viewer.Viewer'//"inr.numass.viewer.test.TestApp" ext.mainClass = 'inr.numass.viewer.Viewer'//"inr.numass.viewer.test.TestApp"
} }
mainClassName = mainClass mainClassName = mainClass
version = "0.4.0" version = "0.4.0"
@ -22,10 +28,7 @@ dependencies {
compile 'org.controlsfx:controlsfx:8.40.12' compile 'org.controlsfx:controlsfx:8.40.12'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:'1.1.2-2" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:'1.1.2-2"
compile "no.tornado:tornadofx:1.7.1" compile "no.tornado:tornadofx:1.7.4"
} }
apply plugin: 'kotlin'
repositories {
mavenCentral()
}

View File

@ -5,6 +5,7 @@ include ":numass-control:cryotemp"
include ":numass-control:magnet" include ":numass-control:magnet"
include ":numass-control:msp" include ":numass-control:msp"
include ":numass-control:vac" include ":numass-control:vac"
include ":numass-control:control-room"
// //
include ":numass-main" include ":numass-main"
// //