From 7579ddfad4ed2605551793d070c01c0345a6ab33 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 28 Dec 2023 22:40:58 +0300 Subject: [PATCH] Quick fix for OPC us server --- .../controls/opcua/client/MetaBsdParser.kt | 18 +++-- .../controls/opcua/server/DeviceNameSpace.kt | 79 ++++++++++--------- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt index 27330bb..48aa6dc 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/client/MetaBsdParser.kt @@ -56,6 +56,7 @@ internal class MetaEnumCodec : OpcUaBinaryDataTypeCodec { internal fun opcToMeta(value: Any?): Meta = when (value) { null -> Meta(Null) + is Variant -> opcToMeta(value.value) is Meta -> value is Value -> Meta(value) is Number -> when (value) { @@ -79,12 +80,17 @@ internal fun opcToMeta(value: Any?): Meta = when (value) { "text" put value.text?.asValue() } is DataValue -> Meta { - "value" put opcToMeta(value.value) // need SerializationContext to do that properly - value.statusCode?.value?.let { "status" put Meta(it.asValue()) } - value.sourceTime?.javaInstant?.let { "sourceTime" put it.toKotlinInstant().toMeta() } - value.sourcePicoseconds?.let { "sourcePicoseconds" put Meta(it.asValue()) } - value.serverTime?.javaInstant?.let { "serverTime" put it.toKotlinInstant().toMeta() } - value.serverPicoseconds?.let { "serverPicoseconds" put Meta(it.asValue()) } + val variant= opcToMeta(value.value) + update(variant)// need SerializationContext to do that properly + //TODO remove after DF 0.7.2 + this.value = variant.value + "@opc" put { + value.statusCode?.value?.let { "status" put Meta(it.asValue()) } + value.sourceTime?.javaInstant?.let { "sourceTime" put it.toKotlinInstant().toMeta() } + value.sourcePicoseconds?.let { "sourcePicoseconds" put Meta(it.asValue()) } + value.serverTime?.javaInstant?.let { "serverTime" put it.toKotlinInstant().toMeta() } + value.serverPicoseconds?.let { "serverPicoseconds" put Meta(it.asValue()) } + } } is ByteString -> Meta(value.bytesOrEmpty().asValue()) is XmlElement -> Meta(value.fragment?.asValue() ?: Null) diff --git a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt index 05fc3c6..057eb2e 100644 --- a/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt +++ b/controls-opcua/src/main/kotlin/space/kscience/controls/opcua/server/DeviceNameSpace.kt @@ -2,7 +2,6 @@ package space.kscience.controls.opcua.server import kotlinx.coroutines.launch import kotlinx.datetime.toJavaInstant -import kotlinx.serialization.json.Json import org.eclipse.milo.opcua.sdk.core.AccessLevel import org.eclipse.milo.opcua.sdk.core.Reference import org.eclipse.milo.opcua.sdk.server.Lifecycle @@ -21,14 +20,15 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText import space.kscience.controls.api.* import space.kscience.controls.manager.DeviceManager +import space.kscience.controls.opcua.client.opcToMeta import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.ValueType import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus -public operator fun CachingDevice.get(propertyDescriptor: PropertyDescriptor): Meta? = getProperty(propertyDescriptor.name) +public operator fun CachingDevice.get(propertyDescriptor: PropertyDescriptor): Meta? = + getProperty(propertyDescriptor.name) public suspend fun Device.read(propertyDescriptor: PropertyDescriptor): Meta = readProperty(propertyDescriptor.name) @@ -38,29 +38,11 @@ https://github.com/eclipse/milo/blob/master/milo-examples/server-examples/src/ma public class DeviceNameSpace( server: OpcUaServer, - public val deviceManager: DeviceManager + public val deviceManager: DeviceManager, ) : ManagedNamespaceWithLifecycle(server, NAMESPACE_URI) { private val subscription = SubscriptionModel(server, this) - init { - lifecycleManager.addLifecycle(subscription) - - lifecycleManager.addStartupTask { - nodeContext.registerHub(deviceManager, Name.EMPTY) - } - - lifecycleManager.addLifecycle(object : Lifecycle { - override fun startup() { - server.addressSpaceManager.register(this@DeviceNameSpace) - } - - override fun shutdown() { - server.addressSpaceManager.unregister(this@DeviceNameSpace) - } - }) - } - private fun UaFolderNode.registerDeviceNodes(deviceName: Name, device: Device) { val nodes = device.propertyDescriptors.associate { descriptor -> val propertyName = descriptor.name @@ -74,14 +56,17 @@ public class DeviceNameSpace( setAccessLevel(AccessLevel.READ_WRITE) setUserAccessLevel(AccessLevel.READ_WRITE) } + descriptor.mutable -> { setAccessLevel(AccessLevel.WRITE_ONLY) setUserAccessLevel(AccessLevel.WRITE_ONLY) } + descriptor.readable -> { setAccessLevel(AccessLevel.READ_ONLY) setUserAccessLevel(AccessLevel.READ_ONLY) } + else -> { setAccessLevel(AccessLevel.NONE) setUserAccessLevel(AccessLevel.NONE) @@ -104,26 +89,23 @@ public class DeviceNameSpace( }.build() // Update initial value, but only if it is cached - if(device is CachingDevice) { + if (device is CachingDevice) { device[descriptor]?.toOpc(sourceTime = null, serverTime = null)?.let { node.value = it } } - /** - * Subscribe to node value changes - */ - node.addAttributeObserver { _: UaNode, attributeId: AttributeId, value: Any -> - if (attributeId == AttributeId.Value) { - val meta: Meta = when (value) { - is Meta -> value - is Boolean -> Meta(value) - is Number -> Meta(value) - is String -> Json.decodeFromString(MetaSerializer, value) - else -> return@addAttributeObserver //TODO("other types not implemented") - } - deviceManager.context.launch { - device.writeProperty(propertyName, meta) + if (descriptor.mutable) { + + /** + * Subscribe to node value changes + */ + node.addAttributeObserver { _: UaNode, attributeId: AttributeId, value: Any? -> + if (attributeId == AttributeId.Value) { + val meta: Meta = opcToMeta(value) + deviceManager.context.launch { + device.writeProperty(propertyName, meta) + } } } } @@ -137,7 +119,10 @@ public class DeviceNameSpace( device.onPropertyChange { nodes[property]?.let { node -> val sourceTime = time?.let { DateTime(it.toJavaInstant()) } - node.value = value.toOpc(sourceTime = sourceTime) + val newValue = value.toOpc(sourceTime = sourceTime) + if (node.value.value != newValue.value) { + node.value = newValue + } } } //recursively add sub-devices @@ -168,6 +153,24 @@ public class DeviceNameSpace( } } + init { + lifecycleManager.addLifecycle(subscription) + + lifecycleManager.addStartupTask { + nodeContext.registerHub(deviceManager, Name.EMPTY) + } + + lifecycleManager.addLifecycle(object : Lifecycle { + override fun startup() { + server.addressSpaceManager.register(this@DeviceNameSpace) + } + + override fun shutdown() { + server.addressSpaceManager.unregister(this@DeviceNameSpace) + } + }) + } + override fun onDataItemsCreated(dataItems: List?) { subscription.onDataItemsCreated(dataItems) }