Add opcua test
This commit is contained in:
parent
197675fc15
commit
e7cfb1d2ba
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,4 +79,48 @@ public suspend inline fun <reified T> MiloDevice.writeOpc(
|
|||||||
): StatusCode {
|
): StatusCode {
|
||||||
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)
|
@ -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)
|
|
@ -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() })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user