131 lines
6.6 KiB
Plaintext
131 lines
6.6 KiB
Plaintext
################################################################
|
|
################################################################
|
|
## 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)
|
|
|