Some fixes to numass devices
This commit is contained in:
parent
b070156c51
commit
8ecdd75066
@ -7,6 +7,12 @@ allprojects{
|
|||||||
javaParameters = true
|
javaParameters = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
experimental {
|
||||||
|
coroutines "enable"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -44,6 +44,14 @@ task debugWithDevice(dependsOn: classes, type: JavaExec) {
|
|||||||
group "debug"
|
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) {
|
task startScriptWithDevices(type: CreateStartScripts) {
|
||||||
applicationName = "control-room-devices"
|
applicationName = "control-room-devices"
|
||||||
mainClassName = mainClass
|
mainClassName = mainClass
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<config>
|
||||||
|
<server port="8337"/>
|
||||||
|
<!--<storage path="/home/numass-storage/" type="numass"/>-->
|
||||||
|
<numass run="2017_11"/>
|
||||||
|
<device type="numass.pkt8" name="thermo-1" averagingDuration ="PT60S" port = "192.168.111.36:4001" abuf ="1" >
|
||||||
|
<channel designation="a" name="T1" r0="1000" transformationType="hyperbolic" coefs="[17.066, -249.29, 1663.6, -5263.8, 9004.7, -7151.8, 2288.3]"/>
|
||||||
|
<channel designation="b" name="T2" r0="1000" transformationType="hyperbolic" coefs="[73.202, -841.5, 3995.9, -9579.1, 12361.0, -7976.5, 2161.3]"/>
|
||||||
|
<channel designation="c" name="T3" r0="1000" transformationType="hyperbolic" coefs="[52.402, -579.55, 2665.5, -6110.0, 7501.9, -4553.5, 1197.1]"/>
|
||||||
|
<channel designation="d" name="T4" visible="false" r0="1000" transformationType="hyperbolic" coefs="[17.136, -249.09, 1667.5, -5316.4, 9226.7, -7515.6, 2382.4]"/>
|
||||||
|
<channel designation="e" name="T5" visible="false" r0="1000" transformationType="hyperbolic" coefs="[58.060, -664.38, 3158.3, -7531.0, 9616.3, -6029.3, 1589.6]"/>
|
||||||
|
<channel designation="g" name="T7" r0="1000" transformationType="hyperbolic" coefs="[71.984, -822.09, 3872.0, -9183.0, 11729.0, -7518.6, 2034.2]"/>
|
||||||
|
<channel designation="h" name="T8" visible ="false" r0="1000" transformationType="hyperbolic" coefs="[23.894, -358.42, 2355.2, -7509.8, 12893.0, -10454.0, 3403.9]"/>
|
||||||
|
<plotConfig maxItems="5000" thickness="3"/>
|
||||||
|
</device>
|
||||||
|
<!-- <device type="numass.msp" name="msp">
|
||||||
|
<connection ip="192.168.111.11" port="10014"/>
|
||||||
|
<peakJump>
|
||||||
|
<peak mass="2" title = "H2" color="black" thickness="4"/>
|
||||||
|
<peak mass="3" title = "T/HD" visible = "false"/>
|
||||||
|
<peak mass="4" title = "D2/TH" color="blue"/>
|
||||||
|
<peak mass="5" title = "TD" color="green" thickness="4"/>
|
||||||
|
<peak mass="6" title = "T2" color="red" thickness="4"/>
|
||||||
|
<peak mass="12" title = "C" visible = "false"/>
|
||||||
|
<peak mass="14" title = "N" visible = "false"/>
|
||||||
|
<peak mass="18" title = "H2O" color = "cyan" visible = "false"/>
|
||||||
|
<peak mass="28" title = "N2" color = "magenta"/>
|
||||||
|
<peak mass="32" title = "O2" color = "orange" visible = "false"/>
|
||||||
|
</peakJump>
|
||||||
|
</device>
|
||||||
|
<device type="numass.vac">
|
||||||
|
<sensor name="P1" color="red" port="tcp::192.168.111.33:4002" sensorType="mks"/>
|
||||||
|
<sensor name="P2" color="blue" port="tcp::192.168.111.32:4002" sensorType="CM32"/>
|
||||||
|
<sensor name="P3" color="green" port="tcp::192.168.111.32:4003" sensorType="CM32"/>
|
||||||
|
<sensor name="Px" color="black" port="tcp::192.168.111.33:4003" sensorType="meradat" address="1"/>
|
||||||
|
<sensor name="Baratron" color="orange" port="tcp::192.168.111.33:4004" sensorType="baratron"/>
|
||||||
|
<!–<sensor name="Collector" color="magenta" port="tcp::192.168.111.33:4003" sensorType="meradat" address="5"/>–>
|
||||||
|
</device>-->
|
||||||
|
</config>
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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<SafeMagnetCondition> 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<Double> 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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -52,7 +52,7 @@ public class TestSynch {
|
|||||||
new SafeLambdaMagnet.SafeMagnetCondition() {
|
new SafeLambdaMagnet.SafeMagnetCondition() {
|
||||||
|
|
||||||
// @Override
|
// @Override
|
||||||
// public boolean isBloking() {
|
// public boolean isBlocking() {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
@Override
|
@Override
|
||||||
|
@ -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<Integer, VirtualMagnetStatus> magnets = new HashMap<>();
|
|
||||||
private final String virtualPortName;
|
|
||||||
|
|
||||||
public VirtualLambdaPort(String portName, Map<Integer, Double> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<LambdaMagnet>();
|
||||||
|
|
||||||
|
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<Device> {
|
||||||
|
return magnets.stream().filter { it.name == name.toUnescaped() }.map { it as Device }.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deviceNames(): Stream<Name> {
|
||||||
|
return magnets.stream().map { Name.ofSingle(it.name) }
|
||||||
|
}
|
||||||
|
}
|
@ -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> { 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,20 +1,95 @@
|
|||||||
package inr.numass.control.magnet
|
package inr.numass.control.magnet
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import hep.dataforge.control.ports.GenericPortController
|
||||||
import hep.dataforge.control.ports.Port
|
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;
|
private fun setAddress(address: Int, timeout: Duration) {
|
||||||
|
val response = sendAndWait("ADR $address\r", timeout) { true }.trim()
|
||||||
init {
|
if (response == "OK") {
|
||||||
port.holdBy(this)
|
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 <R> talk(address: Int, timeout: Duration, action: (GenericPortController) -> R): R {
|
||||||
|
synchronized(this) {
|
||||||
|
setAddress(address, timeout)
|
||||||
|
return action(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun portError(errorMessage: String?, error: Throwable?) {
|
// override fun getContext(): Context = _context
|
||||||
super.portError(errorMessage, error)
|
//
|
||||||
}
|
// private var address: Int = -1;
|
||||||
|
//
|
||||||
|
// private val channel = ConflatedChannel<String>();
|
||||||
|
//
|
||||||
|
// private val listeners = ReferenceRegistry<LambdaPortListener>()
|
||||||
|
//
|
||||||
|
// 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 = { _, _ -> })
|
||||||
}
|
}
|
@ -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<SafeMagnetCondition>()
|
||||||
|
|
||||||
|
// 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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<Int, VirtualMagnetStatus>()
|
||||||
|
private val virtualPortName: String = meta.getString("name", "virtual::numass.lambda")
|
||||||
|
|
||||||
|
// constructor(portName: String, magnets: Map<Int, Double>) {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
@ -120,7 +120,7 @@ class MspDevice(context: Context, meta: Meta) : PortSensor<Values>(context, meta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun portError(errorMessage: String?, error: Throwable?) {
|
override fun acceptError(errorMessage: String?, error: Throwable?) {
|
||||||
notifyError(errorMessage, error)
|
notifyError(errorMessage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,13 +113,13 @@ abstract class DeviceDisplay<D : Device> : Component(), Connection, DeviceListen
|
|||||||
protected fun bindBooleanToState(state: String, property: BooleanProperty) {
|
protected fun bindBooleanToState(state: String, property: BooleanProperty) {
|
||||||
getStateBinding(state).addListener { _, oldValue, newValue ->
|
getStateBinding(state).addListener { _, oldValue, newValue ->
|
||||||
if (isOpen && oldValue !== newValue) {
|
if (isOpen && oldValue !== newValue) {
|
||||||
property.value = newValue.booleanValue()
|
runLater { property.value = newValue.booleanValue() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
property.addListener { _, oldValue, newValue ->
|
property.addListener { _, oldValue, newValue ->
|
||||||
if (isOpen && oldValue != newValue) {
|
if (isOpen && oldValue != newValue) {
|
||||||
runAsync {
|
runAsync {
|
||||||
if(!device.isInitialized){
|
if (!device.isInitialized) {
|
||||||
device.init()
|
device.init()
|
||||||
}
|
}
|
||||||
device.setState(state, newValue).get().booleanValue();
|
device.setState(state, newValue).get().booleanValue();
|
||||||
|
@ -36,13 +36,9 @@ class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, met
|
|||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createMeasurement(): Measurement<Double> {
|
override fun createMeasurement(): Measurement<Double> = CMVacMeasurement()
|
||||||
return CMVacMeasurement()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(): String {
|
override fun getType(): String = meta().getString("type", "numass.vac.CM32")
|
||||||
return meta().getString("type", "Leibold CM32")
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class CMVacMeasurement : SimpleMeasurement<Double>() {
|
private inner class CMVacMeasurement : SimpleMeasurement<Double>() {
|
||||||
|
|
||||||
@ -71,9 +67,7 @@ class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, met
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDevice(): Device {
|
override fun getDevice(): Device = this@CM32Device
|
||||||
return this@CM32Device
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,9 @@ class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor<Double>(conte
|
|||||||
private val channel: Int = meta().getInt("channel", 2)
|
private val channel: Int = meta().getInt("channel", 2)
|
||||||
|
|
||||||
|
|
||||||
override fun createMeasurement(): Measurement<Double> {
|
override fun createMeasurement(): Measurement<Double> = BaratronMeasurement()
|
||||||
return BaratronMeasurement()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(): String {
|
override fun getType(): String = meta().getString("type", "numass.vac.baratron")
|
||||||
return meta().getString("type", "MKS baratron")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(ControlException::class)
|
@Throws(ControlException::class)
|
||||||
override fun buildPort(portName: String): Port {
|
override fun buildPort(portName: String): Port {
|
||||||
|
@ -20,7 +20,11 @@ import hep.dataforge.values.Value
|
|||||||
import hep.dataforge.values.ValueType.BOOLEAN
|
import hep.dataforge.values.ValueType.BOOLEAN
|
||||||
import inr.numass.control.DeviceView
|
import inr.numass.control.DeviceView
|
||||||
import javafx.beans.property.BooleanProperty
|
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 javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder
|
||||||
|
import tornadofx.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,34 +39,17 @@ import java.util.regex.Pattern
|
|||||||
@DeviceView(VacDisplay::class)
|
@DeviceView(VacDisplay::class)
|
||||||
class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
|
class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, meta) {
|
||||||
|
|
||||||
private val deviceAddress: String
|
val deviceAddressProperty = SimpleStringProperty()
|
||||||
get() = meta().getString("address", "253")
|
var deviceAddress by deviceAddressProperty
|
||||||
|
|
||||||
|
|
||||||
private var isPowerOn: Boolean
|
val isPowerOnProperty = SimpleBooleanProperty()
|
||||||
get() = getState("power").booleanValue()
|
var isPowerOn by isPowerOnProperty
|
||||||
@Throws(ControlException::class)
|
|
||||||
set(powerOn) {
|
|
||||||
if (powerOn != isPowerOn) {
|
val channelProperty = SimpleIntegerProperty(meta().getInt("channel", 5)!!)
|
||||||
if (powerOn) {
|
var channel by channelProperty
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val channel: Int = meta().getInt("channel", 5)!!
|
|
||||||
|
|
||||||
@Throws(ControlException::class)
|
@Throws(ControlException::class)
|
||||||
private fun talk(requestContent: String): String? {
|
private fun talk(requestContent: String): String? {
|
||||||
@ -83,17 +70,13 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, m
|
|||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createMeasurement(): Measurement<Double> {
|
override fun createMeasurement(): Measurement<Double> = MKSVacMeasurement()
|
||||||
return MKSVacMeasurement()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(ControlException::class)
|
@Throws(ControlException::class)
|
||||||
override fun computeState(stateName: String): Any {
|
override fun computeState(stateName: String): Any = when (stateName) {
|
||||||
return when (stateName) {
|
|
||||||
"power" -> talk("FP?") == "ON"
|
"power" -> talk("FP?") == "ON"
|
||||||
else -> super.computeState(stateName)
|
else -> super.computeState(stateName)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(ControlException::class)
|
@Throws(ControlException::class)
|
||||||
override fun requestStateChange(stateName: String, value: Value) {
|
override fun requestStateChange(stateName: String, value: Value) {
|
||||||
@ -122,9 +105,7 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, m
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getType(): String {
|
override fun getType(): String = meta().getString("type", "MKS vacuumeter")
|
||||||
return meta().getString("type", "MKS vacuumeter")
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class MKSVacMeasurement : SimpleMeasurement<Double>() {
|
private inner class MKSVacMeasurement : SimpleMeasurement<Double>() {
|
||||||
|
|
||||||
@ -139,18 +120,16 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, m
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val res = java.lang.Double.parseDouble(answer)
|
val res = java.lang.Double.parseDouble(answer)
|
||||||
if (res <= 0) {
|
return if (res <= 0) {
|
||||||
this.updateMessage("No power")
|
this.updateMessage("No power")
|
||||||
invalidateState("power")
|
invalidateState("power")
|
||||||
return null
|
null
|
||||||
} else {
|
} else {
|
||||||
this.updateMessage("OK")
|
this.updateMessage("OK")
|
||||||
return res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDevice(): Device {
|
override fun getDevice(): Device = this@MKSVacDevice
|
||||||
return this@MKSVacDevice
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,23 +33,19 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(contex
|
|||||||
return newHandler
|
return newHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createMeasurement(): Measurement<Double> {
|
override fun createMeasurement(): Measurement<Double> = MeradatMeasurement()
|
||||||
return MeradatMeasurement()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(): String {
|
override fun getType(): String = meta().getString("type", "Vit vacuumeter")
|
||||||
return meta().getString("type", "Vit vacuumeter")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private inner class MeradatMeasurement : SimpleMeasurement<Double>() {
|
private inner class MeradatMeasurement : SimpleMeasurement<Double>() {
|
||||||
|
|
||||||
private val query: String // ":010300000002FA\r\n";
|
|
||||||
private val response: Pattern
|
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 {
|
init {
|
||||||
base = String.format(":%02d", meta().getInt("address", 1))
|
|
||||||
val dataStr = base.substring(1) + REQUEST
|
val dataStr = base.substring(1) + REQUEST
|
||||||
query = base + REQUEST + calculateLRC(dataStr) + "\r\n"
|
query = base + REQUEST + calculateLRC(dataStr) + "\r\n"
|
||||||
response = Pattern.compile(base + "0304(\\w{4})(\\w{4})..\r\n")
|
response = Pattern.compile(base + "0304(\\w{4})(\\w{4})..\r\n")
|
||||||
@ -72,7 +68,7 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(contex
|
|||||||
val base = Integer.parseInt(match.group(1), 16).toDouble() / 10.0
|
val base = Integer.parseInt(match.group(1), 16).toDouble() / 10.0
|
||||||
var exp = Integer.parseInt(match.group(2), 16)
|
var exp = Integer.parseInt(match.group(2), 16)
|
||||||
if (exp > 32766) {
|
if (exp > 32766) {
|
||||||
exp = exp - 65536
|
exp -= 65536
|
||||||
}
|
}
|
||||||
var res = BigDecimal.valueOf(base * Math.pow(10.0, exp.toDouble()))
|
var res = BigDecimal.valueOf(base * Math.pow(10.0, exp.toDouble()))
|
||||||
res = res.setScale(4, RoundingMode.CEILING)
|
res = res.setScale(4, RoundingMode.CEILING)
|
||||||
@ -87,9 +83,7 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDevice(): Device {
|
override fun getDevice(): Device = this@MeradatVacDevice
|
||||||
return this@MeradatVacDevice
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -100,10 +94,7 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(contex
|
|||||||
* String is Hex String, need to convert in ASCII.
|
* String is Hex String, need to convert in ASCII.
|
||||||
*/
|
*/
|
||||||
val bytes = BigInteger(inputString, 16).toByteArray()
|
val bytes = BigInteger(inputString, 16).toByteArray()
|
||||||
var checksum = 0
|
val checksum = bytes.sumBy { it.toInt() }
|
||||||
for (aByte in bytes) {
|
|
||||||
checksum += aByte.toInt()
|
|
||||||
}
|
|
||||||
var value = Integer.toHexString(-checksum)
|
var value = Integer.toHexString(-checksum)
|
||||||
value = value.substring(value.length - 2).toUpperCase()
|
value = value.substring(value.length - 2).toUpperCase()
|
||||||
if (value.length < 2) {
|
if (value.length < 2) {
|
||||||
|
@ -55,13 +55,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<S
|
|||||||
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
|
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
|
||||||
|
|
||||||
|
|
||||||
override fun optDevice(name: Name): Optional<Device> {
|
override fun optDevice(name: Name): Optional<Device> =
|
||||||
return Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
|
Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
|
||||||
}
|
|
||||||
|
|
||||||
override fun deviceNames(): Stream<Name> {
|
override fun deviceNames(): Stream<Name> = sensors.stream().map { Name.ofSingle(it.name) }
|
||||||
return sensors.stream().map { Name.ofSingle(it.name) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
@ -70,15 +67,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<S
|
|||||||
s.init()
|
s.init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createMeasurement(): Measurement<Values> {
|
|
||||||
//TODO use meta
|
//TODO use meta
|
||||||
return VacuumMeasurement()
|
override fun createMeasurement(): Measurement<Values> = VacuumMeasurement()
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(): String {
|
override fun getType(): String = "numass.vac.collector"
|
||||||
return "Numass vacuum"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Throws(ControlException::class)
|
@Throws(ControlException::class)
|
||||||
|
Loading…
Reference in New Issue
Block a user