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 miloVersion: String = "0.6.7"
val miloVersion: String = "0.6.9"
dependencies {
api(project(":controls-core"))
api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${space.kscience.gradle.KScienceVersions.coroutinesVersion}")
api(projects.controlsCore)
api(spclibs.kotlinx.coroutines.jdk8)
api("org.eclipse.milo:sdk-client:$miloVersion")
api("org.eclipse.milo:bsd-parser:$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
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.eclipse.milo.opcua.sdk.client.OpcUaClient
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.MetaSerializer
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
*/
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)
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
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
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.DeviceSpec
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.specOrNull
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>>(
spec: DeviceSpec<D>,
config: MiloConfiguration,
context: Context = Global,
meta: Meta = Meta.EMPTY
) : MiloDevice, DeviceBySpec<D>(spec, context, meta) {
) : MiloDevice, DeviceBySpec<D>(spec, context, config.meta) {
override val client: OpcUaClient by lazy {
val endpointUrl = meta["endpointUrl"].string ?: error("Endpoint url is not defined")
context.createMiloClient(endpointUrl).apply {
context.createMiloClient(
config.endpointUrl,
securityPolicy = SecurityPolicy.None,
identityProvider = config.username?.let {
UsernameProvider(it.username,it.password)
} ?: AnonymousProvider()
).apply {
connect().get()
}
}
override fun close() {
super<MiloDevice>.close()
client.disconnect()
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() })
}
}