Automation Professionals' Integration Toolkit Module

Well, shoot. I found and fixed that back in November, but forgot to publish it.

Production release for Ignition v8.1+: v2.0.20.242981823

11 Likes

Wow, thanks for the speedy response! That fixed it right up.

1 Like

My first time here and looking to join two datasets with different number of rows.

They have a common column and I would like to bring over another column that will be used as a low level setpoint to make another column turn colors if the inventory gets low.

I would like to join these two datasets based on if the ITEMNMBR matches. Then put into a table component and allow the Low Level Setpoint column to be editable and then once changed it can write back to the Lowelevel setpoint dataset and update it and finally refresh the table component with the new data.
Trying to use the Integration toolkit module and see if it can do what I need with the built code in it.

This part can be handled by my module.

  • Place the two datasets in custom properties (typically on the view).
  • In another custom property, perform the join with an expression binding like so:
leftJoin(
	{view.custom.levelsData},
	{view.custom.stockData},
	it()['ITEMNMBR'],
	it()['ITEMNMBR']
)

Note that the above will yield two columns named ITEMNMBR, the latter of which might be null for a no-match. This can confuse later lookups by name, so you might want to wrap the whole thing in a columnRearrange(), or a columnRename(), or perhaps wrap one or both source datasets in an alias().

I would use an expression binding on the table's props.data to reference the joined dataset and apply any per-cell styling.

Yes, the table's onCellEdited method will need to write to the original source of the low level setpoints, and then command a refresh of that dataset's binding. The updates will then flow through other expressions.

1 Like

Do these need to have the same number of rows? As the QTY on hand Dataset could be shorter at times because there may not be any on hand. This is pulled from a query from SQL. The other dataset is a Memory tag dataset that I made for them to update as needed for the low level sp. I know this is a minor task but I didnt wanna make a database in SQL for the low level sp.

I made what you said and my data merges but the second dataset does not show any data it brings over the columns but no data.

leftJoin(
	{this.custom.ds},
	{this.custom.LowLevelDS},
	it()['ITEMNMBR'],
	it()['ITEMNMBR']
)

That means it isn't matching up your item numbers. Extra spaces is a common culprit. Or different data types.

My leftJoin() function is going to work just like a real left join in a database--rows on the left that have no match on the right will get nulls for all the columns that would have come from the right.

If there are rows on the right that don't have a match on the left, they get dropped. Again, just like a real left join.

You were correct I was getting multiple empty spaces after my ITEMNMBR so in the SQL Query I did a Cast as char 12 and this cleared it up. Now I need to try and do the second half of what you had mentioned.

I am trying now to edit the LOWLEVELSP column and on the Component Events (onEditCellCommit) I am scripting

def runAction(self, event):
	newDataSet = self.custom.LowLevelDS
	ITEMNMBR = self.props.selection.data[0].ITEMNMBR
	sp = 2
	
	for row in range(newDataSet.rowCount()):
		if row.ITEMNMBR == ITEMNMBR:
			newDataSet = system.dataset.setValue(newDataSet, row, 'LowLevelSP', sp)
			
	self.custom.LowLevelDS = newDataSet
	
	self.getSibling("Table_3").refreshBinding("props.data")

but I get an error of

Error running action 'component.onEditCellCommit' on Production/ProductionData/QtyOnHand@D/root/FlexContainer/Table_3: Traceback (most recent call last): File "function:runAction", line 6, in runAction TypeError: 'int' object is not callable

row is an integer. It doesn't have an ITEMNMBR attribute. (This script doesn't use my toolkit at all.)

I used the join to get the table joined now trying to edit the LowLevelSP on the table component and write back to the original LowlevelSP dataset to change it.

The user wants to be able to change the LowLevelSP on the table component. So was trying to do that with the code above. I tried now changing to

row in your script is an integer, but your error correctly states you're treating it as if it were a structured datatype/object. You'll need to adjust your script to use the row to get the proper row data for your ITEMNMBR column.

Meanwhile....

Automation Professionals is pleased to announce a BETA release of this module, with a significant new feature: Tag Actors.

For Ignition v8.1+: v2.1.0.250841756

The details of the new functionality are in the freshly updated documentation, under Tag Actors.

The Republish action provides a built-in alternative to the Modbus Publishing algorithm I recommend over here:

The Republish action can also be used with expression tags to revive the old OPC writeback functionality, without needing any scripting.

The Bulk Script action can replace most uses of the queueUtil.py script I introduced in this topic:

The key advantage of the Bulk Script action is that it relieves you of the task managing a long-lived queue object.

The Divert Write actions will likely be the most popular, though, due to a couple key use cases:

  • When subscribing to complete complex objects from an OPC server due to performance or licensing limitations, where the OPC server does support direct access to the leaf nodes of that complex object, any Derived tags that are used to "fan out" the leaf nodes can have their write path hijacked to write directly to the leaf node. No race conditions or any unnecessary complex writes will occur.

  • When receiving tags at high speed from a path that is fundamentally read-only, like Class1 EtherNet/IP buffered data, the writes to such tags can be diverted through another path that can write to the source node. This makes it possible to copy sealed AOI raw data into an I/O buffer or into a DINT array produced tag, decode the raw data into a byte-by-byte equivalent type in Ignition for efficient subscriptions, then divert any desired writes directly to the AOI instance members.

Lightly tested.

5 Likes

The new Tag Actors show up in the designer Tag Editor in their own category, professional-like:

(I'm just missing an icon for them.)

3 Likes

You should use a "redo" icon of some sort with a curved arrow. I don't have a use for this right now, but definitely see use cases for this!

I haven't yet figured out how to attach any icon to my category, fwiw. Hopefully someone ( Cough ) will offer a hint....

I think you need to have a custom editor that renders its own icon in order to have an icon.

oops, category, not property, eh...

update: that panel is not extensible in a way that would allow you to provide an icon. The mapping is hardcoded :frowning:

1 Like

Shoot. At least it took the key and looked up the text in my bundle. Sigh.

Yeah. The icon, though:

icon = when (simpleKey) {
    CAT_BASIC -> "about"
    CAT_VALUE -> "value"
    CAT_PARAMETERS -> "parameter"
    CAT_NUM -> "numeric"
    CAT_META -> "metadata"
    CAT_SECURITY -> "lock"
    CAT_SCRIPT -> "script"
    CAT_ALARMS -> "alarm"
    CAT_HISTORY -> "history"
    CAT_CUSTOM -> "customizer"
    else -> null
}?.let { VectorIcons.get(it, if (isSelected) Base000 else IconDefault) }

I'm not going to sweat it. It is pretty obvious what it does. I think I'll make it just "Toolkit Actions" though. "Tag" is redundant in the Tag Editor. :man_shrugging:

1 Like

Fantastic stuff Phil.

All three of these actions solve problems we’ve had to work around in the past; having the configuration for republishing/queuing live in the tag is going to make things so much easier to understand.

2 Likes