Does Ignition support Open Protocol?


we use MQTT connection to connect to all of our devices, we have some torque wrenches and other tools that requires Open Protocol to control them.

Is there a way without using any sort of middleware?


I'm gonna need you to stop asking this question over and over in new posts.

There isn't, and that hasn't changed since you asked 2 weeks ago.


I communicated with a Stanley Torque tool via the Open Protocol. I set it up as a TCP device. Then used the OPC 'Writeable' and 'Message' tags to read & write to the torque tool. The Open Protocol is just ASCII text.

I wound up changing the torque tool data collection to go through a PLC that is connected to torque tool, however. The torque tool had an AOP for data exchange with the PLC, and I'm collecting data from many PLCs so could use the same code for this data instead of all the custom code for the couple Open Protocol devices we had.

There were a few annoying issues where the TCP connection would drop if I didn't continually send a message to the torque tool.


I'd imagine using the TCP driver could be a little awkward, especially trying to specify a message delimiter that properly frames messages, but doing it from Jython scripting might not be too bad... it really depends what data you need access to.

The Open Protocol ends every message with a NULL character, so using the following settings on the TCP driver made it so the entire message was received prior to being place in the 'Message' tag:
Message Delimiter Type = CharacterBased
Message Delimiter = \u0000

The trickier/messier part was I wanted to receive the torque tool's results automatically with never any interaction with the operator. So if the torque tool ever became disconnected, it would automatically reconnect & subscribe to the results. So I had to maintain some state information - and send certain values after others. I used a tag change event script on the 'Message' tag to process the messages received, for some ideas on what to do here is that script:

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	logger = system.util.getLogger("torque_tool")
	#If value changed
	if currentValue.value != previousValue.value and not initialChange:
		raw_message = currentValue.value"length: " + raw_message + "|" + str(len(raw_message)))
		if len(raw_message)>8 and raw_message[0:8].isnumeric():
			header_length = int(raw_message[0:4])
			header_mid = int(raw_message[4:8])
			header_length = 0
			header_mid = 0
		#Communication start acknowledge
		if header_mid == 2:
			msg_0060_tight_subscribe = b"002000600030        \00"
		#Command error
		elif header_mid == 4:"Command error: " + currentValue.value)
			if len(raw_message) <> 26:"MID0005 Invalid length.  Should be 24." + raw_message)
				error_mid = raw_message[20:24]
				error_code = raw_message[24:26]
				#Error '09': Last tightening result subscription already exists
				if error_code == '09':
					system.tag.writeBlocking(["[default]torque_tool/tightening_subscribe_active"], [True])
				#Error '10': Last tightening result subscription does not exist
				elif error_code == '10':
					system.tag.writeBlocking(["[default]torque_tool/tightening_subscribe_active"], [True])
				#Error '96': Client already connected
				elif error_code == '96':
					system.tag.writeBlocking(["[default]torque_tool/communication_active"], [True])		

		#Command accepted
		elif header_mid == 5:
			accepted_mid = int(raw_message[20:24])
			#Communication stop accepted
			if accepted_mid == 3:
				system.tag.writeBlocking(["[default]torque_tool/communication_active"], [False])
			#tightening subscription accepted
			elif accepted_mid == 60:
				system.tag.writeBlocking(["[default]torque_tool/tightening_subscribe_active"], [True])
			#tightening unsubscribe accepted
			elif accepted_mid == 63:
				msg_0003_comms_end = b"002000030000        \00"
		#tightening result received
		elif header_mid == 61:"debug1")
			system.tag.writeBlocking(["[default]torque_tool/Writable"], b"002000620000        \00")
			result_dict = {}
			result_dict['cell_id']                          = int(raw_message[22:26])         #01
			result_dict['channel_id']                       = int(raw_message[28:30])         #02
			result_dict['torque_controller_name']           =     raw_message[32:57].strip()  #03
			result_dict['vin_number']                       =     raw_message[59:84]          #04
			result_dict['job_id']                           = int(raw_message[86:90])         #05
			result_dict['parameter_set_number']             = int(raw_message[92:95])         #06
			result_dict['strategy']                         = int(raw_message[22:26])         #07
			result_dict['strategy_options']                 = int(raw_message[22:26])         #08
			result_dict['batch_size']                       = int(raw_message[22:26])         #09
			result_dict['batch_counter']                    = int(raw_message[114:118])       #10
			result_dict['tightening_status']                = int(raw_message[120])           #11
			result_dict['batch_status']                    = int(raw_message[123])            #12
			result_dict['torque_status']                    = int(raw_message[126])           #13
			result_dict['angle_status']                     = int(raw_message[129])           #14
			#result_dict['rundown_angle_status']             = int(raw_message[132]) (n/a, space character)
			result_dict['current_monitoring_status']        = int(raw_message[135])           #16
			#result_dict['self_tap_status']                  = int(raw_message[138]) (n/a, space character)
			#result_dict['prevail_torque_monitoring_status'] = int(raw_message[141]) (n/a, space character)
			#result_dict['prevail_torque_compensate_status'] = int(raw_message[144]) (n/a, space character)
			#result_dict['error_status']                     = int(raw_message[147:157].strip())
			result_dict['torque_min_limit']                 = float(raw_message[159:165])/100   #21
			result_dict['torque_max_limit']                 = float(raw_message[167:173])/100   #22
			result_dict['torque_final_target']              = float(raw_message[175:181])/100   #23
			result_dict['torque']                           = float(raw_message[183:189])/100   #24
			result_dict['angle_min']                        = int(raw_message[191:196])         #25
			result_dict['angle_max']                        = int(raw_message[198:203])         #26
			result_dict['final_angle_target']               = int(raw_message[205:210])         #27
			result_dict['angle']                            = int(raw_message[212:217])         #28
			#result_dict['rundown_angle_min']                = int(raw_message[219:224])   
			#result_dict['rundown_angle_max']                = int(raw_message[226:231])
			#result_dict['rundown_angle']                    = int(raw_message[233:238])
			result_dict['current_monitoring_min']           = int(raw_message[240:243])         #32
			result_dict['current_monitoring_max']           = int(raw_message[245:248])         #33
			result_dict['current_monitoring_value']         = int(raw_message[250:253])         #34
			#result_dict['self_tap_min']                     = float(raw_message[255:261])/100
			#result_dict['self_tap_max']                     = float(raw_message[263:269])/100
			#result_dict['self_tap_torque']                  = float(raw_message[271:277])/100
			#result_dict['prevail_torque_monitoring_min']    = float(raw_message[279:285])/100
			#result_dict['prevail_torque_monitoring_max']    = float(raw_message[287:293])/100
			#result_dict['prevail_torque']                   = float(raw_message[295:301])/100
			result_dict['tightening_id']                    = int(raw_message[303:313])         #41
			#result_dict['job_sequence_number']              = int(raw_message[315:320])
			#result_dict['sync_tightening_id']               = int(raw_message[322:327])
			result_dict['tool_serial_number']               =     raw_message[329:342].strip()                               #44
			result_dict['time_stamp']                       =[345:364], 'yyyy-MM-dd:HH:mm:ss') #45
			result_dict['parameter_last_change']            =[366:385], 'yyyy-MM-dd:HH:mm:ss') #46
			result_dict['parameter_set_name']               =     raw_message[387:412].strip()                               #47
			result_dict['torque_value_units']               = int(raw_message[414])      #48
			result_dict['result_type']                      = int(raw_message[417:419])  #49
			result_dict['t_stamp']                          =
			sql_insert_query = "INSERT INTO torque_tightening_result (" + " ,".join(result_dict.keys()) + ") VALUES (" + ','.join('?'*len(result_dict)) + ")"
			system.db.runSFPrepUpdate(sql_insert_query, result_dict.values(), "chs_mfg")"debug2")
		#time received
		elif header_mid == 81:
		else:"Unknown MID: " + currentValue.value)
		if header_mid <> 81:
			update_args = [, raw_message, header_length, header_mid]
			system.db.runSFPrepUpdate(update_query, update_args, "chs_mfg")
	#If quality goes from bad --> good, initiate comms	
	elif previousValue.quality.isNotGood() and currentValue.quality.isGood():
		system.tag.writeBlocking(["[default]torque_tool/Writable"], [b"002000010000        \00"])
	#If quality goes from bad --> good, initiate comms	
	elif previousValue.quality.isGood() and currentValue.quality.isNotGood():
		system.tag.writeBlocking(["[default]torque_tool/communication_active"], False)

The details of the protocol can be found in the "Open Protocol Specification 9836 4415 01" document which can be found online. And note this script probably should be a gateway tag event script instead of the tag's value change script.