Moved numass devices to kotlin and applied new logic
This commit is contained in:
parent
7dc3d601f7
commit
94d538d858
@ -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") }
|
|
||||||
.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();
|
device.init();
|
||||||
return view;
|
return device.getDisplay();
|
||||||
} else {
|
|
||||||
throw RuntimeException("Device factory not found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildStorage(context: Context, meta: Meta): Storage {
|
private fun buildStorage(context: Context, meta: Meta): Storage {
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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()
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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()
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
@ -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"/>
|
||||||
|
@ -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,32 +18,43 @@ 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 {
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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.*
|
||||||
@ -17,10 +16,10 @@ class StorageHelper(private val device: AbstractDevice, private val loaderFactor
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,7 +31,7 @@ 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>()
|
||||||
|
|
||||||
@ -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,11 +109,13 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
|
|||||||
hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
||||||
vbox {
|
vbox {
|
||||||
viewList.forEach {
|
viewList.forEach {
|
||||||
add(it.view)
|
it.view?.let {
|
||||||
|
add(it)
|
||||||
separator(Orientation.HORIZONTAL)
|
separator(Orientation.HORIZONTAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// listview(viewList) {
|
// listview(viewList) {
|
||||||
// cellFormat {
|
// cellFormat {
|
||||||
// graphic = it.fxNode
|
// graphic = it.fxNode
|
@ -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();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
@ -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
|
@ -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))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user