From 977500223d206eb878b52f0ed149a2faeca96433 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 7 Apr 2024 10:07:23 +0300 Subject: [PATCH] Plc4X refactor --- controls-plc4x/build.gradle.kts | 6 +- .../kscience/controls/plc4x/Plc4XDevice.kt | 80 +++++++++++++++---- .../controls/plc4x/Plc4XDeviceBase.kt | 22 +++++ .../kscience/controls/plc4x/Plc4xProperty.kt | 14 +++- demo/constructor/src/jvmMain/kotlin/main.kt | 10 +-- 5 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDeviceBase.kt diff --git a/controls-plc4x/build.gradle.kts b/controls-plc4x/build.gradle.kts index 0836476..e7b063c 100644 --- a/controls-plc4x/build.gradle.kts +++ b/controls-plc4x/build.gradle.kts @@ -11,14 +11,14 @@ description = """ A plugin for Controls-kt device server on top of plc4x library """.trimIndent() -kscience{ +kscience { jvm() - jvmMain{ + jvmMain { api(projects.controlsCore) api("org.apache.plc4x:plc4j-spi:$plc4xVersion") } } -readme{ +readme { maturity = Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDevice.kt b/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDevice.kt index 996d194..9b08538 100644 --- a/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDevice.kt +++ b/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDevice.kt @@ -2,27 +2,75 @@ package space.kscience.controls.plc4x import kotlinx.coroutines.future.await import org.apache.plc4x.java.api.PlcConnection +import org.apache.plc4x.java.api.messages.PlcBrowseItem +import org.apache.plc4x.java.api.messages.PlcTagResponse import org.apache.plc4x.java.api.messages.PlcWriteRequest +import org.apache.plc4x.java.api.messages.PlcWriteResponse +import org.apache.plc4x.java.api.types.PlcResponseCode import space.kscience.controls.api.Device import space.kscience.dataforge.meta.Meta -public interface Plc4XDevice: Device { - public val connection: PlcConnection +private val PlcTagResponse.responseCodes: Map + get() = tagNames.associateWith { getResponseCode(it) } - public suspend fun read(plc4xProperty: Plc4xProperty): Meta = with(plc4xProperty){ - val request = connection.readRequestBuilder().request().build() - val response = request.execute().await() - response.readProperty() - } - - public suspend fun write(plc4xProperty: Plc4xProperty, value: Meta): Unit = with(plc4xProperty){ - val request: PlcWriteRequest = connection.writeRequestBuilder().writeProperty(value).build() - request.execute().await() - } - - public suspend fun subscribe(propertyName: String, plc4xProperty: Plc4xProperty): Unit = with(plc4xProperty){ - - } +private val Map.isOK get() = values.all { it == PlcResponseCode.OK } +public class PlcException(public val codes: Map) : Exception() { + override val message: String + get() = "Plc request unsuccessful:" + codes.entries.joinToString(prefix = "\n\t", separator = "\n\t") { + "${it.key}: ${it.value.name}" + } } +private fun PlcTagResponse.throwOnFail() { + val codes = responseCodes + if (!codes.isOK) throw PlcException(codes) +} + + +public interface Plc4XDevice : Device { + public val connection: PlcConnection +} + + +/** + * Send ping request and suspend until it comes back + */ +public suspend fun Plc4XDevice.ping(): PlcResponseCode = connection.ping().await().responseCode + +/** + * Send browse request to list available tags + */ +public suspend fun Plc4XDevice.browse(): Map> { + require(connection.metadata.isBrowseSupported){"Browse actions are not supported on connection"} + val request = connection.browseRequestBuilder().build() + val response = request.execute().await() + + return response.queryNames.associateWith { response.getValues(it) } +} + +/** + * Send read request and suspend until it returns. Throw a [PlcException] if at least one tag read fails. + * + * @throws PlcException + */ +public suspend fun Plc4XDevice.read(plc4xProperty: Plc4xProperty): Meta = with(plc4xProperty) { + require(connection.metadata.isReadSupported) {"Read actions are not supported on connections"} + val request = connection.readRequestBuilder().request().build() + val response = request.execute().await() + response.throwOnFail() + response.readProperty() +} + + +/** + * Send write request and suspend until it finishes. Throw a [PlcException] if at least one tag write fails. + * + * @throws PlcException + */ +public suspend fun Plc4XDevice.write(plc4xProperty: Plc4xProperty, value: Meta): Unit = with(plc4xProperty) { + require(connection.metadata.isWriteSupported){"Write actions are not supported on connection"} + val request: PlcWriteRequest = connection.writeRequestBuilder().writeProperty(value).build() + val response: PlcWriteResponse = request.execute().await() + response.throwOnFail() +} \ No newline at end of file diff --git a/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDeviceBase.kt b/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDeviceBase.kt new file mode 100644 index 0000000..e25a001 --- /dev/null +++ b/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4XDeviceBase.kt @@ -0,0 +1,22 @@ +package space.kscience.controls.plc4x + +import org.apache.plc4x.java.api.PlcConnection +import space.kscience.controls.spec.DeviceActionSpec +import space.kscience.controls.spec.DeviceBase +import space.kscience.controls.spec.DevicePropertySpec +import space.kscience.dataforge.context.Context +import space.kscience.dataforge.meta.Meta + +public class Plc4XDeviceBase( + context: Context, + meta: Meta, + override val connection: PlcConnection, +) : Plc4XDevice, DeviceBase(context, meta) { + override val properties: Map> + get() = TODO("Not yet implemented") + override val actions: Map> = emptyMap() + + override fun toString(): String { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4xProperty.kt b/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4xProperty.kt index 5b16dec..cfeedd2 100644 --- a/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4xProperty.kt +++ b/controls-plc4x/src/jvmMain/kotlin/space/kscience/controls/plc4x/Plc4xProperty.kt @@ -8,6 +8,8 @@ import space.kscience.dataforge.meta.Meta public interface Plc4xProperty { + public val keys: Set + public fun PlcReadRequest.Builder.request(): PlcReadRequest.Builder public fun PlcReadResponse.readProperty(): Meta @@ -15,17 +17,23 @@ public interface Plc4xProperty { public fun PlcWriteRequest.Builder.writeProperty(meta: Meta): PlcWriteRequest.Builder } -public class DefaultPlc4xProperty( +private class DefaultPlc4xProperty( private val address: String, private val plcValueType: PlcValueType, private val name: String = "@default", ) : Plc4xProperty { + override val keys: Set = setOf(name) + override fun PlcReadRequest.Builder.request(): PlcReadRequest.Builder = addTagAddress(name, address) + override fun PlcReadResponse.readProperty(): Meta = - asPlcValue.toMeta() + getPlcValue(name).toMeta() override fun PlcWriteRequest.Builder.writeProperty(meta: Meta): PlcWriteRequest.Builder = addTagAddress(name, address, meta.toPlcValue(plcValueType)) -} \ No newline at end of file +} + +public fun Plc4xProperty(address: String, plcValueType: PlcValueType, name: String = "@default"): Plc4xProperty = + DefaultPlc4xProperty(address, plcValueType, name) \ No newline at end of file diff --git a/demo/constructor/src/jvmMain/kotlin/main.kt b/demo/constructor/src/jvmMain/kotlin/main.kt index 26ca10a..9b06d2e 100644 --- a/demo/constructor/src/jvmMain/kotlin/main.kt +++ b/demo/constructor/src/jvmMain/kotlin/main.kt @@ -84,24 +84,24 @@ private fun Context.launchPidDevice( showDashboard { plot { - plotNumberState(context, state, maxAge = maxAge) { + plotNumberState(context, state, maxAge = maxAge, sampling = 50.milliseconds) { name = "real position" } - plotDeviceProperty(device.pid, Regulator.position.name, maxAge = maxAge) { + plotDeviceProperty(device.pid, Regulator.position.name, maxAge = maxAge, sampling = 50.milliseconds) { name = "read position" } - plotDeviceProperty(device.pid, Regulator.target.name, maxAge = maxAge) { + plotDeviceProperty(device.pid, Regulator.target.name, maxAge = maxAge, sampling = 50.milliseconds) { name = "target" } } plot { - plotDeviceProperty(device.start, LimitSwitch.locked.name, maxAge = maxAge) { + plotDeviceProperty(device.start, LimitSwitch.locked.name, maxAge = maxAge, sampling = 50.milliseconds) { name = "start measured" mode = ScatterMode.markers } - plotDeviceProperty(device.end, LimitSwitch.locked.name, maxAge = maxAge) { + plotDeviceProperty(device.end, LimitSwitch.locked.name, maxAge = maxAge, sampling = 50.milliseconds) { name = "end measured" mode = ScatterMode.markers }