Updating control

This commit is contained in:
Alexander Nozik 2018-04-08 11:01:58 +03:00
parent 794929890a
commit 515eb2faac
12 changed files with 268 additions and 265 deletions

View File

@ -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<LambdaHub>() {
class LambdaHubDisplay : DeviceDisplayFX<LambdaHub>() {
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!!)

View File

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

View File

@ -9,12 +9,13 @@ 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> { Duration.parse(it) }.orElse(Duration.ofMillis(200))
private fun setAddress(address: Int, timeout: Duration) {
fun setAddress(address: Int) {
if(currentAddress!= address) {
val response = sendAndWait("ADR $address\r", timeout) { true }.trim()
if (response == "OK") {
currentAddress = address
@ -22,14 +23,15 @@ class LambdaPortController(context: Context, port: Port) : GenericPortController
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 <R> talk(address: Int, action: GenericPortController.() -> R): R {
fun <R> 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())

View File

@ -28,15 +28,19 @@ import java.util.*
*/
class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) {
@Volatile private var currentAddress = -1
private val magnets = HashMap<Int, VirtualMagnetStatus>()
var currentAddress = -1
private set
val statusMap = HashMap<Int, VirtualMagnetStatus>()
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,10 +124,10 @@ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) {
if (currentAddress < 0) {
throw RuntimeException()
}
return magnets[currentAddress]!!
return statusMap[currentAddress]!!
}
private inner class VirtualMagnetStatus(val resistance: Double,
inner class VirtualMagnetStatus(val resistance: Double,
var on: Boolean = true,
var out: Boolean = false,
var current: Double = 0.0) {

View File

@ -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<LambdaMagnet>() {
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<LambdaMagnet>() {
val magnetSpeedField: TextField by fxid()
init{
init {
targetIField.textProperty().addListener { observable: ObservableValue<out String>, oldValue: String, newValue: String ->
if (!newValue.matches("\\d*(\\.)?\\d*".toRegex())) {
targetIField.text = oldValue
@ -80,39 +78,43 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
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<ValueState>("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,21 +127,25 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
}
monitorButton.selectedProperty().onChange {
if (device.monitoring.booleanValue != it) {
if (it) {
monitoring = true
device.monitoring.set(true)
} else {
monitoring = false
device.monitoring.set(false)
this.labelU.text = "----"
}
}
}
magnetSpeedField.text = device.speed.toString()
}
@Throws(PortException::class)
private fun setOutputOn(outputOn: Boolean) {
if (outputOn) {
if (showConfirmation) {
/**
* Show confirmation dialog
*/
private fun confirm(): Boolean {
return if (showConfirmation) {
val alert = Alert(Alert.AlertType.WARNING)
alert.contentText = "Изменение токов в сверхпроводящих магнитах можно производить только при выключенном напряжении на спектрометре." + "\nВы уверены что напряжение выключено?"
alert.headerText = "Проверьте напряжение на спектрометре!"
@ -148,30 +154,21 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
alert.buttonTypes.clear()
alert.buttonTypes.addAll(ButtonType.YES, ButtonType.CANCEL)
if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES) {
startCurrentChange()
alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES
} else {
setButton.isSelected = false
}
} else {
startCurrentChange()
}
} else {
device.stopUpdateTask()
targetIField.isDisable = false
magnetSpeedField.isDisable = false
true
}
}
@Throws(PortException::class)
private fun startCurrentChange() {
private fun setOutputOn(outputOn: Boolean) {
if (outputOn && confirm()) {
val speed = java.lang.Double.parseDouble(magnetSpeedField.text)
if (speed > 0 && speed <= 7) {
if (speed > 0 && speed <= LambdaMagnet.MAX_SPEED) {
device.speed = speed
magnetSpeedField.isDisable = true
target = targetIField.text.toDouble()
output = true
updating = true
device.target.set(targetIField.text.toDouble())
device.output.set(true)
device.updating.set(true)
} else {
val alert = Alert(Alert.AlertType.ERROR)
alert.contentText = null
@ -179,12 +176,16 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
alert.title = "Ошибка!"
alert.show()
setButton.isSelected = false
magnetSpeedField.text = java.lang.Double.toString(device.speed)
magnetSpeedField.text = device.speed.toString()
}
} else {
device.updating.set(false)
targetIField.isDisable = false
magnetSpeedField.isDisable = false
}
}
}
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

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<config>
<device name = "numass.magnets">
<device name="numass.magnets">
<magnet name="SOURCE" address="1"/>
<magnet name="PINCH" address="2"/>
<magnet name="CONUS" address="3"/>
<magnet name="DETECTOR" address="4"/>
<bind first="PINCH" second="CONUS" delta="3"/>
<port type="debug">
<magnet name="SOURCE" address="1" resistance="0.02"/>
<magnet name="PINCH" address="2" resistance="0.01"/>

View File

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

View File

@ -132,12 +132,12 @@ class MspDisplay() : DeviceDisplayFX<MspDevice>(), 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<MspDevice>(), 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)

View File

@ -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<D : Device> : Component(), Connection, DeviceListener {
private val bindings = HashMap<String, ObjectBinding<*>>()
abstract class DeviceDisplayFX<D : Device> : Component(), Connection {
private val deviceProperty = SimpleObjectProperty<D>(this, "device", null)
val device: D by deviceProperty
@ -81,54 +77,16 @@ abstract class DeviceDisplayFX<D : Device> : Component(), Connection, DeviceList
protected abstract fun buildView(device: D): UIComponent?;
/**
* Create a binding for specific state and register it in update listener
*/
private fun <T : Any> bindState(state: State<T>): ObjectBinding<T> {
val binding = object : ObjectBinding<T>() {
override fun computeValue(): T {
return state.value
}
}
bindings.putIfAbsent(state.name, binding)
return binding
}
fun valueBinding(state: ValueState): ObjectBinding<Value>{
return bindState(state)
}
fun valueBinding(stateName: String): ObjectBinding<Value> {
fun valueStateProperty(stateName: String): ObjectProperty<Value> {
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 {

View File

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

View File

@ -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<in D : Device> : 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<in D : Device> : 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()
}

View File

@ -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<S
override fun optDevice(name: Name): Optional<Device> =
Optional.ofNullable(sensors.find { it.name == name.toUnescaped() })
override fun getDeviceNames(): Stream<Name> = sensors.stream().map { Name.ofSingle(it.name) }
override val deviceNames: Stream<Name>
get() = sensors.stream().map { Name.ofSingle(it.name) }
override fun init() {
@ -113,15 +113,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<S
helper.push(values)
}
override fun onStateChange(stateName: String, value: Any) {
if (stateName == MEASURING_STATE) {
if (!(value as Value).booleanValue()) {
override fun stopMeasurement() {
super.stopMeasurement()
notifyResult(terminator())
}
}
}
override fun startMeasurement(oldMeta: Meta?, newMeta: Meta) {
oldMeta?.let {