diff --git a/numass-control/control-room/src/main/kotlin/inr/numass/control/BoardController.kt b/numass-control/control-room/src/main/kotlin/inr/numass/control/BoardController.kt index 79c72607..66a38d93 100644 --- a/numass-control/control-room/src/main/kotlin/inr/numass/control/BoardController.kt +++ b/numass-control/control-room/src/main/kotlin/inr/numass/control/BoardController.kt @@ -2,6 +2,7 @@ package inr.numass.control import hep.dataforge.context.Context import hep.dataforge.context.Global +import hep.dataforge.control.DeviceManager import hep.dataforge.control.connections.Roles import hep.dataforge.control.connections.StorageConnection import hep.dataforge.meta.Meta @@ -16,13 +17,12 @@ import javafx.beans.property.SimpleObjectProperty import javafx.collections.FXCollections import javafx.collections.ObservableList import tornadofx.* -import java.io.File /** * Created by darksnake on 12-May-17. */ class BoardController() : Controller(), AutoCloseable { - val devices: ObservableList> = FXCollections.observableArrayList>(); + val devices: ObservableList> = FXCollections.observableArrayList>(); val contextProperty = SimpleObjectProperty(Global.instance()) var context: Context by contextProperty @@ -39,15 +39,8 @@ class BoardController() : Controller(), AutoCloseable { fun load(app: Application) { runAsync { getConfig(app).ifPresent { - val libDir = File(app.parameters.named.getOrDefault("libPath", "../lib")); - val contextBuilder = Context - .builder("NUMASS-SERVER"); - if (libDir.exists()) { - Global.logger().info("Found library directory {}. Loading it into server context", libDir) - contextBuilder.classPath(libDir.listFiles { _, name -> name.endsWith(".jar") }.map { it.toURI().toURL() }) - } - context = contextBuilder.build(); - load(context, it); + val context = Context.build("NUMASS", Global.instance(), it) + load(context, it) } } @@ -87,21 +80,11 @@ class BoardController() : Controller(), AutoCloseable { } - private fun buildDeviceView(context: Context, deviceMeta: Meta): DeviceViewConnection<*> { + private fun buildDeviceView(context: Context, deviceMeta: Meta): DeviceDisplay<*> { context.logger.info("Building device with meta: {}", deviceMeta) - val factory = context.serviceStream(DeviceViewFactory::class.java) - .filter { it.type == deviceMeta.getString("type") } - .findFirst(); - - if (factory.isPresent) { - val device = factory.get().build(context, deviceMeta); - val view = factory.get().buildView(device); - device.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) - device.init(); - return view; - } else { - throw RuntimeException("Device factory not found"); - } + val device = context.loadFeature("devices", DeviceManager::class.java).buildDevice(deviceMeta) + device.init(); + return device.getDisplay(); } private fun buildStorage(context: Context, meta: Meta): Storage { diff --git a/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8Channel.java b/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8Channel.java deleted file mode 100644 index 77a2773d..00000000 --- a/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8Channel.java +++ /dev/null @@ -1,101 +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.cryotemp; - -import hep.dataforge.meta.Meta; -import hep.dataforge.meta.MetaBuilder; -import hep.dataforge.meta.Metoid; -import hep.dataforge.names.Named; -import hep.dataforge.values.Value; - -import java.util.List; -import java.util.function.Function; - -/** - * Created by darksnake on 28-Sep-16. - */ -public class PKT8Channel implements Named, Metoid { - - private final Meta meta; - private final Function transformation; - - public PKT8Channel(String name) { - this.meta = new MetaBuilder("channel") - .putValue("name", name); - transformation = (d) -> d; - } - - public PKT8Channel(Meta meta) { - this.meta = meta; - - String transformationType = meta.getString("transformationType", "default"); - if (meta.hasValue("coefs")) { - switch (transformationType) { - case "default": - case "hyperbolic": - List coefs = meta.getValue("coefs").listValue(); - double r0 = meta.getDouble("r0", 1000); - transformation = (r) -> { - if (coefs == null) { - return -1d; - } else { - double res = 0; - for (int i = 0; i < coefs.size(); i++) { - res += coefs.get(i).doubleValue() * Math.pow(r0 / r, i); - } - return res; - } - }; - break; - default: - throw new RuntimeException("Unknown transformation type"); - } - } else { - //identity transformation - transformation = (d) -> d; - - } - - } - - @Override - public String getName() { - return meta().getString("name"); - } - - @Override - public Meta meta() { - return meta; - } - - public String description() { - return meta().getString("description", ""); - } - - /** - * @param r negative if temperature transformation not defined - * @return - */ - public double getTemperature(double r) { - return transformation.apply(r); - } - - public PKT8Result evaluate(double r) { - return new PKT8Result(getName(), r, getTemperature(r)); - } - -} \ No newline at end of file diff --git a/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8Device.java b/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8Device.java deleted file mode 100644 index 5ba24280..00000000 --- a/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8Device.java +++ /dev/null @@ -1,421 +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.cryotemp; - -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.PortSensor; -import hep.dataforge.control.devices.StateDef; -import hep.dataforge.control.measurements.AbstractMeasurement; -import hep.dataforge.control.measurements.Measurement; -import hep.dataforge.control.ports.PortHandler; -import hep.dataforge.description.ValueDef; -import hep.dataforge.exceptions.ControlException; -import hep.dataforge.exceptions.MeasurementException; -import hep.dataforge.exceptions.StorageException; -import hep.dataforge.meta.Meta; -import hep.dataforge.storage.api.Storage; -import hep.dataforge.storage.api.TableLoader; -import hep.dataforge.storage.commons.LoaderFactory; -import hep.dataforge.tables.TableFormat; -import hep.dataforge.tables.TableFormatBuilder; -import hep.dataforge.utils.DateTimeUtils; -import hep.dataforge.values.Values; -import inr.numass.control.StorageHelper; - -import java.time.Duration; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - - -/** - * A device controller for Dubna PKT 8 cryogenic thermometry device - * - * @author Alexander Nozik - */ -@RoleDef(name = Roles.STORAGE_ROLE) -@RoleDef(name = Roles.VIEW_ROLE) -@ValueDef(name = "port", def = "virtual", info = "The name of the port for this PKT8") -@StateDef(@ValueDef(name = "storing")) -public class PKT8Device extends PortSensor { - public static final String PKT8_DEVICE_TYPE = "numass:pkt8"; - private static final Duration TIMEOUT = Duration.ofMillis(400); - - public static final String PGA = "pga"; - public static final String SPS = "sps"; - public static final String ABUF = "abuf"; - private static final String[] CHANNEL_DESIGNATIONS = {"a", "b", "c", "d", "e", "f", "g", "h"}; - /** - * The key is the letter (a,b,c,d...) as in measurements - */ - private final Map channels = new LinkedHashMap<>(); - private RegularPointCollector collector; - private StorageHelper storageHelper; - - /** - * Cached values - */ - private TableFormat format; - - public PKT8Device() { - } - - - public PKT8Device(Context context, Meta meta) { - setContext(context); - setMeta(meta); - } - - private TableLoader buildLoader(StorageConnection connection) { - Storage storage = connection.getStorage(); - String suffix = DateTimeUtils.fileSuffix(); - - try { - return LoaderFactory.buildPointLoder(storage, - "cryotemp_" + suffix, "", "timestamp", getTableFormat()); - } catch (StorageException e) { - throw new RuntimeException("Failed to builder loader from storage", e); - } - } - - @Override - public void init() throws ControlException { - - //read channel configuration - if (meta().hasMeta("channel")) { - for (Meta node : meta().getMetaList("channel")) { - String designation = node.getString("designation", "default"); - this.channels.put(designation, new PKT8Channel(node)); - } - } else { - //set default channel configuration - for (String designation : CHANNEL_DESIGNATIONS) { - channels.put(designation, new PKT8Channel(designation)); - } - getLogger().warn("No channels defined in configuration"); - } - - super.init(); - - //update parameters from meta - if (meta().hasValue("pga")) { - getLogger().info("Setting dynamic range to " + meta().getInt("pga")); - String response = sendAndWait("g" + meta().getInt("pga"), TIMEOUT).trim(); - if (response.contains("=")) { - updateState(PGA, Integer.parseInt(response.substring(4))); - } else { - getLogger().error("Setting pga failsed with message: " + response); - } - } - - setSPS(meta().getInt("sps", 0)); - setBUF(meta().getInt("abuf", 100)); - - // setting up the collector - storageHelper = new StorageHelper(this, this::buildLoader); - Duration duration = Duration.parse(meta().getString("averagingDuration", "PT30S")); - collector = new RegularPointCollector( - duration, - channels.values().stream().map(PKT8Channel::getName).collect(Collectors.toList()), - (Values dp) -> { - getLogger().debug("Point measurement complete. Pushing..."); - storageHelper.push(dp); - }); - } - - - private TableFormat getTableFormat() { - if (format == null) { - // Building data format - TableFormatBuilder tableFormatBuilder = new TableFormatBuilder() - .addTime("timestamp"); - - for (PKT8Channel channel : channels.values()) { - tableFormatBuilder.addNumber(channel.getName()); - } - format = tableFormatBuilder.build(); - } - return format; - } - - @Override - public void shutdown() throws ControlException { - storageHelper.close(); - if (collector != null) { - collector.stop(); - collector = null; - } - super.shutdown(); - } - - @Override - protected PortHandler buildHandler(String portName) throws ControlException { - PortHandler handler; - //setup connection - if ("virtual".equals(portName)) { - getLogger().info("Starting {} using virtual debug port", getName()); - handler = new PKT8VirtualPort("PKT8", meta().getMetaOrEmpty("debug")); - } else { - handler = super.buildHandler(portName); - } - handler.setDelimiter("\n"); - - return handler; - } - - public Collection getChanels() { - return this.channels.values(); - } - - private void setBUF(int buf) throws ControlException { - getLogger().info("Setting avaraging buffer size to " + buf); - String response; - try { - response = sendAndWait("b" + buf, Duration.ofMillis(400)).trim(); - } catch (Exception ex) { - response = ex.getMessage(); - } - - if (response.contains("=")) { - updateState(ABUF, Integer.parseInt(response.substring(14))); -// getLogger().info("successfully set buffer size to {}", this.abuf); - } else { - getLogger().error("Setting averaging buffer failed with message: " + response); - } - } - - public void changeParameters(int sps, int abuf) throws ControlException { - stopMeasurement(false); - //setting sps - setSPS(sps); - //setting buffer - setBUF(abuf); - } - - /** - * '0' : 2,5 SPS '1' : 5 SPS '2' : 10 SPS '3' : 25 SPS '4' : 50 SPS '5' : - * 100 SPS '6' : 500 SPS '7' : 1 kSPS '8' : 3,75 kSPS - * - * @param sps - * @return - */ - private String spsToStr(int sps) { - switch (sps) { - case 0: - return "2.5 SPS"; - case 1: - return "5 SPS"; - case 2: - return "10 SPS"; - case 3: - return "25 SPS"; - case 4: - return "50 SPS"; - case 5: - return "100 SPS"; - case 6: - return "500 SPS"; - case 7: - return "1 kSPS"; - case 8: - return "3.75 kSPS"; - default: - return "unknown value"; - } - } - - /** - * '0' : ± 5 В '1' : ± 2,5 В '2' : ± 1,25 В '3' : ± 0,625 В '4' : ± 312.5 мВ - * '5' : ± 156,25 мВ '6' : ± 78,125 мВ - * - * @param pga - * @return - */ - private String pgaToStr(int pga) { - switch (pga) { - case 0: - return "± 5 V"; - case 1: - return "± 2,5 V"; - case 2: - return "± 1,25 V"; - case 3: - return "± 0,625 V"; - case 4: - return "± 312.5 mV"; - case 5: - return "± 156.25 mV"; - case 6: - return "± 78.125 mV"; - default: - return "unknown value"; - } - } - - public String getSPS() { - return getState(SPS).stringValue(); - } - - private void setSPS(int sps) throws ControlException { - getLogger().info("Setting sampling rate to " + spsToStr(sps)); - String response; - try { - response = sendAndWait("v" + sps, TIMEOUT).trim(); - } catch (Exception ex) { - response = ex.getMessage(); - } - if (response.contains("=")) { - updateState(SPS, Integer.parseInt(response.substring(4))); -// getLogger().info("successfully sampling rate to {}", spsToStr(this.sps)); - } else { - getLogger().error("Setting sps failsed with message: " + response); - } - } - - public String getPGA() { - return getState(PGA).stringValue(); - } - - public String getABUF() { - return getState(ABUF).stringValue(); - } - -// public void connectPointListener(PointListenerConnection listener) { -// this.connect(listener, Roles.POINT_LISTENER_ROLE); -// } - - @Override - protected Measurement createMeasurement() throws MeasurementException { - if (this.getMeasurement() != null) { - return this.getMeasurement(); - } else { - try { - if (getHandler().isLocked()) { - getLogger().error("Breaking hold on handler because it is locked"); - getHandler().breakHold(); - } - return new PKT8Measurement(getHandler()); - } catch (ControlException e) { - throw new MeasurementException(e); - } - } - } - - @Override - public Measurement startMeasurement() throws MeasurementException { - //clearing PKT queue - try { - send("p"); - sendAndWait("p", TIMEOUT); - } catch (ControlException e) { - getLogger().error("Failed to clear PKT8 port"); - // throw new MeasurementException(e); - } - return super.startMeasurement(); - } - - - public class PKT8Measurement extends AbstractMeasurement implements PortHandler.PortController { - - final PortHandler handler; - - - public PKT8Measurement(PortHandler handler) { - this.handler = handler; - } - - @Override - public Device getDevice() { - return PKT8Device.this; - } - - @Override - public void start() { - if (isStarted()) { - getLogger().warn("Trying to start measurement which is already started"); - } - - try { - getLogger().info("Starting measurement"); - handler.holdBy(this); - send("s"); - afterStart(); - } catch (ControlException ex) { - portError("Failed to start measurement", ex); - } - - } - - @Override - public boolean stop(boolean force) throws MeasurementException { - if (isFinished()) { - getLogger().warn("Trying to stop measurement which is already stopped"); - } - - try { - getLogger().info("Stopping measurement"); - String response = sendAndWait("p", TIMEOUT).trim(); - // Должно быть именно с большой буквы!!! - return "Stopped".equals(response) || "stopped".equals(response); - } catch (Exception ex) { - error(ex); - return false; - } finally { - if (collector != null) { - collector.clear(); - } - handler.unholdBy(this); - } - } - - - @Override - public void acceptPortPhrase(String message) { - String trimmed = message.trim(); - - if (isStarted()) { - if (trimmed.equals("Stopped") || trimmed.equals("stopped")) { - afterPause(); -// getLogger().info("Measurement stopped"); - } else { - String designation = trimmed.substring(0, 1); - double rawValue = Double.parseDouble(trimmed.substring(1)) / 100; - - if (channels.containsKey(designation)) { - PKT8Channel channel = channels.get(designation); - result(channel.evaluate(rawValue)); - if (collector != null) { - collector.put(channel.getName(), channel.getTemperature(rawValue)); - } - } else { - result(new PKT8Result(designation, rawValue, -1)); - } - } - } - } - - @Override - public void portError(String errorMessage, Throwable error) { - super.error(error); - } - } -} \ No newline at end of file diff --git a/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8VirtualPort.java b/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8VirtualPort.java deleted file mode 100644 index d9117022..00000000 --- a/numass-control/cryotemp/src/main/java/inr/numass/control/cryotemp/PKT8VirtualPort.java +++ /dev/null @@ -1,69 +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.cryotemp; - -import hep.dataforge.control.ports.VirtualPort; -import hep.dataforge.meta.Meta; -import hep.dataforge.meta.MetaUtils; -import hep.dataforge.meta.Metoid; -import hep.dataforge.values.Value; - -import java.time.Duration; -import java.util.Random; - - -/** - * @author Alexander Nozik - */ -public class PKT8VirtualPort extends VirtualPort implements Metoid { - - private final Random generator = new Random(); - - public PKT8VirtualPort(String portName, Meta meta) { - super.configure(meta).configureValue("id", portName); - } - - @Override - protected synchronized void evaluateRequest(String request) { - switch (request) { - case "s": - String[] letters = {"a", "b", "c", "d", "e", "f", "g", "h"}; - for (String letter : letters) { - Meta channelMeta = MetaUtils.findNodeByValue(meta(), "channel", "letter", Value.of(letter)).orElse(Meta.empty()); - - double average; - double sigma; - if (channelMeta != null) { - average = channelMeta.getDouble("av", 1200); - sigma = channelMeta.getDouble("sigma", 50); - } else { - average = 1200d; - sigma = 50d; - } - - this.planRegularResponse( - () -> { - double res = average + generator.nextGaussian() * sigma; - //TODO convert double value to formatted string - return String.format("%s000%d", letter, (int) (res * 100)); - }, - Duration.ZERO, Duration.ofMillis(500), letter, "measurement" - ); - } - return; - case "p": - cancelByTag("measurement"); - this.receivePhrase("Stopped"); - } - } - - @Override - public void close() throws Exception { - cancelByTag("measurement"); - super.close(); - } - -} diff --git a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8App.kt b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8App.kt index b77beae5..bb35be23 100644 --- a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8App.kt +++ b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8App.kt @@ -15,9 +15,7 @@ */ package inr.numass.control.cryotemp -import hep.dataforge.control.connections.Roles import hep.dataforge.meta.Meta -import inr.numass.control.DeviceViewConnection import inr.numass.control.NumassControlApplication import javafx.stage.Stage @@ -25,11 +23,6 @@ import javafx.stage.Stage * @author darksnake */ class PKT8App : NumassControlApplication() { - override fun buildView(device: PKT8Device): DeviceViewConnection { - return PKT8ViewConnection().apply { - device.connect(this, Roles.VIEW_ROLE) - } - } override val deviceFactory = PKT8DeviceFactory() diff --git a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Channel.kt b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Channel.kt new file mode 100644 index 00000000..93032488 --- /dev/null +++ b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Channel.kt @@ -0,0 +1,82 @@ +/* + * 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.cryotemp + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.Metoid +import hep.dataforge.names.Named + + +internal fun createChannel(name: String): PKT8Channel { + return PKT8Channel(MetaBuilder("channel").putValue("name", name)) { d -> d } +} + +internal fun createChannel(meta: Meta): PKT8Channel { + val transformationType = meta.getString("transformationType", "default") + if (meta.hasValue("coefs")) { + when (transformationType) { + "default", "hyperbolic" -> { + val coefs = meta.getValue("coefs").listValue() + val r0 = meta.getDouble("r0", 1000.0)!! + return PKT8Channel(meta) { r -> + if (coefs == null) { + -1.0 + } else { + coefs.indices.sumByDouble { coefs[it].doubleValue() * Math.pow(r0 / r, it.toDouble()) } + } + } + } + else -> throw RuntimeException("Unknown transformation type") + } + } else { + //identity transformation + return PKT8Channel(meta) { d -> d } + } +} + + +/** + * Created by darksnake on 28-Sep-16. + */ +class PKT8Channel(private val _meta: Meta, val func: (Double) -> Double) : Named, Metoid { + + override fun getName(): String { + return meta().getString("name") + } + + override fun meta(): Meta { + return _meta + } + + fun description(): String { + return meta().getString("description", "") + } + + /** + * @param r negative if temperature transformation not defined + * @return + */ + fun getTemperature(r: Double): Double { + return func(r) + } + + fun evaluate(r: Double): PKT8Result { + return PKT8Result(name, r, getTemperature(r)) + } + +} \ No newline at end of file diff --git a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Device.kt b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Device.kt new file mode 100644 index 00000000..8354df6e --- /dev/null +++ b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Device.kt @@ -0,0 +1,391 @@ +/* + * 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.cryotemp + +import hep.dataforge.context.Context +import hep.dataforge.control.RoleDef +import hep.dataforge.control.RoleDefs +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.PortSensor +import hep.dataforge.control.devices.StateDef +import hep.dataforge.control.measurements.AbstractMeasurement +import hep.dataforge.control.measurements.Measurement +import hep.dataforge.control.ports.PortHandler +import hep.dataforge.description.ValueDef +import hep.dataforge.exceptions.ControlException +import hep.dataforge.exceptions.MeasurementException +import hep.dataforge.exceptions.StorageException +import hep.dataforge.meta.Meta +import hep.dataforge.storage.api.TableLoader +import hep.dataforge.storage.commons.LoaderFactory +import hep.dataforge.tables.TableFormat +import hep.dataforge.tables.TableFormatBuilder +import hep.dataforge.utils.DateTimeUtils +import hep.dataforge.values.Values +import inr.numass.control.DeviceView +import inr.numass.control.StorageHelper +import java.time.Duration +import java.util.* +import kotlin.streams.toList + + +/** + * A device controller for Dubna PKT 8 cryogenic thermometry device + * + * @author Alexander Nozik + */ +@RoleDefs( + RoleDef(name = Roles.STORAGE_ROLE), + RoleDef(name = Roles.VIEW_ROLE) +) +@ValueDef(name = "port", def = "virtual", info = "The name of the port for this PKT8") +@StateDef(ValueDef(name = "storing")) +@DeviceView(PKT8Display::class) +class PKT8Device(context: Context, meta: Meta) : PortSensor(context, meta) { + /** + * The key is the letter (a,b,c,d...) as in measurements + */ + private val channels = LinkedHashMap() + private var collector: RegularPointCollector? = null + private var storageHelper: StorageHelper? = null + + /** + * Cached values + */ + private var format: TableFormat? = null + + + private// Building data format + val tableFormat: TableFormat by lazy { + val tableFormatBuilder = TableFormatBuilder() + .addTime("timestamp") + + for (channel in channels.values) { + tableFormatBuilder.addNumber(channel.name) + } + tableFormatBuilder.build() + } + + val chanels: Collection + get() = this.channels.values + + val sps: String + get() = getState(SPS).stringValue() + + val pga: String + get() = getState(PGA).stringValue() + + val abuf: String + get() = getState(ABUF).stringValue() + + private fun buildLoader(connection: StorageConnection): TableLoader { + val storage = connection.storage + val suffix = DateTimeUtils.fileSuffix() + + try { + return LoaderFactory.buildPointLoder(storage, + "cryotemp_" + suffix, "", "timestamp", tableFormat) + } catch (e: StorageException) { + throw RuntimeException("Failed to builder loader from storage", e) + } + + } + + @Throws(ControlException::class) + override fun init() { + + //read channel configuration + if (meta().hasMeta("channel")) { + for (node in meta().getMetaList("channel")) { + val designation = node.getString("designation", "default") + this.channels.put(designation, createChannel(node)) + } + } else { + //set default channel configuration + for (designation in CHANNEL_DESIGNATIONS) { + channels.put(designation, createChannel(designation)) + } + logger.warn("No channels defined in configuration") + } + + super.init() + + //update parameters from meta + if (meta().hasValue("pga")) { + logger.info("Setting dynamic range to " + meta().getInt("pga")!!) + val response = sendAndWait("g" + meta().getInt("pga")!!, TIMEOUT).trim { it <= ' ' } + if (response.contains("=")) { + updateState(PGA, Integer.parseInt(response.substring(4))) + } else { + logger.error("Setting pga failsed with message: " + response) + } + } + + setSPS(meta().getInt("sps", 0)!!) + setBUF(meta().getInt("abuf", 100)!!) + + // setting up the collector + storageHelper = StorageHelper(this) { connection: StorageConnection -> this.buildLoader(connection) } + val duration = Duration.parse(meta().getString("averagingDuration", "PT30S")) + collector = RegularPointCollector( + duration, + channels.values.stream().map { it.name }.toList() + ) { dp: Values -> + logger.debug("Point measurement complete. Pushing...") + storageHelper!!.push(dp) + } + } + + @Throws(ControlException::class) + override fun shutdown() { + storageHelper!!.close() + if (collector != null) { + collector!!.stop() + collector = null + } + super.shutdown() + } + + @Throws(ControlException::class) + override fun buildHandler(portName: String): PortHandler { + val handler: PortHandler + //setup connection + if ("virtual" == portName) { + logger.info("Starting {} using virtual debug port", name) + handler = PKT8VirtualPort("PKT8", meta().getMetaOrEmpty("debug")) + } else { + handler = super.buildHandler(portName) + } + handler.setDelimiter("\n") + + return handler + } + + private fun setBUF(buf: Int) { + logger.info("Setting avaraging buffer size to " + buf) + var response: String + try { + response = sendAndWait("b" + buf, Duration.ofMillis(400)).trim { it <= ' ' } + } catch (ex: Exception) { + response = ex.message ?: "" + } + + if (response.contains("=")) { + updateState(ABUF, Integer.parseInt(response.substring(14))) + // getLogger().info("successfully set buffer size to {}", this.abuf); + } else { + logger.error("Setting averaging buffer failed with message: " + response) + } + } + + @Throws(ControlException::class) + fun changeParameters(sps: Int, abuf: Int) { + stopMeasurement(false) + //setting sps + setSPS(sps) + //setting buffer + setBUF(abuf) + } + + /** + * '0' : 2,5 SPS '1' : 5 SPS '2' : 10 SPS '3' : 25 SPS '4' : 50 SPS '5' : + * 100 SPS '6' : 500 SPS '7' : 1 kSPS '8' : 3,75 kSPS + * + * @param sps + * @return + */ + private fun spsToStr(sps: Int): String { + when (sps) { + 0 -> return "2.5 SPS" + 1 -> return "5 SPS" + 2 -> return "10 SPS" + 3 -> return "25 SPS" + 4 -> return "50 SPS" + 5 -> return "100 SPS" + 6 -> return "500 SPS" + 7 -> return "1 kSPS" + 8 -> return "3.75 kSPS" + else -> return "unknown value" + } + } + + /** + * '0' : ± 5 В '1' : ± 2,5 В '2' : ± 1,25 В '3' : ± 0,625 В '4' : ± 312.5 мВ + * '5' : ± 156,25 мВ '6' : ± 78,125 мВ + * + * @param pga + * @return + */ + private fun pgaToStr(pga: Int): String { + when (pga) { + 0 -> return "± 5 V" + 1 -> return "± 2,5 V" + 2 -> return "± 1,25 V" + 3 -> return "± 0,625 V" + 4 -> return "± 312.5 mV" + 5 -> return "± 156.25 mV" + 6 -> return "± 78.125 mV" + else -> return "unknown value" + } + } + + private fun setSPS(sps: Int) { + logger.info("Setting sampling rate to " + spsToStr(sps)) + var response: String + try { + response = sendAndWait("v" + sps, TIMEOUT).trim { it <= ' ' } + } catch (ex: Exception) { + response = ex.message ?: "" + } + + if (response.contains("=")) { + updateState(SPS, Integer.parseInt(response.substring(4))) + // getLogger().info("successfully sampling rate to {}", spsToStr(this.sps)); + } else { + logger.error("Setting sps failsed with message: " + response) + } + } + + // public void connectPointListener(PointListenerConnection listener) { + // this.connect(listener, Roles.POINT_LISTENER_ROLE); + // } + + @Throws(MeasurementException::class) + override fun createMeasurement(): Measurement { + return if (this.measurement != null) { + this.measurement + } else { + try { + if (handler.isLocked) { + logger.error("Breaking hold on handler because it is locked") + handler.breakHold() + } + PKT8Measurement(handler) + } catch (e: ControlException) { + throw MeasurementException(e) + } + + } + } + + @Throws(MeasurementException::class) + override fun startMeasurement(): Measurement { + //clearing PKT queue + try { + send("p") + sendAndWait("p", TIMEOUT) + } catch (e: ControlException) { + logger.error("Failed to clear PKT8 port") + // throw new MeasurementException(e); + } + + return super.startMeasurement() + } + + + inner class PKT8Measurement(internal val handler: PortHandler) : AbstractMeasurement(), PortHandler.PortController { + + override fun getDevice(): Device { + return this@PKT8Device + } + + override fun start() { + if (isStarted) { + logger.warn("Trying to start measurement which is already started") + } + + try { + logger.info("Starting measurement") + handler.holdBy(this) + send("s") + afterStart() + } catch (ex: ControlException) { + portError("Failed to start measurement", ex) + } + + } + + @Throws(MeasurementException::class) + override fun stop(force: Boolean): Boolean { + if (isFinished) { + logger.warn("Trying to stop measurement which is already stopped") + } + + try { + logger.info("Stopping measurement") + val response = sendAndWait("p", TIMEOUT).trim { it <= ' ' } + // Должно быть именно с большой буквы!!! + return "Stopped" == response || "stopped" == response + } catch (ex: Exception) { + error(ex) + return false + } finally { + if (collector != null) { + collector!!.clear() + } + handler.unholdBy(this) + } + } + + + override fun acceptPortPhrase(message: String) { + val trimmed = message.trim { it <= ' ' } + + if (isStarted) { + if (trimmed == "Stopped" || trimmed == "stopped") { + afterPause() + // getLogger().info("Measurement stopped"); + } else { + val designation = trimmed.substring(0, 1) + val rawValue = java.lang.Double.parseDouble(trimmed.substring(1)) / 100 + + val channel = channels[designation] + + if (channel != null) { + result(channel.evaluate(rawValue)) + if (collector != null) { + collector!!.put(channel.getName(), channel.getTemperature(rawValue)) + } + } else { + result(PKT8Result(designation, rawValue, -1.0)) + } + } + } + } + + override fun portError(errorMessage: String, error: Throwable) { + super.error(error) + } + } + + companion object { + val PKT8_DEVICE_TYPE = "numass.pkt8" + private val TIMEOUT = Duration.ofMillis(400) + + val PGA = "pga" + val SPS = "sps" + val ABUF = "abuf" + private val CHANNEL_DESIGNATIONS = arrayOf("a", "b", "c", "d", "e", "f", "g", "h") + } + + init { + setContext(context) + setMeta(meta) + } +} \ No newline at end of file diff --git a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8DeviceFactory.kt b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8DeviceFactory.kt index a78930a0..1e83e140 100644 --- a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8DeviceFactory.kt +++ b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8DeviceFactory.kt @@ -1,15 +1,13 @@ package inr.numass.control.cryotemp import hep.dataforge.context.Context -import hep.dataforge.control.devices.Device +import hep.dataforge.control.devices.DeviceFactory import hep.dataforge.meta.Meta -import inr.numass.control.DeviceViewConnection -import inr.numass.control.DeviceViewFactory /** * Created by darksnake on 09-May-17. */ -class PKT8DeviceFactory : DeviceViewFactory { +class PKT8DeviceFactory : DeviceFactory { override fun getType(): String { return PKT8Device.PKT8_DEVICE_TYPE } @@ -17,8 +15,4 @@ class PKT8DeviceFactory : DeviceViewFactory { override fun build(context: Context, meta: Meta): PKT8Device { return PKT8Device(context, meta) } - - override fun buildView(device: Device): DeviceViewConnection { - return PKT8ViewConnection() - } } diff --git a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8ViewConnection.kt b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Display.kt similarity index 95% rename from numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8ViewConnection.kt rename to numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Display.kt index 0ef16015..a8990ab4 100644 --- a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8ViewConnection.kt +++ b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8Display.kt @@ -12,7 +12,7 @@ import hep.dataforge.plots.PlotUtils import hep.dataforge.plots.data.TimePlot import hep.dataforge.plots.data.TimePlottableGroup import hep.dataforge.plots.jfreechart.JFreeChartFrame -import inr.numass.control.DeviceViewConnection +import inr.numass.control.DeviceDisplay import javafx.application.Platform import javafx.beans.binding.ListBinding import javafx.beans.property.SimpleObjectProperty @@ -31,7 +31,7 @@ import java.time.Instant /** * Created by darksnake on 30-May-17. */ -class PKT8ViewConnection : DeviceViewConnection(), MeasurementListener { +class PKT8Display : DeviceDisplay(), MeasurementListener { override fun buildView(device: PKT8Device): View { return CryoView() diff --git a/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8VirtualPort.kt b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8VirtualPort.kt new file mode 100644 index 00000000..69bb4742 --- /dev/null +++ b/numass-control/cryotemp/src/main/kotlin/inr/numass/control/cryotemp/PKT8VirtualPort.kt @@ -0,0 +1,70 @@ +/* + * 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.cryotemp + +import hep.dataforge.control.ports.VirtualPort +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaUtils +import hep.dataforge.meta.Metoid +import hep.dataforge.values.Value +import java.time.Duration +import java.util.* +import java.util.function.Supplier + + +/** + * @author Alexander Nozik + */ +class PKT8VirtualPort(portName: String, meta: Meta) : VirtualPort(), Metoid { + + private val generator = Random() + + init { + super.configure(meta).configureValue("id", portName) + } + + @Synchronized override fun evaluateRequest(request: String) { + when (request) { + "s" -> { + val letters = arrayOf("a", "b", "c", "d", "e", "f", "g", "h") + for (letter in letters) { + val channelMeta = MetaUtils.findNodeByValue(meta(), "channel", "letter", Value.of(letter)).orElse(Meta.empty()) + + val average: Double + val sigma: Double + if (channelMeta != null) { + average = channelMeta.getDouble("av", 1200.0)!! + sigma = channelMeta.getDouble("sigma", 50.0)!! + } else { + average = 1200.0 + sigma = 50.0 + } + + this.planRegularResponse( + Supplier { + val res = average + generator.nextGaussian() * sigma + //TODO convert double value to formatted string + String.format("%s000%d", letter, (res * 100).toInt()) + }, + Duration.ZERO, Duration.ofMillis(500), letter, "measurement" + ) + } + return + } + "p" -> { + cancelByTag("measurement") + this.receivePhrase("Stopped") + } + } + } + + @Throws(Exception::class) + override fun close() { + cancelByTag("measurement") + super.close() + } + +} diff --git a/numass-control/cryotemp/src/main/resources/META-INF/services/inr.numass.control.DeviceViewFactory b/numass-control/cryotemp/src/main/resources/META-INF/services/hep.dataforge.control.devices.DeviceFactory similarity index 100% rename from numass-control/cryotemp/src/main/resources/META-INF/services/inr.numass.control.DeviceViewFactory rename to numass-control/cryotemp/src/main/resources/META-INF/services/hep.dataforge.control.devices.DeviceFactory diff --git a/numass-control/cryotemp/src/main/resources/fxml/PKT8Indicator.fxml b/numass-control/cryotemp/src/main/resources/fxml/PKT8Indicator.fxml index a6f87fe7..618215d4 100644 --- a/numass-control/cryotemp/src/main/resources/fxml/PKT8Indicator.fxml +++ b/numass-control/cryotemp/src/main/resources/fxml/PKT8Indicator.fxml @@ -4,7 +4,7 @@ + xmlns="http://javafx.com/javafx/8.0.111" fx:controller="inr.numass.control.cryotemp.PKT8Display">
diff --git a/numass-control/msp/src/main/java/inr/numass/control/msp/MspDevice.java b/numass-control/msp/src/main/java/inr/numass/control/msp/MspDevice.java deleted file mode 100644 index 6e91ddd5..00000000 --- a/numass-control/msp/src/main/java/inr/numass/control/msp/MspDevice.java +++ /dev/null @@ -1,547 +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.msp; - -import hep.dataforge.context.Context; -import hep.dataforge.control.NamedValueListener; -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.PortSensor; -import hep.dataforge.control.devices.Sensor; -import hep.dataforge.control.devices.StateDef; -import hep.dataforge.control.measurements.AbstractMeasurement; -import hep.dataforge.control.ports.PortHandler; -import hep.dataforge.control.ports.SyncPortController; -import hep.dataforge.control.ports.TcpPortHandler; -import hep.dataforge.description.ValueDef; -import hep.dataforge.events.EventBuilder; -import hep.dataforge.exceptions.ControlException; -import hep.dataforge.exceptions.MeasurementException; -import hep.dataforge.exceptions.PortException; -import hep.dataforge.exceptions.StorageException; -import hep.dataforge.meta.Meta; -import hep.dataforge.storage.api.Storage; -import hep.dataforge.storage.api.TableLoader; -import hep.dataforge.storage.commons.LoaderFactory; -import hep.dataforge.tables.TableFormat; -import hep.dataforge.tables.TableFormatBuilder; -import hep.dataforge.utils.DateTimeUtils; -import hep.dataforge.values.Value; -import hep.dataforge.values.Values; -import inr.numass.control.StorageHelper; - -import java.time.Duration; -import java.time.Instant; -import java.util.*; -import java.util.function.Consumer; - -/** - * @author Alexander Nozik - */ -@RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection.class) -@RoleDef(name = Roles.VIEW_ROLE) -@StateDef(value = @ValueDef(name = PortSensor.CONNECTED_STATE, info = "Connection with the device itself"), writable = true) -@StateDef(value = @ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true) -@StateDef(value = @ValueDef(name = "filament", info = "The number of filament in use"), writable = true) -@StateDef(value = @ValueDef(name = "filamentOn", info = "Mass-spectrometer filament on"), writable = true) -@StateDef(@ValueDef(name = "filamentStatus", info = "Filament status")) -public class MspDevice extends Sensor implements PortHandler.PortController { - public static final String MSP_DEVICE_TYPE = "msp"; - - private static final Duration TIMEOUT = Duration.ofMillis(200); - - private TcpPortHandler handler; - private SyncPortController controller = new SyncPortController(this); - private Consumer measurementDelegate; - - public MspDevice() { - } - - public MspDevice(Context context, Meta meta) { - setContext(context); - setMeta(meta); - } - - // public MspDevice(String name, Context context, Meta config) { -// super(name, context, config); -// } - @Override - public void init() throws ControlException { - super.init(); - String ip = meta().getString("connection.ip", "127.0.0.1"); - int port = meta().getInt("connection.port", 10014); - getLogger().info("Connection to MKS mass-spectrometer on {}:{}...", ip, port); - handler = new TcpPortHandler(ip, port); - handler.setDelimiter("\r\r"); - } - - @Override - public void shutdown() throws ControlException { - super.stopMeasurement(true); - if (isConnected()) { - setFilamentOn(false); - setConnected(false); - } - getHandler().close(); - super.shutdown(); - } - -// @Override -// protected Meta getMeasurementMeta() { -// return meta().getMeta("peakJump"); -// } - - @Override - protected PeakJumpMeasurement createMeasurement() throws MeasurementException { - Meta measurementMeta = meta().getMeta("peakJump"); - String s = measurementMeta.getString("type", "peakJump"); - if (s.equals("peakJump")) { - PeakJumpMeasurement measurement = new PeakJumpMeasurement(measurementMeta); - this.measurementDelegate = measurement; - return measurement; - } else { - throw new MeasurementException("Unknown measurement type"); - } - } - - @Override - protected Object computeState(String stateName) throws ControlException { - switch (stateName) { - case "connected": - return false; - case "filament": - return 1; - case "filamentOn": - return false;//Always return false on first request - case "filamentStatus": - return "UNKNOWN"; - case "storing": - return false; - default: - return super.computeState(stateName); - } - } - - @Override - public String getType() { - return "MKS E-Vision"; - } - - @Override - protected void requestStateChange(String stateName, Value value) throws ControlException { - switch (stateName) { - case PortSensor.CONNECTED_STATE: - setConnected(value.booleanValue()); - break; - case "filament": - selectFilament(value.intValue()); - break; - case "filamentOn": - setFilamentOn(value.booleanValue()); - break; - default: - super.requestStateChange(stateName, value); - } - } - - /** - * Startup MSP: get available sensors, select sensor and control. - * - * @param connected - * @return - * @throws hep.dataforge.exceptions.ControlException - */ - private boolean setConnected(boolean connected) throws ControlException { - String sensorName; - if (isConnected() != connected) { - if (connected) { - getHandler().holdBy(controller); - MspResponse response = sendAndWait("Sensors"); - if (response.isOK()) { - sensorName = response.get(2, 1); - } else { - portError(response.errorDescription(), null); - return false; - } - //PENDING определеить в конфиге номер прибора - - response = sendAndWait("Select", sensorName); - if (response.isOK()) { - updateState("selected", true); -// selected = true; - } else { - portError(response.errorDescription(), null); - return false; - } - - response = sendAndWait("Control", "inr.numass.msp", "1.0"); - if (response.isOK()) { -// controlled = true; -// invalidateState("controlled"); - updateState("controlled", true); - } else { - portError(response.errorDescription(), null); - return false; - } -// connected = true; - updateState(PortSensor.CONNECTED_STATE, true); - return true; - } else { - getHandler().unholdBy(controller); - return !sendAndWait("Release").isOK(); - } - - } else { - return false; - } - } - - /** - * Send request to the msp - * - * @param command - * @param parameters - * @throws PortException - */ - private void send(String command, Object... parameters) throws PortException { - String request = buildCommand(command, parameters); - dispatchEvent( - EventBuilder - .make("msp") - .setMetaValue("request", request) - .build() - ); - getHandler().send(request); - } - - /** - * A helper method to builder msp command string - * - * @param command - * @param parameters - * @return - */ - private String buildCommand(String command, Object... parameters) { - StringBuilder builder = new StringBuilder(command); - for (Object par : parameters) { - builder.append(String.format(" \"%s\"", par.toString())); - } - builder.append("\n"); - return builder.toString(); - } - - /** - * Send specific command and wait for its results (the result must begin - * with command name) - * - * @param commandName - * @param parameters - * @return - * @throws PortException - */ - private MspResponse sendAndWait(String commandName, Object... parameters) throws PortException { - - String request = buildCommand(commandName, parameters); - dispatchEvent( - EventBuilder - .make("msp") - .setMetaValue("request", request) - .build() - ); - - - getHandler().send(controller, request); - String response = controller.waitFor(TIMEOUT, (String str) -> str.trim().startsWith(commandName)); - return new MspResponse(response); - } - - public boolean isConnected() { - return getState(PortSensor.CONNECTED_STATE).booleanValue(); - } - - public boolean isSelected() { - return getState("selected").booleanValue(); - } - - public boolean isControlled() { - return getState("controlled").booleanValue(); - } - - public boolean isFilamentOn() { - return getState("filamentOn").booleanValue(); - } - - public void selectFilament(int filament) throws PortException { - MspResponse response = sendAndWait("FilamentSelect", filament); - if (response.isOK()) { - updateState("filament", response.get(1, 1)); - } else { - getLogger().error("Failed to set filament with error: {}", response.errorDescription()); - } - } - - /** - * Turn filament on or off - * - * @param filamentOn - * @return - * @throws hep.dataforge.exceptions.PortException - */ - public boolean setFilamentOn(boolean filamentOn) throws PortException { - if (filamentOn) { - return sendAndWait("FilamentControl", "On").isOK(); - } else { - return sendAndWait("FilamentControl", "Off").isOK(); - } - } - - /** - * Evaluate general async messages - * - * @param response - */ - private void evaluateResponse(MspResponse response) { - - } - - @Override - public void acceptPortPhrase(String message) { - dispatchEvent( - EventBuilder - .make("msp") - .setMetaValue("response", message.trim()).build() - ); - MspResponse response = new MspResponse(message); - - switch (response.getCommandName()) { - // all possible async messages - case "FilamentStatus": - String status = response.get(0, 2); - updateState("filamentOn", status.equals("ON")); - updateState("filamentStatus", status); - break; - } - if (measurementDelegate != null) { - measurementDelegate.accept(response); - } - } - - @Override - public void portError(String errorMessage, Throwable error) { - notifyError(errorMessage, error); - } - - private TcpPortHandler getHandler() { - if (handler == null) { - throw new RuntimeException("Device not initialized"); - } - return handler; - } - - private Duration getAveragingDuration() { - return Duration.parse(meta().getString("averagingDuration", "PT30S")); - } - - /** - * The MKS response as two-dimensional array of strings - */ - static class MspResponse { - - private final List> data = new ArrayList<>(); - - MspResponse(String response) { - String rx = "[^\"\\s]+|\"(\\\\.|[^\\\\\"])*\""; - Scanner scanner = new Scanner(response.trim()); - - while (scanner.hasNextLine()) { - List line = new ArrayList<>(); - String next = scanner.findWithinHorizon(rx, 0); - while (next != null) { - line.add(next); - next = scanner.findInLine(rx); - } - data.add(line); - } - } - - String getCommandName() { - return this.get(0, 0); - } - - boolean isOK() { - return "OK".equals(this.get(0, 1)); - } - - int errorCode() { - if (isOK()) { - return -1; - } else { - return Integer.parseInt(get(1, 1)); - } - } - - String errorDescription() { - if (isOK()) { - return null; - } else { - return get(2, 1); - } - } - - String get(int lineNo, int columnNo) { - return data.get(lineNo).get(columnNo); - } - } - - public class PeakJumpMeasurement extends AbstractMeasurement implements Consumer { - - private RegularPointCollector collector = new RegularPointCollector(getAveragingDuration(), this::result); - private StorageHelper helper = new StorageHelper(MspDevice.this, this::makeLoader); - private final Meta meta; - private Map peakMap; - private double zero = 0; - - private PeakJumpMeasurement(Meta meta) { - this.meta = meta; - } - - private TableLoader makeLoader(StorageConnection connection) { - - try { - Storage storage = connection.getStorage(); - - if (peakMap == null) { - throw new IllegalStateException("Peak map is not initialized"); - } - - TableFormatBuilder builder = new TableFormatBuilder().addTime("timestamp"); - this.peakMap.values().forEach(builder::addNumber); - - TableFormat format = builder.build(); - - String suffix = DateTimeUtils.fileSuffix(); - return LoaderFactory - .buildPointLoder(storage, "msp_" + suffix, "", "timestamp", format); - } catch (StorageException ex) { - getLogger().error("Failed to create Loader", ex); - return null; - } - } - - @Override - public Device getDevice() { - return MspDevice.this; - } - - @Override - public void start() { - try { - String measurementName = "peakJump"; - String filterMode = meta.getString("filterMode", "PeakAverage"); - int accuracy = meta.getInt("accuracy", 5); - //PENDING вставить остальные параметры? - sendAndWait("MeasurementRemoveAll"); - if (sendAndWait("AddPeakJump", measurementName, filterMode, accuracy, 0, 0, 0).isOK()) { - peakMap = new LinkedHashMap<>(); - for (Meta peak : meta.getMetaList("peak")) { - peakMap.put(peak.getInt("mass"), peak.getString("name", peak.getString("mass"))); - if (!sendAndWait("MeasurementAddMass", peak.getString("mass")).isOK()) { - throw new ControlException("Can't add mass to measurement measurement for msp"); - } - } - } else { - throw new ControlException("Can't create measurement for msp"); - } - - if (!isFilamentOn()) { - this.error("Can't start measurement. Filament is not turned on.", null); - } - if (!sendAndWait("ScanAdd", measurementName).isOK()) { - this.error("Failed to add scan", null); - } - - if (!sendAndWait("ScanStart", 2).isOK()) { - this.error("Failed to start scan", null); - } - } catch (ControlException ex) { - error(ex); - } - afterStart(); - } - - @Override - public boolean stop(boolean force) throws MeasurementException { - try { - collector.stop(); - boolean stop = sendAndWait("ScanStop").isOK(); - afterStop(); - helper.close(); - return stop; - } catch (PortException ex) { - throw new MeasurementException(ex); - } - } - - @Override - protected synchronized void result(Values result, Instant time) { - super.result(result, time); - helper.push(result); - } - - void error(String errorMessage, Throwable error) { - if (error == null) { - error(new MeasurementException(errorMessage)); - } else { - error(error); - } - } - - @Override - public void accept(MspResponse response) { - - //Evaluating device state change - evaluateResponse(response); - //Evaluating measurement information - switch (response.getCommandName()) { - case "MassReading": - double mass = Double.parseDouble(response.get(0, 1)); - double value = Double.parseDouble(response.get(0, 2)) / 100d; - String massName = Integer.toString((int) Math.floor(mass + 0.5)); - collector.put(massName, value); - forEachConnection(Roles.VIEW_ROLE, NamedValueListener.class, listener -> listener.pushValue(massName, value)); - break; - case "ZeroReading": - zero = Double.parseDouble(response.get(0, 2)) / 100d; - break; - case "StartingScan": - int numScans = Integer.parseInt(response.get(0, 3)); - - if (numScans == 0) { - try { - send("ScanResume", 10); - //FIXME обработать ошибку связи - } catch (PortException ex) { - error(null, ex); - } - } - break; - } - } - } -} diff --git a/numass-control/msp/src/main/java/inr/numass/control/msp/MspTest.java b/numass-control/msp/src/main/java/inr/numass/control/msp/MspTest.java deleted file mode 100644 index 7e1f3fae..00000000 --- a/numass-control/msp/src/main/java/inr/numass/control/msp/MspTest.java +++ /dev/null @@ -1,81 +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.msp; - -import hep.dataforge.exceptions.PortException; - -import java.io.IOException; - -/** - * @author darksnake - */ -public class MspTest { - - /** - * @param args the command line arguments - * @throws hep.dataforge.exceptions.PortException - */ - public static void main(String[] args) throws PortException, IOException, InterruptedException { -// Locale.setDefault(Locale.US);// чтобы отделение десятичных знаков было точкой -// ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); -// rootLogger.setLevel(Level.INFO); -// -// MspListener listener = new MspListener() { -// -// @Override -// public void acceptMeasurement(Map measurement) { -// final StringBuilder mesString = new StringBuilder("["); -// measurement.forEach((Double key, Double value) -> mesString.append(String.format("%g:%g,", key, value))); -// mesString.deleteCharAt(mesString.length() - 1); -// mesString.append("]"); -// System.out.println("MEASUREMENT: " + mesString); -// } -// -// @Override -// public void acceptMessage(String message) { -// System.out.println("RECIEVE: " + message); -// } -// -// @Override -// public void acceptRequest(String message) { -// System.out.println("SEND: " + message); -// } -// -// @Override -// public void error(String errorMessage, Throwable error) { -// System.out.println("ERROR: " + errorMessage); -// if (error != null) { -// error.printStackTrace(); -// } -// } -// }; -// -// MspDevice controller = new MspDevice("127.0.0.1", 10014, listener); -// try { -// controller.init(); -// String name = controller.createMeasurement("default", 2, 4, 18, 28); -// controller.setFileamentOn(true); -// -// controller.startMeasurement(name); -// BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); -// reader.readLine(); -// } finally { -// controller.stop(); -// System.exit(0); -// } - } - -} diff --git a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspApp.kt b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspApp.kt index 0fdc71ef..982d6ef0 100644 --- a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspApp.kt +++ b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspApp.kt @@ -16,7 +16,6 @@ package inr.numass.control.msp import hep.dataforge.meta.Meta -import inr.numass.control.DeviceViewConnection import inr.numass.control.NumassControlApplication import javafx.stage.Stage @@ -25,10 +24,6 @@ import javafx.stage.Stage */ class MspApp : NumassControlApplication() { - override fun buildView(device: MspDevice): DeviceViewConnection { - return MspViewConnection() - } - override val deviceFactory = MspDeviceFactory() diff --git a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt new file mode 100644 index 00000000..8f7c856b --- /dev/null +++ b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDevice.kt @@ -0,0 +1,504 @@ +/* + * 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.msp + +import hep.dataforge.context.Context +import hep.dataforge.control.NamedValueListener +import hep.dataforge.control.RoleDef +import hep.dataforge.control.RoleDefs +import hep.dataforge.control.collectors.RegularPointCollector +import hep.dataforge.control.connections.Roles +import hep.dataforge.control.connections.StorageConnection +import hep.dataforge.control.devices.* +import hep.dataforge.control.measurements.AbstractMeasurement +import hep.dataforge.control.ports.PortHandler +import hep.dataforge.control.ports.SyncPortController +import hep.dataforge.control.ports.TcpPortHandler +import hep.dataforge.description.ValueDef +import hep.dataforge.events.EventBuilder +import hep.dataforge.exceptions.ControlException +import hep.dataforge.exceptions.MeasurementException +import hep.dataforge.exceptions.PortException +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.utils.DateTimeUtils +import hep.dataforge.values.Value +import hep.dataforge.values.Values +import inr.numass.control.DeviceView +import inr.numass.control.StorageHelper +import java.time.Duration +import java.time.Instant +import java.util.* +import java.util.function.Consumer + +/** + * @author Alexander Nozik + */ +@RoleDefs( + RoleDef(name = Roles.STORAGE_ROLE, objectType = StorageConnection::class), + RoleDef(name = Roles.VIEW_ROLE) +) +@StateDefs( + StateDef(value = ValueDef(name = PortSensor.CONNECTED_STATE, info = "Connection with the device itself"), writable = true), + StateDef(value = ValueDef(name = "storing", info = "Define if this device is currently writes to storage"), writable = true), + StateDef(value = ValueDef(name = "filament", info = "The number of filament in use"), writable = true), + StateDef(value = ValueDef(name = "filamentOn", info = "Mass-spectrometer filament on"), writable = true), + StateDef(ValueDef(name = "filamentStatus", info = "Filament status")) +) +@DeviceView(MspDisplay::class) +class MspDevice(context: Context, meta: Meta) : Sensor(context, meta), PortHandler.PortController { + + private var handler: TcpPortHandler? = null + private val controller = SyncPortController(this) + private var measurementDelegate: Consumer? = null + + val isConnected: Boolean + get() = getState(PortSensor.CONNECTED_STATE).booleanValue() + + val isSelected: Boolean + get() = getState("selected").booleanValue() + + val isControlled: Boolean + get() = getState("controlled").booleanValue() + + val isFilamentOn: Boolean + get() = getState("filamentOn").booleanValue() + + private val averagingDuration: Duration + get() = Duration.parse(meta().getString("averagingDuration", "PT30S")) + + // public MspDevice(String name, Context context, Meta config) { + // super(name, context, config); + // } + @Throws(ControlException::class) + override fun init() { + super.init() + val ip = meta().getString("connection.ip", "127.0.0.1") + val port = meta().getInt("connection.port", 10014)!! + logger.info("Connection to MKS mass-spectrometer on {}:{}...", ip, port) + handler = TcpPortHandler(ip, port) + handler!!.setDelimiter("\r\r") + } + + @Throws(ControlException::class) + override fun shutdown() { + super.stopMeasurement(true) + if (isConnected) { + setFilamentOn(false) + setConnected(false) + } + getHandler().close() + super.shutdown() + } + + // @Override + // protected Meta getMeasurementMeta() { + // return meta().getMeta("peakJump"); + // } + + @Throws(MeasurementException::class) + override fun createMeasurement(): PeakJumpMeasurement { + val measurementMeta = meta().getMeta("peakJump") + val s = measurementMeta.getString("type", "peakJump") + if (s == "peakJump") { + val measurement = PeakJumpMeasurement(measurementMeta) + this.measurementDelegate = measurement + return measurement + } else { + throw MeasurementException("Unknown measurement type") + } + } + + @Throws(ControlException::class) + override fun computeState(stateName: String): Any { + when (stateName) { + "connected" -> return false + "filament" -> return 1 + "filamentOn" -> return false//Always return false on first request + "filamentStatus" -> return "UNKNOWN" + "storing" -> return false + else -> return super.computeState(stateName) + } + } + + override fun getType(): String { + return "MKS E-Vision" + } + + @Throws(ControlException::class) + override fun requestStateChange(stateName: String, value: Value) { + when (stateName) { + PortSensor.CONNECTED_STATE -> setConnected(value.booleanValue()) + "filament" -> selectFilament(value.intValue()) + "filamentOn" -> setFilamentOn(value.booleanValue()) + else -> super.requestStateChange(stateName, value) + } + } + + /** + * Startup MSP: get available sensors, select sensor and control. + * + * @param connected + * @return + * @throws hep.dataforge.exceptions.ControlException + */ + @Throws(ControlException::class) + private fun setConnected(connected: Boolean): Boolean { + val sensorName: String + if (isConnected != connected) { + if (connected) { + getHandler().holdBy(controller) + var response = sendAndWait("Sensors") + if (response.isOK) { + sensorName = response[2, 1] + } else { + portError(response.errorDescription(), null) + return false + } + //PENDING определеить в конфиге номер прибора + + response = sendAndWait("Select", sensorName) + if (response.isOK) { + updateState("selected", true) + // selected = true; + } else { + portError(response.errorDescription(), null) + return false + } + + response = sendAndWait("Control", "inr.numass.msp", "1.0") + if (response.isOK) { + // controlled = true; + // invalidateState("controlled"); + updateState("controlled", true) + } else { + portError(response.errorDescription(), null) + return false + } + // connected = true; + updateState(PortSensor.CONNECTED_STATE, true) + return true + } else { + getHandler().unholdBy(controller) + return !sendAndWait("Release").isOK + } + + } else { + return false + } + } + + /** + * Send request to the msp + * + * @param command + * @param parameters + * @throws PortException + */ + @Throws(PortException::class) + private fun send(command: String, vararg parameters: Any) { + val request = buildCommand(command, *parameters) + dispatchEvent( + EventBuilder + .make("msp") + .setMetaValue("request", request) + .build() + ) + getHandler().send(request) + } + + /** + * A helper method to builder msp command string + * + * @param command + * @param parameters + * @return + */ + private fun buildCommand(command: String, vararg parameters: Any): String { + val builder = StringBuilder(command) + for (par in parameters) { + builder.append(String.format(" \"%s\"", par.toString())) + } + builder.append("\n") + return builder.toString() + } + + /** + * Send specific command and wait for its results (the result must begin + * with command name) + * + * @param commandName + * @param parameters + * @return + * @throws PortException + */ + @Throws(PortException::class) + private fun sendAndWait(commandName: String, vararg parameters: Any): MspResponse { + + val request = buildCommand(commandName, *parameters) + dispatchEvent( + EventBuilder + .make("msp") + .setMetaValue("request", request) + .build() + ) + + + getHandler().send(controller, request) + val response = controller.waitFor(TIMEOUT) { str: String -> str.trim { it <= ' ' }.startsWith(commandName) } + return MspResponse(response) + } + + @Throws(PortException::class) + fun selectFilament(filament: Int) { + val response = sendAndWait("FilamentSelect", filament) + if (response.isOK) { + updateState("filament", response[1, 1]) + } else { + logger.error("Failed to set filament with error: {}", response.errorDescription()) + } + } + + /** + * Turn filament on or off + * + * @param filamentOn + * @return + * @throws hep.dataforge.exceptions.PortException + */ + @Throws(PortException::class) + fun setFilamentOn(filamentOn: Boolean): Boolean { + return if (filamentOn) { + sendAndWait("FilamentControl", "On").isOK + } else { + sendAndWait("FilamentControl", "Off").isOK + } + } + + /** + * Evaluate general async messages + * + * @param response + */ + private fun evaluateResponse(response: MspResponse) { + + } + + override fun acceptPortPhrase(message: String) { + dispatchEvent( + EventBuilder + .make("msp") + .setMetaValue("response", message.trim { it <= ' ' }).build() + ) + val response = MspResponse(message) + + when (response.commandName) { + // all possible async messages + "FilamentStatus" -> { + val status = response[0, 2] + updateState("filamentOn", status == "ON") + updateState("filamentStatus", status) + } + } + if (measurementDelegate != null) { + measurementDelegate!!.accept(response) + } + } + + override fun portError(errorMessage: String?, error: Throwable?) { + notifyError(errorMessage, error) + } + + private fun getHandler(): TcpPortHandler { + return handler ?: throw RuntimeException("Device not initialized") + } + + /** + * The MKS response as two-dimensional array of strings + */ + class MspResponse(response: String) { + + private val data = ArrayList>() + + val commandName: String + get() = this[0, 0] + + val isOK: Boolean + get() = "OK" == this[0, 1] + + init { + val rx = "[^\"\\s]+|\"(\\\\.|[^\\\\\"])*\"" + val scanner = Scanner(response.trim { it <= ' ' }) + + while (scanner.hasNextLine()) { + val line = ArrayList() + var next: String? = scanner.findWithinHorizon(rx, 0) + while (next != null) { + line.add(next) + next = scanner.findInLine(rx) + } + data.add(line) + } + } + + fun errorCode(): Int { + return if (isOK) { + -1 + } else { + Integer.parseInt(get(1, 1)) + } + } + + fun errorDescription(): String? { + return if (isOK) { + null + } else { + get(2, 1) + } + } + + operator fun get(lineNo: Int, columnNo: Int): String { + return data[lineNo][columnNo] + } + } + + inner class PeakJumpMeasurement(private val meta: Meta) : AbstractMeasurement(), Consumer { + + private val collector = RegularPointCollector(averagingDuration, Consumer { this.result(it) }) + private val helper = StorageHelper(this@MspDevice) { connection: StorageConnection -> this.makeLoader(connection) } + private var peakMap: MutableMap = LinkedHashMap() + private var zero = 0.0 + + private fun makeLoader(connection: StorageConnection): TableLoader { + val storage = connection.storage + + val builder = TableFormatBuilder().addTime("timestamp") + this.peakMap.values.forEach { builder.addNumber(it) } + + val format = builder.build() + + val suffix = DateTimeUtils.fileSuffix() + return LoaderFactory + .buildPointLoder(storage, "msp_" + suffix, "", "timestamp", format) + + } + + override fun getDevice(): Device { + return this@MspDevice + } + + override fun start() { + try { + val measurementName = "peakJump" + val filterMode = meta.getString("filterMode", "PeakAverage") + val accuracy = meta.getInt("accuracy", 5)!! + //PENDING вставить остальные параметры? + sendAndWait("MeasurementRemoveAll") + if (sendAndWait("AddPeakJump", measurementName, filterMode, accuracy, 0, 0, 0).isOK) { + peakMap.clear() + for (peak in meta.getMetaList("peak")) { + peakMap.put(peak.getInt("mass"), peak.getString("name", peak.getString("mass"))) + if (!sendAndWait("MeasurementAddMass", peak.getString("mass")).isOK) { + throw ControlException("Can't add mass to measurement measurement for msp") + } + } + } else { + throw ControlException("Can't create measurement for msp") + } + + if (!isFilamentOn) { + this.error("Can't start measurement. Filament is not turned on.", null) + } + if (!sendAndWait("ScanAdd", measurementName).isOK) { + this.error("Failed to add scan", null) + } + + if (!sendAndWait("ScanStart", 2).isOK) { + this.error("Failed to start scan", null) + } + } catch (ex: ControlException) { + error(ex) + } + + afterStart() + } + + @Throws(MeasurementException::class) + override fun stop(force: Boolean): Boolean { + try { + collector.stop() + val stop = sendAndWait("ScanStop").isOK + afterStop() + helper.close() + return stop + } catch (ex: PortException) { + throw MeasurementException(ex) + } + + } + + @Synchronized override fun result(result: Values, time: Instant) { + super.result(result, time) + helper.push(result) + } + + internal fun error(errorMessage: String?, error: Throwable?) { + if (error == null) { + error(MeasurementException(errorMessage)) + } else { + error(error) + } + } + + override fun accept(response: MspResponse) { + + //Evaluating device state change + evaluateResponse(response) + //Evaluating measurement information + when (response.commandName) { + "MassReading" -> { + val mass = java.lang.Double.parseDouble(response[0, 1]) + val value = java.lang.Double.parseDouble(response[0, 2]) / 100.0 + val massName = Integer.toString(Math.floor(mass + 0.5).toInt()) + collector.put(massName, value) + forEachConnection(Roles.VIEW_ROLE, NamedValueListener::class.java) { listener -> listener.pushValue(massName, value) } + } + "ZeroReading" -> zero = java.lang.Double.parseDouble(response[0, 2]) / 100.0 + "StartingScan" -> { + val numScans = Integer.parseInt(response[0, 3]) + + if (numScans == 0) { + try { + send("ScanResume", 10) + //FIXME обработать ошибку связи + } catch (ex: PortException) { + error(null, ex) + } + + } + } + } + } + } + + companion object { + val MSP_DEVICE_TYPE = "numass.msp" + + private val TIMEOUT = Duration.ofMillis(200) + } +} diff --git a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDeviceFactory.kt b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDeviceFactory.kt index ba910312..ff87de90 100644 --- a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDeviceFactory.kt +++ b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDeviceFactory.kt @@ -1,25 +1,18 @@ package inr.numass.control.msp import hep.dataforge.context.Context -import hep.dataforge.control.devices.Device +import hep.dataforge.control.devices.DeviceFactory import hep.dataforge.meta.Meta -import inr.numass.control.DeviceViewConnection -import inr.numass.control.DeviceViewFactory /** * Created by darksnake on 09-May-17. */ -class MspDeviceFactory : DeviceViewFactory { +class MspDeviceFactory: DeviceFactory { override fun getType(): String { return MspDevice.MSP_DEVICE_TYPE } override fun build(context: Context, config: Meta): MspDevice { - val device = MspDevice(context, config) - return device - } - - override fun buildView(device: Device): DeviceViewConnection { - return MspViewConnection() + return MspDevice(context, config) } } diff --git a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspViewConnection.kt b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt similarity index 91% rename from numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspViewConnection.kt rename to numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt index a76dd8b5..2c25ab4d 100644 --- a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspViewConnection.kt +++ b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt @@ -30,7 +30,7 @@ import hep.dataforge.plots.data.TimePlot import hep.dataforge.plots.data.TimePlottableGroup import hep.dataforge.plots.jfreechart.JFreeChartFrame import hep.dataforge.values.Value -import inr.numass.control.DeviceViewConnection +import inr.numass.control.DeviceDisplay import inr.numass.control.deviceStateIndicator import inr.numass.control.deviceStateToggle import inr.numass.control.switch @@ -51,7 +51,7 @@ import tornadofx.* * @author darksnake */ -class MspViewConnection() : DeviceViewConnection(), DeviceListener, NamedValueListener { +class MspDisplay() : DeviceDisplay(), DeviceListener, NamedValueListener { private val table = FXCollections.observableHashMap() @@ -71,9 +71,9 @@ class MspViewConnection() : DeviceViewConnection(), DeviceListener, N inner class MspView : View("Numass mass-spectrometer measurement") { - val plotFrameMeta: Meta = device.meta().getMeta("plotConfig", device.meta) + private val plotFrameMeta: Meta = device.meta().getMeta("plotConfig", device.meta) - val plotFrame: PlotFrame by lazy { + private val plotFrame: PlotFrame by lazy { val basePlotConfig = MetaBuilder("plotFrame") .setNode(MetaBuilder("yAxis") .setValue("type", "log") @@ -113,7 +113,7 @@ class MspViewConnection() : DeviceViewConnection(), DeviceListener, N // addLogHandler(device.logger) // }) - val filamentProperty = SimpleObjectProperty(this, "filament", 1).apply { + private val filamentProperty = SimpleObjectProperty(this, "filament", 1).apply { addListener { _, oldValue, newValue -> if (newValue != oldValue) { runAsync { @@ -128,7 +128,7 @@ class MspViewConnection() : DeviceViewConnection(), DeviceListener, N minWidth = 600.0 top { toolbar { - deviceStateToggle(this@MspViewConnection, PortSensor.CONNECTED_STATE, "Connect") + deviceStateToggle(this@MspDisplay, PortSensor.CONNECTED_STATE, "Connect") combobox(filamentProperty, listOf(1, 2)) { cellFormat { text = "Filament $it" @@ -142,7 +142,7 @@ class MspViewConnection() : DeviceViewConnection(), DeviceListener, N .bind(getStateBinding(PortSensor.CONNECTED_STATE).booleanBinding { !it!!.booleanValue() }) bindBooleanToState("filamentOn", selectedProperty()) } - deviceStateIndicator(this@MspViewConnection, "filamentStatus", false) { + deviceStateIndicator(this@MspDisplay, "filamentStatus", false) { when (it.stringValue()) { "ON" -> Paint.valueOf("red") "OFF" -> Paint.valueOf("blue") diff --git a/numass-control/msp/src/main/resources/META-INF/services/inr.numass.control.DeviceViewFactory b/numass-control/msp/src/main/resources/META-INF/services/hep.dataforge.control.devices.DeviceFactory similarity index 100% rename from numass-control/msp/src/main/resources/META-INF/services/inr.numass.control.DeviceViewFactory rename to numass-control/msp/src/main/resources/META-INF/services/hep.dataforge.control.devices.DeviceFactory diff --git a/numass-control/msp/src/main/resources/fxml/MspView.fxml b/numass-control/msp/src/main/resources/fxml/MspView.fxml index e970be6d..3bb4f584 100644 --- a/numass-control/msp/src/main/resources/fxml/MspView.fxml +++ b/numass-control/msp/src/main/resources/fxml/MspView.fxml @@ -25,7 +25,7 @@ limitations under the License. + fx:controller="inr.numass.control.msp.MspDisplay"> diff --git a/numass-control/src/main/kotlin/inr/numass/control/DeviceViewConnection.kt b/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt similarity index 62% rename from numass-control/src/main/kotlin/inr/numass/control/DeviceViewConnection.kt rename to numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt index 82ed48dd..854209b9 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/DeviceViewConnection.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplay.kt @@ -1,6 +1,7 @@ package inr.numass.control import hep.dataforge.control.Connection +import hep.dataforge.control.connections.Roles import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.DeviceListener import hep.dataforge.control.devices.PortSensor @@ -17,33 +18,44 @@ import javafx.scene.layout.HBox import javafx.scene.layout.Priority import tornadofx.* import java.util.* +import kotlin.reflect.KClass +import kotlin.reflect.full.createInstance + + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +annotation class DeviceView(val value: KClass>) /** + * Get existing view connection or create a new one + */ +fun Device.getDisplay(): DeviceDisplay<*> { + val type = DefaultDisplay::class; + return optConnection(Roles.VIEW_ROLE, DeviceDisplay::class.java).orElseGet { + type.createInstance().also { + connect(it, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE); + } + } +} + + +/** + * + * An FX View to represent the device * Created by darksnake on 14-May-17. */ -abstract class DeviceViewConnection : Component(), Connection, DeviceListener { +abstract class DeviceDisplay : Component(), Connection, DeviceListener { private val bindings = HashMap>() private val deviceProperty = SimpleObjectProperty(this, "device", null) - val device: D - get() { - val res = deviceProperty.get(); - if (res == null) { - throw RuntimeException("Not connected!"); - } else { - return res - } - } + val device: D by deviceProperty - private val viewProperty = SimpleObjectProperty(this, "view", null) - val view: View - get() { - if (viewProperty.get() == null) { - viewProperty.set(buildView(device)) - } - return viewProperty.get(); - } + // private val viewProperty = SimpleObjectProperty(this, "view", null) + val view: UIComponent? by lazy { + buildView(device) + } override fun isOpen(): Boolean { return this.deviceProperty.get() != null @@ -60,13 +72,13 @@ abstract class DeviceViewConnection : Component(), Connection, Devic } override fun close() { - if (viewProperty.isNotNull.get()) { - view.close() + if (isOpen) { + view?.close() + deviceProperty.set(null) } - deviceProperty.set(null) } - abstract fun buildView(device: D): View; + abstract fun buildView(device: D): UIComponent?; /** * Get binding for a given device state @@ -125,17 +137,31 @@ abstract class DeviceViewConnection : Component(), Connection, Devic return HBox().apply { alignment = Pos.CENTER_LEFT vgrow = Priority.ALWAYS; - deviceStateIndicator(this@DeviceViewConnection, Device.INITIALIZED_STATE) - deviceStateIndicator(this@DeviceViewConnection, PortSensor.CONNECTED_STATE) - deviceStateIndicator(this@DeviceViewConnection, Sensor.MEASURING_STATE) - deviceStateIndicator(this@DeviceViewConnection, "storing") + deviceStateIndicator(this@DeviceDisplay, Device.INITIALIZED_STATE) + deviceStateIndicator(this@DeviceDisplay, PortSensor.CONNECTED_STATE) + deviceStateIndicator(this@DeviceDisplay, Sensor.MEASURING_STATE) + deviceStateIndicator(this@DeviceDisplay, "storing") pane { hgrow = Priority.ALWAYS } togglebutton("View") { isSelected = false - view.bindWindow(this.selectedProperty()) + if (view == null) { + isDisable = true + } + view?.bindWindow(selectedProperty()) } } } } + + +/** + * Default display shows only board pane and nothing else + */ +class DefaultDisplay() : DeviceDisplay() { + override fun buildView(device: Device): UIComponent? { + return null; + } +} + diff --git a/numass-control/src/main/kotlin/inr/numass/control/DeviceViewFactory.kt b/numass-control/src/main/kotlin/inr/numass/control/DeviceViewFactory.kt deleted file mode 100644 index e673668f..00000000 --- a/numass-control/src/main/kotlin/inr/numass/control/DeviceViewFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package inr.numass.control - -import hep.dataforge.control.devices.Device -import hep.dataforge.control.devices.DeviceFactory - -interface DeviceViewFactory : DeviceFactory { - /** - * Create but do not connect view connection for the device - * @return - */ - fun buildView(device: Device): DeviceViewConnection -} diff --git a/numass-control/src/main/kotlin/inr/numass/control/FXExtensions.kt b/numass-control/src/main/kotlin/inr/numass/control/FXExtensions.kt index bfc552bd..1eb066e0 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/FXExtensions.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/FXExtensions.kt @@ -76,7 +76,7 @@ class Indicator(radius: Double = 10.0) : Circle(radius, Color.GRAY) { fun EventTarget.indicator(radius: Double = 10.0, op: (Indicator.() -> Unit)? = null) = opcr(this, Indicator(radius), op) -fun Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform: ((Value) -> Paint)? = null) { +fun Indicator.bind(connection: DeviceDisplay<*>, state: String, transform: ((Value) -> Paint)? = null) { tooltip(state) if (transform != null) { bind(connection.getStateBinding(state), transform); @@ -96,7 +96,7 @@ fun Indicator.bind(connection: DeviceViewConnection<*>, state: String, transform /** * State name + indicator */ -fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state: String, showName: Boolean = true, transform: ((Value) -> Paint)? = null) { +fun EventTarget.deviceStateIndicator(connection: DeviceDisplay<*>, state: String, showName: Boolean = true, transform: ((Value) -> Paint)? = null) { if (connection.device.hasState(state)) { if (showName) { text("${state.toUpperCase()}: ") @@ -113,7 +113,7 @@ fun EventTarget.deviceStateIndicator(connection: DeviceViewConnection<*>, state: /** * A togglebutton + indicator for boolean state */ -fun Node.deviceStateToggle(connection: DeviceViewConnection<*>, state: String, title: String = state) { +fun Node.deviceStateToggle(connection: DeviceDisplay<*>, state: String, title: String = state) { if (connection.device.hasState(state)) { togglebutton(title) { isSelected = false diff --git a/numass-control/src/main/kotlin/inr/numass/control/NumassControlApplication.kt b/numass-control/src/main/kotlin/inr/numass/control/NumassControlApplication.kt index be1f0214..e7076a6d 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/NumassControlApplication.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/NumassControlApplication.kt @@ -3,9 +3,9 @@ package inr.numass.control import ch.qos.logback.classic.Level import hep.dataforge.control.connections.Roles import hep.dataforge.control.devices.Device +import hep.dataforge.control.devices.DeviceFactory import hep.dataforge.exceptions.ControlException import hep.dataforge.meta.Meta -import hep.dataforge.utils.ContextMetaFactory import javafx.scene.Scene import javafx.stage.Stage import org.slf4j.LoggerFactory @@ -25,9 +25,9 @@ abstract class NumassControlApplication : App() { rootLogger.level = Level.INFO device = setupDevice() - val controller = buildView(device) + val controller = device.getDisplay() device.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) - val scene = Scene(controller.view.root) + val scene = Scene(controller.view?.root ?: controller.getBoardView()) stage.scene = scene stage.show() @@ -35,19 +35,12 @@ abstract class NumassControlApplication : App() { setDFStageIcon(stage) } - /** - * Build a view connection - - * @return - */ - protected abstract fun buildView(device: D): DeviceViewConnection - /** * Get a device factory for given device * @return */ - protected abstract val deviceFactory: ContextMetaFactory + protected abstract val deviceFactory: DeviceFactory protected abstract fun setupStage(stage: Stage, device: D) diff --git a/numass-control/src/main/kotlin/inr/numass/control/StorageHelper.kt b/numass-control/src/main/kotlin/inr/numass/control/StorageHelper.kt index a42f2539..a165a1b7 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/StorageHelper.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/StorageHelper.kt @@ -2,7 +2,6 @@ package inr.numass.control import hep.dataforge.control.connections.StorageConnection import hep.dataforge.control.devices.AbstractDevice -import hep.dataforge.exceptions.StorageException import hep.dataforge.storage.api.TableLoader import hep.dataforge.values.Values import java.util.* @@ -11,16 +10,16 @@ import java.util.* * A helper to store points in multiple loaders * Created by darksnake on 16-May-17. */ -class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection)-> TableLoader) : AutoCloseable { +class StorageHelper(private val device: AbstractDevice, private val loaderFactory: (StorageConnection) -> TableLoader) : AutoCloseable { private val loaderMap = HashMap() fun push(point: Values) { if (!device.hasState("storing") || device.getState("storing").booleanValue()) { device.forEachConnection("storage", StorageConnection::class.java) { connection -> - val pl = loaderMap.computeIfAbsent(connection, loaderFactory) try { + val pl = loaderMap.computeIfAbsent(connection, loaderFactory) pl.push(point) - } catch (ex: StorageException) { + } catch (ex: Exception) { device.logger.error("Push to loader failed", ex) } } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt index 82f4a87b..6be92b58 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/CM32Device.kt @@ -15,17 +15,13 @@ import hep.dataforge.control.ports.PortFactory import hep.dataforge.control.ports.PortHandler import hep.dataforge.exceptions.ControlException import hep.dataforge.meta.Meta +import inr.numass.control.DeviceView /** * @author Alexander Nozik */ -class CM32Device : PortSensor { - constructor() {} - - constructor(context: Context, meta: Meta) { - setContext(context) - setMeta(meta) - } +@DeviceView(VacDisplay::class) +class CM32Device(context: Context, meta: Meta) : PortSensor(context, meta) { @Throws(ControlException::class) override fun buildHandler(portName: String): PortHandler { @@ -54,7 +50,7 @@ class CM32Device : PortSensor { @Throws(Exception::class) override fun doMeasure(): Double? { - val answer = sendAndWait(CM32_QUERY, timeout()) + val answer = sendAndWait("MES R PM 1\r\n", timeout()) if (answer.isEmpty()) { this.updateMessage("No signal") @@ -82,9 +78,4 @@ class CM32Device : PortSensor { } - companion object { - - private val CM32_QUERY = "MES R PM 1\r\n" - } - } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt index ec1e09bf..1da68bc5 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSBaratronDevice.kt @@ -14,22 +14,16 @@ import hep.dataforge.control.ports.PortHandler import hep.dataforge.description.ValueDef import hep.dataforge.exceptions.ControlException import hep.dataforge.meta.Meta +import inr.numass.control.DeviceView /** * @author Alexander Nozik */ @ValueDef(name = "channel") -class MKSBaratronDevice : PortSensor { +@DeviceView(VacDisplay::class) +class MKSBaratronDevice(context: Context, meta: Meta) : PortSensor(context, meta) { - private val channel: Int - get() = meta().getInt("channel", 2)!! - - constructor() {} - - constructor(context: Context, meta: Meta) { - setContext(context) - setMeta(meta) - } + private val channel: Int = meta().getInt("channel", 2) override fun createMeasurement(): Measurement { diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt index 4457b094..0da8d2a4 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MKSVacDevice.kt @@ -13,10 +13,12 @@ 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.description.ValueDefs import hep.dataforge.exceptions.ControlException import hep.dataforge.meta.Meta 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.adapter.JavaBeanBooleanPropertyBuilder import java.util.regex.Pattern @@ -24,22 +26,20 @@ 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") +@ValueDefs( + 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 { +@DeviceView(VacDisplay::class) +class MKSVacDevice(context: Context, meta: Meta) : PortSensor(context, meta) { - private//PENDING cache this? - val deviceAddress: String + private 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 + private var isPowerOn: Boolean get() = getState("power").booleanValue() @Throws(ControlException::class) set(powerOn) { @@ -62,15 +62,7 @@ class MKSVacDevice : PortSensor { } } - private val channel: Int - get() = meta().getInt("channel", 5)!! - - constructor() {} - - constructor(context: Context, meta: Meta) { - setContext(context) - setMeta(meta) - } + private val channel: Int = meta().getInt("channel", 5)!! @Throws(ControlException::class) private fun talk(requestContent: String): String? { @@ -97,9 +89,9 @@ class MKSVacDevice : PortSensor { @Throws(ControlException::class) override fun computeState(stateName: String): Any { - when (stateName) { - "power" -> return talk("FP?") == "ON" - else -> return super.computeState(stateName) + return when (stateName) { + "power" -> talk("FP?") == "ON" + else -> super.computeState(stateName) } } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt index ee35e92f..99dfab5d 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/MeradatVacDevice.kt @@ -24,14 +24,7 @@ import java.util.regex.Pattern * @author Alexander Nozik */ @ValueDef(name = "address", type = arrayOf(NUMBER), def = "1", info = "A modbus address") -class MeradatVacDevice : PortSensor { - - constructor() {} - - constructor(context: Context, meta: Meta) { - setContext(context) - setMeta(meta) - } +class MeradatVacDevice(context: Context, meta: Meta) : PortSensor(context, meta) { @Throws(ControlException::class) override fun buildHandler(portName: String): PortHandler { @@ -111,14 +104,13 @@ class MeradatVacDevice : PortSensor { 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` + var value = Integer.toHexString(-checksum) + value = value.substring(value.length - 2).toUpperCase() + if (value.length < 2) { + value = "0" + value } - return `val` + return value } } - } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/ReadVac.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/ReadVac.kt index 14ef6ff2..86da489c 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/ReadVac.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/ReadVac.kt @@ -6,8 +6,6 @@ package inr.numass.control.readvac import hep.dataforge.meta.Meta -import hep.dataforge.utils.ContextMetaFactory -import inr.numass.control.DeviceViewConnection import inr.numass.control.NumassControlApplication import javafx.stage.Stage @@ -15,11 +13,8 @@ import javafx.stage.Stage * @author Alexander Nozik */ class ReadVac : NumassControlApplication() { - override fun buildView(device: VacCollectorDevice): DeviceViewConnection { - return VacCollectorViewConnection() - } - override val deviceFactory: ContextMetaFactory = VacDeviceFactory() + override val deviceFactory = VacDeviceFactory() override fun setupStage(stage: Stage, device: VacCollectorDevice) { stage.title = "Numass vacuum measurements" diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt index 76eb61ef..02ba0c8f 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDevice.kt @@ -6,6 +6,7 @@ package inr.numass.control.readvac import hep.dataforge.context.Context +import hep.dataforge.control.Connection import hep.dataforge.control.RoleDef import hep.dataforge.control.collectors.RegularPointCollector import hep.dataforge.control.connections.Roles @@ -29,6 +30,7 @@ import hep.dataforge.utils.DateTimeUtils import hep.dataforge.values.Value import hep.dataforge.values.ValueType import hep.dataforge.values.Values +import inr.numass.control.DeviceView import inr.numass.control.StorageHelper import java.time.Duration import java.time.Instant @@ -44,48 +46,27 @@ import java.util.stream.Stream */ @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, DeviceHub { +@DeviceView(VacCollectorDisplay::class) +class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection>) : Sensor(context, meta), DeviceHub { - private var sensorMap: MutableMap> = LinkedHashMap() private val helper = StorageHelper(this, this::buildLoader) - val sensors: Collection> - 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 { - return Optional.ofNullable(sensorMap.get(name.toString())) + return Optional.ofNullable(sensors.find { it.name == name.toUnescaped() }) } override fun deviceNames(): Stream { - return sensorMap.keys.stream().map { Name.ofSingle(it) } + return sensors.stream().map { Name.ofSingle(it.name) } } - fun setSensors(sensors: Iterable>) { - sensorMap = LinkedHashMap() - for (sensor in sensors) { - sensorMap.put(sensor.name, sensor) - } - } - - fun setSensors(vararg sensors: Sensor) { - setSensors(Arrays.asList(*sensors)) - } - - @Throws(ControlException::class) override fun init() { super.init() - for (s in sensorMap.values) { + for (s in sensors) { s.init() } } @@ -118,6 +99,16 @@ class VacCollectorDevice : Sensor, DeviceHub { return LoaderFactory.buildPointLoder(connection.storage, "vactms_" + suffix, "", "timestamp", format.build()) } + override fun connectAll(connection: Connection, vararg roles: String) { + connect(connection, *roles) + this.sensors.forEach { it.connect(connection, *roles) } + } + + override fun connectAll(context: Context, meta: Meta) { + this.connectionHelper.connect(context, meta) + this.sensors.forEach { it.connectionHelper.connect(context, meta) } + } + private inner class VacuumMeasurement : AbstractMeasurement() { private val collector = RegularPointCollector(averagingDuration) { this.result(it) } @@ -133,7 +124,7 @@ class VacCollectorDevice : Sensor, DeviceHub { executor = Executors.newSingleThreadScheduledExecutor { r: Runnable -> Thread(r, "VacuumMeasurement thread") } val delay = meta().getInt("delay", 5)!! * 1000 currentTask = executor!!.scheduleWithFixedDelay({ - sensorMap.values.forEach { sensor -> + sensors.forEach { sensor -> try { val value: Any? value = if (sensor.optBooleanState(CONNECTED_STATE).orElse(false)) { @@ -158,7 +149,7 @@ class VacCollectorDevice : Sensor, DeviceHub { private fun terminator(): Values { val p = ValueMap.Builder() p.putValue("timestamp", DateTimeUtils.now()) - sensorMap.keys.forEach { n -> p.putValue(n, null) } + deviceNames().forEach { n -> p.putValue(n.toUnescaped(), null) } return p.build() } @@ -175,5 +166,4 @@ class VacCollectorDevice : Sensor, DeviceHub { return isRunning } } - } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorViewConnection.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDisplay.kt similarity index 85% rename from numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorViewConnection.kt rename to numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDisplay.kt index 32b21027..bf390471 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorViewConnection.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacCollectorDisplay.kt @@ -15,7 +15,7 @@ import hep.dataforge.fx.fragments.LogFragment import hep.dataforge.plots.data.TimePlot import hep.dataforge.plots.data.TimePlottableGroup import hep.dataforge.values.Value -import inr.numass.control.DeviceViewConnection +import inr.numass.control.DeviceDisplay import inr.numass.control.deviceStateToggle import inr.numass.control.plot import javafx.collections.FXCollections @@ -31,13 +31,13 @@ import java.time.Instant * @author [Alexander Nozik](mailto:altavir@gmail.com) */ -class VacCollectorViewConnection : DeviceViewConnection() { +class VacCollectorDisplay : DeviceDisplay() { private val table = FXCollections.observableHashMap() - private val sensorConnection = object : MeasurementListener, Connection{ + private val sensorConnection = object : MeasurementListener, Connection { override fun onMeasurementResult(measurement: Measurement<*>, result: Any, time: Instant?) { - if(result is Double){ + if (result is Double) { table.put(measurement.device.name, result); } } @@ -47,7 +47,7 @@ class VacCollectorViewConnection : DeviceViewConnection() { } } - private val viewList = FXCollections.observableArrayList(); + private val viewList = FXCollections.observableArrayList(); override fun buildView(device: VacCollectorDevice): View { return VacCollectorView(); @@ -56,7 +56,7 @@ class VacCollectorViewConnection : DeviceViewConnection() { override fun open(obj: Any) { super.open(obj) device.sensors.forEach { sensor -> - val view = VacViewConnection() + val view = VacDisplay() sensor.connect(view, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) sensor.connect(sensorConnection, Roles.MEASUREMENT_LISTENER_ROLE); viewList.add(view) @@ -81,8 +81,8 @@ class VacCollectorViewConnection : DeviceViewConnection() { override val root = borderpane { top { toolbar { - deviceStateToggle(this@VacCollectorViewConnection, Sensor.MEASURING_STATE, "Measure") - deviceStateToggle(this@VacCollectorViewConnection, "storing", "Store") + deviceStateToggle(this@VacCollectorDisplay, Sensor.MEASURING_STATE, "Measure") + deviceStateToggle(this@VacCollectorDisplay, "storing", "Store") pane { hgrow = Priority.ALWAYS } @@ -109,8 +109,10 @@ class VacCollectorViewConnection : DeviceViewConnection() { hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER vbox { viewList.forEach { - add(it.view) - separator(Orientation.HORIZONTAL) + it.view?.let { + add(it) + separator(Orientation.HORIZONTAL) + } } } } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDeviceFactory.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDeviceFactory.kt index 1f46a514..83480df9 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDeviceFactory.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDeviceFactory.kt @@ -1,46 +1,41 @@ package inr.numass.control.readvac import hep.dataforge.context.Context -import hep.dataforge.control.devices.Device +import hep.dataforge.control.devices.DeviceFactory import hep.dataforge.control.devices.Sensor import hep.dataforge.control.virtual.VirtualDevice import hep.dataforge.meta.Meta -import inr.numass.control.DeviceViewConnection -import inr.numass.control.DeviceViewFactory import java.util.stream.Collectors /** * A factory for vacuum measurements collector * Created by darksnake on 16-May-17. */ -class VacDeviceFactory : DeviceViewFactory { +class VacDeviceFactory : DeviceFactory { override fun getType(): String { - return "numass:vac" + return "numass.vac" } - fun buildSensor(context: Context, sensorConfig: Meta): Sensor { - when (sensorConfig.getString("sensorType", "")) { - "mks" -> return MKSVacDevice(context, sensorConfig) - "CM32" -> return CM32Device(context, sensorConfig) - "meradat" -> return MeradatVacDevice(context, sensorConfig) - "baratron" -> return MKSBaratronDevice(context, sensorConfig) - VirtualDevice.VIRTUAL_SENSOR_TYPE -> return VirtualDevice.randomDoubleSensor(context, sensorConfig) + private fun buildSensor(context: Context, sensorConfig: Meta): Sensor { + return when (sensorConfig.getString("sensorType", "")) { + "mks" -> MKSVacDevice(context, sensorConfig) + "CM32" -> CM32Device(context, sensorConfig) + "meradat" -> MeradatVacDevice(context, sensorConfig) + "baratron" -> MKSBaratronDevice(context, sensorConfig) + VirtualDevice.VIRTUAL_SENSOR_TYPE -> VirtualDevice.randomDoubleSensor(context, sensorConfig) else -> throw RuntimeException("Unknown vacuum sensor type") } } override fun build(context: Context, config: Meta): VacCollectorDevice { val sensors = config.getMetaList("sensor").stream() - .map { sensorConfig -> - buildSensor(context, sensorConfig) - }.collect(Collectors.toList>()) + .map { sensorConfig -> buildSensor(context, sensorConfig) } + .collect(Collectors.toList>()) - val collector = VacCollectorDevice(context, config) - collector.setSensors(sensors) - return collector + return VacCollectorDevice(context, config, sensors) } - override fun buildView(device: Device): DeviceViewConnection { - return VacCollectorViewConnection(); - } +// override fun buildView(device: Device): DeviceDisplay { +// return VacCollectorDisplay(); +// } } diff --git a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacViewConnection.kt b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDisplay.kt similarity index 95% rename from numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacViewConnection.kt rename to numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDisplay.kt index 0cc8f31a..d8e2e856 100644 --- a/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacViewConnection.kt +++ b/numass-control/vac/src/main/kotlin/inr/numass/control/readvac/VacDisplay.kt @@ -10,7 +10,7 @@ import hep.dataforge.control.devices.PortSensor.CONNECTED_STATE import hep.dataforge.control.devices.Sensor import hep.dataforge.control.measurements.Measurement import hep.dataforge.control.measurements.MeasurementListener -import inr.numass.control.DeviceViewConnection +import inr.numass.control.DeviceDisplay import inr.numass.control.switch import javafx.application.Platform import javafx.beans.property.SimpleObjectProperty @@ -31,7 +31,7 @@ import java.time.format.DateTimeFormatter /** * @author [Alexander Nozik](mailto:altavir@gmail.com) */ -open class VacViewConnection : DeviceViewConnection>(), MeasurementListener { +open class VacDisplay : DeviceDisplay>(), MeasurementListener { val statusProperty = SimpleStringProperty("") var status: String by statusProperty diff --git a/numass-control/vac/src/main/resources/META-INF/services/inr.numass.control.DeviceViewFactory b/numass-control/vac/src/main/resources/META-INF/services/hep.dataforge.control.devices.DeviceFactory similarity index 100% rename from numass-control/vac/src/main/resources/META-INF/services/inr.numass.control.DeviceViewFactory rename to numass-control/vac/src/main/resources/META-INF/services/hep.dataforge.control.devices.DeviceFactory diff --git a/numass-main/src/main/java/inr/numass/Numass.java b/numass-main/src/main/java/inr/numass/Numass.java index 55ed216f..fc332ecc 100644 --- a/numass-main/src/main/java/inr/numass/Numass.java +++ b/numass-main/src/main/java/inr/numass/Numass.java @@ -59,7 +59,7 @@ public class Numass { am.getAllActions() .map(name -> am.optAction(name).get()) - .map(action -> ActionDescriptor.build(action)).forEach(descriptor -> + .map(ActionDescriptor::build).forEach(descriptor -> builder.text("\t").content(MarkupUtils.markupDescriptor(descriptor)) );