diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt index 8995dad7..8f1e0fdc 100644 --- a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt +++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaHub.kt @@ -35,16 +35,30 @@ class LambdaHub(context: Context, meta: Meta) : DeviceHub, AbstractDevice(contex magnets.add(LambdaMagnet(controller, it)) } - meta.useEachMeta("bind") { - TODO("add binding") + meta.useEachMeta("bind") { bindMeta -> + val first = magnets.find { it.name == bindMeta.getString("first") } + val second = magnets.find { it.name == bindMeta.getString("second") } + val delta = bindMeta.getDouble("delta") + bind(first!!, second!!, delta) + logger.info("Bound magnet $first to magnet $second with delta $delta") } } + /** + * Add symmetric non-blocking conditions to ensure currents in two magnets have difference within given value. + * @param controller + * @param difference + */ + fun bind(first: LambdaMagnet, second: LambdaMagnet, difference: Double) { + first.bound = { i -> Math.abs(second.current.doubleValue - i) <= difference } + second.bound = { i -> Math.abs(first.current.doubleValue - i) <= difference } + } + private fun buildPort(): Port { val portMeta = meta.getMetaOrEmpty("port") - return if(portMeta.getString("type") == "debug"){ + return if (portMeta.getString("type") == "debug") { VirtualLambdaPort(portMeta) - } else{ + } else { PortFactory.build(portMeta) } } @@ -68,9 +82,9 @@ class LambdaHub(context: Context, meta: Meta) : DeviceHub, AbstractDevice(contex get() = magnets.stream().map { Name.ofSingle(it.name) } } -class LambdaHubDisplay: DeviceDisplayFX() { +class LambdaHubDisplay : DeviceDisplayFX() { override fun buildView(device: LambdaHub): UIComponent? { - return object: View() { + return object : View() { override val root: Parent = vbox { device.magnets.forEach { this.add(it.getDisplay().view!!) diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt index 151f34e2..0d139fec 100644 --- a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt +++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaMagnet.kt @@ -32,26 +32,25 @@ import hep.dataforge.values.ValueType.* import inr.numass.control.DeviceView import inr.numass.control.magnet.fx.MagnetDisplay import kotlinx.coroutines.experimental.runBlocking +import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit import java.util.concurrent.Future -import java.util.concurrent.ScheduledThreadPoolExecutor -import java.util.concurrent.TimeUnit /** * @author Polina */ @StateDefs( - StateDef(value = ValueDef(name = "current", type = arrayOf(NUMBER), def = "0", info = "Current current")), - StateDef(value = ValueDef(name = "voltage", type = arrayOf(NUMBER), def = "0", info = "Current voltage")), + StateDef(value = ValueDef(name = "current", type = arrayOf(NUMBER), def = "-1", info = "Current current")), + StateDef(value = ValueDef(name = "voltage", type = arrayOf(NUMBER), def = "-1", info = "Current voltage")), StateDef(value = ValueDef(name = "outCurrent", type = arrayOf(NUMBER), def = "0", info = "Target current"), writable = true), StateDef(value = ValueDef(name = "outVoltage", type = arrayOf(NUMBER), def = "5.0", info = "Target voltage"), writable = true), StateDef(value = ValueDef(name = "output", type = arrayOf(BOOLEAN), def = "false", info = "Weather output on or off"), writable = true), StateDef(value = ValueDef(name = "lastUpdate", type = arrayOf(TIME), def = "0", info = "Time of the last update"), writable = true), StateDef(value = ValueDef(name = "updating", type = arrayOf(BOOLEAN), def = "false", info = "Shows if current ramping in progress"), writable = true), StateDef(value = ValueDef(name = "monitoring", type = arrayOf(BOOLEAN), def = "false", info = "Shows if monitoring task is running"), writable = true), - StateDef(value = ValueDef(name = "speed", type = arrayOf(NUMBER), info = "Current change speed in Ampere per minute"), writable = true), + StateDef(value = ValueDef(name = "speed", type = arrayOf(NUMBER), def = "5", info = "Current change speed in Ampere per minute"), writable = true), StateDef(ValueDef(name = "status", type = [STRING], def = "INIT", enumeration = LambdaMagnet.MagnetStatus::class, info = "Current state of magnet operation")) ) @DeviceView(MagnetDisplay::class) @@ -65,7 +64,7 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A val address: Int = meta.getInt("address", 1) override val name: String = meta.getString("name", "LAMBDA_$address") - private val scheduler = ScheduledThreadPoolExecutor(1) + //private val scheduler = ScheduledThreadPoolExecutor(1) //var listener: MagnetStateListener? = null // private volatile double current = 0; @@ -81,24 +80,20 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A val voltage = valueState("voltage", getter = { s2d(controller.getParameter(address, "MV")) }) - var target = valueState("target") + val target = valueState("target") //output values of current and voltage private var outCurrent by valueState("outCurrent", getter = { s2d(controller.getParameter(address, "PC")) }) { _, value -> - if (controller.setParameter(address, "PC", value.doubleValue())) { - lastUpdate = DateTimeUtils.now() - } else { - notifyError("Can't set the target current") - } + setCurrent(value.doubleValue()) return@valueState value }.doubleDelegate - private val outVoltage = valueState("outVoltage", getter = { s2d(controller.getParameter(address, "PV")) }) { _, value -> + private var outVoltage = valueState("outVoltage", getter = { s2d(controller.getParameter(address, "PV")) }) { _, value -> if (!controller.setParameter(address, "PV", value.doubleValue())) { notifyError("Can't set the target voltage") } return@valueState value - } + }.doubleDelegate val output = valueState("output", getter = { controller.talk(address, "OUT?") == "OK" }) { _, value -> setOutputMode(value.booleanValue()) @@ -107,7 +102,7 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A } } - var monitoring = valueState("monitoring", getter = { monitorTask != null }) { _, value -> + val monitoring = valueState("monitoring", getter = { monitorTask != null }) { _, value -> if (value.booleanValue()) { startMonitorTask() } else { @@ -119,7 +114,7 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A /** * */ - var updating = valueState("updating", getter = { updateTask != null }) { _, value -> + val updating = valueState("updating", getter = { updateTask != null }) { _, value -> if (value.booleanValue()) { startUpdateTask() } else { @@ -143,7 +138,19 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A /** * The binding limit for magnet current */ - var bound: (Double) -> Boolean = { it < meta.getDouble("maxCurrent") } + var bound: (Double) -> Boolean = { + it < meta.getDouble("maxCurrent", 170.0) + } + + private fun setCurrent(current: Double) { + return if (controller.setParameter(address, "PC", current)) { + lastUpdate = DateTimeUtils.now() + //this.current.update(current) + } else { + notifyError("Can't set the target current") + status = MagnetStatus.ERROR + } + } /** * A setup for single magnet controller @@ -188,62 +195,9 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A private fun s2d(str: String): Double = java.lang.Double.valueOf(str) /** - * Cancel current update task + * Calculate next current step */ - fun stopUpdateTask() { - updateTask?.cancel(false) - } - - /** - * Start recursive updates of current with given delays between updates. If - * delay is 0 then updates are made immediately. - * - * @param targetI - * @param delay - */ - - private fun startUpdateTask(delay: Int = DEFAULT_DELAY) { - assert(delay > 0) - stopUpdateTask() - val call = { - try { - val measuredI = current.doubleValue - val targetI = target.doubleValue - updateState("current", measuredI) - if (Math.abs(measuredI - targetI) > CURRENT_PRECISION) { - val nextI = nextI(measuredI, targetI) - if (bound(nextI)) { - outCurrent = nextI - status = MagnetStatus.OK - } else { - status = MagnetStatus.BOUND - } - } else { - stopUpdateTask() - } - - } catch (ex: PortException) { - notifyError("Error in update task", ex) - stopUpdateTask() - } - } - - updateTask = scheduler.scheduleWithFixedDelay(call, 0, delay.toLong(), TimeUnit.MILLISECONDS) - updateState("updating", true) - } - - @Throws(PortException::class) - private fun setOutputMode(out: Boolean) { - val outState: Int = if (out) 1 else 0 - if (!controller.setParameter(address, "OUT", outState)) { - notifyError("Can't set output mode") - } else { - updateState("output", out) - } - } - private fun nextI(measuredI: Double, targetI: Double): Double { - var step = if (lastUpdate == Instant.EPOCH) { MIN_UP_STEP_SIZE } else { @@ -269,6 +223,58 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A } } + /** + * Start recursive updates of current with given delays between updates. If + * delay is 0 then updates are made immediately. + * + * @param targetI + * @param delay + */ + private fun startUpdateTask(delay: Long = DEFAULT_DELAY.toLong()) { + assert(delay > 0) + stopUpdateTask() + updateTask = repeat(Duration.ofMillis(delay)) { + try { + val measuredI = current.readBlocking().doubleValue() + val targetI = target.doubleValue + if (Math.abs(measuredI - targetI) > CURRENT_PRECISION) { + val nextI = nextI(measuredI, targetI) + status = if (bound(nextI)) { + setCurrent(nextI) + MagnetStatus.OK + } else { + MagnetStatus.BOUND + } + } else { + setCurrent(targetI) + updating.set(false) + } + + } catch (ex: PortException) { + notifyError("Error in update task", ex) + updating.set(false) + } + } + updateState("updating", true) + } + + /** + * Cancel current update task + */ + private fun stopUpdateTask() { + updateTask?.cancel(false) + } + + @Throws(PortException::class) + private fun setOutputMode(out: Boolean) { + val outState: Int = if (out) 1 else 0 + if (!controller.setParameter(address, "OUT", outState)) { + notifyError("Can't set output mode") + } else { + updateState("output", out) + } + } + /** * Cancel current monitoring task */ @@ -285,35 +291,21 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A * * @param delay an interval between scans in milliseconds */ - private fun startMonitorTask(delay: Int = DEFAULT_MONITOR_DELAY) { + private fun startMonitorTask(delay: Long = DEFAULT_MONITOR_DELAY.toLong()) { assert(delay >= 1000) stopMonitorTask() - - val call = Runnable { + monitorTask = repeat(Duration.ofMillis(delay)) { try { runBlocking { - states["voltage"]?.read() - states["current"]?.read() + voltage.read() + current.read() } } catch (ex: PortException) { notifyError("Port connection exception during status measurement", ex) stopMonitorTask() } } - - monitorTask = scheduler.scheduleWithFixedDelay(call, 0, delay.toLong(), TimeUnit.MILLISECONDS) - - } - - /** - * Add symmetric non-blocking conditions to ensure currents in two magnets have difference within given value. - * @param controller - * @param difference - */ - fun bindTo(controller: LambdaMagnet, difference: Double) { - this.bound = { i -> Math.abs(controller.current.doubleValue - i) <= difference } - controller.bound = { i -> Math.abs(this.current.doubleValue - i) <= difference } } enum class MagnetStatus { @@ -330,7 +322,7 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A const val DEFAULT_DELAY = 1 const val DEFAULT_MONITOR_DELAY = 2000 const val MAX_STEP_SIZE = 0.2 - const val MIN_UP_STEP_SIZE = 0.005 + const val MIN_UP_STEP_SIZE = 0.01 const val MIN_DOWN_STEP_SIZE = 0.05 const val MAX_SPEED = 5.0 // 5 A per minute diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt index b55855c7..efa3b808 100644 --- a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt +++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/LambdaPortController.kt @@ -9,27 +9,29 @@ import java.text.DecimalFormat import java.time.Duration //@ValueDef(name = "timeout", type = [(ValueType.NUMBER)], def = "400", info = "A timeout for port response") -class LambdaPortController(context: Context, port: Port) : GenericPortController(context, port) { +class LambdaPortController(context: Context, port: Port) : GenericPortController(context, port, "\r") { private var currentAddress: Int = -1; private val timeout: Duration = port.meta.optString("timeout").map { Duration.parse(it) }.orElse(Duration.ofMillis(200)) - private fun setAddress(address: Int, timeout: Duration) { - val response = sendAndWait("ADR $address\r", timeout) { true }.trim() - if (response == "OK") { - currentAddress = address - } else { - throw RuntimeException("Failed to set address to LAMBDA device on $port") + fun setAddress(address: Int) { + if(currentAddress!= address) { + val response = sendAndWait("ADR $address\r", timeout) { true }.trim() + if (response == "OK") { + currentAddress = address + } else { + throw RuntimeException("Failed to set address to LAMBDA device on $port") + } } } /** * perform series of synchronous actions ensuring that all of them have the same address */ - private fun talk(address: Int, action: GenericPortController.() -> R): R { + fun talk(address: Int, action: GenericPortController.() -> R): R { synchronized(this) { - setAddress(address, timeout) - return this.action() + setAddress(address) + return action.invoke(this) } } @@ -51,7 +53,14 @@ class LambdaPortController(context: Context, port: Port) : GenericPortController fun getParameter(address: Int, name: String): String = talk(address, "$name?") - fun setParameter(address: Int, key: String, state: String): Boolean = "OK" == talk(address, "$key $state") + fun setParameter(address: Int, key: String, state: String): Boolean { + try { + return "OK" == talk(address, "$key $state") + } catch (ex: Exception) { + logger.error("Failed to send message", ex) + return false + } + } fun setParameter(address: Int, key: String, state: Int): Boolean = setParameter(address, key, state.toString()) diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt index 2e232a24..e754f3af 100644 --- a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt +++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/VirtualLambdaPort.kt @@ -28,15 +28,19 @@ import java.util.* */ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) { - @Volatile private var currentAddress = -1 - private val magnets = HashMap() + var currentAddress = -1 + private set + + val statusMap = HashMap() override val name: String = meta.getString("name", "virtual::numass.lambda") + override val delimeter: String = "\r" + init { meta.useEachMeta("magnet") { val num = it.getInt("address", 1) val resistance = it.getDouble("resistance", 1.0) - magnets[num] = VirtualMagnetStatus(resistance) + statusMap[num] = VirtualMagnetStatus(resistance) } } @@ -69,21 +73,18 @@ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) { when (comand) { "ADR" -> { val address = Integer.parseInt(value) - if (magnets.containsKey(address)) { + if (statusMap.containsKey(address)) { currentAddress = address sendOK() } - return } "ADR?" -> { planResponse(Integer.toString(currentAddress), latency) - return } "OUT" -> { val state = Integer.parseInt(value) currentMagnet().out = state == 1 sendOK() - return } "OUT?" -> { val out = currentMagnet().out @@ -92,32 +93,28 @@ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) { } else { planResponse("OFF", latency) } - return } "PC" -> { - var current = java.lang.Double.parseDouble(value) - if (current < 0.5) { - current = 0.0 + val doubleValue = value.toDouble() + val current = if (doubleValue < 0.5) { + 0.0 + } else { + doubleValue } currentMagnet().current = current sendOK() - return } "PC?" -> { planResponse(java.lang.Double.toString(currentMagnet().current), latency) - return } "MC?" -> { planResponse(java.lang.Double.toString(currentMagnet().current), latency) - return } "PV?" -> { planResponse(java.lang.Double.toString(currentMagnet().voltage), latency) - return } "MV?" -> { planResponse(java.lang.Double.toString(currentMagnet().voltage), latency) - return } else -> LoggerFactory.getLogger(javaClass).warn("Unknown command {}", comand) } @@ -127,13 +124,13 @@ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) { if (currentAddress < 0) { throw RuntimeException() } - return magnets[currentAddress]!! + return statusMap[currentAddress]!! } - private inner class VirtualMagnetStatus(val resistance: Double, - var on: Boolean = true, - var out: Boolean = false, - var current: Double = 0.0) { + inner class VirtualMagnetStatus(val resistance: Double, + var on: Boolean = true, + var out: Boolean = false, + var current: Double = 0.0) { val voltage get() = current * resistance } diff --git a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/fx/MagnetDisplay.kt b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/fx/MagnetDisplay.kt index 2c8c97f7..a4c74390 100644 --- a/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/fx/MagnetDisplay.kt +++ b/numass-control/magnet/src/main/kotlin/inr/numass/control/magnet/fx/MagnetDisplay.kt @@ -16,9 +16,12 @@ package inr.numass.control.magnet.fx import hep.dataforge.exceptions.PortException +import hep.dataforge.fx.asDoubleProperty +import hep.dataforge.states.ValueState import inr.numass.control.DeviceDisplayFX import inr.numass.control.magnet.LambdaMagnet import javafx.application.Platform +import javafx.beans.value.ObservableDoubleValue import javafx.beans.value.ObservableValue import javafx.scene.control.* import javafx.scene.layout.AnchorPane @@ -43,13 +46,8 @@ class MagnetDisplay : DeviceDisplayFX() { var showConfirmation = true - val current = valueBinding(device.voltage) - val voltage = valueBinding(device.current) - var target by device.target.doubleDelegate - var output by device.output.booleanDelegate - var monitoring by device.monitoring.booleanDelegate - var updating by device.updating.booleanDelegate - //TODO add status + val current: ObservableDoubleValue = device.current.asDoubleProperty() + val voltage: ObservableDoubleValue = device.voltage.asDoubleProperty() val labelI: Label by fxid() @@ -62,7 +60,7 @@ class MagnetDisplay : DeviceDisplayFX() { val magnetSpeedField: TextField by fxid() - init{ + init { targetIField.textProperty().addListener { observable: ObservableValue, oldValue: String, newValue: String -> if (!newValue.matches("\\d*(\\.)?\\d*".toRegex())) { targetIField.text = oldValue @@ -80,39 +78,43 @@ class MagnetDisplay : DeviceDisplayFX() { current.onChange { runLater { - labelI.text = it?.stringValue() + labelI.text = String.format("%.2f",it) } } voltage.onChange { runLater { - labelU.text = it?.stringValue() + labelU.text = String.format("%.4f",it) } } - valueBinding(device.output).onChange { + device.states.getState("status")?.onChange{ + runLater { + this.statusLabel.text = it.stringValue() + } + } + + device.output.onChange { Platform.runLater { - if (it?.booleanValue() == true) { - this.statusLabel.text = "OK" + if (it.booleanValue()) { this.statusLabel.textFill = Color.BLUE } else { - this.statusLabel.text = "OFF" this.statusLabel.textFill = Color.BLACK } } } - valueBinding(device.updating).onChange { - val updateTaskRunning = it?.booleanValue() ?: false + device.updating.onChange { + val updateTaskRunning = it.booleanValue() runLater { this.setButton.isSelected = updateTaskRunning targetIField.isDisable = updateTaskRunning } } - valueBinding(device.monitoring).onChange { + device.monitoring.onChange { runLater { - monitorButton.isScaleShape = it?.booleanValue() ?: false + monitorButton.isScaleShape = it.booleanValue() } } @@ -125,66 +127,65 @@ class MagnetDisplay : DeviceDisplayFX() { } monitorButton.selectedProperty().onChange { - if (it) { - monitoring = true - } else { - monitoring = false - this.labelU.text = "----" + if (device.monitoring.booleanValue != it) { + if (it) { + device.monitoring.set(true) + } else { + device.monitoring.set(false) + this.labelU.text = "----" + } } } + + magnetSpeedField.text = device.speed.toString() } + /** + * Show confirmation dialog + */ + private fun confirm(): Boolean { + return if (showConfirmation) { + val alert = Alert(Alert.AlertType.WARNING) + alert.contentText = "Изменение токов в сверхпроводящих магнитах можно производить только при выключенном напряжении на спектрометре." + "\nВы уверены что напряжение выключено?" + alert.headerText = "Проверьте напряжение на спектрометре!" + alert.height = 150.0 + alert.title = "Внимание!" + alert.buttonTypes.clear() + alert.buttonTypes.addAll(ButtonType.YES, ButtonType.CANCEL) + + alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES + } else { + true + } + } - @Throws(PortException::class) private fun setOutputOn(outputOn: Boolean) { - if (outputOn) { - if (showConfirmation) { - val alert = Alert(Alert.AlertType.WARNING) - alert.contentText = "Изменение токов в сверхпроводящих магнитах можно производить только при выключенном напряжении на спектрометре." + "\nВы уверены что напряжение выключено?" - alert.headerText = "Проверьте напряжение на спектрометре!" - alert.height = 150.0 - alert.title = "Внимание!" - alert.buttonTypes.clear() - alert.buttonTypes.addAll(ButtonType.YES, ButtonType.CANCEL) - - if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES) { - startCurrentChange() - } else { - setButton.isSelected = false - } + if (outputOn && confirm()) { + val speed = java.lang.Double.parseDouble(magnetSpeedField.text) + if (speed > 0 && speed <= LambdaMagnet.MAX_SPEED) { + device.speed = speed + magnetSpeedField.isDisable = true + device.target.set(targetIField.text.toDouble()) + device.output.set(true) + device.updating.set(true) } else { - startCurrentChange() + val alert = Alert(Alert.AlertType.ERROR) + alert.contentText = null + alert.headerText = "Недопустимое значение скорости изменения тока" + alert.title = "Ошибка!" + alert.show() + setButton.isSelected = false + magnetSpeedField.text = device.speed.toString() } } else { - device.stopUpdateTask() + device.updating.set(false) targetIField.isDisable = false magnetSpeedField.isDisable = false } } - @Throws(PortException::class) - private fun startCurrentChange() { - val speed = java.lang.Double.parseDouble(magnetSpeedField.text) - if (speed > 0 && speed <= 7) { - device.speed = speed - magnetSpeedField.isDisable = true - target = targetIField.text.toDouble() - output = true - updating = true - } else { - val alert = Alert(Alert.AlertType.ERROR) - alert.contentText = null - alert.headerText = "Недопустимое значение скорости изменения тока" - alert.title = "Ошибка!" - alert.show() - setButton.isSelected = false - magnetSpeedField.text = java.lang.Double.toString(device.speed) - } - - } - - fun displayError(name: String, errorMessage: String?, throwable: Throwable) { + private fun displayError(name: String, errorMessage: String?, throwable: Throwable) { Platform.runLater { this.statusLabel.text = "ERROR" this.statusLabel.textFill = Color.RED diff --git a/numass-control/magnet/src/main/resources/debug.xml b/numass-control/magnet/src/main/resources/debug.xml index a0e25c4c..53f91821 100644 --- a/numass-control/magnet/src/main/resources/debug.xml +++ b/numass-control/magnet/src/main/resources/debug.xml @@ -1,10 +1,11 @@ - + + diff --git a/numass-control/magnet/src/test/kotlin/inr/numass/control/magnet/VirtualLambdaPortTest.kt b/numass-control/magnet/src/test/kotlin/inr/numass/control/magnet/VirtualLambdaPortTest.kt new file mode 100644 index 00000000..f8863fc8 --- /dev/null +++ b/numass-control/magnet/src/test/kotlin/inr/numass/control/magnet/VirtualLambdaPortTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package inr.numass.control.magnet + +import hep.dataforge.context.Global +import hep.dataforge.kodex.buildMeta +import org.junit.Assert.assertEquals +import org.junit.Test + +class VirtualLambdaPortTest{ + val magnetMeta = buildMeta { + node("magnet"){ + "address" to 2 + } + } + + @Test + fun testSendOk(){ + val port = VirtualLambdaPort(magnetMeta) + val controller = LambdaPortController(Global,port) + controller.setAddress(2) + assertEquals(2,port.currentAddress) + } +} \ No newline at end of file diff --git a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt index b85554c7..97f384bd 100644 --- a/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt +++ b/numass-control/msp/src/main/kotlin/inr/numass/control/msp/MspDisplay.kt @@ -132,12 +132,12 @@ class MspDisplay() : DeviceDisplayFX(), DeviceListener, NamedValueLis cellFormat { text = "Filament $it" } - disableProperty().bind(booleanBinding(PortSensor.CONNECTED_STATE).not()) + disableProperty().bind(booleanStateProperty(PortSensor.CONNECTED_STATE).not()) } switch { padding = Insets(5.0, 0.0, 0.0, 0.0) disableProperty() - .bind(booleanBinding(PortSensor.CONNECTED_STATE)) + .bind(booleanStateProperty(PortSensor.CONNECTED_STATE)) bindBooleanToState("filamentOn", selectedProperty()) } deviceStateIndicator(this@MspDisplay, "filamentStatus", false) { @@ -152,13 +152,13 @@ class MspDisplay() : DeviceDisplayFX(), DeviceListener, NamedValueLis togglebutton("Measure") { isSelected = false - disableProperty().bind(booleanBinding(PortSensor.CONNECTED_STATE).not()) + disableProperty().bind(booleanStateProperty(PortSensor.CONNECTED_STATE).not()) bindBooleanToState(Sensor.MEASURING_STATE, selectedProperty()) } togglebutton("Store") { isSelected = false - disableProperty().bind(booleanBinding(Sensor.MEASURING_STATE).not()) + disableProperty().bind(booleanStateProperty(Sensor.MEASURING_STATE).not()) bindBooleanToState("storing", selectedProperty()) } separator(Orientation.VERTICAL) diff --git a/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplayFX.kt b/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplayFX.kt index de604c63..0afdfe61 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplayFX.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/DeviceDisplayFX.kt @@ -3,24 +3,22 @@ package inr.numass.control import hep.dataforge.connections.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 import hep.dataforge.control.devices.Sensor import hep.dataforge.exceptions.NameNotFoundException +import hep.dataforge.fx.asBooleanProperty +import hep.dataforge.fx.asProperty import hep.dataforge.fx.bindWindow -import hep.dataforge.states.State import hep.dataforge.states.ValueState import hep.dataforge.values.Value -import javafx.beans.binding.BooleanBinding -import javafx.beans.binding.ObjectBinding import javafx.beans.property.BooleanProperty +import javafx.beans.property.ObjectProperty import javafx.beans.property.SimpleObjectProperty import javafx.geometry.Pos import javafx.scene.Parent import javafx.scene.layout.HBox import javafx.scene.layout.Priority import tornadofx.* -import java.util.* import kotlin.reflect.KClass import kotlin.reflect.full.createInstance @@ -37,7 +35,7 @@ fun Device.getDisplay(): DeviceDisplayFX<*> { val type = (this::class.annotations.find { it is DeviceView } as DeviceView?)?.value ?: DefaultDisplay::class return optConnection(Roles.VIEW_ROLE, DeviceDisplayFX::class.java).orElseGet { type.createInstance().also { - connect(it, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE); + connect(it, Roles.VIEW_ROLE); } } } @@ -48,9 +46,7 @@ fun Device.getDisplay(): DeviceDisplayFX<*> { * An FX View to represent the device * Created by darksnake on 14-May-17. */ -abstract class DeviceDisplayFX : Component(), Connection, DeviceListener { - - private val bindings = HashMap>() +abstract class DeviceDisplayFX : Component(), Connection { private val deviceProperty = SimpleObjectProperty(this, "device", null) val device: D by deviceProperty @@ -81,54 +77,16 @@ abstract class DeviceDisplayFX : Component(), Connection, DeviceList protected abstract fun buildView(device: D): UIComponent?; - /** - * Create a binding for specific state and register it in update listener - */ - private fun bindState(state: State): ObjectBinding { - val binding = object : ObjectBinding() { - override fun computeValue(): T { - return state.value - } - } - bindings.putIfAbsent(state.name, binding) - return binding - } - - fun valueBinding(state: ValueState): ObjectBinding{ - return bindState(state) - } - - fun valueBinding(stateName: String): ObjectBinding { + fun valueStateProperty(stateName: String): ObjectProperty { val state: ValueState = device.states.filterIsInstance(ValueState::class.java).find { it.name == stateName } ?: throw NameNotFoundException("State with name $stateName not found") - return valueBinding(state) + return state.asProperty() } - fun booleanBinding(stateName: String): BooleanBinding { - return valueBinding(stateName).booleanBinding { it?.booleanValue() ?: false } - } - - /** - * Bind existing boolean property to writable device state - - * @param state - * @param property - */ - protected fun bindBooleanToState(state: String, property: BooleanProperty) { - valueBinding(state).addListener { _, oldValue, newValue -> - if (isOpen && oldValue != newValue) { - runLater { property.value = newValue.booleanValue() } - } - } - property.addListener { _, oldValue, newValue -> - if (isOpen && oldValue != newValue) { - device.states[state] = newValue - } - } - } - - override fun notifyStateChanged(device: Device, name: String, state: Any) { - bindings[name]?.invalidate() + fun booleanStateProperty(stateName: String): BooleanProperty { + val state: ValueState = device.states.filterIsInstance(ValueState::class.java).find { it.name == stateName } + ?: throw NameNotFoundException("State with name $stateName not found") + return state.asBooleanProperty() } open fun getBoardView(): Parent { 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 58abbd15..d5293f29 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/FXExtensions.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/FXExtensions.kt @@ -76,9 +76,9 @@ fun EventTarget.indicator(radius: Double = 10.0, op: (Indicator.() -> Unit) = {} fun Indicator.bind(connection: DeviceDisplayFX<*>, state: String, transform: ((Value) -> Paint)? = null) { tooltip(state) if (transform != null) { - bind(connection.valueBinding(state), transform); + bind(connection.valueStateProperty(state), transform); } else { - bind(connection.valueBinding(state)) { + bind(connection.valueStateProperty(state)) { when { it.isNull -> Color.GRAY it.booleanValue() -> Color.GREEN @@ -117,7 +117,7 @@ fun Node.deviceStateToggle(connection: DeviceDisplayFX<*>, state: String, title: connection.device.states[state] = newValue } } - connection.valueBinding(state).onChange { + connection.valueStateProperty(state).onChange { isSelected = it?.booleanValue() ?: 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 72fd3ff3..bda6ff27 100644 --- a/numass-control/src/main/kotlin/inr/numass/control/NumassControlApplication.kt +++ b/numass-control/src/main/kotlin/inr/numass/control/NumassControlApplication.kt @@ -1,7 +1,6 @@ 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 @@ -26,7 +25,6 @@ abstract class NumassControlApplication : App() { device = setupDevice().also { val controller = it.getDisplay() - it.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE) val scene = Scene(controller.view?.root ?: controller.getBoardView()) stage.scene = scene @@ -72,8 +70,8 @@ abstract class NumassControlApplication : App() { try { device?.shutdown() } catch (ex: Exception) { + LoggerFactory.getLogger(javaClass).error("Failed to properly shutdown application", ex); device?.context?.close() - LoggerFactory.getLogger(javaClass).error("Failed to shutdown application", ex); } finally { super.stop() } 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 03c62449..6be249cd 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 @@ -26,7 +26,6 @@ import hep.dataforge.storage.commons.StorageConnection import hep.dataforge.tables.TableFormatBuilder import hep.dataforge.tables.ValueMap import hep.dataforge.utils.DateTimeUtils -import hep.dataforge.values.Value import hep.dataforge.values.ValueType import hep.dataforge.values.Values import inr.numass.control.DeviceView @@ -65,7 +64,8 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection = Optional.ofNullable(sensors.find { it.name == name.toUnescaped() }) - override fun getDeviceNames(): Stream = sensors.stream().map { Name.ofSingle(it.name) } + override val deviceNames: Stream + get() = sensors.stream().map { Name.ofSingle(it.name) } override fun init() { @@ -113,14 +113,9 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection