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
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
Wow, thanks for the speedy response! That fixed it right up.
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.
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.
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.
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.)
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
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.
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.