Running SIP/CIP Scripts for Tag Triggers to Create Reports and Save in PDF Folder

When I pull the logs from the gateway it reads:

2024-07-14 02:05:57:061 gateway-tags-eventscripts-2 ([default]Hassia1/Reporting/SIPReporting/CycleEnd, valueChanged) Error executing tag event script: Traceback (most recent call last): File "<tagevent:valueChanged>", line 4, in valueChanged AttributeError: 'NoneType' object has no attribute 'getValue'
com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last):
  File "<tagevent:valueChanged>", line 4, in valueChanged
AttributeError: 'NoneType' object has no attribute 'getValue'

.getvalue is to get a dataset. I believe this might be causing the issue. I would like some feedback if that's the case of what I am missing. I inserted the script below:


	# Eliminate any executions if the value is still the same and also not the initialization.
	#MCSI EDIT: Need to verify script only runs on false to true transition, so currentValue.value added as additional condition
	if currentValue.value and  previousValue.getValue() is not None and previousValue.getValue() == 0 and currentValue.getValue() == 1 and not initialChange:
		
		# Construct the tag path for the current cycle ID tag so we can read from it and write to it.
		tagPathParts = tagPath.split('/')
		currentCycleIDPath = '/'.join(tagPathParts[:-1]) + '/CurrentCycleID'
			
		# Create a list of tags that need to be read before executing any other functions.
		pathsToRead = [
				'[.]Parameters/ThisSIPLine'
			,	'[.]Parameters/MaxCycleTimeHours'
			,	'[.]Parameters/MinCycleTimeSeconds'
			,	'[.]Parameters/QueueTagPath'
			,	currentCycleIDPath
			,	'[.]Parameters/ReportConfigurations'
		]

		# Define a method to process the tag values.
		def processTagValues(valuesRead):
			"""Process the tag values read by the async function.
			
			Note: valuesRead will contain the values at the paths specified
				in the order they were specified.
			
			"""
			from java.math import BigDecimal
			
			# Define some inner functions.				
			def processQualifier(qual, LOGGER, currentCycleID):
				from java.math import BigDecimal
				
				# Ensure all required keys are present
				if all(k in qual.keys() for k in ['key', 'value', 'valueType', 'comparisonType']):
					
					# Extract the values.
					key, value, valueType, comparisonType = str(qual['key']), qual['value'], qual['valueType'], qual['comparisonType']
					
					# Attempt to perform data type conversions.
					try:
						if valueType == 'str':
							value = str(value)
						elif valueType == 'int':
							if isinstance(value, BigDecimal):
								value = value.intValue()
							else:
								value = int(value)
						elif valueType == 'float':
							value = float(value)
						elif valueType == 'boolean':
							if isinstance(value, str) or isinstance(value, unicode):
								if value.upper() == 'TRUE':
									value = True
								elif value.upper() == 'FALSE':
									value = False
							value = bool(value)
					except ValueError, e:
						LOGGER.warn('For SIP report ID: %s, processQualifier: unable to convert qualifier value to desired type.' % str(currentCycleID))
						return False
						
					if comparisonType == 'equal':	
						
						# No comparisons defined.
						if 1 == 1:
							pass
						else:
							LOGGER.warn('For SIP report ID: %s, processQualifier: an equal comparison type was made with no result.' % str(currentCycleID))
							return False
					
					elif comparisonType == 'notEqual':
						
						# No comparisons defined.
						if 1 == 1:
							pass
						else:
							LOGGER.warn('For SIP report ID: %s, processQualifier: an notEqual comparison type was made with no result.' % str(currentCycleID))
							return False
							
					else:
						LOGGER.warn('For SIP report ID: %s, processQualifier: comparison type not found.' % str(currentCycleID))
						return False
					
				else:
					LOGGER.warn('For SIP report ID: %s, processQualifier: not all required keys present.' % str(currentCycleID))
					return False
			
			# Define the variables we need.
			thisSIPLine, queueTagPath = '', ''
			maxCycleTimeHours, minCycleTimeSeconds, currentCycleID = 300, 24, 0 
			reportConfigurations = {}
			approvedReports = []
			
			# Extract the tag values using a loop to prevent any ArrayIndexOutOfBounds conditions.
			for i in range(len(valuesRead)):
				if i == 0:
					thisSIPLine = str(valuesRead[i].getValue())
				elif i == 1:
					maxCycleTimeHours = int(valuesRead[i].getValue())
				elif i == 2:
					minCycleTimeSeconds = int(valuesRead[i].getValue())		
				elif i == 3:
					queueTagPath = str(valuesRead[i].getValue())
				elif i == 4:
					currentCycleID = int(valuesRead[i].getValue())
				elif i == 5:
					reportConfigurations = valuesRead[i].getValue().toDict()
		
			# Define a logger that we can use to write to the system log if errors happen
			LOGGER = system.util.getLogger('SIPTagEventLogger-%s' % thisSIPLine)
						
			# The cycle is ending. Update the database and obtain back information for reporting.
			cycleData = system.db.runNamedQuery(
					'global'
				,	'SIP/endCycle'
				,	{
							'cycleID' : currentCycleID
						,	'minCycleTimeSeconds' : minCycleTimeSeconds
						,	'maxCycleTimeHours' : maxCycleTimeHours
					}	
			)
			
			# Extract from the returned data.
			cycleData = system.dataset.toPyDataSet(cycleData)
			
			# Define the variables to hold the data we want to extract from the returned data.
			isShortCycle, isLongCycle = None, None
			
			# Define a variable to determine if we want to add to the queue or not.
			isAddToQueue = True
			
			# Ensure we actually obtained data.
			if len(cycleData) > 0:
				isShortCycle = cycleData[0]['isShortCycle']
				isLongCycle = cycleData[0]['isLongCycle']
			else:
				isAddToQueue = False
				
			# Validations
			if isShortCycle:
				isAddToQueue = False
				LOGGER.warn('For SIP report ID: %s, the duration of the cycle was below the minimum threshold of %s seconds. Report excluded from queue.' % (str(currentCycleID), str(minCycleTimeSeconds)))
				
			if isLongCycle:
				isAddToQueue = False
				LOGGER.warn('For SIP report ID: %s, the duration of the cycle was above the maximum threshold of %s hours. Report excluded from queue.' % (str(currentCycleID), str(maxCycleTimeHours)))	

			# Ensure the report configurations has at least the reports key.
			if 'reports' not in reportConfigurations.keys():
				isAddToQueue = False
				LOGGER.warn('For SIP report ID: %s, the report configuration was not strucutred correctly. Report excluded from queue.' % (str(currentCycleID)))
			
			if isAddToQueue:
				# Call the function to add the report to the queue.
				
				# But first, extract data from the report configurations.
				for report in reportConfigurations['reports']:
					
					addIndividualReportToQueue = True
					
					# Extract and find things we may have to substitute.		
					if 'identifier' in report.keys():
						identifier = report['identifier']
					else:
						identifier = None
						addIndividualReportToQueue = False
						LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown identifier). Details: %s.' % (str(currentCycleID), str(report)))
						
					if 'reportType' in report.keys():
						reportType = report['reportType']
					else:
						reportType = None
						addIndividualReportToQueue = False
						LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportType). Details: %s.' % (str(currentCycleID), str(report)))
						
					if 'delay' in report.keys():
						if isinstance(report['delay'], BigDecimal):
							delay = report['delay'].intValue()
						else:
							delay = int(report['delay'])
					else:
						delay = None
						addIndividualReportToQueue = False
						LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown delay). Details: %s.' % (str(currentCycleID), str(report)))
					
					if 'reportConfig' in report.keys():
						reportConfig = report['reportConfig']
					else:
						reportConfig = None
						addIndividualReportToQueue = False
						LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig). Details: %s.' % (str(currentCycleID), str(report)))
						
					if reportConfig:
						if 'reportProject' in reportConfig.keys():
							reportProject = reportConfig['reportProject']
						else:
							reportProject = None
							addIndividualReportToQueue = False
							LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.reportProject). Details: %s.' % (str(currentCycleID), str(report)))
					
						if 'reportPath' in reportConfig.keys():
							reportPath = reportConfig['reportPath']
						else:
							reportPath = None
							addIndividualReportToQueue = False
							LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.reportPath). Details: %s.' % (str(currentCycleID), str(report)))
					
						if 'action' in reportConfig.keys():
							action = reportConfig['action']
						else:
							action = None
							addIndividualReportToQueue = False
							LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.action). Details: %s.' % (str(currentCycleID), str(report)))
						
						if 'actionSettings' in reportConfig.keys():
							actionSettings = reportConfig['actionSettings']
						else:
							actionSettings = None 
							addIndividualReportToQueue = False
							LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.action). Details: %s.' % (str(currentCycleID), str(report)))
						
						if actionSettings:
							
							if action == 'save':
								if 'path' in actionSettings.keys():
									path = actionSettings['path']
									if path == 'lookup':
										path = ''
										actionSettings['path'] = path
								else:
									path = None
									addIndividualReportToQueue = False
									LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.actionSettings.path). Details: %s.' % (str(currentCycleID), str(report)))
									
								if 'fileName' in actionSettings.keys():
									fileName = actionSettings['fileName']
									if fileName == 'Summary_Date_CycleID':
										fileName = 'Summary_%s_%s' % (system.date.format(system.date.now(), "yyyy-MM-dd HHmm"), str(currentCycleID))
										actionSettings['fileName'] = fileName
									#Added for fixed axes reports -- Kevin Waldron 6/15/22  
									elif fileName == 'Summary_FixedAxes_Date_CycleID':
										fileName = 'Summary_FixedAxes_%s_%s' % (system.date.format(system.date.now(), "yyyy-MM-dd HHmm"), str(currentCycleID))
										actionSettings['fileName'] = fileName
								else:
									fileName = None
									addIndividualReportToQueue = False
									LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.actionSettings.fileName). Details: %s.' % (str(currentCycleID), str(report)))
								
								if 'format' in actionSettings.keys():
									format = actionSettings['format']
								else:
									format = None
									addIndividualReportToQueue = False
									LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.actionSettings.format). Details: %s.' % (str(currentCycleID), str(report)))
							
						if 'parameters' in reportConfig.keys():
							parameters = reportConfig['parameters']
						else:
							parameters = None
							addIndividualReportToQueue = False
							LOGGER.warn('For SIP report ID: %s, a configured report has been excluded from the queue (unknown reportConfig.parameters). Details: %s.' % (str(currentCycleID), str(report)))
							
						if parameters:
							if 'runID' in parameters.keys():
								if parameters['runID'] == 'currentCycleID':
									parameters['runID'] = currentCycleID
									
						if 'executionQualifiers' in report.keys():
							executionQualifiersResults = [processQualifier(qual, LOGGER, currentCycleID) for qual in report['executionQualifiers']]
							
							if not all(executionQualifiersResults):
								addIndividualReportToQueue = False
								LOGGER.info('For SIP report ID: %s, a configured report has been excluded from the queue (not all qualifiers met). Details: %s.' % (str(currentCycleID), str(report)))
									
						if 'allowExecution' in report.keys():
							allowExecution = report['allowExecution']
							if isinstance(allowExecution, str) or isinstance(allowExecution, unicode):
								if allowExecution.upper() == 'TRUE':
									allowExecution = True
								elif allowExecution.upper() == 'FALSE':
									allowExecution = False
									
								allowExecution = bool(allowExecution)
								
							if not allowExecution:
								addIndividualReportToQueue = False
								LOGGER.info('For SIP report ID: %s, a configured report has been excluded from the queue (allowExecution=False). Details: %s.' % (str(currentCycleID), str(report)))
					
