Numass devices uodate

This commit is contained in:
Alexander Nozik 2017-11-09 21:41:42 +03:00
parent f387302388
commit b15ffe6bda
14 changed files with 695 additions and 735 deletions

View File

@ -7,13 +7,12 @@ import tornadofx.*
* Created by darksnake on 19-May-17. * Created by darksnake on 19-May-17.
*/ */
class ServerApp : App(BoardView::class) { class ServerApp : App(BoardView::class) {
val controller: BoardController by inject(); private val controller: BoardController by inject();
override fun start(stage: Stage) { override fun start(stage: Stage) {
controller.load(this) controller.load(this)
super.start(stage) super.start(stage)
setDFStageIcon(stage)
} }
override fun stop() { override fun stop() {

View File

@ -6,13 +6,12 @@ import hep.dataforge.exceptions.StorageException
import hep.dataforge.storage.api.TableLoader import hep.dataforge.storage.api.TableLoader
import hep.dataforge.values.Values import hep.dataforge.values.Values
import java.util.* import java.util.*
import java.util.function.Function
/** /**
* A helper to store points in multiple loaders * A helper to store points in multiple loaders
* Created by darksnake on 16-May-17. * Created by darksnake on 16-May-17.
*/ */
class StorageHelper(private val device: AbstractDevice, private val loaderFactory: Function<StorageConnection, TableLoader>) : AutoCloseable { class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection)-> TableLoader) : AutoCloseable {
private val loaderMap = HashMap<StorageConnection, TableLoader>() private val loaderMap = HashMap<StorageConnection, TableLoader>()
fun push(point: Values) { fun push(point: Values) {

View File

@ -1,88 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.ComPortHandler;
import hep.dataforge.control.ports.PortFactory;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
/**
* @author Alexander Nozik
*/
public class CM32Device extends PortSensor<Double> {
public CM32Device() {
}
public CM32Device(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
@Override
protected PortHandler buildHandler(String portName) throws ControlException {
getLogger().info("Connecting to port {}", portName);
PortHandler newHandler;
if (portName.startsWith("com")) {
newHandler = new ComPortHandler(portName, 2400, 8, 1, 0);
} else {
newHandler = PortFactory.getPort(portName);
}
newHandler.setDelimiter("T--\r");
return newHandler;
}
@Override
protected Measurement<Double> createMeasurement() {
return new CMVacMeasurement();
}
@Override
public String getType() {
return meta().getString("type", "Leibold CM32");
}
private class CMVacMeasurement extends SimpleMeasurement<Double> {
private static final String CM32_QUERY = "MES R PM 1\r\n";
@Override
protected synchronized Double doMeasure() throws Exception {
String answer = sendAndWait(CM32_QUERY, timeout());
if (answer.isEmpty()) {
this.updateMessage("No signal");
updateState(CONNECTED_STATE, false);
return null;
} else if (!answer.contains("PM1:mbar")) {
this.updateMessage("Wrong answer: " + answer);
updateState(CONNECTED_STATE, false);
return null;
} else if (answer.substring(14, 17).equals("OFF")) {
this.updateMessage("Off");
updateState(CONNECTED_STATE, true);
return null;
} else {
this.updateMessage("OK");
updateState(CONNECTED_STATE, true);
return Double.parseDouble(answer.substring(14, 17) + answer.substring(19, 23));
}
}
@Override
public Device getDevice() {
return CM32Device.this;
}
}
}

View File

@ -1,57 +0,0 @@
package inr.numass.control.readvac;
import hep.dataforge.control.devices.Sensor;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import java.time.Duration;
import java.time.Instant;
/**
* A console based application to test vacuum readings
* Created by darksnake on 06-Dec-16.
*/
public class ConsoleVac {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
Options options = new Options();
options.addOption("c", "class", true, "A short or long class name for vacuumeter device");
options.addOption("n", "name", true, "A device name");
options.addOption("p", "port", true, "Port name in dataforge-control notation");
options.addOption("d", "delay", true, "A delay between measurements in Duration notation");
if (args.length == 0) {
new HelpFormatter().printHelp("vac-console", options);
return;
}
DefaultParser parser = new DefaultParser();
CommandLine cli = parser.parse(options, args);
String className = cli.getOptionValue("c");
if (!className.contains(".")) {
className = "inr.numass.readvac.devices." + className;
}
String name = cli.getOptionValue("n", "sensor");
String port = cli.getOptionValue("p", "com::/dev/ttyUSB0");
Duration delay = Duration.parse(cli.getOptionValue("d", "PT1M"));
if (className == null) {
throw new RuntimeException("Vacuumeter class not defined");
}
Sensor<Double> sensor = (Sensor<Double>) Class.forName(className)
.getConstructor(String.class).newInstance(port);
try {
sensor.init();
while (true) {
System.out.printf("(%s) %s -> %g%n", Instant.now().toString(), name, sensor.read());
Thread.sleep(delay.toMillis());
}
} finally {
sensor.shutdown();
}
}
}

View File

@ -1,84 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
/**
* @author Alexander Nozik
*/
@ValueDef(name = "channel")
public class MKSBaratronDevice extends PortSensor<Double> {
public MKSBaratronDevice() {
}
public MKSBaratronDevice(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
@Override
protected Measurement<Double> createMeasurement() {
return new BaratronMeasurement();
}
@Override
public String getType() {
return meta().getString("type", "MKS baratron");
}
@Override
protected PortHandler buildHandler(String portName) throws ControlException {
PortHandler handler = super.buildHandler(portName);
handler.setDelimiter("\r");
return handler;
}
private int getChannel() {
return meta().getInt("channel", 2);
}
private class BaratronMeasurement extends SimpleMeasurement<Double> {
@Override
public Device getDevice() {
return MKSBaratronDevice.this;
}
@Override
protected synchronized Double doMeasure() throws Exception {
String answer = sendAndWait("AV" + getChannel() + "\r", timeout());
if (answer == null || answer.isEmpty()) {
// invalidateState("connection");
updateState(CONNECTED_STATE, false);
this.updateMessage("No connection");
return null;
} else {
updateState(CONNECTED_STATE, true);
}
double res = Double.parseDouble(answer);
if (res <= 0) {
this.updateMessage("Non positive");
// invalidateState("power");
return null;
} else {
this.updateMessage("OK");
return res;
}
}
}
}

View File

@ -1,176 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.devices.StateDef;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
import hep.dataforge.values.Value;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static hep.dataforge.values.ValueType.BOOLEAN;
/**
* @author Alexander Nozik
*/
@ValueDef(name = "address", def = "253")
@ValueDef(name = "channel", def = "5")
@ValueDef(name = "powerButton", type = {BOOLEAN}, def = "true")
@StateDef(value = @ValueDef(name = "power", info = "Device powered up"), writable = true)
public class MKSVacDevice extends PortSensor<Double> {
public MKSVacDevice() {
}
public MKSVacDevice(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
private String talk(String requestContent) throws ControlException {
String answer = sendAndWait(String.format("@%s%s;FF", getDeviceAddress(), requestContent), timeout());
Matcher match = Pattern.compile("@" + getDeviceAddress() + "ACK(.*);FF").matcher(answer);
if (match.matches()) {
return match.group(1);
} else {
throw new ControlException(answer);
}
}
@Override
protected PortHandler buildHandler(String portName) throws ControlException {
PortHandler handler = super.buildHandler(portName);
handler.setDelimiter(";FF");
return handler;
}
private String getDeviceAddress() {
//PENDING cache this?
return meta().getString("address", "253");
}
@Override
protected Measurement<Double> createMeasurement() {
return new MKSVacMeasurement();
}
@Override
protected Object computeState(String stateName) throws ControlException {
switch (stateName) {
case "power":
return talk("FP?").equals("ON");
default:
return super.computeState(stateName);
}
}
@Override
protected void requestStateChange(String stateName, Value value) throws ControlException {
switch (stateName) {
case "power":
setPowerOn(value.booleanValue());
break;
default:
super.requestStateChange(stateName, value);
}
}
@Override
public void shutdown() throws ControlException {
if (isConnected()) {
setPowerOn(false);
}
super.shutdown();
}
private boolean isPowerOn() {
return getState("power").booleanValue();
}
private void setPowerOn(boolean powerOn) throws ControlException {
if (powerOn != isPowerOn()) {
if (powerOn) {
// String ans = talkMKS(p1Port, "@253ENC!OFF;FF");
// if (!ans.equals("OFF")) {
// LoggerFactory.getLogger(getClass()).warn("The @253ENC!OFF;FF command is not working");
// }
String ans = talk("FP!ON");
if (ans.equals("ON")) {
updateState("power", true);
} else {
this.notifyError("Failed to set power state", null);
}
} else {
String ans = talk("FP!OFF");
if (ans.equals("OFF")) {
updateState("power", false);
} else {
this.notifyError("Failed to set power state", null);
}
}
}
}
public BooleanProperty powerOnProperty() {
try {
return new JavaBeanBooleanPropertyBuilder().bean(this)
.name("powerOn").getter("isPowerOn").setter("setPowerOn").build();
} catch (NoSuchMethodException ex) {
throw new Error(ex);
}
}
@Override
public String getType() {
return meta().getString("type", "MKS vacuumeter");
}
private int getChannel() {
return meta().getInt("channel", 5);
}
private class MKSVacMeasurement extends SimpleMeasurement<Double> {
@Override
protected synchronized Double doMeasure() throws Exception {
// if (getState("power").booleanValue()) {
String answer = talk("PR" + getChannel() + "?");
if (answer == null || answer.isEmpty()) {
updateState(CONNECTED_STATE, false);
this.updateMessage("No connection");
return null;
}
double res = Double.parseDouble(answer);
if (res <= 0) {
this.updateMessage("No power");
invalidateState("power");
return null;
} else {
this.updateMessage("OK");
return res;
}
}
@Override
public Device getDevice() {
return MKSVacDevice.this;
}
}
}

View File

@ -1,127 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac;
import hep.dataforge.context.Context;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static hep.dataforge.values.ValueType.NUMBER;
/**
* @author Alexander Nozik
*/
@ValueDef(name = "address", type = {NUMBER}, def = "1", info = "A modbus address")
public class MeradatVacDevice extends PortSensor<Double> {
private static final String REQUEST = "0300000002";
public MeradatVacDevice() {
}
public MeradatVacDevice(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
@Override
protected PortHandler buildHandler(String portName) throws ControlException {
PortHandler newHandler = super.buildHandler(portName);
newHandler.setDelimiter("\r\n");
return newHandler;
}
@Override
protected Measurement<Double> createMeasurement() {
return new MeradatMeasurement();
}
@Override
public String getType() {
return meta().getString("type", "Vit vacuumeter");
}
public static String calculateLRC(String inputString) {
/*
* String is Hex String, need to convert in ASCII.
*/
byte[] bytes = new BigInteger(inputString, 16).toByteArray();
int checksum = 0;
for (byte aByte : bytes) {
checksum += aByte;
}
String val = Integer.toHexString(-checksum);
val = val.substring(val.length() - 2).toUpperCase();
if (val.length() < 2) {
val = "0" + val;
}
return val;
}
private class MeradatMeasurement extends SimpleMeasurement<Double> {
private final String query; // ":010300000002FA\r\n";
private final Pattern response;
private final String base;
public MeradatMeasurement() {
base = String.format(":%02d", meta().getInt("address", 1));
String dataStr = base.substring(1) + REQUEST;
query = base + REQUEST + calculateLRC(dataStr) + "\r\n";
response = Pattern.compile(base + "0304(\\w{4})(\\w{4})..\r\n");
}
@Override
protected synchronized Double doMeasure() throws Exception {
String answer = sendAndWait(query, timeout(), phrase -> phrase.startsWith(base));
if (answer.isEmpty()) {
this.updateMessage("No signal");
updateState(CONNECTED_STATE, false);
return null;
} else {
Matcher match = response.matcher(answer);
if (match.matches()) {
double base = (double) (Integer.parseInt(match.group(1), 16)) / 10d;
int exp = Integer.parseInt(match.group(2), 16);
if (exp > 32766) {
exp = exp - 65536;
}
BigDecimal res = BigDecimal.valueOf(base * Math.pow(10, exp));
res = res.setScale(4, RoundingMode.CEILING);
this.updateMessage("OK");
updateState(CONNECTED_STATE, true);
return res.doubleValue();
} else {
this.updateMessage("Wrong answer: " + answer);
updateState(CONNECTED_STATE, false);
return null;
}
}
}
@Override
public Device getDevice() {
return MeradatVacDevice.this;
}
}
}

View File

@ -1,199 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac;
import hep.dataforge.context.Context;
import hep.dataforge.control.RoleDef;
import hep.dataforge.control.collectors.RegularPointCollector;
import hep.dataforge.control.collectors.ValueCollector;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.Device;
import hep.dataforge.control.devices.Sensor;
import hep.dataforge.control.devices.StateDef;
import hep.dataforge.control.measurements.AbstractMeasurement;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.meta.Meta;
import hep.dataforge.storage.api.TableLoader;
import hep.dataforge.storage.commons.LoaderFactory;
import hep.dataforge.tables.TableFormatBuilder;
import hep.dataforge.tables.ValueMap;
import hep.dataforge.utils.DateTimeUtils;
import hep.dataforge.values.Value;
import hep.dataforge.values.ValueType;
import hep.dataforge.values.Values;
import inr.numass.control.StorageHelper;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static hep.dataforge.control.devices.PortSensor.CONNECTED_STATE;
/**
* @author <a href="mailto:altavir@gmail.com">Alexander Nozik</a>
*/
@RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection.class, info = "Storage for acquired points")
@StateDef(
value = @ValueDef(name = "storing", info = "Define if this device is currently writes to storage"),
writable = true
)
public class VacCollectorDevice extends Sensor<Values> {
private Map<String, Sensor<Double>> sensorMap = new LinkedHashMap<>();
private StorageHelper helper = new StorageHelper(VacCollectorDevice.this, this::buildLoader);
public VacCollectorDevice() {
}
public VacCollectorDevice(Context context, Meta meta) {
setContext(context);
setMeta(meta);
}
public void setSensors(Iterable<Sensor<Double>> sensors) {
sensorMap = new LinkedHashMap<>();
for (Sensor<Double> sensor : sensors) {
sensorMap.put(sensor.getName(), sensor);
}
}
public void setSensors(Sensor<Double>... sensors) {
setSensors(Arrays.asList(sensors));
}
@Override
public void init() throws ControlException {
super.init();
for (Sensor<Double> s : sensorMap.values()) {
s.init();
}
}
//TODO add dot path notation for states
@Override
protected Measurement<Values> createMeasurement() {
//TODO use meta
return new VacuumMeasurement();
}
@Override
public String getType() {
return "Numass vacuum";
}
// public void setDelay(int delay) throws MeasurementException {
// this.delay = delay;
// if (isMeasuring()) {
// getMeasurement().stop(false);
// getMeasurement().start();
// }
// }
@Override
public void shutdown() throws ControlException {
super.shutdown();
helper.close();
for (Sensor sensor : getSensors()) {
sensor.shutdown();
}
}
private TableLoader buildLoader(StorageConnection connection) {
TableFormatBuilder format = new TableFormatBuilder().setType("timestamp", ValueType.TIME);
getSensors().forEach((s) -> {
format.setType(s.getName(), ValueType.NUMBER);
});
String suffix = DateTimeUtils.fileSuffix();
return LoaderFactory.buildPointLoder(connection.getStorage(), "vactms_" + suffix, "", "timestamp", format.build());
}
public Collection<Sensor<Double>> getSensors() {
return sensorMap.values();
}
private Duration getAveragingDuration() {
return Duration.parse(meta().getString("averagingDuration", "PT30S"));
}
private class VacuumMeasurement extends AbstractMeasurement<Values> {
private final ValueCollector collector = new RegularPointCollector(getAveragingDuration(), this::result);
private ScheduledExecutorService executor;
private ScheduledFuture<?> currentTask;
@Override
public Device getDevice() {
return VacCollectorDevice.this;
}
@Override
public void start() {
executor = Executors.newSingleThreadScheduledExecutor((Runnable r) -> new Thread(r, "VacuumMeasurement thread"));
int delay = meta().getInt("delay", 5) * 1000;
currentTask = executor.scheduleWithFixedDelay(() -> {
sensorMap.values().forEach((sensor) -> {
try {
Object value;
if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) {
value = sensor.read();
} else {
value = null;
}
collector.put(sensor.getName(), value);
} catch (Exception ex) {
collector.put(sensor.getName(), Value.NULL);
}
});
}, 0, delay, TimeUnit.MILLISECONDS);
}
@Override
protected synchronized void result(Values result, Instant time) {
super.result(result, time);
helper.push(result);
}
private Values terminator() {
ValueMap.Builder p = new ValueMap.Builder();
p.putValue("timestamp", DateTimeUtils.now());
sensorMap.keySet().forEach((n) -> {
p.putValue(n, null);
});
return p.build();
}
@Override
public boolean stop(boolean force) {
boolean isRunning = currentTask != null;
if (isRunning) {
getLogger().debug("Stoping vacuum collector measurement. Writing terminator point");
result(terminator());
currentTask.cancel(force);
executor.shutdown();
currentTask = null;
afterStop();
}
return isRunning;
}
}
}

View File

@ -0,0 +1,90 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.measurements.SimpleMeasurement
import hep.dataforge.control.ports.ComPortHandler
import hep.dataforge.control.ports.PortFactory
import hep.dataforge.control.ports.PortHandler
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
/**
* @author Alexander Nozik
*/
class CM32Device : PortSensor<Double> {
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler {
logger.info("Connecting to port {}", portName)
val newHandler: PortHandler
if (portName.startsWith("com")) {
newHandler = ComPortHandler(portName, 2400, 8, 1, 0)
} else {
newHandler = PortFactory.getPort(portName)
}
newHandler.setDelimiter("T--\r")
return newHandler
}
override fun createMeasurement(): Measurement<Double> {
return CMVacMeasurement()
}
override fun getType(): String {
return meta().getString("type", "Leibold CM32")
}
private inner class CMVacMeasurement : SimpleMeasurement<Double>() {
@Synchronized
@Throws(Exception::class)
override fun doMeasure(): Double? {
val answer = sendAndWait(CM32_QUERY, timeout())
if (answer.isEmpty()) {
this.updateMessage("No signal")
updateState(PortSensor.CONNECTED_STATE, false)
return null
} else if (!answer.contains("PM1:mbar")) {
this.updateMessage("Wrong answer: " + answer)
updateState(PortSensor.CONNECTED_STATE, false)
return null
} else if (answer.substring(14, 17) == "OFF") {
this.updateMessage("Off")
updateState(PortSensor.CONNECTED_STATE, true)
return null
} else {
this.updateMessage("OK")
updateState(PortSensor.CONNECTED_STATE, true)
return java.lang.Double.parseDouble(answer.substring(14, 17) + answer.substring(19, 23))
}
}
override fun getDevice(): Device {
return this@CM32Device
}
}
companion object {
private val CM32_QUERY = "MES R PM 1\r\n"
}
}

View File

@ -0,0 +1,56 @@
package inr.numass.control.readvac
import hep.dataforge.control.devices.Sensor
import org.apache.commons.cli.DefaultParser
import org.apache.commons.cli.HelpFormatter
import org.apache.commons.cli.Options
import java.time.Duration
import java.time.Instant
/**
* A console based application to test vacuum readings
* Created by darksnake on 06-Dec-16.
*/
object ConsoleVac {
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val options = Options()
options.addOption("c", "class", true, "A short or long class name for vacuumeter device")
options.addOption("n", "name", true, "A device name")
options.addOption("p", "port", true, "Port name in dataforge-control notation")
options.addOption("d", "delay", true, "A delay between measurements in Duration notation")
if (args.size == 0) {
HelpFormatter().printHelp("vac-console", options)
return
}
val parser = DefaultParser()
val cli = parser.parse(options, args)
var className: String? = cli.getOptionValue("c")
if (!className!!.contains(".")) {
className = "inr.numass.readvac.devices." + className
}
val name = cli.getOptionValue("n", "sensor")
val port = cli.getOptionValue("p", "com::/dev/ttyUSB0")
val delay = Duration.parse(cli.getOptionValue("d", "PT1M"))
if (className == null) {
throw RuntimeException("Vacuumeter class not defined")
}
val sensor = Class.forName(className)
.getConstructor(String::class.java).newInstance(port) as Sensor<Double>
try {
sensor.init()
while (true) {
System.out.printf("(%s) %s -> %g%n", Instant.now().toString(), name, sensor.read())
Thread.sleep(delay.toMillis())
}
} finally {
sensor.shutdown()
}
}
}

View File

@ -0,0 +1,80 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.measurements.SimpleMeasurement
import hep.dataforge.control.ports.PortHandler
import hep.dataforge.description.ValueDef
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
/**
* @author Alexander Nozik
*/
@ValueDef(name = "channel")
class MKSBaratronDevice : PortSensor<Double> {
private val channel: Int
get() = meta().getInt("channel", 2)!!
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
override fun createMeasurement(): Measurement<Double> {
return BaratronMeasurement()
}
override fun getType(): String {
return meta().getString("type", "MKS baratron")
}
@Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler {
val handler = super.buildHandler(portName)
handler.setDelimiter("\r")
return handler
}
private inner class BaratronMeasurement : SimpleMeasurement<Double>() {
override fun getDevice(): Device {
return this@MKSBaratronDevice
}
@Synchronized
@Throws(Exception::class)
override fun doMeasure(): Double? {
val answer = sendAndWait("AV" + channel + "\r", timeout())
if (answer == null || answer.isEmpty()) {
// invalidateState("connection");
updateState(PortSensor.CONNECTED_STATE, false)
this.updateMessage("No connection")
return null
} else {
updateState(PortSensor.CONNECTED_STATE, true)
}
val res = java.lang.Double.parseDouble(answer)
if (res <= 0) {
this.updateMessage("Non positive")
// invalidateState("power");
return null
} else {
this.updateMessage("OK")
return res
}
}
}
}

View File

@ -0,0 +1,164 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.devices.StateDef
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.measurements.SimpleMeasurement
import hep.dataforge.control.ports.PortHandler
import hep.dataforge.description.ValueDef
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType.BOOLEAN
import javafx.beans.property.BooleanProperty
import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder
import java.util.regex.Pattern
/**
* @author Alexander Nozik
*/
@ValueDef(name = "address", def = "253")
@ValueDef(name = "channel", def = "5")
@ValueDef(name = "powerButton", type = arrayOf(BOOLEAN), def = "true")
@StateDef(value = ValueDef(name = "power", info = "Device powered up"), writable = true)
class MKSVacDevice : PortSensor<Double> {
private//PENDING cache this?
val deviceAddress: String
get() = meta().getString("address", "253")
private// String ans = talkMKS(p1Port, "@253ENC!OFF;FF");
// if (!ans.equals("OFF")) {
// LoggerFactory.getLogger(getClass()).warn("The @253ENC!OFF;FF command is not working");
// }
var isPowerOn: Boolean
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)
}
}
}
}
private val channel: Int
get() = meta().getInt("channel", 5)!!
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@Throws(ControlException::class)
private fun talk(requestContent: String): String? {
val answer = sendAndWait(String.format("@%s%s;FF", deviceAddress, requestContent), timeout())
val match = Pattern.compile("@" + deviceAddress + "ACK(.*);FF").matcher(answer)
return if (match.matches()) {
match.group(1)
} else {
throw ControlException(answer)
}
}
@Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler {
val handler = super.buildHandler(portName)
handler.setDelimiter(";FF")
return handler
}
override fun createMeasurement(): Measurement<Double> {
return MKSVacMeasurement()
}
@Throws(ControlException::class)
override fun computeState(stateName: String): Any {
when (stateName) {
"power" -> return talk("FP?") == "ON"
else -> return super.computeState(stateName)
}
}
@Throws(ControlException::class)
override fun requestStateChange(stateName: String, value: Value) {
when (stateName) {
"power" -> isPowerOn = value.booleanValue()
else -> super.requestStateChange(stateName, value)
}
}
@Throws(ControlException::class)
override fun shutdown() {
if (isConnected) {
isPowerOn = false
}
super.shutdown()
}
fun powerOnProperty(): BooleanProperty {
try {
return JavaBeanBooleanPropertyBuilder().bean(this)
.name("powerOn").getter("isPowerOn").setter("setPowerOn").build()
} catch (ex: NoSuchMethodException) {
throw Error(ex)
}
}
override fun getType(): String {
return meta().getString("type", "MKS vacuumeter")
}
private inner class MKSVacMeasurement : SimpleMeasurement<Double>() {
@Synchronized
@Throws(Exception::class)
override fun doMeasure(): Double? {
// if (getState("power").booleanValue()) {
val answer = talk("PR$channel?")
if (answer == null || answer.isEmpty()) {
updateState(PortSensor.CONNECTED_STATE, false)
this.updateMessage("No connection")
return null
}
val res = java.lang.Double.parseDouble(answer)
if (res <= 0) {
this.updateMessage("No power")
invalidateState("power")
return null
} else {
this.updateMessage("OK")
return res
}
}
override fun getDevice(): Device {
return this@MKSVacDevice
}
}
}

View File

@ -0,0 +1,124 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac
import hep.dataforge.context.Context
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.control.measurements.SimpleMeasurement
import hep.dataforge.control.ports.PortHandler
import hep.dataforge.description.ValueDef
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.values.ValueType.NUMBER
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
import java.util.regex.Pattern
/**
* @author Alexander Nozik
*/
@ValueDef(name = "address", type = arrayOf(NUMBER), def = "1", info = "A modbus address")
class MeradatVacDevice : PortSensor<Double> {
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
@Throws(ControlException::class)
override fun buildHandler(portName: String): PortHandler {
val newHandler = super.buildHandler(portName)
newHandler.setDelimiter("\r\n")
return newHandler
}
override fun createMeasurement(): Measurement<Double> {
return MeradatMeasurement()
}
override fun getType(): String {
return 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
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")
}
@Synchronized
@Throws(Exception::class)
override fun doMeasure(): Double? {
val answer = sendAndWait(query, timeout()) { phrase -> phrase.startsWith(base) }
if (answer.isEmpty()) {
this.updateMessage("No signal")
updateState(PortSensor.CONNECTED_STATE, false)
return null
} else {
val match = response.matcher(answer)
if (match.matches()) {
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
}
var res = BigDecimal.valueOf(base * Math.pow(10.0, exp.toDouble()))
res = res.setScale(4, RoundingMode.CEILING)
this.updateMessage("OK")
updateState(PortSensor.CONNECTED_STATE, true)
return res.toDouble()
} else {
this.updateMessage("Wrong answer: " + answer)
updateState(PortSensor.CONNECTED_STATE, false)
return null
}
}
}
override fun getDevice(): Device {
return this@MeradatVacDevice
}
}
companion object {
private val REQUEST = "0300000002"
fun calculateLRC(inputString: String): String {
/*
* 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()
}
var `val` = Integer.toHexString(-checksum)
`val` = `val`.substring(`val`.length - 2).toUpperCase()
if (`val`.length < 2) {
`val` = "0" + `val`
}
return `val`
}
}
}

View File

@ -0,0 +1,179 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.control.readvac
import hep.dataforge.context.Context
import hep.dataforge.control.RoleDef
import hep.dataforge.control.collectors.RegularPointCollector
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.connections.StorageConnection
import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceHub
import hep.dataforge.control.devices.PortSensor.CONNECTED_STATE
import hep.dataforge.control.devices.Sensor
import hep.dataforge.control.devices.StateDef
import hep.dataforge.control.measurements.AbstractMeasurement
import hep.dataforge.control.measurements.Measurement
import hep.dataforge.description.ValueDef
import hep.dataforge.exceptions.ControlException
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.storage.api.TableLoader
import hep.dataforge.storage.commons.LoaderFactory
import hep.dataforge.tables.TableFormatBuilder
import hep.dataforge.tables.ValueMap
import hep.dataforge.utils.DateTimeUtils
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
import hep.dataforge.values.Values
import inr.numass.control.StorageHelper
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.stream.Stream
/**
* @author [Alexander Nozik](mailto:altavir@gmail.com)
*/
@RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection::class, info = "Storage for acquired points")
@StateDef(value = ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true)
class VacCollectorDevice : Sensor<Values>, DeviceHub {
private var sensorMap: MutableMap<String, Sensor<Double>> = LinkedHashMap()
private val helper = StorageHelper(this, this::buildLoader)
val sensors: Collection<Sensor<Double>>
get() = sensorMap.values
private val averagingDuration: Duration
get() = Duration.parse(meta().getString("averagingDuration", "PT30S"))
constructor() {}
constructor(context: Context, meta: Meta) {
setContext(context)
setMeta(meta)
}
override fun optDevice(name: Name): Optional<Device> {
return Optional.ofNullable(sensorMap.get(name.toString()))
}
override fun deviceNames(): Stream<Name> {
return sensorMap.keys.stream().map { Name.ofSingle(it) }
}
fun setSensors(sensors: Iterable<Sensor<Double>>) {
sensorMap = LinkedHashMap()
for (sensor in sensors) {
sensorMap.put(sensor.name, sensor)
}
}
fun setSensors(vararg sensors: Sensor<Double>) {
setSensors(Arrays.asList(*sensors))
}
@Throws(ControlException::class)
override fun init() {
super.init()
for (s in sensorMap.values) {
s.init()
}
}
override fun createMeasurement(): Measurement<Values> {
//TODO use meta
return VacuumMeasurement()
}
override fun getType(): String {
return "Numass vacuum"
}
@Throws(ControlException::class)
override fun shutdown() {
super.shutdown()
helper.close()
for (sensor in sensors) {
sensor.shutdown()
}
}
private fun buildLoader(connection: StorageConnection): TableLoader {
val format = TableFormatBuilder().setType("timestamp", ValueType.TIME)
sensors.forEach { s -> format.setType(s.name, ValueType.NUMBER) }
val suffix = DateTimeUtils.fileSuffix()
return LoaderFactory.buildPointLoder(connection.storage, "vactms_" + suffix, "", "timestamp", format.build())
}
private inner class VacuumMeasurement : AbstractMeasurement<Values>() {
private val collector = RegularPointCollector(averagingDuration) { this.result(it) }
private var executor: ScheduledExecutorService? = null
private var currentTask: ScheduledFuture<*>? = null
override fun getDevice(): Device {
return this@VacCollectorDevice
}
override fun start() {
executor = Executors.newSingleThreadScheduledExecutor { r: Runnable -> Thread(r, "VacuumMeasurement thread") }
val delay = meta().getInt("delay", 5)!! * 1000
currentTask = executor!!.scheduleWithFixedDelay({
sensorMap.values.forEach { sensor ->
try {
val value: Any?
value = if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) {
sensor.read()
} else {
null
}
collector.put(sensor.name, value)
} catch (ex: Exception) {
collector.put(sensor.name, Value.NULL)
}
}
}, 0, delay.toLong(), TimeUnit.MILLISECONDS)
}
@Synchronized override fun result(result: Values, time: Instant) {
super.result(result, time)
helper.push(result)
}
private fun terminator(): Values {
val p = ValueMap.Builder()
p.putValue("timestamp", DateTimeUtils.now())
sensorMap.keys.forEach { n -> p.putValue(n, null) }
return p.build()
}
override fun stop(force: Boolean): Boolean {
val isRunning = currentTask != null
if (isRunning) {
logger.debug("Stopping vacuum collector measurement. Writing terminator point")
result(terminator())
currentTask!!.cancel(force)
executor!!.shutdown()
currentTask = null
afterStop()
}
return isRunning
}
}
}