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,11 +35,25 @@ class LambdaHub(context: Context, meta: Meta) : DeviceHub, AbstractDevice(contex
magnets.add(LambdaMagnet(controller, it)) magnets.add(LambdaMagnet(controller, it))
} }
meta.useEachMeta("bind") { meta.useEachMeta("bind") { bindMeta ->
TODO("add binding") 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 { private fun buildPort(): Port {
val portMeta = meta.getMetaOrEmpty("port") val portMeta = meta.getMetaOrEmpty("port")
return if (portMeta.getString("type") == "debug") { return if (portMeta.getString("type") == "debug") {

View File

@ -32,26 +32,25 @@ import hep.dataforge.values.ValueType.*
import inr.numass.control.DeviceView import inr.numass.control.DeviceView
import inr.numass.control.magnet.fx.MagnetDisplay import inr.numass.control.magnet.fx.MagnetDisplay
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.experimental.runBlocking
import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.TimeUnit
/** /**
* @author Polina * @author Polina
*/ */
@StateDefs( @StateDefs(
StateDef(value = ValueDef(name = "current", type = arrayOf(NUMBER), def = "0", info = "Current current")), StateDef(value = ValueDef(name = "current", type = arrayOf(NUMBER), def = "-1", info = "Current current")),
StateDef(value = ValueDef(name = "voltage", type = arrayOf(NUMBER), def = "0", info = "Current voltage")), 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 = "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 = "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 = "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 = "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 = "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 = "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")) StateDef(ValueDef(name = "status", type = [STRING], def = "INIT", enumeration = LambdaMagnet.MagnetStatus::class, info = "Current state of magnet operation"))
) )
@DeviceView(MagnetDisplay::class) @DeviceView(MagnetDisplay::class)
@ -65,7 +64,7 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A
val address: Int = meta.getInt("address", 1) val address: Int = meta.getInt("address", 1)
override val name: String = meta.getString("name", "LAMBDA_$address") override val name: String = meta.getString("name", "LAMBDA_$address")
private val scheduler = ScheduledThreadPoolExecutor(1) //private val scheduler = ScheduledThreadPoolExecutor(1)
//var listener: MagnetStateListener? = null //var listener: MagnetStateListener? = null
// private volatile double current = 0; // 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")) }) val voltage = valueState("voltage", getter = { s2d(controller.getParameter(address, "MV")) })
var target = valueState("target") val target = valueState("target")
//output values of current and voltage //output values of current and voltage
private var outCurrent by valueState("outCurrent", getter = { s2d(controller.getParameter(address, "PC")) }) { _, value -> private var outCurrent by valueState("outCurrent", getter = { s2d(controller.getParameter(address, "PC")) }) { _, value ->
if (controller.setParameter(address, "PC", value.doubleValue())) { setCurrent(value.doubleValue())
lastUpdate = DateTimeUtils.now()
} else {
notifyError("Can't set the target current")
}
return@valueState value return@valueState value
}.doubleDelegate }.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())) { if (!controller.setParameter(address, "PV", value.doubleValue())) {
notifyError("Can't set the target voltage") notifyError("Can't set the target voltage")
} }
return@valueState value return@valueState value
} }.doubleDelegate
val output = valueState("output", getter = { controller.talk(address, "OUT?") == "OK" }) { _, value -> val output = valueState("output", getter = { controller.talk(address, "OUT?") == "OK" }) { _, value ->
setOutputMode(value.booleanValue()) 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()) { if (value.booleanValue()) {
startMonitorTask() startMonitorTask()
} else { } 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()) { if (value.booleanValue()) {
startUpdateTask() startUpdateTask()
} else { } else {
@ -143,7 +138,19 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A
/** /**
* The binding limit for magnet current * 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 * 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) 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 { private fun nextI(measuredI: Double, targetI: Double): Double {
var step = if (lastUpdate == Instant.EPOCH) { var step = if (lastUpdate == Instant.EPOCH) {
MIN_UP_STEP_SIZE MIN_UP_STEP_SIZE
} else { } 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 * 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 * @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) assert(delay >= 1000)
stopMonitorTask() stopMonitorTask()
monitorTask = repeat(Duration.ofMillis(delay)) {
val call = Runnable {
try { try {
runBlocking { runBlocking {
states["voltage"]?.read() voltage.read()
states["current"]?.read() current.read()
} }
} catch (ex: PortException) { } catch (ex: PortException) {
notifyError("Port connection exception during status measurement", ex) notifyError("Port connection exception during status measurement", ex)
stopMonitorTask() 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 { enum class MagnetStatus {
@ -330,7 +322,7 @@ class LambdaMagnet(private val controller: LambdaPortController, meta: Meta) : A
const val DEFAULT_DELAY = 1 const val DEFAULT_DELAY = 1
const val DEFAULT_MONITOR_DELAY = 2000 const val DEFAULT_MONITOR_DELAY = 2000
const val MAX_STEP_SIZE = 0.2 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 MIN_DOWN_STEP_SIZE = 0.05
const val MAX_SPEED = 5.0 // 5 A per minute const val MAX_SPEED = 5.0 // 5 A per minute

View File

@ -9,12 +9,13 @@ import java.text.DecimalFormat
import java.time.Duration import java.time.Duration
//@ValueDef(name = "timeout", type = [(ValueType.NUMBER)], def = "400", info = "A timeout for port response") //@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 var currentAddress: Int = -1;
private val timeout: Duration = port.meta.optString("timeout").map<Duration> { Duration.parse(it) }.orElse(Duration.ofMillis(200)) 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() val response = sendAndWait("ADR $address\r", timeout) { true }.trim()
if (response == "OK") { if (response == "OK") {
currentAddress = address currentAddress = address
@ -22,14 +23,15 @@ class LambdaPortController(context: Context, port: Port) : GenericPortController
throw RuntimeException("Failed to set address to LAMBDA device on $port") 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 * 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) { synchronized(this) {
setAddress(address, timeout) setAddress(address)
return this.action() 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 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()) 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) { class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) {
@Volatile private var currentAddress = -1 var currentAddress = -1
private val magnets = HashMap<Int, VirtualMagnetStatus>() private set
val statusMap = HashMap<Int, VirtualMagnetStatus>()
override val name: String = meta.getString("name", "virtual::numass.lambda") override val name: String = meta.getString("name", "virtual::numass.lambda")
override val delimeter: String = "\r"
init { init {
meta.useEachMeta("magnet") { meta.useEachMeta("magnet") {
val num = it.getInt("address", 1) val num = it.getInt("address", 1)
val resistance = it.getDouble("resistance", 1.0) 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) { when (comand) {
"ADR" -> { "ADR" -> {
val address = Integer.parseInt(value) val address = Integer.parseInt(value)
if (magnets.containsKey(address)) { if (statusMap.containsKey(address)) {
currentAddress = address currentAddress = address
sendOK() sendOK()
} }
return
} }
"ADR?" -> { "ADR?" -> {
planResponse(Integer.toString(currentAddress), latency) planResponse(Integer.toString(currentAddress), latency)
return
} }
"OUT" -> { "OUT" -> {
val state = Integer.parseInt(value) val state = Integer.parseInt(value)
currentMagnet().out = state == 1 currentMagnet().out = state == 1
sendOK() sendOK()
return
} }
"OUT?" -> { "OUT?" -> {
val out = currentMagnet().out val out = currentMagnet().out
@ -92,32 +93,28 @@ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) {
} else { } else {
planResponse("OFF", latency) planResponse("OFF", latency)
} }
return
} }
"PC" -> { "PC" -> {
var current = java.lang.Double.parseDouble(value) val doubleValue = value.toDouble()
if (current < 0.5) { val current = if (doubleValue < 0.5) {
current = 0.0 0.0
} else {
doubleValue
} }
currentMagnet().current = current currentMagnet().current = current
sendOK() sendOK()
return
} }
"PC?" -> { "PC?" -> {
planResponse(java.lang.Double.toString(currentMagnet().current), latency) planResponse(java.lang.Double.toString(currentMagnet().current), latency)
return
} }
"MC?" -> { "MC?" -> {
planResponse(java.lang.Double.toString(currentMagnet().current), latency) planResponse(java.lang.Double.toString(currentMagnet().current), latency)
return
} }
"PV?" -> { "PV?" -> {
planResponse(java.lang.Double.toString(currentMagnet().voltage), latency) planResponse(java.lang.Double.toString(currentMagnet().voltage), latency)
return
} }
"MV?" -> { "MV?" -> {
planResponse(java.lang.Double.toString(currentMagnet().voltage), latency) planResponse(java.lang.Double.toString(currentMagnet().voltage), latency)
return
} }
else -> LoggerFactory.getLogger(javaClass).warn("Unknown command {}", comand) else -> LoggerFactory.getLogger(javaClass).warn("Unknown command {}", comand)
} }
@ -127,10 +124,10 @@ class VirtualLambdaPort(meta: Meta) : VirtualPort(meta) {
if (currentAddress < 0) { if (currentAddress < 0) {
throw RuntimeException() 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 on: Boolean = true,
var out: Boolean = false, var out: Boolean = false,
var current: Double = 0.0) { var current: Double = 0.0) {

View File

@ -16,9 +16,12 @@
package inr.numass.control.magnet.fx package inr.numass.control.magnet.fx
import hep.dataforge.exceptions.PortException import hep.dataforge.exceptions.PortException
import hep.dataforge.fx.asDoubleProperty
import hep.dataforge.states.ValueState
import inr.numass.control.DeviceDisplayFX import inr.numass.control.DeviceDisplayFX
import inr.numass.control.magnet.LambdaMagnet import inr.numass.control.magnet.LambdaMagnet
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.value.ObservableDoubleValue
import javafx.beans.value.ObservableValue import javafx.beans.value.ObservableValue
import javafx.scene.control.* import javafx.scene.control.*
import javafx.scene.layout.AnchorPane import javafx.scene.layout.AnchorPane
@ -43,13 +46,8 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
var showConfirmation = true var showConfirmation = true
val current = valueBinding(device.voltage) val current: ObservableDoubleValue = device.current.asDoubleProperty()
val voltage = valueBinding(device.current) val voltage: ObservableDoubleValue = device.voltage.asDoubleProperty()
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 labelI: Label by fxid() val labelI: Label by fxid()
@ -80,39 +78,43 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
current.onChange { current.onChange {
runLater { runLater {
labelI.text = it?.stringValue() labelI.text = String.format("%.2f",it)
} }
} }
voltage.onChange { voltage.onChange {
runLater { 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 { Platform.runLater {
if (it?.booleanValue() == true) { if (it.booleanValue()) {
this.statusLabel.text = "OK"
this.statusLabel.textFill = Color.BLUE this.statusLabel.textFill = Color.BLUE
} else { } else {
this.statusLabel.text = "OFF"
this.statusLabel.textFill = Color.BLACK this.statusLabel.textFill = Color.BLACK
} }
} }
} }
valueBinding(device.updating).onChange { device.updating.onChange {
val updateTaskRunning = it?.booleanValue() ?: false val updateTaskRunning = it.booleanValue()
runLater { runLater {
this.setButton.isSelected = updateTaskRunning this.setButton.isSelected = updateTaskRunning
targetIField.isDisable = updateTaskRunning targetIField.isDisable = updateTaskRunning
} }
} }
valueBinding(device.monitoring).onChange { device.monitoring.onChange {
runLater { runLater {
monitorButton.isScaleShape = it?.booleanValue() ?: false monitorButton.isScaleShape = it.booleanValue()
} }
} }
@ -125,21 +127,25 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
} }
monitorButton.selectedProperty().onChange { monitorButton.selectedProperty().onChange {
if (device.monitoring.booleanValue != it) {
if (it) { if (it) {
monitoring = true device.monitoring.set(true)
} else { } else {
monitoring = false device.monitoring.set(false)
this.labelU.text = "----" this.labelU.text = "----"
} }
} }
} }
magnetSpeedField.text = device.speed.toString()
}
@Throws(PortException::class) /**
private fun setOutputOn(outputOn: Boolean) { * Show confirmation dialog
if (outputOn) { */
if (showConfirmation) { private fun confirm(): Boolean {
return if (showConfirmation) {
val alert = Alert(Alert.AlertType.WARNING) val alert = Alert(Alert.AlertType.WARNING)
alert.contentText = "Изменение токов в сверхпроводящих магнитах можно производить только при выключенном напряжении на спектрометре." + "\nВы уверены что напряжение выключено?" alert.contentText = "Изменение токов в сверхпроводящих магнитах можно производить только при выключенном напряжении на спектрометре." + "\nВы уверены что напряжение выключено?"
alert.headerText = "Проверьте напряжение на спектрометре!" alert.headerText = "Проверьте напряжение на спектрометре!"
@ -148,30 +154,21 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
alert.buttonTypes.clear() alert.buttonTypes.clear()
alert.buttonTypes.addAll(ButtonType.YES, ButtonType.CANCEL) alert.buttonTypes.addAll(ButtonType.YES, ButtonType.CANCEL)
if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES) { alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.YES
startCurrentChange()
} else { } else {
setButton.isSelected = false true
}
} else {
startCurrentChange()
}
} else {
device.stopUpdateTask()
targetIField.isDisable = false
magnetSpeedField.isDisable = false
} }
} }
@Throws(PortException::class) private fun setOutputOn(outputOn: Boolean) {
private fun startCurrentChange() { if (outputOn && confirm()) {
val speed = java.lang.Double.parseDouble(magnetSpeedField.text) val speed = java.lang.Double.parseDouble(magnetSpeedField.text)
if (speed > 0 && speed <= 7) { if (speed > 0 && speed <= LambdaMagnet.MAX_SPEED) {
device.speed = speed device.speed = speed
magnetSpeedField.isDisable = true magnetSpeedField.isDisable = true
target = targetIField.text.toDouble() device.target.set(targetIField.text.toDouble())
output = true device.output.set(true)
updating = true device.updating.set(true)
} else { } else {
val alert = Alert(Alert.AlertType.ERROR) val alert = Alert(Alert.AlertType.ERROR)
alert.contentText = null alert.contentText = null
@ -179,12 +176,16 @@ class MagnetDisplay : DeviceDisplayFX<LambdaMagnet>() {
alert.title = "Ошибка!" alert.title = "Ошибка!"
alert.show() alert.show()
setButton.isSelected = false 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
}
} }
} private fun displayError(name: String, errorMessage: String?, throwable: Throwable) {
fun displayError(name: String, errorMessage: String?, throwable: Throwable) {
Platform.runLater { Platform.runLater {
this.statusLabel.text = "ERROR" this.statusLabel.text = "ERROR"
this.statusLabel.textFill = Color.RED this.statusLabel.textFill = Color.RED

View File

@ -5,6 +5,7 @@
<magnet name="PINCH" address="2"/> <magnet name="PINCH" address="2"/>
<magnet name="CONUS" address="3"/> <magnet name="CONUS" address="3"/>
<magnet name="DETECTOR" address="4"/> <magnet name="DETECTOR" address="4"/>
<bind first="PINCH" second="CONUS" delta="3"/>
<port type="debug"> <port type="debug">
<magnet name="SOURCE" address="1" resistance="0.02"/> <magnet name="SOURCE" address="1" resistance="0.02"/>
<magnet name="PINCH" address="2" resistance="0.01"/> <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 { cellFormat {
text = "Filament $it" text = "Filament $it"
} }
disableProperty().bind(booleanBinding(PortSensor.CONNECTED_STATE).not()) disableProperty().bind(booleanStateProperty(PortSensor.CONNECTED_STATE).not())
} }
switch { switch {
padding = Insets(5.0, 0.0, 0.0, 0.0) padding = Insets(5.0, 0.0, 0.0, 0.0)
disableProperty() disableProperty()
.bind(booleanBinding(PortSensor.CONNECTED_STATE)) .bind(booleanStateProperty(PortSensor.CONNECTED_STATE))
bindBooleanToState("filamentOn", selectedProperty()) bindBooleanToState("filamentOn", selectedProperty())
} }
deviceStateIndicator(this@MspDisplay, "filamentStatus", false) { deviceStateIndicator(this@MspDisplay, "filamentStatus", false) {
@ -152,13 +152,13 @@ class MspDisplay() : DeviceDisplayFX<MspDevice>(), DeviceListener, NamedValueLis
togglebutton("Measure") { togglebutton("Measure") {
isSelected = false isSelected = false
disableProperty().bind(booleanBinding(PortSensor.CONNECTED_STATE).not()) disableProperty().bind(booleanStateProperty(PortSensor.CONNECTED_STATE).not())
bindBooleanToState(Sensor.MEASURING_STATE, selectedProperty()) bindBooleanToState(Sensor.MEASURING_STATE, selectedProperty())
} }
togglebutton("Store") { togglebutton("Store") {
isSelected = false isSelected = false
disableProperty().bind(booleanBinding(Sensor.MEASURING_STATE).not()) disableProperty().bind(booleanStateProperty(Sensor.MEASURING_STATE).not())
bindBooleanToState("storing", selectedProperty()) bindBooleanToState("storing", selectedProperty())
} }
separator(Orientation.VERTICAL) separator(Orientation.VERTICAL)

View File

@ -3,24 +3,22 @@ package inr.numass.control
import hep.dataforge.connections.Connection import hep.dataforge.connections.Connection
import hep.dataforge.control.connections.Roles import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceListener
import hep.dataforge.control.devices.PortSensor import hep.dataforge.control.devices.PortSensor
import hep.dataforge.control.devices.Sensor import hep.dataforge.control.devices.Sensor
import hep.dataforge.exceptions.NameNotFoundException import hep.dataforge.exceptions.NameNotFoundException
import hep.dataforge.fx.asBooleanProperty
import hep.dataforge.fx.asProperty
import hep.dataforge.fx.bindWindow import hep.dataforge.fx.bindWindow
import hep.dataforge.states.State
import hep.dataforge.states.ValueState import hep.dataforge.states.ValueState
import hep.dataforge.values.Value import hep.dataforge.values.Value
import javafx.beans.binding.BooleanBinding
import javafx.beans.binding.ObjectBinding
import javafx.beans.property.BooleanProperty import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.geometry.Pos import javafx.geometry.Pos
import javafx.scene.Parent import javafx.scene.Parent
import javafx.scene.layout.HBox import javafx.scene.layout.HBox
import javafx.scene.layout.Priority import javafx.scene.layout.Priority
import tornadofx.* import tornadofx.*
import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance 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 val type = (this::class.annotations.find { it is DeviceView } as DeviceView?)?.value ?: DefaultDisplay::class
return optConnection(Roles.VIEW_ROLE, DeviceDisplayFX::class.java).orElseGet { return optConnection(Roles.VIEW_ROLE, DeviceDisplayFX::class.java).orElseGet {
type.createInstance().also { 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 * An FX View to represent the device
* Created by darksnake on 14-May-17. * Created by darksnake on 14-May-17.
*/ */
abstract class DeviceDisplayFX<D : Device> : Component(), Connection, DeviceListener { abstract class DeviceDisplayFX<D : Device> : Component(), Connection {
private val bindings = HashMap<String, ObjectBinding<*>>()
private val deviceProperty = SimpleObjectProperty<D>(this, "device", null) private val deviceProperty = SimpleObjectProperty<D>(this, "device", null)
val device: D by deviceProperty val device: D by deviceProperty
@ -81,54 +77,16 @@ abstract class DeviceDisplayFX<D : Device> : Component(), Connection, DeviceList
protected abstract fun buildView(device: D): UIComponent?; protected abstract fun buildView(device: D): UIComponent?;
/** fun valueStateProperty(stateName: String): ObjectProperty<Value> {
* 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> {
val state: ValueState = device.states.filterIsInstance(ValueState::class.java).find { it.name == stateName } val state: ValueState = device.states.filterIsInstance(ValueState::class.java).find { it.name == stateName }
?: throw NameNotFoundException("State with name $stateName not found") ?: throw NameNotFoundException("State with name $stateName not found")
return valueBinding(state) return state.asProperty()
} }
fun booleanBinding(stateName: String): BooleanBinding { fun booleanStateProperty(stateName: String): BooleanProperty {
return valueBinding(stateName).booleanBinding { it?.booleanValue() ?: false } val state: ValueState = device.states.filterIsInstance(ValueState::class.java).find { it.name == stateName }
} ?: throw NameNotFoundException("State with name $stateName not found")
return state.asBooleanProperty()
/**
* 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()
} }
open fun getBoardView(): Parent { 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) { fun Indicator.bind(connection: DeviceDisplayFX<*>, state: String, transform: ((Value) -> Paint)? = null) {
tooltip(state) tooltip(state)
if (transform != null) { if (transform != null) {
bind(connection.valueBinding(state), transform); bind(connection.valueStateProperty(state), transform);
} else { } else {
bind(connection.valueBinding(state)) { bind(connection.valueStateProperty(state)) {
when { when {
it.isNull -> Color.GRAY it.isNull -> Color.GRAY
it.booleanValue() -> Color.GREEN it.booleanValue() -> Color.GREEN
@ -117,7 +117,7 @@ fun Node.deviceStateToggle(connection: DeviceDisplayFX<*>, state: String, title:
connection.device.states[state] = newValue connection.device.states[state] = newValue
} }
} }
connection.valueBinding(state).onChange { connection.valueStateProperty(state).onChange {
isSelected = it?.booleanValue() ?: false isSelected = it?.booleanValue() ?: false
} }
} }

View File

@ -1,7 +1,6 @@
package inr.numass.control package inr.numass.control
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import hep.dataforge.control.connections.Roles
import hep.dataforge.control.devices.Device import hep.dataforge.control.devices.Device
import hep.dataforge.control.devices.DeviceFactory import hep.dataforge.control.devices.DeviceFactory
import hep.dataforge.exceptions.ControlException import hep.dataforge.exceptions.ControlException
@ -26,7 +25,6 @@ abstract class NumassControlApplication<in D : Device> : App() {
device = setupDevice().also { device = setupDevice().also {
val controller = it.getDisplay() val controller = it.getDisplay()
it.connect(controller, Roles.VIEW_ROLE, Roles.DEVICE_LISTENER_ROLE)
val scene = Scene(controller.view?.root ?: controller.getBoardView()) val scene = Scene(controller.view?.root ?: controller.getBoardView())
stage.scene = scene stage.scene = scene
@ -72,8 +70,8 @@ abstract class NumassControlApplication<in D : Device> : App() {
try { try {
device?.shutdown() device?.shutdown()
} catch (ex: Exception) { } catch (ex: Exception) {
LoggerFactory.getLogger(javaClass).error("Failed to properly shutdown application", ex);
device?.context?.close() device?.context?.close()
LoggerFactory.getLogger(javaClass).error("Failed to shutdown application", ex);
} finally { } finally {
super.stop() super.stop()
} }

View File

@ -26,7 +26,6 @@ import hep.dataforge.storage.commons.StorageConnection
import hep.dataforge.tables.TableFormatBuilder import hep.dataforge.tables.TableFormatBuilder
import hep.dataforge.tables.ValueMap import hep.dataforge.tables.ValueMap
import hep.dataforge.utils.DateTimeUtils import hep.dataforge.utils.DateTimeUtils
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.values.Values import hep.dataforge.values.Values
import inr.numass.control.DeviceView 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> = override fun optDevice(name: Name): Optional<Device> =
Optional.ofNullable(sensors.find { it.name == name.toUnescaped() }) 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() { override fun init() {
@ -113,15 +113,10 @@ class VacCollectorDevice(context: Context, meta: Meta, val sensors: Collection<S
helper.push(values) helper.push(values)
} }
override fun stopMeasurement() {
super.stopMeasurement()
override fun onStateChange(stateName: String, value: Any) {
if (stateName == MEASURING_STATE) {
if (!(value as Value).booleanValue()) {
notifyResult(terminator()) notifyResult(terminator())
} }
}
}
override fun startMeasurement(oldMeta: Meta?, newMeta: Meta) { override fun startMeasurement(oldMeta: Meta?, newMeta: Meta) {
oldMeta?.let { oldMeta?.let {