diff --git a/numass-control/build.gradle b/numass-control/build.gradle
index f0dfac84..9eefc6d5 100644
--- a/numass-control/build.gradle
+++ b/numass-control/build.gradle
@@ -7,6 +7,12 @@ allprojects{
javaParameters = true
}
}
+
+ kotlin {
+ experimental {
+ coroutines "enable"
+ }
+ }
}
dependencies {
diff --git a/numass-control/control-room/build.gradle b/numass-control/control-room/build.gradle
index 5b47f69e..31954a8d 100644
--- a/numass-control/control-room/build.gradle
+++ b/numass-control/control-room/build.gradle
@@ -44,6 +44,14 @@ task debugWithDevice(dependsOn: classes, type: JavaExec) {
group "debug"
}
+task runWithDevice(dependsOn: classes, type: JavaExec) {
+ main mainClass
+ args = ["--config.resource=/config/control-real.xml"]
+ classpath = sourceSets.main.runtimeClasspath + configurations.devices
+ description "Start application in debug mode"
+ group "debug"
+}
+
task startScriptWithDevices(type: CreateStartScripts) {
applicationName = "control-room-devices"
mainClassName = mainClass
diff --git a/numass-control/control-room/src/main/resources/config/control-real.xml b/numass-control/control-room/src/main/resources/config/control-real.xml
new file mode 100644
index 00000000..06d25124
--- /dev/null
+++ b/numass-control/control-room/src/main/resources/config/control-real.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/numass-control/magnet/src/main/java/inr/numass/control/magnet/LambdaMagnet.java b/numass-control/magnet/src/main/java/inr/numass/control/magnet/LambdaMagnet.java
deleted file mode 100644
index cdb54df5..00000000
--- a/numass-control/magnet/src/main/java/inr/numass/control/magnet/LambdaMagnet.java
+++ /dev/null
@@ -1,496 +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.magnet;
-
-import hep.dataforge.context.Context;
-import hep.dataforge.control.devices.AbstractDevice;
-import hep.dataforge.control.devices.StateDef;
-import hep.dataforge.control.ports.GenericPortController;
-import hep.dataforge.control.ports.PortFactory;
-import hep.dataforge.control.ports.PortTimeoutException;
-import hep.dataforge.description.ValueDef;
-import hep.dataforge.exceptions.ControlException;
-import hep.dataforge.exceptions.PortException;
-import hep.dataforge.meta.Meta;
-import hep.dataforge.utils.DateTimeUtils;
-import org.slf4j.LoggerFactory;
-
-import java.text.DecimalFormat;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import static hep.dataforge.values.ValueType.*;
-
-/**
- * @author Polina
- */
-@ValueDef(name = "timeout", type = {NUMBER}, def = "400", info = "A timeout for port response")
-@StateDef(value = @ValueDef(name = "output", type = BOOLEAN, info = "Weather output on or off"), writable = true)
-@StateDef(value = @ValueDef(name = "current", type = NUMBER, info = "Current current"))
-@StateDef(value = @ValueDef(name = "voltage", type = NUMBER, info = "Current voltage"))
-@StateDef(value = @ValueDef(name = "targetCurrent", type = NUMBER, info = "Target current"), writable = true)
-@StateDef(value = @ValueDef(name = "targetVoltage", type = NUMBER, info = "Target voltage"), writable = true)
-@StateDef(value = @ValueDef(name = "lastUpdate", type = TIME, info = "Time of the last update"), writable = true)
-public class LambdaMagnet extends AbstractDevice {
-
- private static final DecimalFormat LAMBDA_FORMAT = new DecimalFormat("###.##");
- public static double CURRENT_PRECISION = 0.05;
- // public static double CURRENT_STEP = 0.05;
- public static int DEFAULT_DELAY = 1;
- public static int DEFAULT_MONITOR_DELAY = 2000;
- public static double MAX_STEP_SIZE = 0.2;
- public static double MIN_UP_STEP_SIZE = 0.005;
- public static double MIN_DOWN_STEP_SIZE = 0.05;
- public static double MAX_SPEED = 5d; // 5 A per minute
-
- private boolean closePortOnShutDown = false;
-
- private final String name;
- private final int address;
- private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
-
- protected MagnetStateListener listener;
- // private volatile double current = 0;
- private final Duration timeout;
-// private Future monitorTask;
-// private Future updateTask;
-// private Instant lastUpdate = null;
-
- private double speed = MAX_SPEED;
-
- private final GenericPortController controller;
-
-
- /**
- * A setup for single magnet controller
- *
- * @param context
- * @param meta
- * @throws ControlException
- */
- public LambdaMagnet(Context context, Meta meta) throws ControlException {
- this(context, meta, new GenericPortController(context, PortFactory.getPort(meta.getString("port"))));
- closePortOnShutDown = true;
- }
-
-
- /**
- * Initialize magnet device with given port controller
- *
- * @param context
- * @param meta
- * @param controller
- */
- public LambdaMagnet(Context context, Meta meta, GenericPortController controller) {
- super(context, meta);
- this.controller = controller;
- name = meta.getString("name", "LAMBDA");
- address = meta.getInt("address", 1);
- timeout = meta.optString("timeout").map(Duration::parse).orElse(Duration.ofMillis(200));
- }
-
-// /**
-// * This method creates an element of class MegnetController with exact
-// * parameters. If you have two parameters for your method - the next
-// * constructor will be used.
-// *
-// * @param name
-// * @param port number of COM-port on your computer that you want to use
-// * @param address number of TDK - Lambda
-// * @param timeout waiting time for response
-// */
-// public LambdaMagnet(String name, Port port, int address, int timeout) {
-// this.name = name;
-// this.port = port;
-// this.port.setDelimiter("\r");//PENDING меняем состояние внешнего объекта?
-// this.address = address;
-// this.timeout = Duration.ofMillis(timeout);
-// }
-//
-// public LambdaMagnet(Port port, int address, int timeout) {
-// this(null, port, address, timeout);
-// }
-//
-// public LambdaMagnet(Port port, int address) {
-// this(null, port, address);
-// }
-//
-// public LambdaMagnet(String name, Port port, int address) {
-// this(name, port, address, 300);
-// }
-
-
- @Override
- public void init() throws ControlException {
- super.init();
- controller.open();
- }
-
- @Override
- public void shutdown() throws ControlException {
- super.shutdown();
- try {
- controller.close();
- if (closePortOnShutDown) {
- controller.getPort().close();
- }
- } catch (Exception ex) {
- throw new ControlException("Failed to close the port", ex);
- }
- }
-
- /**
- * Method converts double to LAMBDA string
- *
- * @param d double that should be converted to string
- * @return string
- */
- private static String d2s(double d) {
- return LAMBDA_FORMAT.format(d);
- }
-
- public void setListener(MagnetStateListener listener) {
- this.listener = listener;
- }
-
-// public double getMeasuredI() {
-// return current;
-// }
-
-// @Override
-// public void acceptPhrase(String message) {
-//
-// }
-//
-// @Override
-// public void reportError(String errorMessage, Throwable error) {
-// if (this.listener != null) {
-// listener.error(getName(), errorMessage, error);
-// } else {
-// LoggerFactory.getLogger(getClass()).error(errorMessage, error);
-// }
-// }
-
- private void reportError(String errorMessage, Throwable error) {
- if (this.listener != null) {
- listener.error(getName(), errorMessage, error);
- } else {
- LoggerFactory.getLogger(getClass()).error(errorMessage, error);
- }
- }
-
- private String talk(String request) throws PortException {
- try {
- controller.send(request + "\r");
- return controller.waitFor(timeout).trim();
- } catch (PortTimeoutException tex) {
- //Single retry on timeout
- LoggerFactory.getLogger(getClass()).warn("A timeout exception for request '" + request + "'. Making another atempt.");
- controller.send(request + "\r");
- return controller.waitFor(timeout).trim();
- }
- }
-
- private String getParameter(String name) throws PortException {
- String res = talk(name + "?");
- return res;
- }
-
- private boolean setParameter(String name, String state) throws PortException {
- String res = talk(name + " " + state);
- return "OK".equals(res);
- }
-
- private boolean setParameter(String name, int state) throws PortException {
- String res = talk(name + " " + state);
- return "OK".equals(res);
- }
-
- private boolean setParameter(String name, double state) throws PortException {
- String res = talk(name + " " + d2s(state));
- return "OK".equals(res);
- }
-
- /**
- * Extract number from LAMBDA response
- *
- * @param str
- * @return
- */
- private double s2d(String str) {
- return Double.valueOf(str);
- }
-
- private double getCurrent() throws PortException {
- if (!setADR()) {
- if (listener != null) {
- listener.error(getName(), "Can't set address", null);
- }
- throw new PortException("Can't set address");
- }
- return s2d(getParameter("MC"));
- }
-
- protected void setCurrent(double current) throws PortException {
- if (!setParameter("PC", current)) {
- reportError("Can't set the current", null);
- } else {
- lastUpdate = DateTimeUtils.now();
- }
- }
-
- private boolean setADR() throws PortException {
- if (setParameter("ADR", getAddress())) {
- if (listener != null) {
- listener.addressChanged(getName(), address);
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Gets status of magnet for current moment
- *
- * @return status of magnet
- */
- private MagnetStatus getStatus() throws PortException {
- if (!setADR()) {
- return MagnetStatus.off();
- }
-
- boolean out;
-
- out = "ON".equals(talk("OUT?"));
-
- double measuredCurrent = s2d(getParameter("MC"));
- this.current = measuredCurrent;
- double setCurrent = s2d(getParameter("PC"));
- double measuredVoltage = s2d(getParameter("MV"));
- double setVoltage = s2d(getParameter("PV"));
-
- MagnetStatus monitor = new MagnetStatus(out, measuredCurrent, setCurrent, measuredVoltage, setVoltage);
-
- if (listener != null) {
- listener.acceptStatus(getName(), monitor);
- }
- return monitor;
- }
-
- /**
- * Cancel current update task
- */
- public void stopUpdateTask() {
- if (updateTask != null) {
- updateTask.cancel(false);
- lastUpdate = null;
- if (listener != null) {
- listener.updateTaskStateChanged(getName(), false);
- }
- }
- }
-
- public void startUpdateTask(double targetI) {
- startUpdateTask(targetI, DEFAULT_DELAY);
- }
-
- /**
- * Start recursive updates of current with given delays between updates. If
- * delay is 0 then updates are made immediately.
- *
- * @param targetI
- * @param delay
- */
- public void startUpdateTask(double targetI, int delay) {
- assert delay > 0;
- stopUpdateTask();
- Runnable call = () -> {
- try {
- double measuredI = getCurrent();
- this.current = measuredI;
-
- if (listener != null) {
- listener.acceptMeasuredI(getName(), measuredI);
-
- }
-
- if (Math.abs(measuredI - targetI) > CURRENT_PRECISION) {
- double nextI = nextI(measuredI, targetI);
-
- if (listener != null) {
- listener.acceptNextI(getName(), nextI);
- }
- setCurrent(nextI);
- } else {
- stopUpdateTask();
- }
-
- } catch (PortException ex) {
- reportError("Error in update task", ex);
- stopUpdateTask();
- }
- };
-
- updateTask = scheduler.scheduleWithFixedDelay(call, 0, delay, TimeUnit.MILLISECONDS);
- if (listener != null) {
- listener.updateTaskStateChanged(getName(), true);
- }
- }
-
- public void setOutputMode(boolean out) throws PortException {
- if (!setADR()) {
- throw new RuntimeException();
- }
- int outState;
- if (out) {
- outState = 1;
- } else {
- outState = 0;
- }
- if (!setParameter("OUT", outState)) {
- if (listener != null) {
- listener.error(getName(), "Can't set output mode", null);
- }
- } else if (listener != null) {
- listener.outputModeChanged(getName(), out);
- }
- }
-
- private double nextI(double measuredI, double targetI) {
- assert measuredI != targetI;
-
- double step;
- if (lastUpdate == null) {
- step = MIN_UP_STEP_SIZE;
- } else {
- //Choose optimal speed but do not exceed maximum speed
- step = Math.min(MAX_STEP_SIZE,
- (double) lastUpdate.until(DateTimeUtils.now(), ChronoUnit.MILLIS) / 60000d * getSpeed());
- }
-
- double res;
- if (targetI > measuredI) {
- step = Math.max(MIN_UP_STEP_SIZE, step);
- res = Math.min(targetI, measuredI + step);
- } else {
- step = Math.max(MIN_DOWN_STEP_SIZE, step);
- res = Math.max(targetI, measuredI - step);
- }
-
- // не вводится ток меньше 0.5
- if (res < 0.5 && targetI > CURRENT_PRECISION) {
- return 0.5;
- } else if (res < 0.5 && targetI < CURRENT_PRECISION) {
- return 0;
- } else {
- return res;
- }
- }
-
- /**
- * Cancel current monitoring task
- */
- public void stopMonitorTask() {
- if (monitorTask != null) {
- monitorTask.cancel(true);
- if (listener != null) {
- listener.monitorTaskStateChanged(getName(), false);
- }
- monitorTask = null;
- }
- }
-
- public String getName() {
- if (this.name == null || this.name.isEmpty()) {
- return "LAMBDA " + getAddress();
- } else {
- return this.name;
- }
- }
-
- public void startMonitorTask() {
- startMonitorTask(DEFAULT_MONITOR_DELAY);
- }
-
- /**
- * Start monitoring task which checks for magnet status and then waits for
- * fixed time.
- *
- * @param delay an interval between scans in milliseconds
- */
- public void startMonitorTask(int delay) {
- assert delay >= 1000;
- stopMonitorTask();
-
- Runnable call = () -> {
- try {
- getStatus();
- } catch (PortException ex) {
- reportError("Port connection exception during status measurement", ex);
- stopMonitorTask();
- }
- };
-
- monitorTask = scheduler.scheduleWithFixedDelay(call, 0, delay, TimeUnit.MILLISECONDS);
-
- if (listener != null) {
- listener.monitorTaskStateChanged(getName(), true);
- }
-
- }
-
- public String request(String message) {
- try {
- if (!setADR()) {
- throw new RuntimeException("F")
- }
- return talk(message);
- } catch (PortException ex) {
- reportError("Can not send message to the port", ex);
- return null;
- }
- }
-
- /**
- * @return the address
- */
- public int getAddress() {
- return address;
- }
-
- /**
- * Get current change speed in Amper per minute
- *
- * @return
- */
- public double getSpeed() {
- return speed;
- }
-
- /**
- * Set current change speed in Amper per minute
- *
- * @param speed
- */
- public void setSpeed(double speed) {
- this.speed = speed;
- }
-
-}
diff --git a/numass-control/magnet/src/main/java/inr/numass/control/magnet/SafeLambdaMagnet.java b/numass-control/magnet/src/main/java/inr/numass/control/magnet/SafeLambdaMagnet.java
deleted file mode 100644
index 15e06a45..00000000
--- a/numass-control/magnet/src/main/java/inr/numass/control/magnet/SafeLambdaMagnet.java
+++ /dev/null
@@ -1,102 +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.magnet;
-
-import hep.dataforge.control.ports.Port;
-import hep.dataforge.exceptions.PortException;
-import org.slf4j.LoggerFactory;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.function.Predicate;
-
-/**
- *
- * @author Polina
- */
-public class SafeLambdaMagnet extends LambdaMagnet {
-
- private final Set safeConditions = new HashSet<>();
-
- public SafeLambdaMagnet(String name, Port port, int address, int timeout, SafeMagnetCondition... safeConditions) {
- super(name, port, address, timeout);
- this.safeConditions.addAll(Arrays.asList(safeConditions));
- }
-
- public SafeLambdaMagnet(String name, Port port, int address, SafeMagnetCondition... safeConditions) {
- super(name, port, address);
- this.safeConditions.addAll(Arrays.asList(safeConditions));
- }
-
- public void addSafeCondition(Predicate condition, boolean isBlocking){
- this.safeConditions.add(new SafeMagnetCondition() {
-
- @Override
- public boolean isSafe(int address, double current) {
- return condition.test(current);
- }
-
- @Override
- public boolean isBloking() {
- return isBlocking;
- }
- });
- }
-
- /**
- * Add symmetric non-blocking conditions to ensure currents in two magnets have difference within given tolerance.
- * @param controller
- * @param tolerance
- */
- public void bindTo(SafeLambdaMagnet controller, double tolerance){
- this.addSafeCondition((I)->Math.abs(controller.getMeasuredI() - I) <= tolerance, false);
- controller.addSafeCondition((I)->Math.abs(this.getMeasuredI() - I) <= tolerance, false);
- }
-
- @Override
- protected void setCurrent(double current) throws PortException {
- for (SafeMagnetCondition condition : safeConditions) {
- if (!condition.isSafe(getAddress(), current)) {
- if (condition.isBloking()) {
- condition.onFail();
- throw new RuntimeException("Can't set current. Condition not satisfied.");
- } else {
- if(listener!= null){
- listener.displayState("BOUND");
- }
- return;
- }
- }
- }
-
- super.setCurrent(current);
- }
-
- public interface SafeMagnetCondition {
-
- boolean isSafe(int address, double current);
-
- default boolean isBloking() {
- return true;
- }
-
- default void onFail() {
- LoggerFactory.getLogger(getClass()).error("Can't set current. Condition not satisfied.");
- }
- }
-
-}
diff --git a/numass-control/magnet/src/main/java/inr/numass/control/magnet/TestSynch.java b/numass-control/magnet/src/main/java/inr/numass/control/magnet/TestSynch.java
index 04acf136..8b3553ae 100644
--- a/numass-control/magnet/src/main/java/inr/numass/control/magnet/TestSynch.java
+++ b/numass-control/magnet/src/main/java/inr/numass/control/magnet/TestSynch.java
@@ -52,7 +52,7 @@ public class TestSynch {
new SafeLambdaMagnet.SafeMagnetCondition() {
// @Override
-// public boolean isBloking() {
+// public boolean isBlocking() {
// return false;
// }
@Override
diff --git a/numass-control/magnet/src/main/java/inr/numass/control/magnet/VirtualLambdaPort.java b/numass-control/magnet/src/main/java/inr/numass/control/magnet/VirtualLambdaPort.java
deleted file mode 100644
index 8846285c..00000000
--- a/numass-control/magnet/src/main/java/inr/numass/control/magnet/VirtualLambdaPort.java
+++ /dev/null
@@ -1,187 +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.magnet;
-
-import hep.dataforge.control.ports.VirtualPort;
-import hep.dataforge.exceptions.PortException;
-import hep.dataforge.meta.Meta;
-import org.slf4j.LoggerFactory;
-
-import java.time.Duration;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- *
- * @author Alexander Nozik
- */
-public class VirtualLambdaPort extends VirtualPort {
-
- private static final Duration latency = Duration.ofMillis(50);
-
- private volatile int currentAddress = -1;
- private Map magnets = new HashMap<>();
- private final String virtualPortName;
-
- public VirtualLambdaPort(String portName, Map magnets) {
- this.virtualPortName = portName;
- magnets.entrySet().stream().forEach((entry) -> {
- this.magnets.put(entry.getKey(), new VirtualMagnetStatus(entry.getValue()));
- });
- }
-
- public VirtualLambdaPort(String portName, int... magnets) {
- this.virtualPortName = portName;
- for (int magnet : magnets) {
- this.magnets.put(magnet, new VirtualMagnetStatus(0.01));
- }
- }
-
- @Override
- public String toString() {
- return virtualPortName;
- }
-
- @Override
- protected void evaluateRequest(String request) {
- String comand;
- String value = "";
- String[] split = request.split(" ");
- if (split.length == 1) {
- comand = request;
- } else {
- comand = split[0];
- value = split[1];
- }
- try {
- evaluateRequest(comand.trim(), value.trim());
- } catch (RuntimeException ex) {
-
- receivePhrase("FAIL");//TODO какая команда правильная?
- LoggerFactory.getLogger(getClass()).error("Request evaluation failure", ex);
- }
-
- }
-
- private void sendOK() {
- planResponse("OK", latency);
- }
-
- private void evaluateRequest(String comand, String value) {
- switch (comand) {
- case "ADR":
- int address = Integer.parseInt(value);
- if (magnets.containsKey(address)) {
- currentAddress = address;
- sendOK();
- }
- return;
- case "ADR?":
- planResponse(Integer.toString(currentAddress), latency);
- return;
- case "OUT":
- int state = Integer.parseInt(value);
- currentMagnet().out = (state == 1);
- sendOK();
- return;
- case "OUT?":
- boolean out = currentMagnet().out;
- if (out) {
- planResponse("ON", latency);
- } else {
- planResponse("OFF", latency);
- }
- return;
- case "PC":
- double current = Double.parseDouble(value);
- if (current < 0.5) {
- current = 0;
- }
- currentMagnet().current = current;
- sendOK();
- return;
- case "PC?":
- planResponse(Double.toString(currentMagnet().current), latency);
- return;
- case "MC?":
- planResponse(Double.toString(currentMagnet().current), latency);
- return;
- case "PV?":
- planResponse(Double.toString(currentMagnet().voltage()), latency);
- return;
- case "MV?":
- planResponse(Double.toString(currentMagnet().voltage()), latency);
- return;
- default:
- LoggerFactory.getLogger(getClass()).warn("Unknown comand {}", comand);
- }
- }
-
- private VirtualMagnetStatus currentMagnet() {
- if (currentAddress < 0) {
- throw new RuntimeException();
- }
- return magnets.get(currentAddress);
- }
-
- @Override
- public void close() throws Exception {
-
- }
-
- @Override
- public void open() throws PortException {
-
- }
-
- @Override
- public boolean isOpen() {
- return true;
- }
-
- @Override
- public Meta meta() {
- return Meta.buildEmpty("virtualPort");
-
- }
-
- private class VirtualMagnetStatus {
-
- public VirtualMagnetStatus(double resistance) {
- this.resistance = resistance;
- this.on = true;
- this.out = false;
- this.current = 0;
- }
-
- public VirtualMagnetStatus(double resistance, boolean on, boolean out, double current) {
- this.resistance = resistance;
- this.on = on;
- this.out = out;
- this.current = current;
- }
-
- private final double resistance;
- private boolean on;
- private boolean out;
- private double current;
-
- public double voltage() {
- return current * resistance;
- }
-
- }
-}
diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt
new file mode 100644
index 00000000..33255cd1
--- /dev/null
+++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt
@@ -0,0 +1,50 @@
+package inr.numass.control.magnet
+
+import hep.dataforge.context.Context
+import hep.dataforge.control.devices.AbstractDevice
+import hep.dataforge.control.devices.Device
+import hep.dataforge.control.devices.DeviceHub
+import hep.dataforge.control.devices.StateDef
+import hep.dataforge.control.ports.PortFactory
+import hep.dataforge.description.ValueDef
+import hep.dataforge.kodex.useEachMeta
+import hep.dataforge.meta.Meta
+import hep.dataforge.names.Name
+import hep.dataforge.values.ValueType
+import java.util.*
+import java.util.stream.Stream
+import kotlin.collections.ArrayList
+
+@StateDef(value = ValueDef(name = "address", type = arrayOf(ValueType.NUMBER), info = "Current active magnet"))
+class LambdaHub(context: Context, meta: Meta) : DeviceHub, AbstractDevice(context, meta) {
+
+ private val magnets = ArrayList();
+
+ private val port = PortFactory.getPort(meta.getString("port"))
+ private val controller = LambdaPortController(context, port)
+
+ init {
+ meta.useEachMeta("magnet") {
+ magnets.add(LambdaMagnet(context, it, controller))
+ }
+ }
+
+ override fun init() {
+ super.init()
+ controller.open()
+ }
+
+ override fun shutdown() {
+ super.shutdown()
+ controller.close()
+ port.close()
+ }
+
+ override fun optDevice(name: Name): Optional {
+ return magnets.stream().filter { it.name == name.toUnescaped() }.map { it as Device }.findFirst()
+ }
+
+ override fun deviceNames(): Stream {
+ return magnets.stream().map { Name.ofSingle(it.name) }
+ }
+}
\ No newline at end of file
diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt
new file mode 100644
index 00000000..8b9adf20
--- /dev/null
+++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt
@@ -0,0 +1,434 @@
+/*
+ * 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.magnet
+
+import hep.dataforge.context.Context
+import hep.dataforge.control.devices.AbstractDevice
+import hep.dataforge.control.devices.StateDef
+import hep.dataforge.control.devices.StateDefs
+import hep.dataforge.control.ports.PortFactory
+import hep.dataforge.control.ports.PortTimeoutException
+import hep.dataforge.description.ValueDef
+import hep.dataforge.exceptions.ControlException
+import hep.dataforge.exceptions.PortException
+import hep.dataforge.meta.Meta
+import hep.dataforge.utils.DateTimeUtils
+import hep.dataforge.values.ValueType.*
+import org.slf4j.LoggerFactory
+import java.text.DecimalFormat
+import java.time.Duration
+import java.time.Instant
+import java.time.temporal.ChronoUnit
+import java.util.concurrent.Future
+import java.util.concurrent.ScheduledThreadPoolExecutor
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Polina
+ */
+@ValueDef(name = "timeout", type = arrayOf(NUMBER), def = "400", info = "A timeout for port response")
+@StateDefs(
+ StateDef(value = ValueDef(name = "output", type = arrayOf(BOOLEAN), info = "Weather output on or off"), writable = true),
+ StateDef(value = ValueDef(name = "current", type = arrayOf(NUMBER), info = "Current current")),
+ StateDef(value = ValueDef(name = "voltage", type = arrayOf(NUMBER), info = "Current voltage")),
+ StateDef(value = ValueDef(name = "targetCurrent", type = arrayOf(NUMBER), info = "Target current"), writable = true),
+ StateDef(value = ValueDef(name = "targetVoltage", type = arrayOf(NUMBER), info = "Target voltage"), writable = true),
+ StateDef(value = ValueDef(name = "lastUpdate", type = arrayOf(TIME), info = "Time of the last update"), writable = true),
+ StateDef(value = ValueDef(name = "updating", type = arrayOf(BOOLEAN), info = "Shows if current ramping in progress"), writable = true),
+ StateDef(value = ValueDef(name = "monitoring", type = arrayOf(BOOLEAN), info = "Shows if monitoring task is running"), writable = true)
+)
+open class LambdaMagnet(context: Context, meta: Meta, private val controller: LambdaPortController) : AbstractDevice(context, meta) {
+
+ private var closePortOnShutDown = false
+
+ private val name: String? = meta.getString("name", "LAMBDA")
+ /**
+ * @return the address
+ */
+ val address: Int = meta.getInt("address", 1)!!
+ private val scheduler = ScheduledThreadPoolExecutor(1)
+
+ var listener: MagnetStateListener? = null
+ // private volatile double current = 0;
+ private val timeout: Duration = meta.optString("timeout").map { Duration.parse(it) }.orElse(Duration.ofMillis(200))
+ private var monitorTask: Future<*>? = null
+ private var updateTask: Future<*>? = null
+ private var lastUpdate: Instant? = null
+
+ /**
+ * Get current change speed in Amper per minute
+ *
+ * @return
+ */
+ /**
+ * Set current change speed in Amper per minute
+ *
+ * @param speed
+ */
+ var speed = MAX_SPEED
+
+ fun getCurrent(): Double = controller.talk(address, timeout) {
+ s2d(getParameter("MC"))
+ }
+
+ protected open fun setCurrent(current: Double) {
+
+ if (!setParameter("PC", current)) {
+ reportError("Can't set the current", null)
+ } else {
+ lastUpdate = DateTimeUtils.now()
+ }
+ }
+
+ /**
+ * Gets status of magnet for current moment
+ *
+ * @return status of magnet
+ */
+ private val status: MagnetStatus
+ @Throws(PortException::class)
+ get() {
+ return controller.talk(address, timeout) {
+ val out: Boolean = "ON" == talk("OUT?")
+
+ val measuredCurrent = s2d(getParameter("MC"))
+ updateState("current", measuredCurrent)
+ val setCurrent = s2d(getParameter("PC"))
+ val measuredVoltage = s2d(getParameter("MV"))
+ val setVoltage = s2d(getParameter("PV"))
+
+ MagnetStatus(out, measuredCurrent, setCurrent, measuredVoltage, setVoltage).also {
+ listener?.acceptStatus(getName(), it)
+ }
+ }
+ }
+
+
+ /**
+ * A setup for single magnet controller
+ *
+ * @param context
+ * @param meta
+ * @throws ControlException
+ */
+ @Throws(ControlException::class)
+ constructor(context: Context, meta: Meta) : this(context, meta, LambdaPortController(context, PortFactory.getPort(meta.getString("port")))) {
+ closePortOnShutDown = true
+ }
+
+
+ // /**
+ // * This method creates an element of class MegnetController with exact
+ // * parameters. If you have two parameters for your method - the next
+ // * constructor will be used.
+ // *
+ // * @param name
+ // * @param port number of COM-port on your computer that you want to use
+ // * @param address number of TDK - Lambda
+ // * @param timeout waiting time for response
+ // */
+ // public LambdaMagnet(String name, Port port, int address, int timeout) {
+ // this.name = name;
+ // this.port = port;
+ // this.port.setDelimiter("\r");//PENDING меняем состояние внешнего объекта?
+ // this.address = address;
+ // this.timeout = Duration.ofMillis(timeout);
+ // }
+ //
+ // public LambdaMagnet(Port port, int address, int timeout) {
+ // this(null, port, address, timeout);
+ // }
+ //
+ // public LambdaMagnet(Port port, int address) {
+ // this(null, port, address);
+ // }
+ //
+ // public LambdaMagnet(String name, Port port, int address) {
+ // this(name, port, address, 300);
+ // }
+
+
+ @Throws(ControlException::class)
+ override fun init() {
+ super.init()
+ controller.open()
+ }
+
+ @Throws(ControlException::class)
+ override fun shutdown() {
+ super.shutdown()
+ try {
+ if (closePortOnShutDown) {
+ controller.close()
+ controller.port.close()
+ }
+ } catch (ex: Exception) {
+ throw ControlException("Failed to close the port", ex)
+ }
+
+ }
+
+
+ // public double getMeasuredI() {
+ // return current;
+ // }
+
+ // @Override
+ // public void acceptPhrase(String message) {
+ //
+ // }
+ //
+ // @Override
+ // public void reportError(String errorMessage, Throwable error) {
+ // if (this.listener != null) {
+ // listener.error(getName(), errorMessage, error);
+ // } else {
+ // LoggerFactory.getLogger(getClass()).error(errorMessage, error);
+ // }
+ // }
+
+ private fun reportError(errorMessage: String, error: Throwable?) {
+ listener?.error(getName(), errorMessage, error) ?: LoggerFactory.getLogger(javaClass).error(errorMessage, error)
+
+ }
+
+ @Throws(PortException::class)
+ private fun talk(request: String): String {
+ try {
+ controller.send(request + "\r")
+ return controller.waitFor(timeout).trim()
+ } catch (tex: PortTimeoutException) {
+ //Single retry on timeout
+ LoggerFactory.getLogger(javaClass).warn("A timeout exception for request '$request'. Making another attempt.")
+ controller.send(request + "\r")
+ return controller.waitFor(timeout).trim()
+ }
+ }
+
+ private fun update(key: String, value: String) {
+ when (key) {
+ "OUT" -> updateState("output", value == "ON")
+ "MC" -> updateState("current", s2d(value))
+ "PC" -> updateState("targetCurrent", s2d(value))
+ "MV" -> updateState("voltage", s2d(value))
+ "PV" -> updateState("targetVoltage", s2d(value))
+ }
+ }
+
+ @Throws(PortException::class)
+ private fun getParameter(name: String): String {
+ return talk(name + "?").also {
+ update(name, it)
+ }
+ }
+
+ @Throws(PortException::class)
+ private fun setParameter(key: String, state: String): Boolean {
+ val res = talk("$key $state")
+ if ("OK" == res) {
+ update(key, state)
+ return true
+ } else {
+ return false
+ }
+ }
+
+ @Throws(PortException::class)
+ private fun setParameter(key: String, state: Int): Boolean = setParameter(key, state.toString())
+
+ @Throws(PortException::class)
+ private fun setParameter(key: String, state: Double): Boolean = setParameter(key, d2s(state))
+
+ /**
+ * Extract number from LAMBDA response
+ *
+ * @param str
+ * @return
+ */
+ private fun s2d(str: String): Double = java.lang.Double.valueOf(str)
+
+ /**
+ * Cancel current update task
+ */
+ fun stopUpdateTask() {
+ updateTask?.let {
+ it.cancel(false)
+ lastUpdate = null
+ listener?.updateTaskStateChanged(getName(), false)
+ }
+ }
+
+ /**
+ * Start recursive updates of current with given delays between updates. If
+ * delay is 0 then updates are made immediately.
+ *
+ * @param targetI
+ * @param delay
+ */
+ @JvmOverloads
+ fun startUpdateTask(targetI: Double, delay: Int = DEFAULT_DELAY) {
+ assert(delay > 0)
+ stopUpdateTask()
+ val call = {
+ try {
+ val measuredI = getCurrent()
+ listener?.acceptMeasuredI(getName(), measuredI)
+ if (Math.abs(measuredI - targetI) > CURRENT_PRECISION) {
+ val nextI = nextI(measuredI, targetI)
+ listener?.acceptNextI(getName(), nextI)
+ setCurrent(nextI)
+ } else {
+ stopUpdateTask()
+ }
+
+ } catch (ex: PortException) {
+ reportError("Error in update task", ex)
+ stopUpdateTask()
+ }
+ }
+
+ updateTask = scheduler.scheduleWithFixedDelay(call, 0, delay.toLong(), TimeUnit.MILLISECONDS)
+ listener?.updateTaskStateChanged(getName(), true)
+ }
+
+ @Throws(PortException::class)
+ fun setOutputMode(out: Boolean) {
+ controller.talk(address, timeout) {
+ val outState: Int = if (out) {
+ 1
+ } else {
+ 0
+ }
+ if (!setParameter("OUT", outState)) {
+ listener?.error(getName(), "Can't set output mode", null)
+ } else {
+ listener?.outputModeChanged(getName(), out)
+ }
+ }
+ }
+
+ private fun nextI(measuredI: Double, targetI: Double): Double {
+ assert(measuredI != targetI)
+
+ var step: Double
+ if (lastUpdate == null) {
+ step = MIN_UP_STEP_SIZE
+ } else {
+ //Choose optimal speed but do not exceed maximum speed
+ step = Math.min(MAX_STEP_SIZE,
+ lastUpdate!!.until(DateTimeUtils.now(), ChronoUnit.MILLIS).toDouble() / 60000.0 * speed)
+ }
+
+ val res: Double
+ if (targetI > measuredI) {
+ step = Math.max(MIN_UP_STEP_SIZE, step)
+ res = Math.min(targetI, measuredI + step)
+ } else {
+ step = Math.max(MIN_DOWN_STEP_SIZE, step)
+ res = Math.max(targetI, measuredI - step)
+ }
+
+ // не вводится ток меньше 0.5
+ return if (res < 0.5 && targetI > CURRENT_PRECISION) {
+ 0.5
+ } else if (res < 0.5 && targetI < CURRENT_PRECISION) {
+ 0.0
+ } else {
+ res
+ }
+ }
+
+ /**
+ * Cancel current monitoring task
+ */
+ fun stopMonitorTask() {
+ monitorTask?.let {
+ it.cancel(true)
+ listener?.monitorTaskStateChanged(getName(), false)
+ monitorTask = null
+ }
+ }
+
+ override fun getName(): String {
+ return if (this.name == null || this.name.isEmpty()) {
+ "LAMBDA " + address
+ } else {
+ this.name
+ }
+ }
+
+ /**
+ * Start monitoring task which checks for magnet status and then waits for
+ * fixed time.
+ *
+ * @param delay an interval between scans in milliseconds
+ */
+ @JvmOverloads
+ fun startMonitorTask(delay: Int = DEFAULT_MONITOR_DELAY) {
+ assert(delay >= 1000)
+ stopMonitorTask()
+
+ val call = Runnable {
+ try {
+ status
+ } catch (ex: PortException) {
+ reportError("Port connection exception during status measurement", ex)
+ stopMonitorTask()
+ }
+ }
+
+ monitorTask = scheduler.scheduleWithFixedDelay(call, 0, delay.toLong(), TimeUnit.MILLISECONDS)
+ listener?.monitorTaskStateChanged(getName(), true)
+
+ }
+
+// fun request(message: String): String? {
+// try {
+// if (!setADR()) {
+// throw RuntimeException("F")
+// }
+// return talk(message)
+// } catch (ex: PortException) {
+// reportError("Can not send message to the port", ex)
+// return null
+// }
+//
+// }
+
+ companion object {
+
+ private val LAMBDA_FORMAT = DecimalFormat("###.##")
+ var CURRENT_PRECISION = 0.05
+ // public static double CURRENT_STEP = 0.05;
+ var DEFAULT_DELAY = 1
+ var DEFAULT_MONITOR_DELAY = 2000
+ var MAX_STEP_SIZE = 0.2
+ var MIN_UP_STEP_SIZE = 0.005
+ var MIN_DOWN_STEP_SIZE = 0.05
+ var MAX_SPEED = 5.0 // 5 A per minute
+
+ /**
+ * Method converts double to LAMBDA string
+ *
+ * @param d double that should be converted to string
+ * @return string
+ */
+ private fun d2s(d: Double): String {
+ return LAMBDA_FORMAT.format(d)
+ }
+ }
+
+}
diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt
index 2778fcf7..bc90c416 100644
--- a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt
+++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt
@@ -1,20 +1,95 @@
package inr.numass.control.magnet
+import hep.dataforge.context.Context
+import hep.dataforge.control.ports.GenericPortController
import hep.dataforge.control.ports.Port
+import java.time.Duration
-class LambdaPortController(private val port: Port) : Port.PortController {
+class LambdaPortController(context: Context, port: Port) : GenericPortController(context, port) {
+ private var currentAddress: Int = -1;
- private var address: Int = -1;
-
- init {
- port.holdBy(this)
+ private fun setAddress(address: Int, timeout: Duration) {
+ val response = sendAndWait("ADR $address\r", timeout) { true }.trim()
+ if (response == "OK") {
+ currentAddress = address
+ } else {
+ throw RuntimeException("Failed to set address to LAMBDA device on $port")
+ }
}
- override fun acceptPhrase(message: String?) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
+ /**
+ * perform series of synchronous actions ensuring that all of them have the same address
+ */
+ fun talk(address: Int, timeout: Duration, action: (GenericPortController) -> R): R {
+ synchronized(this) {
+ setAddress(address, timeout)
+ return action(this)
+ }
}
- override fun portError(errorMessage: String?, error: Throwable?) {
- super.portError(errorMessage, error)
- }
+// override fun getContext(): Context = _context
+//
+// private var address: Int = -1;
+//
+// private val channel = ConflatedChannel();
+//
+// private val listeners = ReferenceRegistry()
+//
+// fun open() {
+// try {
+// port.holdBy(this)
+// if (!port.isOpen) {
+// port.open()
+// }
+// } catch (e: PortException) {
+// throw RuntimeException("Can't hold the port $port LAMBDA port controller", e)
+// }
+// }
+//
+//
+// private suspend fun setAddress(address: Int, timeout: Duration) {
+// synchronized(this) {
+// port.send(this, "ADR $address\r")
+// val res = channel.receive()
+// if (res == "OK") {
+// this.address = address
+// }
+// }
+// }
+//
+// private suspend fun sendMessage(message: String): String {
+//
+// }
+//
+// suspend fun fireSequence(address: Int, timeout: Duration, vararg messages: String) {
+// setAddress(address, timeout);
+// for (message in messages) {
+// sendMessage(message);
+// }
+// }
+//
+// override fun close() {
+// port.releaseBy(this)
+// }
+//
+// override fun acceptPhrase(message: String) {
+// async {
+// channel.send(message);
+// }
+// listeners.forEach {
+// if (it.address == address) {
+// context.parallelExecutor().submit { it.action(message) }
+// }
+// }
+// }
+//
+// override fun acceptError(errorMessage: String?, error: Throwable?) {
+// listeners.forEach {
+// if (it.address == address) {
+// context.parallelExecutor().submit { it.onError(errorMessage, error) }
+// }
+// }
+// }
+//
+// class LambdaPortListener(val address: Int, val action: (String) -> Unit, val onError: (String?, Throwable?) -> Unit = { _, _ -> })
}
\ No newline at end of file
diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/SafeLambdaMagnet.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/SafeLambdaMagnet.kt
new file mode 100644
index 00000000..18fc9cd9
--- /dev/null
+++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/SafeLambdaMagnet.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.magnet
+
+import hep.dataforge.context.Context
+import hep.dataforge.exceptions.PortException
+import hep.dataforge.meta.Meta
+import org.slf4j.LoggerFactory
+import java.util.*
+
+/**
+ *
+ * @author Polina
+ */
+class SafeLambdaMagnet(context: Context, meta: Meta, controller: LambdaPortController) :
+ LambdaMagnet(context, meta, controller) {
+
+ private val safeConditions = HashSet()
+
+ // public SafeLambdaMagnet(String name, Port port, int address, int timeout, SafeMagnetCondition... safeConditions) {
+ // super(name, port, address, timeout);
+ // this.safeConditions.addAll(Arrays.asList(safeConditions));
+ // }
+ //
+ // public SafeLambdaMagnet(String name, Port port, int address, SafeMagnetCondition... safeConditions) {
+ // super(name, port, address);
+ // this.safeConditions.addAll(Arrays.asList(safeConditions));
+ // }
+
+ fun addSafeCondition(isBlocking: Boolean, condition: (Double) -> Boolean) {
+ this.safeConditions.add(object : SafeMagnetCondition {
+
+ override fun isBlocking(): Boolean = isBlocking
+
+ override fun isSafe(address: Int, current: Double): Boolean = condition(current)
+ })
+ }
+
+ /**
+ * Add symmetric non-blocking conditions to ensure currents in two magnets have difference within given tolerance.
+ * @param controller
+ * @param tolerance
+ */
+ fun bindTo(controller: SafeLambdaMagnet, tolerance: Double) {
+ this.addSafeCondition(false) { I -> Math.abs(controller.getCurrent() - I) <= tolerance }
+ controller.addSafeCondition(false) { I -> Math.abs(this.getCurrent() - I) <= tolerance }
+ }
+
+
+ @Throws(PortException::class)
+ override fun setCurrent(current: Double) {
+ safeConditions
+ .filterNot { it.isSafe(address, current) }
+ .forEach {
+ if (it.isBlocking()) {
+ it.onFail()
+ throw RuntimeException("Can't set current. Condition not satisfied.")
+ } else {
+ listener?.displayState("BOUND")
+ }
+ }
+
+ super.setCurrent(current)
+ }
+
+ interface SafeMagnetCondition {
+
+ fun isBlocking(): Boolean = true
+
+ fun isSafe(address: Int, current: Double): Boolean
+
+ fun onFail() {
+ LoggerFactory.getLogger(javaClass).error("Can't set current. Condition not satisfied.")
+ }
+ }
+
+}
diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt
new file mode 100644
index 00000000..e5ac2974
--- /dev/null
+++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.magnet
+
+import hep.dataforge.control.ports.VirtualPort
+import hep.dataforge.exceptions.PortException
+import hep.dataforge.kodex.useEachMeta
+import hep.dataforge.meta.Meta
+import org.slf4j.LoggerFactory
+import java.time.Duration
+import java.util.*
+
+/**
+ *
+ * @author Alexander Nozik
+ */
+class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) {
+
+ @Volatile private var currentAddress = -1
+ private val magnets = HashMap()
+ private val virtualPortName: String = meta.getString("name", "virtual::numass.lambda")
+
+// constructor(portName: String, magnets: Map) {
+// this.virtualPortName = portName
+// magnets.forEach { key, value -> this.magnets.put(key, VirtualMagnetStatus(value)) }
+// }
+//
+// constructor(portName: String, vararg magnets: Int) {
+// this.virtualPortName = portName
+// for (magnet in magnets) {
+// this.magnets.put(magnet, VirtualMagnetStatus(0.01))
+// }
+// }
+
+ init {
+ meta.useEachMeta("magnet") {
+ val num = it.getInt("address", 1)
+ val resistance = it.getDouble("resistance", 1.0)
+ magnets.put(num, VirtualMagnetStatus(resistance))
+ }
+ }
+
+ override fun toString(): String = virtualPortName
+
+ override fun evaluateRequest(request: String) {
+ val command: String
+ var value = ""
+ val split = request.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ if (split.size == 1) {
+ command = request
+ } else {
+ command = split[0]
+ value = split[1]
+ }
+ try {
+ evaluateRequest(command.trim { it <= ' ' }, value.trim { it <= ' ' })
+ } catch (ex: RuntimeException) {
+
+ receivePhrase("FAIL")//TODO какая команда правильная?
+ LoggerFactory.getLogger(javaClass).error("Request evaluation failure", ex)
+ }
+
+ }
+
+ private fun sendOK() {
+ planResponse("OK", latency)
+ }
+
+ private fun evaluateRequest(comand: String, value: String) {
+ when (comand) {
+ "ADR" -> {
+ val address = Integer.parseInt(value)
+ if (magnets.containsKey(address)) {
+ currentAddress = address
+ sendOK()
+ }
+ return
+ }
+ "ADR?" -> {
+ planResponse(Integer.toString(currentAddress), latency)
+ return
+ }
+ "OUT" -> {
+ val state = Integer.parseInt(value)
+ currentMagnet().out = state == 1
+ sendOK()
+ return
+ }
+ "OUT?" -> {
+ val out = currentMagnet().out
+ if (out) {
+ planResponse("ON", latency)
+ } else {
+ planResponse("OFF", latency)
+ }
+ return
+ }
+ "PC" -> {
+ var current = java.lang.Double.parseDouble(value)
+ if (current < 0.5) {
+ current = 0.0
+ }
+ currentMagnet().current = current
+ sendOK()
+ return
+ }
+ "PC?" -> {
+ planResponse(java.lang.Double.toString(currentMagnet().current), latency)
+ return
+ }
+ "MC?" -> {
+ planResponse(java.lang.Double.toString(currentMagnet().current), latency)
+ return
+ }
+ "PV?" -> {
+ planResponse(java.lang.Double.toString(currentMagnet().getVoltage()), latency)
+ return
+ }
+ "MV?" -> {
+ planResponse(java.lang.Double.toString(currentMagnet().getVoltage()), latency)
+ return
+ }
+ else -> LoggerFactory.getLogger(javaClass).warn("Unknown comand {}", comand)
+ }
+ }
+
+ private fun currentMagnet(): VirtualMagnetStatus {
+ if (currentAddress < 0) {
+ throw RuntimeException()
+ }
+ return magnets[currentAddress]!!
+ }
+
+ @Throws(Exception::class)
+ override fun close() {
+
+ }
+
+ @Throws(PortException::class)
+ override fun open() {
+
+ }
+
+ override fun isOpen(): Boolean = true
+
+ private inner class VirtualMagnetStatus(val resistance: Double,
+ var on: Boolean = true,
+ var out: Boolean = false,
+ var current: Double = 0.0) {
+
+ fun getVoltage() = current * resistance
+ }
+
+ companion object {
+
+ private val latency = Duration.ofMillis(50)
+ }
+}
diff --git a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt
index 205dbff9..c5af5a72 100644
--- a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt
+++ b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt
@@ -120,7 +120,7 @@ class MspDevice(context: Context, meta: Meta) : PortSensor(context, meta
}
}
- override fun portError(errorMessage: String?, error: Throwable?) {
+ override fun acceptError(errorMessage: String?, error: Throwable?) {
notifyError(errorMessage, error)
}
diff --git a/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt b/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt
index 73b1e8be..a2631616 100644
--- a/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt
+++ b/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt
@@ -113,13 +113,13 @@ abstract class DeviceDisplay : Component(), Connection, DeviceListen
protected fun bindBooleanToState(state: String, property: BooleanProperty) {
getStateBinding(state).addListener { _, oldValue, newValue ->
if (isOpen && oldValue !== newValue) {
- property.value = newValue.booleanValue()
+ runLater { property.value = newValue.booleanValue() }
}
}
property.addListener { _, oldValue, newValue ->
if (isOpen && oldValue != newValue) {
runAsync {
- if(!device.isInitialized){
+ if (!device.isInitialized) {
device.init()
}
device.setState(state, newValue).get().booleanValue();
diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt
index 0c033c32..83d86e6e 100644
--- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt
+++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt
@@ -36,13 +36,9 @@ class CM32Device(context: Context, meta: Meta) : PortSensor(context, met
return new
}
- override fun createMeasurement(): Measurement {
- return CMVacMeasurement()
- }
+ override fun createMeasurement(): Measurement = CMVacMeasurement()
- override fun getType(): String {
- return meta().getString("type", "Leibold CM32")
- }
+ override fun getType(): String = meta().getString("type", "numass.vac.CM32")
private inner class CMVacMeasurement : SimpleMeasurement() {
@@ -71,9 +67,7 @@ class CM32Device(context: Context, meta: Meta) : PortSensor(context, met
}
}
- override fun getDevice(): Device {
- return this@CM32Device
- }
+ override fun getDevice(): Device = this@CM32Device
}
diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt
index ba1e2446..b589d15a 100644
--- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt
+++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt
@@ -26,13 +26,9 @@ class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor(conte
private val channel: Int = meta().getInt("channel", 2)
- override fun createMeasurement(): Measurement {
- return BaratronMeasurement()
- }
+ override fun createMeasurement(): Measurement = BaratronMeasurement()
- override fun getType(): String {
- return meta().getString("type", "MKS baratron")
- }
+ override fun getType(): String = meta().getString("type", "numass.vac.baratron")
@Throws(ControlException::class)
override fun buildPort(portName: String): Port {
diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt
index ebe35942..9aa57e0f 100644
--- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt
+++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt
@@ -20,7 +20,11 @@ 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.SimpleBooleanProperty
+import javafx.beans.property.SimpleIntegerProperty
+import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder
+import tornadofx.*
import java.util.regex.Pattern
/**
@@ -35,34 +39,17 @@ import java.util.regex.Pattern
@DeviceView(VacDisplay::class)
class MKSVacDevice(context: Context, meta: Meta) : PortSensor(context, meta) {
- private val deviceAddress: String
- get() = meta().getString("address", "253")
+ val deviceAddressProperty = SimpleStringProperty()
+ var deviceAddress by deviceAddressProperty
- private var isPowerOn: Boolean
- get() = getState("power").booleanValue()
- @Throws(ControlException::class)
- set(powerOn) {
- if (powerOn != isPowerOn) {
- if (powerOn) {
- val ans = talk("FP!ON")
- if (ans == "ON") {
- updateState("power", true)
- } else {
- this.notifyError("Failed to set power state", null)
- }
- } else {
- val ans = talk("FP!OFF")
- if (ans == "OFF") {
- updateState("power", false)
- } else {
- this.notifyError("Failed to set power state", null)
- }
- }
- }
- }
+ val isPowerOnProperty = SimpleBooleanProperty()
+ var isPowerOn by isPowerOnProperty
+
+
+ val channelProperty = SimpleIntegerProperty(meta().getInt("channel", 5)!!)
+ var channel by channelProperty
- private val channel: Int = meta().getInt("channel", 5)!!
@Throws(ControlException::class)
private fun talk(requestContent: String): String? {
@@ -83,16 +70,12 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor(context, m
return handler
}
- override fun createMeasurement(): Measurement {
- return MKSVacMeasurement()
- }
+ override fun createMeasurement(): Measurement = MKSVacMeasurement()
@Throws(ControlException::class)
- override fun computeState(stateName: String): Any {
- return when (stateName) {
- "power" -> talk("FP?") == "ON"
- else -> super.computeState(stateName)
- }
+ override fun computeState(stateName: String): Any = when (stateName) {
+ "power" -> talk("FP?") == "ON"
+ else -> super.computeState(stateName)
}
@Throws(ControlException::class)
@@ -122,9 +105,7 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor(context, m
}
- override fun getType(): String {
- return meta().getString("type", "MKS vacuumeter")
- }
+ override fun getType(): String = meta().getString("type", "MKS vacuumeter")
private inner class MKSVacMeasurement : SimpleMeasurement() {
@@ -139,18 +120,16 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor(context, m
return null
}
val res = java.lang.Double.parseDouble(answer)
- if (res <= 0) {
+ return if (res <= 0) {
this.updateMessage("No power")
invalidateState("power")
- return null
+ null
} else {
this.updateMessage("OK")
- return res
+ res
}
}
- override fun getDevice(): Device {
- return this@MKSVacDevice
- }
+ override fun getDevice(): Device = this@MKSVacDevice
}
}
diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt
index c1c7a77b..396e6010 100644
--- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt
+++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt
@@ -33,23 +33,19 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor(contex
return newHandler
}
- override fun createMeasurement(): Measurement {
- return MeradatMeasurement()
- }
+ override fun createMeasurement(): Measurement = MeradatMeasurement()
- override fun getType(): String {
- return meta().getString("type", "Vit vacuumeter")
- }
+ override fun getType(): String = meta().getString("type", "Vit vacuumeter")
private inner class MeradatMeasurement : SimpleMeasurement() {
- private val query: String // ":010300000002FA\r\n";
+
private val response: Pattern
- private val base: String
+ private val base: String = String.format(":%02d", meta().getInt("address", 1))
+ private val query: String // ":010300000002FA\r\n";
init {
- base = String.format(":%02d", meta().getInt("address", 1))
val dataStr = base.substring(1) + REQUEST
query = base + REQUEST + calculateLRC(dataStr) + "\r\n"
response = Pattern.compile(base + "0304(\\w{4})(\\w{4})..\r\n")
@@ -72,7 +68,7 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor(contex
val base = Integer.parseInt(match.group(1), 16).toDouble() / 10.0
var exp = Integer.parseInt(match.group(2), 16)
if (exp > 32766) {
- exp = exp - 65536
+ exp -= 65536
}
var res = BigDecimal.valueOf(base * Math.pow(10.0, exp.toDouble()))
res = res.setScale(4, RoundingMode.CEILING)
@@ -87,9 +83,7 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor(contex
}
}
- override fun getDevice(): Device {
- return this@MeradatVacDevice
- }
+ override fun getDevice(): Device = this@MeradatVacDevice
}
companion object {
@@ -100,10 +94,7 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor(contex
* String is Hex String, need to convert in ASCII.
*/
val bytes = BigInteger(inputString, 16).toByteArray()
- var checksum = 0
- for (aByte in bytes) {
- checksum += aByte.toInt()
- }
+ val checksum = bytes.sumBy { it.toInt() }
var value = Integer.toHexString(-checksum)
value = value.substring(value.length - 2).toUpperCase()
if (value.length < 2) {
diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt
index cb6394d7..39e9d891 100644
--- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt
+++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt
@@ -55,13 +55,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection {
- return Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
- }
+ override fun optDevice(name: Name): Optional =
+ Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
- override fun deviceNames(): Stream {
- return sensors.stream().map { Name.ofSingle(it.name) }
- }
+ override fun deviceNames(): Stream = sensors.stream().map { Name.ofSingle(it.name) }
override fun init() {
@@ -70,15 +67,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection = VacuumMeasurement()
- override fun createMeasurement(): Measurement {
- //TODO use meta
- return VacuumMeasurement()
- }
-
- override fun getType(): String {
- return "Numass vacuum"
- }
+ override fun getType(): String = "numass.vac.collector"
@Throws(ControlException::class)