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)