# Automation Professionals' Integration Toolkit Module

**URL:** <https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934>\
**Category:** 3rd Party Modules\
**Tags:** perspective, ignition81, ignition83, vision, scripting\
**Created:** [June 13, 2023, 12:42am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934 "2023-06-13T00:42:35Z")\
**Posts on this page:** 20\
**Page:** 11

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 19, 2025, 2:55pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/201 "2025-03-19T14:55:27Z")

</div>

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](https://www.automation-pros.com/ignition/Integration%20Toolkit-2.0.20.242981823-v81.modl)

---

<div class="post-metadata">

**Author:** ![eli.w](https://avatars.discourse-cdn.com/v4/letter/e/c5a1d2/32.png) [@eli.w](https://forum.inductiveautomation.com/u/eli.w)\
**Post date:** [March 19, 2025, 3:32pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/202 "2025-03-19T15:32:08Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![rpaisley](https://avatars.discourse-cdn.com/v4/letter/r/919ad9/32.png) [@rpaisley](https://forum.inductiveautomation.com/u/rpaisley)\
**Post date:** [March 21, 2025, 1:56pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/203 "2025-03-21T13:56:21Z")

</div>

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.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/8/f/8f5d5ac9b1bc929f11d12efd30cdb445c52c3a77.png)

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/8/8/885551a01527dd19f0fb9edba46e8ce7f3e1ee05.png)

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.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 21, 2025, 2:51pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/204 "2025-03-21T14:51:59Z")

</div>

> [@rpaisley](#):
>
> join these two datasets

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:

```python
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()`.

> [@rpaisley](#):
>
> put into a table component

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

> [@rpaisley](#):
>
> editable and then once changed it can write back to the Lowelevel setpoint dataset

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.

---

<div class="post-metadata">

**Author:** ![rpaisley](https://avatars.discourse-cdn.com/v4/letter/r/919ad9/32.png) [@rpaisley](https://forum.inductiveautomation.com/u/rpaisley)\
**Post date:** [March 21, 2025, 5:35pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/205 "2025-03-21T17:35:39Z")

</div>

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.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/0/8/086a8173486f85db0e9ea6cff508e501135edc2a.png)

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/3/a3701b0722a9049668f2987dc44f58362869b591.png)

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

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 21, 2025, 5:56pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/206 "2025-03-21T17:56:14Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![rpaisley](https://avatars.discourse-cdn.com/v4/letter/r/919ad9/32.png) [@rpaisley](https://forum.inductiveautomation.com/u/rpaisley)\
**Post date:** [March 21, 2025, 6:32pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/207 "2025-03-21T18:32:59Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![rpaisley](https://avatars.discourse-cdn.com/v4/letter/r/919ad9/32.png) [@rpaisley](https://forum.inductiveautomation.com/u/rpaisley)\
**Post date:** [March 25, 2025, 5:23pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/208 "2025-03-25T17:23:39Z")

</div>

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

```python
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

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 25, 2025, 5:28pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/209 "2025-03-25T17:28:32Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![rpaisley](https://avatars.discourse-cdn.com/v4/letter/r/919ad9/32.png) [@rpaisley](https://forum.inductiveautomation.com/u/rpaisley)\
**Post date:** [March 25, 2025, 5:33pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/210 "2025-03-25T17:33:36Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![michael.flagler](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/michael.flagler/32/26272_2.png) [@michael.flagler](https://forum.inductiveautomation.com/u/michael.flagler)\
**Post date:** [March 25, 2025, 5:44pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/211 "2025-03-25T17:44:52Z")

</div>

`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.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 25, 2025, 6:26pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/212 "2025-03-25T18:26:13Z")

</div>

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](https://www.automation-pros.com/toolkit/doc/actors.html).

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

> [@Modbus Slave/Server to Generate Read Only Tags](https://forum.inductiveautomation.com/t/modbus-slave-server-to-generate-read-only-tags/87583/21):
>
> I've create a generic "Modbus Publish" script that eliminates the need for a second set of tags. I've publicly posted it here: [https://www.automation-pros.com/ignition/modbusPublish.py](https://www.automation-pros.com/ignition/modbusPublish.py) It requires the "Queue Utility" script posted here: [https://www.automation-pros.com/ignition/queueUtil.py](https://www.automation-pros.com/ignition/queueUtil.py) and described over here: Both scripts rely on my Integration Toolkit's globalVarMap() script function, but you can rework that (not quite 1:1) to use system.util.getGlobals() if you must. (Not recomme…

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:

> [@Java concurrent queue for tag change scripts](https://forum.inductiveautomation.com/t/java-concurrent-queue-for-tag-change-scripts/82044/38):
>
> Okay. Draft for your edification: [queueUtil.py](https://www.automation-pros.com/ignition/queueUtil.py) Includes Google docstrings. Utterly untested. grimacing Maybe later. Edit: docstring typos fixed and ambiguities addressed. Heh: if you delete all comments, docstrings, and blank lines, it is 29 lines of code.

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.** _

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 25, 2025, 6:51pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/213 "2025-03-25T18:51:18Z")

</div>

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

 ![ToolkitTagActions](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/a/2aeb4b55de6ec6e7cae95232c92e75bd8a73b5a4.png)

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

---

<div class="post-metadata">

**Author:** ![michael.flagler](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/michael.flagler/32/26272_2.png) [@michael.flagler](https://forum.inductiveautomation.com/u/michael.flagler)\
**Post date:** [March 25, 2025, 7:34pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/214 "2025-03-25T19:34:41Z")

</div>

> [@pturmel](#):
>
> (I'm just missing an icon for them.)

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!

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 25, 2025, 11:36pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/215 "2025-03-25T23:36:23Z")

</div>

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

---

<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:** [March 25, 2025, 11:41pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/216 "2025-03-25T23:41:16Z")

</div>

~~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 ☹

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 26, 2025, 12:01am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/217 "2025-03-26T00:01:26Z")

</div>

> [@Kevin.Herron](#):
>
> hardcoded

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

---

<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:** [March 26, 2025, 12:07am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/218 "2025-03-26T00:07:20Z")

</div>

Yeah. The icon, though:

```python
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) }

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 26, 2025, 12:09am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/219 "2025-03-26T00:09:34Z")

</div>

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. 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [March 26, 2025, 12:27am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/220 "2025-03-26T00:27:12Z")

</div>

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.

[Previous page](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934.md?page=10)

[Next page](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934.md?page=12)
