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.Global
import hep.dataforge.control.DeviceManager
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.meta.Meta
@ -16,13 +17,12 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import tornadofx.*
import java.io.File
/**
* Created by darksnake on 12-May-17.
*/
class BoardController() : Controller(), AutoCloseable {
val devices: ObservableList<DeviceViewConnection<*>> = FXCollections.observableArrayList<DeviceViewConnection<*>>();
val devices: ObservableList<DeviceDisplay<*>> = FXCollections.observableArrayList<DeviceDisplay<*>>();
val contextProperty = SimpleObjectProperty<Context>(Global.instance())
var context: Context by contextProperty
@ -39,15 +39,8 @@ class BoardController() : Controller(), AutoCloseable {
fun load(app: Application) {
runAsync {
getConfig(app).ifPresent {
val libDir = File(app.parameters.named.getOrDefault("libPath", "../lib"));
val contextBuilder = Context
.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);
val context = Context.build("NUMASS", Global.instance(), it)
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)
val factory = context.serviceStream(DeviceViewFactory::class.java)
.filter { it.type == deviceMeta.getString("type") }
.findFirst();
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");
}
val device = context.loadFeature("devices", DeviceManager::class.java).buildDevice(deviceMeta)
device.init();
return device.getDisplay();
}
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
import hep.dataforge.control.connections.Roles
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication
import javafx.stage.Stage
@ -25,11 +23,6 @@ import javafx.stage.Stage
* @author darksnake
*/
class PKT8App : NumassControlApplication<PKT8Device>() {
override fun buildView(device: PKT8Device): DeviceViewConnection<PKT8Device> {
return PKT8ViewConnection().apply {
device.connect(this, Roles.VIEW_ROLE)
}
}
override val deviceFactory = 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
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceViewFactory
/**
* Created by darksnake on 09-May-17.
*/
class PKT8DeviceFactory : DeviceViewFactory<PKT8Device> {
class PKT8DeviceFactory : DeviceFactory {
override fun getType(): String {
return PKT8Device.PKT8_DEVICE_TYPE
}
@ -17,8 +15,4 @@ class PKT8DeviceFactory : DeviceViewFactory<PKT8Device> {
override fun build(context: Context, meta: Meta): PKT8Device {
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.TimePlottableGroup
import hep.dataforge.plots.jfreechart.JFreeChartFrame
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceDisplay
import javafx.application.Platform
import javafx.beans.binding.ListBinding
import javafx.beans.property.SimpleObjectProperty
@ -31,7 +31,7 @@ import java.time.Instant
/**
* Created by darksnake on 30-May-17.
*/
class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListener {
class PKT8Display : DeviceDisplay<PKT8Device>(), MeasurementListener {
override fun buildView(device: PKT8Device): View {
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.text.Font?>
<BorderPane fx:id="root" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="400.0"
xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8ViewConnection">
xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8Display">
<center>
<TableView fx:id="table" BorderPane.alignment="CENTER">
<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
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication
import javafx.stage.Stage
@ -25,10 +24,6 @@ import javafx.stage.Stage
*/
class MspApp : NumassControlApplication<MspDevice>() {
override fun buildView(device: MspDevice): DeviceViewConnection<MspDevice> {
return MspViewConnection()
}
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
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceViewFactory
/**
* Created by darksnake on 09-May-17.
*/
class MspDeviceFactory : DeviceViewFactory<MspDevice> {
class MspDeviceFactory: DeviceFactory {
override fun getType(): String {
return MspDevice.MSP_DEVICE_TYPE
}
override fun build(context: Context, config: Meta): MspDevice {
val device = MspDevice(context, config)
return device
}
override fun buildView(device: Device): DeviceViewConnection<MspDevice> {
return MspViewConnection()
return MspDevice(context, config)
}
}

View File

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

View File

@ -25,7 +25,7 @@ limitations under the License.
<?import org.controlsfx.control.ToggleSwitch?>
<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"
fx:controller="inr.numass.control.msp.MspViewConnection">
fx:controller="inr.numass.control.msp.MspDisplay">
<top>
<ToolBar prefHeight="50.0" prefWidth="200.0">
<ToggleButton fx:id="connectButton" mnemonicParsing="false" text="Connect"/>

View File

@ -1,6 +1,7 @@
package inr.numass.control
import hep.dataforge.control.Connection
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceListener
import hep.dataforge.control.devices.PortSensor
@ -17,33 +18,44 @@ import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import tornadofx.*
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.
*/
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 deviceProperty = SimpleObjectProperty<D>(this, "device", null)
val device: D
get() {
val res = deviceProperty.get();
if (res == null) {
throw RuntimeException("Not connected!");
} else {
return res
}
}
val device: D by deviceProperty
private val viewProperty = SimpleObjectProperty<View>(this, "view", null)
val view: View
get() {
if (viewProperty.get() == null) {
viewProperty.set(buildView(device))
}
return viewProperty.get();
}
// private val viewProperty = SimpleObjectProperty<UIComponent>(this, "view", null)
val view: UIComponent? by lazy {
buildView(device)
}
override fun isOpen(): Boolean {
return this.deviceProperty.get() != null
@ -60,13 +72,13 @@ abstract class DeviceViewConnection<D : Device> : Component(), Connection, Devic
}
override fun close() {
if (viewProperty.isNotNull.get()) {
view.close()
if (isOpen) {
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
@ -125,17 +137,31 @@ abstract class DeviceViewConnection<D : Device> : Component(), Connection, Devic
return HBox().apply {
alignment = Pos.CENTER_LEFT
vgrow = Priority.ALWAYS;
deviceStateIndicator(this@DeviceViewConnection, Device.INITIALIZED_STATE)
deviceStateIndicator(this@DeviceViewConnection, PortSensor.CONNECTED_STATE)
deviceStateIndicator(this@DeviceViewConnection, Sensor.MEASURING_STATE)
deviceStateIndicator(this@DeviceViewConnection, "storing")
deviceStateIndicator(this@DeviceDisplay, Device.INITIALIZED_STATE)
deviceStateIndicator(this@DeviceDisplay, PortSensor.CONNECTED_STATE)
deviceStateIndicator(this@DeviceDisplay, Sensor.MEASURING_STATE)
deviceStateIndicator(this@DeviceDisplay, "storing")
pane {
hgrow = Priority.ALWAYS
}
togglebutton("View") {
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 Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform: ((Value) -> Paint)? = null) {
fun Indicator.bind(connection: DeviceDisplay<*>, state: String, transform: ((Value) -> Paint)? = null) {
tooltip(state)
if (transform != null) {
bind(connection.getStateBinding(state), transform);
@ -96,7 +96,7 @@ fun Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform
/**
* 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 (showName) {
text("${state.toUpperCase()}: ")
@ -113,7 +113,7 @@ fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, 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)) {
togglebutton(title) {
isSelected = false

View File

@ -3,9 +3,9 @@ package inr.numass.control
import ch.qos.logback.classic.Level
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.utils.ContextMetaFactory
import javafx.scene.Scene
import javafx.stage.Stage
import org.slf4j.LoggerFactory
@ -25,9 +25,9 @@ abstract class NumassControlApplication<D : Device> : App() {
rootLogger.level = Level.INFO
device = setupDevice()
val controller = buildView(device)
val controller = device.getDisplay()
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.show()
@ -35,19 +35,12 @@ abstract class NumassControlApplication<D : Device> : App() {
setDFStageIcon(stage)
}
/**
* Build a view connection
* @return
*/
protected abstract fun buildView(device: D): DeviceViewConnection<D>
/**
* Get a device factory for given device
* @return
*/
protected abstract val deviceFactory: ContextMetaFactory<D>
protected abstract val deviceFactory: DeviceFactory
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.devices.AbstractDevice
import hep.dataforge.exceptions.StorageException
import hep.dataforge.storage.api.TableLoader
import hep.dataforge.values.Values
import java.util.*
@ -11,16 +10,16 @@ import java.util.*
* A helper to store points in multiple loaders
* 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>()
fun push(point: Values) {
if (!device.hasState("storing") || device.getState("storing").booleanValue()) {
device.forEachConnection("storage", StorageConnection::class.java) { connection ->
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
try {
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
pl.push(point)
} catch (ex: StorageException) {
} catch (ex: Exception) {
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.exceptions.ControlException
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceView
/**
* @author Alexander Nozik
*/
class CM32Device : PortSensor<Double> {
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@DeviceView(VacDisplay::class)
class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
@Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler {
@ -54,7 +50,7 @@ class CM32Device : PortSensor<Double> {
@Throws(Exception::class)
override fun doMeasure(): Double? {
val answer = sendAndWait(CM32_QUERY, timeout())
val answer = sendAndWait("MES R PM 1\r\n", timeout())
if (answer.isEmpty()) {
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.exceptions.ControlException
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceView
/**
* @author Alexander Nozik
*/
@ValueDef(name = "channel")
class MKSBaratronDevice : PortSensor<Double> {
@DeviceView(VacDisplay::class)
class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
private val channel: Int
get() = meta().getInt("channel", 2)!!
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
private val channel: Int = meta().getInt("channel", 2)
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.ports.PortHandler
import hep.dataforge.description.ValueDef
import hep.dataforge.description.ValueDefs
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType.BOOLEAN
import inr.numass.control.DeviceView
import javafx.beans.property.BooleanProperty
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder
import java.util.regex.Pattern
@ -24,22 +26,20 @@ import java.util.regex.Pattern
/**
* @author Alexander Nozik
*/
@ValueDef(name = "address", def = "253")
@ValueDef(name = "channel", def = "5")
@ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "true")
@ValueDefs(
ValueDef(name = "address", def = "253"),
ValueDef(name = "channel", def = "5"),
ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "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?
val deviceAddress: String
private val deviceAddress: String
get() = meta().getString("address", "253")
private// String ans = talkMKS(p1Port, "@253ENC!OFF;FF");
// if (!ans.equals("OFF")) {
// LoggerFactory.getLogger(getClass()).warn("The @253ENC!OFF;FF command is not working");
// }
var isPowerOn: Boolean
private var isPowerOn: Boolean
get() = getState("power").booleanValue()
@Throws(ControlException::class)
set(powerOn) {
@ -62,15 +62,7 @@ class MKSVacDevice : PortSensor<Double> {
}
}
private val channel: Int
get() = meta().getInt("channel", 5)!!
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
private val channel: Int = meta().getInt("channel", 5)!!
@Throws(ControlException::class)
private fun talk(requestContent: String): String? {
@ -97,9 +89,9 @@ class MKSVacDevice : PortSensor<Double> {
@Throws(ControlException::class)
override fun computeState(stateName: String): Any {
when (stateName) {
"power" -> return talk("FP?") == "ON"
else -> return super.computeState(stateName)
return when (stateName) {
"power" -> talk("FP?") == "ON"
else -> super.computeState(stateName)
}
}

View File

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

View File

@ -6,8 +6,6 @@
package inr.numass.control.readvac
import hep.dataforge.meta.Meta
import hep.dataforge.utils.ContextMetaFactory
import inr.numass.control.DeviceViewConnection
import inr.numass.control.NumassControlApplication
import javafx.stage.Stage
@ -15,11 +13,8 @@ import javafx.stage.Stage
* @author Alexander Nozik
*/
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) {
stage.title = "Numass vacuum measurements"

View File

@ -6,6 +6,7 @@
package inr.numass.control.readvac
import hep.dataforge.context.Context
import hep.dataforge.control.Connection
import hep.dataforge.control.RoleDef
import hep.dataforge.control.collectors.RegularPointCollector
import hep.dataforge.control.connections.Roles
@ -29,6 +30,7 @@ import hep.dataforge.utils.DateTimeUtils
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
import hep.dataforge.values.Values
import inr.numass.control.DeviceView
import inr.numass.control.StorageHelper
import java.time.Duration
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")
@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)
val sensors: Collection<Sensor<Double>>
get() = sensorMap.values
private val averagingDuration: Duration
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
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> {
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() {
super.init()
for (s in sensorMap.values) {
for (s in sensors) {
s.init()
}
}
@ -118,6 +99,16 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
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 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") }
val delay = meta().getInt("delay", 5)!! * 1000
currentTask = executor!!.scheduleWithFixedDelay({
sensorMap.values.forEach { sensor ->
sensors.forEach { sensor ->
try {
val value: Any?
value = if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) {
@ -158,7 +149,7 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
private fun terminator(): Values {
val p = ValueMap.Builder()
p.putValue("timestamp", DateTimeUtils.now())
sensorMap.keys.forEach { n -> p.putValue(n, null) }
deviceNames().forEach { n -> p.putValue(n.toUnescaped(), null) }
return p.build()
}
@ -175,5 +166,4 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
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.TimePlottableGroup
import hep.dataforge.values.Value
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceDisplay
import inr.numass.control.deviceStateToggle
import inr.numass.control.plot
import javafx.collections.FXCollections
@ -31,13 +31,13 @@ import java.time.Instant
* @author [Alexander Nozik](mailto:altavir@gmail.com)
*/
class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
class VacCollectorDisplay : DeviceDisplay<VacCollectorDevice>() {
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?) {
if(result is Double){
if (result is Double) {
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 {
return VacCollectorView();
@ -56,7 +56,7 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
override fun open(obj: Any) {
super.open(obj)
device.sensors.forEach { sensor ->
val view = VacViewConnection()
val view = VacDisplay()
sensor.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
sensor.connect(sensorConnection, Roles.MEASUREMENT_LISTENER_ROLE);
viewList.add(view)
@ -81,8 +81,8 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
override val root = borderpane {
top {
toolbar {
deviceStateToggle(this@VacCollectorViewConnection, Sensor.MEASURING_STATE, "Measure")
deviceStateToggle(this@VacCollectorViewConnection, "storing", "Store")
deviceStateToggle(this@VacCollectorDisplay, Sensor.MEASURING_STATE, "Measure")
deviceStateToggle(this@VacCollectorDisplay, "storing", "Store")
pane {
hgrow = Priority.ALWAYS
}
@ -109,8 +109,10 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
vbox {
viewList.forEach {
add(it.view)
separator(Orientation.HORIZONTAL)
it.view?.let {
add(it)
separator(Orientation.HORIZONTAL)
}
}
}
}

View File

@ -1,46 +1,41 @@
package inr.numass.control.readvac
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.virtual.VirtualDevice
import hep.dataforge.meta.Meta
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceViewFactory
import java.util.stream.Collectors
/**
* A factory for vacuum measurements collector
* Created by darksnake on 16-May-17.
*/
class VacDeviceFactory : DeviceViewFactory<VacCollectorDevice> {
class VacDeviceFactory : DeviceFactory {
override fun getType(): String {
return "numass:vac"
return "numass.vac"
}
fun buildSensor(context: Context, sensorConfig: Meta): Sensor<Double> {
when (sensorConfig.getString("sensorType", "")) {
"mks" -> return MKSVacDevice(context, sensorConfig)
"CM32" -> return CM32Device(context, sensorConfig)
"meradat" -> return MeradatVacDevice(context, sensorConfig)
"baratron" -> return MKSBaratronDevice(context, sensorConfig)
VirtualDevice.VIRTUAL_SENSOR_TYPE -> return VirtualDevice.randomDoubleSensor(context, sensorConfig)
private fun buildSensor(context: Context, sensorConfig: Meta): Sensor<Double> {
return when (sensorConfig.getString("sensorType", "")) {
"mks" -> MKSVacDevice(context, sensorConfig)
"CM32" -> CM32Device(context, sensorConfig)
"meradat" -> MeradatVacDevice(context, sensorConfig)
"baratron" -> MKSBaratronDevice(context, sensorConfig)
VirtualDevice.VIRTUAL_SENSOR_TYPE -> VirtualDevice.randomDoubleSensor(context, sensorConfig)
else -> throw RuntimeException("Unknown vacuum sensor type")
}
}
override fun build(context: Context, config: Meta): VacCollectorDevice {
val sensors = config.getMetaList("sensor").stream()
.map { sensorConfig ->
buildSensor(context, sensorConfig)
}.collect(Collectors.toList<Sensor<Double>>())
.map { sensorConfig -> buildSensor(context, sensorConfig) }
.collect(Collectors.toList<Sensor<Double>>())
val collector = VacCollectorDevice(context, config)
collector.setSensors(sensors)
return collector
return VacCollectorDevice(context, config, sensors)
}
override fun buildView(device: Device): DeviceViewConnection<VacCollectorDevice> {
return VacCollectorViewConnection();
}
// override fun buildView(device: Device): DeviceDisplay<VacCollectorDevice> {
// 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.measurements.Measurement
import hep.dataforge.control.measurements.MeasurementListener
import inr.numass.control.DeviceViewConnection
import inr.numass.control.DeviceDisplay
import inr.numass.control.switch
import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty
@ -31,7 +31,7 @@ import java.time.format.DateTimeFormatter
/**
* @author [Alexander Nozik](mailto:altavir@gmail.com)
*/
open class VacViewConnection : DeviceViewConnection<Sensor<Double>>(), MeasurementListener {
open class VacDisplay : DeviceDisplay<Sensor<Double>>(), MeasurementListener {
val statusProperty = SimpleStringProperty("")
var status: String by statusProperty

View File

@ -59,7 +59,7 @@ public class Numass {
am.getAllActions()
.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))
);