From 24b6856f153a81ecef7ab9559707da129bb6e479 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 11 May 2024 17:56:28 +0300 Subject: [PATCH] Move all-things to Compose --- build.gradle.kts | 1 - .../constructor/library/LimitSwitch.kt | 2 +- demo/all-things/build.gradle.kts | 51 ++-- .../controls/demo/DemoControllerView.kt | 225 +++++++++--------- .../controls/demo/generateMessageSchema.kt | 10 - demo/motors/build.gradle.kts | 17 +- settings.gradle.kts | 1 - 7 files changed, 146 insertions(+), 161 deletions(-) delete mode 100644 demo/all-things/src/main/kotlin/space/kscience/controls/demo/generateMessageSchema.kt diff --git a/build.gradle.kts b/build.gradle.kts index aed37b5..38d5c6a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,6 @@ import space.kscience.gradle.useSPCTeam plugins { id("space.kscience.gradle.project") - alias(libs.plugins.versions) } allprojects { diff --git a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt index 895cee8..af6436f 100644 --- a/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt +++ b/controls-constructor/src/commonMain/kotlin/space/kscience/controls/constructor/library/LimitSwitch.kt @@ -35,7 +35,7 @@ public class VirtualLimitSwitch( public val lockedState: DeviceState, ) : DeviceBySpec(LimitSwitch, context), LimitSwitch { - init { + override suspend fun onStart() { lockedState.valueFlow.onEach { propertyChanged(LimitSwitch.locked, it) }.launchIn(this) diff --git a/demo/all-things/build.gradle.kts b/demo/all-things/build.gradle.kts index b6074ed..2abde80 100644 --- a/demo/all-things/build.gradle.kts +++ b/demo/all-things/build.gradle.kts @@ -1,7 +1,6 @@ plugins { kotlin("jvm") - id("org.openjfx.javafxplugin") version "0.0.13" - application + alias(spclibs.plugins.compose) } @@ -20,28 +19,46 @@ dependencies { implementation(projects.controlsOpcua) implementation(spclibs.ktor.client.cio) - implementation(libs.tornadofx) implementation(libs.plotlykt.server) // implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") + + implementation(compose.runtime) + implementation(compose.desktop.currentOs) + implementation(compose.material3) +// implementation("org.pushing-pixels:aurora-window:1.3.0") +// implementation("org.pushing-pixels:aurora-component:1.3.0") +// implementation("org.pushing-pixels:aurora-theming:1.3.0") + implementation(spclibs.logback.classic) } kotlin{ - jvmToolchain(11) -} - - -tasks.withType().configureEach { - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn") + jvmToolchain(17) + compilerOptions { + freeCompilerArgs.addAll("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn") } } -javafx { - version = "17" - modules("javafx.controls") +compose{ + desktop{ + application{ + mainClass = "space.kscience.controls.demo.DemoControllerViewKt" + } + } } - -application { - mainClass.set("space.kscience.controls.demo.DemoControllerViewKt") -} \ No newline at end of file +// +// +//tasks.withType().configureEach { +// kotlinOptions { +// freeCompilerArgs = freeCompilerArgs + listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn") +// } +//} +// +//javafx { +// version = "17" +// modules("javafx.controls") +//} +// +//application { +// mainClass.set("space.kscience.controls.demo.DemoControllerViewKt") +//} \ No newline at end of file diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt index f341256..cc9d445 100644 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt +++ b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoControllerView.kt @@ -1,14 +1,19 @@ package space.kscience.controls.demo +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +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 io.ktor.server.engine.ApplicationEngine -import javafx.scene.Parent -import javafx.scene.control.Slider -import javafx.scene.layout.Priority -import javafx.stage.Stage +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json import org.eclipse.milo.opcua.sdk.server.OpcUaServer import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText @@ -17,9 +22,6 @@ import space.kscience.controls.api.GetDescriptionMessage import space.kscience.controls.api.PropertyChangedMessage import space.kscience.controls.client.launchMagixService import space.kscience.controls.client.magixFormat -import space.kscience.controls.demo.DemoDevice.Companion.cosScale -import space.kscience.controls.demo.DemoDevice.Companion.sinScale -import space.kscience.controls.demo.DemoDevice.Companion.timeScale import space.kscience.controls.manager.DeviceManager import space.kscience.controls.manager.install import space.kscience.controls.opcua.server.OpcUaServer @@ -35,16 +37,15 @@ import space.kscience.magix.rsocket.rSocketWithWebSockets import space.kscience.magix.server.RSocketMagixFlowPlugin import space.kscience.magix.server.startMagixServer import space.kscince.magix.zmq.ZmqMagixFlowPlugin -import tornadofx.* import java.awt.Desktop import java.net.URI -class DemoController : Controller(), ContextAware { +class DemoController : ContextAware { var device: DemoDevice? = null var magixServer: ApplicationEngine? = null var visualizer: ApplicationEngine? = null - var opcUaServer: OpcUaServer = OpcUaServer { + val opcUaServer: OpcUaServer = OpcUaServer { setApplicationName(LocalizedText.english("space.kscience.controls.opcua")) endpoint { @@ -60,39 +61,37 @@ class DemoController : Controller(), ContextAware { private val deviceManager = context.request(DeviceManager) - fun init() { - context.launch { - device = deviceManager.install("demo", DemoDevice) - //starting magix event loop - magixServer = startMagixServer( - RSocketMagixFlowPlugin(), //TCP rsocket support - ZmqMagixFlowPlugin() //ZMQ support - ) - //Launch a device client and connect it to the server - val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost") - deviceManager.launchMagixService(deviceEndpoint) - //connect visualization to a magix endpoint - val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost") - visualizer = startDemoDeviceServer(visualEndpoint) + fun start(): Job = context.launch { + device = deviceManager.install("demo", DemoDevice) + //starting magix event loop + magixServer = startMagixServer( + RSocketMagixFlowPlugin(), //TCP rsocket support + ZmqMagixFlowPlugin() //ZMQ support + ) + //Launch a device client and connect it to the server + val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost") + deviceManager.launchMagixService(deviceEndpoint) + //connect visualization to a magix endpoint + val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost") + visualizer = startDemoDeviceServer(visualEndpoint) - //serve devices as OPC-UA namespace - opcUaServer.startup() - opcUaServer.serveDevices(deviceManager) + //serve devices as OPC-UA namespace + opcUaServer.startup() + opcUaServer.serveDevices(deviceManager) - val listenerEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost") - listenerEndpoint.subscribe(DeviceManager.magixFormat).onEach { (_, deviceMessage)-> - // print all messages that are not property change message - if(deviceMessage !is PropertyChangedMessage){ - println(">> ${Json.encodeToString(DeviceMessage.serializer(), deviceMessage)}") - } - }.launchIn(this) - listenerEndpoint.send(DeviceManager.magixFormat, GetDescriptionMessage(), "listener", "controls-kt") + val listenerEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost") + listenerEndpoint.subscribe(DeviceManager.magixFormat).onEach { (_, deviceMessage) -> + // print all messages that are not property change message + if (deviceMessage !is PropertyChangedMessage) { + println(">> ${Json.encodeToString(DeviceMessage.serializer(), deviceMessage)}") + } + }.launchIn(this) + listenerEndpoint.send(DeviceManager.magixFormat, GetDescriptionMessage(), "listener", "controls-kt") - } } - suspend fun shutdown() { + fun shutdown(): Job = context.launch { logger.info { "Shutting down..." } opcUaServer.shutdown() logger.info { "OpcUa server stopped" } @@ -102,92 +101,82 @@ class DemoController : Controller(), ContextAware { logger.info { "Magix server stopped" } device?.stop() logger.info { "Device server stopped" } - context.close() + } +} + +@Composable +fun DemoControls(controller: DemoController) { + var timeScale by remember { mutableStateOf(5000f) } + var xScale by remember { mutableStateOf(1f) } + var yScale by remember { mutableStateOf(1f) } + + Surface(Modifier.padding(5.dp)) { + Column { + Row(Modifier.fillMaxWidth()) { + Text("Time Scale", modifier = Modifier.align(Alignment.CenterVertically).width(100.dp)) + TextField(String.format("%.2f", timeScale),{}, enabled = false, modifier = Modifier.align(Alignment.CenterVertically).width(100.dp)) + Slider(timeScale, onValueChange = { timeScale = it }, steps = 20, valueRange = 1000f..5000f) + } + Row(Modifier.fillMaxWidth()) { + Text("X scale", modifier = Modifier.align(Alignment.CenterVertically).width(100.dp)) + TextField(String.format("%.2f", xScale),{}, enabled = false, modifier = Modifier.align(Alignment.CenterVertically).width(100.dp)) + Slider(xScale, onValueChange = { xScale = it }, steps = 20, valueRange = 0.1f..2.0f) + } + Row(Modifier.fillMaxWidth()) { + Text("Y scale", modifier = Modifier.align(Alignment.CenterVertically).width(100.dp)) + TextField(String.format("%.2f", yScale),{}, enabled = false, modifier = Modifier.align(Alignment.CenterVertically).width(100.dp)) + Slider(yScale, onValueChange = { yScale = it }, steps = 20, valueRange = 0.1f..2.0f) + } + Row(Modifier.fillMaxWidth()) { + Button( + onClick = { + controller.device?.run { + launch { + write(DemoDevice.timeScale, timeScale.toDouble()) + write(DemoDevice.sinScale, xScale.toDouble()) + write(DemoDevice.cosScale, yScale.toDouble()) + } + } + }, + Modifier.fillMaxWidth() + ) { + Text("Submit") + } + } + Row(Modifier.fillMaxWidth()) { + Button( + onClick = { + controller.visualizer?.run { + val host = "localhost"//environment.connectors.first().host + val port = environment.connectors.first().port + val uri = URI("http", null, host, port, "/", null, null) + Desktop.getDesktop().browse(uri) + } + }, + Modifier.fillMaxWidth() + ) { + Text("Show plots") + } + } + } + } } -class DemoControllerView : View(title = " Demo controller remote") { - private val controller: DemoController by inject() - private var timeScaleSlider: Slider by singleAssign() - private var xScaleSlider: Slider by singleAssign() - private var yScaleSlider: Slider by singleAssign() +fun main() = application { + val controller = remember { DemoController().apply { start() } } - override val root: Parent = vbox { - hbox { - label("Time scale") - pane { - hgrow = Priority.ALWAYS - } - timeScaleSlider = slider(1000..10000, 5000) { - isShowTickLabels = true - isShowTickMarks = true - } - } - hbox { - label("X scale") - pane { - hgrow = Priority.ALWAYS - } - xScaleSlider = slider(0.1..2.0, 1.0) { - isShowTickLabels = true - isShowTickMarks = true - } - } - hbox { - label("Y scale") - pane { - hgrow = Priority.ALWAYS - } - yScaleSlider = slider(0.1..2.0, 1.0) { - isShowTickLabels = true - isShowTickMarks = true - } - } - button("Submit") { - useMaxWidth = true - action { - controller.device?.run { - launch { - write(timeScale, timeScaleSlider.value) - write(sinScale, xScaleSlider.value) - write(cosScale, yScaleSlider.value) - } - } - } - } - button("Show plots") { - useMaxWidth = true - action { - controller.visualizer?.run { - val host = "localhost"//environment.connectors.first().host - val port = environment.connectors.first().port - val uri = URI("http", null, host, port, "/", null, null) - Desktop.getDesktop().browse(uri) - } - } - } - } -} - - -class DemoControllerApp : App(DemoControllerView::class) { - private val controller: DemoController by inject() - - override fun start(stage: Stage) { - super.start(stage) - controller.init() - } - - override fun stop() { - runBlocking { + Window( + title = "All things control", + onCloseRequest = { controller.shutdown() + exitApplication() + }, + state = rememberWindowState(width = 400.dp, height = 300.dp) + ) { + MaterialTheme { + DemoControls(controller) } - super.stop() } -} - - -fun main() { - launch() } \ No newline at end of file diff --git a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/generateMessageSchema.kt b/demo/all-things/src/main/kotlin/space/kscience/controls/demo/generateMessageSchema.kt deleted file mode 100644 index d50ec2c..0000000 --- a/demo/all-things/src/main/kotlin/space/kscience/controls/demo/generateMessageSchema.kt +++ /dev/null @@ -1,10 +0,0 @@ -package space.kscience.controls.demo - -//import com.github.ricky12awesome.jss.encodeToSchema -//import com.github.ricky12awesome.jss.globalJson -//import space.kscience.controls.api.DeviceMessage - -//fun main() { -// val schema = globalJson.encodeToSchema(DeviceMessage.serializer(), generateDefinitions = false) -// println(schema) -//} \ No newline at end of file diff --git a/demo/motors/build.gradle.kts b/demo/motors/build.gradle.kts index dd64935..faca2b0 100644 --- a/demo/motors/build.gradle.kts +++ b/demo/motors/build.gradle.kts @@ -1,19 +1,11 @@ plugins { id("space.kscience.gradle.jvm") - application - id("org.openjfx.javafxplugin") + alias(spclibs.plugins.compose) } -//TODO to be moved to a separate project - -javafx { - version = "17" - modules = listOf("javafx.controls") -} - -application{ - mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt") -} +//application{ +// mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt") +//} kotlin{ explicitApi = null @@ -25,5 +17,4 @@ val dataforgeVersion: String by extra dependencies { implementation(project(":controls-ports-ktor")) implementation(projects.controlsMagix) - implementation(libs.tornadofx) } diff --git a/settings.gradle.kts b/settings.gradle.kts index ed33e53..39adc72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,7 +18,6 @@ pluginManagement { id("space.kscience.gradle.mpp") version toolsVersion id("space.kscience.gradle.jvm") version toolsVersion id("space.kscience.gradle.js") version toolsVersion - id("org.openjfx.javafxplugin") version "0.0.13" } }