Creating driver to interface with Ignition OPC-UA Server

I am trying to write a driver module that will be able to read and write to the OPC-UA server. I have looked at the SDK documentation and also some of the source code examples provided with the module SDK. I am still not seeing an appropriate way to do this.

I am trying to determine the easiest way to get data from a USB canbus device to the OPC UA Server. I already have the source code for my USB Canbus device which will be receiving data at timed intervals. Based on the MSG ID I recieve I will need to take action on the incoming data to parse it. Once the data is parsed out I need to write it to its specific tag in the OPC-UA Server. Also, some tags will need to be writable from vision so that I can send commands to the device on the other end of my USB canbus interface.

Does anybody have any insight on how best to approach this and what specific pieces of ignition I need to be concerned with?

Perhaps its best if we clarify the way things work first.

A driver doesn’t read or write to the OPC server, it adds nodes to the OPC server, and in turn, responds to requests FROM the OPC server to read or write to a specific node. These requests come from OPC clients, one of which is usually the Ignition gateway itself.

A driver for the server would be able to accomplish both things you’re trying to do.

Using AbstractTagDriver as an example starting point, you would add a DriverTag for each node you want to the OPC server to make available to clients. Each DriverTag represents some data point that makes sense for your driver implementation. Add instances of WritableDriverTag instead of DriverTag for tags that you want to give the ability to respond to writes as well.

That being said, if no other OPC clients (other than Ignition) are going to be consuming this data, then you don’t really need to make it available via the OPC server (by writing a driver). Instead you could make it available for a TagProvider, which would give you the ability to accomplish both tasks still, but the data points would only be available as SQLTags and therefore only avaialble for use within Ignition.

What kind of programming experience do you currently have?

1 Like

The data only needs to be available via SQL Tags based on your description. As long as I can make the data available to Ignition, then I am good at that point.

My programming experience includes C, C++, Java, visual basic, ladder logic, structured text, and some more specific languages.

[quote=“scott.fischer”]The data only needs to be available via SQL Tags based on your description. As long as I can make the data available to Ignition, then I am good at that point.

My programming experience includes C, C++, Java, visual basic, ladder logic, structured text, and some more specific languages.[/quote]

Alright, just wanted to make sure you know that writing a module is a bit more of an arduous journey than any of the scripting that happens in Ignition. We get quite a few people who mistakenly venture towards using the module SDK.

It sounds like the SimpleTagProvider example will be the best place for you to start.

I have compiled the SimpleTagProviderExample from the module SDK and I have installed the module on the gateway. I launch designer and create a simple button with an action event and I try to modify the TagCount value.

I see in the source code that this has a WriteHandler so it should be writable. When I try to change it’s value I get an error:

Error writing to tag [DynamicTags]Tag Count Gateway Comm Mode is not Read/Write

Am I doing something wrong assuming I can write this value.

Here is the code from the SimpleTagProviderExample

public class SimpleProviderGatewayHook extends AbstractGatewayModuleHook {
	private static final String TASK_NAME = "UpdateSampleValues";
	//This pattern will be used for tag names. So, tags will be created under the "Custom Tags" folder.
	private static final String TAG_NAME_PATTERN = "Custom Tags/Tag %d";
	//This is the name of our "control" tag. It will be in the root folder.
	private static final String CONTROL_TAG = "Tag Count";
	Logger logger;
	GatewayContext context;
	SimpleTagProvider ourProvider;
	ExtendedTagType ourTagType;
	//This example adds/removes tags, so we'll track how many we currently have.
	int currentTagCount = 0;

	public SimpleProviderGatewayHook() {
		logger = LogManager.getLogger(this.getClass());
	}

