# OPC-UA javadoc and general functionality

**URL:** <https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913>\
**Category:** Module Development\
**Tags:** ignition81\
**Created:** [November 15, 2023, 6:33am UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913 "2023-11-15T06:33:02Z")\
**Posts on this page:** 7\
**Page:** 2

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [March 28, 2025, 12:18pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/21 "2025-03-28T12:18:26Z")

</div>

I try a minimal implementation, but when I Browse the devices, I have a read time out:

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/4/a4500cc6923c1c7dc9f71789db89c6e4c68bf09e.png)

**com.inductiveautomation.ignition.client.gateway\_interface.GatewayException: Read timed out**

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/3/3/330e96e123ef8161ac3e41a753d86de8b3844345.png)

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/3/63363e340fe50eb709bc66e825ab496d1b62446c.png)

I probably need to implement **getReference** but I not sure how to do that ?

```python
package com.inductiveautomation.ignition.examples.tagdriver;

import com.inductiveautomation.ignition.gateway.opcua.server.api.Device;
import com.inductiveautomation.ignition.gateway.opcua.server.api.DeviceContext;
import com.inductiveautomation.ignition.gateway.opcua.server.api.DeviceSettingsRecord;
import org.eclipse.milo.opcua.sdk.core.AccessLevel;
import org.eclipse.milo.opcua.sdk.core.Reference;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.UaNodeManager;
import org.eclipse.milo.opcua.sdk.server.api.DataItem;
import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;
import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext;
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
import org.eclipse.milo.opcua.sdk.server.nodes.filters.AttributeFilters;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.BuiltinDataType;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.ViewDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Set;

/*
https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/13
 */

public class ExampleDevice implements Device {

    private final Logger logger = LoggerFactory.getLogger(getClass());
    // private final SubscriptionModel subscriptionModel;

    private final DeviceContext deviceContext;
    private final DeviceSettingsRecord deviceSettings;

    private static GenericDeviceNodeContext nodeContext;

    private String status = "Running";

    public ExampleDevice(
            DeviceContext deviceContext,
            DeviceSettingsRecord deviceSettings
    ) {
        this.deviceContext = deviceContext;
        this.deviceSettings = deviceSettings;
    }

    @Override
    public @NotNull
    String getName() {
        return deviceSettings.getName();
    }

    @Override
    public @NotNull
    String getStatus() {
        //return "Running";
        return status;
    }

    @Override
    public @NotNull
    String getTypeId() {
        return deviceSettings.getType();
    }

    @Override
    public void onDataItemsCreated(List<DataItem> dataItems) {

        for (DataItem dataItem : dataItems) {
            logger.info("onDataItemsCreated : {}", dataItem.getReadValueId().getNodeId().getIdentifier().toString());
            logger.info("onDataItemsCreated dataItem.getId() : {}", dataItem.getId());
            logger.info("onDataItemsCreated dataItem.getSubscriptionId() : {}", dataItem.getSubscriptionId());
        }

    }

    @Override
    public void onDataItemsModified(List<DataItem> dataItems) {
        //subscriptionModel.onDataItemsModified(dataItems);
        for (DataItem dataItem : dataItems) {
            logger.info("onDataItemsModified : {}", dataItem.getReadValueId().getNodeId().getIdentifier().toString());
            logger.info("onDataItemsModified dataItem.getId() : {}", dataItem.getId());
        }
    }

    @Override
    public void onDataItemsDeleted(List<DataItem> dataItems) {
        //subscriptionModel.onDataItemsDeleted(dataItems);
        for (DataItem dataItem : dataItems) {
            logger.info("onDataItemsDeleted : {}", dataItem.getReadValueId().getNodeId().getIdentifier().toString());
            logger.info("onDataItemsDeleted dataItem.getId() : {}", dataItem.getId());
        }
    }

    @Override
    public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) {
        //subscriptionModel.onMonitoringModeChanged(monitoredItems);
        for (MonitoredItem monitoredItem : monitoredItems) {
            logger.info("onMonitoringModeChanged : {}", monitoredItem.getReadValueId().getNodeId().getIdentifier().toString());
            logger.info("onMonitoringModeChanged dataItem.getId() : {}", monitoredItem.getId());
        }
    }

    @Override
    public void startup() {

        logger.info("startup()");

        this.nodeContext = new GenericDeviceNodeContext(deviceContext);

        UaFolderNode rootNode = new UaFolderNode(
                nodeContext,
                deviceContext.nodeId(getName()),
                deviceContext.qualifiedName(String.format("[%s]", getName())),
                new LocalizedText(String.format("[%s]", getName()))
        );

        logger.info("create node : {}", String.format("[%s]", getName()));

        // add the folder node to the server
        nodeContext.getNodeManager().addNode(rootNode);

        // add a reference to the root "Devices" folder node
        rootNode.addReference(new org.eclipse.milo.opcua.sdk.core.Reference(
                rootNode.getNodeId(),
                Identifiers.Organizes,
                deviceContext.getRootNodeId().expanded(),
                Reference.Direction.INVERSE
        ));

        addStaticNodes(rootNode, "readOnly", 10, AccessLevel.READ_ONLY);
    }

    private void addStaticNodes(UaFolderNode rootNode, String name, int count, Set<AccessLevel> accessLevel) {
        UaFolderNode folder = new UaFolderNode(
                this.nodeContext,
                deviceContext.nodeId(name),
                deviceContext.qualifiedName(name),
                new LocalizedText(name)
        );
        this.nodeContext.getNodeManager().addNode(folder);
        rootNode.addOrganizes(folder);

        String name1 = folder.getDisplayName().getText();
        for (int i = 0; i < count; i++) {
            String formattedName = String.format("%s%d", name1, i);
            UaVariableNode node = UaVariableNode.builder(this.nodeContext)
                    .setNodeId(deviceContext.nodeId(String.format("%s/node%d", formattedName, i)))
                    .setBrowseName(deviceContext.qualifiedName(formattedName))
                    .setDisplayName(new LocalizedText(formattedName))
                    .setDataType(BuiltinDataType.UInt16.getNodeId())
                    .setTypeDefinition(Identifiers.BaseDataVariableType)
                    .setAccessLevel(accessLevel)
                    .setUserAccessLevel(accessLevel)
                    .build();
            node.setValue(new DataValue(new Variant(i)));

            if (accessLevel.contains(AccessLevel.CurrentWrite)) {
                // This filter just intercepts the write to log it before
                // passing it to the next filter in the chain. The default
                // filter instance at the end will write the attribute to
                // the UaNode instance.
                node.getFilterChain().addLast(AttributeFilters.setValue((ctx, value) -> {
                    logger.info("setValue: {}", value.getValue().getValue());
                    ctx.setAttribute(AttributeId.Value, value);
                }));
            }
            nodeContext.getNodeManager().addNode(node);
            folder.addOrganizes(node);
        }
    }

    @Override
    public void shutdown() {
        logger.info("shutdown()");
    }

    @Override
    public void read(ReadContext readContext, Double aDouble, TimestampsToReturn timestampsToReturn, List<ReadValueId> list) {
        logger.info("read()");
    }

    @Override
    public void write(WriteContext writeContext, List<WriteValue> list) {
        logger.info("write()");
    }

    @Override
    public void browse(BrowseContext browseContext, ViewDescription viewDescription, NodeId nodeId) {
        logger.info("browse()");
    }

    @Override
    public void getReferences(BrowseContext browseContext, ViewDescription viewDescription, NodeId nodeId) {
        logger.info("getReferences() - viewDescription = {} - nodeId = {}",viewDescription,nodeId);
    }

    public static class GenericDeviceNodeContext implements UaNodeContext {
        public final DeviceContext deviceContext;
        public final UaNodeManager nodeManager;

        public GenericDeviceNodeContext(DeviceContext deviceContext) {
            this.deviceContext = deviceContext;
            this.nodeManager = new UaNodeManager();
        }

        @Override
        public OpcUaServer getServer() {
            return deviceContext.getServer();
        }

        @Override
        public UaNodeManager getNodeManager() {
            return nodeManager;
        }
    }

}

```

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [March 28, 2025, 12:20pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/22 "2025-03-28T12:20:43Z")

