2026-02-28 17:04:25 +04:00

465 lines
23 KiB
Python

def onStartup():
# ============================================================================
# Gateway Timer Script - Create Database Indexes, Tables, and Set Sync Status ON
# Delay: 30000 ms
#
# RUNTIME: Ignition Gateway (Python 2.7/Jython)
# NOTE: 'system' module is provided by Ignition runtime - linter warnings are expected
# DEPLOYMENT: Place in Gateway → Scripting → Timer Scripts
# TRIGGER: On Gateway startup with 30 second delay
#
# PURPOSE: This script performs comprehensive database initialization tasks:
# 1. Creates required database tables (scanner_reads, item_data, induction_data, tipper_status, lane_data, jam_area)
# 2. Adds all necessary columns to these tables
# 3. Creates optimized indexes for alarm queries (6 indexes)
# 4. Sets synchronization status tag to indicate completion
# 5. Resets Item bData_Ready bits to False
# ============================================================================
import time
from datetime import datetime
# Initialize comprehensi ve logging system
# Note: Logging levels are configured in Ignition Gateway settings
# Use logger.debug() for detailed diagnostics, logger.info() for important events
logger = system.util.getLogger("TPA8_Startup_Script")
# Log script initialization with timestamp
script_start_time = time.time()
current_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
logger.info("TPA8 STARTUP SCRIPT: Initialization started at %s" % current_timestamp)
try:
# ========================================================================
# PHASE 1: SYSTEM MODULE AVAILABILITY CHECK
# ========================================================================
# Test if system is available
test = system.db
DS = "MariaDB"
DB_NAME = "ignition" # Database name for INFORMATION_SCHEMA queries
logger.info("PHASE 1: System module verification - SUCCESS (Database: %s)" % DS)
# ========================================================================
# PHASE 2: DATABASE CONNECTION VERIFICATION WITH RETRY
# ========================================================================
max_retries = 12 # 12 attempts * 5 seconds = 60 seconds max
retry_delay = 5 # Start with 5 second delays
connection_successful = False
logger.info("PHASE 2: Waiting for database to become available (up to 60 seconds)...")
for attempt in range(1, max_retries + 1):
try:
system.db.runScalarQuery("SELECT 1", DS)
connection_successful = True
logger.info("PHASE 2: Database connection established after %d attempt(s)" % attempt)
break
except Exception as conn_test_error:
if attempt < max_retries:
time.sleep(retry_delay)
else:
logger.error("PHASE 2: Database connection failed after %d attempts - %s" % (max_retries, str(conn_test_error)))
if not connection_successful:
logger.warning("PHASE 2: Proceeding with caution - connection may be unstable")
# ========================================================================
# PHASE 3: TABLE AND COLUMN CREATION
# ========================================================================
phase3_start_time = time.time()
logger.info("PHASE 3: Starting table and column creation...")
# Define table structures: table_name -> {column_name: column_definition}
table_structures = {
"scanner_reads": {
"id": "INT AUTO_INCREMENT PRIMARY KEY",
"t_stamp": "DATETIME DEFAULT CURRENT_TIMESTAMP",
"sScanner_Name": "VARCHAR(50) NOT NULL",
"diScanner_bad_reads": "TINYINT(1) DEFAULT 0",
"diScanner_comm_fault": "TINYINT(1) DEFAULT 0",
"diScanner_good_reads": "TINYINT(1) DEFAULT 0",
"diScanner_multi_items": "TINYINT(1) DEFAULT 0",
"diScanner_multi_reads": "TINYINT(1) DEFAULT 0",
"diScanner_no_data": "TINYINT(1) DEFAULT 0",
"diScanner_no_reads": "TINYINT(1) DEFAULT 0"
},
"item_data": {
"id": "INT AUTO_INCREMENT PRIMARY KEY",
"t_stamp": "DATETIME DEFAULT CURRENT_TIMESTAMP",
"adiSort_Code_0": "INT DEFAULT 0",
"adiSort_Code_1": "INT DEFAULT 0",
"adiSort_Code_2": "INT DEFAULT 0",
"adiSort_Code_3": "INT DEFAULT 0",
"asRequested_Dest_ID_0": "VARCHAR(20)",
"asRequested_Dest_ID_1": "VARCHAR(20)",
"asRequested_Dest_ID_2": "VARCHAR(20)",
"asRequested_Dest_ID_3": "VARCHAR(20)",
"bData_Ready": "TINYINT(1) DEFAULT 0",
"diPLC_RecordNumber": "INT",
"iItem_Carrier_Count": "INT",
"iPackage_Length": "INT",
"sActual_Dest_ID": "VARCHAR(20)",
"sBarcode": "VARCHAR(50)",
"sLocation_ID": "VARCHAR(20)"
},
"induction_data": {
"id": "INT AUTO_INCREMENT PRIMARY KEY",
"t_stamp": "DATETIME DEFAULT CURRENT_TIMESTAMP",
"sInduction_Name": "VARCHAR(50) NOT NULL",
"diTotal_Single_Carrier": "INT DEFAULT 0",
"diTotal_Double_Carrier": "INT DEFAULT 0"
},
"tipper_status": {
"id": "INT AUTO_INCREMENT PRIMARY KEY",
"t_stamp": "DATETIME DEFAULT CURRENT_TIMESTAMP",
"Name": "VARCHAR(50) NOT NULL",
"Tipper_Faulted": "TINYINT(1) DEFAULT 0",
"Dumping": "TINYINT(1) DEFAULT 0"
},
"lane_data": {
"id": "INT AUTO_INCREMENT PRIMARY KEY",
"t_stamp": "DATETIME DEFAULT CURRENT_TIMESTAMP",
"Name": "VARCHAR(64) NOT NULL",
"Full": "INT UNSIGNED DEFAULT 0",
"Half_Full": "INT UNSIGNED DEFAULT 0",
"Jam": "INT UNSIGNED DEFAULT 0",
"No_Container": "INT UNSIGNED DEFAULT 0",
"Block_Operation": "INT UNSIGNED DEFAULT 0",
"Faulted": "INT UNSIGNED DEFAULT 0",
"Disabled": "INT UNSIGNED DEFAULT 0"
},
"jam_area": {
"t_stamp": "DATETIME NOT NULL",
"Name": "VARCHAR(255)",
"Fluid_Inbound_Merge_1": "TINYINT(1) DEFAULT 0",
"Fluid_Inbound_Merge_2": "TINYINT(1) DEFAULT 0",
"Fluid_Inbound_Merge_3": "TINYINT(1) DEFAULT 0",
"Fluid_Inbound_Merge_4": "TINYINT(1) DEFAULT 0",
"Fluid_Inbound_Merge_5": "TINYINT(1) DEFAULT 0",
"Fluid_Inbound_Merge_6": "TINYINT(1) DEFAULT 0",
"Fluid_Inbound_Merge_7": "TINYINT(1) DEFAULT 0",
"Non_Con_Merge": "TINYINT(1) DEFAULT 0",
"NCP": "TINYINT(1) DEFAULT 0",
"Bulk_Inbound_MCM04": "TINYINT(1) DEFAULT 0",
"Fluid_Outbound_MCM04": "TINYINT(1) DEFAULT 0",
"Problem_Solve_MCM04": "TINYINT(1) DEFAULT 0",
"Bulk_Inbound_MCM05": "TINYINT(1) DEFAULT 0",
"Fluid_Outbound_MCM05": "TINYINT(1) DEFAULT 0",
"Problem_Solve_MCM05": "TINYINT(1) DEFAULT 0",
"NCS1": "TINYINT(1) DEFAULT 0",
"NCS2": "TINYINT(1) DEFAULT 0",
"BYB": "TINYINT(1) DEFAULT 0",
"BYA": "TINYINT(1) DEFAULT 0",
"BYC": "TINYINT(1) DEFAULT 0",
"BYD": "TINYINT(1) DEFAULT 0",
"Chutes_MCM04": "TINYINT(1) DEFAULT 0",
"Chutes_MCM05": "TINYINT(1) DEFAULT 0"
}
}
# ========================================================================
# STEP 3.1: CREATE TABLES
# ========================================================================
table_queries = [
("scanner_reads", """CREATE TABLE IF NOT EXISTS scanner_reads (
id INT AUTO_INCREMENT PRIMARY KEY,
t_stamp DATETIME DEFAULT CURRENT_TIMESTAMP,
sScanner_Name VARCHAR(50) NOT NULL,
diScanner_bad_reads TINYINT(1) DEFAULT 0,
diScanner_comm_fault TINYINT(1) DEFAULT 0,
diScanner_good_reads TINYINT(1) DEFAULT 0,
diScanner_multi_items TINYINT(1) DEFAULT 0,
diScanner_multi_reads TINYINT(1) DEFAULT 0,
diScanner_no_data TINYINT(1) DEFAULT 0,
diScanner_no_reads TINYINT(1) DEFAULT 0
)"""),
("item_data", """CREATE TABLE IF NOT EXISTS item_data (
id INT AUTO_INCREMENT PRIMARY KEY,
t_stamp DATETIME DEFAULT CURRENT_TIMESTAMP,
adiSort_Code_0 INT DEFAULT 0,
adiSort_Code_1 INT DEFAULT 0,
adiSort_Code_2 INT DEFAULT 0,
adiSort_Code_3 INT DEFAULT 0,
asRequested_Dest_ID_0 VARCHAR(20),
asRequested_Dest_ID_1 VARCHAR(20),
asRequested_Dest_ID_2 VARCHAR(20),
asRequested_Dest_ID_3 VARCHAR(20),
bData_Ready TINYINT(1) DEFAULT 0,
diPLC_RecordNumber INT,
iItem_Carrier_Count INT,
iPackage_Length INT,
sActual_Dest_ID VARCHAR(20),
sBarcode VARCHAR(50),
sLocation_ID VARCHAR(20)
)"""),
("induction_data", """CREATE TABLE IF NOT EXISTS induction_data (
id INT AUTO_INCREMENT PRIMARY KEY,
t_stamp DATETIME DEFAULT CURRENT_TIMESTAMP,
sInduction_Name VARCHAR(50) NOT NULL,
diTotal_Single_Carrier INT DEFAULT 0,
diTotal_Double_Carrier INT DEFAULT 0
)"""),
("tipper_status", """CREATE TABLE IF NOT EXISTS tipper_status (
id INT AUTO_INCREMENT PRIMARY KEY,
t_stamp DATETIME DEFAULT CURRENT_TIMESTAMP,
Name VARCHAR(50) NOT NULL,
Tipper_Faulted TINYINT(1) DEFAULT 0,
Dumping TINYINT(1) DEFAULT 0
)"""),
("lane_data", """CREATE TABLE IF NOT EXISTS lane_data (
id INT AUTO_INCREMENT PRIMARY KEY,
t_stamp DATETIME DEFAULT CURRENT_TIMESTAMP,
Name VARCHAR(64) NOT NULL,
Full INT UNSIGNED DEFAULT 0,
Half_Full INT UNSIGNED DEFAULT 0,
Jam INT UNSIGNED DEFAULT 0,
No_Container INT UNSIGNED DEFAULT 0,
Block_Operation INT UNSIGNED DEFAULT 0,
Faulted INT UNSIGNED DEFAULT 0,
Disabled INT UNSIGNED DEFAULT 0
)"""),
("jam_area", """CREATE TABLE IF NOT EXISTS jam_area (
t_stamp DATETIME NOT NULL,
Name VARCHAR(255),
Fluid_Inbound_Merge_1 TINYINT(1) DEFAULT 0,
Fluid_Inbound_Merge_2 TINYINT(1) DEFAULT 0,
Fluid_Inbound_Merge_3 TINYINT(1) DEFAULT 0,
Fluid_Inbound_Merge_4 TINYINT(1) DEFAULT 0,
Fluid_Inbound_Merge_5 TINYINT(1) DEFAULT 0,
Fluid_Inbound_Merge_6 TINYINT(1) DEFAULT 0,
Fluid_Inbound_Merge_7 TINYINT(1) DEFAULT 0,
Non_Con_Merge TINYINT(1) DEFAULT 0,
NCP TINYINT(1) DEFAULT 0,
Bulk_Inbound_MCM04 TINYINT(1) DEFAULT 0,
Fluid_Outbound_MCM04 TINYINT(1) DEFAULT 0,
Problem_Solve_MCM04 TINYINT(1) DEFAULT 0,
Bulk_Inbound_MCM05 TINYINT(1) DEFAULT 0,
Fluid_Outbound_MCM05 TINYINT(1) DEFAULT 0,
Problem_Solve_MCM05 TINYINT(1) DEFAULT 0,
NCS1 TINYINT(1) DEFAULT 0,
NCS2 TINYINT(1) DEFAULT 0,
BYB TINYINT(1) DEFAULT 0,
BYA TINYINT(1) DEFAULT 0,
BYC TINYINT(1) DEFAULT 0,
BYD TINYINT(1) DEFAULT 0,
Chutes_MCM04 TINYINT(1) DEFAULT 0,
Chutes_MCM05 TINYINT(1) DEFAULT 0,
INDEX idx_t_stamp (t_stamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
]
tables_created_count = 0
tables_existed_count = 0
tables_failed_count = 0
logger.info("STEP 3.1: Checking/creating %d tables..." % len(table_queries))
for table_name, query in table_queries:
try:
# Check if table exists
table_exists_query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'" % (DB_NAME, table_name)
table_exists_result = system.db.runScalarQuery(table_exists_query, DS)
table_exists = int(table_exists_result) > 0 if table_exists_result is not None else False
if table_exists:
tables_existed_count += 1
logger.info("STEP 3.1: Table '%s' already exists (OK)" % table_name)
else:
step_start_time = time.time()
system.db.runUpdateQuery(query, DS)
step_duration = time.time() - step_start_time
tables_created_count += 1
logger.info("STEP 3.1: Table '%s' created successfully (%.3f seconds)" % (table_name, step_duration))
except Exception as e:
tables_failed_count += 1
logger.error("STEP 3.1: FAILED to create table '%s' - %s" % (table_name, str(e)))
logger.info("STEP 3.1: Completed - Created=%d, Existed=%d, Failed=%d" % (tables_created_count, tables_existed_count, tables_failed_count))
# ========================================================================
# STEP 3.2: ADD COLUMNS TO TABLES
# ========================================================================
total_columns_added_count = 0
total_columns_existed_count = 0
total_columns_failed_count = 0
logger.info("STEP 3.2: Checking/adding columns to tables...")
for table_name, columns in table_structures.items():
logger.info("STEP 3.2: Checking table '%s'..." % table_name)
table_columns_added = 0
table_columns_existed = 0
table_columns_failed = 0
for column_name, column_def in columns.items():
try:
# Check if column exists
column_check_query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' AND COLUMN_NAME = '%s'" % (DB_NAME, table_name, column_name)
column_exists_result = system.db.runScalarQuery(column_check_query, DS)
column_exists = int(column_exists_result) > 0 if column_exists_result is not None else False
if column_exists:
table_columns_existed += 1
total_columns_existed_count += 1
else:
# Column doesn't exist, add it
# Skip PRIMARY KEY keyword in ALTER TABLE
clean_def = column_def.replace("AUTO_INCREMENT PRIMARY KEY", "AUTO_INCREMENT").replace("PRIMARY KEY", "")
alter_query = "ALTER TABLE `%s` ADD COLUMN `%s` %s" % (table_name, column_name, clean_def)
system.db.runUpdateQuery(alter_query, DS)
table_columns_added += 1
total_columns_added_count += 1
logger.info("STEP 3.2: Added column '%s.%s'" % (table_name, column_name))
except Exception as e:
table_columns_failed += 1
total_columns_failed_count += 1
error_msg = str(e)
if "exist" not in error_msg.lower() and "duplicate" not in error_msg.lower():
logger.error("STEP 3.2: Failed to add column '%s.%s' - %s" % (table_name, column_name, error_msg))
logger.info("STEP 3.2: Table '%s' - Added=%d, Existed=%d, Failed=%d" % (table_name, table_columns_added, table_columns_existed, table_columns_failed))
logger.info("STEP 3.2: Completed - Added=%d, Existed=%d, Failed=%d" % (total_columns_added_count, total_columns_existed_count, total_columns_failed_count))
phase3_duration = time.time() - phase3_start_time
logger.info("PHASE 3: Completed in %.3f seconds" % phase3_duration)
# ========================================================================
# PHASE 4: CREATE ALARM EVENT INDEXES
# ========================================================================
phase4_start_time = time.time()
index_queries = [
("idx_alarm_events_active", "alarm_events", "(eventtype, eventtime DESC, id)"),
("idx_alarm_events_type_time_id", "alarm_events", "(eventtype, eventtime DESC, id)"),
("idx_alarm_events_grouping", "alarm_events", "(eventtype, source, displaypath, priority, eventtime DESC, id)"),
("idx_alarm_events_clear", "alarm_events", "(eventtype, eventtime, eventid)"),
("idx_alarm_event_data_lookup", "alarm_event_data", "(id, propname)"),
("idx_alarm_events_eventid", "alarm_events", "(eventid, eventtype)")
]
logger.info("PHASE 4: Checking/creating %d alarm event indexes..." % len(index_queries))
success_count = 0
error_count = 0
existed_count = 0
for index_name, table_name, columns in index_queries:
try:
# Check if index exists
index_check_query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' AND INDEX_NAME = '%s'" % (DB_NAME, table_name, index_name)
index_exists_result = system.db.runScalarQuery(index_check_query, "MariaDB")
index_exists = int(index_exists_result) > 0 if index_exists_result is not None else False
if index_exists:
existed_count += 1
success_count += 1
else:
query = "CREATE INDEX IF NOT EXISTS %s ON %s%s" % (index_name, table_name, columns)
system.db.runUpdateQuery(query, "MariaDB")
success_count += 1
except Exception as e:
error_count += 1
logger.error("PHASE 4: Failed to create index '%s' - %s" % (index_name, str(e)))
phase4_duration = time.time() - phase4_start_time
logger.info("PHASE 4: Completed - Created=%d, Existed=%d, Failed=%d (%.3f seconds)" % (success_count - existed_count, existed_count, error_count, phase4_duration))
# ========================================================================
# PHASE 5: INDEX CREATION SUMMARY
# ========================================================================
total_success = success_count
total_error = error_count
total_existed = existed_count
total_created = total_success - total_existed
logger.info("PHASE 5: Index Summary - Alarm: Created=%d/Existed=%d/Failed=%d, Total: Created=%d/Existed=%d/Failed=%d" %
(success_count - existed_count, existed_count, error_count,
total_created, total_existed, total_error))
# ========================================================================
# PHASE 6: SET SYNCHRONIZATION STATUS TAG (ALWAYS SET REGARDLESS OF ERRORS)
# ========================================================================
time.sleep(30)
try:
tag_path = "[MTN6_SCADA_TAG_PROVIDER]System/SMC/Statistics/bDataSynchronized"
logger.info("PHASE 6: Setting synchronization status tag (regardless of errors)...")
system.tag.writeBlocking([tag_path], [True])
logger.info("PHASE 6: Synchronization tag set to ON - Database initialization complete")
except Exception as tag_error:
logger.error("PHASE 6: Failed to set synchronization tag - %s" % str(tag_error))
# ========================================================================
# PHASE 7: RESET ITEM bData_Ready BITS TO FALSE (0 to 100)
# ========================================================================
phase7_start_time = time.time()
logger.info("PHASE 7: Resetting Item bData_Ready bits to False...")
try:
tag_paths = []
tag_values = []
# Generate tag paths for Item_000 through Item_100
for i in range(101): # 0 to 100 inclusive
tag_path = "[MTN6_SCADA_TAG_PROVIDER]System/SMC/Statistics/Item/Item_%03d/bData_Ready" % i
tag_paths.append(tag_path)
tag_values.append(False)
logger.info("PHASE 7: Writing False to %d bData_Ready tags (Item_000 to Item_100)..." % len(tag_paths))
system.tag.writeBlocking(tag_paths, tag_values)
phase7_duration = time.time() - phase7_start_time
logger.info("PHASE 7: Successfully reset all bData_Ready bits to False (%.3f seconds)" % phase7_duration)
except Exception as e:
logger.error("PHASE 7: ERROR resetting bData_Ready bits - %s" % str(e))
# ========================================================================
# SCRIPT COMPLETION SUMMARY
# ========================================================================
script_duration = time.time() - script_start_time
completion_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if total_error == 0:
logger.info("SCRIPT COMPLETE: SUCCESS - Finished at %s, Duration: %.3f seconds, Indexes: %d created, %d existed" %
(completion_timestamp, script_duration, total_created, total_existed))
else:
logger.warning("SCRIPT COMPLETE: COMPLETED WITH ERRORS - Finished at %s, Duration: %.3f seconds, %d failures" %
(completion_timestamp, script_duration, total_error))
except NameError as e:
error_type = type(e).__name__
logger.error("=" * 80)
logger.error("CRITICAL ERROR: SYSTEM MODULE NOT AVAILABLE")
logger.error("=" * 80)
logger.error("Error type: %s" % error_type)
logger.error("Error message: %s" % str(e))
logger.error("")
logger.error("This script requires Ignition Gateway runtime environment")
logger.error("The 'system' module is provided by Ignition and is not available")
logger.error("Please ensure this script is running in Ignition Gateway context")
logger.error("=" * 80)
except Exception as e:
error_type = type(e).__name__
script_duration = time.time() - script_start_time if 'script_start_time' in locals() else 0
logger.error("=" * 80)
logger.error("CRITICAL ERROR: UNEXPECTED EXCEPTION")
logger.error("=" * 80)
logger.error("Error type: %s" % error_type)
logger.error("Error message: %s" % str(e))
logger.error("Script execution time before error: %.3f seconds" % script_duration)
logger.error("")
logger.error("An unexpected error occurred during script execution")
logger.error("Please review the error details above and check:")
logger.error(" - Database connectivity")
logger.error(" - Database permissions")
logger.error(" - SQL syntax validity")
logger.error(" - Ignition Gateway logs for additional context")
logger.error("=" * 80)
# Re-raise the exception to ensure it's visible in Ignition logs
raise