OPC-UA javadoc and general functionality

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

com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Read timed out

image

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

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;
        }
    }

}


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 as a reference, it seems like you're just re-creating that, but with some amount of laziness to how you add your Nodes.

1 Like