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
#logger.info("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])
else:
header_length = 0
header_mid = 0
#Communication start acknowledge
if header_mid == 2:
msg_0060_tight_subscribe = b"002000600030 \00"
system.tag.writeBlocking(["[default]torque_tool/communication_active",
"[default]torque_tool/Writable"],
[True,
msg_0060_tight_subscribe])
#Command error
elif header_mid == 4:
logger.info("Command error: " + currentValue.value)
if len(raw_message) <> 26:
logger.info("MID0005 Invalid length. Should be 24." + raw_message)
else:
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"
system.tag.writeBlocking(["[default]torque_tool/tightening_subscribe_active",
"[default]torque_tool/Writable"],
[False,
msg_0003_comms_end])
#tightening result received
elif header_mid == 61:
#logger.info("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'] = system.date.parse(raw_message[345:364], 'yyyy-MM-dd:HH:mm:ss') #45
result_dict['parameter_last_change'] = system.date.parse(raw_message[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'] = system.date.now()
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")
#logger.info("debug2")
#time received
elif header_mid == 81:
pass
else:
logger.info("Unknown MID: " + currentValue.value)
if header_mid <> 81:
update_args = [system.date.now(), 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.