	@Override
	public void setup(GatewayContext context) {
		try {
			this.context = context;
			ourProvider = new SimpleTagProvider("DynamicTags");

			//Set up our tag type. By doing this, we can allow our tags to use alerting, history, etc.
			//The STANDARD_STATUS flag set in TagEditingFlags provides for all features, without allowing tags to be renamed or deleted.
			ourTagType = TagType.Custom;
			ourProvider.configureTagType(ourTagType, TagEditingFlags.STANDARD_STATUS, null);

			//Set up the control tag.
			//1) Register the tag, and configure it's type.
			//2) Register the write handler, so the tag can be modified.
			ourProvider.configureTag(CONTROL_TAG, DataType.Int4, ourTagType);
			ourProvider.registerWriteHandler(CONTROL_TAG, new WriteHandler() {
				@Override
				public Quality write(TagPath target, Object value) {
					Integer intVal = TypeUtilities.toInteger(value);
					//The adjustTags function will add/remove tags, AND update the current value of the control tag.
					adjustTags(intVal);
					return CommonQualities.GOOD;
				}
			});

			//Now set up our first batch of tags.
			adjustTags(10);
		} catch (Exception e) {
			logger.fatal("Error setting up SimpleTagProvider example module.", e);
		}
	}

	@Override
	public void startup(LicenseState activationState) {
		try {
			ourProvider.startup(context);

			//Register a task with the execution system to update values every second.
			context.getExecutionManager().register(getClass().getName(), TASK_NAME, new Runnable() {
				@Override
				public void run() {
					updateValues();
				}
			}, 1000);

			logger.info("Example Provider module started.");
		} catch (Exception e) {
			logger.fatal("Error starting up SimpleTagProvider example module.", e);
		}
	}

	@Override
	public void shutdown() {
		//Clean up the things we've registered with the platform- namely, our provider type.
		try {
			if (context != null) {
				//Remove our value update task
				context.getExecutionManager().unRegister(getClass().getName(), TASK_NAME);
				//Shutdown our provider
				ourProvider.shutdown();
			}
		} catch (Exception e) {
			logger.error("Error stopping SimpleTagProvider example module.", e);
		}
		logger.info("SimpleTagProvider Example module stopped.");
	}

	/**
	 * This function adds or removes tags to/from our custom provider. Notice that it is synchronized, since we are
	 * updating the values asynchronously. If we weren't careful to synchronize the threading, it might happen that
	 * right as we remove tags, they're added again implicitly, because the value update is happening at the same time.
	 *
	 * @param newCount
	 */
	protected synchronized void adjustTags(int newCount) {
		if (newCount > currentTagCount) {
			for (int i = currentTagCount; i < newCount; i++) {
				ourProvider.configureTag(String.format(TAG_NAME_PATTERN, i), DataType.Float8, ourTagType);
			}
		} else if (newCount < currentTagCount) {
			for (int i = currentTagCount; i > newCount; i--) {
				ourProvider.removeTag(String.format(TAG_NAME_PATTERN, i));
			}
		}
		//Update current count.
		currentTagCount = newCount;
		//Make sure to update the control tag with the current value.
		ourProvider.updateValue(CONTROL_TAG, currentTagCount, DataQuality.GOOD_DATA);
	}

	/**
	 * Update the values of the tags.
	 */
	protected synchronized void updateValues() {
		Random r = new Random();
		for (int i = 0; i < currentTagCount; i++) {
			ourProvider.updateValue(String.format(TAG_NAME_PATTERN, i), r.nextFloat(), DataQuality.GOOD_DATA);
		}
	}
}

Try switching the designer’s comm mode to read/write :smiling_imp:

Check under the Project menu, or use the toolbar button.


That did the trick. My bad, stupid mistake. I am not fully acquainted with Designer yet. Nice catch :thumb_right:

I have been able to get the SimpleTagProvider example to work for the module I am creating. I am trying to implement a read thread for my can bus device and when I do this in the gateway module hook, it seems to lock up, then fault the gateway. Is it not possible to run a third party thread from the gateway module hook or do I need to involve the execution manager?

I’ve been reading in the module SDK documentation that the shared execution manager may not be the best place to invoke a task that may be intensive. I see now that I can create and execution manager to specifically manage the task I need to complete. Is there some example code on creating an execution manager in the gateway module hook and then starting the task?

