################################################################ ################################################################ ## Version: 1.0 / Author: Dillon Uzar ## ## DESC: For use in WCS Sorting Lane Lookup & Recording ## WARN: Modifying code may cause system to function incorrectly ################################################################ ################################################################ from __future__ import with_statement import csv import os import random import re import string import system import time from os import path ####################################################### ####################################################### ####################################################### #### Constants ####################################################### # Logger: LOG = system.util.logger("WCS Sorting Handler") # Defaults for function arguments: PROGRAM_PATH = "" CARTON_PATH = "WCS_Package" DEFAULT_DEVICE = "MCP20" DEFAULT_OPC_SERVER = "Ignition OPC UA Server" # Configuration variables: MAX_CARTONS = 100 # Max possible carton IDs MAX_BARCODE_SIZE = 82 # Max possible size of full barcode # For extracting PLC info: PACKAGE_DATA = [ #["Induct_ID", "induct"], # The induct the package was inducted from #["Scanner_ID", "scanner"], # The scanner the package was scanned from #["Sorter_ID", "sorter"], # The sorter the package was sorted from ["BCR_Img_ID", "bcr_imgid"], # Barcode reader, sequence ID ["BCR_Seq_ID", "bcr_seqid"], # Barcode reader, image ID ["Length", "pkg_length"], # Package length in units of 1/10". Only populated if available, use 0 if not ["Width", "pkg_width"], # Package width in units of 1/10". Only populated if available, use 0 if not ["Height", "pkg_height"], # Package height in units of 1/10". Only populated if available, use 0 if not ["Carriers", "carriers"], # Num of carriers (trays/shoes) on sorter that the package is occupying. Only populated if available, use 0 if not ["Gap_Leading", "gap_leading"], # Leading edge gap distance in units of 1/10". Only populated if available, use 0 if not. If value exceeds INT size, cap it to the max INT value ["Gap_Trailing", "gap_trailing"], # Trailing edge gap distance in units of 1/10". Only populated if available, use 0 if not. If value exceeds INT size, cap it to the max INT value ["S01_Barcode", "s01_barcode"], # The barcode sent in the S01 message ["S02_Req_Dest_ID", "s02_req_dest"], # The requested destination ID sent in the S02 message ["S02_Alt_Dest_ID", "s02_alt_dest"], # The alternate destination ID sent in the S02 message ["S04_PLC_ID", "s04_plc_id"], # Internal PLC number used to track packages and cross reference to the Host ID ["S04_Host_ID", "s04_host_id"], # The PLC record number sent in the S04 message, (1 - 9999) ["S04_Req_Dest_ID", "s04_req_dest"], # The requested destination ID sent in the S04 message ["S04_Act_Dest_ID", "s04_act_dest"], # The actual destination ID sent in the S04 message ["S04_Sort_Code", "s04_sort_code"], # The reason code sent in the S04 message ["Req_Dest_Reason", "req_dest_reason"], # See Destination Reason Bit-Map sheet ["Alt_Dest_Reason", "alt_dest_reason"] # See Destination Reason Bit-Map sheet ] TIMESTAMP_DATA = [ ["S01_Timestamp_H", "s01_timestamp"], # The timestamp sent in the S01 message, Upper 32-bits, UTS in microseconds ["S01_Timestamp_L", "s01_timestamp"], # The timestamp sent in the S01 message, Lower 32-bits, UTS in microseconds ["S02_Timestamp_H", "s02_timestamp"], # The timestamp sent in the S02 message, Upper 32-bits, UTS in microseconds ["S02_Timestamp_L", "s02_timestamp"], # The timestamp sent in the S02 message, Lower 32-bits, UTS in microseconds ["S04_Timestamp_H", "s04_timestamp"], # The timestamp sent in the S04 message, Upper 32-bits, UTS in microseconds ["S04_Timestamp_L", "s04_timestamp"] # The timestamp sent in the S04 message, Lower 32-bits, UTS in microseconds ] # Generate array of paths to read: PACKAGE_PATHS = [v[0] for v in PACKAGE_DATA] TIMESTAMP_PATHS = [v[0] for v in TIMESTAMP_DATA] DATA_PATHS = PACKAGE_PATHS + TIMESTAMP_PATHS # For inserting data into database: PACKAGE_COLS = [v[1] for v in PACKAGE_DATA] PACKAGE_VALS = ["?"]*len(PACKAGE_COLS) TIMESTAMP_COLS = [TIMESTAMP_DATA[i][1] for i in range(len(TIMESTAMP_DATA)) if TIMESTAMP_DATA[i][1] in (v[1] for v in TIMESTAMP_DATA[:i])] TIMESTAMP_VALS = ["FROM_UNIXTIME(((?&(POWER(2, 32)-1))*POWER(2, 32)+(?&(POWER(2, 32)-1)))/1000000)"]*len(TIMESTAMP_COLS) UNIQUE_COLS = ["trackid", "induct", "scanner", "sorter"] + PACKAGE_COLS + TIMESTAMP_COLS UNIQUE_VALS = ["?"]*4 + PACKAGE_VALS + TIMESTAMP_VALS CONFIRM_INSERT_QUERY = "INSERT IGNORE INTO package_history (" + ",".join(UNIQUE_COLS) + ") VALUES (" + ",".join(UNIQUE_VALS) + ")" ####################################################### ####################################################### ####################################################### #### Parsing Utils ####################################################### def isNoRead(field): return field.replace("?","") == "" def isMultiRead(field): return field.replace("#","") == "" def isBadRead(field): return (field == None or isNoRead(field) or isMultiRead(field)) def logTime(title, trackID, seconds): millisec = round(seconds * 1000, 1) LOG.info("%s[ID=%s] took %sms to process" % (title, trackID, millisec)) ####################################################### ####################################################### ####################################################### #### PLC Event Handling ####################################################### def processConfirmAsync(trackID, induct, scanner, sorter, program=PROGRAM_PATH, carton=CARTON_PATH, device=DEFAULT_DEVICE, opcServer=DEFAULT_OPC_SERVER): # This function handles confirm events, and logs the event in SQL # Ensure ID is valid if trackID > 0 and trackID < MAX_CARTONS: def processConfirmInner(): start_time = time.time() # Setup carton location: devicePrefix = "[" + device + "]" + program cartonPrefix = devicePrefix + carton + "[" + str(trackID) + "]" # Read all carton data directly: tags = [cartonPrefix + "." + path for path in DATA_PATHS] values = [trackID, induct, scanner, sorter] + [value.value for value in system.opc.readValues(opcServer, tags)] logTime("Confirm[PLC_READ]", trackID, time.time() - start_time) start_time = time.time() # Log confirm event in SQL: # Insert into Package History: system.db.runPrepUpdate(CONFIRM_INSERT_QUERY, values) logTime("Confirm[DB_INSERT]", trackID, time.time() - start_time) system.util.invokeAsynchronous(processConfirmInner)