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.Global
|
||||
import hep.dataforge.control.DeviceManager
|
||||
import hep.dataforge.control.connections.Roles
|
||||
import hep.dataforge.control.connections.StorageConnection
|
||||
import hep.dataforge.meta.Meta
|
||||
@ -16,13 +17,12 @@ import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import tornadofx.*
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Created by darksnake on 12-May-17.
|
||||
*/
|
||||
class BoardController() : Controller(), AutoCloseable {
|
||||
val devices: ObservableList<DeviceViewConnection<*>> = FXCollections.observableArrayList<DeviceViewConnection<*>>();
|
||||
val devices: ObservableList<DeviceDisplay<*>> = FXCollections.observableArrayList<DeviceDisplay<*>>();
|
||||
|
||||
val contextProperty = SimpleObjectProperty<Context>(Global.instance())
|
||||
var context: Context by contextProperty
|
||||
@ -39,15 +39,8 @@ class BoardController() : Controller(), AutoCloseable {
|
||||
fun load(app: Application) {
|
||||
runAsync {
|
||||
getConfig(app).ifPresent {
|
||||
val libDir = File(app.parameters.named.getOrDefault("libPath", "../lib"));
|
||||
val contextBuilder = Context
|
||||
.builder("NUMASS-SERVER");
|
||||
if (libDir.exists()) {
|
||||
Global.logger().info("Found library directory {}. Loading it into server context", libDir)
|
||||
contextBuilder.classPath(libDir.listFiles { _, name -> name.endsWith(".jar") }.map { it.toURI().toURL() })
|
||||
}
|
||||
context = contextBuilder.build();
|
||||
load(context, it);
|
||||
val context = Context.build("NUMASS", Global.instance(), it)
|
||||
load(context, it)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,21 +80,11 @@ class BoardController() : Controller(), AutoCloseable {
|
||||
|
||||
}
|
||||
|
||||
private fun buildDeviceView(context: Context, deviceMeta: Meta): DeviceViewConnection<*> {
|
||||
private fun buildDeviceView(context: Context, deviceMeta: Meta): DeviceDisplay<*> {
|
||||
context.logger.info("Building device with meta: {}", deviceMeta)
|
||||
val factory = context.serviceStream(DeviceViewFactory::class.java)
|
||||
.filter { it.type == deviceMeta.getString("type") }
|
||||
.findFirst();
|
||||
|
||||
if (factory.isPresent) {
|
||||
val device = factory.get().build(context, deviceMeta);
|
||||
val view = factory.get().buildView(device);
|
||||
device.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
|
||||
device.init();
|
||||
return view;
|
||||
} else {
|
||||
throw RuntimeException("Device factory not found");
|
||||
}
|
||||
val device = context.loadFeature("devices", DeviceManager::class.java).buildDevice(deviceMeta)
|
||||
device.init();
|
||||
return device.getDisplay();
|
||||
}
|
||||
|
||||
private fun buildStorage(context: Context, meta: Meta): Storage {
|
||||
|
@ -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
|
||||
|
||||
import hep.dataforge.control.connections.Roles
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.NumassControlApplication
|
||||
import javafx.stage.Stage
|
||||
|
||||
@ -25,11 +23,6 @@ import javafx.stage.Stage
|
||||
* @author darksnake
|
||||
*/
|
||||
class PKT8App : NumassControlApplication<PKT8Device>() {
|
||||
override fun buildView(device: PKT8Device): DeviceViewConnection<PKT8Device> {
|
||||
return PKT8ViewConnection().apply {
|
||||
device.connect(this, Roles.VIEW_ROLE)
|
||||
}
|
||||
}
|
||||
|
||||
override val deviceFactory = PKT8DeviceFactory()
|
||||
|
||||
|
@ -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
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.DeviceFactory
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceViewFactory
|
||||
|
||||
/**
|
||||
* Created by darksnake on 09-May-17.
|
||||
*/
|
||||
class PKT8DeviceFactory : DeviceViewFactory<PKT8Device> {
|
||||
class PKT8DeviceFactory : DeviceFactory {
|
||||
override fun getType(): String {
|
||||
return PKT8Device.PKT8_DEVICE_TYPE
|
||||
}
|
||||
@ -17,8 +15,4 @@ class PKT8DeviceFactory : DeviceViewFactory<PKT8Device> {
|
||||
override fun build(context: Context, meta: Meta): PKT8Device {
|
||||
return PKT8Device(context, meta)
|
||||
}
|
||||
|
||||
override fun buildView(device: Device): DeviceViewConnection<PKT8Device> {
|
||||
return PKT8ViewConnection()
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import hep.dataforge.plots.PlotUtils
|
||||
import hep.dataforge.plots.data.TimePlot
|
||||
import hep.dataforge.plots.data.TimePlottableGroup
|
||||
import hep.dataforge.plots.jfreechart.JFreeChartFrame
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceDisplay
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.binding.ListBinding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
@ -31,7 +31,7 @@ import java.time.Instant
|
||||
/**
|
||||
* Created by darksnake on 30-May-17.
|
||||
*/
|
||||
class PKT8ViewConnection : DeviceViewConnection<PKT8Device>(), MeasurementListener {
|
||||
class PKT8Display : DeviceDisplay<PKT8Device>(), MeasurementListener {
|
||||
|
||||
override fun buildView(device: PKT8Device): View {
|
||||
return CryoView()
|
@ -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.text.Font?>
|
||||
<BorderPane fx:id="root" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="400.0"
|
||||
xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8ViewConnection">
|
||||
xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8Display">
|
||||
<center>
|
||||
<TableView fx:id="table" BorderPane.alignment="CENTER">
|
||||
<columns>
|
||||
|
@ -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
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.NumassControlApplication
|
||||
import javafx.stage.Stage
|
||||
|
||||
@ -25,10 +24,6 @@ import javafx.stage.Stage
|
||||
*/
|
||||
class MspApp : NumassControlApplication<MspDevice>() {
|
||||
|
||||
override fun buildView(device: MspDevice): DeviceViewConnection<MspDevice> {
|
||||
return MspViewConnection()
|
||||
}
|
||||
|
||||
override val deviceFactory = MspDeviceFactory()
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.DeviceFactory
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceViewFactory
|
||||
|
||||
/**
|
||||
* Created by darksnake on 09-May-17.
|
||||
*/
|
||||
class MspDeviceFactory : DeviceViewFactory<MspDevice> {
|
||||
class MspDeviceFactory: DeviceFactory {
|
||||
override fun getType(): String {
|
||||
return MspDevice.MSP_DEVICE_TYPE
|
||||
}
|
||||
|
||||
override fun build(context: Context, config: Meta): MspDevice {
|
||||
val device = MspDevice(context, config)
|
||||
return device
|
||||
}
|
||||
|
||||
override fun buildView(device: Device): DeviceViewConnection<MspDevice> {
|
||||
return MspViewConnection()
|
||||
return MspDevice(context, config)
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import hep.dataforge.plots.data.TimePlot
|
||||
import hep.dataforge.plots.data.TimePlottableGroup
|
||||
import hep.dataforge.plots.jfreechart.JFreeChartFrame
|
||||
import hep.dataforge.values.Value
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceDisplay
|
||||
import inr.numass.control.deviceStateIndicator
|
||||
import inr.numass.control.deviceStateToggle
|
||||
import inr.numass.control.switch
|
||||
@ -51,7 +51,7 @@ import tornadofx.*
|
||||
|
||||
* @author darksnake
|
||||
*/
|
||||
class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, NamedValueListener {
|
||||
class MspDisplay() : DeviceDisplay<MspDevice>(), DeviceListener, NamedValueListener {
|
||||
|
||||
private val table = FXCollections.observableHashMap<String, Value>()
|
||||
|
||||
@ -71,9 +71,9 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
|
||||
|
||||
|
||||
inner class MspView : View("Numass mass-spectrometer measurement") {
|
||||
val plotFrameMeta: Meta = device.meta().getMeta("plotConfig", device.meta)
|
||||
private val plotFrameMeta: Meta = device.meta().getMeta("plotConfig", device.meta)
|
||||
|
||||
val plotFrame: PlotFrame by lazy {
|
||||
private val plotFrame: PlotFrame by lazy {
|
||||
val basePlotConfig = MetaBuilder("plotFrame")
|
||||
.setNode(MetaBuilder("yAxis")
|
||||
.setValue("type", "log")
|
||||
@ -113,7 +113,7 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
|
||||
// addLogHandler(device.logger)
|
||||
// })
|
||||
|
||||
val filamentProperty = SimpleObjectProperty<Int>(this, "filament", 1).apply {
|
||||
private val filamentProperty = SimpleObjectProperty<Int>(this, "filament", 1).apply {
|
||||
addListener { _, oldValue, newValue ->
|
||||
if (newValue != oldValue) {
|
||||
runAsync {
|
||||
@ -128,7 +128,7 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
|
||||
minWidth = 600.0
|
||||
top {
|
||||
toolbar {
|
||||
deviceStateToggle(this@MspViewConnection, PortSensor.CONNECTED_STATE, "Connect")
|
||||
deviceStateToggle(this@MspDisplay, PortSensor.CONNECTED_STATE, "Connect")
|
||||
combobox(filamentProperty, listOf(1, 2)) {
|
||||
cellFormat {
|
||||
text = "Filament $it"
|
||||
@ -142,7 +142,7 @@ class MspViewConnection() : DeviceViewConnection<MspDevice>(), DeviceListener, N
|
||||
.bind(getStateBinding(PortSensor.CONNECTED_STATE).booleanBinding { !it!!.booleanValue() })
|
||||
bindBooleanToState("filamentOn", selectedProperty())
|
||||
}
|
||||
deviceStateIndicator(this@MspViewConnection, "filamentStatus", false) {
|
||||
deviceStateIndicator(this@MspDisplay, "filamentStatus", false) {
|
||||
when (it.stringValue()) {
|
||||
"ON" -> Paint.valueOf("red")
|
||||
"OFF" -> Paint.valueOf("blue")
|
@ -25,7 +25,7 @@ limitations under the License.
|
||||
<?import org.controlsfx.control.ToggleSwitch?>
|
||||
<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="400.0" minWidth="600.0"
|
||||
xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="inr.numass.control.msp.MspViewConnection">
|
||||
fx:controller="inr.numass.control.msp.MspDisplay">
|
||||
<top>
|
||||
<ToolBar prefHeight="50.0" prefWidth="200.0">
|
||||
<ToggleButton fx:id="connectButton" mnemonicParsing="false" text="Connect"/>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package inr.numass.control
|
||||
|
||||
import hep.dataforge.control.Connection
|
||||
import hep.dataforge.control.connections.Roles
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.DeviceListener
|
||||
import hep.dataforge.control.devices.PortSensor
|
||||
@ -17,33 +18,44 @@ import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.Priority
|
||||
import tornadofx.*
|
||||
import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.createInstance
|
||||
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class DeviceView(val value: KClass<out DeviceDisplay<*>>)
|
||||
|
||||
/**
|
||||
* Get existing view connection or create a new one
|
||||
*/
|
||||
fun Device.getDisplay(): DeviceDisplay<*> {
|
||||
val type = DefaultDisplay::class;
|
||||
return optConnection(Roles.VIEW_ROLE, DeviceDisplay::class.java).orElseGet {
|
||||
type.createInstance().also {
|
||||
connect(it, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* An FX View to represent the device
|
||||
* Created by darksnake on 14-May-17.
|
||||
*/
|
||||
abstract class DeviceViewConnection<D : Device> : Component(), Connection, DeviceListener {
|
||||
abstract class DeviceDisplay<D : Device> : Component(), Connection, DeviceListener {
|
||||
|
||||
private val bindings = HashMap<String, ObjectBinding<Value>>()
|
||||
|
||||
private val deviceProperty = SimpleObjectProperty<D>(this, "device", null)
|
||||
val device: D
|
||||
get() {
|
||||
val res = deviceProperty.get();
|
||||
if (res == null) {
|
||||
throw RuntimeException("Not connected!");
|
||||
} else {
|
||||
return res
|
||||
}
|
||||
}
|
||||
val device: D by deviceProperty
|
||||
|
||||
private val viewProperty = SimpleObjectProperty<View>(this, "view", null)
|
||||
val view: View
|
||||
get() {
|
||||
if (viewProperty.get() == null) {
|
||||
viewProperty.set(buildView(device))
|
||||
}
|
||||
return viewProperty.get();
|
||||
}
|
||||
// private val viewProperty = SimpleObjectProperty<UIComponent>(this, "view", null)
|
||||
val view: UIComponent? by lazy {
|
||||
buildView(device)
|
||||
}
|
||||
|
||||
override fun isOpen(): Boolean {
|
||||
return this.deviceProperty.get() != null
|
||||
@ -60,13 +72,13 @@ abstract class DeviceViewConnection<D : Device> : Component(), Connection, Devic
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (viewProperty.isNotNull.get()) {
|
||||
view.close()
|
||||
if (isOpen) {
|
||||
view?.close()
|
||||
deviceProperty.set(null)
|
||||
}
|
||||
deviceProperty.set(null)
|
||||
}
|
||||
|
||||
abstract fun buildView(device: D): View;
|
||||
abstract fun buildView(device: D): UIComponent?;
|
||||
|
||||
/**
|
||||
* Get binding for a given device state
|
||||
@ -125,17 +137,31 @@ abstract class DeviceViewConnection<D : Device> : Component(), Connection, Devic
|
||||
return HBox().apply {
|
||||
alignment = Pos.CENTER_LEFT
|
||||
vgrow = Priority.ALWAYS;
|
||||
deviceStateIndicator(this@DeviceViewConnection, Device.INITIALIZED_STATE)
|
||||
deviceStateIndicator(this@DeviceViewConnection, PortSensor.CONNECTED_STATE)
|
||||
deviceStateIndicator(this@DeviceViewConnection, Sensor.MEASURING_STATE)
|
||||
deviceStateIndicator(this@DeviceViewConnection, "storing")
|
||||
deviceStateIndicator(this@DeviceDisplay, Device.INITIALIZED_STATE)
|
||||
deviceStateIndicator(this@DeviceDisplay, PortSensor.CONNECTED_STATE)
|
||||
deviceStateIndicator(this@DeviceDisplay, Sensor.MEASURING_STATE)
|
||||
deviceStateIndicator(this@DeviceDisplay, "storing")
|
||||
pane {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
togglebutton("View") {
|
||||
isSelected = false
|
||||
view.bindWindow(this.selectedProperty())
|
||||
if (view == null) {
|
||||
isDisable = true
|
||||
}
|
||||
view?.bindWindow(selectedProperty())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default display shows only board pane and nothing else
|
||||
*/
|
||||
class DefaultDisplay() : DeviceDisplay<Device>() {
|
||||
override fun buildView(device: Device): UIComponent? {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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 Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform: ((Value) -> Paint)? = null) {
|
||||
fun Indicator.bind(connection: DeviceDisplay<*>, state: String, transform: ((Value) -> Paint)? = null) {
|
||||
tooltip(state)
|
||||
if (transform != null) {
|
||||
bind(connection.getStateBinding(state), transform);
|
||||
@ -96,7 +96,7 @@ fun Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform
|
||||
/**
|
||||
* State name + indicator
|
||||
*/
|
||||
fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state: String, showName: Boolean = true, transform: ((Value) -> Paint)? = null) {
|
||||
fun EventTarget.deviceStateIndicator(connection: DeviceDisplay<*>, state: String, showName: Boolean = true, transform: ((Value) -> Paint)? = null) {
|
||||
if (connection.device.hasState(state)) {
|
||||
if (showName) {
|
||||
text("${state.toUpperCase()}: ")
|
||||
@ -113,7 +113,7 @@ fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state:
|
||||
/**
|
||||
* A togglebutton + indicator for boolean state
|
||||
*/
|
||||
fun Node.deviceStateToggle(connection: DeviceViewConnection<*>, state: String, title: String = state) {
|
||||
fun Node.deviceStateToggle(connection: DeviceDisplay<*>, state: String, title: String = state) {
|
||||
if (connection.device.hasState(state)) {
|
||||
togglebutton(title) {
|
||||
isSelected = false
|
||||
|
@ -3,9 +3,9 @@ package inr.numass.control
|
||||
import ch.qos.logback.classic.Level
|
||||
import hep.dataforge.control.connections.Roles
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.DeviceFactory
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.utils.ContextMetaFactory
|
||||
import javafx.scene.Scene
|
||||
import javafx.stage.Stage
|
||||
import org.slf4j.LoggerFactory
|
||||
@ -25,9 +25,9 @@ abstract class NumassControlApplication<D : Device> : App() {
|
||||
rootLogger.level = Level.INFO
|
||||
|
||||
device = setupDevice()
|
||||
val controller = buildView(device)
|
||||
val controller = device.getDisplay()
|
||||
device.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
|
||||
val scene = Scene(controller.view.root)
|
||||
val scene = Scene(controller.view?.root ?: controller.getBoardView())
|
||||
stage.scene = scene
|
||||
|
||||
stage.show()
|
||||
@ -35,19 +35,12 @@ abstract class NumassControlApplication<D : Device> : App() {
|
||||
setDFStageIcon(stage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a view connection
|
||||
|
||||
* @return
|
||||
*/
|
||||
protected abstract fun buildView(device: D): DeviceViewConnection<D>
|
||||
|
||||
/**
|
||||
* Get a device factory for given device
|
||||
|
||||
* @return
|
||||
*/
|
||||
protected abstract val deviceFactory: ContextMetaFactory<D>
|
||||
protected abstract val deviceFactory: DeviceFactory
|
||||
|
||||
protected abstract fun setupStage(stage: Stage, device: D)
|
||||
|
||||
|
@ -2,7 +2,6 @@ package inr.numass.control
|
||||
|
||||
import hep.dataforge.control.connections.StorageConnection
|
||||
import hep.dataforge.control.devices.AbstractDevice
|
||||
import hep.dataforge.exceptions.StorageException
|
||||
import hep.dataforge.storage.api.TableLoader
|
||||
import hep.dataforge.values.Values
|
||||
import java.util.*
|
||||
@ -11,16 +10,16 @@ import java.util.*
|
||||
* A helper to store points in multiple loaders
|
||||
* Created by darksnake on 16-May-17.
|
||||
*/
|
||||
class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection)-> TableLoader) : AutoCloseable {
|
||||
class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection) -> TableLoader) : AutoCloseable {
|
||||
private val loaderMap = HashMap<StorageConnection, TableLoader>()
|
||||
|
||||
fun push(point: Values) {
|
||||
if (!device.hasState("storing") || device.getState("storing").booleanValue()) {
|
||||
device.forEachConnection("storage", StorageConnection::class.java) { connection ->
|
||||
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
|
||||
try {
|
||||
val pl = loaderMap.computeIfAbsent(connection, loaderFactory)
|
||||
pl.push(point)
|
||||
} catch (ex: StorageException) {
|
||||
} catch (ex: Exception) {
|
||||
device.logger.error("Push to loader failed", ex)
|
||||
}
|
||||
}
|
||||
|
@ -15,17 +15,13 @@ import hep.dataforge.control.ports.PortFactory
|
||||
import hep.dataforge.control.ports.PortHandler
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceView
|
||||
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
class CM32Device : PortSensor<Double> {
|
||||
constructor() {}
|
||||
|
||||
constructor(context: Context, meta: Meta) {
|
||||
setContext(context)
|
||||
setMeta(meta)
|
||||
}
|
||||
@DeviceView(VacDisplay::class)
|
||||
class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun buildHandler(portName: String): PortHandler {
|
||||
@ -54,7 +50,7 @@ class CM32Device : PortSensor<Double> {
|
||||
@Throws(Exception::class)
|
||||
override fun doMeasure(): Double? {
|
||||
|
||||
val answer = sendAndWait(CM32_QUERY, timeout())
|
||||
val answer = sendAndWait("MES R PM 1\r\n", timeout())
|
||||
|
||||
if (answer.isEmpty()) {
|
||||
this.updateMessage("No signal")
|
||||
@ -82,9 +78,4 @@ class CM32Device : PortSensor<Double> {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val CM32_QUERY = "MES R PM 1\r\n"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,22 +14,16 @@ import hep.dataforge.control.ports.PortHandler
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceView
|
||||
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@ValueDef(name = "channel")
|
||||
class MKSBaratronDevice : PortSensor<Double> {
|
||||
@DeviceView(VacDisplay::class)
|
||||
class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
|
||||
|
||||
private val channel: Int
|
||||
get() = meta().getInt("channel", 2)!!
|
||||
|
||||
constructor() {}
|
||||
|
||||
constructor(context: Context, meta: Meta) {
|
||||
setContext(context)
|
||||
setMeta(meta)
|
||||
}
|
||||
private val channel: Int = meta().getInt("channel", 2)
|
||||
|
||||
|
||||
override fun createMeasurement(): Measurement<Double> {
|
||||
|
@ -13,10 +13,12 @@ import hep.dataforge.control.measurements.Measurement
|
||||
import hep.dataforge.control.measurements.SimpleMeasurement
|
||||
import hep.dataforge.control.ports.PortHandler
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.description.ValueDefs
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.ValueType.BOOLEAN
|
||||
import inr.numass.control.DeviceView
|
||||
import javafx.beans.property.BooleanProperty
|
||||
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder
|
||||
import java.util.regex.Pattern
|
||||
@ -24,22 +26,20 @@ import java.util.regex.Pattern
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@ValueDef(name = "address", def = "253")
|
||||
@ValueDef(name = "channel", def = "5")
|
||||
@ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "true")
|
||||
@ValueDefs(
|
||||
ValueDef(name = "address", def = "253"),
|
||||
ValueDef(name = "channel", def = "5"),
|
||||
ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "true")
|
||||
)
|
||||
@StateDef(value = ValueDef(name = "power", info = "Device powered up"), writable = true)
|
||||
class MKSVacDevice : PortSensor<Double> {
|
||||
@DeviceView(VacDisplay::class)
|
||||
class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
|
||||
|
||||
private//PENDING cache this?
|
||||
val deviceAddress: String
|
||||
private val deviceAddress: String
|
||||
get() = meta().getString("address", "253")
|
||||
|
||||
|
||||
private// String ans = talkMKS(p1Port, "@253ENC!OFF;FF");
|
||||
// if (!ans.equals("OFF")) {
|
||||
// LoggerFactory.getLogger(getClass()).warn("The @253ENC!OFF;FF command is not working");
|
||||
// }
|
||||
var isPowerOn: Boolean
|
||||
private var isPowerOn: Boolean
|
||||
get() = getState("power").booleanValue()
|
||||
@Throws(ControlException::class)
|
||||
set(powerOn) {
|
||||
@ -62,15 +62,7 @@ class MKSVacDevice : PortSensor<Double> {
|
||||
}
|
||||
}
|
||||
|
||||
private val channel: Int
|
||||
get() = meta().getInt("channel", 5)!!
|
||||
|
||||
constructor() {}
|
||||
|
||||
constructor(context: Context, meta: Meta) {
|
||||
setContext(context)
|
||||
setMeta(meta)
|
||||
}
|
||||
private val channel: Int = meta().getInt("channel", 5)!!
|
||||
|
||||
@Throws(ControlException::class)
|
||||
private fun talk(requestContent: String): String? {
|
||||
@ -97,9 +89,9 @@ class MKSVacDevice : PortSensor<Double> {
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun computeState(stateName: String): Any {
|
||||
when (stateName) {
|
||||
"power" -> return talk("FP?") == "ON"
|
||||
else -> return super.computeState(stateName)
|
||||
return when (stateName) {
|
||||
"power" -> talk("FP?") == "ON"
|
||||
else -> super.computeState(stateName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,14 +24,7 @@ import java.util.regex.Pattern
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@ValueDef(name = "address", type = arrayOf(NUMBER), def = "1", info = "A modbus address")
|
||||
class MeradatVacDevice : PortSensor<Double> {
|
||||
|
||||
constructor() {}
|
||||
|
||||
constructor(context: Context, meta: Meta) {
|
||||
setContext(context)
|
||||
setMeta(meta)
|
||||
}
|
||||
class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun buildHandler(portName: String): PortHandler {
|
||||
@ -111,14 +104,13 @@ class MeradatVacDevice : PortSensor<Double> {
|
||||
for (aByte in bytes) {
|
||||
checksum += aByte.toInt()
|
||||
}
|
||||
var `val` = Integer.toHexString(-checksum)
|
||||
`val` = `val`.substring(`val`.length - 2).toUpperCase()
|
||||
if (`val`.length < 2) {
|
||||
`val` = "0" + `val`
|
||||
var value = Integer.toHexString(-checksum)
|
||||
value = value.substring(value.length - 2).toUpperCase()
|
||||
if (value.length < 2) {
|
||||
value = "0" + value
|
||||
}
|
||||
|
||||
return `val`
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,8 +6,6 @@
|
||||
package inr.numass.control.readvac
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.utils.ContextMetaFactory
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.NumassControlApplication
|
||||
import javafx.stage.Stage
|
||||
|
||||
@ -15,11 +13,8 @@ import javafx.stage.Stage
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
class ReadVac : NumassControlApplication<VacCollectorDevice>() {
|
||||
override fun buildView(device: VacCollectorDevice): DeviceViewConnection<VacCollectorDevice> {
|
||||
return VacCollectorViewConnection()
|
||||
}
|
||||
|
||||
override val deviceFactory: ContextMetaFactory<VacCollectorDevice> = VacDeviceFactory()
|
||||
override val deviceFactory = VacDeviceFactory()
|
||||
|
||||
override fun setupStage(stage: Stage, device: VacCollectorDevice) {
|
||||
stage.title = "Numass vacuum measurements"
|
||||
|
@ -6,6 +6,7 @@
|
||||
package inr.numass.control.readvac
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.Connection
|
||||
import hep.dataforge.control.RoleDef
|
||||
import hep.dataforge.control.collectors.RegularPointCollector
|
||||
import hep.dataforge.control.connections.Roles
|
||||
@ -29,6 +30,7 @@ import hep.dataforge.utils.DateTimeUtils
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.Values
|
||||
import inr.numass.control.DeviceView
|
||||
import inr.numass.control.StorageHelper
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@ -44,48 +46,27 @@ import java.util.stream.Stream
|
||||
*/
|
||||
@RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection::class, info = "Storage for acquired points")
|
||||
@StateDef(value = ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true)
|
||||
class VacCollectorDevice : Sensor<Values>, DeviceHub {
|
||||
@DeviceView(VacCollectorDisplay::class)
|
||||
class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<Sensor<Double>>) : Sensor<Values>(context, meta), DeviceHub {
|
||||
|
||||
private var sensorMap: MutableMap<String, Sensor<Double>> = LinkedHashMap()
|
||||
private val helper = StorageHelper(this, this::buildLoader)
|
||||
|
||||
val sensors: Collection<Sensor<Double>>
|
||||
get() = sensorMap.values
|
||||
|
||||
private val averagingDuration: Duration
|
||||
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
|
||||
|
||||
constructor() {}
|
||||
|
||||
constructor(context: Context, meta: Meta) {
|
||||
setContext(context)
|
||||
setMeta(meta)
|
||||
}
|
||||
|
||||
override fun optDevice(name: Name): Optional<Device> {
|
||||
return Optional.ofNullable(sensorMap.get(name.toString()))
|
||||
return Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
|
||||
}
|
||||
|
||||
override fun deviceNames(): Stream<Name> {
|
||||
return sensorMap.keys.stream().map { Name.ofSingle(it) }
|
||||
return sensors.stream().map { Name.ofSingle(it.name) }
|
||||
}
|
||||
|
||||
|
||||
fun setSensors(sensors: Iterable<Sensor<Double>>) {
|
||||
sensorMap = LinkedHashMap()
|
||||
for (sensor in sensors) {
|
||||
sensorMap.put(sensor.name, sensor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSensors(vararg sensors: Sensor<Double>) {
|
||||
setSensors(Arrays.asList(*sensors))
|
||||
}
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun init() {
|
||||
super.init()
|
||||
for (s in sensorMap.values) {
|
||||
for (s in sensors) {
|
||||
s.init()
|
||||
}
|
||||
}
|
||||
@ -118,6 +99,16 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
|
||||
return LoaderFactory.buildPointLoder(connection.storage, "vactms_" + suffix, "", "timestamp", format.build())
|
||||
}
|
||||
|
||||
override fun connectAll(connection: Connection, vararg roles: String) {
|
||||
connect(connection, *roles)
|
||||
this.sensors.forEach { it.connect(connection, *roles) }
|
||||
}
|
||||
|
||||
override fun connectAll(context: Context, meta: Meta) {
|
||||
this.connectionHelper.connect(context, meta)
|
||||
this.sensors.forEach { it.connectionHelper.connect(context, meta) }
|
||||
}
|
||||
|
||||
private inner class VacuumMeasurement : AbstractMeasurement<Values>() {
|
||||
|
||||
private val collector = RegularPointCollector(averagingDuration) { this.result(it) }
|
||||
@ -133,7 +124,7 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
|
||||
executor = Executors.newSingleThreadScheduledExecutor { r: Runnable -> Thread(r, "VacuumMeasurement thread") }
|
||||
val delay = meta().getInt("delay", 5)!! * 1000
|
||||
currentTask = executor!!.scheduleWithFixedDelay({
|
||||
sensorMap.values.forEach { sensor ->
|
||||
sensors.forEach { sensor ->
|
||||
try {
|
||||
val value: Any?
|
||||
value = if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) {
|
||||
@ -158,7 +149,7 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
|
||||
private fun terminator(): Values {
|
||||
val p = ValueMap.Builder()
|
||||
p.putValue("timestamp", DateTimeUtils.now())
|
||||
sensorMap.keys.forEach { n -> p.putValue(n, null) }
|
||||
deviceNames().forEach { n -> p.putValue(n.toUnescaped(), null) }
|
||||
return p.build()
|
||||
}
|
||||
|
||||
@ -175,5 +166,4 @@ class VacCollectorDevice : Sensor<Values>, DeviceHub {
|
||||
return isRunning
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import hep.dataforge.fx.fragments.LogFragment
|
||||
import hep.dataforge.plots.data.TimePlot
|
||||
import hep.dataforge.plots.data.TimePlottableGroup
|
||||
import hep.dataforge.values.Value
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceDisplay
|
||||
import inr.numass.control.deviceStateToggle
|
||||
import inr.numass.control.plot
|
||||
import javafx.collections.FXCollections
|
||||
@ -31,13 +31,13 @@ import java.time.Instant
|
||||
|
||||
* @author [Alexander Nozik](mailto:altavir@gmail.com)
|
||||
*/
|
||||
class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
|
||||
class VacCollectorDisplay : DeviceDisplay<VacCollectorDevice>() {
|
||||
|
||||
private val table = FXCollections.observableHashMap<String, Double>()
|
||||
|
||||
private val sensorConnection = object : MeasurementListener, Connection{
|
||||
private val sensorConnection = object : MeasurementListener, Connection {
|
||||
override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant?) {
|
||||
if(result is Double){
|
||||
if (result is Double) {
|
||||
table.put(measurement.device.name, result);
|
||||
}
|
||||
}
|
||||
@ -47,7 +47,7 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
|
||||
}
|
||||
}
|
||||
|
||||
private val viewList = FXCollections.observableArrayList<VacViewConnection>();
|
||||
private val viewList = FXCollections.observableArrayList<VacDisplay>();
|
||||
|
||||
override fun buildView(device: VacCollectorDevice): View {
|
||||
return VacCollectorView();
|
||||
@ -56,7 +56,7 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
|
||||
override fun open(obj: Any) {
|
||||
super.open(obj)
|
||||
device.sensors.forEach { sensor ->
|
||||
val view = VacViewConnection()
|
||||
val view = VacDisplay()
|
||||
sensor.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
|
||||
sensor.connect(sensorConnection, Roles.MEASUREMENT_LISTENER_ROLE);
|
||||
viewList.add(view)
|
||||
@ -81,8 +81,8 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
|
||||
override val root = borderpane {
|
||||
top {
|
||||
toolbar {
|
||||
deviceStateToggle(this@VacCollectorViewConnection, Sensor.MEASURING_STATE, "Measure")
|
||||
deviceStateToggle(this@VacCollectorViewConnection, "storing", "Store")
|
||||
deviceStateToggle(this@VacCollectorDisplay, Sensor.MEASURING_STATE, "Measure")
|
||||
deviceStateToggle(this@VacCollectorDisplay, "storing", "Store")
|
||||
pane {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
@ -109,8 +109,10 @@ class VacCollectorViewConnection : DeviceViewConnection<VacCollectorDevice>() {
|
||||
hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
||||
vbox {
|
||||
viewList.forEach {
|
||||
add(it.view)
|
||||
separator(Orientation.HORIZONTAL)
|
||||
it.view?.let {
|
||||
add(it)
|
||||
separator(Orientation.HORIZONTAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +1,41 @@
|
||||
package inr.numass.control.readvac
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.DeviceFactory
|
||||
import hep.dataforge.control.devices.Sensor
|
||||
import hep.dataforge.control.virtual.VirtualDevice
|
||||
import hep.dataforge.meta.Meta
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceViewFactory
|
||||
import java.util.stream.Collectors
|
||||
|
||||
/**
|
||||
* A factory for vacuum measurements collector
|
||||
* Created by darksnake on 16-May-17.
|
||||
*/
|
||||
class VacDeviceFactory : DeviceViewFactory<VacCollectorDevice> {
|
||||
class VacDeviceFactory : DeviceFactory {
|
||||
override fun getType(): String {
|
||||
return "numass:vac"
|
||||
return "numass.vac"
|
||||
}
|
||||
|
||||
fun buildSensor(context: Context, sensorConfig: Meta): Sensor<Double> {
|
||||
when (sensorConfig.getString("sensorType", "")) {
|
||||
"mks" -> return MKSVacDevice(context, sensorConfig)
|
||||
"CM32" -> return CM32Device(context, sensorConfig)
|
||||
"meradat" -> return MeradatVacDevice(context, sensorConfig)
|
||||
"baratron" -> return MKSBaratronDevice(context, sensorConfig)
|
||||
VirtualDevice.VIRTUAL_SENSOR_TYPE -> return VirtualDevice.randomDoubleSensor(context, sensorConfig)
|
||||
private fun buildSensor(context: Context, sensorConfig: Meta): Sensor<Double> {
|
||||
return when (sensorConfig.getString("sensorType", "")) {
|
||||
"mks" -> MKSVacDevice(context, sensorConfig)
|
||||
"CM32" -> CM32Device(context, sensorConfig)
|
||||
"meradat" -> MeradatVacDevice(context, sensorConfig)
|
||||
"baratron" -> MKSBaratronDevice(context, sensorConfig)
|
||||
VirtualDevice.VIRTUAL_SENSOR_TYPE -> VirtualDevice.randomDoubleSensor(context, sensorConfig)
|
||||
else -> throw RuntimeException("Unknown vacuum sensor type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(context: Context, config: Meta): VacCollectorDevice {
|
||||
val sensors = config.getMetaList("sensor").stream()
|
||||
.map { sensorConfig ->
|
||||
buildSensor(context, sensorConfig)
|
||||
}.collect(Collectors.toList<Sensor<Double>>())
|
||||
.map { sensorConfig -> buildSensor(context, sensorConfig) }
|
||||
.collect(Collectors.toList<Sensor<Double>>())
|
||||
|
||||
val collector = VacCollectorDevice(context, config)
|
||||
collector.setSensors(sensors)
|
||||
return collector
|
||||
return VacCollectorDevice(context, config, sensors)
|
||||
}
|
||||
|
||||
override fun buildView(device: Device): DeviceViewConnection<VacCollectorDevice> {
|
||||
return VacCollectorViewConnection();
|
||||
}
|
||||
// override fun buildView(device: Device): DeviceDisplay<VacCollectorDevice> {
|
||||
// return VacCollectorDisplay();
|
||||
// }
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import hep.dataforge.control.devices.PortSensor.CONNECTED_STATE
|
||||
import hep.dataforge.control.devices.Sensor
|
||||
import hep.dataforge.control.measurements.Measurement
|
||||
import hep.dataforge.control.measurements.MeasurementListener
|
||||
import inr.numass.control.DeviceViewConnection
|
||||
import inr.numass.control.DeviceDisplay
|
||||
import inr.numass.control.switch
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
@ -31,7 +31,7 @@ import java.time.format.DateTimeFormatter
|
||||
/**
|
||||
* @author [Alexander Nozik](mailto:altavir@gmail.com)
|
||||
*/
|
||||
open class VacViewConnection : DeviceViewConnection<Sensor<Double>>(), MeasurementListener {
|
||||
open class VacDisplay : DeviceDisplay<Sensor<Double>>(), MeasurementListener {
|
||||
|
||||
val statusProperty = SimpleStringProperty("")
|
||||
var status: String by statusProperty
|
@ -59,7 +59,7 @@ public class Numass {
|
||||
|
||||
am.getAllActions()
|
||||
.map(name -> am.optAction(name).get())
|
||||
.map(action -> ActionDescriptor.build(action)).forEach(descriptor ->
|
||||
.map(ActionDescriptor::build).forEach(descriptor ->
|
||||
builder.text("\t").content(MarkupUtils.markupDescriptor(descriptor))
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user