</div>

You need to implement all of those callbacks.

See [milo/opc-ua-sdk/sdk-server/src/main/java/org/eclipse/milo/opcua/sdk/server/api/ManagedAddressSpace.java at bd150672c2cee24e7934001eecb8ca581ddc4ffc · eclipse-milo/milo · GitHub](https://github.com/eclipse-milo/milo/blob/bd150672c2cee24e7934001eecb8ca581ddc4ffc/opc-ua-sdk/sdk-server/src/main/java/org/eclipse/milo/opcua/sdk/server/api/ManagedAddressSpace.java) as a reference, it seems like you're just re-creating that, but with some amount of laziness to how you add your Nodes.

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [May 22, 2025, 5:16pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/23 "2025-05-22T17:16:49Z")

</div>

@Kevin.Herron, @pturmel

in my driver use case with dynamic nodes, I create UaVariableNode with a BuiltinDataType.UInt16,  
and when read request provide me the value, I know the expected data type, so I change the node dataType.

I would like to store in the node that the dataType is "known" in order to avoid to change it for next request.

Is it possible to store some "user" data in the UaVariableNode, in order to retreive it later ?  
If not I can have another structure for that, but I would like to avoid to multiply search operation.

Another way could be to use the value, Null if data type not known or perhaps a quality code can be use for such case ?

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [May 22, 2025, 5:38pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/24 "2025-05-22T17:38:46Z")

</div>

> [@mazeyrat](#):
>
> Is it possible to store some "user" data in the UaVariableNode, in order to retreive it later ?

No, I almost did something like this at one point in the past but it never made it.

You can just create a `Map<UaVariableNode, Object>` to store your own data or something.

---

<div class="post-metadata">

**Author:** ![mazeyrat](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mazeyrat/32/10955_2.png) [@mazeyrat](https://forum.inductiveautomation.com/u/mazeyrat)\
**Post date:** [May 22, 2025, 7:46pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/25 "2025-05-22T19:46:51Z")

</div>

Thanks !

another question, when we remove node:

```python
    @Override
    public void onDataItemsDeleted(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsDeleted(dataItems);
        for (DataItem dataItem : dataItems) {
            logger.info("onDataItemsDeleted : {}", dataItem.getReadValueId().getNodeId().getIdentifier().toString());
            logger.info("onDataItemsDeleted dataItem.getId() : {}", dataItem.getId());

            logger.info("onDataItemsDeleted removeNode nodeId : {}", dataItem.getReadValueId().getNodeId());
            nodeContext.getNodeManager().removeNode(dataItem.getReadValueId().getNodeId());

            for (NodeId node : nodeContext.getNodeManager().getNodeIds()){
                logger.info("node = {}",node);
            }
        }
    }

```

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/d/1/d17672326e9c0c45d04eb75e012ff14a7ea8ebb8.png)

is there other instructions to call to update device opcua tree, in Ignition, deleted node are still visible ?

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/8/28b1f6e2db5f5a97f1cdc07acd2de266fd796ba0.png)

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [May 22, 2025, 8:11pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/26 "2025-05-22T20:11:37Z")

</div>

If they're still visible then your driver is still returning them in the `browse` and/or `gather` methods.

(assuming you've refreshed that view)

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [May 22, 2025, 8:15pm UTC](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913/27 "2025-05-22T20:15:18Z")

</div>

Try `UaNode::delete` instead, which removes both the Node and References from the NodeManager.

[Previous page](https://forum.inductiveautomation.com/t/opc-ua-javadoc-and-general-functionality/81913.md?page=1)
