# OPC-UA driver for Ignition 8: New "Device" interface?

**URL:** <https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046>\
**Category:** Module Development\
**Tags:** ignition80, sdk\
**Created:** [May 16, 2019, 1:53am UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046 "2019-05-16T01:53:38Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [May 16, 2019, 1:53am UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/1 "2019-05-16T01:53:38Z")

</div>

Was trying to follow the [example](https://github.com/inductiveautomation/ignition-sdk-examples/tree/master/opc-ua-device) for developing a module for an OPC-UA device for Ignition 8.0. It’s confusing for a few reasons:

- The examples for Ignition 8.0 use the `com.inductiveautomation.ignition.gateway.opcua.server.api.Device` interface, whereas previous examples I have for for Ignition 7.9 use `com.inductiveautomation.xopc.driver.api.Driver` and `com.inductiveautomation.xopc.driver.api.tags.DriverTagManager` interfaces
- There is no documentation in the [Ignition 8.0 JavaDocs](http://files.inductiveautomation.com/sdk/javadoc/ignition80/8.0.0-beta/index.html) for the `Device` interface, or for anything else in the `com.inductiveautomation.ignition.gateway.opcua` package hierarchy
- The example for Ignition 8.0 is using a simulator that just generates values on demand, and doesn’t show how requests to read and write values from an actual device would work

Are there any other examples of how to go about writing an OPC-UA driver for Ignition 8.0? If not, are there any good starting points that I could use? Should I still implement `Driver` and `DriverTagManager` and hook them in somehow, or are those interfaces no longer used? Really looking for any guidance here, since the example and JavaDoc are really sparse or missing.

---

<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 16, 2019, 2:05am UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/2 "2019-05-16T02:05:30Z")

</div>

You could technically still build a driver against the old `Driver` interface and it will work because there’s a mechanism to bridge `Driver` to `Device`, but this API is now deprecated and unsupported.

There are some javadocs written for `Device` but it looks like none of the javadocs from the UA module are being published for some reason. I’ll have to look into this.

The [OPC UA Device example](https://github.com/inductiveautomation/ignition-sdk-examples/tree/master/opc-ua-device) is the recommended starting point if you’re building for 8.0. There isn’t actually much to the new device API because it’s mostly an extension of the [Eclipse Milo](https://github.com/eclipse/milo/) address space APIs plus the additional context from being an Ignition module.

As always with the module SDK… it is what is. You’re welcome to keep asking questions in the forums and myself or others will answer what they can.

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [May 16, 2019, 3:07am UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/3 "2019-05-16T03:07:08Z")

</div>

Thanks, that helps a lot! Knowing that `Driver` is deprecated, I’ll steer away from that and focus on the Milo address space API (`ManagedAddressSpaceServices` and related APIs are where I will look for now, based on the example).

Thanks for the quick response. I’m sure I’ll have more questions, and I’ll try to make some progress based on the example. The thing I’m struggling with most at the moment is trying to figure out where to hook in code that communicates with the actual device.

---

<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 16, 2019, 12:17pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/4 "2019-05-16T12:17:43Z")

</div>

Extending `ManagedAddressSpaceServices` is probably the wrong approach for something that is meant to talk to a real device over the network. The “Managed” subclasses implement browse, read, and write under the assumption that all your Nodes will be managed by a NodeManager _and_ that their values can be read from and written to those Nodes directly.

For a real device, you can still have Node instances in memory and use a NodeManager, but you likely want to intercept the read and write services when they target the Value attribute and actually read from or write to the device, rather than from the Node you have in memory. How you implement browse depends on whether the device supports browsing or not.

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [April 21, 2020, 1:27pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/5 "2020-04-21T13:27:30Z")

</div>

Bringing this topic back… Instead of trying to write a device module last time, I ended up a managed tag provider module since it was a good fit for that case. Now I’m trying to communicate with a different type of device, and writing a device module is more appropriate. I’m getting stuck and was hoping for some guidance.

My device module will communicate with individual hardware devices. These devices allow read and write of data points, as well as browsing their hierarchical structure. I’ve based my project on the [OPC UA Device example](https://github.com/inductiveautomation/ignition-sdk-examples/tree/master/opc-ua-device), but without my `Device` implementing `ManagedAddressSpaceServices` – instead, I am implementing `Device` directly, as recommended above.

Here’s where I am so far:

- I’m able to compile a module and install it
- When module installed, logger says `ModuleManager` is `Installing module` and `Starting up module`, in both cases followed by my module ID and version number
- I’ve added logging statements to the beginning of all the `Device` methods that I need to implement for the various `AddressSpaceServices` methods it inherits, so that I can see which methods are called by Ignition
  - When I add a device connection, my `Device` is instantiated, and `Device.startup()` is called
  - When I open the Ignition OPC UA Server folder in the OPC Quick Browser, my `Device.getReferences()` is called with a node ID of `ns=0;i=85`

- `Device.shutdown()` is called when I uninstall the module

Some questions:

- Do I need to maintain my own data structures to hold the tree of data values (state) of my device, or am I supposed to be directly manipulating the nodes on the Ignition OPC UA server in order to hold that information and represent the device? I assumed that the Ignition OPC UA server would maintain its nodes based on what is returned by my `Device` class, and that I would have to represent the state of my device as though it were an OPC UA device, via OPC UA Nodes and Variables. But, I may have made a bad assumption here.
- If that’s what I need to do, do I need to create my own `NodeManager` and `Node`s to represent the variables? If so, how should I best go about that?
- What should I return when Ignition calls my `Device.getReferences()`? I’m not sure what the response of my `Device` class should be in order to have Ignition represent the hardware device correctly in its device tree.
- Are there any examples that you know of that involve implementing the `Device` interface without using `ManagedAddressSpaceServices`? I poked around GitHub and could find no examples, so it’s hard to understand how my `Device` implementation should manage its state and respond to calls from Ignition.
- Is there some easier way to implement this? I think that in Ignition 7.9 when the `Driver` interface still was around, there were abstract classes that helped to provide some of the implementation necessary to represent and maintain the state of the actual device. I poked around but did not find anything like that in the current libraries.

I feel like I’m misunderstanding the fundamental strategy involved to implement `Device` for reading, writing, and browsing a hardware device and could use some guidance on the broad concepts. Once I understand the general strategy that is intended for writing a Device implementation I should be OK to run with it, but I haven’t been able to find any examples online nor any similar discussions in the forums. Any and all advice, guidance, and pointers here would be much appreciated!

---

<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:** [April 21, 2020, 1:41pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/6 "2020-04-21T13:41:56Z")

</div>

I may have given you some bad advice previously. It might be entirely appropriate to use the `ManagedAddressSpaceServices` for your device implementation, especially if it’s not going to have a large quantity of Nodes.

`ManagedAddressSpaceServices` takes care of a lot of the state management you’re asking about by using a `UaNodeManager` to manage nodes and implement read, write, and browse services.

You can still extend from this and use its node management facilities and override the read and write methods to be more performant if necessary. The default implementations will read from or write to the UaNode instances directly, which means you have to install a delegate or filter (depends on the Mio version) on these to “intercept” the call and actually act on it if needed. This can be too slow if, e.g. hundreds of values are being read at once and you need to batch these up into a single call to some underlying device. This is when it becomes appropriate to override the `read` method with something custom.

---

<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:** [April 21, 2020, 1:44pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/7 "2020-04-21T13:44:31Z")

</div>

Ah, and just as a warning to you and anyone else who might be using this new Device API, there are some breaking changing coming down the line ☹

Whenever Milo 0.5.0 is released and Ignition updates to it there will be some required changes to your Device implementations. You can get a feel for what’s changing here: [Update opc-ua-device example for upcoming Device API changes by kevinherron · Pull Request #43 · inductiveautomation/ignition-sdk-examples · GitHub](https://github.com/inductiveautomation/ignition-sdk-examples/pull/43)

and the corresponding change in Milo here: [Remove AbstractLifecycle from ManagedAddressSpace class hierarchy by kevinherron · Pull Request #627 · eclipse/milo · GitHub](https://github.com/eclipse/milo/pull/627)

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [April 21, 2020, 5:08pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/8 "2020-04-21T17:08:43Z")

</div>

Thanks for the advice! I had recently been looking around at `ManagedAddressSpaceServices` and thought that it seemed to take on a lot of the work, and was wondering if I could leverage that. I think I’ll go that route and see what I can do, based on your OPC UA device example.

Also, thanks for the heads-up on the breaking changes, that’s very good to know! The changes look like a nice improvement. I see how you are using an `AttributeFilter` with the new Milo API to intercept the moment when the value is asked for. Looking at the current OPC UA example project, I see there is a similar technique using `UaVariableNode.setAttributeDelegate()` to intercept read calls using the current Milo API. I’ll try to do the same and report back on how it goes.

---

<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:** [April 21, 2020, 5:18pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/9 "2020-04-21T17:18:16Z")

</div>

Yeah, `AttributeDelegate` and `AttributeFilter` are very similar concepts, the filter is mostly just an evolution of the concept after some experience using the delegate implementation. The delegate will eventually be removed when 1.0 is released.

I was also hoping to making the filter version async capable and possibly support batching but so far it has proven too difficult to design a reasonable API for.

Everything is much easier when you use “managed” UaNodes and the “managed” AddressSpace implementations, and for most cases this is good enough. It mostly becomes a problem when:

1. your AddressSpace needs to represent more nodes than is reasonable to keep in memory.
2. you need more sophisticated handling of reads, writes, and subscriptions, so that you can batch/group/optimize these instead of handling them on a node-by-node basis.

If you consider case 1 it should become apparent why the raw AddressSpace API is designed the way it is - every one of those calls can be answered without actually having a UaNode instance in memory, it’s just a lot more work depending on the call.

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [April 21, 2020, 5:42pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/10 "2020-04-21T17:42:03Z")

</div>

That all makes a lot of sense. I think my devices will be lightweight in the amount of nodes needed, and I think the traffic is low enough to not need more sophisticated batching or other optimizations. Once I’m able to get the `ManagedAddressSpaceServices` version of my `Device` working, I’ll keep the other ideas in mind in case optimization is needed.

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [May 6, 2020, 1:32am UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/11 "2020-05-06T01:32:32Z")

</div>

Struggling with the OPC UA APIs and how they relate to Ignition. The OPC UA spec is so massive and freeform that it makes for a huge wall to climb to do simple things. And, I’m not exactly sure what Ignition is expecting me to do in order to provide a `Device` implementation that it likes.

Here is an example: In my `Device` class (which extends `ManagedAddressSpaceServices`), I’m trying to set a property on a node and then read it back.

```auto
UaNode node = <some node constructor, like UaFolderNode>

final QualifiedProperty<String> fooProperty =                                            
        new QualifiedProperty<>(                                                                      
                getNodeContext().getNamespaceTable().getUri(node.getNodeId().getNamespaceIndex()),    
                "foo",                                                                 
                Identifiers.String,                                                                   
                ValueRanks.Scalar,                                                                    
                String.class                                                                          
        );                                                                                            
node.setProperty(fooProperty, "test");                      
                                                                                                      
// now try to read it back immediately using the exact same QualifiedProperty 
// this logs "Read back the property as Optional.empty"                                                            
logger.debug("Read back the property as {}", node.getProperty(fooProperty));             
                                                                                                      
// maybe try to get the node instead of the property?
// this logs "Able to get the property node: false"                                                                     
logger.debug("Able to get the property node: {}", node.getPropertyNode("foo").isPresent());

```

Any idea what could be going wrong here? Unfortunately, I’m hitting my head against a number of similarly simple things that just don’t work the way I’m expecting.

Another thing I’m noticing is that there are some “magical” things that happen as far as node IDs go. The device name ends up being prepended to NodeId identifiers even though I don’t add it. And, property identifiers seem to take on their parent’s identifier suffixed with a dot and the property name. Some of these things make sense but are taking me by surprise when I set a particular node ID and get something else back. Without documentation, I don’t find out until I try to use a node ID I provided previously and see that it doesn’t work due to these modifications. Are there other things like that I should watch out for?

---

<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 6, 2020, 3:37am UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/12 "2020-05-06T03:37:53Z")

</div>

What is it you’re trying to accomplish here? Do you really want to be creating a Property and PropertyNode?

Either way, this should work, but maybe there’s something wrong with the way the parent Node is being created or with doing this in the context of a Device… not sure yet.

The device name is prepended to the NodeIds you get from `DeviceContext.nodeId` because that’s how the larger AddressSpace your device is a part of identifies your AddressSpace as the one that is responsible for it. Using `[$deviceName]` prepended is a convention used by Ignition’s device API and the helper function is just there to make it easier to create correct NodeIds for your Device instance.

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [May 6, 2020, 1:17pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/13 "2020-05-06T13:17:27Z")

</div>

Kevin, thanks for the help!

> What is it you’re trying to accomplish here? Do you really want to be creating a Property and PropertyNode?

I'm creating `UaFolderNode` or `UaVariableNode` nodes to represent folders and tags from my device. Each folder and tag on my device has an address that uniquely identifies it. The address is made up of two arbitrary strings. I wanted to store each string in a property of its corresponding folder or variable node. That way, when I get a request to read a node's value, I could retrieve the node's two address properties and use them to access the corresponding tag on the device.

I could do the same thing by keeping a `Map` of node IDs to device addresses. However, since OPC UA already has a facility for keeping track of arbitrary meta-information that is related to a node (a property), I thought I could leverage that and keep the data together.

However, I don't seem to be able to read any properties that I create. As in the code above, even using the same `QualifiedProperty` to set and get a property doesn't work -- the "set" seems to work, but the "get" comes back empty. In general, if a value can be set via `object.set(key, value)` I would expect `object.get(key)` to retrieve the value, but there must be something different going on here.

Is using properties in this way a bad idea? Would you recommend using my own `Map` instead as mentioned above, or is there some better way this usually is done?

> Using `[$deviceName]` prepended is a convention used by Ignition’s device API and the helper function is just there to make it easier to create correct NodeIds for your Device instance.

This makes sense in hindsight, just was unexpected that the node ID I specified would be changed. I guess this is one of the "unwritten rules" of writing a device driver for Ignition. Looks like this happens when creating a `NodeId` via `DeviceContext.nodeId()`. Would be good to have basic JavaDoc on `DeviceContext` and what its purpose is, when to use it, how it modifies node IDs, etc. However, the `com.inductiveautomation.ignition.gateway.opcua.server.api` package is missing from the [JavaDoc](http://files.inductiveautomation.com/sdk/javadoc/ignition80/8.0.12/index.html), so the only thing to go on is the method signatures and class names from the bytecode in the `opc-ua-gateway-api` JAR.

Perhaps there is other documentation on using the classes in that API package, but I haven't found it.  
If it existed, I would assume it would be in the Ignition SDK Programmer's Guide. However, the [section on writing an OPC UA driver](https://docs.inductiveautomation.com/display/SE/Development+Guide) still references the old 7.x `Driver` API. The missing documentation the for the new 8.x API leads to a difficult first-time experience, especially combined with having to learn OPC UA to make use of it.

So, there probably are some misunderstandings I'm having that seem quite basic to you, but for someone having to learn all of this via method signatures and OPC UA spec documents, it's quite difficult to know what path to follow in the sea of options. I appreciate all of the guidance you've been able to provide so far. 🙂

---

<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 6, 2020, 1:22pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/14 "2020-05-06T13:22:09Z")

</div>

> [@brett](#):
>
> Is using properties in this way a bad idea?

No, this is actually a completely appropriate use for them. Sorry I assumed you understand less about OPC UA than you do.

I'm not sure why your property code isn't working. Can you share some of the surrounding code, e.g. the creation of the parent nodes and the NodeIds being used there?

I copied and pasted your property set/get code into the device example from the GitHub SDK examples repo, adding the property to the `rootNode` folder, and it worked as expected.

```auto
    @Override
    public void onStartup() {
        super.onStartup();

        // create a folder node for our configured device
        UaFolderNode rootNode = new UaFolderNode(
            getNodeContext(),
            deviceContext.nodeId(getName()),
            deviceContext.qualifiedName(String.format("[%s]", getName())),
            new LocalizedText(String.format("[%s]", getName()))
        );

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

        final QualifiedProperty<String> fooProperty =
            new QualifiedProperty<>(
                getNodeContext().getNamespaceTable().getUri(rootNode.getNodeId().getNamespaceIndex()),
                "foo",
                Identifiers.String,
                ValueRanks.Scalar,
                String.class
            );

        rootNode.setProperty(fooProperty, "test");

        logger.info("Read back the property as {}", rootNode.getProperty(fooProperty));
        logger.info("Able to get the property node: {}", rootNode.getPropertyNode("foo").isPresent());

```

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [May 6, 2020, 6:54pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/15 "2020-05-06T18:54:40Z")

</div>

Mr. Herron.

I just erased a long post wherein I tried your code inside my `onStartup()`, and it didn’t work. I also tried moving my code to `onStartup()` to try it there, and it didn’t work.

I’m scratching my head, thinking, “hmm, I mean, he’s doing the exact same thing, creating a new node, setting a property… where exactly did Kevin add that code… let’s see… no code before it except for `super.onStartup()`…”

That’s where smacked myself in the forehead audibly after realizing I had not called `super.onStartup()`. It turns out that the one thing the superclass does, which is kind of important, is to register the address space.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/2X/7/7450406d941fe5cb917597cf0aa2d1a72f544855.jpeg)

Seems like a bit of [rubber duck debugging](https://en.wikipedia.org/wiki/Rubber_duck_debugging) helped a lot. Anyway, things at the moment are going a bit better. My code snippet now works, as does yours. Let’s see if I can make some more headway in a more sane world, where address spaces are registered as the OPC UA gods intended. I’ll keep you posted on progress whether good or bad, and probably will have some other questions. Thanks again for your help so far!

---

<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 6, 2020, 7:02pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/16 "2020-05-06T19:02:04Z")

</div>

A happy side effect of the breaking changes that I mentioned are coming down the line is it won’t be possible to make this kind of simple mistake - the superclass will run its startup logic regardless of whether subclasses remembered to call super.onStartup(). If subclasses want to have startup logic too, they can, by registering it with the lifecycle manager, but they don’t _override_ it any more.

Not that this helps you now 🙂

---

<div class="post-metadata">

**Author:** ![brett](https://avatars.discourse-cdn.com/v4/letter/b/bc79bd/32.png) [@brett](https://forum.inductiveautomation.com/u/brett)\
**Post date:** [June 19, 2020, 6:13pm UTC](https://forum.inductiveautomation.com/t/opc-ua-driver-for-ignition-8-new-device-interface/25046/17 "2020-06-19T18:13:20Z")

</div>

Just closing the loop on this… Was able to create a driver, read/write tags, browse, get device status, etc.

I ended up not using OPC UA properties, solely due to the visual aspect of having properties rendered as separate tags. For some reason, I was thinking that properties would render inside of the tag properties that are seen when a tag is expanded in the UI. Instead, I encoded the information I needed to keep for each tag into the node ID, as the information was unique for each node and so could serve as an ID.

I have some other questions that I’ll raise in another thread. Thanks for your help so far – I have a functional driver!
