Some fixes to numass devices

This commit is contained in:
Alexander Nozik 2017-11-21 03:17:13 +03:00
parent b070156c51
commit 8ecdd75066
19 changed files with 926 additions and 887 deletions

View File

@ -7,6 +7,12 @@ allprojects{
javaParameters = true
}
}
kotlin {
experimental {
coroutines "enable"
}
}
}
dependencies {

View File

@ -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

View File

@ -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"/>
&lt;!&ndash;<sensor name="Collector" color="magenta" port="tcp::192.168.111.33:4003" sensorType="meradat" address="5"/>&ndash;&gt;
</device>-->
</config>

View File

@ -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;
}
}

View File

@ -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.");
}
}
}

View File

@ -52,7 +52,7 @@ public class TestSynch {
new SafeLambdaMagnet.SafeMagnetCondition() {
// @Override
// public boolean isBloking() {
// public boolean isBlocking() {
// return false;
// }
@Override

View File

@ -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;
}
}
}

View File

@ -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) }
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
override fun acceptPhrase(message: String?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun portError(errorMessage: String?, error: Throwable?) {
super.portError(errorMessage, error)
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")
}
}
/**
* 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 getContext(): Context = _context
//
// 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 = { _, _ -> })
}

View File

@ -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.")
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -113,7 +113,7 @@ abstract class DeviceDisplay<D : Device> : 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 ->

View File

@ -36,13 +36,9 @@ class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, met
return new
}
override fun createMeasurement(): Measurement<Double> {
return CMVacMeasurement()
}
override fun createMeasurement(): Measurement<Double> = 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<Double>() {
@ -71,9 +67,7 @@ class CM32Device(context: Context, meta: Meta) : PortSensor<Double>(context, met
}
}
override fun getDevice(): Device {
return this@CM32Device
}
override fun getDevice(): Device = this@CM32Device
}

View File

@ -26,13 +26,9 @@ class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor<Double>(conte
private val channel: Int = meta().getInt("channel", 2)
override fun createMeasurement(): Measurement<Double> {
return BaratronMeasurement()
}
override fun createMeasurement(): Measurement<Double> = 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 {

View File

@ -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<Double>(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,17 +70,13 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(context, m
return handler
}
override fun createMeasurement(): Measurement<Double> {
return MKSVacMeasurement()
}
override fun createMeasurement(): Measurement<Double> = MKSVacMeasurement()
@Throws(ControlException::class)
override fun computeState(stateName: String): Any {
return when (stateName) {
override fun computeState(stateName: String): Any = when (stateName) {
"power" -> talk("FP?") == "ON"
else -> super.computeState(stateName)
}
}
@Throws(ControlException::class)
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 {
return meta().getString("type", "MKS vacuumeter")
}
override fun getType(): String = meta().getString("type", "MKS vacuumeter")
private inner class MKSVacMeasurement : SimpleMeasurement<Double>() {
@ -139,18 +120,16 @@ class MKSVacDevice(context: Context, meta: Meta) : PortSensor<Double>(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
}
}

View File

@ -33,23 +33,19 @@ class MeradatVacDevice(context: Context, meta: Meta) : PortSensor<Double>(contex
return newHandler
}
override fun createMeasurement(): Measurement<Double> {
return MeradatMeasurement()
}
override fun createMeasurement(): Measurement<Double> = 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<Double>() {
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<Double>(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<Double>(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<Double>(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) {

View File

@ -55,13 +55,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<S
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
override fun optDevice(name: Name): Optional<Device> {
return Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
}
override fun optDevice(name: Name): Optional<Device> =
Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
override fun deviceNames(): Stream<Name> {
return sensors.stream().map { Name.ofSingle(it.name) }
}
override fun deviceNames(): Stream<Name> = sensors.stream().map { Name.ofSingle(it.name) }
override fun init() {
@ -70,15 +67,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<S
s.init()
}
}
override fun createMeasurement(): Measurement<Values> {
//TODO use meta
return VacuumMeasurement()
}
override fun createMeasurement(): Measurement<Values> = VacuumMeasurement()
override fun getType(): String {
return "Numass vacuum"
}
override fun getType(): String = "numass.vac.collector"
@Throws(ControlException::class)