#					# All the data valid and we should add this report?
#					if addIndividualReportToQueue:
#						# Construct the reportConfig.
#						thisReportConfig = {
#								'reportProject' : reportProject
#							,	'reportPath' : reportPath
#							,	'action' : action
#							,	'actionSettings' : actionSettings
#							,	'parameters' : parameters
#						}
#					
#						# Add the report to the queue.
#						shared.reporting.queue.add(
#								queueTagPath = queueTagPath
#							, 	delay = delay
#							, 	reportConfig = thisReportConfig
#							, 	identifier = identifier
#							, 	reportType = reportType
#						)
					# All the data valid and we should add this report?
					if addIndividualReportToQueue:
						approvedReports.append({
								'delay' : delay
							,	'reportConfig' : {
										'reportProject' : reportProject
									,	'reportPath' : reportPath
									,	'action' : action
									,	'actionSettings' : actionSettings
									,	'parameters' : parameters
								}
							,	'identifier' : identifier
							,	'reportType' : reportType
						})	
					
			# Add all the approved reports to the queue.
			shared.reporting.queue.add(
					queueTagPath
				,	approvedReports
			)
							
			# Update the current cycle ID to zero to indicate that we aren't in a cycle.
			system.tag.writeAsync([currentCycleIDPath], [0])

		# Read the tags and call the processing function.
		system.tag.readAsync(pathsToRead, processTagValues)	

