[WIP] moving from java fx to compose in examples
This commit is contained in:
parent
24b6856f15
commit
44514cd477
@ -48,7 +48,7 @@ public operator fun DeviceHub.get(nameToken: NameToken): Device =
|
|||||||
|
|
||||||
public fun DeviceHub.getOrNull(name: Name): Device? = when {
|
public fun DeviceHub.getOrNull(name: Name): Device? = when {
|
||||||
name.isEmpty() -> this as? Device
|
name.isEmpty() -> this as? Device
|
||||||
name.length == 1 -> get(name.firstOrNull()!!)
|
name.length == 1 -> devices[name.firstOrNull()!!]
|
||||||
else -> (get(name.firstOrNull()!!) as? DeviceHub)?.getOrNull(name.cutFirst())
|
else -> (get(name.firstOrNull()!!) as? DeviceHub)?.getOrNull(name.cutFirst())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package space.kscience.controls.demo
|
package space.kscience.controls.demo
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -173,7 +173,7 @@ fun main() = application {
|
|||||||
controller.shutdown()
|
controller.shutdown()
|
||||||
exitApplication()
|
exitApplication()
|
||||||
},
|
},
|
||||||
state = rememberWindowState(width = 400.dp, height = 300.dp)
|
state = rememberWindowState(width = 400.dp, height = 320.dp)
|
||||||
) {
|
) {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
DemoControls(controller)
|
DemoControls(controller)
|
||||||
|
@ -3,10 +3,6 @@ plugins {
|
|||||||
alias(spclibs.plugins.compose)
|
alias(spclibs.plugins.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
//application{
|
|
||||||
// mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt")
|
|
||||||
//}
|
|
||||||
|
|
||||||
kotlin{
|
kotlin{
|
||||||
explicitApi = null
|
explicitApi = null
|
||||||
}
|
}
|
||||||
@ -17,4 +13,17 @@ val dataforgeVersion: String by extra
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":controls-ports-ktor"))
|
implementation(project(":controls-ports-ktor"))
|
||||||
implementation(projects.controlsMagix)
|
implementation(projects.controlsMagix)
|
||||||
|
|
||||||
|
implementation(compose.runtime)
|
||||||
|
implementation(compose.desktop.currentOs)
|
||||||
|
implementation(compose.material3)
|
||||||
|
implementation(spclibs.logback.classic)
|
||||||
|
}
|
||||||
|
|
||||||
|
compose{
|
||||||
|
desktop{
|
||||||
|
application{
|
||||||
|
mainClass = "ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,193 @@
|
|||||||
package ru.mipt.npm.devices.pimotionmaster
|
package ru.mipt.npm.devices.pimotionmaster
|
||||||
|
|
||||||
import javafx.beans.property.ReadOnlyProperty
|
|
||||||
import javafx.beans.property.SimpleIntegerProperty
|
import androidx.compose.foundation.layout.Column
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import javafx.beans.property.SimpleStringProperty
|
import androidx.compose.foundation.layout.Row
|
||||||
import javafx.geometry.Pos
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import javafx.scene.Parent
|
import androidx.compose.material.Button
|
||||||
import javafx.scene.layout.Priority
|
import androidx.compose.material.OutlinedTextField
|
||||||
import javafx.scene.layout.VBox
|
import androidx.compose.material.Slider
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.maxPosition
|
|
||||||
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.minPosition
|
|
||||||
import ru.mipt.npm.devices.pimotionmaster.PiMotionMasterDevice.Axis.Companion.position
|
|
||||||
import space.kscience.controls.manager.DeviceManager
|
import space.kscience.controls.manager.DeviceManager
|
||||||
import space.kscience.controls.manager.installing
|
import space.kscience.controls.manager.installing
|
||||||
import space.kscience.controls.spec.read
|
import space.kscience.controls.spec.read
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.request
|
import space.kscience.dataforge.context.request
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
class PiMotionMasterApp : App(PiMotionMasterView::class)
|
//class PiMotionMasterApp : App(PiMotionMasterView::class)
|
||||||
|
//
|
||||||
|
//class PiMotionMasterController : Controller() {
|
||||||
|
// //initialize context
|
||||||
|
// val context = Context("piMotionMaster") {
|
||||||
|
// plugin(DeviceManager)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //initialize deviceManager plugin
|
||||||
|
// val deviceManager: DeviceManager = context.request(DeviceManager)
|
||||||
|
//
|
||||||
|
// // install device
|
||||||
|
// val motionMaster: PiMotionMasterDevice by deviceManager.installing(PiMotionMasterDevice)
|
||||||
|
//}
|
||||||
|
|
||||||
class PiMotionMasterController : Controller() {
|
@Composable
|
||||||
//initialize context
|
fun ColumnScope.piMotionMasterAxis(
|
||||||
val context = Context("piMotionMaster"){
|
axisName: String,
|
||||||
|
axis: PiMotionMasterDevice.Axis,
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Text(axisName)
|
||||||
|
var min by remember { mutableStateOf(0f) }
|
||||||
|
var max by remember { mutableStateOf(0f) }
|
||||||
|
var targetPosition by remember { mutableStateOf(0f) }
|
||||||
|
val position: Double by axis.composeState(PiMotionMasterDevice.Axis.position, 0.0)
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
LaunchedEffect(axis) {
|
||||||
|
min = axis.read(PiMotionMasterDevice.Axis.minPosition).toFloat()
|
||||||
|
max = axis.read(PiMotionMasterDevice.Axis.maxPosition).toFloat()
|
||||||
|
targetPosition = axis.read(PiMotionMasterDevice.Axis.position).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Slider(
|
||||||
|
value = position.toFloat(),
|
||||||
|
enabled = false,
|
||||||
|
onValueChange = { },
|
||||||
|
valueRange = min..max
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
value = targetPosition,
|
||||||
|
onValueChange = { newPosition ->
|
||||||
|
scope.launch {
|
||||||
|
axis.move(newPosition.toDouble())
|
||||||
|
}
|
||||||
|
targetPosition = newPosition
|
||||||
|
},
|
||||||
|
valueRange = min..max
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AxisPane(axes: Map<String, PiMotionMasterDevice.Axis>) {
|
||||||
|
Column {
|
||||||
|
axes.forEach { (name, axis) ->
|
||||||
|
this.piMotionMasterAxis(name, axis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PiMotionMasterApp(device: PiMotionMasterDevice) {
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val connected by device.composeState(PiMotionMasterDevice.connected, false)
|
||||||
|
var debugServerJob by remember { mutableStateOf<Job?>(null) }
|
||||||
|
var axes by remember { mutableStateOf<Map<String, PiMotionMasterDevice.Axis>?>(null) }
|
||||||
|
//private val axisList = FXCollections.observableArrayList<Map.Entry<String, PiMotionMasterDevice.Axis>>()
|
||||||
|
var host by remember { mutableStateOf("127.0.0.1") }
|
||||||
|
var port by remember { mutableStateOf(10024) }
|
||||||
|
|
||||||
|
Scaffold {
|
||||||
|
Column {
|
||||||
|
|
||||||
|
|
||||||
|
Text("Address:")
|
||||||
|
Row {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = host,
|
||||||
|
onValueChange = { host = it },
|
||||||
|
label = { Text("Host") },
|
||||||
|
enabled = debugServerJob == null,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
var portError by remember { mutableStateOf(false) }
|
||||||
|
OutlinedTextField(
|
||||||
|
value = port.toString(),
|
||||||
|
onValueChange = {
|
||||||
|
it.toIntOrNull()?.let { value ->
|
||||||
|
port = value
|
||||||
|
portError = false
|
||||||
|
} ?: run {
|
||||||
|
portError = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text("Port") },
|
||||||
|
enabled = debugServerJob == null,
|
||||||
|
isError = portError,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (debugServerJob == null) {
|
||||||
|
debugServerJob = device.context.launchPiDebugServer(port, listOf("1", "2", "3", "4"))
|
||||||
|
} else {
|
||||||
|
debugServerJob?.cancel()
|
||||||
|
debugServerJob = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
if (debugServerJob == null) {
|
||||||
|
Text("Start debug server")
|
||||||
|
} else {
|
||||||
|
Text("Stop debug server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (!connected) {
|
||||||
|
device.launch {
|
||||||
|
device.connect(host, port)
|
||||||
|
}
|
||||||
|
axes = device.axes
|
||||||
|
} else {
|
||||||
|
device.launch {
|
||||||
|
device.disconnect()
|
||||||
|
}
|
||||||
|
axes = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
if (!connected) {
|
||||||
|
Text("Connect")
|
||||||
|
} else {
|
||||||
|
Text("Disconnect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axes?.let { axes ->
|
||||||
|
AxisPane(axes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
|
||||||
|
val context = Context("piMotionMaster") {
|
||||||
plugin(DeviceManager)
|
plugin(DeviceManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,131 +196,14 @@ class PiMotionMasterController : Controller() {
|
|||||||
|
|
||||||
// install device
|
// install device
|
||||||
val motionMaster: PiMotionMasterDevice by deviceManager.installing(PiMotionMasterDevice)
|
val motionMaster: PiMotionMasterDevice by deviceManager.installing(PiMotionMasterDevice)
|
||||||
}
|
|
||||||
|
|
||||||
fun VBox.piMotionMasterAxis(
|
Window(
|
||||||
axisName: String,
|
title = "Pi motion master demo",
|
||||||
axis: PiMotionMasterDevice.Axis,
|
onCloseRequest = { exitApplication() },
|
||||||
coroutineScope: CoroutineScope,
|
state = rememberWindowState(width = 400.dp, height = 300.dp)
|
||||||
) = hbox {
|
) {
|
||||||
alignment = Pos.CENTER
|
MaterialTheme {
|
||||||
label(axisName)
|
PiMotionMasterApp(motionMaster)
|
||||||
coroutineScope.launch {
|
|
||||||
with(axis) {
|
|
||||||
val min: Double = read(minPosition)
|
|
||||||
val max: Double = read(maxPosition)
|
|
||||||
val positionProperty = fxProperty(position)
|
|
||||||
val startPosition = read(position)
|
|
||||||
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() {
|
|
||||||
|
|
||||||
private val controller: PiMotionMasterController by inject()
|
|
||||||
val device = controller.motionMaster
|
|
||||||
|
|
||||||
private val connectedProperty: ReadOnlyProperty<Boolean> = device.fxProperty(PiMotionMasterDevice.connected)
|
|
||||||
private val debugServerJobProperty = SimpleObjectProperty<Job>()
|
|
||||||
private val debugServerStarted = debugServerJobProperty.booleanBinding { it != null }
|
|
||||||
//private val axisList = FXCollections.observableArrayList<Map.Entry<String, PiMotionMasterDevice.Axis>>()
|
|
||||||
|
|
||||||
override val root: Parent = borderpane {
|
|
||||||
top {
|
|
||||||
form {
|
|
||||||
val host = SimpleStringProperty("127.0.0.1")
|
|
||||||
val port = SimpleIntegerProperty(10024)
|
|
||||||
fieldset("Address:") {
|
|
||||||
field("Host:") {
|
|
||||||
textfield(host) {
|
|
||||||
enableWhen(debugServerStarted.not())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field("Port:") {
|
|
||||||
textfield(port) {
|
|
||||||
stripNonNumeric()
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
textProperty().bind(debugServerStarted.stringBinding {
|
|
||||||
if (it != true) {
|
|
||||||
"Start debug server"
|
|
||||||
} else {
|
|
||||||
"Stop debug server"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
action {
|
|
||||||
if (!debugServerStarted.get()) {
|
|
||||||
debugServerJobProperty.value =
|
|
||||||
controller.context.launchPiDebugServer(port.get(), listOf("1", "2", "3", "4"))
|
|
||||||
} else {
|
|
||||||
debugServerJobProperty.get().cancel()
|
|
||||||
debugServerJobProperty.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
textProperty().bind(connectedProperty.stringBinding {
|
|
||||||
if (it == false) {
|
|
||||||
"Connect"
|
|
||||||
} else {
|
|
||||||
"Disconnect"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
action {
|
|
||||||
if (!connectedProperty.value) {
|
|
||||||
device.connect(host.get(), port.get())
|
|
||||||
center {
|
|
||||||
axisPane(device.axes,controller.context)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this@borderpane.center = null
|
|
||||||
device.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
launch<PiMotionMasterApp>()
|
|
||||||
}
|
}
|
@ -7,13 +7,15 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.flow.transformWhile
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import space.kscience.controls.api.DeviceHub
|
import space.kscience.controls.api.DeviceHub
|
||||||
import space.kscience.controls.api.PropertyDescriptor
|
import space.kscience.controls.api.PropertyDescriptor
|
||||||
import space.kscience.controls.ports.*
|
import space.kscience.controls.ports.AsynchronousPort
|
||||||
|
import space.kscience.controls.ports.KtorTcpPort
|
||||||
|
import space.kscience.controls.ports.send
|
||||||
|
import space.kscience.controls.ports.withStringDelimiter
|
||||||
import space.kscience.controls.spec.*
|
import space.kscience.controls.spec.*
|
||||||
import space.kscience.dataforge.context.*
|
import space.kscience.dataforge.context.*
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
@ -33,10 +35,8 @@ class PiMotionMasterDevice(
|
|||||||
//PortProxy { portFactory(address ?: error("The device is not connected"), context) }
|
//PortProxy { portFactory(address ?: error("The device is not connected"), context) }
|
||||||
|
|
||||||
|
|
||||||
fun disconnect() {
|
suspend fun disconnect() {
|
||||||
runBlocking {
|
execute(disconnect)
|
||||||
execute(disconnect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeoutValue: Duration = 200.milliseconds
|
var timeoutValue: Duration = 200.milliseconds
|
||||||
@ -54,13 +54,11 @@ class PiMotionMasterDevice(
|
|||||||
if (errorCode != 0) error(message(errorCode))
|
if (errorCode != 0) error(message(errorCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connect(host: String, port: Int) {
|
suspend fun connect(host: String, port: Int) {
|
||||||
runBlocking {
|
execute(connect, Meta {
|
||||||
execute(connect, Meta {
|
"host" put host
|
||||||
"host" put host
|
"port" put port
|
||||||
"port" put port
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
@ -103,7 +101,7 @@ class PiMotionMasterDevice(
|
|||||||
}.toList()
|
}.toList()
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.warn { "Error during PIMotionMaster request. Requesting error code." }
|
logger.error(ex) { "Error during PIMotionMaster request. Requesting error code." }
|
||||||
val errorCode = getErrorCode()
|
val errorCode = getErrorCode()
|
||||||
dispatchError(errorCode)
|
dispatchError(errorCode)
|
||||||
logger.warn { "Error code $errorCode" }
|
logger.warn { "Error code $errorCode" }
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package ru.mipt.npm.devices.pimotionmaster
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import space.kscience.controls.api.Device
|
||||||
|
import space.kscience.controls.spec.DevicePropertySpec
|
||||||
|
import space.kscience.controls.spec.propertyFlow
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <D : Device, T : Any> D.composeState(
|
||||||
|
spec: DevicePropertySpec<D, T>,
|
||||||
|
initialState: T,
|
||||||
|
): State<T> = propertyFlow(spec).collectAsState(initialState)
|
@ -1,58 +0,0 @@
|
|||||||
package ru.mipt.npm.devices.pimotionmaster
|
|
||||||
|
|
||||||
import javafx.beans.property.ObjectPropertyBase
|
|
||||||
import javafx.beans.property.Property
|
|
||||||
import javafx.beans.property.ReadOnlyProperty
|
|
||||||
import space.kscience.controls.api.Device
|
|
||||||
import space.kscience.controls.spec.*
|
|
||||||
import space.kscience.dataforge.context.info
|
|
||||||
import space.kscience.dataforge.context.logger
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind a FX property to a device property with a given [spec]
|
|
||||||
*/
|
|
||||||
fun <D : Device, T : Any> D.fxProperty(
|
|
||||||
spec: DevicePropertySpec<D, T>,
|
|
||||||
): ReadOnlyProperty<T> = object : ObjectPropertyBase<T>() {
|
|
||||||
override fun getBean(): Any = this
|
|
||||||
override fun getName(): String = spec.name
|
|
||||||
|
|
||||||
init {
|
|
||||||
//Read incoming changes
|
|
||||||
onPropertyChange(spec) {
|
|
||||||
runLater {
|
|
||||||
try {
|
|
||||||
set(it)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
logger.info { "Failed to set property $name to $it" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <D : Device, T : Any> D.fxProperty(spec: MutableDevicePropertySpec<D, T>): Property<T> =
|
|
||||||
object : ObjectPropertyBase<T>() {
|
|
||||||
override fun getBean(): Any = this
|
|
||||||
override fun getName(): String = spec.name
|
|
||||||
|
|
||||||
init {
|
|
||||||
//Read incoming changes
|
|
||||||
onPropertyChange(spec) {
|
|
||||||
runLater {
|
|
||||||
try {
|
|
||||||
set(it)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
logger.info { "Failed to set property $name to $it" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange { newValue ->
|
|
||||||
if (newValue != null) {
|
|
||||||
writeAsync(spec, newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,41 +18,41 @@ val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
|||||||
@OptIn(InternalAPI::class)
|
@OptIn(InternalAPI::class)
|
||||||
fun Context.launchPiDebugServer(port: Int, axes: List<String>): Job = launch(exceptionHandler) {
|
fun Context.launchPiDebugServer(port: Int, axes: List<String>): Job = launch(exceptionHandler) {
|
||||||
val virtualDevice = PiMotionMasterVirtualDevice(this@launchPiDebugServer, axes)
|
val virtualDevice = PiMotionMasterVirtualDevice(this@launchPiDebugServer, axes)
|
||||||
val server = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().bind("localhost", port)
|
aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().bind("localhost", port).use { server ->
|
||||||
println("Started virtual port server at ${server.localAddress}")
|
println("Started virtual port server at ${server.localAddress}")
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val socket = server.accept()
|
val socket = server.accept()
|
||||||
launch(SupervisorJob(coroutineContext[Job])) {
|
launch(SupervisorJob(coroutineContext[Job])) {
|
||||||
println("Socket accepted: ${socket.remoteAddress}")
|
println("Socket accepted: ${socket.remoteAddress}")
|
||||||
val input = socket.openReadChannel()
|
val input = socket.openReadChannel()
|
||||||
val output = socket.openWriteChannel()
|
val output = socket.openWriteChannel()
|
||||||
|
|
||||||
val sendJob = launch {
|
val sendJob = launch {
|
||||||
virtualDevice.subscribe().collect {
|
virtualDevice.subscribe().collect {
|
||||||
//println("Sending: ${it.decodeToString()}")
|
//println("Sending: ${it.decodeToString()}")
|
||||||
output.writeAvailable(it)
|
output.writeAvailable(it)
|
||||||
output.flush()
|
output.flush()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (isActive) {
|
|
||||||
input.read { buffer ->
|
|
||||||
val array = buffer.moveToByteArray()
|
|
||||||
launch {
|
|
||||||
virtualDevice.send(array)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
|
||||||
e.printStackTrace()
|
|
||||||
sendJob.cancel()
|
|
||||||
socket.close()
|
|
||||||
} finally {
|
|
||||||
println("Socket closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (isActive) {
|
||||||
|
input.read { buffer ->
|
||||||
|
val array = buffer.moveToByteArray()
|
||||||
|
launch {
|
||||||
|
virtualDevice.send(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
sendJob.cancel()
|
||||||
|
socket.close()
|
||||||
|
} finally {
|
||||||
|
println("Client socket closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user