I have tried using my own executionManager to start my thread and the JVM is still timing out. Here is part of my logs

INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [ModuleManager                 ] [12:40:32,172]: Installing module: "com.test.canbusSimpleTagProvider"
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [ModuleManager                 ] [12:40:32,232]: Starting up module "com.test.canbusSimpleTagProvider"...
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [CanSimpleProviderGatewayHook  ] [12:40:32,312]: Canbus Provider module started.
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [CanSimpleProviderGatewayHook  ] [12:40:32,703]: PCAN_USBBUS1 Succesfully Initialized
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [CANReadThread                 ] [12:40:32,713]: In my thread
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [CanSimpleProviderGatewayHook  ] [12:40:32,713]: Successfully set RcvEvent
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [CanSimpleProviderGatewayHook  ] [12:40:32,713]: PCAN_ERROR_OK
INFO   | jvm 1    | 2013/05/03 12:40:32 | INFO  [CanSimpleProviderGatewayHook  ] [12:40:32,713]: in my run once
WARN   | wrapper  | 2013/05/03 12:41:06 | ------------------------------------------------------------------------
WARN   | wrapper  | 2013/05/03 12:41:06 | Ping: Timed out waiting for signal from JVM.
WARN   | wrapper  | 2013/05/03 12:41:06 | The JVM was launched with debug options so this may be because the JVM
WARN   | wrapper  | 2013/05/03 12:41:06 | is currently suspended by a debugger.  Any future timeouts during this
WARN   | wrapper  | 2013/05/03 12:41:06 | JVM invocation will be silently ignored.
WARN   | wrapper  | 2013/05/03 12:41:06 | ------------------------------------------------------------------------
ERROR  | wrapper  | 2013/05/03 12:44:38 | The JVM process terminated due to an uncaught exception: EXCEPTION_ACCESS_VIOLATION (0xc0000005)
WARN   | wrapper  | 2013/05/03 12:44:38 | JVM exited unexpectedly while stopping the application.
STATUS | wrapper  | 2013/05/03 12:44:39 | <-- Wrapper Stopped

Does anybody have any ideas what might be going on?

Unfortunately it looks like the library you’re using is crashing the JVM.

See if you can find the error/dump file that gets created; maybe it will have some hints as to what is going wrong.

Do you know where I might find the dump files? I was able to take a look at the wrapper.log file like I posted, but I have no idea where these other files were dumped.

In the mean time, I approached the problem using my own execution manager calling the read function on the canbus device every 100ms. This seems to work ok. I will be going with this approach in the near term.

Do you know how I get access to the developer portal to register the module? I tried to login in and it says I have insufficient privileges. I requested access about 2 or 3 weeks ago.

[quote=“scott.fischer”]Do you know where I might find the dump files? I was able to take a look at the wrapper.log file like I posted, but I have no idea where these other files were dumped.

In the mean time, I approached the problem using my own execution manager calling the read function on the canbus device every 100ms. This seems to work ok. I will be going with this approach in the near term.

Do you know how I get access to the developer portal to register the module? I tried to login in and it says I have insufficient privileges. I requested access about 2 or 3 weeks ago.[/quote]

I think everything is done through the module marketplace now, so sign up as a developer on there instead.

I don’t plan on ever selling this module. The Module Marketplace wants the module developer to sign into an agreement. Is there anyway to run the module without having to go this process or is this the only way?

Yeah I believe as long as the module is marked “free” in the module’s XML descriptor we’ll sign it for internal use no problem. I’ll talk to Travis and find out if we’re still using the portal for that, the marketplace, or what the deal is.

Ok, sounds good. Do you know how long this process might take?

Travis said he added your account to the portal.

I am still getting insufficient privileges @ inductiveautomation.com/developers/login. I logged into the module marketplace but it still says I need to register as a developer. I thought I already registered as a developer to get the developer CD Key and downloading the SDK. Is there another site I need to be going to?

scott.fischer, try it now.