Have you tried simply previousValue.value just like you have for currentValue? When you are running a tag event script, the returned objects like previousValue and currentValue are qualified values. They have 3 properties...the .value and the .quality and .timestamp. Once you obtain the .value of the tag, then if it is a dataset, you can issue the .getValueAt() function.

Line 4 contains these two checks, which are causing your issue:

previousValue.getValue() is not None and previousValue.getValue() == 0

You need to verify that the entire previousValue object itself is not null.

When you say ".getValue()" is that the same as ".getValueAt"?

In the link below Ignition only has .getValueAt

Typo. the .getValueAt(row,column) function can be used after you have obtained the previousValue.value variable and made sure it is not None...(and assuming it really is a dataset).

I would not use .getValue()...instead, simply use .value just like you did with the currentValue.

image

Hello guys.
Thanks to Paul and David for taking a look at this. I spent a few hours tonight looking at this project and have a few screenshots to qualify the Gateway Error Raul is seeing in the Script he posted.
This reporting application was authored by a long-gone developer. At least he left us decent comments!
I don't understand why.. for a lot here.
Raul just needs to make it work and not reinvent it.
There are other reports scripted the same way running without error on the same Gateway.
The script Raul posted is in the ValueChanged slot on and Expression Tag. There are no datasets. This is not .GetValueAt. It is .GetValue() (from python?). The tag value comes from the expression... Just check out these screenshots.
CycleEnd and CycleStart are the expression tags. They are scripted similarly.




Given that. I am thinking since these scripts work elsewhere that what Paul said about the previousValue object being null might be spot on. This report has never run, so it's not initialized.

Raul (and I) only have access to the Dev environment, the gateway errors are off the production gateway. I am going to request someone with production access give us the status of the tags tomorrow to check this theory.

We would still like to better understand this .getValue() business and If anyone more experience understands what why this was done this way, we would appreciate an explanation. Thanks

I had a chance to look at this project and I think you are right here, but I have no way to verify. Check out my post on the main thread please. Thanks for the help.