More or less working motors app

This commit is contained in:
Alexander Nozik 2020-10-12 15:37:46 +03:00
parent 62dc6ef127
commit 599d08b62a
4 changed files with 98 additions and 44 deletions

View File

@ -93,6 +93,21 @@ public fun DeviceBase.readingNumber(
} }
) )
public fun DeviceBase.readingDouble(
default: Number? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {},
getter: suspend () -> Double,
): PropertyDelegateProvider<DeviceBase, TypedReadOnlyPropertyDelegate<Double>> = TypedReadOnlyDevicePropertyProvider(
this,
default?.let { MetaItem.ValueItem(it.asValue()) },
MetaConverter.double,
descriptorBuilder,
getter = {
val number = getter()
MetaItem.ValueItem(number.asValue())
}
)
public fun DeviceBase.readingString( public fun DeviceBase.readingString(
default: String? = null, default: String? = null,
descriptorBuilder: PropertyDescriptor.() -> Unit = {}, descriptorBuilder: PropertyDescriptor.() -> Unit = {},

View File

@ -7,9 +7,11 @@ import javafx.beans.property.ReadOnlyProperty
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections import javafx.geometry.Pos
import javafx.scene.Parent import javafx.scene.Parent
import javafx.scene.layout.Priority import javafx.scene.layout.Priority
import javafx.scene.layout.VBox
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tornadofx.* import tornadofx.*
@ -27,6 +29,51 @@ class PiMotionMasterController : Controller() {
val motionMaster: PiMotionMasterDevice by deviceManager.installing(PiMotionMasterDevice) val motionMaster: PiMotionMasterDevice by deviceManager.installing(PiMotionMasterDevice)
} }
fun VBox.piMotionMasterAxis(
axisName: String,
axis: PiMotionMasterDevice.Axis,
coroutineScope: CoroutineScope,
) = hbox {
alignment = Pos.CENTER
label(axisName)
coroutineScope.launch {
val min = axis.minPosition.readTyped(true)
val max = axis.maxPosition.readTyped(true)
val positionProperty = axis.position.fxProperty(axis)
val startPosition = axis.position.readTyped(true)
runLater {
vbox {
hgrow = Priority.ALWAYS
slider(min..max, startPosition) {
minWidth = 300.0
isShowTickLabels = true
isShowTickMarks = true
minorTickCount = 10
majorTickUnit = 1.0
valueProperty().onChange {
coroutineScope.launch {
axis.move(value)
}
}
}
slider(min..max) {
isDisable = true
valueProperty().bind(positionProperty)
}
}
}
}
}
fun Parent.axisPane(axes: Map<String, PiMotionMasterDevice.Axis>, coroutineScope: CoroutineScope) {
vbox {
axes.forEach { (name, axis) ->
this.piMotionMasterAxis(name, axis, coroutineScope)
}
}
}
class PiMotionMasterView : View() { class PiMotionMasterView : View() {
private val controller: PiMotionMasterController by inject() private val controller: PiMotionMasterController by inject()
@ -35,7 +82,7 @@ class PiMotionMasterView : View() {
private val connectedProperty: ReadOnlyProperty<Boolean> = device.connected.fxProperty(device) private val connectedProperty: ReadOnlyProperty<Boolean> = device.connected.fxProperty(device)
private val debugServerJobProperty = SimpleObjectProperty<Job>() private val debugServerJobProperty = SimpleObjectProperty<Job>()
private val debugServerStarted = debugServerJobProperty.booleanBinding { it != null } private val debugServerStarted = debugServerJobProperty.booleanBinding { it != null }
private val axisList = FXCollections.observableArrayList<Map.Entry<String, PiMotionMasterDevice.Axis>>() //private val axisList = FXCollections.observableArrayList<Map.Entry<String, PiMotionMasterDevice.Axis>>()
override val root: Parent = borderpane { override val root: Parent = borderpane {
top { top {
@ -49,7 +96,7 @@ class PiMotionMasterView : View() {
} }
} }
field("Port:") { field("Port:") {
textfield(port){ textfield(port) {
stripNonNumeric() stripNonNumeric()
} }
button { button {
@ -86,9 +133,11 @@ class PiMotionMasterView : View() {
action { action {
if (!connectedProperty.value) { if (!connectedProperty.value) {
device.connect(host.get(), port.get()) device.connect(host.get(), port.get())
axisList.addAll(device.axes.entries) center {
axisPane(device.axes,controller.context)
}
} else { } else {
axisList.removeAll() this@borderpane.center = null
device.disconnect() device.disconnect()
} }
} }
@ -98,34 +147,6 @@ class PiMotionMasterView : View() {
} }
} }
center {
listview(axisList) {
cellFormat { (name, axis) ->
hbox {
minHeight = 40.0
label(name)
controller.context.launch {
val min = axis.minPosition.readTyped(true)
val max = axis.maxPosition.readTyped(true)
runLater {
slider(min.toDouble()..max.toDouble()){
hgrow = Priority.ALWAYS
valueProperty().onChange {
isDisable = true
launch {
axis.move(value)
runLater {
isDisable = false
}
}
}
}
}
}
}
}
}
}
} }
} }

View File

@ -81,7 +81,7 @@ class PiMotionMasterDevice(
} }
fun disconnect() { fun disconnect() {
runBlocking{ runBlocking {
disconnect.invoke() disconnect.invoke()
} }
} }
@ -165,7 +165,7 @@ class PiMotionMasterDevice(
private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap { private suspend fun requestAndParse(command: String, vararg arguments: String): Map<String, String> = buildMap {
request(command, *arguments).forEach { line -> request(command, *arguments).forEach { line ->
val (key, value) = line.split("=") val (key, value) = line.split("=")
put(key, value) put(key, value.trim())
} }
} }
@ -277,7 +277,7 @@ class PiMotionMasterDevice(
send("FRF", axisId) send("FRF", axisId)
} }
val minPosition by readingNumber( val minPosition by readingDouble(
descriptorBuilder = { descriptorBuilder = {
info = "Minimal position value for the axis" info = "Minimal position value for the axis"
}, },
@ -287,7 +287,7 @@ class PiMotionMasterDevice(
} }
) )
val maxPosition by readingNumber( val maxPosition by readingDouble(
descriptorBuilder = { descriptorBuilder = {
info = "Maximal position value for the axis" info = "Maximal position value for the axis"
}, },
@ -297,9 +297,15 @@ class PiMotionMasterDevice(
} }
) )
val position: TypedDeviceProperty<Double> by axisNumberProperty("POS") { val position by readingDouble(
info = "The current axis position." descriptorBuilder = {
} info = "The current axis position."
},
getter = {
requestAndParse("POS?", axisId)[axisId]?.toDoubleOrNull()
?: error("Malformed `POS?` response. Should include float value for $axisId")
}
)
val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") { val openLoopTarget: DeviceProperty by axisNumberProperty("OMA") {
info = "Position for open-loop operation." info = "Position for open-loop operation."
@ -320,7 +326,7 @@ class PiMotionMasterDevice(
it.node["velocity"].double?.let { v -> it.node["velocity"].double?.let { v ->
velocity.write(v) velocity.write(v)
} }
position.write(target) targetPosition.write(target)
//read `onTarget` and `position` properties in a cycle until movement is complete //read `onTarget` and `position` properties in a cycle until movement is complete
while (!onTarget.readTyped(true)) { while (!onTarget.readTyped(true)) {
position.read(true) position.read(true)

View File

@ -8,6 +8,8 @@ import hep.dataforge.control.ports.withDelimiter
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.math.abs import kotlin.math.abs
import kotlin.time.Duration import kotlin.time.Duration
@ -20,8 +22,12 @@ abstract class VirtualDevice(val scope: CoroutineScope) : Socket<ByteArray> {
private val toReceive = Channel<ByteArray>(100) private val toReceive = Channel<ByteArray>(100)
private val toRespond = Channel<ByteArray>(100) private val toRespond = Channel<ByteArray>(100)
private val mutex = Mutex()
private val receiveJob: Job = toReceive.consumeAsFlow().transformRequests().onEach { private val receiveJob: Job = toReceive.consumeAsFlow().transformRequests().onEach {
evaluateRequest(it) mutex.withLock {
evaluateRequest(it)
}
}.catch { }.catch {
it.printStackTrace() it.printStackTrace()
}.launchIn(scope) }.launchIn(scope)
@ -143,7 +149,12 @@ class PiMotionMasterVirtualDevice(
} }
val response = selectedAxis.joinToString(separator = " \n") { val response = selectedAxis.joinToString(separator = " \n") {
val state = axisState.getValue(it) val state = axisState.getValue(it)
"$it=${state.extract(it)}" val value = when (val extracted = state.extract(it)) {
true -> 1
false -> 0
else -> extracted
}
"$it=$value"
} }
respond(response) respond(response)
} }
@ -241,13 +252,14 @@ class PiMotionMasterVirtualDevice(
"TMX?" -> respondForAllAxis(axisIds) { maxPosition } "TMX?" -> respondForAllAxis(axisIds) { maxPosition }
"VEL?" -> respondForAllAxis(axisIds) { velocity } "VEL?" -> respondForAllAxis(axisIds) { velocity }
"SRG?" -> respond(WAT) "SRG?" -> respond(WAT)
"ONT?" -> respondForAllAxis(axisIds) { onTarget() }
"SVO" -> doForEachAxis(parts) { key, value -> "SVO" -> doForEachAxis(parts) { key, value ->
axisState[key]?.servoMode = value.toInt() axisState[key]?.servoMode = value.toInt()
} }
"MOV" -> doForEachAxis(parts) { key, value -> "MOV" -> doForEachAxis(parts) { key, value ->
axisState[key]?.targetPosition = value.toDouble() axisState[key]?.targetPosition = value.toDouble()
} }
"VEL"-> doForEachAxis(parts){key, value -> "VEL" -> doForEachAxis(parts) { key, value ->
axisState[key]?.velocity = value.toDouble() axisState[key]?.velocity = value.toDouble()
} }
"INI" -> { "INI" -> {