Add opcua test

This commit is contained in:
Alexander Nozik 2023-04-16 12:29:24 +03:00
parent 197675fc15
commit e7cfb1d2ba
4 changed files with 145 additions and 56 deletions

View File

@ -4,14 +4,16 @@ plugins {
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
val miloVersion: String = "0.6.7" val miloVersion: String = "0.6.9"
dependencies { dependencies {
api(project(":controls-core")) api(projects.controlsCore)
api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${space.kscience.gradle.KScienceVersions.coroutinesVersion}") api(spclibs.kotlinx.coroutines.jdk8)
api("org.eclipse.milo:sdk-client:$miloVersion") api("org.eclipse.milo:sdk-client:$miloVersion")
api("org.eclipse.milo:bsd-parser:$miloVersion") api("org.eclipse.milo:bsd-parser:$miloVersion")
api("org.eclipse.milo:sdk-server:$miloVersion") api("org.eclipse.milo:sdk-server:$miloVersion")
testImplementation(spclibs.kotlinx.coroutines.test)
} }

View File

@ -1,6 +1,8 @@
package space.kscience.controls.opcua.client package space.kscience.controls.opcua.client
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.eclipse.milo.opcua.sdk.client.OpcUaClient import org.eclipse.milo.opcua.sdk.client.OpcUaClient
import org.eclipse.milo.opcua.stack.core.types.builtin.* import org.eclipse.milo.opcua.stack.core.types.builtin.*
@ -9,6 +11,8 @@ import space.kscience.controls.api.Device
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.transformations.MetaConverter import space.kscience.dataforge.meta.transformations.MetaConverter
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/** /**
@ -19,11 +23,6 @@ public interface MiloDevice : Device {
* The OPC-UA client initialized on first use * The OPC-UA client initialized on first use
*/ */
public val client: OpcUaClient public val client: OpcUaClient
override fun close() {
client.disconnect()
super.close()
}
} }
/** /**
@ -81,3 +80,47 @@ public suspend inline fun <reified T> MiloDevice.writeOpc(
val meta = converter.objectToMeta(value) val meta = converter.objectToMeta(value)
return client.writeValue(nodeId, DataValue(Variant(meta))).await() return client.writeValue(nodeId, DataValue(Variant(meta))).await()
} }
/**
* A device-bound OPC-UA property. Does not trigger device properties change.
*/
public inline fun <reified T> MiloDevice.opc(
nodeId: NodeId,
converter: MetaConverter<T>,
magAge: Double = 500.0
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking {
readOpc(nodeId, converter, magAge)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
launch {
writeOpc(nodeId, converter, value)
}
}
}
/**
* Register a mutable OPC-UA based [Double] property in a device spec
*/
public fun MiloDevice.opcDouble(
nodeId: NodeId,
magAge: Double = 1.0
): ReadWriteProperty<Any?, Double> = opc<Double>(nodeId, MetaConverter.double, magAge)
/**
* Register a mutable OPC-UA based [Int] property in a device spec
*/
public fun MiloDevice.opcInt(
nodeId: NodeId,
magAge: Double = 1.0
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
/**
* Register a mutable OPC-UA based [String] property in a device spec
*/
public fun MiloDevice.opcString(
nodeId: NodeId,
magAge: Double = 1.0
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)

View File

@ -1,69 +1,66 @@
package space.kscience.controls.opcua.client package space.kscience.controls.opcua.client
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.eclipse.milo.opcua.sdk.client.OpcUaClient import org.eclipse.milo.opcua.sdk.client.OpcUaClient
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy
import space.kscience.controls.spec.DeviceBySpec import space.kscience.controls.spec.DeviceBySpec
import space.kscience.controls.spec.DeviceSpec import space.kscience.controls.spec.DeviceSpec
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.specOrNull
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.meta.transformations.MetaConverter
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
public sealed class MiloIdentity: Scheme()
public class MiloUsername : MiloIdentity() {
public var username: String by string{ error("Username not defined") }
public var password: String by string{ error("Password not defined") }
public companion object : SchemeSpec<MiloUsername>(::MiloUsername)
}
//public class MiloKeyPair : MiloIdentity() {
//
// public companion object : SchemeSpec<MiloUsername>(::MiloUsername)
//}
public class MiloConfiguration : Scheme() {
public var endpointUrl: String by string { error("Endpoint url is not defined") }
public var username: MiloUsername? by specOrNull(MiloUsername)
public companion object : SchemeSpec<MiloConfiguration>(::MiloConfiguration)
}
/**
* A variant of [DeviceBySpec] that includes OPC-UA client
*/
public open class MiloDeviceBySpec<D : MiloDeviceBySpec<D>>( public open class MiloDeviceBySpec<D : MiloDeviceBySpec<D>>(
spec: DeviceSpec<D>, spec: DeviceSpec<D>,
config: MiloConfiguration,
context: Context = Global, context: Context = Global,
meta: Meta = Meta.EMPTY ) : MiloDevice, DeviceBySpec<D>(spec, context, config.meta) {
) : MiloDevice, DeviceBySpec<D>(spec, context, meta) {
override val client: OpcUaClient by lazy { override val client: OpcUaClient by lazy {
val endpointUrl = meta["endpointUrl"].string ?: error("Endpoint url is not defined") context.createMiloClient(
context.createMiloClient(endpointUrl).apply { config.endpointUrl,
securityPolicy = SecurityPolicy.None,
identityProvider = config.username?.let {
UsernameProvider(it.username,it.password)
} ?: AnonymousProvider()
).apply {
connect().get() connect().get()
} }
} }
override fun close() { override fun close() {
super<MiloDevice>.close() client.disconnect()
super<DeviceBySpec>.close() super<DeviceBySpec>.close()
} }
} }
/**
* A device-bound OPC-UA property. Does not trigger device properties change.
*/
public inline fun <reified T> MiloDeviceBySpec<*>.opc(
nodeId: NodeId,
converter: MetaConverter<T>,
magAge: Double = 500.0
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = runBlocking {
readOpc(nodeId, converter, magAge)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
launch {
writeOpc(nodeId, converter, value)
}
}
}
public inline fun <reified T> MiloDeviceBySpec<*>.opcDouble(
nodeId: NodeId,
magAge: Double = 1.0
): ReadWriteProperty<Any?, Double> = opc(nodeId, MetaConverter.double, magAge)
public inline fun <reified T> MiloDeviceBySpec<*>.opcInt(
nodeId: NodeId,
magAge: Double = 1.0
): ReadWriteProperty<Any?, Int> = opc(nodeId, MetaConverter.int, magAge)
public inline fun <reified T> MiloDeviceBySpec<*>.opcString(
nodeId: NodeId,
magAge: Double = 1.0
): ReadWriteProperty<Any?, String> = opc(nodeId, MetaConverter.string, magAge)

View File

@ -0,0 +1,47 @@
package space.kscience.controls.opcua.client
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId
import org.junit.jupiter.api.Test
import space.kscience.controls.opcua.client.OpcUaClientTest.DemoMiloDevice.Companion.randomDouble
import space.kscience.controls.spec.DeviceSpec
import space.kscience.controls.spec.doubleProperty
import space.kscience.dataforge.meta.transformations.MetaConverter
class OpcUaClientTest {
class DemoMiloDevice(config: MiloConfiguration) : MiloDeviceBySpec<DemoMiloDevice>(DemoMiloDevice, config) {
//val randomDouble by opcDouble(NodeId(2, "Dynamic/RandomDouble"))
suspend fun readRandomDouble() = readOpc(NodeId(2, "Dynamic/RandomDouble"), MetaConverter.double)
companion object : DeviceSpec<DemoMiloDevice>() {
fun build(): DemoMiloDevice {
val config = MiloConfiguration {
endpointUrl = "opc.tcp://milo.digitalpetri.com:62541/milo"
// username = MiloUsername{
// username = "user1"
// password = "password"
// }
}
return DemoMiloDevice(config)
}
inline fun <R> use(block: DemoMiloDevice.() -> R): R = build().use(block)
val randomDouble by doubleProperty(read = DemoMiloDevice::readRandomDouble)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testReadDouble() = runTest {
println(DemoMiloDevice.use { randomDouble.read() })
}
}