11package space.kscience.controls.opcua.server
22
3+ import kotlinx.coroutines.CoroutineScope
34import kotlinx.coroutines.launch
45import kotlinx.datetime.toJavaInstant
56import org.eclipse.milo.opcua.sdk.core.AccessLevel
@@ -21,11 +22,8 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText
2122import space.kscience.controls.api.*
2223import space.kscience.controls.manager.DeviceManager
2324import space.kscience.controls.opcua.client.opcToMeta
24- import space.kscience.dataforge.context.Context
2525import space.kscience.dataforge.meta.Meta
2626import space.kscience.dataforge.meta.ValueType
27- import space.kscience.dataforge.names.Name
28- import space.kscience.dataforge.names.plus
2927
3028
3129public operator fun CachingDevice.get (propertyDescriptor : PropertyDescriptor ): Meta ? =
@@ -38,21 +36,24 @@ https://github.com/eclipse/milo/blob/master/milo-examples/server-examples/src/ma
3836 */
3937
4038public class DeviceNameSpace (
41- private val context : Context ,
39+ private val scope : CoroutineScope ,
4240 server : OpcUaServer ,
4341 public val deviceHub : DeviceHub
4442) : ManagedNamespaceWithLifecycle(server, NAMESPACE_URI ) {
4543
4644 private val subscription = SubscriptionModel (server, this )
4745
48- private fun UaFolderNode.registerDeviceNodes (deviceName : Name , device : Device ) {
46+ /* *
47+ * Register device node within existing folder
48+ */
49+ private fun UaFolderNode.registerDeviceNodes (deviceName : String , device : Device ) {
4950 val nodes = device.propertyDescriptors.associate { descriptor ->
5051 val propertyName = descriptor.name
5152
5253
5354 val node: UaVariableNode = UaVariableNode .UaVariableNodeBuilder (nodeContext).apply {
5455 // for now, use DF paths as ids
55- nodeId = newNodeId(" ${ deviceName.tokens.joinToString( " / " )} /$propertyName " )
56+ nodeId = newNodeId(" $deviceName /$propertyName " )
5657 when {
5758 descriptor.readable && descriptor.mutable -> {
5859 setAccessLevel(AccessLevel .READ_WRITE )
@@ -90,7 +91,7 @@ public class DeviceNameSpace(
9091 setTypeDefinition(Identifiers .BaseDataVariableType )
9192 }.build()
9293
93- // Update initial value, but only if it is cached
94+ // Update the initial value, but only if it is cached
9495 if (device is CachingDevice ) {
9596 device[descriptor]?.toOpc(sourceTime = null , serverTime = null )?.let {
9697 node.value = it
@@ -105,7 +106,7 @@ public class DeviceNameSpace(
105106 node.addAttributeObserver { _: UaNode , attributeId: AttributeId , value: Any? ->
106107 if (attributeId == AttributeId .Value ) {
107108 val meta: Meta = opcToMeta(value)
108- context .launch {
109+ scope .launch {
109110 device.writeProperty(propertyName, meta)
110111 }
111112 }
@@ -127,39 +128,70 @@ public class DeviceNameSpace(
127128 }
128129 }
129130 }
131+
130132 // recursively add sub-devices
131133 if (device is DeviceHub ) {
132- nodeContext.registerHub(device, deviceName)
134+ device.devices.forEach { (childDeviceName, device) ->
135+
136+ val deviceFolder = UaFolderNode (
137+ nodeContext,
138+ newNodeId(" $deviceName /$childDeviceName " ),
139+ newQualifiedName(" $deviceName /$childDeviceName " ),
140+ LocalizedText .english(childDeviceName.toString())
141+ )
142+
143+ deviceFolder.registerDeviceNodes(" $deviceName /$childDeviceName " , device)
144+
145+ nodeManager.addNode(deviceFolder)
146+ addOrganizes(deviceFolder)
147+ }
133148 }
134149 }
135150
136- private fun UaNodeContext.registerHub (hub : DeviceHub , namePrefix : Name ) {
151+ private fun UaNodeContext.registerTopLevelHub (hub : DeviceHub ) {
152+ val rootNode = UaFolderNode (
153+ nodeContext,
154+ newNodeId(" Controls" ),
155+ newQualifiedName(" Controls" ),
156+ LocalizedText .english(" Controls" )
157+ )
158+
137159 hub.devices.forEach { (deviceName, device) ->
138- val tokenAsString = deviceName.toString()
160+ val nameAsString = " $deviceName "
161+
139162 val deviceFolder = UaFolderNode (
140- this ,
141- newNodeId(tokenAsString),
142- newQualifiedName(tokenAsString),
143- LocalizedText .english(tokenAsString)
144- )
145- deviceFolder.addReference(
146- Reference (
147- deviceFolder.nodeId,
148- Identifiers .Organizes ,
149- Identifiers .ObjectsFolder .expanded(),
150- false
151- )
163+ nodeContext,
164+ newNodeId(nameAsString),
165+ newQualifiedName(nameAsString),
166+ LocalizedText .english(nameAsString)
152167 )
153- deviceFolder.registerDeviceNodes(namePrefix + deviceName, device)
154- this .nodeManager.addNode(deviceFolder)
168+
169+ deviceFolder.registerDeviceNodes(deviceName.toString(), device)
170+
171+ nodeManager.addNode(deviceFolder)
172+
173+ rootNode.addOrganizes(deviceFolder)
155174 }
175+
176+ nodeManager.addNode(rootNode)
177+
178+ rootNode.addReference(
179+ Reference (
180+ rootNode.nodeId,
181+ Identifiers .Organizes ,
182+ Identifiers .ObjectsFolder .expanded(),
183+ false
184+ )
185+ )
186+
187+
156188 }
157189
158190 init {
159191 lifecycleManager.addLifecycle(subscription)
160192
161193 lifecycleManager.addStartupTask {
162- nodeContext.registerHub (deviceHub, Name . EMPTY )
194+ nodeContext.registerTopLevelHub (deviceHub)
163195 }
164196
165197 lifecycleManager.addLifecycle(object : Lifecycle {
@@ -195,8 +227,8 @@ public class DeviceNameSpace(
195227}
196228
197229
198- public fun OpcUaServer.serveDevices (context : Context , deviceHub : DeviceHub ): DeviceNameSpace =
199- DeviceNameSpace (context , this , deviceHub).apply { startup() }
230+ public fun OpcUaServer.serveDevices (scope : CoroutineScope , deviceHub : DeviceHub ): DeviceNameSpace =
231+ DeviceNameSpace (scope , this , deviceHub).apply { startup() }
200232
201233/* *
202234 * Serve devices from [deviceManager] as OPC-UA
0 commit comments