Moved numass devices to kotlin and applied new logic

This commit is contained in:
Alexander Nozik 2017-11-12 19:17:58 +03:00
parent 7dc3d601f7
commit 94d538d858
36 changed files with 1214 additions and 1471 deletions

View File

@ -2,6 +2,7 @@ package inr.numass.control
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.control.DeviceManager
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.meta.Meta import hep.dataforge.meta.Meta
@ -16,13 +17,12 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.collections.ObservableList import javafx.collections.ObservableList
import tornadofx.* import tornadofx.*
import java.io.File
/** /**
* Created by darksnake on 12-May-17. * Created by darksnake on 12-May-17.
*/ */
class BoardController() : Controller(), AutoCloseable { class BoardController() : Controller(), AutoCloseable {
val devices: ObservableList<DeviceViewConnection<*>> = FXCollections.observableArrayList<DeviceViewConnection<*>>(); val devices: ObservableList<DeviceDisplay<*>> = FXCollections.observableArrayList<DeviceDisplay<*>>();
val contextProperty = SimpleObjectProperty<Context>(Global.instance()) val contextProperty = SimpleObjectProperty<Context>(Global.instance())
var context: Context by contextProperty var context: Context by contextProperty
@ -39,15 +39,8 @@ class BoardController() : Controller(), AutoCloseable {
fun load(app: Application) { fun load(app: Application) {
runAsync { runAsync {
getConfig(app).ifPresent { getConfig(app).ifPresent {
val libDir = File(app.parameters.named.getOrDefault("libPath", "../lib")); val context = Context.build("NUMASS", Global.instance(), it)
val contextBuilder = Context load(context, it)
.builder("NUMASS-SERVER");
if (libDir.exists()) {
Global.logger().info("Found library directory {}. Loading it into server context", libDir)
contextBuilder.classPath(libDir.listFiles { _, name -> name.endsWith(".jar") }.map { it.toURI().toURL() })
}
context = contextBuilder.build();
load(context, it);
} }
} }
@ -87,21 +80,11 @@ class BoardController() : Controller(), AutoCloseable {
} }
private fun buildDeviceView(context: Context, deviceMeta: Meta): DeviceViewConnection<*> { private fun buildDeviceView(context: Context, deviceMeta: Meta): DeviceDisplay<*> {
context.logger.info("Building device with meta: {}", deviceMeta) context.logger.info("Building device with meta: {}", deviceMeta)
val factory = context.serviceStream(DeviceViewFactory::class.java) val device = context.loadFeature("devices", DeviceManager::class.java).buildDevice(deviceMeta)
.filter { it.type == deviceMeta.getString("type") } device.init();
.findFirst(); return device.getDisplay();
if (factory.isPresent) {
val device = factory.get().build(context, deviceMeta);
val view = factory.get().buildView(device);
device.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
device.init();
return view;
} else {
throw RuntimeException("Device factory not found");
}
} }
private fun buildStorage(context: Context, meta: Meta): Storage { private fun buildStorage(context: Context, meta: Meta): Storage {

View File

@ -1,101 +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.meta.Meta;
import hep.dataforge.meta.MetaBuilder;
import hep.dataforge.meta.Metoid;
import hep.dataforge.names.Named;
import hep.dataforge.values.Value;
import java.util.List;
import java.util.function.Function;
/**
* Created by darksnake on 28-Sep-16.
*/
public class PKT8Channel implements Named, Metoid {
private final Meta meta;
private final Function<Double, Double> transformation;
public PKT8Channel(String name) {
this.meta = new MetaBuilder("channel")
.putValue("name", name);
transformation = (d) -> d;
}
public PKT8Channel(Meta meta) {
this.meta = meta;
String transformationType = meta.getString("transformationType", "default");
if (meta.hasValue("coefs")) {
switch (transformationType) {
case "default":
case "hyperbolic":
List<Value> coefs = meta.getValue("coefs").listValue();
double r0 = meta.getDouble("r0", 1000);
transformation = (r) -> {
if (coefs == null) {
return -1d;
} else {
double res = 0;
for (int i = 0; i < coefs.size(); i++) {
res += coefs.get(i).doubleValue() * Math.pow(r0 / r, i);
}
return res;
}
};
break;
default:
throw new RuntimeException("Unknown transformation type");
}
} else {
//identity transformation
transformation = (d) -> d;
}
}
@Override
public String getName() {
return meta().getString("name");
}
@Override
public Meta meta() {
return meta;
}
public String description() {
return meta().getString("description", "");
}
/**
* @param r negative if temperature transformation not defined
* @return
*/
public double getTemperature(double r) {
return transformation.apply(r);
}
public PKT8Result evaluate(double r) {
return new PKT8Result(getName(), r, getTemperature(r));
}
}

View File

@ -1,421 +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.context.Context;
import hep.dataforge.control.RoleDef;
import hep.dataforge.control.collectors.RegularPointCollector;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.devices.StateDef;
import hep.dataforge.control.measurements.AbstractMeasurement;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.exceptions.MeasurementException;
import hep.dataforge.exceptions.StorageException;
import hep.dataforge.meta.Meta;
import hep.dataforge.storage.api.Storage;
import hep.dataforge.storage.api.TableLoader;
import hep.dataforge.storage.commons.LoaderFactory;
import hep.dataforge.tables.TableFormat;
import hep.dataforge.tables.TableFormatBuilder;
import hep.dataforge.utils.DateTimeUtils;
import hep.dataforge.values.Values;
import inr.numass.control.StorageHelper;
import java.time.Duration;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* A device controller for Dubna PKT 8 cryogenic thermometry device
*
* @author Alexander Nozik
*/
@RoleDef(name = Roles.STORAGE_ROLE)
@RoleDef(name = Roles.VIEW_ROLE)
@ValueDef(name = "port", def = "virtual", info = "The name of the port for this PKT8")
@StateDef(@ValueDef(name = "storing"))
public class PKT8Device extends PortSensor<PKT8Result> {
public static final String PKT8_DEVICE_TYPE = "numass:pkt8";
private static final Duration TIMEOUT = Duration.ofMillis(400);
public static final String PGA = "pga";
public static final String SPS = "sps";
public static final String ABUF = "abuf";
private static final String[] CHANNEL_DESIGNATIONS = {"a", "b", "c", "d", "e", "f", "g", "h"};
/**
* The key is the letter (a,b,c,d...) as in measurements
*/
private final Map<String, PKT8Channel> channels = new LinkedHashMap<>();
private RegularPointCollector collector;
private StorageHelper storageHelper;
/**
* Cached values
*/
private TableFormat format;
public PKT8Device() {
}
public PKT8Device(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
private TableLoader buildLoader(StorageConnection connection) {
Storage storage = connection.getStorage();
String suffix = DateTimeUtils.fileSuffix();
try {
return LoaderFactory.buildPointLoder(storage,
"cryotemp_" + suffix, "", "timestamp", getTableFormat());
} catch (StorageException e) {
throw new RuntimeException("Failed to builder loader from storage", e);
}
}
@Override
public void init() throws ControlException {
//read channel configuration
if (meta().hasMeta("channel")) {
for (Meta node : meta().getMetaList("channel")) {
String designation = node.getString("designation", "default");
this.channels.put(designation, new PKT8Channel(node));
}
} else {
//set default channel configuration
for (String designation : CHANNEL_DESIGNATIONS) {
channels.put(designation, new PKT8Channel(designation));
}
getLogger().warn("No channels defined in configuration");
}
super.init();
//update parameters from meta
if (meta().hasValue("pga")) {
getLogger().info("Setting dynamic range to " + meta().getInt("pga"));
String response = sendAndWait("g" + meta().getInt("pga"), TIMEOUT).trim();
if (response.contains("=")) {
updateState(PGA, Integer.parseInt(response.substring(4)));
} else {
getLogger().error("Setting pga failsed with message: " + response);
}
}
setSPS(meta().getInt("sps", 0));
setBUF(meta().getInt("abuf", 100));
// setting up the collector
storageHelper = new StorageHelper(this, this::buildLoader);
Duration duration = Duration.parse(meta().getString("averagingDuration", "PT30S"));
collector = new RegularPointCollector(
duration,
channels.values().stream().map(PKT8Channel::getName).collect(Collectors.toList()),
(Values dp) -> {
getLogger().debug("Point measurement complete. Pushing...");
storageHelper.push(dp);
});
}
private TableFormat getTableFormat() {
if (format == null) {
// Building data format
TableFormatBuilder tableFormatBuilder = new TableFormatBuilder()
.addTime("timestamp");
for (PKT8Channel channel : channels.values()) {
tableFormatBuilder.addNumber(channel.getName());
}
format = tableFormatBuilder.build();
}
return format;
}
@Override
public void shutdown() throws ControlException {
storageHelper.close();
if (collector != null) {
collector.stop();
collector = null;
}
super.shutdown();
}
@Override
protected PortHandler buildHandler(String portName) throws ControlException {
PortHandler handler;
//setup connection
if ("virtual".equals(portName)) {
getLogger().info("Starting {} using virtual debug port", getName());
handler = new PKT8VirtualPort("PKT8", meta().getMetaOrEmpty("debug"));
} else {
handler = super.buildHandler(portName);
}
handler.setDelimiter("\n");
return handler;
}
public Collection<PKT8Channel> getChanels() {
return this.channels.values();
}
private void setBUF(int buf) throws ControlException {
getLogger().info("Setting avaraging buffer size to " + buf);
String response;
try {
response = sendAndWait("b" + buf, Duration.ofMillis(400)).trim();
} catch (Exception ex) {
response = ex.getMessage();
}
if (response.contains("=")) {
updateState(ABUF, Integer.parseInt(response.substring(14)));
// getLogger().info("successfully set buffer size to {}", this.abuf);
} else {
getLogger().error("Setting averaging buffer failed with message: " + response);
}
}
public void changeParameters(int sps, int abuf) throws ControlException {
stopMeasurement(false);
//setting sps
setSPS(sps);
//setting buffer
setBUF(abuf);
}
/**
* '0' : 2,5 SPS '1' : 5 SPS '2' : 10 SPS '3' : 25 SPS '4' : 50 SPS '5' :
* 100 SPS '6' : 500 SPS '7' : 1 kSPS '8' : 3,75 kSPS
*
* @param sps
* @return
*/
private String spsToStr(int sps) {
switch (sps) {
case 0:
return "2.5 SPS";
case 1:
return "5 SPS";
case 2:
return "10 SPS";
case 3:
return "25 SPS";
case 4:
return "50 SPS";
case 5:
return "100 SPS";
case 6:
return "500 SPS";
case 7:
return "1 kSPS";
case 8:
return "3.75 kSPS";
default:
return "unknown value";
}
}
/**
* '0' : ± 5 В '1' : ± 2,5 В '2' : ± 1,25 В '3' : ± 0,625 В '4' : ± 312.5 мВ
* '5' : ± 156,25 мВ '6' : ± 78,125 мВ
*
* @param pga
* @return
*/
private String pgaToStr(int pga) {
switch (pga) {
case 0:
return "± 5 V";
case 1:
return "± 2,5 V";
case 2:
return "± 1,25 V";
case 3:
return "± 0,625 V";
case 4:
return "± 312.5 mV";
case 5:
return "± 156.25 mV";
case 6:
return "± 78.125 mV";
default:
return "unknown value";
}
}
public String getSPS() {
return getState(SPS).stringValue();
}
private void setSPS(int sps) throws ControlException {
getLogger().info("Setting sampling rate to " + spsToStr(sps));
String response;
try {
response = sendAndWait("v" + sps, TIMEOUT).trim();
} catch (Exception ex) {
response = ex.getMessage();
}
if (response.contains("=")) {
updateState(SPS, Integer.parseInt(response.substring(4)));
// getLogger().info("successfully sampling rate to {}", spsToStr(this.sps));
} else {
getLogger().error("Setting sps failsed with message: " + response);
}
}
public String getPGA() {
return getState(PGA).stringValue();
}
public String getABUF() {
return getState(ABUF).stringValue();
}
// public void connectPointListener(PointListenerConnection listener) {
// this.connect(listener, Roles.POINT_LISTENER_ROLE);
// }
@Override
protected Measurement<PKT8Result> createMeasurement() throws MeasurementException {
if (this.getMeasurement() != null) {
return this.getMeasurement();
} else {
try {
if (getHandler().isLocked()) {
getLogger().error("Breaking hold on handler because it is locked");
getHandler().breakHold();
}
return new PKT8Measurement(getHandler());
} catch (ControlException e) {
throw new MeasurementException(e);
}
}
}
@Override
public Measurement<PKT8Result> startMeasurement() throws MeasurementException {
//clearing PKT queue
try {
send("p");
sendAndWait("p", TIMEOUT);
} catch (ControlException e) {
getLogger().error("Failed to clear PKT8 port");
// throw new MeasurementException(e);
}
return super.startMeasurement();
}
public class PKT8Measurement extends AbstractMeasurement<PKT8Result> implements PortHandler.PortController {
final PortHandler handler;
public PKT8Measurement(PortHandler handler) {
this.handler = handler;
}
@Override
public Device getDevice() {
return PKT8Device.this;
}
@Override
public void start() {
if (isStarted()) {
getLogger().warn("Trying to start measurement which is already started");
}
try {
getLogger().info("Starting measurement");
handler.holdBy(this);
send("s");
afterStart();
} catch (ControlException ex) {
portError("Failed to start measurement", ex);
}
}
@Override
public boolean stop(boolean force) throws MeasurementException {
if (isFinished()) {
getLogger().warn("Trying to stop measurement which is already stopped");
}
try {
getLogger().info("Stopping measurement");
String response = sendAndWait("p", TIMEOUT).trim();
// Должно быть именно с большой буквы!!!
return "Stopped".equals(response) || "stopped".equals(response);
} catch (Exception ex) {
error(ex);
return false;
} finally {
if (collector != null) {
collector.clear();
}
handler.unholdBy(this);
}
}
@Override
public void acceptPortPhrase(String message) {
String trimmed = message.trim();
if (isStarted()) {
if (trimmed.equals("Stopped") || trimmed.equals("stopped")) {
afterPause();
// getLogger().info("Measurement stopped");
} else {
String designation = trimmed.substring(0, 1);
double rawValue = Double.parseDouble(trimmed.substring(1)) / 100;
if (channels.containsKey(designation)) {
PKT8Channel channel = channels.get(designation);
result(channel.evaluate(rawValue));
if (collector != null) {
collector.put(channel.getName(), channel.getTemperature(rawValue));
}
} else {
result(new PKT8Result(designation, rawValue, -1));
}
}
}
}
@Override
public void portError(String errorMessage, Throwable error) {
super.error(error);
}
}
}

View File

@ -1,69 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.cryotemp;
import hep.dataforge.control.ports.VirtualPort;
import hep.dataforge.meta.Meta;
import hep.dataforge.meta.MetaUtils;
import hep.dataforge.meta.Metoid;
import hep.dataforge.values.Value;
import java.time.Duration;
import java.util.Random;
/**
* @author Alexander Nozik
*/
public class PKT8VirtualPort extends VirtualPort implements Metoid {
private final Random generator = new Random();
public PKT8VirtualPort(String portName, Meta meta) {
super.configure(meta).configureValue("id", portName);
}
@Override
protected synchronized void evaluateRequest(String request) {
switch (request) {
case "s":
String[] letters = {"a", "b", "c", "d", "e", "f", "g", "h"};
for (String letter : letters) {
Meta channelMeta = MetaUtils.findNodeByValue(meta(), "channel", "letter", Value.of(letter)).orElse(Meta.empty());
double average;
double sigma;
if (channelMeta != null) {
average = channelMeta.getDouble("av", 1200);
sigma = channelMeta.getDouble("sigma", 50);
} else {
average = 1200d;
sigma = 50d;
}
this.planRegularResponse(
() -> {
double res = average + generator.nextGaussian() * sigma;
//TODO convert double value to formatted string
return String.format("%s000%d", letter, (int) (res * 100));
},
Duration.ZERO, Duration.ofMillis(500), letter, "measurement"
);
}
return;
case "p":
cancelByTag("measurement");
this.receivePhrase("Stopped");
}
}
@Override
public void close() throws Exception {
cancelByTag("measurement");
super.close();
}
}

View File

@ -15,9 +15,7 @@
*/ */
package inr.numass.control.cryotemp package inr.numass.control.cryotemp
import hep.dataforge.control.connections.Roles
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication import inr.numass.control.NumassControlApplication
import javafx.stage.Stage import javafx.stage.Stage
@ -25,11 +23,6 @@ import javafx.stage.Stage
* @author darksnake * @author darksnake
*/ */
class PKT8App : NumassControlApplication<PKT8Device>() { class PKT8App : NumassControlApplication<PKT8Device>() {
override fun buildView(device: PKT8Device): DeviceViewConnection<PKT8Device> {
return PKT8ViewConnection().apply {
device.connect(this, Roles.VIEW_ROLE)
}
}
override val deviceFactory = PKT8DeviceFactory() override val deviceFactory = PKT8DeviceFactory()

View File

@ -0,0 +1,82 @@
/*
* 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.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.Metoid
import hep.dataforge.names.Named
internal fun createChannel(name: String): PKT8Channel {
return PKT8Channel(MetaBuilder("channel").putValue("name", name)) { d -> d }
}
internal fun createChannel(meta: Meta): PKT8Channel {
val transformationType = meta.getString("transformationType", "default")
if (meta.hasValue("coefs")) {
when (transformationType) {
"default", "hyperbolic" -> {
val coefs = meta.getValue("coefs").listValue()
val r0 = meta.getDouble("r0", 1000.0)!!
return PKT8Channel(meta) { r ->
if (coefs == null) {
-1.0
} else {
coefs.indices.sumByDouble { coefs[it].doubleValue() * Math.pow(r0 / r, it.toDouble()) }
}
}
}
else -> throw RuntimeException("Unknown transformation type")
}
} else {
//identity transformation
return PKT8Channel(meta) { d -> d }
}
}
/**
* Created by darksnake on 28-Sep-16.
*/
class PKT8Channel(private val _meta: Meta, val func: (Double) -> Double) : Named, Metoid {
override fun getName(): String {
return meta().getString("name")
}
override fun meta(): Meta {
return _meta
}
fun description(): String {
return meta().getString("description", "")
}
/**
* @param r negative if temperature transformation not defined
* @return
*/
fun getTemperature(r: Double): Double {
return func(r)
}
fun evaluate(r: Double): PKT8Result {
return PKT8Result(name, r, getTemperature(r))
}
}

View File

@ -0,0 +1,391 @@
/*
* 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.context.Context
import hep.dataforge.control.RoleDef
import hep.dataforge.control.RoleDefs
import hep.dataforge.control.collectors.RegularPointCollector
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.devices.StateDef
import hep.dataforge.control.measurements.AbstractMeasurement
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.ports.PortHandler
import hep.dataforge.description.ValueDef
import hep.dataforge.exceptions.ControlException
import hep.dataforge.exceptions.MeasurementException
import hep.dataforge.exceptions.StorageException
import hep.dataforge.meta.Meta
import hep.dataforge.storage.api.TableLoader
import hep.dataforge.storage.commons.LoaderFactory
import hep.dataforge.tables.TableFormat
import hep.dataforge.tables.TableFormatBuilder
import hep.dataforge.utils.DateTimeUtils
import hep.dataforge.values.Values
import inr.numass.control.DeviceView
import inr.numass.control.StorageHelper
import java.time.Duration
import java.util.*
import kotlin.streams.toList
/**
* A device controller for Dubna PKT 8 cryogenic thermometry device
*
* @author Alexander Nozik
*/
@RoleDefs(
RoleDef(name = Roles.STORAGE_ROLE),
RoleDef(name = Roles.VIEW_ROLE)
)
@ValueDef(name = "port", def = "virtual", info = "The name of the port for this PKT8")
@StateDef(ValueDef(name = "storing"))
@DeviceView(PKT8Display::class)
class PKT8Device(context: Context, meta: Meta) : PortSensor<PKT8Result>(context, meta) {
/**
* The key is the letter (a,b,c,d...) as in measurements
*/
private val channels = LinkedHashMap<String, PKT8Channel>()
private var collector: RegularPointCollector? = null
private var storageHelper: StorageHelper? = null
/**
* Cached values
*/
private var format: TableFormat? = null
private// Building data format
val tableFormat: TableFormat by lazy {
val tableFormatBuilder = TableFormatBuilder()
.addTime("timestamp")
for (channel in channels.values) {
tableFormatBuilder.addNumber(channel.name)
}
tableFormatBuilder.build()
}
val chanels: Collection<PKT8Channel>
get() = this.channels.values
val sps: String
get() = getState(SPS).stringValue()
val pga: String
get() = getState(PGA).stringValue()
val abuf: String
get() = getState(ABUF).stringValue()
private fun buildLoader(connection: StorageConnection): TableLoader {
val storage = connection.storage
val suffix = DateTimeUtils.fileSuffix()
try {
return LoaderFactory.buildPointLoder(storage,
"cryotemp_" + suffix, "", "timestamp", tableFormat)
} catch (e: StorageException) {
throw RuntimeException("Failed to builder loader from storage", e)
}
}
@Throws(ControlException::class)
override fun init() {
//read channel configuration
if (meta().hasMeta("channel")) {
for (node in meta().getMetaList("channel")) {
val designation = node.getString("designation", "default")
this.channels.put(designation, createChannel(node))
}
} else {
//set default channel configuration
for (designation in CHANNEL_DESIGNATIONS) {
channels.put(designation, createChannel(designation))
}
logger.warn("No channels defined in configuration")
}
super.init()
//update parameters from meta
if (meta().hasValue("pga")) {
logger.info("Setting dynamic range to " + meta().getInt("pga")!!)
val response = sendAndWait("g" + meta().getInt("pga")!!, TIMEOUT).trim { it <= ' ' }
if (response.contains("=")) {
updateState(PGA, Integer.parseInt(response.substring(4)))
} else {
logger.error("Setting pga failsed with message: " + response)
}
}
setSPS(meta().getInt("sps", 0)!!)
setBUF(meta().getInt("abuf", 100)!!)
// setting up the collector
storageHelper = StorageHelper(this) { connection: StorageConnection -> this.buildLoader(connection) }
val duration = Duration.parse(meta().getString("averagingDuration", "PT30S"))
collector = RegularPointCollector(
duration,
channels.values.stream().map { it.name }.toList()
) { dp: Values ->
logger.debug("Point measurement complete. Pushing...")
storageHelper!!.push(dp)
}
}
@Throws(ControlException::class)
override fun shutdown() {
storageHelper!!.close()
if (collector != null) {
collector!!.stop()
collector = null
}
super.shutdown()
}
@Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler {
val handler: PortHandler
//setup connection
if ("virtual" == portName) {
logger.info("Starting {} using virtual debug port", name)
handler = PKT8VirtualPort("PKT8", meta().getMetaOrEmpty("debug"))
} else {
handler = super.buildHandler(portName)
}
handler.setDelimiter("\n")
return handler
}
private fun setBUF(buf: Int) {
logger.info("Setting avaraging buffer size to " + buf)
var response: String
try {
response = sendAndWait("b" + buf, Duration.ofMillis(400)).trim { it <= ' ' }
} catch (ex: Exception) {
response = ex.message ?: ""
}
if (response.contains("=")) {
updateState(ABUF, Integer.parseInt(response.substring(14)))
// getLogger().info("successfully set buffer size to {}", this.abuf);
} else {
logger.error("Setting averaging buffer failed with message: " + response)
}
}
@Throws(ControlException::class)
fun changeParameters(sps: Int, abuf: Int) {
stopMeasurement(false)
//setting sps
setSPS(sps)
//setting buffer
setBUF(abuf)
}
/**
* '0' : 2,5 SPS '1' : 5 SPS '2' : 10 SPS '3' : 25 SPS '4' : 50 SPS '5' :
* 100 SPS '6' : 500 SPS '7' : 1 kSPS '8' : 3,75 kSPS
*
* @param sps
* @return
*/
private fun spsToStr(sps: Int): String {
when (sps) {
0 -> return "2.5 SPS"
1 -> return "5 SPS"
2 -> return "10 SPS"
3 -> return "25 SPS"
4 -> return "50 SPS"
5 -> return "100 SPS"
6 -> return "500 SPS"
7 -> return "1 kSPS"
8 -> return "3.75 kSPS"
else -> return "unknown value"
}
}
/**
* '0' : ± 5 В '1' : ± 2,5 В '2' : ± 1,25 В '3' : ± 0,625 В '4' : ± 312.5 мВ
* '5' : ± 156,25 мВ '6' : ± 78,125 мВ
*
* @param pga
* @return
*/
private fun pgaToStr(pga: Int): String {
when (pga) {
0 -> return "± 5 V"
1 -> return "± 2,5 V"
2 -> return "± 1,25 V"
3 -> return "± 0,625 V"
4 -> return "± 312.5 mV"
5 -> return "± 156.25 mV"
6 -> return "± 78.125 mV"
else -> return "unknown value"
}
}
private fun setSPS(sps: Int) {
logger.info("Setting sampling rate to " + spsToStr(sps))
var response: String
try {
response = sendAndWait("v" + sps, TIMEOUT).trim { it <= ' ' }
} catch (ex: Exception) {
response = ex.message ?: ""
}
if (response.contains("=")) {
updateState(SPS, Integer.parseInt(response.substring(4)))
// getLogger().info("successfully sampling rate to {}", spsToStr(this.sps));
} else {
logger.error("Setting sps failsed with message: " + response)
}
}
// public void connectPointListener(PointListenerConnection listener) {
// this.connect(listener, Roles.POINT_LISTENER_ROLE);
// }
@Throws(MeasurementException::class)
override fun createMeasurement(): Measurement<PKT8Result> {
return if (this.measurement != null) {
this.measurement
} else {
try {
if (handler.isLocked) {
logger.error("Breaking hold on handler because it is locked")
handler.breakHold()
}
PKT8Measurement(handler)
} catch (e: ControlException) {
throw MeasurementException(e)
}
}
}
@Throws(MeasurementException::class)
override fun startMeasurement(): Measurement<PKT8Result> {
//clearing PKT queue
try {
send("p")
sendAndWait("p", TIMEOUT)
} catch (e: ControlException) {
logger.error("Failed to clear PKT8 port")
// throw new MeasurementException(e);
}
return super.startMeasurement()
}
inner class PKT8Measurement(internal val handler: PortHandler) : AbstractMeasurement<PKT8Result>(), PortHandler.PortController {
override fun getDevice(): Device {
return this@PKT8Device
}
override fun start() {
if (isStarted) {
logger.warn("Trying to start measurement which is already started")
}
try {
logger.info("Starting measurement")
handler.holdBy(this)
send("s")
afterStart()
} catch (ex: ControlException) {
portError("Failed to start measurement", ex)
}
}
@Throws(MeasurementException::class)
override fun stop(force: Boolean): Boolean {
if (isFinished) {
logger.warn("Trying to stop measurement which is already stopped")
}
try {
logger.info("Stopping measurement")
val response = sendAndWait("p", TIMEOUT).trim { it <= ' ' }
// Должно быть именно с большой буквы!!!
return "Stopped" == response || "stopped" == response
} catch (ex: Exception) {
error(ex)
return false
} finally {
if (collector != null) {
collector!!.clear()
}
handler.unholdBy(this)
}
}
override fun acceptPortPhrase(message: String) {
val trimmed = message.trim { it <= ' ' }
if (isStarted) {
if (trimmed == "Stopped" || trimmed == "stopped") {
afterPause()
// getLogger().info("Measurement stopped");
} else {
val designation = trimmed.substring(0, 1)
val rawValue = java.lang.Double.parseDouble(trimmed.substring(1)) / 100
val channel = channels[designation]
if (channel != null) {
result(channel.evaluate(rawValue))
if (collector != null) {
collector!!.put(channel.getName(), channel.getTemperature(rawValue))
}
} else {
result(PKT8Result(designation, rawValue, -1.0))
}
}
}
}
override fun portError(errorMessage: String, error: Throwable) {
super.error(error)
}
}
companion object {
val PKT8_DEVICE_TYPE = "numass.pkt8"
private val TIMEOUT = Duration.ofMillis(400)
val PGA = "pga"
val SPS = "sps"
val ABUF = "abuf"
private val CHANNEL_DESIGNATIONS = arrayOf("a", "b", "c", "d", "e", "f", "g", "h")
}
init {
setContext(context)
setMeta(meta)
}
}

View File

@ -1,15 +1,13 @@
package inr.numass.control.cryotemp package inr.numass.control.cryotemp
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceViewFactory
/** /**
* Created by darksnake on 09-May-17. * Created by darksnake on 09-May-17.
*/ */
class PKT8DeviceFactory : DeviceViewFactory<PKT8Device> { class PKT8DeviceFactory : DeviceFactory {
override fun getType(): String { override fun getType(): String {
return PKT8Device.PKT8_DEVICE_TYPE return PKT8Device.PKT8_DEVICE_TYPE
} }
@ -17,8 +15,4 @@ class PKT8DeviceFactory : DeviceViewFactory<PKT8Device> {
override fun build(context: Context, meta: Meta): PKT8Device { override fun build(context: Context, meta: Meta): PKT8Device {
return PKT8Device(context, meta) return PKT8Device(context, meta)
} }
override fun buildView(device: Device): DeviceViewConnection<PKT8Device> {
return PKT8ViewConnection()
}
} }

View File

@ -12,7 +12,7 @@ import hep.dataforge.plots.PlotUtils
import hep.dataforge.plots.data.TimePlot import hep.dataforge.plots.data.TimePlot
import hep.dataforge.plots.data.TimePlottableGroup import hep.dataforge.plots.data.TimePlottableGroup
import hep.dataforge.plots.jfreechart.JFreeChartFrame import hep.dataforge.plots.jfreechart.JFreeChartFrame
import inr.numass.control.DeviceViewConnection import inr.numass.control.DeviceDisplay
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.binding.ListBinding import javafx.beans.binding.ListBinding
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
@ -31,7 +31,7 @@ import java.time.Instant
/** /**
* Created by darksnake on 30-May-17. * Created by darksnake on 30-May-17.
*/ */
class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListener { class PKT8Display : DeviceDisplay<PKT8Device>(), MeasurementListener {
override fun buildView(device: PKT8Device): View { override fun buildView(device: PKT8Device): View {
return CryoView() return CryoView()

View File

@ -0,0 +1,70 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.cryotemp
import hep.dataforge.control.ports.VirtualPort
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaUtils
import hep.dataforge.meta.Metoid
import hep.dataforge.values.Value
import java.time.Duration
import java.util.*
import java.util.function.Supplier
/**
* @author Alexander Nozik
*/
class PKT8VirtualPort(portName: String, meta: Meta) : VirtualPort(), Metoid {
private val generator = Random()
init {
super.configure(meta).configureValue("id", portName)
}
@Synchronized override fun evaluateRequest(request: String) {
when (request) {
"s" -> {
val letters = arrayOf("a", "b", "c", "d", "e", "f", "g", "h")
for (letter in letters) {
val channelMeta = MetaUtils.findNodeByValue(meta(), "channel", "letter", Value.of(letter)).orElse(Meta.empty())
val average: Double
val sigma: Double
if (channelMeta != null) {
average = channelMeta.getDouble("av", 1200.0)!!
sigma = channelMeta.getDouble("sigma", 50.0)!!
} else {
average = 1200.0
sigma = 50.0
}
this.planRegularResponse(
Supplier {
val res = average + generator.nextGaussian() * sigma
//TODO convert double value to formatted string
String.format("%s000%d", letter, (res * 100).toInt())
},
Duration.ZERO, Duration.ofMillis(500), letter, "measurement"
)
}
return
}
"p" -> {
cancelByTag("measurement")
this.receivePhrase("Stopped")
}
}
}
@Throws(Exception::class)
override fun close() {
cancelByTag("measurement")
super.close()
}
}

View File

@ -4,7 +4,7 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<BorderPane fx:id="root" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="400.0" <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.PKT8ViewConnection"> xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8Display">
<center> <center>
<TableView fx:id="table" BorderPane.alignment="CENTER"> <TableView fx:id="table" BorderPane.alignment="CENTER">
<columns> <columns>

View File

@ -1,547 +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.msp;
import hep.dataforge.context.Context;
import hep.dataforge.control.NamedValueListener;
import hep.dataforge.control.RoleDef;
import hep.dataforge.control.collectors.RegularPointCollector;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.devices.Sensor;
import hep.dataforge.control.devices.StateDef;
import hep.dataforge.control.measurements.AbstractMeasurement;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.control.ports.SyncPortController;
import hep.dataforge.control.ports.TcpPortHandler;
import hep.dataforge.description.ValueDef;
import hep.dataforge.events.EventBuilder;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.exceptions.MeasurementException;
import hep.dataforge.exceptions.PortException;
import hep.dataforge.exceptions.StorageException;
import hep.dataforge.meta.Meta;
import hep.dataforge.storage.api.Storage;
import hep.dataforge.storage.api.TableLoader;
import hep.dataforge.storage.commons.LoaderFactory;
import hep.dataforge.tables.TableFormat;
import hep.dataforge.tables.TableFormatBuilder;
import hep.dataforge.utils.DateTimeUtils;
import hep.dataforge.values.Value;
import hep.dataforge.values.Values;
import inr.numass.control.StorageHelper;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.function.Consumer;
/**
* @author Alexander Nozik
*/
@RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection.class)
@RoleDef(name = Roles.VIEW_ROLE)
@StateDef(value = @ValueDef(name = PortSensor.CONNECTED_STATE, info = "Connection with the device itself"), writable = true)
@StateDef(value = @ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true)
@StateDef(value = @ValueDef(name = "filament", info = "The number of filament in use"), writable = true)
@StateDef(value = @ValueDef(name = "filamentOn", info = "Mass-spectrometer filament on"), writable = true)
@StateDef(@ValueDef(name = "filamentStatus", info = "Filament status"))
public class MspDevice extends Sensor<Values> implements PortHandler.PortController {
public static final String MSP_DEVICE_TYPE = "msp";
private static final Duration TIMEOUT = Duration.ofMillis(200);
private TcpPortHandler handler;
private SyncPortController controller = new SyncPortController(this);
private Consumer<MspResponse> measurementDelegate;
public MspDevice() {
}
public MspDevice(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
// public MspDevice(String name, Context context, Meta config) {
// super(name, context, config);
// }
@Override
public void init() throws ControlException {
super.init();
String ip = meta().getString("connection.ip", "127.0.0.1");
int port = meta().getInt("connection.port", 10014);
getLogger().info("Connection to MKS mass-spectrometer on {}:{}...", ip, port);
handler = new TcpPortHandler(ip, port);
handler.setDelimiter("\r\r");
}
@Override
public void shutdown() throws ControlException {
super.stopMeasurement(true);
if (isConnected()) {
setFilamentOn(false);
setConnected(false);
}
getHandler().close();
super.shutdown();
}
// @Override
// protected Meta getMeasurementMeta() {
// return meta().getMeta("peakJump");
// }
@Override
protected PeakJumpMeasurement createMeasurement() throws MeasurementException {
Meta measurementMeta = meta().getMeta("peakJump");
String s = measurementMeta.getString("type", "peakJump");
if (s.equals("peakJump")) {
PeakJumpMeasurement measurement = new PeakJumpMeasurement(measurementMeta);
this.measurementDelegate = measurement;
return measurement;
} else {
throw new MeasurementException("Unknown measurement type");
}
}
@Override
protected Object computeState(String stateName) throws ControlException {
switch (stateName) {
case "connected":
return false;
case "filament":
return 1;
case "filamentOn":
return false;//Always return false on first request
case "filamentStatus":
return "UNKNOWN";
case "storing":
return false;
default:
return super.computeState(stateName);
}
}
@Override
public String getType() {
return "MKS E-Vision";
}
@Override
protected void requestStateChange(String stateName, Value value) throws ControlException {
switch (stateName) {
case PortSensor.CONNECTED_STATE:
setConnected(value.booleanValue());
break;
case "filament":
selectFilament(value.intValue());
break;
case "filamentOn":
setFilamentOn(value.booleanValue());
break;
default:
super.requestStateChange(stateName, value);
}
}
/**
* Startup MSP: get available sensors, select sensor and control.
*
* @param connected
* @return
* @throws hep.dataforge.exceptions.ControlException
*/
private boolean setConnected(boolean connected) throws ControlException {
String sensorName;
if (isConnected() != connected) {
if (connected) {
getHandler().holdBy(controller);
MspResponse response = sendAndWait("Sensors");
if (response.isOK()) {
sensorName = response.get(2, 1);
} else {
portError(response.errorDescription(), null);
return false;
}
//PENDING определеить в конфиге номер прибора
response = sendAndWait("Select", sensorName);
if (response.isOK()) {
updateState("selected", true);
// selected = true;
} else {
portError(response.errorDescription(), null);
return false;
}
response = sendAndWait("Control", "inr.numass.msp", "1.0");
if (response.isOK()) {
// controlled = true;
// invalidateState("controlled");
updateState("controlled", true);
} else {
portError(response.errorDescription(), null);
return false;
}
// connected = true;
updateState(PortSensor.CONNECTED_STATE, true);
return true;
} else {
getHandler().unholdBy(controller);
return !sendAndWait("Release").isOK();
}
} else {
return false;
}
}
/**
* Send request to the msp
*
* @param command
* @param parameters
* @throws PortException
*/
private void send(String command, Object... parameters) throws PortException {
String request = buildCommand(command, parameters);
dispatchEvent(
EventBuilder
.make("msp")
.setMetaValue("request", request)
.build()
);
getHandler().send(request);
}
/**
* A helper method to builder msp command string
*
* @param command
* @param parameters
* @return
*/
private String buildCommand(String command, Object... parameters) {
StringBuilder builder = new StringBuilder(command);
for (Object par : parameters) {
builder.append(String.format(" \"%s\"", par.toString()));
}
builder.append("\n");
return builder.toString();
}
/**
* Send specific command and wait for its results (the result must begin
* with command name)
*
* @param commandName
* @param parameters
* @return
* @throws PortException
*/
private MspResponse sendAndWait(String commandName, Object... parameters) throws PortException {
String request = buildCommand(commandName, parameters);
dispatchEvent(
EventBuilder
.make("msp")
.setMetaValue("request", request)
.build()
);
getHandler().send(controller, request);
String response = controller.waitFor(TIMEOUT, (String str) -> str.trim().startsWith(commandName));
return new MspResponse(response);
}
public boolean isConnected() {
return getState(PortSensor.CONNECTED_STATE).booleanValue();
}
public boolean isSelected() {
return getState("selected").booleanValue();
}
public boolean isControlled() {
return getState("controlled").booleanValue();
}
public boolean isFilamentOn() {
return getState("filamentOn").booleanValue();
}
public void selectFilament(int filament) throws PortException {
MspResponse response = sendAndWait("FilamentSelect", filament);
if (response.isOK()) {
updateState("filament", response.get(1, 1));
} else {
getLogger().error("Failed to set filament with error: {}", response.errorDescription());
}
}
/**
* Turn filament on or off
*
* @param filamentOn
* @return
* @throws hep.dataforge.exceptions.PortException
*/
public boolean setFilamentOn(boolean filamentOn) throws PortException {
if (filamentOn) {
return sendAndWait("FilamentControl", "On").isOK();
} else {
return sendAndWait("FilamentControl", "Off").isOK();
}
}
/**
* Evaluate general async messages
*
* @param response
*/
private void evaluateResponse(MspResponse response) {
}
@Override
public void acceptPortPhrase(String message) {
dispatchEvent(
EventBuilder
.make("msp")
.setMetaValue("response", message.trim()).build()
);
MspResponse response = new MspResponse(message);
switch (response.getCommandName()) {
// all possible async messages
case "FilamentStatus":
String status = response.get(0, 2);
updateState("filamentOn", status.equals("ON"));
updateState("filamentStatus", status);
break;
}
if (measurementDelegate != null) {
measurementDelegate.accept(response);
}
}
@Override
public void portError(String errorMessage, Throwable error) {
notifyError(errorMessage, error);
}
private TcpPortHandler getHandler() {
if (handler == null) {
throw new RuntimeException("Device not initialized");
}
return handler;
}
private Duration getAveragingDuration() {
return Duration.parse(meta().getString("averagingDuration", "PT30S"));
}
/**
* The MKS response as two-dimensional array of strings
*/
static class MspResponse {
private final List<List<String>> data = new ArrayList<>();
MspResponse(String response) {
String rx = "[^\"\\s]+|\"(\\\\.|[^\\\\\"])*\"";
Scanner scanner = new Scanner(response.trim());
while (scanner.hasNextLine()) {
List<String> line = new ArrayList<>();
String next = scanner.findWithinHorizon(rx, 0);
while (next != null) {
line.add(next);
next = scanner.findInLine(rx);
}
data.add(line);
}
}
String getCommandName() {
return this.get(0, 0);
}
boolean isOK() {
return "OK".equals(this.get(0, 1));
}
int errorCode() {
if (isOK()) {
return -1;
} else {
return Integer.parseInt(get(1, 1));
}
}
String errorDescription() {
if (isOK()) {
return null;
} else {
return get(2, 1);
}
}
String get(int lineNo, int columnNo) {
return data.get(lineNo).get(columnNo);
}
}
public class PeakJumpMeasurement extends AbstractMeasurement<Values> implements Consumer<MspResponse> {
private RegularPointCollector collector = new RegularPointCollector(getAveragingDuration(), this::result);
private StorageHelper helper = new StorageHelper(MspDevice.this, this::makeLoader);
private final Meta meta;
private Map<Integer, String> peakMap;
private double zero = 0;
private PeakJumpMeasurement(Meta meta) {
this.meta = meta;
}
private TableLoader makeLoader(StorageConnection connection) {
try {
Storage storage = connection.getStorage();
if (peakMap == null) {
throw new IllegalStateException("Peak map is not initialized");
}
TableFormatBuilder builder = new TableFormatBuilder().addTime("timestamp");
this.peakMap.values().forEach(builder::addNumber);
TableFormat format = builder.build();
String suffix = DateTimeUtils.fileSuffix();
return LoaderFactory
.buildPointLoder(storage, "msp_" + suffix, "", "timestamp", format);
} catch (StorageException ex) {
getLogger().error("Failed to create Loader", ex);
return null;
}
}
@Override
public Device getDevice() {
return MspDevice.this;
}
@Override
public void start() {
try {
String measurementName = "peakJump";
String filterMode = meta.getString("filterMode", "PeakAverage");
int accuracy = meta.getInt("accuracy", 5);
//PENDING вставить остальные параметры?
sendAndWait("MeasurementRemoveAll");
if (sendAndWait("AddPeakJump", measurementName, filterMode, accuracy, 0, 0, 0).isOK()) {
peakMap = new LinkedHashMap<>();
for (Meta peak : meta.getMetaList("peak")) {
peakMap.put(peak.getInt("mass"), peak.getString("name", peak.getString("mass")));
if (!sendAndWait("MeasurementAddMass", peak.getString("mass")).isOK()) {
throw new ControlException("Can't add mass to measurement measurement for msp");
}
}
} else {
throw new ControlException("Can't create measurement for msp");
}
if (!isFilamentOn()) {
this.error("Can't start measurement. Filament is not turned on.", null);
}
if (!sendAndWait("ScanAdd", measurementName).isOK()) {
this.error("Failed to add scan", null);
}
if (!sendAndWait("ScanStart", 2).isOK()) {
this.error("Failed to start scan", null);
}
} catch (ControlException ex) {
error(ex);
}
afterStart();
}
@Override
public boolean stop(boolean force) throws MeasurementException {
try {
collector.stop();
boolean stop = sendAndWait("ScanStop").isOK();
afterStop();
helper.close();
return stop;
} catch (PortException ex) {
throw new MeasurementException(ex);
}
}
@Override
protected synchronized void result(Values result, Instant time) {
super.result(result, time);
helper.push(result);
}
void error(String errorMessage, Throwable error) {
if (error == null) {
error(new MeasurementException(errorMessage));
} else {
error(error);
}
}
@Override
public void accept(MspResponse response) {
//Evaluating device state change
evaluateResponse(response);
//Evaluating measurement information
switch (response.getCommandName()) {
case "MassReading":
double mass = Double.parseDouble(response.get(0, 1));
double value = Double.parseDouble(response.get(0, 2)) / 100d;
String massName = Integer.toString((int) Math.floor(mass + 0.5));
collector.put(massName, value);
forEachConnection(Roles.VIEW_ROLE, NamedValueListener.class, listener -> listener.pushValue(massName, value));
break;
case "ZeroReading":
zero = Double.parseDouble(response.get(0, 2)) / 100d;
break;
case "StartingScan":
int numScans = Integer.parseInt(response.get(0, 3));
if (numScans == 0) {
try {
send("ScanResume", 10);
//FIXME обработать ошибку связи
} catch (PortException ex) {
error(null, ex);
}
}
break;
}
}
}
}

View File

@ -1,81 +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.msp;
import hep.dataforge.exceptions.PortException;
import java.io.IOException;
/**
* @author darksnake
*/
public class MspTest {
/**
* @param args the command line arguments
* @throws hep.dataforge.exceptions.PortException
*/
public static void main(String[] args) throws PortException, IOException, InterruptedException {
// 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);
//
// MspListener listener = new MspListener() {
//
// @Override
// public void acceptMeasurement(Map<Double, Double> measurement) {
// final StringBuilder mesString = new StringBuilder("[");
// measurement.forEach((Double key, Double value) -> mesString.append(String.format("%g:%g,", key, value)));
// mesString.deleteCharAt(mesString.length() - 1);
// mesString.append("]");
// System.out.println("MEASUREMENT: " + mesString);
// }
//
// @Override
// public void acceptMessage(String message) {
// System.out.println("RECIEVE: " + message);
// }
//
// @Override
// public void acceptRequest(String message) {
// System.out.println("SEND: " + message);
// }
//
// @Override
// public void error(String errorMessage, Throwable error) {
// System.out.println("ERROR: " + errorMessage);
// if (error != null) {
// error.printStackTrace();
// }
// }
// };
//
// MspDevice controller = new MspDevice("127.0.0.1", 10014, listener);
// try {
// controller.init();
// String name = controller.createMeasurement("default", 2, 4, 18, 28);
// controller.setFileamentOn(true);
//
// controller.startMeasurement(name);
// BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
// reader.readLine();
// } finally {
// controller.stop();
// System.exit(0);
// }
}
}

View File

@ -16,7 +16,6 @@
package inr.numass.control.msp package inr.numass.control.msp
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication import inr.numass.control.NumassControlApplication
import javafx.stage.Stage import javafx.stage.Stage
@ -25,10 +24,6 @@ import javafx.stage.Stage
*/ */
class MspApp : NumassControlApplication<MspDevice>() { class MspApp : NumassControlApplication<MspDevice>() {
override fun buildView(device: MspDevice): DeviceViewConnection<MspDevice> {
return MspViewConnection()
}
override val deviceFactory = MspDeviceFactory() override val deviceFactory = MspDeviceFactory()

View File

@ -0,0 +1,504 @@
/*
* 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.msp
import hep.dataforge.context.Context
import hep.dataforge.control.NamedValueListener
import hep.dataforge.control.RoleDef
import hep.dataforge.control.RoleDefs
import hep.dataforge.control.collectors.RegularPointCollector
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.control.devices.*
import hep.dataforge.control.measurements.AbstractMeasurement
import hep.dataforge.control.ports.PortHandler
import hep.dataforge.control.ports.SyncPortController
import hep.dataforge.control.ports.TcpPortHandler
import hep.dataforge.description.ValueDef
import hep.dataforge.events.EventBuilder
import hep.dataforge.exceptions.ControlException
import hep.dataforge.exceptions.MeasurementException
import hep.dataforge.exceptions.PortException
import hep.dataforge.meta.Meta
import hep.dataforge.storage.api.TableLoader
import hep.dataforge.storage.commons.LoaderFactory
import hep.dataforge.tables.TableFormatBuilder
import hep.dataforge.utils.DateTimeUtils
import hep.dataforge.values.Value
import hep.dataforge.values.Values
import inr.numass.control.DeviceView
import inr.numass.control.StorageHelper
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.function.Consumer
/**
* @author Alexander Nozik
*/
@RoleDefs(
RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection::class),
RoleDef(name = Roles.VIEW_ROLE)
)
@StateDefs(
StateDef(value = ValueDef(name = PortSensor.CONNECTED_STATE, info = "Connection with the device itself"), writable = true),
StateDef(value = ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true),
StateDef(value = ValueDef(name = "filament", info = "The number of filament in use"), writable = true),
StateDef(value = ValueDef(name = "filamentOn", info = "Mass-spectrometer filament on"), writable = true),
StateDef(ValueDef(name = "filamentStatus", info = "Filament status"))
)
@DeviceView(MspDisplay::class)
class MspDevice(context: Context, meta: Meta) : Sensor<Values>(context, meta), PortHandler.PortController {
private var handler: TcpPortHandler? = null
private val controller = SyncPortController(this)
private var measurementDelegate: Consumer<MspResponse>? = null
val isConnected: Boolean
get() = getState(PortSensor.CONNECTED_STATE).booleanValue()
val isSelected: Boolean
get() = getState("selected").booleanValue()
val isControlled: Boolean
get() = getState("controlled").booleanValue()
val isFilamentOn: Boolean
get() = getState("filamentOn").booleanValue()
private val averagingDuration: Duration
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
// public MspDevice(String name, Context context, Meta config) {
// super(name, context, config);
// }
@Throws(ControlException::class)
override fun init() {
super.init()
val ip = meta().getString("connection.ip", "127.0.0.1")
val port = meta().getInt("connection.port", 10014)!!
logger.info("Connection to MKS mass-spectrometer on {}:{}...", ip, port)
handler = TcpPortHandler(ip, port)
handler!!.setDelimiter("\r\r")
}
@Throws(ControlException::class)
override fun shutdown() {
super.stopMeasurement(true)
if (isConnected) {
setFilamentOn(false)
setConnected(false)
}
getHandler().close()
super.shutdown()
}
// @Override
// protected Meta getMeasurementMeta() {
// return meta().getMeta("peakJump");
// }
@Throws(MeasurementException::class)
override fun createMeasurement(): PeakJumpMeasurement {
val measurementMeta = meta().getMeta("peakJump")
val s = measurementMeta.getString("type", "peakJump")
if (s == "peakJump") {
val measurement = PeakJumpMeasurement(measurementMeta)
this.measurementDelegate = measurement
return measurement
} else {
throw MeasurementException("Unknown measurement type")
}
}
@Throws(ControlException::class)
override fun computeState(stateName: String): Any {
when (stateName) {
"connected" -> return false
"filament" -> return 1
"filamentOn" -> return false//Always return false on first request
"filamentStatus" -> return "UNKNOWN"
"storing" -> return false
else -> return super.computeState(stateName)
}
}
override fun getType(): String {
return "MKS E-Vision"
}
@Throws(ControlException::class)
override fun requestStateChange(stateName: String, value: Value) {
when (stateName) {
PortSensor.CONNECTED_STATE -> setConnected(value.booleanValue())
"filament" -> selectFilament(value.intValue())
"filamentOn" -> setFilamentOn(value.booleanValue())
else -> super.requestStateChange(stateName, value)
}
}
/**
* Startup MSP: get available sensors, select sensor and control.
*
* @param connected
* @return
* @throws hep.dataforge.exceptions.ControlException
*/
@Throws(ControlException::class)
private fun setConnected(connected: Boolean): Boolean {
val sensorName: String
if (isConnected != connected) {
if (connected) {
getHandler().holdBy(controller)
var response = sendAndWait("Sensors")
if (response.isOK) {
sensorName = response[2, 1]
} else {
portError(response.errorDescription(), null)
return false
}
//PENDING определеить в конфиге номер прибора
response = sendAndWait("Select", sensorName)
if (response.isOK) {
updateState("selected", true)
// selected = true;
} else {
portError(response.errorDescription(), null)
return false
}
response = sendAndWait("Control", "inr.numass.msp", "1.0")
if (response.isOK) {
// controlled = true;
// invalidateState("controlled");
updateState("controlled", true)
} else {
portError(response.errorDescription(), null)
return false
}
// connected = true;
updateState(PortSensor.CONNECTED_STATE, true)
return true
} else {
getHandler().unholdBy(controller)
return !sendAndWait("Release").isOK
}
} else {
return false
}
}
/**
* Send request to the msp
*
* @param command
* @param parameters
* @throws PortException
*/
@Throws(PortException::class)
private fun send(command: String, vararg parameters: Any) {
val request = buildCommand(command, *parameters)
dispatchEvent(
EventBuilder
.make("msp")
.setMetaValue("request", request)
.build()
)
getHandler().send(request)
}
/**
* A helper method to builder msp command string
*
* @param command
* @param parameters
* @return
*/
private fun buildCommand(command: String, vararg parameters: Any): String {
val builder = StringBuilder(command)
for (par in parameters) {
builder.append(String.format(" \"%s\"", par.toString()))
}
builder.append("\n")
return builder.toString()
}
/**
* Send specific command and wait for its results (the result must begin
* with command name)
*
* @param commandName
* @param parameters
* @return
* @throws PortException
*/
@Throws(PortException::class)
private fun sendAndWait(commandName: String, vararg parameters: Any): MspResponse {
val request = buildCommand(commandName, *parameters)
dispatchEvent(
EventBuilder
.make("msp")
.setMetaValue("request", request)
.build()
)
getHandler().send(controller, request)
val response = controller.waitFor(TIMEOUT) { str: String -> str.trim { it <= ' ' }.startsWith(commandName) }
return MspResponse(response)
}
@Throws(PortException::class)
fun selectFilament(filament: Int) {
val response = sendAndWait("FilamentSelect", filament)
if (response.isOK) {
updateState("filament", response[1, 1])
} else {
logger.error("Failed to set filament with error: {}", response.errorDescription())
}
}
/**
* Turn filament on or off
*
* @param filamentOn
* @return
* @throws hep.dataforge.exceptions.PortException
*/
@Throws(PortException::class)
fun setFilamentOn(filamentOn: Boolean): Boolean {
return if (filamentOn) {
sendAndWait("FilamentControl", "On").isOK
} else {
sendAndWait("FilamentControl", "Off").isOK
}
}
/**
* Evaluate general async messages
*
* @param response
*/
private fun evaluateResponse(response: MspResponse) {
}
override fun acceptPortPhrase(message: String) {
dispatchEvent(
EventBuilder
.make("msp")
.setMetaValue("response", message.trim { it <= ' ' }).build()
)
val response = MspResponse(message)
when (response.commandName) {
// all possible async messages
"FilamentStatus" -> {
val status = response[0, 2]
updateState("filamentOn", status == "ON")
updateState("filamentStatus", status)
}
}
if (measurementDelegate != null) {
measurementDelegate!!.accept(response)
}
}
override fun portError(errorMessage: String?, error: Throwable?) {
notifyError(errorMessage, error)
}
private fun getHandler(): TcpPortHandler {
return handler ?: throw RuntimeException("Device not initialized")
}
/**
* The MKS response as two-dimensional array of strings
*/
class MspResponse(response: String) {
private val data = ArrayList<List<String>>()
val commandName: String
get() = this[0, 0]
val isOK: Boolean
get() = "OK" == this[0, 1]
init {
val rx = "[^\"\\s]+|\"(\\\\.|[^\\\\\"])*\""
val scanner = Scanner(response.trim { it <= ' ' })
while (scanner.hasNextLine()) {
val line = ArrayList<String>()
var next: String? = scanner.findWithinHorizon(rx, 0)
while (next != null) {
line.add(next)
next = scanner.findInLine(rx)
}
data.add(line)
}
}
fun errorCode(): Int {
return if (isOK) {
-1
} else {
Integer.parseInt(get(1, 1))
}
}
fun errorDescription(): String? {
return if (isOK) {
null
} else {
get(2, 1)
}
}
operator fun get(lineNo: Int, columnNo: Int): String {
return data[lineNo][columnNo]
}
}
inner class PeakJumpMeasurement(private val meta: Meta) : AbstractMeasurement<Values>(), Consumer<MspResponse> {
private val collector = RegularPointCollector(averagingDuration, Consumer { this.result(it) })
private val helper = StorageHelper(this@MspDevice) { connection: StorageConnection -> this.makeLoader(connection) }
private var peakMap: MutableMap<Int, String> = LinkedHashMap()
private var zero = 0.0
private fun makeLoader(connection: StorageConnection): TableLoader {
val storage = connection.storage
val builder = TableFormatBuilder().addTime("timestamp")
this.peakMap.values.forEach { builder.addNumber(it) }
val format = builder.build()
val suffix = DateTimeUtils.fileSuffix()
return LoaderFactory
.buildPointLoder(storage, "msp_" + suffix, "", "timestamp", format)
}
override fun getDevice(): Device {
return this@MspDevice
}
override fun start() {
try {
val measurementName = "peakJump"
val filterMode = meta.getString("filterMode", "PeakAverage")
val accuracy = meta.getInt("accuracy", 5)!!
//PENDING вставить остальные параметры?
sendAndWait("MeasurementRemoveAll")
if (sendAndWait("AddPeakJump", measurementName, filterMode, accuracy, 0, 0, 0).isOK) {
peakMap.clear()
for (peak in meta.getMetaList("peak")) {
peakMap.put(peak.getInt("mass"), peak.getString("name", peak.getString("mass")))
if (!sendAndWait("MeasurementAddMass", peak.getString("mass")).isOK) {
throw ControlException("Can't add mass to measurement measurement for msp")
}
}
} else {
throw ControlException("Can't create measurement for msp")
}
if (!isFilamentOn) {
this.error("Can't start measurement. Filament is not turned on.", null)
}
if (!sendAndWait("ScanAdd", measurementName).isOK) {
this.error("Failed to add scan", null)
}
if (!sendAndWait("ScanStart", 2).isOK) {
this.error("Failed to start scan", null)
}
} catch (ex: ControlException) {
error(ex)
}
afterStart()
}
@Throws(MeasurementException::class)
override fun stop(force: Boolean): Boolean {
try {
collector.stop()
val stop = sendAndWait("ScanStop").isOK
afterStop()
helper.close()
return stop
} catch (ex: PortException) {
throw MeasurementException(ex)
}
}
@Synchronized override fun result(result: Values, time: Instant) {
super.result(result, time)
helper.push(result)
}
internal fun error(errorMessage: String?, error: Throwable?) {
if (error == null) {
error(MeasurementException(errorMessage))
} else {
error(error)
}
}
override fun accept(response: MspResponse) {
//Evaluating device state change
evaluateResponse(response)
//Evaluating measurement information
when (response.commandName) {
"MassReading" -> {
val mass = java.lang.Double.parseDouble(response[0, 1])
val value = java.lang.Double.parseDouble(response[0, 2]) / 100.0
val massName = Integer.toString(Math.floor(mass + 0.5).toInt())
collector.put(massName, value)
forEachConnection(Roles.VIEW_ROLE, NamedValueListener::class.java) { listener -> listener.pushValue(massName, value) }
}
"ZeroReading" -> zero = java.lang.Double.parseDouble(response[0, 2]) / 100.0
"StartingScan" -> {
val numScans = Integer.parseInt(response[0, 3])
if (numScans == 0) {
try {
send("ScanResume", 10)
//FIXME обработать ошибку связи
} catch (ex: PortException) {
error(null, ex)
}
}
}
}
}
}
companion object {
val MSP_DEVICE_TYPE = "numass.msp"
private val TIMEOUT = Duration.ofMillis(200)
}
}

View File

@ -1,25 +1,18 @@
package inr.numass.control.msp package inr.numass.control.msp
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceViewFactory
/** /**
* Created by darksnake on 09-May-17. * Created by darksnake on 09-May-17.
*/ */
class MspDeviceFactory : DeviceViewFactory<MspDevice> { class MspDeviceFactory: DeviceFactory {
override fun getType(): String { override fun getType(): String {
return MspDevice.MSP_DEVICE_TYPE return MspDevice.MSP_DEVICE_TYPE
} }
override fun build(context: Context, config: Meta): MspDevice { override fun build(context: Context, config: Meta): MspDevice {
val device = MspDevice(context, config) return MspDevice(context, config)
return device
}
override fun buildView(device: Device): DeviceViewConnection<MspDevice> {
return MspViewConnection()
} }
} }

View File

@ -30,7 +30,7 @@ import hep.dataforge.plots.data.TimePlot
import hep.dataforge.plots.data.TimePlottableGroup import hep.dataforge.plots.data.TimePlottableGroup
import hep.dataforge.plots.jfreechart.JFreeChartFrame import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.values.Value import hep.dataforge.values.Value
import inr.numass.control.DeviceViewConnection import inr.numass.control.DeviceDisplay
import inr.numass.control.deviceStateIndicator import inr.numass.control.deviceStateIndicator
import inr.numass.control.deviceStateToggle import inr.numass.control.deviceStateToggle
import inr.numass.control.switch import inr.numass.control.switch
@ -51,7 +51,7 @@ import tornadofx.*
* @author darksnake * @author darksnake
*/ */
class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, NamedValueListener { class MspDisplay() : DeviceDisplay<MspDevice>(), DeviceListener, NamedValueListener {
private val table = FXCollections.observableHashMap<String, Value>() private val table = FXCollections.observableHashMap<String, Value>()
@ -71,9 +71,9 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
inner class MspView : View("Numass mass-spectrometer measurement") { inner class MspView : View("Numass mass-spectrometer measurement") {
val plotFrameMeta: Meta = device.meta().getMeta("plotConfig", device.meta) private val plotFrameMeta: Meta = device.meta().getMeta("plotConfig", device.meta)
val plotFrame: PlotFrame by lazy { private val plotFrame: PlotFrame by lazy {
val basePlotConfig = MetaBuilder("plotFrame") val basePlotConfig = MetaBuilder("plotFrame")
.setNode(MetaBuilder("yAxis") .setNode(MetaBuilder("yAxis")
.setValue("type", "log") .setValue("type", "log")
@ -113,7 +113,7 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
// addLogHandler(device.logger) // addLogHandler(device.logger)
// }) // })
val filamentProperty = SimpleObjectProperty<Int>(this, "filament", 1).apply { private val filamentProperty = SimpleObjectProperty<Int>(this, "filament", 1).apply {
addListener { _, oldValue, newValue -> addListener { _, oldValue, newValue ->
if (newValue != oldValue) { if (newValue != oldValue) {
runAsync { runAsync {
@ -128,7 +128,7 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
minWidth = 600.0 minWidth = 600.0
top { top {
toolbar { toolbar {
deviceStateToggle(this@MspViewConnection, PortSensor.CONNECTED_STATE, "Connect") deviceStateToggle(this@MspDisplay, PortSensor.CONNECTED_STATE, "Connect")
combobox(filamentProperty, listOf(1, 2)) { combobox(filamentProperty, listOf(1, 2)) {
cellFormat { cellFormat {
text = "Filament $it" text = "Filament $it"
@ -142,7 +142,7 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
.bind(getStateBinding(PortSensor.CONNECTED_STATE).booleanBinding { !it!!.booleanValue() }) .bind(getStateBinding(PortSensor.CONNECTED_STATE).booleanBinding { !it!!.booleanValue() })
bindBooleanToState("filamentOn", selectedProperty()) bindBooleanToState("filamentOn", selectedProperty())
} }
deviceStateIndicator(this@MspViewConnection, "filamentStatus", false) { deviceStateIndicator(this@MspDisplay, "filamentStatus", false) {
when (it.stringValue()) { when (it.stringValue()) {
"ON" -> Paint.valueOf("red") "ON" -> Paint.valueOf("red")
"OFF" -> Paint.valueOf("blue") "OFF" -> Paint.valueOf("blue")

View File

@ -25,7 +25,7 @@ limitations under the License.
<?import org.controlsfx.control.ToggleSwitch?> <?import org.controlsfx.control.ToggleSwitch?>
<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="400.0" minWidth="600.0" <BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="400.0" minWidth="600.0"
xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="inr.numass.control.msp.MspViewConnection"> fx:controller="inr.numass.control.msp.MspDisplay">
<top> <top>
<ToolBar prefHeight="50.0" prefWidth="200.0"> <ToolBar prefHeight="50.0" prefWidth="200.0">
<ToggleButton fx:id="connectButton" mnemonicParsing="false" text="Connect"/> <ToggleButton fx:id="connectButton" mnemonicParsing="false" text="Connect"/>

View File

@ -1,6 +1,7 @@
package inr.numass.control package inr.numass.control
import hep.dataforge.control.Connection import hep.dataforge.control.Connection
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceListener import hep.dataforge.control.devices.DeviceListener
import hep.dataforge.control.devices.PortSensor import hep.dataforge.control.devices.PortSensor
@ -17,33 +18,44 @@ import javafx.scene.layout.HBox
import javafx.scene.layout.Priority import javafx.scene.layout.Priority
import tornadofx.* import tornadofx.*
import java.util.* import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class DeviceView(val value: KClass<out DeviceDisplay<*>>)
/** /**
* Get existing view connection or create a new one
*/
fun Device.getDisplay(): DeviceDisplay<*> {
val type = DefaultDisplay::class;
return optConnection(Roles.VIEW_ROLE, DeviceDisplay::class.java).orElseGet {
type.createInstance().also {
connect(it, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE);
}
}
}
/**
*
* An FX View to represent the device
* Created by darksnake on 14-May-17. * Created by darksnake on 14-May-17.
*/ */
abstract class DeviceViewConnection<D : Device> : Component(), Connection, DeviceListener { abstract class DeviceDisplay<D : Device> : Component(), Connection, DeviceListener {
private val bindings = HashMap<String, ObjectBinding<Value>>() private val bindings = HashMap<String, ObjectBinding<Value>>()
private val deviceProperty = SimpleObjectProperty<D>(this, "device", null) private val deviceProperty = SimpleObjectProperty<D>(this, "device", null)
val device: D val device: D by deviceProperty
get() {
val res = deviceProperty.get();
if (res == null) {
throw RuntimeException("Not connected!");
} else {
return res
}
}
private val viewProperty = SimpleObjectProperty<View>(this, "view", null) // private val viewProperty = SimpleObjectProperty<UIComponent>(this, "view", null)
val view: View val view: UIComponent? by lazy {
get() { buildView(device)
if (viewProperty.get() == null) { }
viewProperty.set(buildView(device))
}
return viewProperty.get();
}
override fun isOpen(): Boolean { override fun isOpen(): Boolean {
return this.deviceProperty.get() != null return this.deviceProperty.get() != null
@ -60,13 +72,13 @@ abstract class DeviceViewConnection<D : Device> : Component(), Connection, Devic
} }
override fun close() { override fun close() {
if (viewProperty.isNotNull.get()) { if (isOpen) {
view.close() view?.close()
deviceProperty.set(null)
} }
deviceProperty.set(null)
} }
abstract fun buildView(device: D): View; abstract fun buildView(device: D): UIComponent?;
/** /**
* Get binding for a given device state * Get binding for a given device state
@ -125,17 +137,31 @@ abstract class DeviceViewConnection<D : Device> : Component(), Connection, Devic
return HBox().apply { return HBox().apply {
alignment = Pos.CENTER_LEFT alignment = Pos.CENTER_LEFT
vgrow = Priority.ALWAYS; vgrow = Priority.ALWAYS;
deviceStateIndicator(this@DeviceViewConnection, Device.INITIALIZED_STATE) deviceStateIndicator(this@DeviceDisplay, Device.INITIALIZED_STATE)
deviceStateIndicator(this@DeviceViewConnection, PortSensor.CONNECTED_STATE) deviceStateIndicator(this@DeviceDisplay, PortSensor.CONNECTED_STATE)
deviceStateIndicator(this@DeviceViewConnection, Sensor.MEASURING_STATE) deviceStateIndicator(this@DeviceDisplay, Sensor.MEASURING_STATE)
deviceStateIndicator(this@DeviceViewConnection, "storing") deviceStateIndicator(this@DeviceDisplay, "storing")
pane { pane {
hgrow = Priority.ALWAYS hgrow = Priority.ALWAYS
} }
togglebutton("View") { togglebutton("View") {
isSelected = false isSelected = false
view.bindWindow(this.selectedProperty()) if (view == null) {
isDisable = true
}
view?.bindWindow(selectedProperty())
} }
} }
} }
} }
/**
* Default display shows only board pane and nothing else
*/
class DefaultDisplay() : DeviceDisplay<Device>() {
override fun buildView(device: Device): UIComponent? {
return null;
}
}

View File

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

View File

@ -76,7 +76,7 @@ class Indicator(radius: Double = 10.0) : Circle(radius, Color.GRAY) {
fun EventTarget.indicator(radius: Double = 10.0, op: (Indicator.() -> Unit)? = null) = opcr(this, Indicator(radius), op) fun EventTarget.indicator(radius: Double = 10.0, op: (Indicator.() -> Unit)? = null) = opcr(this, Indicator(radius), op)
fun Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform: ((Value) -> Paint)? = null) { fun Indicator.bind(connection: DeviceDisplay<*>, state: String, transform: ((Value) -> Paint)? = null) {
tooltip(state) tooltip(state)
if (transform != null) { if (transform != null) {
bind(connection.getStateBinding(state), transform); bind(connection.getStateBinding(state), transform);
@ -96,7 +96,7 @@ fun Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform
/** /**
* State name + indicator * State name + indicator
*/ */
fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state: String, showName: Boolean = true, transform: ((Value) -> Paint)? = null) { fun EventTarget.deviceStateIndicator(connection: DeviceDisplay<*>, state: String, showName: Boolean = true, transform: ((Value) -> Paint)? = null) {
if (connection.device.hasState(state)) { if (connection.device.hasState(state)) {
if (showName) { if (showName) {
text("${state.toUpperCase()}: ") text("${state.toUpperCase()}: ")
@ -113,7 +113,7 @@ fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state:
/** /**
* A togglebutton + indicator for boolean state * A togglebutton + indicator for boolean state
*/ */
fun Node.deviceStateToggle(connection: DeviceViewConnection<*>, state: String, title: String = state) { fun Node.deviceStateToggle(connection: DeviceDisplay<*>, state: String, title: String = state) {
if (connection.device.hasState(state)) { if (connection.device.hasState(state)) {
togglebutton(title) { togglebutton(title) {
isSelected = false isSelected = false

View File

@ -3,9 +3,9 @@ package inr.numass.control
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import hep.dataforge.control.connections.Roles import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.exceptions.ControlException import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.utils.ContextMetaFactory
import javafx.scene.Scene import javafx.scene.Scene
import javafx.stage.Stage import javafx.stage.Stage
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -25,9 +25,9 @@ abstract class NumassControlApplication<D : Device> : App() {
rootLogger.level = Level.INFO rootLogger.level = Level.INFO
device = setupDevice() device = setupDevice()
val controller = buildView(device) val controller = device.getDisplay()
device.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) device.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
val scene = Scene(controller.view.root) val scene = Scene(controller.view?.root ?: controller.getBoardView())
stage.scene = scene stage.scene = scene
stage.show() stage.show()
@ -35,19 +35,12 @@ abstract class NumassControlApplication<D : Device> : App() {
setDFStageIcon(stage) setDFStageIcon(stage)
} }
/**
* Build a view connection
* @return
*/
protected abstract fun buildView(device: D): DeviceViewConnection<D>
/** /**
* Get a device factory for given device * Get a device factory for given device
* @return * @return
*/ */
protected abstract val deviceFactory: ContextMetaFactory<D> protected abstract val deviceFactory: DeviceFactory
protected abstract fun setupStage(stage: Stage, device: D) protected abstract fun setupStage(stage: Stage, device: D)

View File

@ -2,7 +2,6 @@ package inr.numass.control
import hep.dataforge.control.connections.StorageConnection import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.control.devices.AbstractDevice import hep.dataforge.control.devices.AbstractDevice
import hep.dataforge.exceptions.StorageException
import hep.dataforge.storage.api.TableLoader import hep.dataforge.storage.api.TableLoader
import hep.dataforge.values.Values import hep.dataforge.values.Values
import java.util.* import java.util.*
@ -11,16 +10,16 @@ import java.util.*
* A helper to store points in multiple loaders * A helper to store points in multiple loaders
* Created by darksnake on 16-May-17. * Created by darksnake on 16-May-17.
*/ */
class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection)-> TableLoader) : AutoCloseable { class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection) -> TableLoader) : AutoCloseable {
private val loaderMap = HashMap<StorageConnection, TableLoader>() private val loaderMap = HashMap<StorageConnection, TableLoader>()
fun push(point: Values) { fun push(point: Values) {
if (!device.hasState("storing") || device.getState("storing").booleanValue()) { if (!device.hasState("storing") || device.getState("storing").booleanValue()) {
device.forEachConnection("storage", StorageConnection::class.java) { connection -> device.forEachConnection("storage", StorageConnection::class.java) { connection ->
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
try { try {
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
pl.push(point) pl.push(point)
} catch (ex: StorageException) { } catch (ex: Exception) {
device.logger.error("Push to loader failed", ex) device.logger.error("Push to loader failed", ex)
} }
} }

View File

@ -15,17 +15,13 @@ import hep.dataforge.control.ports.PortFactory
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 hep.dataforge.meta.Meta
import inr.numass.control.DeviceView
/** /**
* @author Alexander Nozik * @author Alexander Nozik
*/ */
class CM32Device : PortSensor<Double> { @DeviceView(VacDisplay::class)
constructor() {} class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@Throws(ControlException::class) @Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler { override fun buildHandler(portName: String): PortHandler {
@ -54,7 +50,7 @@ class CM32Device : PortSensor<Double> {
@Throws(Exception::class) @Throws(Exception::class)
override fun doMeasure(): Double? { override fun doMeasure(): Double? {
val answer = sendAndWait(CM32_QUERY, timeout()) val answer = sendAndWait("MES R PM 1\r\n", timeout())
if (answer.isEmpty()) { if (answer.isEmpty()) {
this.updateMessage("No signal") this.updateMessage("No signal")
@ -82,9 +78,4 @@ class CM32Device : PortSensor<Double> {
} }
companion object {
private val CM32_QUERY = "MES R PM 1\r\n"
}
} }

View File

@ -14,22 +14,16 @@ 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 hep.dataforge.meta.Meta
import inr.numass.control.DeviceView
/** /**
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@ValueDef(name = "channel") @ValueDef(name = "channel")
class MKSBaratronDevice : PortSensor<Double> { @DeviceView(VacDisplay::class)
class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
private val channel: Int private val channel: Int = meta().getInt("channel", 2)
get() = meta().getInt("channel", 2)!!
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
override fun createMeasurement(): Measurement<Double> { override fun createMeasurement(): Measurement<Double> {

View File

@ -13,10 +13,12 @@ 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.description.ValueDefs
import hep.dataforge.exceptions.ControlException import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.ValueType.BOOLEAN import hep.dataforge.values.ValueType.BOOLEAN
import inr.numass.control.DeviceView
import javafx.beans.property.BooleanProperty import javafx.beans.property.BooleanProperty
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder
import java.util.regex.Pattern import java.util.regex.Pattern
@ -24,22 +26,20 @@ import java.util.regex.Pattern
/** /**
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@ValueDef(name = "address", def = "253") @ValueDefs(
@ValueDef(name = "channel", def = "5") ValueDef(name = "address", def = "253"),
@ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "true") ValueDef(name = "channel", def = "5"),
ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "true")
)
@StateDef(value = ValueDef(name = "power", info = "Device powered up"), writable = true) @StateDef(value = ValueDef(name = "power", info = "Device powered up"), writable = true)
class MKSVacDevice : PortSensor<Double> { @DeviceView(VacDisplay::class)
class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
private//PENDING cache this? private val deviceAddress: String
val deviceAddress: String
get() = meta().getString("address", "253") get() = meta().getString("address", "253")
private// String ans = talkMKS(p1Port, "@253ENC!OFF;FF"); private var isPowerOn: Boolean
// if (!ans.equals("OFF")) {
// LoggerFactory.getLogger(getClass()).warn("The @253ENC!OFF;FF command is not working");
// }
var isPowerOn: Boolean
get() = getState("power").booleanValue() get() = getState("power").booleanValue()
@Throws(ControlException::class) @Throws(ControlException::class)
set(powerOn) { set(powerOn) {
@ -62,15 +62,7 @@ class MKSVacDevice : PortSensor<Double> {
} }
} }
private val channel: Int private val channel: Int = meta().getInt("channel", 5)!!
get() = meta().getInt("channel", 5)!!
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@Throws(ControlException::class) @Throws(ControlException::class)
private fun talk(requestContent: String): String? { private fun talk(requestContent: String): String? {
@ -97,9 +89,9 @@ class MKSVacDevice : PortSensor<Double> {
@Throws(ControlException::class) @Throws(ControlException::class)
override fun computeState(stateName: String): Any { override fun computeState(stateName: String): Any {
when (stateName) { return when (stateName) {
"power" -> return talk("FP?") == "ON" "power" -> talk("FP?") == "ON"
else -> return super.computeState(stateName) else -> super.computeState(stateName)
} }
} }

View File

@ -24,14 +24,7 @@ import java.util.regex.Pattern
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@ValueDef(name = "address", type = arrayOf(NUMBER), def = "1", info = "A modbus address") @ValueDef(name = "address", type = arrayOf(NUMBER), def = "1", info = "A modbus address")
class MeradatVacDevice : PortSensor<Double> { class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@Throws(ControlException::class) @Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler { override fun buildHandler(portName: String): PortHandler {
@ -111,14 +104,13 @@ class MeradatVacDevice : PortSensor<Double> {
for (aByte in bytes) { for (aByte in bytes) {
checksum += aByte.toInt() checksum += aByte.toInt()
} }
var `val` = Integer.toHexString(-checksum) var value = Integer.toHexString(-checksum)
`val` = `val`.substring(`val`.length - 2).toUpperCase() value = value.substring(value.length - 2).toUpperCase()
if (`val`.length < 2) { if (value.length < 2) {
`val` = "0" + `val` value = "0" + value
} }
return `val` return value
} }
} }
} }

View File

@ -6,8 +6,6 @@
package inr.numass.control.readvac package inr.numass.control.readvac
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.utils.ContextMetaFactory
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication import inr.numass.control.NumassControlApplication
import javafx.stage.Stage import javafx.stage.Stage
@ -15,11 +13,8 @@ import javafx.stage.Stage
* @author Alexander Nozik * @author Alexander Nozik
*/ */
class ReadVac : NumassControlApplication<VacCollectorDevice>() { class ReadVac : NumassControlApplication<VacCollectorDevice>() {
override fun buildView(device: VacCollectorDevice): DeviceViewConnection<VacCollectorDevice> {
return VacCollectorViewConnection()
}
override val deviceFactory: ContextMetaFactory<VacCollectorDevice> = VacDeviceFactory() override val deviceFactory = VacDeviceFactory()
override fun setupStage(stage: Stage, device: VacCollectorDevice) { override fun setupStage(stage: Stage, device: VacCollectorDevice) {
stage.title = "Numass vacuum measurements" stage.title = "Numass vacuum measurements"

View File

@ -6,6 +6,7 @@
package inr.numass.control.readvac package inr.numass.control.readvac
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.control.Connection
import hep.dataforge.control.RoleDef import hep.dataforge.control.RoleDef
import hep.dataforge.control.collectors.RegularPointCollector import hep.dataforge.control.collectors.RegularPointCollector
import hep.dataforge.control.connections.Roles import hep.dataforge.control.connections.Roles
@ -29,6 +30,7 @@ import hep.dataforge.utils.DateTimeUtils
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.Values import hep.dataforge.values.Values
import inr.numass.control.DeviceView
import inr.numass.control.StorageHelper import inr.numass.control.StorageHelper
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@ -44,48 +46,27 @@ import java.util.stream.Stream
*/ */
@RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection::class, info = "Storage for acquired points") @RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection::class, info = "Storage for acquired points")
@StateDef(value = ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true) @StateDef(value = ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true)
class VacCollectorDevice : Sensor<Values>, DeviceHub { @DeviceView(VacCollectorDisplay::class)
class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<Sensor<Double>>) : Sensor<Values>(context, meta), DeviceHub {
private var sensorMap: MutableMap<String, Sensor<Double>> = LinkedHashMap()
private val helper = StorageHelper(this, this::buildLoader) private val helper = StorageHelper(this, this::buildLoader)
val sensors: Collection<Sensor<Double>>
get() = sensorMap.values
private val averagingDuration: Duration private val averagingDuration: Duration
get() = Duration.parse(meta().getString("averagingDuration", "PT30S")) get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
override fun optDevice(name: Name): Optional<Device> { override fun optDevice(name: Name): Optional<Device> {
return Optional.ofNullable(sensorMap.get(name.toString())) return Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
} }
override fun deviceNames(): Stream<Name> { override fun deviceNames(): Stream<Name> {
return sensorMap.keys.stream().map { Name.ofSingle(it) } return sensors.stream().map { Name.ofSingle(it.name) }
} }
fun setSensors(sensors: Iterable<Sensor<Double>>) {
sensorMap = LinkedHashMap()
for (sensor in sensors) {
sensorMap.put(sensor.name, sensor)
}
}
fun setSensors(vararg sensors: Sensor<Double>) {
setSensors(Arrays.asList(*sensors))
}
@Throws(ControlException::class)
override fun init() { override fun init() {
super.init() super.init()
for (s in sensorMap.values) { for (s in sensors) {
s.init() s.init()
} }
} }
@ -118,6 +99,16 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
return LoaderFactory.buildPointLoder(connection.storage, "vactms_" + suffix, "", "timestamp", format.build()) return LoaderFactory.buildPointLoder(connection.storage, "vactms_" + suffix, "", "timestamp", format.build())
} }
override fun connectAll(connection: Connection, vararg roles: String) {
connect(connection, *roles)
this.sensors.forEach { it.connect(connection, *roles) }
}
override fun connectAll(context: Context, meta: Meta) {
this.connectionHelper.connect(context, meta)
this.sensors.forEach { it.connectionHelper.connect(context, meta) }
}
private inner class VacuumMeasurement : AbstractMeasurement<Values>() { private inner class VacuumMeasurement : AbstractMeasurement<Values>() {
private val collector = RegularPointCollector(averagingDuration) { this.result(it) } private val collector = RegularPointCollector(averagingDuration) { this.result(it) }
@ -133,7 +124,7 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
executor = Executors.newSingleThreadScheduledExecutor { r: Runnable -> Thread(r, "VacuumMeasurement thread") } executor = Executors.newSingleThreadScheduledExecutor { r: Runnable -> Thread(r, "VacuumMeasurement thread") }
val delay = meta().getInt("delay", 5)!! * 1000 val delay = meta().getInt("delay", 5)!! * 1000
currentTask = executor!!.scheduleWithFixedDelay({ currentTask = executor!!.scheduleWithFixedDelay({
sensorMap.values.forEach { sensor -> sensors.forEach { sensor ->
try { try {
val value: Any? val value: Any?
value = if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) { value = if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) {
@ -158,7 +149,7 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
private fun terminator(): Values { private fun terminator(): Values {
val p = ValueMap.Builder() val p = ValueMap.Builder()
p.putValue("timestamp", DateTimeUtils.now()) p.putValue("timestamp", DateTimeUtils.now())
sensorMap.keys.forEach { n -> p.putValue(n, null) } deviceNames().forEach { n -> p.putValue(n.toUnescaped(), null) }
return p.build() return p.build()
} }
@ -175,5 +166,4 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
return isRunning return isRunning
} }
} }
} }

View File

@ -15,7 +15,7 @@ import hep.dataforge.fx.fragments.LogFragment
import hep.dataforge.plots.data.TimePlot import hep.dataforge.plots.data.TimePlot
import hep.dataforge.plots.data.TimePlottableGroup import hep.dataforge.plots.data.TimePlottableGroup
import hep.dataforge.values.Value import hep.dataforge.values.Value
import inr.numass.control.DeviceViewConnection import inr.numass.control.DeviceDisplay
import inr.numass.control.deviceStateToggle import inr.numass.control.deviceStateToggle
import inr.numass.control.plot import inr.numass.control.plot
import javafx.collections.FXCollections import javafx.collections.FXCollections
@ -31,13 +31,13 @@ import java.time.Instant
* @author [Alexander Nozik](mailto:altavir@gmail.com) * @author [Alexander Nozik](mailto:altavir@gmail.com)
*/ */
class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() { class VacCollectorDisplay : DeviceDisplay<VacCollectorDevice>() {
private val table = FXCollections.observableHashMap<String, Double>() private val table = FXCollections.observableHashMap<String, Double>()
private val sensorConnection = object : MeasurementListener, Connection{ private val sensorConnection = object : MeasurementListener, Connection {
override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant?) { override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant?) {
if(result is Double){ if (result is Double) {
table.put(measurement.device.name, result); table.put(measurement.device.name, result);
} }
} }
@ -47,7 +47,7 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
} }
} }
private val viewList = FXCollections.observableArrayList<VacViewConnection>(); private val viewList = FXCollections.observableArrayList<VacDisplay>();
override fun buildView(device: VacCollectorDevice): View { override fun buildView(device: VacCollectorDevice): View {
return VacCollectorView(); return VacCollectorView();
@ -56,7 +56,7 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
override fun open(obj: Any) { override fun open(obj: Any) {
super.open(obj) super.open(obj)
device.sensors.forEach { sensor -> device.sensors.forEach { sensor ->
val view = VacViewConnection() val view = VacDisplay()
sensor.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) sensor.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
sensor.connect(sensorConnection, Roles.MEASUREMENT_LISTENER_ROLE); sensor.connect(sensorConnection, Roles.MEASUREMENT_LISTENER_ROLE);
viewList.add(view) viewList.add(view)
@ -81,8 +81,8 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
override val root = borderpane { override val root = borderpane {
top { top {
toolbar { toolbar {
deviceStateToggle(this@VacCollectorViewConnection, Sensor.MEASURING_STATE, "Measure") deviceStateToggle(this@VacCollectorDisplay, Sensor.MEASURING_STATE, "Measure")
deviceStateToggle(this@VacCollectorViewConnection, "storing", "Store") deviceStateToggle(this@VacCollectorDisplay, "storing", "Store")
pane { pane {
hgrow = Priority.ALWAYS hgrow = Priority.ALWAYS
} }
@ -109,8 +109,10 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
vbox { vbox {
viewList.forEach { viewList.forEach {
add(it.view) it.view?.let {
separator(Orientation.HORIZONTAL) add(it)
separator(Orientation.HORIZONTAL)
}
} }
} }
} }

View File

@ -1,46 +1,41 @@
package inr.numass.control.readvac package inr.numass.control.readvac
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.control.devices.Sensor import hep.dataforge.control.devices.Sensor
import hep.dataforge.control.virtual.VirtualDevice import hep.dataforge.control.virtual.VirtualDevice
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceViewFactory
import java.util.stream.Collectors import java.util.stream.Collectors
/** /**
* A factory for vacuum measurements collector * A factory for vacuum measurements collector
* Created by darksnake on 16-May-17. * Created by darksnake on 16-May-17.
*/ */
class VacDeviceFactory : DeviceViewFactory<VacCollectorDevice> { class VacDeviceFactory : DeviceFactory {
override fun getType(): String { override fun getType(): String {
return "numass:vac" return "numass.vac"
} }
fun buildSensor(context: Context, sensorConfig: Meta): Sensor<Double> { private fun buildSensor(context: Context, sensorConfig: Meta): Sensor<Double> {
when (sensorConfig.getString("sensorType", "")) { return when (sensorConfig.getString("sensorType", "")) {
"mks" -> return MKSVacDevice(context, sensorConfig) "mks" -> MKSVacDevice(context, sensorConfig)
"CM32" -> return CM32Device(context, sensorConfig) "CM32" -> CM32Device(context, sensorConfig)
"meradat" -> return MeradatVacDevice(context, sensorConfig) "meradat" -> MeradatVacDevice(context, sensorConfig)
"baratron" -> return MKSBaratronDevice(context, sensorConfig) "baratron" -> MKSBaratronDevice(context, sensorConfig)
VirtualDevice.VIRTUAL_SENSOR_TYPE -> return VirtualDevice.randomDoubleSensor(context, sensorConfig) VirtualDevice.VIRTUAL_SENSOR_TYPE -> VirtualDevice.randomDoubleSensor(context, sensorConfig)
else -> throw RuntimeException("Unknown vacuum sensor type") else -> throw RuntimeException("Unknown vacuum sensor type")
} }
} }
override fun build(context: Context, config: Meta): VacCollectorDevice { override fun build(context: Context, config: Meta): VacCollectorDevice {
val sensors = config.getMetaList("sensor").stream() val sensors = config.getMetaList("sensor").stream()
.map { sensorConfig -> .map { sensorConfig -> buildSensor(context, sensorConfig) }
buildSensor(context, sensorConfig) .collect(Collectors.toList<Sensor<Double>>())
}.collect(Collectors.toList<Sensor<Double>>())
val collector = VacCollectorDevice(context, config) return VacCollectorDevice(context, config, sensors)
collector.setSensors(sensors)
return collector
} }
override fun buildView(device: Device): DeviceViewConnection<VacCollectorDevice> { // override fun buildView(device: Device): DeviceDisplay<VacCollectorDevice> {
return VacCollectorViewConnection(); // return VacCollectorDisplay();
} // }
} }

View File

@ -10,7 +10,7 @@ import hep.dataforge.control.devices.PortSensor.CONNECTED_STATE
import hep.dataforge.control.devices.Sensor import hep.dataforge.control.devices.Sensor
import hep.dataforge.control.measurements.Measurement import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.measurements.MeasurementListener import hep.dataforge.control.measurements.MeasurementListener
import inr.numass.control.DeviceViewConnection import inr.numass.control.DeviceDisplay
import inr.numass.control.switch import inr.numass.control.switch
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
@ -31,7 +31,7 @@ import java.time.format.DateTimeFormatter
/** /**
* @author [Alexander Nozik](mailto:altavir@gmail.com) * @author [Alexander Nozik](mailto:altavir@gmail.com)
*/ */
open class VacViewConnection : DeviceViewConnection<Sensor<Double>>(), MeasurementListener { open class VacDisplay : DeviceDisplay<Sensor<Double>>(), MeasurementListener {
val statusProperty = SimpleStringProperty("") val statusProperty = SimpleStringProperty("")
var status: String by statusProperty var status: String by statusProperty

View File

@ -59,7 +59,7 @@ public class Numass {
am.getAllActions() am.getAllActions()
.map(name -> am.optAction(name).get()) .map(name -> am.optAction(name).get())
.map(action -> ActionDescriptor.build(action)).forEach(descriptor -> .map(ActionDescriptor::build).forEach(descriptor ->
builder.text("\t").content(MarkupUtils.markupDescriptor(descriptor)) builder.text("\t").content(MarkupUtils.markupDescriptor(descriptor))
); );