OPC-UA Driver or OPC-UA Device !?

After 20 years working in Industrial Automation and Monitoring, I met the powerful Ignition application.
After I tried to get Gold Certification, I found one big problem with the Inductive company! It does not have enough resources to support its community. When I started developing a new module, I became more confident! I have two reasons; First, after 75 days of posting my GOLD 8.1 test file, I did not get any response! Second, incomplete SDK documentation!

Currently, I am very thankful for @Kevin.Herron and @pturmel :pray: They are very active in this forum :100:

But what is my problem:
I want to create an OPC-UA Driver that allows reading data without predefined nodes.

I completely studied: Ignition SDK Programmers Guide. In the Development Guide page, it says: All OPC-UA drivers must, at a minimum, implement the Driver interface. But in OPC-UA device example in github, I could find any implementation of Driver interface. In that example, it implemented ManagedDevice and AbstractDeviceModuleHook.

After confusion, I started to read most of the related posts in the module-development and 3rd-party-modules sections. I found the following posts useful:

Also, I got an outdated ModbusDriverExample that it is not compatible with Ignition 8.1.

So, could you please provide a very simple sample code that provides ability reading tags like the Modbus Driver pattern? e.g “ns=1;s=[device]HRF1234”. It will be great if you provide that sample code based on OPC-UA device example in github.

It's not simple.

Also, I haven't switched to the new Device model, so have no current advice. I'll be learning it before too long.

FWIW, I think your criticism of the SDK is unfair. That Ignition HAS an SDK at all puts them miles ahead of the competition. Ping me when Kepware offers an SDK for their platform.

3 Likes

Dear @pturmel, thank you for your prompt response.
Still, I believe that Ignition is powerful. So I spend a lot of energy learning it.
I apologize Ignition community for my criticism. (@Kevin.Herron, @pturmel)

Anybody can provide Development Guide using the new Device model, please?

The opc-ua-device example is the only sample code available.

You already saw the post where I suggested how you might implement "dynamic" nodes, but I'll restate. Either:

  1. implement the Device interface directly
  2. override the read/write/etc... calls from ManagedDevice

in either case, the calls will be targeting nodes with string-based NodeIds prefixed with [$DeviceName]. The remainder of the NodeId is up to you to to interpret as meaningful or not. In ManagedDevice it simply decides a NodeId is valid based on whether an actual UaNode instance has been created and added to the node manager. If not, StatusCode Bad_NodeIdUnknown is returned.

ManagedDevice is just an extremely simple abstract class that extends ManagedAddressSpace, which might serve as an example of how to implement these calls.

1 Like

I found other good posts that are related to this topic:

@Kevin.Herron I tried to edit ExampleDevice.java as following code.
Is it correct to use the passed nodeId by read() function to create a dynamic node?
Please see my first output in the attached picture.
After running my module, the CPU usage goes to %100 and the Ignition hangs after a while!

	private int nNodeCounter = 0;
	private int nSimCounter = 0;
	private UaFolderNode folderDynamicX = null;
	private void addMyNode(NodeId nodeId)
	{
		if(folderDynamicX == null)
		{
			String name = "DynamicX";
			folderDynamicX = new UaFolderNode(getNodeContext(), deviceContext.nodeId(name), deviceContext.qualifiedName(name), new LocalizedText(name));
			getNodeManager().addNode(folderDynamicX);
			rootNode.addOrganizes(folderDynamicX);
		}

		nNodeCounter++;
		if((nNodeCounter < (10)))
		{
			String strIden = nodeId.getIdentifier().toString();
			strIden = strIden.substring(strIden.indexOf(']')+1);
			String formattedName = strIden;

			UaVariableNode node = UaVariableNode.builder(getNodeContext())
					.setNodeId(nodeId)
					.setBrowseName(deviceContext.qualifiedName(formattedName))
					.setDisplayName(new LocalizedText(formattedName))
					.setDataType(BuiltinDataType.UInt16.getNodeId())
					.setTypeDefinition(Identifiers.BaseDataVariableType)
					.setAccessLevel(AccessLevel.READ_ONLY)
					.setUserAccessLevel(AccessLevel.READ_ONLY)
					.build();

			//node.setValue(new DataValue(new Variant(nNodeCounter)));
			node.setValue(new DataValue(new Variant(nSimCounter)));
			node.getFilterChain().addLast(AttributeFilters.getValue(getAttributeContext -> new DataValue(nSimCounter++)));

			getNodeManager().addNode(node);
			folderDynamicX.addOrganizes(node);
		}
	}

	@Override
	public void read(ReadContext context, Double maxAge, TimestampsToReturn timestamps, List<ReadValueId> readValueIds)
	{
		//super.read(context, maxAge, timestamps, readValueIds);
		List<DataValue> results = Lists.newArrayListWithCapacity(readValueIds.size());

		for(ReadValueId readValueId : readValueIds)
		{
			boolean bRepeat = false;
			do
			{
				UaServerNode node = getNodeManager().get(readValueId.getNodeId());

				if(node != null)
				{
					DataValue value = node.readAttribute(new AttributeContext(context), readValueId.getAttributeId(),
							timestamps, readValueId.getIndexRange(), readValueId.getDataEncoding());

					logger.debug("Read value {} from attribute {} of {}", value.getValue().getValue(),
							AttributeId.from(readValueId.getAttributeId()).map(Object::toString).orElse("unknown"), node.getNodeId());

					results.add(value);
				}
				else
				{
					if(!bRepeat)
					{
						bRepeat = true;
						addMyNode(readValueId.getNodeId());
					}
					else
					{
						results.add(new DataValue(new Variant(321)));
						//results.add(new DataValue(StatusCodes.Bad_NodeIdUnknown));
						break;
					}
				}
			}
			while(bRepeat);
		}

		context.success(results);
	}

Well, I suppose you could do this, but generally the point of the dynamic addressing approach is to avoid creating instances of a Node at all, and to just derive whatever information is requested using the NodeId and AttributeId being read, written, or browsed.

1 Like