OPC-UA javadoc and general functionality

I am trying to create an OPC-UA device driver in 8.1 and am using the OPC-UA example from GitHub as the starting point.

I understand the general gist of the example, but am trying to figure out how to implement a device that subscribes to data changes in a controller. I can't figure out the correct way to push these changes back to ignition, or how to handle polls from ignition where there has been no data change. My device also doesn't support browsing, and I can't figure out from the example how to implement tags without populating the tag browser.

I've tried looking for the javadoc for the managed device. It appears that all of the opcua packages are missing from the ignition javadoc web pages. com.inductiveautomation.ignition.gateway.opcua.server.api.ManagedDevice

I'm also not sure the function of the following methods:

public void onDataItemsCreated(List<DataItem> dataItems) {
    subscriptionModel.onDataItemsCreated(dataItems);
}

public void onDataItemsModified(List<DataItem> dataItems) {
    subscriptionModel.onDataItemsModified(dataItems);
}

public void onDataItemsDeleted(List<DataItem> dataItems) {
    subscriptionModel.onDataItemsDeleted(dataItems);
}

public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) {
    subscriptionModel.onMonitoringModeChanged(monitoredItems);
}

Ignition use the Milo open-source OPC UA protocol stack and SDK. Code here:

(Ignition also employs the Milo project lead, @Kevin.Herron, fyi.)

I don't see any javadocs....

Thanks for the info regarding milo.
I've looked through the milo examples and I'm still a bit lost.
I don't understand how the ManagedDevice lines up with milo or how I am supposed to receive notification of new tags with a new 'OPC Item Path'.

Yeah, the core interfaces are not part of IA's public javadocs.

Fundamentally, a driver must implement this interface:

com.inductiveautomation.ignition.gateway.opcua.server.api.Device

which looks like this in IntelliJ:

public interface Device : org.eclipse.milo.opcua.sdk.server.api.AddressSpace, org.eclipse.milo.opcua.sdk.server.Lifecycle

Those two Milo interfaces are the meat of the requirement.

I myself am still puttering around with this, as all of my drivers predate Milo and still rely on the Driver compatibility shim.

You'll have to bug Kevin to cough up some javadocs.

Thanks pturml

@Kevin.Herron any chance the opcua javadocs can be posted?

I'll look into this on Monday. I know there are some docs written on the basic interfaces, but those interfaces are written in Kotlin, and so producing Javadocs for those interfaces/classes presented some issue for our build system that I don't think was ever resolved. Might not be able to produce any until I rewrite the interfaces back to Java in 8.3

1 Like

Thanks Kevin,

My main issue is how to access tags that have been defined by there 'OPC Item Path' as I can't browse my device.

I'm also not sure about subscribed vs polled. The example shows how to respond to a poll, but I can't see how to send data without the poll?

Cheers

OPC Item Path is an OPC UA NodeId.

If you can't browse your device, and you don't know ahead of time or at configuration time what Nodes will exist, then the ManagedDevice abstraction won't be of any use to you and you'll just have to implement Device directly.

Your implementation doesn't have to implement browsing. By default, any NodeId with a String identifier starting with [YourDeviceName] will be passed to your Device implementation's Read, Write, Browse, or Monitored Item related calls.

The example is implementing Subscription / Monitored Item behavior by simply calling the read method over and over. It's up to your implementation to do something smarter if it's available. You don't ever just arbitrarily send information to the client, though. The client creates Monitored Items, and you are responsible for updating that item at the requested Sampling Interval, based on your best efforts.

1 Like

Thanks for the hint, I found the read and write methods in the AttributeService interface.

Do you have Kotlin documentation for the opcua, that would be useful.

I understand the high level data flow, its the interface detail I'm not clear on. I might try the milo forums as well.

The honest reality is that between the current examples (Ignition SDK, Milo example server and demo server) and the available code (all Milo stuff is open source), it's sink or swim and you need to figure it out.

Most of the Device API is just a thin extension on top of Milo OPC UA stuff, except the basic things on Device that aren't inherited, or the registration of a DeviceType, etc...

Hey Kevin,

If I understood correctly, you will be rewriting interfaces written in Kotlin back to Java. May I ask what's wrong with Kotlin?

Nothing, but for some reason our build system, or the KDoc plugin, or some combination, won't produce/combine Javadocs for the stuff written in Kotlin. I don't remember the details any more because I don't work on the build.

1 Like

I have a driver where nodes are not known at startup, so I need to implement Device interface instead of extend a ManagedDevice.

Is it possible to create the UaNode from the onDataItemsCreated ?
Is onDataItemsCreated called when Ignition tag opc item has not been created in the device ?

public class TestDevice implements Device {
    @NotNull
    @Override
    public String getName() {
        return null;
    }

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

    @NotNull
    @Override
    public String getTypeId() {
        return null;
    }

    @Override
    public void startup() {
    }

    @Override
    public void shutdown() {

    }

    @Override
    public void read(ReadContext readContext, Double aDouble, TimestampsToReturn timestampsToReturn, List<ReadValueId> list) {

    }

    @Override
    public void write(WriteContext writeContext, List<WriteValue> list) {

    }

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

    }

    @Override
    public void onDataItemsModified(List<DataItem> list) {

    }

    @Override
    public void onDataItemsDeleted(List<DataItem> list) {

    }

    @Override
    public void onMonitoringModeChanged(List<MonitoredItem> list) {

    }

    @Override
    public void browse(BrowseContext browseContext, ViewDescription viewDescription, NodeId nodeId) {

    }

    @Override
    public void getReferences(BrowseContext browseContext, ViewDescription viewDescription, NodeId nodeId) {

    }
}

It won't be called unless your Device indicates that Node exists and is managed by your AddressSpace. It doesn't necessarily need a UaNode instance to exist, but when a client calls CreateMonitoredItems, you'll end up getting a call to read for some of the attributes needed when creating a MonitoredItem, and provided this call is successful and a MonitoredItem can be created, you'll get a subsequent call to onDataItemsCreated.

If you're building a driver where you don't know what exists ahead of time, then maybe consider just not creating any UaNode instances at all. It's possible to do everything dynamically. My Modbus Server Driver does this.

https://github.com/kevinherron/modbus-server-driver/tree/master

https://github.com/kevinherron/modbus-server-driver/blob/b7ef35f0ce450861a505a1c3e3d5833e42917e60/msd-gateway/src/main/java/com/kevinherron/ignition/modbus/ModbusAddressSpace.java

1 Like

I wimped out on this one. Future improvements, perhaps. For the moment, I create nodes on demand--whether read/write/subscribe or browse.

We use both approaches, depends on the driver.

For Modbus and Modbus-like drivers where you just address memory ranges it makes a lot of sense. For others where there's a well-defined model (Logix, 61850), or other reasons (DNP3, BACnet), we use UaNode instances.

That said - a future rewrite of the Logix driver would go fully dynamic.

Part of the reason the AddressSpace API looks like it does (instead of being perhaps easier to use) is to maintain the ability for an unlimited number of Nodes to exist without actually needing instances of them to exist in memory.

My drivers are good candidates, then, since they all have a "model" of the target's configured or browsed address space. In other words, its all in memory that way now. Just need attribute handling for those.

After the v83 conversion hysteria settles, I'll revisit this.