Automation Professionals' Integration Toolkit Module

Oh yeah, flatten in that case will not do what you need.

Since you are essentially trying to create a map with a variable amount of keys. You may need to use asPairs() in some form and build two lists with one being the key name and one being the key value.

I swear I have done this at least once and cannot find it.

1 Like

This works for the entire row background color, cell color is still MIA for now.

forEach(
	{this.custom.data},
	asMap(
		'value',asMap(it()),
		'style',case(getDayofWeek(it()['scheduled']),
					2, asMap('color','white','backgroundColor','blue'),
					3, asMap('backgroundColor','orange'),
					4, asMap('color','white','backgroundColor','green'),
					5, asMap('backgroundColor','yellow'),
					6, asMap('color','white','backgroundColor','purple'),
					None
				)
	)
)

Try this

edit: @Matthew.gaitan's is cleaner

forEach(
	{this.custom.test},
	asMap(
		asPairs(
			forEach(it(), 
				asList(
					it()[0], 
					asMap(
						"value", it()[1], 
						"style", asMap()
					)
				)
			)
		)
	)
)

outputs

[
  {
    "cover": {
      "value": "03-01D",
      "style": {}
    },
    "in_queue": {
      "value": 1722880635000,
      "style": {}
    },
    "serial": {
      "value": 4363915,
      "style": {}
    },
    "scheduled": {
      "value": 1723003200000,
      "style": {}
    },
    "style": {
      "value": "M9231351",
      "style": {}
    },
    "seats": {
      "value": "07-01A",
      "style": {}
    },
    "backs": {
      "value": "07-01A",
      "style": {}
    },
    "frame": {
      "value": "0M91-51",
      "style": {}
    }
  }
]
2 Likes

lol @ryan.white just beat me!

forEach(
	{this.custom.data},
	asMap(
		forEach(
			it(),
			it()[0], asMap(
				'value', it()[1],
				"style", None
			)
		)
	)
)
2 Likes

Looks like I had the second level asMap and forEach swapped. Thank you for the help @Matthew.gaitan @ryan.white

Happy to help! Kudos again to Phil for this awesome module that saves a lot of time :smiley:

2 Likes

You can also do this:

forEach(
	{this.custom.rawData},
	asMap(  // Convert list of pairs back into a map (later keys replace earlier)
		asPairs(
			it(), // The map from the rawData gets converted to pairs.
			asMap( // Updates to merge get converted to pairs
				'someKey', "someValue',
				'anotherKey', 'anotherValue'
			)
		)
	)
)

asMap() has only two form: a single arg that is a list of lists of pairs, or an even number of args as key-value pairs.

3 Likes

On ignition version 8.1.25, when attempting to use tags by itself in an expression gateway tag I get the error Error_TypeConversion("class java.lang.Object: Invalid DataType for Dataset.")

My gateway tag is configured as such:

Gateway Tag Configuration

TagsSetup1

ActiveCells is another gateway expression tag configured to be a dataset with the following expression:

unionAll(
		asMap("path", "str"),
		forEach(
			{[.]Cell_Count},
			asMap("path",
				if(
					it() = 0,
					"[.]../Cell_V",
					"[.]../Cell_V " + (it() + 1)
			)
		)
	)
)

that produces the following dataset:

"#NAMES"
"path"
"#TYPES"
"str"
"#ROWS","3"
"[.]../Cell_V"
"[.]../Cell_V 2"
"[.]../Cell_V 3"

However, if I use tags within another expression such as

mean(
	forEach(
		tags({[.]ActiveCells}),
		it()[1]
	)
)

I have no errors and the math matches the value provided by brute forcing the expression like mean({[.]Cell_V},{[.]Cell_V 2},{[.]Cell_V 3})

If I apply the expression tags([tagProvider]path/to/ActiveCells), and apply it to a perspective property, it presents as a dataset type as expected.

Many of my Toolkit functions are chameleons--their return type can vary based on the argument types given. Ignition expects functions to declare their return type, and chameleons must declare as java.lang.Object. (tags() returns a dataset when given a dataset, but a list of pairs when given a list.)

Perspective bindings are lax and just take whatever comes out of an expression. Expression tags check the function's declared return type against the tag's configured data type. For this situation, just wrap tags() inside a toDataSet() type casting function.

Unfortunately that results in the same error as well. I even deleted and rebuilt the tag in case it was something that had stuck with the tag.

Tag configuration

TagsSetup2

toDataSet(
	tags(
		{[.]ActiveCells}
	)
)

Could this be something specific to 8.1.25? We are looking to update the version soon™

Wait! I know what this is. I declare the value column coming back from tags() to also be java.lang.Object, as I do not know ahead of time what the tag value types are. Ignition has a whitelist of allowed column types for tags.

You will need to wrap tags() in a unionAll() that explicitly coerces the second column's values into a single numeric type.

1 Like

That did the trick! Thank you for your help.

New beta production release: v2.0.18.242262035

Added a new expression function: parsePath()

This function accepts a tag path or qualified path in string format and produces a dataset with the various qualified components broken out. If necessary, additional strings may be supplied, and the result dataset will have a row for each.

(I've been frustrated with the need for scripts to produce proper historical paths from simple tag paths.)

I'm using this with a client right now, so will likely be declared production grade soon.

11 Likes

Using V2.0.16(b241352008)

Getting the following NPE:

Null Pointer Exception
Stop Occurances.Root Container.Packaging Group.packagingMinsTable.summedData
	at com.inductiveautomation.factorypmi.application.binding.ExpressionPropertyAdapter.runExpression(ExpressionPropertyAdapter.java:92)
	at com.inductiveautomation.factorypmi.application.binding.ExpressionPropertyAdapter$1.run(ExpressionPropertyAdapter.java:59)
	at com.inductiveautomation.ignition.client.util.EDTUtil$ProcessQueue.run(EDTUtil.java:126)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(Unknown Source)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.run(Unknown Source)
Caused by: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "values[0]" is null
	at com.automation_pros.simaids.expressions.AsMap.execute(AsMap.java:58)
	at com.inductiveautomation.ignition.client.expressions.ClientDynamicDispatchFunction.execute(ClientDynamicDispatchFunction.java:43)
	at com.inductiveautomation.ignition.client.expressions.ClientDynamicDispatchFunction.execute(ClientDynamicDispatchFunction.java:43)
	at com.inductiveautomation.ignition.common.expressions.FunctionExpression.execute(FunctionExpression.java:69)
	at com.inductiveautomation.ignition.common.expressions.SubscriptExpression.execute(SubscriptExpression.java:50)
	at com.inductiveautomation.ignition.common.expressions.functions.BaseFunction.executeAll(BaseFunction.java:64)
	at com.automation_pros.simaids.expressions.SimpleIterator.execute(SimpleIterator.java:177)
	at com.inductiveautomation.ignition.client.expressions.ClientDynamicDispatchFunction.execute(ClientDynamicDispatchFunction.java:43)
	at com.inductiveautomation.ignition.client.expressions.ClientDynamicDispatchFunction.execute(ClientDynamicDispatchFunction.java:43)
	at com.inductiveautomation.ignition.common.expressions.FunctionExpression.execute(FunctionExpression.java:69)
	at com.inductiveautomation.factorypmi.application.binding.ExpressionPropertyAdapter.runExpression(ExpressionPropertyAdapter.java:83)
	... 15 common frames omitted
Expression producing exception
selectStar(
		{Root Container.Packaging Group.packagingMinsTable.pivotedData},
		asMap(	
			forEach(
				groupBy(
					{Root Container.Packaging Group.packagingMinsTable.last8HistoricalData},
					it()['path']
				),
				asList(
					it()[0],
					round(sum(it()[1],'value')/60.0,1)
				)
			)
		)[it()['Occurrence']],
		'Last 8',
		'F',
		asMap(
			forEach(
				groupBy(
					{Root Container.Packaging Group.packagingMinsTable.historicalData},
					it()['path']
				),
				asList(
					it()[0],
					round(sum(it()[1],'value') / 60.0,1)
				)
			)
		)[it()['Occurrence']],
		'Last 24',
		'F'
	)
Sample pivotedData
"#NAMES"
"Occurrence","_7A","_8A","_9A","_10A","_11A","_12P","_1P","_2P","_3P","_4P","_5P","_6P","_7P","_8P","_9P","_10P","_11P","_12A","_1A","_2A","_3A","_4A","_5A","_6A"
"#TYPES"
"str","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F"
"#ROWS","5"
"productlevel_at_sp","60.0","60.0","60.0","60.0","60.0","47.2","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0"
"cap_supply","60.0","60.0","60.0","60.0","60.0","47.2","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0"
"downstream_stop_request","60.0","60.0","60.0","60.0","60.0","47.2","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0"
"empty_bottles_present","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0","0.0"
"empty_bottles_primied","60.0","60.0","60.0","60.0","60.0","47.2","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0","60.0"
Sample historicalData
"#NAMES"
"path","value","quality","timestamp"
"#TYPES"
"str","L","I","date"
"#ROWS","120"
"Stacker","0","192","2024-08-14 11:00:00.000"
"Dynac","564","192","2024-08-14 11:00:00.000"
"Labeler","561","192","2024-08-14 11:00:00.000"
"Laner","121","192","2024-08-14 11:00:00.000"
"Caser","416","192","2024-08-14 11:00:00.000"
"Stacker","25","192","2024-08-14 12:00:00.000"
"Dynac","0","192","2024-08-14 12:00:00.000"
"Labeler","0","192","2024-08-14 12:00:00.000"
"Laner","0","192","2024-08-14 12:00:00.000"
"Caser","0","192","2024-08-14 12:00:00.000"
"Stacker","0","192","2024-08-14 13:00:00.000"
"Dynac","0","192","2024-08-14 13:00:00.000"
"Labeler","371","192","2024-08-14 13:00:00.000"
"Laner","0","192","2024-08-14 13:00:00.000"
"Caser","0","192","2024-08-14 13:00:00.000"
"Stacker","0","192","2024-08-14 14:00:00.000"
"Dynac","1087","192","2024-08-14 14:00:00.000"
"Labeler","270","192","2024-08-14 14:00:00.000"
"Laner","86","192","2024-08-14 14:00:00.000"
"Caser","1919","192","2024-08-14 14:00:00.000"
"Stacker","160","192","2024-08-14 15:00:00.000"
"Dynac","143","192","2024-08-14 15:00:00.000"
"Labeler","948","192","2024-08-14 15:00:00.000"
"Laner","0","192","2024-08-14 15:00:00.000"
"Caser","1049","192","2024-08-14 15:00:00.000"
"Stacker","1118","192","2024-08-14 16:00:00.000"
"Dynac","0","192","2024-08-14 16:00:00.000"
"Labeler","4","192","2024-08-14 16:00:00.000"
"Laner","212","192","2024-08-14 16:00:00.000"
"Caser","1809","192","2024-08-14 16:00:00.000"
"Stacker","135","192","2024-08-14 17:00:00.000"
"Dynac","0","192","2024-08-14 17:00:00.000"
"Labeler","0","192","2024-08-14 17:00:00.000"
"Laner","3391","192","2024-08-14 17:00:00.000"
"Caser","0","192","2024-08-14 17:00:00.000"
"Stacker","3168","192","2024-08-14 18:00:00.000"
"Dynac","0","192","2024-08-14 18:00:00.000"
"Labeler","0","192","2024-08-14 18:00:00.000"
"Laner","0","192","2024-08-14 18:00:00.000"
"Caser","0","192","2024-08-14 18:00:00.000"
"Stacker","0","192","2024-08-14 19:00:00.000"
"Dynac","0","192","2024-08-14 19:00:00.000"
"Labeler","0","192","2024-08-14 19:00:00.000"
"Laner","0","192","2024-08-14 19:00:00.000"
"Caser","0","192","2024-08-14 19:00:00.000"
"Stacker","673","192","2024-08-14 20:00:00.000"
"Dynac","0","192","2024-08-14 20:00:00.000"
"Labeler","0","192","2024-08-14 20:00:00.000"
"Laner","0","192","2024-08-14 20:00:00.000"
"Caser","9","192","2024-08-14 20:00:00.000"
"Stacker","1194","192","2024-08-14 21:00:00.000"
"Dynac","208","192","2024-08-14 21:00:00.000"
"Labeler","587","192","2024-08-14 21:00:00.000"
"Laner","0","192","2024-08-14 21:00:00.000"
"Caser","985","192","2024-08-14 21:00:00.000"
"Stacker","0","192","2024-08-14 22:00:00.000"
"Dynac","1474","192","2024-08-14 22:00:00.000"
"Labeler","108","192","2024-08-14 22:00:00.000"
"Laner","0","192","2024-08-14 22:00:00.000"
"Caser","1568","192","2024-08-14 22:00:00.000"
"Stacker","0","192","2024-08-14 23:00:00.000"
"Dynac","0","192","2024-08-14 23:00:00.000"
"Labeler","1588","192","2024-08-14 23:00:00.000"
"Laner","0","192","2024-08-14 23:00:00.000"
"Caser","1710","192","2024-08-14 23:00:00.000"
"Stacker","0","192","2024-08-15 00:00:00.000"
"Dynac","2651","192","2024-08-15 00:00:00.000"
"Labeler","1467","192","2024-08-15 00:00:00.000"
"Laner","0","192","2024-08-15 00:00:00.000"
"Caser","0","192","2024-08-15 00:00:00.000"
"Stacker","0","192","2024-08-15 01:00:00.000"
"Dynac","63","192","2024-08-15 01:00:00.000"
"Labeler","374","192","2024-08-15 01:00:00.000"
"Laner","0","192","2024-08-15 01:00:00.000"
"Caser","130","192","2024-08-15 01:00:00.000"
"Stacker","0","192","2024-08-15 02:00:00.000"
"Dynac","110","192","2024-08-15 02:00:00.000"
"Labeler","0","192","2024-08-15 02:00:00.000"
"Laner","0","192","2024-08-15 02:00:00.000"
"Caser","276","192","2024-08-15 02:00:00.000"
"Stacker","0","192","2024-08-15 03:00:00.000"
"Dynac","461","192","2024-08-15 03:00:00.000"
"Labeler","0","192","2024-08-15 03:00:00.000"
"Laner","0","192","2024-08-15 03:00:00.000"
"Caser","777","192","2024-08-15 03:00:00.000"
"Stacker","19","192","2024-08-15 04:00:00.000"
"Dynac","768","192","2024-08-15 04:00:00.000"
"Labeler","615","192","2024-08-15 04:00:00.000"
"Laner","0","192","2024-08-15 04:00:00.000"
"Caser","672","192","2024-08-15 04:00:00.000"
"Stacker","121","192","2024-08-15 05:00:00.000"
"Dynac","103","192","2024-08-15 05:00:00.000"
"Labeler","54","192","2024-08-15 05:00:00.000"
"Laner","0","192","2024-08-15 05:00:00.000"
"Caser","238","192","2024-08-15 05:00:00.000"
"Stacker","239","192","2024-08-15 06:00:00.000"
"Dynac","813","192","2024-08-15 06:00:00.000"
"Labeler","230","192","2024-08-15 06:00:00.000"
"Laner","0","192","2024-08-15 06:00:00.000"
"Caser","362","192","2024-08-15 06:00:00.000"
"Stacker","550","192","2024-08-15 07:00:00.000"
"Dynac","0","192","2024-08-15 07:00:00.000"
"Labeler","0","192","2024-08-15 07:00:00.000"
"Laner","34","192","2024-08-15 07:00:00.000"
"Caser","773","192","2024-08-15 07:00:00.000"
"Stacker","0","192","2024-08-15 08:00:00.000"
"Dynac","598","192","2024-08-15 08:00:00.000"
"Labeler","214","192","2024-08-15 08:00:00.000"
"Laner","37","200","2024-08-15 08:00:00.000"
"Caser","302","192","2024-08-15 08:00:00.000"
"Stacker","553","200","2024-08-15 09:00:00.000"
"Dynac","0","200","2024-08-15 09:00:00.000"
"Labeler","77","200","2024-08-15 09:00:00.000"
"Laner","0","200","2024-08-15 09:00:00.000"
"Caser","1306","200","2024-08-15 09:00:00.000"
"Stacker","0","200","2024-08-15 10:00:00.000"
"Dynac","0","200","2024-08-15 10:00:00.000"
"Labeler","0","200","2024-08-15 10:00:00.000"
"Laner","0","200","2024-08-15 10:00:00.000"
"Caser","0","200","2024-08-15 10:00:00.000"

Sample last8HistoricalData
"#NAMES"
"path","value","quality","timestamp"
"#TYPES"
"str","L","I","date"
"#ROWS","40"
"Stacker","0","192","2024-08-15 03:00:00.000"
"Dynac","461","192","2024-08-15 03:00:00.000"
"Labeler","0","192","2024-08-15 03:00:00.000"
"Laner","0","192","2024-08-15 03:00:00.000"
"Caser","777","192","2024-08-15 03:00:00.000"
"Stacker","19","192","2024-08-15 04:00:00.000"
"Dynac","768","192","2024-08-15 04:00:00.000"
"Labeler","615","192","2024-08-15 04:00:00.000"
"Laner","0","192","2024-08-15 04:00:00.000"
"Caser","672","192","2024-08-15 04:00:00.000"
"Stacker","121","192","2024-08-15 05:00:00.000"
"Dynac","103","192","2024-08-15 05:00:00.000"
"Labeler","54","192","2024-08-15 05:00:00.000"
"Laner","0","192","2024-08-15 05:00:00.000"
"Caser","238","192","2024-08-15 05:00:00.000"
"Stacker","239","192","2024-08-15 06:00:00.000"
"Dynac","813","192","2024-08-15 06:00:00.000"
"Labeler","230","192","2024-08-15 06:00:00.000"
"Laner","0","192","2024-08-15 06:00:00.000"
"Caser","362","192","2024-08-15 06:00:00.000"
"Stacker","550","192","2024-08-15 07:00:00.000"
"Dynac","0","192","2024-08-15 07:00:00.000"
"Labeler","0","192","2024-08-15 07:00:00.000"
"Laner","34","192","2024-08-15 07:00:00.000"
"Caser","773","192","2024-08-15 07:00:00.000"
"Stacker","0","192","2024-08-15 08:00:00.000"
"Dynac","598","192","2024-08-15 08:00:00.000"
"Labeler","214","192","2024-08-15 08:00:00.000"
"Laner","37","200","2024-08-15 08:00:00.000"
"Caser","302","192","2024-08-15 08:00:00.000"
"Stacker","553","200","2024-08-15 09:00:00.000"
"Dynac","0","192","2024-08-15 09:00:00.000"
"Labeler","77","192","2024-08-15 09:00:00.000"
"Laner","0","200","2024-08-15 09:00:00.000"
"Caser","1306","192","2024-08-15 09:00:00.000"
"Stacker","0","200","2024-08-15 10:00:00.000"
"Dynac","251","192","2024-08-15 10:00:00.000"
"Labeler","2268","192","2024-08-15 10:00:00.000"
"Laner","0","200","2024-08-15 10:00:00.000"
"Caser","563","192","2024-08-15 10:00:00.000"

I have corrected why values[0] was null, but thought you would like to have the report either way.

Indeed. Simple fix--will be in next release.

Hey Phil, it would be really good to get some more examples into your docs for your toolkit functions. I'm looking at you, https://www.automation-pros.com/toolkit/doc/iteration.html#iterables :grin:

5 Likes

Yeah, I've thought about posting some recipes for common expressions. I'll tackle this in my copious free time.

2 Likes

Meanwhile, I'm declaring the above beta a production release. It has survived a week of (ab)use at the client mentioned.

1 Like

Yeah, I keep seeing lots of good feedback about the module, but haven't had the time to dig into its uses, but more examples would help me out too. Maybe I don't know what I'm missing out on! To be fair, @pturmel has put a lot of work into this module that anyone can download and use for free, so the amount of documentation already is exceedingly better than most Exchange resources.

5 Likes

The ability to iterate through data in an expression is a gamechanger. The performance gains alone are worth it. Things that can only be done with a script transform in perspective are now completely achievable in a pure expression binding.

7 Likes