diff --git a/IO Tree Configuration Generator/__pycache__/controller_builder.cpython-312.pyc b/IO Tree Configuration Generator/__pycache__/controller_builder.cpython-312.pyc index 50d0832..71b2352 100644 Binary files a/IO Tree Configuration Generator/__pycache__/controller_builder.cpython-312.pyc and b/IO Tree Configuration Generator/__pycache__/controller_builder.cpython-312.pyc differ diff --git a/IO Tree Configuration Generator/__pycache__/enhanced_mcm_generator.cpython-312.pyc b/IO Tree Configuration Generator/__pycache__/enhanced_mcm_generator.cpython-312.pyc deleted file mode 100644 index 5a31313..0000000 Binary files a/IO Tree Configuration Generator/__pycache__/enhanced_mcm_generator.cpython-312.pyc and /dev/null differ diff --git a/IO Tree Configuration Generator/__pycache__/routine_generator.cpython-312.pyc b/IO Tree Configuration Generator/__pycache__/routine_generator.cpython-312.pyc deleted file mode 100644 index 7ef7eb2..0000000 Binary files a/IO Tree Configuration Generator/__pycache__/routine_generator.cpython-312.pyc and /dev/null differ diff --git a/IO Tree Configuration Generator/controller_builder.py b/IO Tree Configuration Generator/controller_builder.py index 1455700..fbab74d 100644 --- a/IO Tree Configuration Generator/controller_builder.py +++ b/IO Tree Configuration Generator/controller_builder.py @@ -19,6 +19,7 @@ import os import re import xml.etree.ElementTree as ET from datetime import datetime +import copy from typing import Optional # Base controller & fixed slot models @@ -78,6 +79,9 @@ class ControllerBuilder: def __init__(self, controller_name: str, skip_chassis_modules: bool = False): self.controller_name = controller_name self.skip_chassis_modules = skip_chassis_modules + # Raw base XML snippets to preserve CDATA on save + self._raw_base_aoi_xml: Optional[str] = None + self._raw_base_dtypes_xml: Optional[str] = None # 1. Build base controller from boilerplate controller_cfg = create_l83es_controller(controller_name) @@ -129,6 +133,86 @@ class ControllerBuilder: # Indent & persist self._save_project(self.tree, filename) + def import_base_sections_from_l5x(self, base_l5x_path: str): + """Import AOIs and DataTypes from a base L5X and insert with required ordering. + + - is inserted immediately before . + - is inserted immediately before . + + Args: + base_l5x_path: Path to the L5X file containing base definitions + """ + base_xml_text: Optional[str] = None + try: + # Read raw text first so CDATA can be preserved verbatim + with open(base_l5x_path, "r", encoding="utf-8") as fh: + base_xml_text = fh.read() + base_tree = ET.fromstring(base_xml_text) + base_root = base_tree + except Exception as exc: + print(f"WARNING: Failed to parse base L5X at {base_l5x_path}: {exc}") + return + + base_controller = base_root.find(".//Controller") + if base_controller is None: + print("WARNING: No found in base L5X") + return + + controller = self.root.find(".//Controller[@Use='Target']") + if controller is None: + print("ERROR: Target controller not found in builder") + return + + # Ensure anchor elements exist + tags = controller.find("Tags") + if tags is None: + tags = ET.SubElement(controller, "Tags") + + modules = controller.find("Modules") + if modules is None: + # Create Modules just after SafetyInfo if present, else append + safety_info = controller.find("SafetyInfo") + if safety_info is not None: + insert_idx = list(controller).index(safety_info) + 1 + modules = ET.Element("Modules") + controller.insert(insert_idx, modules) + else: + modules = ET.SubElement(controller, "Modules") + + # Import AOIs before Tags + base_aoi = base_controller.find("AddOnInstructionDefinitions") + if base_aoi is not None: + existing_aoi = controller.find("AddOnInstructionDefinitions") + if existing_aoi is not None: + controller.remove(existing_aoi) + try: + insert_idx = list(controller).index(tags) + except ValueError: + insert_idx = len(list(controller)) + controller.insert(insert_idx, copy.deepcopy(base_aoi)) + # Capture raw AOI XML to preserve CDATA on save + if base_xml_text is not None: + m = re.search(r"", base_xml_text) + if m: + self._raw_base_aoi_xml = m.group(0) + + # Import DataTypes before Modules + base_dts = base_controller.find("DataTypes") + if base_dts is not None: + existing_dts = controller.find("DataTypes") + if existing_dts is not None: + controller.remove(existing_dts) + try: + insert_idx = list(controller).index(modules) + except ValueError: + insert_idx = len(list(controller)) + controller.insert(insert_idx, copy.deepcopy(base_dts)) + # Capture raw DataTypes XML to preserve CDATA on save + if base_xml_text is not None: + m = re.search(r"", base_xml_text) + if m: + self._raw_base_dtypes_xml = m.group(0) + def add_generated_tags(self, routines_generator_dir: str, zones_dict=None): """Add the generated tags from the Routines Generator into this controller. @@ -557,6 +641,22 @@ class ControllerBuilder: # Apply CDATA wrapping to Text elements full_xml = re.sub(text_pattern, lambda m: f"{m.group(1)}\n\n{m.group(3)}" if m.group(2).strip() else m.group(0), full_xml, flags=re.DOTALL) + # If raw base sections were imported, replace the generated sections with raw text + if self._raw_base_aoi_xml: + full_xml = re.sub( + r"", + lambda _m: self._raw_base_aoi_xml, + full_xml, + count=1, + ) + if self._raw_base_dtypes_xml: + full_xml = re.sub( + r"", + lambda _m: self._raw_base_dtypes_xml, + full_xml, + count=1, + ) + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, "w", encoding="utf-8") as fh: fh.write(full_xml) diff --git a/IO Tree Configuration Generator/enhanced_mcm_generator.py b/IO Tree Configuration Generator/enhanced_mcm_generator.py index ee473e4..5479c89 100644 --- a/IO Tree Configuration Generator/enhanced_mcm_generator.py +++ b/IO Tree Configuration Generator/enhanced_mcm_generator.py @@ -362,9 +362,7 @@ class EnhancedMCMGenerator: # Print summary of modules found if self.iolm_modules: - print(f"Found {len(self.iolm_modules)} IOLM modules:") - for iolm in self.iolm_modules: - print(f" {iolm['name']} (IP: {iolm.get('ip_address', 'N/A')})") + print(f"Found {len(self.iolm_modules)} IOLM modules") if self.unknown_modules: print(f"WARNING: {len(self.unknown_modules)} unknown modules found") @@ -391,22 +389,33 @@ class EnhancedMCMGenerator: modules_section = builder.get_modules_section() self._add_excel_modules(modules_section) + # 2b. Import AOIs and DataTypes from BaseProgram.L5X with required ordering + try: + base_l5x_path = os.path.join(os.path.dirname(__file__), "BaseProgram.L5X") + if os.path.exists(base_l5x_path): + print(f" Importing AOIs/DataTypes from base: {base_l5x_path}") + builder.import_base_sections_from_l5x(base_l5x_path) + else: + print(f" WARNING: BaseProgram.L5X not found at {base_l5x_path}") + except Exception as e: + print(f" WARNING: Failed importing base sections: {e}") + # 3. Embed generated programs from Routines Generator - TEMPORARILY DISABLED # routines_generator_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "Routines Generator") # print(f"Looking for generated programs in: {routines_generator_dir}") # builder.add_generated_programs(routines_generator_dir) - print(" Skipping generated programs embedding (temporarily disabled)") + # Skipping generated programs embedding (temporarily disabled) # 4. Embed generated tags from Routines Generator - TEMPORARILY DISABLED # print(f"Looking for generated tags in: {routines_generator_dir}") # builder.add_generated_tags(routines_generator_dir, zones_dict=getattr(self, 'zones_dict', None)) - print(" Skipping generated tags embedding (temporarily disabled)") + # Skipping generated tags embedding (temporarily disabled) # 5. Finalise and save output_file = os.path.join(self.generated_dir, f"{self.controller_name}.L5X") builder.finalise_and_save(output_file) - print(f"Generated complete project: {output_file}") + print(f"OK: Generated project: {output_file}") return [output_file] def generate_split_projects(self) -> Tuple[str, str]: @@ -415,7 +424,7 @@ class EnhancedMCMGenerator: Returns: Tuple of (file1_path, file2_path) """ - print("Generating split projects...") + print("Split projects generation") # Validate parent-child relationships before splitting self._validate_parent_child_relationships() @@ -440,9 +449,9 @@ class EnhancedMCMGenerator: 2 ) - print(f"Split projects generated successfully:") - print(f" Part 1: {file1_path} ({self._count_modules_in_group(group1_modules)} modules)") - print(f" Part 2: {file2_path} ({self._count_modules_in_group(group2_modules)} modules)") + print("OK: Split projects generated") + print(f"- Part 1: {file1_path} ({self._count_modules_in_group(group1_modules)} modules)") + print(f"- Part 2: {file2_path} ({self._count_modules_in_group(group2_modules)} modules)") return file1_path, file2_path @@ -452,7 +461,7 @@ class EnhancedMCMGenerator: Returns: Tuple of (group1_dict, group2_dict) where each dict contains module lists by type """ - print(" Creating balanced module groups...") + # Creating balanced module groups # Initialize empty groups group1 = { @@ -591,9 +600,7 @@ class EnhancedMCMGenerator: family_size += len(children) # Log family assignment for debugging - if children: - child_names = [child.get('name', 'Unknown') for _, child in children] - print(f" Assigning family: {module_name} with {len(children)} children: {child_names}") + # Assignment details suppressed for concise output # Assign to group with fewer modules if group1_total <= group2_total: @@ -605,8 +612,7 @@ class EnhancedMCMGenerator: group2_total += family_size group_name = "Group 2" - if children: - print(f" -> {group_name} (family size: {family_size})") + # Assignment target suppressed for concise output # Add parent module target_group[module_type].append(module) @@ -645,12 +651,7 @@ class EnhancedMCMGenerator: orphaned_modules.append(('solenoid', solenoid)) # Distribute orphaned modules - if orphaned_modules: - print(f" Found {len(orphaned_modules)} orphaned modules:") - for module_type, module in orphaned_modules: - module_name = module.get('name', 'Unknown') - parent = module.get('parent_module', 'None') - print(f" - {module_type}: {module_name} (expected parent: {parent})") + # Orphan details suppressed; only assignment occurs for module_type, module in orphaned_modules: if group1_total <= group2_total: @@ -662,8 +663,7 @@ class EnhancedMCMGenerator: group2_total += 1 group_name = "Group 2" - module_name = module.get('name', 'Unknown') - print(f" -> Assigning orphaned {module_type} '{module_name}' to {group_name}") + # Assignment summary suppressed assigned_modules.add(module_name) def _generate_project_with_modules(self, project_name: str, module_groups: Dict, part_number: int) -> str: @@ -686,6 +686,17 @@ class EnhancedMCMGenerator: # Add modules from the specific groups self._add_excel_modules_from_groups(modules_section, module_groups) + # Import AOIs and DataTypes from BaseProgram.L5X + try: + base_l5x_path = os.path.join(os.path.dirname(__file__), "BaseProgram.L5X") + if os.path.exists(base_l5x_path): + print(f" Importing AOIs/DataTypes from base: {base_l5x_path}") + builder.import_base_sections_from_l5x(base_l5x_path) + else: + print(f" WARNING: BaseProgram.L5X not found at {base_l5x_path}") + except Exception as e: + print(f" WARNING: Failed importing base sections: {e}") + # Save the file output_filename = f"{self.generated_dir}/{project_name}.L5X" builder.finalise_and_save(output_filename) @@ -785,7 +796,7 @@ class EnhancedMCMGenerator: def _validate_parent_child_relationships(self): """Validate parent-child relationships before splitting.""" - print(" Validating parent-child relationships...") + # Validating parent-child relationships issues = [] @@ -844,15 +855,15 @@ class EnhancedMCMGenerator: issues.append(f"Solenoid module '{solenoid.get('name')}' references non-existent IOLM parent '{parent}'") if issues: - print(f" WARNING: Found {len(issues)} parent-child relationship issues:") - for issue in issues: - print(f" - {issue}") + print(f"Warning: {len(issues)} parent-child relationship issues detected") + for issue in issues[:5]: + print(f"- {issue}") else: - print(" [SUCCESS] All parent-child relationships are valid") + print("OK: Parent-child relationships valid") def _validate_split_integrity(self, group1: Dict, group2: Dict): """Validate that the split maintains parent-child relationships.""" - print(" Validating split integrity...") + # Validating split integrity # Build module name to group mapping group1_modules = set() @@ -894,12 +905,12 @@ class EnhancedMCMGenerator: violations.append(f"{child_type} '{child_name}' separated from parent '{parent_name}'") if violations: - print(f" ERROR: Found {len(violations)} split integrity violations:") - for violation in violations: - print(f" - {violation}") + print(f"Error: {len(violations)} split integrity violations detected") + for violation in violations[:5]: + print(f"- {violation}") raise ValueError("Split integrity validation failed - parent-child relationships would be broken") else: - print(" [SUCCESS] Split maintains all parent-child relationships") + print("OK: Split integrity valid") def _configure_controller_settings(self, root): """Configure controller-specific settings.""" @@ -1166,11 +1177,10 @@ def main(): if len(sys.argv) > 2 and not sys.argv[2].startswith("--"): project_name = sys.argv[2] - print(f"Enhanced MCM Generator") - print(f"Project: {project_name}") - print(f"Excel file: {excel_file}") - print(f"Mode: {'Split (2 files)' if split_mode else 'Single file'}") - print(f"Zones: {'Custom' if zones_dict else 'Default'}") + print("Enhanced MCM Generator") + print(f"- Project: {project_name}") + print(f"- Excel: {excel_file}") + print(f"- Mode: {'Split' if split_mode else 'Single file'}") print("-" * 50) # Create generator with zones @@ -1181,15 +1191,14 @@ def main(): if split_mode: # Generate split projects file1, file2 = generator.generate_complete_project(split_mode=True) - print(f"\nSplit generation complete!") - print(f"Generated files:") - print(f" {file1}") - print(f" {file2}") + print("Split generation complete") + print(f"- {file1}") + print(f"- {file2}") else: # Generate single project output_file = generator.generate_complete_project(split_mode=False) - print(f"\nSingle file generation complete!") - print(f"Generated file: {output_file}") + print("Single file generation complete") + print(f"- {output_file}") else: print("ERROR: Failed to load/process Excel data") diff --git a/IO Tree Configuration Generator/test_sio_implementation.py b/IO Tree Configuration Generator/test_sio_implementation.py deleted file mode 100644 index 4a05242..0000000 --- a/IO Tree Configuration Generator/test_sio_implementation.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for SIO module implementation -======================================== - -Tests the SIO boilerplate model to ensure it works correctly with -IP address configuration and comment updates. -""" - -import os -import sys - -# Add the models directory to the path -sys.path.append(os.path.join(os.path.dirname(__file__), 'models')) - -from models.sio_boilerplate_model import create_sio_module, SIOModuleGenerator - - -def test_sio_basic_functionality(): - """Test basic SIO module creation and configuration.""" - print("Testing SIO module basic functionality...") - - # Create a basic SIO configuration - config = create_sio_module( - name="SIO_TEST_1", - ip_address="192.168.1.100", - safety_input_names={ - 0: "Emergency Stop Line 1", - 1: "Emergency Stop Line 2", - 2: "Safety Gate Position", - 3: "Light Curtain Active", - 4: "Safety Mat Pressure", - 5: "Reset Button Pressed", - 6: "Enable Switch Active", - 7: "Safety Scanner Zone" - }, - safety_output_names={ - 0: "Main Safety Relay", - 1: "Backup Safety Relay", - 2: "Warning Light Red", - 3: "Warning Light Amber", - 4: "Safety Brake Release", - 5: "Safety Brake Release", - 6: "Safety Brake Release", - 7: "Safety Brake Release" - } - ) - - # Create generator and apply updates - generator = SIOModuleGenerator(config) - - try: - generator.load_boilerplate() - print(" ✓ Boilerplate loaded successfully") - - generator.apply_updates() - print(" ✓ Updates applied successfully") - - # Check that the IP address was updated - port = generator.root.find(".//Port[@Type='Ethernet']") - if port is not None and port.get("Address") == "192.168.1.100": - print(" ✓ IP address updated correctly") - else: - print(" ✗ IP address not updated correctly") - return False - - # Check that module name was updated - module = generator.root.find(".//Module[@Use='Target']") - if module is not None and module.get("Name") == "SIO_TEST_1": - print(" ✓ Module name updated correctly") - else: - print(" ✗ Module name not updated correctly") - return False - - # Check safety input comments - si_comments = generator.root.find(".//Connection[@Name='_200424962CC22C87']/InputTag/Comments") - if si_comments is not None: - comment_count = len(list(si_comments)) - if comment_count == 8: # Should have 8 safety input comments - print(f" ✓ Safety input comments added ({comment_count} comments)") - else: - print(f" ✗ Expected 8 safety input comments, got {comment_count}") - return False - else: - print(" ✗ Safety input comments section not found") - return False - - # Check safety output comments - so_comments = generator.root.find(".//Connection[@Name='_200424962C862CC2']/OutputTag/Comments") - if so_comments is not None: - comment_count = len(list(so_comments)) - if comment_count == 5: # Should have 5 safety output comments - print(f" ✓ Safety output comments added ({comment_count} comments)") - else: - print(f" ✗ Expected 5 safety output comments, got {comment_count}") - return False - else: - print(" ✗ Safety output comments section not found") - return False - - # Test saving the output - os.makedirs("generated", exist_ok=True) - output_path = "generated/SIO_TEST_1.L5X" - generator.save(output_path) - - if os.path.exists(output_path): - print(f" ✓ SIO module saved successfully to {output_path}") - # Check file size to ensure it's not empty - file_size = os.path.getsize(output_path) - if file_size > 1000: # Reasonable size for XML file - print(f" ✓ Generated file has reasonable size ({file_size} bytes)") - else: - print(f" ✗ Generated file seems too small ({file_size} bytes)") - return False - else: - print(" ✗ Failed to save SIO module") - return False - - return True - - except FileNotFoundError as e: - print(f" ✗ Boilerplate file not found: {e}") - return False - except Exception as e: - print(f" ✗ Error during testing: {e}") - return False - - -def test_sio_minimal_config(): - """Test SIO module with minimal configuration.""" - print("\nTesting SIO module with minimal configuration...") - - # Create minimal configuration - config = create_sio_module( - name="SIO_MINIMAL", - ip_address="10.0.0.50" - ) - - generator = SIOModuleGenerator(config) - - try: - generator.load_boilerplate() - generator.apply_updates() - - # Verify basic properties - module = generator.root.find(".//Module[@Use='Target']") - port = generator.root.find(".//Port[@Type='Ethernet']") - - if (module is not None and module.get("Name") == "SIO_MINIMAL" and - port is not None and port.get("Address") == "10.0.0.50"): - print(" ✓ Minimal configuration applied successfully") - return True - else: - print(" ✗ Minimal configuration failed") - return False - - except Exception as e: - print(f" ✗ Error with minimal configuration: {e}") - return False - - -def main(): - """Run all SIO tests.""" - print("Running SIO Module Implementation Tests") - print("=" * 40) - - test_results = [] - - # Test basic functionality - test_results.append(test_sio_basic_functionality()) - - # Test minimal configuration - test_results.append(test_sio_minimal_config()) - - # Summary - print("\n" + "=" * 40) - passed = sum(test_results) - total = len(test_results) - - if passed == total: - print(f"✓ All tests passed ({passed}/{total})") - print("SIO module implementation is working correctly!") - return 0 - else: - print(f"✗ Some tests failed ({passed}/{total})") - print("Please check the SIO implementation.") - return 1 - - -if __name__ == "__main__": - exit(main()) \ No newline at end of file diff --git a/L5X2ACD Compiler/__pycache__/compilation_manager.cpython-312.pyc b/L5X2ACD Compiler/__pycache__/compilation_manager.cpython-312.pyc index bb4948b..6759eb3 100644 Binary files a/L5X2ACD Compiler/__pycache__/compilation_manager.cpython-312.pyc and b/L5X2ACD Compiler/__pycache__/compilation_manager.cpython-312.pyc differ diff --git a/L5X2ACD Compiler/compile_MTN6_MCM05_CHUTE_LOAD.bat b/L5X2ACD Compiler/compile_mtn6.bat similarity index 78% rename from L5X2ACD Compiler/compile_MTN6_MCM05_CHUTE_LOAD.bat rename to L5X2ACD Compiler/compile_mtn6.bat index ab76cc1..39a09f9 100644 --- a/L5X2ACD Compiler/compile_MTN6_MCM05_CHUTE_LOAD.bat +++ b/L5X2ACD Compiler/compile_mtn6.bat @@ -1,7 +1,7 @@ @echo off echo ==================================== -echo PLC Compilation: MTN6_MCM05_CHUTE_LOAD -echo Project Type: MCM05 +echo PLC Compilation: mtn6 +echo Project Type: UNKNOWN echo ==================================== echo. @@ -9,8 +9,8 @@ cd /d "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler" echo Working directory: %CD% echo. -if not exist "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.L5X" ( - echo ERROR: L5X file not found: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.L5X +if not exist "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.L5X" ( + echo ERROR: L5X file not found: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.L5X pause exit /b 1 ) @@ -61,28 +61,28 @@ if errorlevel 1 ( echo ✓ Logix Designer SDK found echo. -echo Input L5X file: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.L5X -for %%F in ("C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.L5X") do echo File size: %%~zF bytes +echo Input L5X file: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.L5X +for %%F in ("C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.L5X") do echo File size: %%~zF bytes echo. echo Starting compilation... -echo Command: py -3.12 l5x_to_acd.py "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.L5X" +echo Command: py -3.12 l5x_to_acd.py "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.L5X" echo. -py -3.12 l5x_to_acd.py "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.L5X" +py -3.12 l5x_to_acd.py "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.L5X" -if exist "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.ACD" ( +if exist "C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.ACD" ( echo. echo ==================================== echo SUCCESS: Compilation completed! - echo Output: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.ACD - for %%F in ("C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.ACD") do echo ACD size: %%~zF bytes + echo Output: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.ACD + for %%F in ("C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.ACD") do echo ACD size: %%~zF bytes echo ==================================== ) else ( echo. echo ==================================== echo ERROR: Compilation failed! - echo Expected output: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\MTN6_MCM05_CHUTE_LOAD.ACD + echo Expected output: C:\Users\ilia.gurielidze\Projects\PLC Generation\L5X2ACD Compiler\mtn6.ACD echo ==================================== ) diff --git a/PLC Data Generator/DESC_IP_MERGED.xlsx b/PLC Data Generator/DESC_IP_MERGED.xlsx index 20c81fb..26ef655 100644 Binary files a/PLC Data Generator/DESC_IP_MERGED.xlsx and b/PLC Data Generator/DESC_IP_MERGED.xlsx differ diff --git a/PLC Data Generator/MCM01_DESC_IP_MERGED.xlsx b/PLC Data Generator/MCM01_DESC_IP_MERGED.xlsx index 3d256d5..4c63c8c 100644 Binary files a/PLC Data Generator/MCM01_DESC_IP_MERGED.xlsx and b/PLC Data Generator/MCM01_DESC_IP_MERGED.xlsx differ diff --git a/PLC Data Generator/MCM02_DESC_IP_MERGED.xlsx b/PLC Data Generator/MCM02_DESC_IP_MERGED.xlsx new file mode 100644 index 0000000..b295c02 Binary files /dev/null and b/PLC Data Generator/MCM02_DESC_IP_MERGED.xlsx differ diff --git a/PLC Data Generator/MCM02_OUTPUT.csv b/PLC Data Generator/MCM02_OUTPUT.csv new file mode 100644 index 0000000..e2eb76a --- /dev/null +++ b/PLC Data Generator/MCM02_OUTPUT.csv @@ -0,0 +1,410 @@ +Name,Description,Subsystem +Local:5:I.Data.0,MCM02 S_PBLT START,MCM02 +Local:5:I.Data.1,MCM02 ST_PB STOP,MCM02 +Local:5:I.Data.10,SPARE,MCM02 +Local:5:I.Data.11,SPARE,MCM02 +Local:5:I.Data.12,SPARE,MCM02 +Local:5:I.Data.13,SPARE,MCM02 +Local:5:I.Data.14,SPARE,MCM02 +Local:5:I.Data.15,SPARE,MCM02 +Local:5:I.Data.2,MCM02 MF_PBLT MOTOR FAULT RESET,MCM02 +Local:5:I.Data.3,MCM02 JR_PBLT JAM RESET,MCM02 +Local:5:I.Data.4,MCM02 LAP_PBLT LOW AIR PRESSURE,MCM02 +Local:5:I.Data.5,MCM02 PBFR_PBLT POWER BRANCH FAULT RESET,MCM02 +Local:5:I.Data.6,MCM02 BATTERY_FAULT_UPS BATTERY FAULT,MCM02 +Local:5:I.Data.7,MCM02 ON_BATTERY ON BATTERY,MCM02 +Local:5:I.Data.8,MCM02 UPS_BATTERY_LOW UPS BATTERY LOW,MCM02 +Local:5:I.Data.9,MCM02 NAT_SWITCH ALARM NAT SWITCH,MCM02 +Local:7:I.Pt00.Data,MCM02 FIRE_ALARM FIRE ALARM,MCM02 +Local:7:I.Pt01.Data,SPARE,MCM02 +Local:7:I.Pt10.Data,SPARE,MCM02 +Local:7:I.Pt11.Data,SPARE,MCM02 +Local:7:I.Pt12.Data,SPARE,MCM02 +Local:7:I.Pt13.Data,SPARE,MCM02 +Local:7:I.Pt14.Data,SPARE,MCM02 +Local:7:I.Pt15.Data,SPARE,MCM02 +Local:7:I.Pt02.Data,MCM02 ES_PB ES_PB_CH1,MCM02 +Local:7:I.Pt03.Data,MCM02 ES_PB ES_PB_CH2,MCM02 +Local:7:I.Pt04.Data,SPARE,MCM02 +Local:7:I.Pt05.Data,SPARE,MCM02 +Local:7:I.Pt06.Data,SPARE,MCM02 +Local:7:I.Pt07.Data,SPARE,MCM02 +Local:7:I.Pt08.Data,SPARE,MCM02 +Local:7:I.Pt09.Data,SPARE,MCM02 +Local:6:O.Data.0,MCM02 ES_LT EMERGENCY STOP ACTUATED,MCM02 +Local:6:O.Data.1,MCM02 S_PBLT START LIGHT,MCM02 +Local:6:O.Data.10,SPARE,MCM02 +Local:6:O.Data.11,SPARE,MCM02 +Local:6:O.Data.12,SPARE,MCM02 +Local:6:O.Data.13,SPARE,MCM02 +Local:6:O.Data.14,SPARE,MCM02 +Local:6:O.Data.15,SPARE,MCM02 +Local:6:O.Data.2,MCM02 MF_PBLT MOTOR FAULT LIGHT,MCM02 +Local:6:O.Data.3,MCM02 JR_PBLT JAM RESTART LIGHT,MCM02 +Local:6:O.Data.4,MCM02 LAP_PBLT LOW AIR PRESSURE LIGHT,MCM02 +Local:6:O.Data.5,MCM02 PBFR_PBLT POWER BRANCH FAULT RESET LIGHT,MCM02 +Local:6:O.Data.6,SPARE,MCM02 +Local:6:O.Data.7,SPARE,MCM02 +Local:6:O.Data.8,SPARE,MCM02 +Local:6:O.Data.9,SPARE,MCM02 +PS2_3_VFD1:I.In_0,PS2_3_DISC AUXILIARY DISCONNECT,MCM02 +PS2_3_VFD1:I.In_1,SPARE,MCM02 +PS2_3_VFD1:I.In_2,PS2_3_TPE1 TRACKING PHOTOEYE,MCM02 +PS2_3_VFD1:I.In_3,PS2_3_TPE2 TRACKING PHOTOEYE,MCM02 +PS2_3_VFD1:I.IO_0,SPARE,MCM02 +PS2_3_VFD1:I.IO_1,SPARE,MCM02 +PS2_3_VFD1:SI.In00Data,SPARE,MCM02 +PS2_3_VFD1:SI.In01Data,SPARE,MCM02 +PS2_3_VFD1:SI.In02Data,SPARE,MCM02 +PS2_3_VFD1:SI.In03Data,SPARE,MCM02 +PS2_3_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_4A_VFD1:I.In_0,PS2_4A_DISC AUXILIARY DISCONNECT,MCM02 +PS2_4A_VFD1:I.In_1,SPARE,MCM02 +PS2_4A_VFD1:I.In_2,SPARE,MCM02 +PS2_4A_VFD1:I.In_3,SPARE,MCM02 +PS2_4A_VFD1:I.IO_0,SPARE,MCM02 +PS2_4A_VFD1:I.IO_1,SPARE,MCM02 +PS2_4A_VFD1:SI.In00Data,SPARE,MCM02 +PS2_4A_VFD1:SI.In01Data,SPARE,MCM02 +PS2_4A_VFD1:SI.In02Data,SPARE,MCM02 +PS2_4A_VFD1:SI.In03Data,SPARE,MCM02 +PS2_4A_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_4B_VFD1:I.In_0,PS2_4B_DISC AUXILIARY DISCONNECT,MCM02 +PS2_4B_VFD1:I.In_1,SPARE,MCM02 +PS2_4B_VFD1:I.In_2,SPARE,MCM02 +PS2_4B_VFD1:I.In_3,SPARE,MCM02 +PS2_4B_VFD1:O.IO_0,PS2_7_BCN2_R RED BEACON,MCM02 +PS2_4B_VFD1:I.IO_1,SPARE,MCM02 +PS2_4B_VFD1:SI.In00Data,SPARE,MCM02 +PS2_4B_VFD1:SI.In01Data,SPARE,MCM02 +PS2_4B_VFD1:SI.In02Data,SPARE,MCM02 +PS2_4B_VFD1:SI.In03Data,SPARE,MCM02 +PS2_4B_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_5_VFD1:I.In_0,PS2_5_DISC AUXILIARY DISCONNECT,MCM02 +PS2_5_VFD1:I.In_1,PS2_5_ENSH1 SHAFT ENCODER,MCM02 +PS2_5_VFD1:I.In_2,PS2_5_TPE1 TRACKING PHOTOEYE,MCM02 +PS2_5_VFD1:I.In_3,PS2_5_TPE2 TRACKING PHOTOEYE,MCM02 +PS2_5_VFD1:I.IO_0,PS2_7_S2_PB START PUSHBUTTON,MCM02 +PS2_5_VFD1:O.IO_1,PS2_7_S2_PB_LT START PUSHBUTTON LIGHT,MCM02 +PS2_5_VFD1:SI.In00Data,SPARE,MCM02 +PS2_5_VFD1:SI.In01Data,SPARE,MCM02 +PS2_5_VFD1:SI.In02Data,SPARE,MCM02 +PS2_5_VFD1:SI.In03Data,SPARE,MCM02 +PS2_5_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_6_VFD1:I.In_0,PS2_6_DISC AUXILIARY DISCONNECT,MCM02 +PS2_6_VFD1:I.In_1,PS2_6_ENSH1 SHAFT ENCODER,MCM02 +PS2_6_VFD1:I.In_2,PS2_6_TPE1 TRACKING PHOTOEYE,MCM02 +PS2_6_VFD1:I.In_3,SPARE,MCM02 +PS2_6_VFD1:O.IO_0,PS2_7_BCN1_R RED BEACON,MCM02 +PS2_6_VFD1:I.IO_1,SPARE,MCM02 +PS2_6_VFD1:SI.In00Data,SPARE,MCM02 +PS2_6_VFD1:SI.In01Data,SPARE,MCM02 +PS2_6_VFD1:SI.In02Data,SPARE,MCM02 +PS2_6_VFD1:SI.In03Data,SPARE,MCM02 +PS2_6_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_7_VFD1:I.In_0,PS2_7_DISC AUXILIARY DISCONNECT,MCM02 +PS2_7_VFD1:I.In_1,PS2_7_ENSH1 SHAFT ENCODER,MCM02 +PS2_7_VFD1:I.In_2,PS2_7_TPE1 TRACKING PHOTOEYE,MCM02 +PS2_7_VFD1:I.In_3,SPARE,MCM02 +PS2_7_VFD1:I.IO_0,PS2_7_S1_PB START PUSHBUTTON,MCM02 +PS2_7_VFD1:O.IO_1,PS2_7_S1_PB_LT START PUSHBUTTON LIGHT,MCM02 +PS2_7_VFD1:SI.In00Data,PS2_7_EPC1 EMERGENCY PULLCORD,MCM02 +PS2_7_VFD1:SI.In01Data,PS2_7_EPC1_2 EMERGENCY PULLCORD,MCM02 +PS2_7_VFD1:SI.In02Data,PS2_7_EPC2 EMERGENCY PULLCORD,MCM02 +PS2_7_VFD1:SI.In03Data,PS2_7_EPC2_2 EMERGENCY PULLCORD,MCM02 +PS2_7_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_5_VFD1:I.In_0,UL4_5_DISC AUXILIARY DISCONNECT,MCM02 +UL4_5_VFD1:I.In_1,UL4_5_ENSH1 SHAFT ENCODER,MCM02 +UL4_5_VFD1:I.In_2,UL4_5_TPE1 TRACKING PHOTOEYE,MCM02 +UL4_5_VFD1:I.In_3,UL4_5_TPE2 TRACKING PHOTOEYE,MCM02 +UL4_5_VFD1:O.IO_0,UL4_3_BCN1_A AMBER BEACON,MCM02 +UL4_5_VFD1:I.IO_1,SPARE,MCM02 +UL4_5_VFD1:SI.In00Data,SPARE,MCM02 +UL4_5_VFD1:SI.In01Data,SPARE,MCM02 +UL4_5_VFD1:SI.In02Data,SPARE,MCM02 +UL4_5_VFD1:SI.In03Data,SPARE,MCM02 +UL4_5_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_6_VFD1:I.In_0,UL4_6_DISC AUXILIARY DISCONNECT,MCM02 +UL4_6_VFD1:I.In_1,UL4_6_ENSH1 SHAFT ENCODER,MCM02 +UL4_6_VFD1:I.In_2,UL4_6_TPE1 TRACKING PHOTOEYE,MCM02 +UL4_6_VFD1:I.In_3,SPARE,MCM02 +UL4_6_VFD1:I.IO_0,SPARE,MCM02 +UL4_6_VFD1:I.IO_1,SPARE,MCM02 +UL4_6_VFD1:SI.In00Data,SPARE,MCM02 +UL4_6_VFD1:SI.In01Data,SPARE,MCM02 +UL4_6_VFD1:SI.In02Data,SPARE,MCM02 +UL4_6_VFD1:SI.In03Data,SPARE,MCM02 +UL4_6_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_7_VFD1:I.In_0,UL4_7_DISC AUXILIARY DISCONNECT,MCM02 +UL4_7_VFD1:I.In_1,UL4_7_ENSH1 SHAFT ENCODER,MCM02 +UL4_7_VFD1:I.In_2,UL4_7_TPE1 TRACKING PHOTOEYE,MCM02 +UL4_7_VFD1:I.In_3,SPARE,MCM02 +UL4_7_VFD1:I.IO_0,SPARE,MCM02 +UL4_7_VFD1:I.IO_1,SPARE,MCM02 +UL4_7_VFD1:SI.In00Data,SPARE,MCM02 +UL4_7_VFD1:SI.In01Data,SPARE,MCM02 +UL4_7_VFD1:SI.In02Data,SPARE,MCM02 +UL4_7_VFD1:SI.In03Data,SPARE,MCM02 +UL4_7_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_8_VFD1:I.In_0,UL4_8_DISC AUXILIARY DISCONNECT,MCM02 +UL4_8_VFD1:I.In_1,UL4_8_ENSH1 SHAFT ENCODER,MCM02 +UL4_8_VFD1:I.In_2,PS2_1_TPE1 TRACKING PHOTOEYE,MCM02 +UL4_8_VFD1:I.In_3,SPARE,MCM02 +UL4_8_VFD1:I.IO_0,SPARE,MCM02 +UL4_8_VFD1:I.IO_1,SPARE,MCM02 +UL4_8_VFD1:SI.In00Data,SPARE,MCM02 +UL4_8_VFD1:SI.In01Data,SPARE,MCM02 +UL4_8_VFD1:SI.In02Data,SPARE,MCM02 +UL4_8_VFD1:SI.In03Data,SPARE,MCM02 +UL4_8_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_5_VFD1:I.In_0,UL5_5_DISC AUXILIARY DISCONNECT,MCM02 +UL5_5_VFD1:I.In_1,UL5_5_ENSH1 SHAFT ENCODER,MCM02 +UL5_5_VFD1:I.In_2,UL5_5_TPE1 TRACKING PHOTOEYE,MCM02 +UL5_5_VFD1:I.In_3,UL5_5_TPE2 TRACKING PHOTOEYE,MCM02 +UL5_5_VFD1:I.IO_0,SPARE,MCM02 +UL5_5_VFD1:I.IO_1,SPARE,MCM02 +UL5_5_VFD1:SI.In00Data,SPARE,MCM02 +UL5_5_VFD1:SI.In01Data,SPARE,MCM02 +UL5_5_VFD1:SI.In02Data,SPARE,MCM02 +UL5_5_VFD1:SI.In03Data,SPARE,MCM02 +UL5_5_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_6_VFD1:I.In_0,UL5_6_DISC AUXILIARY DISCONNECT,MCM02 +UL5_6_VFD1:I.In_1,UL5_6_ENSH1 SHAFT ENCODER,MCM02 +UL5_6_VFD1:I.In_2,UL5_6_TPE1 TRACKING PHOTOEYE,MCM02 +UL5_6_VFD1:I.In_3,SPARE,MCM02 +UL5_6_VFD1:I.IO_0,SPARE,MCM02 +UL5_6_VFD1:I.IO_1,SPARE,MCM02 +UL5_6_VFD1:SI.In00Data,SPARE,MCM02 +UL5_6_VFD1:SI.In01Data,SPARE,MCM02 +UL5_6_VFD1:SI.In02Data,SPARE,MCM02 +UL5_6_VFD1:SI.In03Data,SPARE,MCM02 +UL5_6_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_7_VFD1:I.In_0,UL5_7_DISC AUXILIARY DISCONNECT,MCM02 +UL5_7_VFD1:I.In_1,UL5_7_ENSH1 SHAFT ENCODER,MCM02 +UL5_7_VFD1:I.In_2,UL5_7_TPE1 TRACKING PHOTOEYE,MCM02 +UL5_7_VFD1:I.In_3,SPARE,MCM02 +UL5_7_VFD1:I.IO_0,SPARE,MCM02 +UL5_7_VFD1:I.IO_1,SPARE,MCM02 +UL5_7_VFD1:SI.In00Data,SPARE,MCM02 +UL5_7_VFD1:SI.In01Data,SPARE,MCM02 +UL5_7_VFD1:SI.In02Data,SPARE,MCM02 +UL5_7_VFD1:SI.In03Data,SPARE,MCM02 +UL5_7_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_8_VFD1:I.In_0,UL5_8_DISC AUXILIARY DISCONNECT,MCM02 +UL5_8_VFD1:I.In_1,UL5_8_ENSH1 SHAFT ENCODER,MCM02 +UL5_8_VFD1:I.In_2,UL5_8_TPE1 TRACKING PHOTOEYE,MCM02 +UL5_8_VFD1:I.In_3,SPARE,MCM02 +UL5_8_VFD1:O.IO_0,PS2_1_BCN1_A AMBER BEACON,MCM02 +UL5_8_VFD1:I.IO_1,SPARE,MCM02 +UL5_8_VFD1:SI.In00Data,SPARE,MCM02 +UL5_8_VFD1:SI.In01Data,SPARE,MCM02 +UL5_8_VFD1:SI.In02Data,SPARE,MCM02 +UL5_8_VFD1:SI.In03Data,SPARE,MCM02 +UL5_8_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_9_VFD1:I.In_0,UL5_9_DISC AUXILIARY DISCONNECT,MCM02 +UL5_9_VFD1:I.In_1,UL5_9_ENSH1 SHAFT ENCODER,MCM02 +UL5_9_VFD1:I.In_2,PS2_1_TPE2 TRACKING PHOTOEYE,MCM02 +UL5_9_VFD1:I.In_3,SPARE,MCM02 +UL5_9_VFD1:I.IO_0,PS2_1_JR1_PB JAM RESET PUSHBUTTON,MCM02 +UL5_9_VFD1:O.IO_1,PS2_1_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM02 +UL5_9_VFD1:SI.In00Data,SPARE,MCM02 +UL5_9_VFD1:SI.In01Data,SPARE,MCM02 +UL5_9_VFD1:SI.In02Data,SPARE,MCM02 +UL5_9_VFD1:SI.In03Data,SPARE,MCM02 +UL5_9_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_1_VFD1:I.In_0,UL6_1_DISC AUXILIARY DISCONNECT,MCM02 +UL6_1_VFD1:I.In_1,SPARE,MCM02 +UL6_1_VFD1:I.In_2,UL6_1_TPE1 TRACKING PHOTOEYE,MCM02 +UL6_1_VFD1:I.In_3,SPARE,MCM02 +UL6_1_VFD1:I.IO_0,SPARE,MCM02 +UL6_1_VFD1:I.IO_1,SPARE,MCM02 +UL6_1_VFD1:SI.In00Data,UL6_1_EPC1 EMERGENCY PULLCORD,MCM02 +UL6_1_VFD1:SI.In01Data,UL6_1_EPC1_2 EMERGENCY PULLCORD,MCM02 +UL6_1_VFD1:SI.In02Data,UL6_1_EPC2 EMERGENCY PULLCORD,MCM02 +UL6_1_VFD1:SI.In03Data,UL6_1_EPC2_2 EMERGENCY PULLCORD,MCM02 +UL6_1_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_4_VFD1:I.In_0,UL6_4_DISC AUXILIARY DISCONNECT,MCM02 +UL6_4_VFD1:I.In_1,UL6_4_ENW1 WHEEL ENCODER,MCM02 +UL6_4_VFD1:I.In_2,UL6_4_TPE1 TRACKING PHOTOEYE,MCM02 +UL6_4_VFD1:I.In_3,UL6_4_TPE2 TRACKING PHOTOEYE,MCM02 +UL6_4_VFD1:O.IO_0,UL6_2_BCN3_A AMBER BEACON,MCM02 +UL6_4_VFD1:I.IO_1,SPARE,MCM02 +UL6_4_VFD1:SI.In00Data,SPARE,MCM02 +UL6_4_VFD1:SI.In01Data,SPARE,MCM02 +UL6_4_VFD1:SI.In02Data,SPARE,MCM02 +UL6_4_VFD1:SI.In03Data,SPARE,MCM02 +UL6_4_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_5_VFD1:I.In_0,UL6_5_DISC AUXILIARY DISCONNECT,MCM02 +UL6_5_VFD1:I.In_1,UL6_5_ENSH1 SHAFT ENCODER,MCM02 +UL6_5_VFD1:I.In_2,UL6_5_TPE1 TRACKING PHOTOEYE,MCM02 +UL6_5_VFD1:I.In_3,SPARE,MCM02 +UL6_5_VFD1:I.IO_0,SPARE,MCM02 +UL6_5_VFD1:I.IO_1,SPARE,MCM02 +UL6_5_VFD1:SI.In00Data,SPARE,MCM02 +UL6_5_VFD1:SI.In01Data,SPARE,MCM02 +UL6_5_VFD1:SI.In02Data,SPARE,MCM02 +UL6_5_VFD1:SI.In03Data,SPARE,MCM02 +UL6_5_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_6_VFD1:I.In_0,UL6_6_DISC AUXILIARY DISCONNECT,MCM02 +UL6_6_VFD1:I.In_1,UL6_6_ENSH1 SHAFT ENCODER,MCM02 +UL6_6_VFD1:I.In_2,UL6_6_TPE1 TRACKING PHOTOEYE,MCM02 +UL6_6_VFD1:I.In_3,SPARE,MCM02 +UL6_6_VFD1:I.IO_0,SPARE,MCM02 +UL6_6_VFD1:I.IO_1,SPARE,MCM02 +UL6_6_VFD1:SI.In00Data,SPARE,MCM02 +UL6_6_VFD1:SI.In01Data,SPARE,MCM02 +UL6_6_VFD1:SI.In02Data,SPARE,MCM02 +UL6_6_VFD1:SI.In03Data,SPARE,MCM02 +UL6_6_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_7_VFD1:I.In_0,UL6_7_DISC AUXILIARY DISCONNECT,MCM02 +UL6_7_VFD1:I.In_1,UL6_7_ENSH1 SHAFT ENCODER,MCM02 +UL6_7_VFD1:I.In_2,UL6_7_TPE1 TRACKING PHOTOEYE,MCM02 +UL6_7_VFD1:I.In_3,SPARE,MCM02 +UL6_7_VFD1:O.IO_0,PS2_1_BCN2_A AMBER BEACON,MCM02 +UL6_7_VFD1:I.IO_1,SPARE,MCM02 +UL6_7_VFD1:SI.In00Data,SPARE,MCM02 +UL6_7_VFD1:SI.In01Data,SPARE,MCM02 +UL6_7_VFD1:SI.In02Data,SPARE,MCM02 +UL6_7_VFD1:SI.In03Data,SPARE,MCM02 +UL6_7_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_8_VFD1:I.In_0,UL6_8_DISC AUXILIARY DISCONNECT,MCM02 +UL6_8_VFD1:I.In_1,UL6_8_ENSH1 SHAFT ENCODER,MCM02 +UL6_8_VFD1:I.In_2,PS2_1_TPE3 TRACKING PHOTOEYE,MCM02 +UL6_8_VFD1:I.In_3,SPARE,MCM02 +UL6_8_VFD1:I.IO_0,PS2_1_JR2_PB JAM RESET PUSHBUTTON,MCM02 +UL6_8_VFD1:O.IO_1,PS2_1_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM02 +UL6_8_VFD1:SI.In00Data,SPARE,MCM02 +UL6_8_VFD1:SI.In01Data,SPARE,MCM02 +UL6_8_VFD1:SI.In02Data,SPARE,MCM02 +UL6_8_VFD1:SI.In03Data,SPARE,MCM02 +UL6_8_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_2_VFD1:I.In_0,PS2_2_DISC AUXILIARY DISCONNECT,MCM02 +PS2_2_VFD1:I.In_1,PS2_2_ENSH1 SHAFT ENCODER,MCM02 +PS2_2_VFD1:I.In_2,SPARE,MCM02 +PS2_2_VFD1:I.In_3,SPARE,MCM02 +PS2_2_VFD1:I.IO_0,PS2_1_BCN3,MCM02 +PS2_2_VFD1:O.IO_1,PS2_1_BCN3_A AMBER BEACON,MCM02 +PS2_2_VFD1:SI.In00Data,SPARE,MCM02 +PS2_2_VFD1:SI.In01Data,SPARE,MCM02 +PS2_2_VFD1:SI.In02Data,SPARE,MCM02 +PS2_2_VFD1:SI.In03Data,SPARE,MCM02 +PS2_2_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_4_VFD1:I.In_0,UL4_4_DISC AUXILIARY DISCONNECT,MCM02 +UL4_4_VFD1:I.In_1,UL4_4_ENSH1 SHAFT ENCODER,MCM02 +UL4_4_VFD1:I.In_2,UL4_3_TPE3 TRACKING PHOTOEYE,MCM02 +UL4_4_VFD1:I.In_3,SPARE,MCM02 +UL4_4_VFD1:I.IO_0,UL4_3_JR1_PB JAM RESET PUSHBUTTON,MCM02 +UL4_4_VFD1:O.IO_1,UL4_3_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM02 +UL4_4_VFD1:SI.In00Data,SPARE,MCM02 +UL4_4_VFD1:SI.In01Data,SPARE,MCM02 +UL4_4_VFD1:SI.In02Data,SPARE,MCM02 +UL4_4_VFD1:SI.In03Data,SPARE,MCM02 +UL4_4_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_4_VFD1:I.In_0,UL5_4_DISC AUXILIARY DISCONNECT,MCM02 +UL5_4_VFD1:I.In_1,UL5_4_ENSH1 SHAFT ENCODER,MCM02 +UL5_4_VFD1:I.In_2,SPARE,MCM02 +UL5_4_VFD1:I.In_3,SPARE,MCM02 +UL5_4_VFD1:I.IO_0,SPARE,MCM02 +UL5_4_VFD1:I.IO_1,SPARE,MCM02 +UL5_4_VFD1:SI.In00Data,SPARE,MCM02 +UL5_4_VFD1:SI.In01Data,SPARE,MCM02 +UL5_4_VFD1:SI.In02Data,SPARE,MCM02 +UL5_4_VFD1:SI.In03Data,SPARE,MCM02 +UL5_4_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_3_VFD1:I.In_0,UL6_3_DISC AUXILIARY DISCONNECT,MCM02 +UL6_3_VFD1:I.In_1,UL6_3_ENSH1 SHAFT ENCODER,MCM02 +UL6_3_VFD1:I.In_2,UL6_2_TPE2 TRACKING PHOTOEYE,MCM02 +UL6_3_VFD1:I.In_3,SPARE,MCM02 +UL6_3_VFD1:I.IO_0,UL6_2_JR1_PB JAM RESET PUSHBUTTON,MCM02 +UL6_3_VFD1:O.IO_1,UL6_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM02 +UL6_3_VFD1:SI.In00Data,SPARE,MCM02 +UL6_3_VFD1:SI.In01Data,SPARE,MCM02 +UL6_3_VFD1:SI.In02Data,SPARE,MCM02 +UL6_3_VFD1:SI.In03Data,SPARE,MCM02 +UL6_3_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_3_VFD1:I.In_0,UL4_3_DISC AUXILIARY DISCONNECT,MCM02 +UL4_3_VFD1:I.In_1,UL4_3_ENW1 WHEEL ENCODER,MCM02 +UL4_3_VFD1:I.In_2,SPARE,MCM02 +UL4_3_VFD1:I.In_3,SPARE,MCM02 +UL4_3_VFD1:I.IO_0,SPARE,MCM02 +UL4_3_VFD1:I.IO_1,SPARE,MCM02 +UL4_3_VFD1:SI.In00Data,SPARE,MCM02 +UL4_3_VFD1:SI.In01Data,SPARE,MCM02 +UL4_3_VFD1:SI.In02Data,SPARE,MCM02 +UL4_3_VFD1:SI.In03Data,SPARE,MCM02 +UL4_3_VFD1:SO.Out00Output,SPARE,MCM02 +UL5_3_VFD1:I.In_0,UL5_3_DISC AUXILIARY DISCONNECT,MCM02 +UL5_3_VFD1:I.In_1,UL5_3_ENW1 WHEEL ENCODER,MCM02 +UL5_3_VFD1:I.In_2,UL5_3_TPE3 TRACKING PHOTOEYE,MCM02 +UL5_3_VFD1:I.In_3,SPARE,MCM02 +UL5_3_VFD1:I.IO_0,UL5_3_JR1_PB JAM RESET PUSHBUTTON,MCM02 +UL5_3_VFD1:O.IO_1,UL5_3_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM02 +UL5_3_VFD1:SI.In00Data,SPARE,MCM02 +UL5_3_VFD1:SI.In01Data,SPARE,MCM02 +UL5_3_VFD1:SI.In02Data,SPARE,MCM02 +UL5_3_VFD1:SI.In03Data,SPARE,MCM02 +UL5_3_VFD1:SO.Out00Output,SPARE,MCM02 +UL6_2_VFD1:I.In_0,UL6_2_DISC AUXILIARY DISCONNECT,MCM02 +UL6_2_VFD1:I.In_1,UL6_2_ENW1 WHEEL ENCODER,MCM02 +UL6_2_VFD1:I.In_2,UL6_2_TPE1 TRACKING PHOTOEYE,MCM02 +UL6_2_VFD1:I.In_3,SPARE,MCM02 +UL6_2_VFD1:I.IO_0,SPARE,MCM02 +UL6_2_VFD1:I.IO_1,SPARE,MCM02 +UL6_2_VFD1:SI.In00Data,SPARE,MCM02 +UL6_2_VFD1:SI.In01Data,SPARE,MCM02 +UL6_2_VFD1:SI.In02Data,SPARE,MCM02 +UL6_2_VFD1:SI.In03Data,SPARE,MCM02 +UL6_2_VFD1:SO.Out00Output,SPARE,MCM02 +PS2_1_VFD1:I.In_0,PS2_1_DISC AUXILIARY DISCONNECT,MCM02 +PS2_1_VFD1:I.In_1,PS2_1_ENW1 WHEEL ENCODER,MCM02 +PS2_1_VFD1:I.In_2,PS2_1_TPE4 TRACKING PHOTOEYE,MCM02 +PS2_1_VFD1:I.In_3,SPARE,MCM02 +PS2_1_VFD1:I.IO_0,PS2_1_JR3_PB JAM RESET PUSHBUTTON,MCM02 +PS2_1_VFD1:O.IO_1,PS2_1_JR3_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM02 +PS2_1_VFD1:SI.In00Data,SPARE,MCM02 +PS2_1_VFD1:SI.In01Data,SPARE,MCM02 +PS2_1_VFD1:SI.In02Data,SPARE,MCM02 +PS2_1_VFD1:SI.In03Data,SPARE,MCM02 +PS2_1_VFD1:SO.Out00Output,SPARE,MCM02 +UL4_3_FIO1:I.Pt01.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt03.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt04.Data,UL4_3_TPE1 TRACKING PHOTOEYE,MCM02 +UL4_3_FIO1:O.Pt05.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt06.Data,UL4_3_TPE2 TRACKING PHOTOEYE,MCM02 +UL4_3_FIO1:O.Pt07.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt08.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt09.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt10.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt11.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt12.Data,SPARE,MCM02 +UL4_3_FIO1:O.Pt13.Data,SPARE,MCM02 +UL4_3_FIO1:I.Pt14.Data,SPARE,MCM02 +UL4_3_FIO1:O.Pt15.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt01.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt03.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt04.Data,UL5_3_TPE1 TRACKING PHOTOEYE,MCM02 +UL5_3_FIO1:O.Pt05.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt06.Data,UL5_3_TPE2 TRACKING PHOTOEYE,MCM02 +UL5_3_FIO1:O.Pt07.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt08.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt09.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt10.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt11.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt12.Data,SPARE,MCM02 +UL5_3_FIO1:O.Pt13.Data,SPARE,MCM02 +UL5_3_FIO1:I.Pt14.Data,SPARE,MCM02 +UL5_3_FIO1:O.Pt15.Data,SPARE,MCM02 +UL6_2_FIO1:I.Pt00.Data,UL6_1_SS1_SPB STOP PUSHBUTTON,MCM02 +UL6_2_FIO1:O.Pt01.Data,UL6_1_SS1_STPB_LT START PUSHBUTTON LIGHT,MCM02 +UL6_2_FIO1:I.Pt02.Data,UL6_1_SS1_STPB START PUSHBUTTON,MCM02 +UL6_2_FIO1:I.Pt03.Data,SPARE,MCM02 +UL6_2_FIO1:O.Pt04.Data,UL6_2_BCN1_R RED BEACON,MCM02 +UL6_2_FIO1:O.Pt05.Data,SPARE,MCM02 +UL6_2_FIO1:O.Pt06.Data,UL6_2_BCN2_R RED BEACON,MCM02 +UL6_2_FIO1:O.Pt07.Data,SPARE,MCM02 +UL6_2_FIO1:I.Pt09.Data,SPARE,MCM02 +UL6_2_FIO1:I.Pt11.Data,SPARE,MCM02 +UL6_2_FIO1:I.Pt12.Data,SPARE,MCM02 +UL6_2_FIO1:O.Pt13.Data,SPARE,MCM02 +UL6_2_FIO1:I.Pt14.Data,SPARE,MCM02 +UL6_2_FIO1:O.Pt15.Data,SPARE,MCM02 diff --git a/PLC Data Generator/MCM05_DESC_IP_MERGED.xlsx b/PLC Data Generator/MCM05_DESC_IP_MERGED.xlsx index 40789b1..9f633a8 100644 Binary files a/PLC Data Generator/MCM05_DESC_IP_MERGED.xlsx and b/PLC Data Generator/MCM05_DESC_IP_MERGED.xlsx differ diff --git a/PLC Data Generator/MCM05_OUTPUT.csv b/PLC Data Generator/MCM05_OUTPUT.csv index 5d8c292..b3fa288 100644 --- a/PLC Data Generator/MCM05_OUTPUT.csv +++ b/PLC Data Generator/MCM05_OUTPUT.csv @@ -1,1682 +1,1681 @@ -Name,Description,Subsystem -FL2074_2_VFD1:I.In_0,FL2074_2_DISC DISCONNECT AUX,MCM05 -FL2074_2_VFD1:I.In_1,SPARE,MCM05 -FL2074_2_VFD1:I.In_2,FL2074_2_PE1 TRACKING PHOTOEYE,MCM05 -FL2074_2_VFD1:I.In_3,FL2074_3CH_PE1 FULL PHOTOEYE,MCM05 -FL2074_2_VFD1:I.IO_0,FL2074_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL2074_2_VFD1:O.IO_1,FL2074_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2074_2_VFD1:SI.In00Data,FL2074_1_ESTOP ESTOP OK,MCM05 -FL2074_2_VFD1:SI.In01Data,SPARE,MCM05 -FL2074_2_VFD1:SI.In02Data,SPARE,MCM05 -FL2074_2_VFD1:SI.In03Data,SPARE,MCM05 -FL2074_2_VFD1:SO.Out00Output,FL2074_1_STO1 ESTOP OK,MCM05 -FL2078_2_VFD1:I.In_0,FL2078_2_DISC DISCONNECT AUX,MCM05 -FL2078_2_VFD1:I.In_1,SPARE,MCM05 -FL2078_2_VFD1:I.In_2,FL2078_2_PE1 TRACKING PHOTOEYE,MCM05 -FL2078_2_VFD1:I.In_3,FL2078_3CH_PE1 FULL PHOTOEYE,MCM05 -FL2078_2_VFD1:I.IO_0,FL2078_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL2078_2_VFD1:O.IO_1,FL2078_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2078_2_VFD1:SI.In00Data,FL2078_1_ESTOP ESTOP OK,MCM05 -FL2078_2_VFD1:SI.In01Data,SPARE,MCM05 -FL2078_2_VFD1:SI.In02Data,SPARE,MCM05 -FL2078_2_VFD1:SI.In03Data,SPARE,MCM05 -FL2078_2_VFD1:SO.Out00Output,FL2078_1_STO1 ESTOP OK,MCM05 -FL2086_2_VFD1:I.In_0,FL2086_2_DISC DISCONNECT AUX,MCM05 -FL2086_2_VFD1:I.In_1,SPARE,MCM05 -FL2086_2_VFD1:I.In_2,FL2086_2_PE1 TRACKING PHOTOEYE,MCM05 -FL2086_2_VFD1:I.In_3,FL2086_3CH_PE1 FULL PHOTOEYE,MCM05 -FL2086_2_VFD1:I.IO_0,FL2086_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL2086_2_VFD1:O.IO_1,FL2086_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2086_2_VFD1:SI.In00Data,FL2086_1_ESTOP ESTOP OK,MCM05 -FL2086_2_VFD1:SI.In01Data,SPARE,MCM05 -FL2086_2_VFD1:SI.In02Data,SPARE,MCM05 -FL2086_2_VFD1:SI.In03Data,SPARE,MCM05 -FL2086_2_VFD1:SO.Out00Output,FL2086_1_STO1 ESTOP OK,MCM05 -FL2090_2_VFD1:I.In_0,FL2090_2_DISC DISCONNECT AUX,MCM05 -FL2090_2_VFD1:I.In_1,SPARE,MCM05 -FL2090_2_VFD1:I.In_2,FL2090_2_PE1 TRACKING PHOTOEYE,MCM05 -FL2090_2_VFD1:I.In_3,FL2090_3CH_PE1 FULL PHOTOEYE,MCM05 -FL2090_2_VFD1:I.IO_0,FL2090_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL2090_2_VFD1:O.IO_1,FL2090_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2090_2_VFD1:SI.In00Data,FL2090_1_ESTOP ESTOP OK,MCM05 -FL2090_2_VFD1:SI.In01Data,SPARE,MCM05 -FL2090_2_VFD1:SI.In02Data,SPARE,MCM05 -FL2090_2_VFD1:SI.In03Data,SPARE,MCM05 -FL2090_2_VFD1:SO.Out00Output,FL2090_1_STO1 ESTOP OK,MCM05 -FL2094_2_VFD1:I.In_0,FL2094_2_DISC DISCONNECT AUX,MCM05 -FL2094_2_VFD1:I.In_1,SPARE,MCM05 -FL2094_2_VFD1:I.In_2,FL2094_2_PE1 TRACKING PHOTOEYE,MCM05 -FL2094_2_VFD1:I.In_3,FL2094_3CH_PE1 FULL PHOTOEYE,MCM05 -FL2094_2_VFD1:I.IO_0,FL2094_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL2094_2_VFD1:O.IO_1,FL2094_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2094_2_VFD1:SI.In00Data,FL2094_1_ESTOP ESTOP OK,MCM05 -FL2094_2_VFD1:SI.In01Data,SPARE,MCM05 -FL2094_2_VFD1:SI.In02Data,SPARE,MCM05 -FL2094_2_VFD1:SI.In03Data,SPARE,MCM05 -FL2094_2_VFD1:SO.Out00Output,FL2094_1_STO1 ESTOP OK,MCM05 -FL4066_2_VFD1:I.In_0,FL4066_2_DISC DISCONNECT AUX,MCM05 -FL4066_2_VFD1:I.In_1,SPARE,MCM05 -FL4066_2_VFD1:I.In_2,FL4066_2_PE1 TRACKING PHOTOEYE,MCM05 -FL4066_2_VFD1:I.In_3,FL4066_3CH_PE1 FULL PHOTOEYE,MCM05 -FL4066_2_VFD1:I.IO_0,FL4066_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL4066_2_VFD1:O.IO_1,FL4066_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4066_2_VFD1:SI.In00Data,FL4066_1_ESTOP ESTOP OK,MCM05 -FL4066_2_VFD1:SI.In01Data,SPARE,MCM05 -FL4066_2_VFD1:SI.In02Data,SPARE,MCM05 -FL4066_2_VFD1:SI.In03Data,SPARE,MCM05 -FL4066_2_VFD1:SO.Out00Output,FL4066_1_STO1 ESTOP OK,MCM05 -FL4070_2_VFD1:I.In_0,FL4070_2_DISC DISCONNECT AUX,MCM05 -FL4070_2_VFD1:I.In_1,SPARE,MCM05 -FL4070_2_VFD1:I.In_2,FL4070_2_PE1 TRACKING PHOTOEYE,MCM05 -FL4070_2_VFD1:I.In_3,FL4070_3CH_PE1 FULL PHOTOEYE,MCM05 -FL4070_2_VFD1:I.IO_0,FL4070_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL4070_2_VFD1:O.IO_1,FL4070_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4070_2_VFD1:SI.In00Data,FL4070_1_ESTOP ESTOP OK,MCM05 -FL4070_2_VFD1:SI.In01Data,SPARE,MCM05 -FL4070_2_VFD1:SI.In02Data,SPARE,MCM05 -FL4070_2_VFD1:SI.In03Data,SPARE,MCM05 -FL4070_2_VFD1:SO.Out00Output,FL4070_1_STO1 ESTOP OK,MCM05 -FL4074_2_VFD1:I.In_0,FL4074_2_DISC DISCONNECT AUX,MCM05 -FL4074_2_VFD1:I.In_1,SPARE,MCM05 -FL4074_2_VFD1:I.In_2,FL4074_2_PE1 TRACKING PHOTOEYE,MCM05 -FL4074_2_VFD1:I.In_3,FL4074_3CH_PE1 FULL PHOTOEYE,MCM05 -FL4074_2_VFD1:I.IO_0,FL4074_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL4074_2_VFD1:O.IO_1,FL4074_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4074_2_VFD1:SI.In00Data,FL4074_1_ESTOP ESTOP OK,MCM05 -FL4074_2_VFD1:SI.In01Data,SPARE,MCM05 -FL4074_2_VFD1:SI.In02Data,SPARE,MCM05 -FL4074_2_VFD1:SI.In03Data,SPARE,MCM05 -FL4074_2_VFD1:SO.Out00Output,FL4074_1_STO1 ESTOP OK,MCM05 -FL4078_2_VFD1:I.In_0,FL4078_2_DISC DISCONNECT AUX,MCM05 -FL4078_2_VFD1:I.In_1,SPARE,MCM05 -FL4078_2_VFD1:I.In_2,FL4078_2_PE1 TRACKING PHOTOEYE,MCM05 -FL4078_2_VFD1:I.In_3,FL4078_3CH_PE1 FULL PHOTOEYE,MCM05 -FL4078_2_VFD1:I.IO_0,FL4078_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL4078_2_VFD1:O.IO_1,FL4078_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4078_2_VFD1:SI.In00Data,FL4078_1_ESTOP ESTOP OK,MCM05 -FL4078_2_VFD1:SI.In01Data,SPARE,MCM05 -FL4078_2_VFD1:SI.In02Data,SPARE,MCM05 -FL4078_2_VFD1:SI.In03Data,SPARE,MCM05 -FL4078_2_VFD1:SO.Out00Output,FL4078_1_STO1 ESTOP OK,MCM05 -FL4082_2_VFD1:I.In_0,FL4082_2_DISC DISCONNECT AUX,MCM05 -FL4082_2_VFD1:I.In_1,SPARE,MCM05 -FL4082_2_VFD1:I.In_2,FL4082_2_PE1 TRACKING PHOTOEYE,MCM05 -FL4082_2_VFD1:I.In_3,FL4082_3CH_PE1 FULL PHOTOEYE,MCM05 -FL4082_2_VFD1:I.IO_0,FL4082_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 -FL4082_2_VFD1:O.IO_1,FL4082_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4082_2_VFD1:SI.In00Data,FL4082_1_ESTOP ESTOP OK,MCM05 -FL4082_2_VFD1:SI.In01Data,SPARE,MCM05 -FL4082_2_VFD1:SI.In02Data,SPARE,MCM05 -FL4082_2_VFD1:SI.In03Data,SPARE,MCM05 -FL4082_2_VFD1:SO.Out00Output,FL4082_1_STO1 ESTOP OK,MCM05 -PDP11_FIO1:I.Pt00.Data,PDP11_CB1 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt01.Data,PDP11_CB2 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt02.Data,PDP11_CB3 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt03.Data,PDP11_CB4 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt04.Data,PDP11_CB5 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:O.Pt05.Data,SPARE,MCM05 -PDP11_FIO1:I.Pt06.Data,PDP11_CB6 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:O.Pt07.Data,SPARE,MCM05 -PDP11_FIO1:I.Pt08.Data,PDP11_CB7 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt09.Data,PDP11_CB8 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt10.Data,PDP11_CB9 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt11.Data,PDP11_CB10 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIO1:I.Pt12.Data,PDP11_PWM1 PHASE MONITOR,MCM05 -PDP11_FIO1:O.Pt13.Data,SPARE,MCM05 -PDP14_FIO1:I.Pt00.Data,PDP14_CB1 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt01.Data,PDP14_CB2 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt02.Data,PDP14_CB3 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt03.Data,PDP14_CB4 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt04.Data,PDP14_CB5 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:O.Pt05.Data,SPARE,MCM05 -PDP14_FIO1:I.Pt06.Data,PDP14_CB6 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:O.Pt07.Data,SPARE,MCM05 -PDP14_FIO1:I.Pt08.Data,PDP14_CB7 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt09.Data,PDP14_CB8 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt10.Data,PDP14_CB9 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt11.Data,PDP14_CB10 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIO1:I.Pt12.Data,PDP14_PWM1 PHASE MONITOR,MCM05 -PDP14_FIO1:O.Pt13.Data,SPARE,MCM05 -VSB_DPM1_FIO1:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO1:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO1:I.Pt12.Data,S012005_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO1:O.Pt13.Data,S012005_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO1:I.Pt14.Data,S012007_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO1:O.Pt15.Data,S012007_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO1:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO1:I.Pt04.Data,S012001_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO1:O.Pt05.Data,S012001_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO1:I.Pt06.Data,S012003_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO1:O.Pt07.Data,S012003_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO1:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO2:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO2:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO2:I.Pt14.Data,S012001_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO2:O.Pt15.Data,S012001_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO2:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO2:I.Pt06.Data,S012002_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO2:O.Pt07.Data,S012002_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO2:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO3:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO3:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO3:I.Pt12.Data,S012013_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO3:O.Pt13.Data,S012013_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO3:I.Pt14.Data,S012015_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO3:O.Pt15.Data,S012015_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO3:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO3:I.Pt04.Data,S012009_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO3:O.Pt05.Data,S012009_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO3:I.Pt06.Data,S012011_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO3:O.Pt07.Data,S012011_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO3:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO4:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO4:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO4:I.Pt14.Data,S012009_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO4:O.Pt15.Data,S012009_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO4:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO4:I.Pt06.Data,S012010_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO4:O.Pt07.Data,S012010_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO4:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO5:I.Pt00.Data,SPARE,MCM05 -VSB_DPM1_FIO5:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO5:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO5:I.Pt12.Data,S012021_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO5:O.Pt13.Data,S012021_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO5:I.Pt14.Data,S012023_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO5:O.Pt15.Data,S012023_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO5:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO5:I.Pt04.Data,SPARE,MCM05 -VSB_DPM1_FIO5:O.Pt05.Data,SPARE,MCM05 -VSB_DPM1_FIO5:I.Pt06.Data,S012019_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO5:O.Pt07.Data,S012019_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO5:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO6:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO6:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO6:I.Pt14.Data,S012019_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO6:O.Pt15.Data,S012019_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO6:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO6:I.Pt06.Data,S012018_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO6:O.Pt07.Data,S012018_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO6:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO7:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO7:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO7:I.Pt12.Data,S012029_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO7:O.Pt13.Data,S012029_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO7:I.Pt14.Data,S012031_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO7:O.Pt15.Data,S012031_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO7:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO7:I.Pt04.Data,S012025_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO7:O.Pt05.Data,S012025_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO7:I.Pt06.Data,S012027_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM1_FIO7:O.Pt07.Data,S012027_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO7:I.Pt09.Data,SPARE,MCM05 -VSB_DPM1_FIO8:I.Pt01.Data,SPARE,MCM05 -VSB_DPM1_FIO8:I.Pt11.Data,SPARE,MCM05 -VSB_DPM1_FIO8:I.Pt14.Data,S012027_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO8:O.Pt15.Data,S012027_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO8:I.Pt03.Data,SPARE,MCM05 -VSB_DPM1_FIO8:I.Pt06.Data,S012026_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM1_FIO8:O.Pt07.Data,S012026_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM1_FIO8:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt00.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt12.Data,S012037_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO1:O.Pt13.Data,S012037_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO1:I.Pt14.Data,S012039_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO1:O.Pt15.Data,S012039_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO1:I.Pt02.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt04.Data,SPARE,MCM05 -VSB_DPM2_FIO1:O.Pt05.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt06.Data,SPARE,MCM05 -VSB_DPM2_FIO1:O.Pt07.Data,SPARE,MCM05 -VSB_DPM2_FIO1:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO2:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO2:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO2:I.Pt14.Data,S012037_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO2:O.Pt15.Data,S012037_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO2:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO2:I.Pt06.Data,S012034_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO2:O.Pt07.Data,S012034_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO2:I.Pt08.Data,SPARE,MCM05 -VSB_DPM2_FIO2:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO3:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO3:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO3:I.Pt12.Data,S012045_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO3:O.Pt13.Data,S012045_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO3:I.Pt14.Data,S012047_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO3:O.Pt15.Data,S012047_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO3:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO3:I.Pt04.Data,S012041_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO3:O.Pt05.Data,S012041_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO3:I.Pt06.Data,S012043_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO3:O.Pt07.Data,S012043_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO3:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO4:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO4:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO4:I.Pt14.Data,S012045_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO4:O.Pt15.Data,S012045_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO4:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO4:I.Pt06.Data,S012044_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO4:O.Pt07.Data,S012044_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO4:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO5:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO5:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO5:I.Pt12.Data,S012053_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO5:O.Pt13.Data,S012053_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO5:I.Pt14.Data,S012055_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO5:O.Pt15.Data,S012055_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO5:I.Pt02.Data,SPARE,MCM05 -VSB_DPM2_FIO5:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO5:I.Pt04.Data,S012049_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO5:O.Pt05.Data,S012049_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO5:I.Pt06.Data,SPARE,MCM05 -VSB_DPM2_FIO5:O.Pt07.Data,SPARE,MCM05 -VSB_DPM2_FIO5:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO6:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO6:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO6:I.Pt14.Data,S012055_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO6:O.Pt15.Data,S012055_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO6:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO6:I.Pt06.Data,S012052_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO6:O.Pt07.Data,S012052_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO6:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO7:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO7:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO7:I.Pt12.Data,S012061_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO7:O.Pt13.Data,S012061_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO7:I.Pt14.Data,S012063_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO7:O.Pt15.Data,S012063_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO7:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO7:I.Pt04.Data,S012057_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO7:O.Pt05.Data,S012057_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO7:I.Pt06.Data,S012059_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM2_FIO7:O.Pt07.Data,S012059_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO7:I.Pt09.Data,SPARE,MCM05 -VSB_DPM2_FIO8:I.Pt01.Data,SPARE,MCM05 -VSB_DPM2_FIO8:I.Pt11.Data,SPARE,MCM05 -VSB_DPM2_FIO8:I.Pt14.Data,S012063_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO8:O.Pt15.Data,S012063_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO8:I.Pt03.Data,SPARE,MCM05 -VSB_DPM2_FIO8:I.Pt06.Data,S012060_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM2_FIO8:O.Pt07.Data,S012060_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM2_FIO8:I.Pt09.Data,SPARE,MCM05 -VSB_DPM3_FIO1:I.Pt01.Data,SPARE,MCM05 -VSB_DPM3_FIO1:I.Pt11.Data,SPARE,MCM05 -VSB_DPM3_FIO1:I.Pt12.Data,S012069_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO1:O.Pt13.Data,S012069_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO1:I.Pt14.Data,S012071_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO1:O.Pt15.Data,S012071_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO1:I.Pt02.Data,SPARE,MCM05 -VSB_DPM3_FIO1:I.Pt03.Data,SPARE,MCM05 -VSB_DPM3_FIO1:I.Pt04.Data,S012065_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO1:O.Pt05.Data,S012065_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO1:I.Pt06.Data,SPARE,MCM05 -VSB_DPM3_FIO1:O.Pt07.Data,SPARE,MCM05 -VSB_DPM3_FIO1:I.Pt09.Data,SPARE,MCM05 -VSB_DPM3_FIO2:I.Pt01.Data,SPARE,MCM05 -VSB_DPM3_FIO2:I.Pt11.Data,SPARE,MCM05 -VSB_DPM3_FIO2:I.Pt14.Data,SPARE,MCM05 -VSB_DPM3_FIO2:O.Pt15.Data,SPARE,MCM05 -VSB_DPM3_FIO2:I.Pt03.Data,SPARE,MCM05 -VSB_DPM3_FIO2:I.Pt06.Data,S012070_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM3_FIO2:O.Pt07.Data,S012070_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO2:I.Pt08.Data,SPARE,MCM05 -VSB_DPM3_FIO2:I.Pt09.Data,SPARE,MCM05 -VSB_DPM3_FIO3:I.Pt01.Data,SPARE,MCM05 -VSB_DPM3_FIO3:I.Pt11.Data,SPARE,MCM05 -VSB_DPM3_FIO3:I.Pt12.Data,S012077_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO3:O.Pt13.Data,S012077_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO3:I.Pt14.Data,S012079_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO3:O.Pt15.Data,S012079_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO3:I.Pt03.Data,SPARE,MCM05 -VSB_DPM3_FIO3:I.Pt04.Data,S012073_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO3:O.Pt05.Data,S012073_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO3:I.Pt06.Data,S012075_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSB_DPM3_FIO3:O.Pt07.Data,S012075_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO3:I.Pt09.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt01.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt10.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt11.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt14.Data,S012073_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSB_DPM3_FIO4:O.Pt15.Data,S012073_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSB_DPM3_FIO4:I.Pt02.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt03.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt06.Data,SPARE,MCM05 -VSB_DPM3_FIO4:O.Pt07.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt08.Data,SPARE,MCM05 -VSB_DPM3_FIO4:I.Pt09.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt00.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt01.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt11.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt14.Data,SPARE,MCM05 -VSB_DPM3_FIO6:O.Pt15.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt02.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt03.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt06.Data,SPARE,MCM05 -VSB_DPM3_FIO6:O.Pt07.Data,SPARE,MCM05 -VSB_DPM3_FIO6:I.Pt09.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt01.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt10.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt11.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt14.Data,SPARE,MCM05 -VSB_DPM3_FIO8:O.Pt15.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt02.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt03.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt06.Data,SPARE,MCM05 -VSB_DPM3_FIO8:O.Pt07.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt08.Data,SPARE,MCM05 -VSB_DPM3_FIO8:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO1:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO1:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO1:I.Pt14.Data,SPARE,MCM05 -VSD_DPM1_FIO1:O.Pt15.Data,SPARE,MCM05 -VSD_DPM1_FIO1:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO1:I.Pt06.Data,S014008_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO1:O.Pt07.Data,S014008_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO1:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO2:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO2:I.Pt10.Data,SPARE,MCM05 -VSD_DPM1_FIO2:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO2:I.Pt12.Data,S014003_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO2:O.Pt13.Data,S014003_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO2:I.Pt14.Data,SPARE,MCM05 -VSD_DPM1_FIO2:O.Pt15.Data,SPARE,MCM05 -VSD_DPM1_FIO2:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO2:I.Pt04.Data,S014007_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO2:O.Pt05.Data,S014007_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO2:I.Pt06.Data,S014005_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO2:O.Pt07.Data,S014005_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO2:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO3:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO3:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO3:I.Pt14.Data,S014009_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO3:O.Pt15.Data,S014009_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO3:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO3:I.Pt06.Data,S014016_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO3:O.Pt07.Data,S014016_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO3:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO4:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO4:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO4:I.Pt12.Data,S014011_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO4:O.Pt13.Data,S014011_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO4:I.Pt14.Data,S014009_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO4:O.Pt15.Data,S014009_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO4:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO4:I.Pt04.Data,S014015_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO4:O.Pt05.Data,S014015_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO4:I.Pt06.Data,S014013_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO4:O.Pt07.Data,S014013_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO4:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO5:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO5:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO5:I.Pt14.Data,S014017_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO5:O.Pt15.Data,S014017_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO5:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO5:I.Pt06.Data,S014024_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO5:O.Pt07.Data,S014024_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO5:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO6:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO6:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO6:I.Pt12.Data,SPARE,MCM05 -VSD_DPM1_FIO6:O.Pt13.Data,SPARE,MCM05 -VSD_DPM1_FIO6:I.Pt14.Data,S014017_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO6:O.Pt15.Data,S014017_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO6:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO6:I.Pt04.Data,S014023_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO6:O.Pt05.Data,S014023_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO6:I.Pt06.Data,S014021_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO6:O.Pt07.Data,S014021_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO6:I.Pt08.Data,SPARE,MCM05 -VSD_DPM1_FIO6:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO7:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO7:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO7:I.Pt14.Data,S014025_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO7:O.Pt15.Data,S014025_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO7:I.Pt02.Data,SPARE,MCM05 -VSD_DPM1_FIO7:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO7:I.Pt06.Data,S014026_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM1_FIO7:O.Pt07.Data,S014026_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO7:I.Pt09.Data,SPARE,MCM05 -VSD_DPM1_FIO8:I.Pt01.Data,SPARE,MCM05 -VSD_DPM1_FIO8:I.Pt11.Data,SPARE,MCM05 -VSD_DPM1_FIO8:I.Pt12.Data,SPARE,MCM05 -VSD_DPM1_FIO8:O.Pt13.Data,SPARE,MCM05 -VSD_DPM1_FIO8:I.Pt14.Data,S014025_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO8:O.Pt15.Data,S014025_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO8:I.Pt03.Data,SPARE,MCM05 -VSD_DPM1_FIO8:I.Pt04.Data,S014031_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO8:O.Pt05.Data,S014031_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO8:I.Pt06.Data,S014029_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM1_FIO8:O.Pt07.Data,S014029_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM1_FIO8:I.Pt08.Data,SPARE,MCM05 -VSD_DPM1_FIO8:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO1:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO1:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO1:I.Pt14.Data,S014033_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO1:O.Pt15.Data,S014033_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO1:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO1:I.Pt06.Data,S014036_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO1:O.Pt07.Data,S014036_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO1:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO2:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO2:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO2:I.Pt12.Data,SPARE,MCM05 -VSD_DPM2_FIO2:O.Pt13.Data,SPARE,MCM05 -VSD_DPM2_FIO2:I.Pt14.Data,S014033_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO2:O.Pt15.Data,S014033_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO2:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO2:I.Pt04.Data,S014039_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO2:O.Pt05.Data,S014039_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO2:I.Pt06.Data,S014037_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO2:O.Pt07.Data,S014037_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO2:I.Pt08.Data,SPARE,MCM05 -VSD_DPM2_FIO2:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO3:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO3:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO3:I.Pt14.Data,S014043_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO3:O.Pt15.Data,S014043_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO3:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO3:I.Pt06.Data,S014044_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO3:O.Pt07.Data,S014044_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO3:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO4:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO4:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO4:I.Pt12.Data,S014043_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO4:O.Pt13.Data,S014043_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO4:I.Pt14.Data,S014041_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO4:O.Pt15.Data,S014041_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO4:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO4:I.Pt04.Data,S014047_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO4:O.Pt05.Data,S014047_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO4:I.Pt06.Data,S014045_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO4:O.Pt07.Data,S014045_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO4:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO5:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO5:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO5:I.Pt14.Data,S014049_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO5:O.Pt15.Data,S014049_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO5:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO5:I.Pt06.Data,S014052_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO5:O.Pt07.Data,S014052_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO5:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO6:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO6:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO6:I.Pt12.Data,SPARE,MCM05 -VSD_DPM2_FIO6:O.Pt13.Data,SPARE,MCM05 -VSD_DPM2_FIO6:I.Pt14.Data,S014049_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO6:O.Pt15.Data,S014049_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO6:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO6:I.Pt04.Data,S014055_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO6:O.Pt05.Data,S014055_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO6:I.Pt06.Data,S014053_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO6:O.Pt07.Data,S014053_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO6:I.Pt08.Data,SPARE,MCM05 -VSD_DPM2_FIO6:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO7:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO7:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO7:I.Pt14.Data,S014058_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO7:O.Pt15.Data,S014058_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO7:I.Pt02.Data,S014057_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO7:O.Pt03.Data,S014057_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO7:I.Pt06.Data,S014064_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM2_FIO7:O.Pt07.Data,S014064_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO7:I.Pt09.Data,SPARE,MCM05 -VSD_DPM2_FIO8:I.Pt01.Data,SPARE,MCM05 -VSD_DPM2_FIO8:I.Pt11.Data,SPARE,MCM05 -VSD_DPM2_FIO8:I.Pt12.Data,SPARE,MCM05 -VSD_DPM2_FIO8:O.Pt13.Data,SPARE,MCM05 -VSD_DPM2_FIO8:I.Pt14.Data,S014057_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO8:O.Pt15.Data,S014057_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO8:I.Pt03.Data,SPARE,MCM05 -VSD_DPM2_FIO8:I.Pt04.Data,S014063_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO8:O.Pt05.Data,S014063_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO8:I.Pt06.Data,S014061_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM2_FIO8:O.Pt07.Data,S014061_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM2_FIO8:I.Pt08.Data,SPARE,MCM05 -VSD_DPM2_FIO8:I.Pt09.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt00.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt01.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt11.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt14.Data,S014065_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM3_FIO1:O.Pt15.Data,S014065_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO1:I.Pt02.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt03.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt06.Data,S014072_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM3_FIO1:O.Pt07.Data,S014072_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO1:I.Pt08.Data,SPARE,MCM05 -VSD_DPM3_FIO1:I.Pt09.Data,SPARE,MCM05 -VSD_DPM3_FIO2:I.Pt01.Data,SPARE,MCM05 -VSD_DPM3_FIO2:I.Pt11.Data,SPARE,MCM05 -VSD_DPM3_FIO2:I.Pt12.Data,SPARE,MCM05 -VSD_DPM3_FIO2:O.Pt13.Data,SPARE,MCM05 -VSD_DPM3_FIO2:I.Pt14.Data,S014065_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO2:O.Pt15.Data,S014065_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO2:I.Pt03.Data,SPARE,MCM05 -VSD_DPM3_FIO2:I.Pt04.Data,S014071_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO2:O.Pt05.Data,S014071_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO2:I.Pt06.Data,S014069_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO2:O.Pt07.Data,S014069_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO2:I.Pt08.Data,SPARE,MCM05 -VSD_DPM3_FIO2:I.Pt09.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt00.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt01.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt11.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt14.Data,S014073_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM3_FIO3:O.Pt15.Data,S014073_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO3:I.Pt02.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt03.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt06.Data,S014080_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM3_FIO3:O.Pt07.Data,S014080_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO3:I.Pt08.Data,SPARE,MCM05 -VSD_DPM3_FIO3:I.Pt09.Data,SPARE,MCM05 -VSD_DPM3_FIO4:I.Pt01.Data,SPARE,MCM05 -VSD_DPM3_FIO4:I.Pt11.Data,SPARE,MCM05 -VSD_DPM3_FIO4:I.Pt12.Data,SPARE,MCM05 -VSD_DPM3_FIO4:O.Pt13.Data,SPARE,MCM05 -VSD_DPM3_FIO4:I.Pt14.Data,S014073_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO4:O.Pt15.Data,S014073_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO4:I.Pt03.Data,SPARE,MCM05 -VSD_DPM3_FIO4:I.Pt04.Data,S014079_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO4:O.Pt05.Data,S014079_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO4:I.Pt06.Data,S014077_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO4:O.Pt07.Data,S014077_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO4:I.Pt08.Data,SPARE,MCM05 -VSD_DPM3_FIO4:I.Pt09.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt00.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt01.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt10.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt11.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt12.Data,SPARE,MCM05 -VSD_DPM3_FIO5:O.Pt13.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt14.Data,S014081_JR1_PB JAM RESET PUSHBUTTON,MCM05 -VSD_DPM3_FIO5:O.Pt15.Data,S014081_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO5:I.Pt02.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt03.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt06.Data,SPARE,MCM05 -VSD_DPM3_FIO5:O.Pt07.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt08.Data,SPARE,MCM05 -VSD_DPM3_FIO5:I.Pt09.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt00.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt01.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt11.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt12.Data,SPARE,MCM05 -VSD_DPM3_FIO6:O.Pt13.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt14.Data,S014081_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 -VSD_DPM3_FIO6:O.Pt15.Data,S014081_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 -VSD_DPM3_FIO6:I.Pt02.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt03.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt04.Data,SPARE,MCM05 -VSD_DPM3_FIO6:O.Pt05.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt06.Data,SPARE,MCM05 -VSD_DPM3_FIO6:O.Pt07.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt08.Data,SPARE,MCM05 -VSD_DPM3_FIO6:I.Pt09.Data,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL2074_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -FL2074_FIOH1:O.ProcessDataOut.Connector_6_B_Pin_2,FL2074_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL2074_1CH_PE1 FULL PHOTOEYE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -FL2074_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL2074_2_BCN2_A AMBER BEACON LIGHT,MCM05 -FL2074_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL2074_2_BCN2_B BLUE BEACON LIGHT,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 -FL2074_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -FL2074_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL2074_2_BCN1_A AMBER BEACON LIGHT,MCM05 -FL2074_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL2074_2_BCN1_B BLUE BEACON LIGHT,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL2086_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -FL2086_FIOH1:O.ProcessDataOut.Connector_6_B_Pin_2,FL2086_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL2086_1CH_PE1 FULL PHOTOEYE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -FL2086_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL2086_2_BCN2_A AMBER BEACON LIGHT,MCM05 -FL2086_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL2086_2_BCN2_B BLUE BEACON LIGHT,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 -FL2086_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -FL2086_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL2086_2_BCN1_A AMBER BEACON LIGHT,MCM05 -FL2086_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL2086_2_BCN1_B BLUE BEACON LIGHT,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL2090_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -FL2090_FIOH1:O.ProcessDataOut.Connector_6_B_Pin_2,FL2090_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL2090_1CH_PE1 FULL PHOTOEYE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -FL2090_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL2090_2_BCN2_A AMBER BEACON LIGHT,MCM05 -FL2090_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL2090_2_BCN2_B BLUE BEACON LIGHT,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 -FL2090_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -FL2090_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL2090_2_BCN1_A AMBER BEACON LIGHT,MCM05 -FL2090_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL2090_2_BCN1_B BLUE BEACON LIGHT,MCM05 -FL4066_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL4066_2_BCN1_A AMBER BEACON LIGHT,MCM05 -FL4066_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL4066_2_BCN1_B BLUE BEACON LIGHT,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL4066_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -FL4066_FIOH1:O.ProcessDataOut.Connector_5_B_Pin_2,FL4066_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL4066_1CH_PE1 FULL PHOTOEYE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -FL4066_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL4066_2_BCN2_A AMBER BEACON LIGHT,MCM05 -FL4066_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL4066_2_BCN2_B BLUE BEACON LIGHT,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 -FL4066_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -FL4074_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL4074_2_BCN1_A AMBER BEACON LIGHT,MCM05 -FL4074_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL4074_2_BCN1_B BLUE BEACON LIGHT,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL4074_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -FL4074_FIOH1:O.ProcessDataOut.Connector_5_B_Pin_2,FL4074_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL4074_1CH_PE1 FULL PHOTOEYE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -FL4074_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL4074_2_BCN2_A AMBER BEACON LIGHT,MCM05 -FL4074_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL4074_2_BCN2_B BLUE BEACON LIGHT,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 -FL4074_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -FL4082_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL4082_2_BCN1_A AMBER BEACON LIGHT,MCM05 -FL4082_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL4082_2_BCN1_B BLUE BEACON LIGHT,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL4082_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -FL4082_FIOH1:O.ProcessDataOut.Connector_5_B_Pin_2,FL4082_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL4082_1CH_PE1 FULL PHOTOEYE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -FL4082_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL4082_2_BCN2_A AMBER BEACON LIGHT,MCM05 -FL4082_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL4082_2_BCN2_B BLUE BEACON LIGHT,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 -FL4082_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,PDP11_CB11 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,PDP11_CB12 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,PDP11_CB13 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,PDP11_CB14 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,PDP11_CB15 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,PDP11_CB16 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,PDP11_CB17 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,PDP11_CB18 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,PDP11_CB19 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,PDP11_CB20 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,PDP11_CB21 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,PDP11_CB22 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,PDP11_CB23 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,PDP11_CB24 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,PDP11_CB25 CIRCUIT BREAKER MONITORING,MCM05 -PDP11_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,PDP11_CB26 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,PDP14_CB11 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,PDP14_CB12 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,PDP14_CB13 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,PDP14_CB14 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,PDP14_CB15 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,PDP14_CB16 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,PDP14_CB17 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,PDP14_CB18 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,PDP14_CB19 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,PDP14_CB20 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,PDP14_CB21 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,PDP14_CB22 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,PDP14_CB23 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,PDP14_CB24 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,PDP14_CB25 CIRCUIT BREAKER MONITORING,MCM05 -PDP14_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,PDP14_CB26 CIRCUIT BREAKER MONITORING,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012004_PE1 FULL PHOTOEYE 100%,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012002_PE2 FULL PHOTOEYE 50%,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012002_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012002_SOL1 PKG RELEASE SOLENOID,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012004_PE2 FULL PHOTOEYE 50%,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012004_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012004_SOL1 PKG RELEASE SOLENOID,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012002_PE1 FULL PHOTOEYE 100%,MCM05 -S012004_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012008_PE1 FULL PHOTOEYE 100%,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012006_PE2 FULL PHOTOEYE 50%,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012006_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012006_SOL1 PKG RELEASE SOLENOID,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012008_PE2 FULL PHOTOEYE 50%,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012008_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012008_SOL1 PKG RELEASE SOLENOID,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012006_PE1 FULL PHOTOEYE 100%,MCM05 -S012008_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012012_PE1 FULL PHOTOEYE 100%,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012010_PE2 FULL PHOTOEYE 50%,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012010_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012010_SOL1 PKG RELEASE SOLENOID,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012012_PE2 FULL PHOTOEYE 50%,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012012_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012012_SOL1 PKG RELEASE SOLENOID,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012010_PE1 FULL PHOTOEYE 100%,MCM05 -S012012_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012016_PE1 FULL PHOTOEYE 100%,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012014_PE2 FULL PHOTOEYE 50%,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012014_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012014_SOL1 PKG RELEASE SOLENOID,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012016_PE2 FULL PHOTOEYE 50%,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012016_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012016_SOL1 PKG RELEASE SOLENOID,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012014_PE1 FULL PHOTOEYE 100%,MCM05 -S012016_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012020_PE1 FULL PHOTOEYE 100%,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012018_PE2 FULL PHOTOEYE 50%,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012018_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012018_SOL1 PKG RELEASE SOLENOID,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012020_PE2 FULL PHOTOEYE 50%,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012020_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012020_SOL1 PKG RELEASE SOLENOID,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012018_PE1 FULL PHOTOEYE 100%,MCM05 -S012020_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012024_PE1 FULL PHOTOEYE 100%,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012022_PE2 FULL PHOTOEYE 50%,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012022_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012022_SOL1 PKG RELEASE SOLENOID,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012024_PE2 FULL PHOTOEYE 50%,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012024_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012024_SOL1 PKG RELEASE SOLENOID,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012022_PE1 FULL PHOTOEYE 100%,MCM05 -S012024_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012028_PE1 FULL PHOTOEYE 100%,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012026_PE2 FULL PHOTOEYE 50%,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012026_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012026_SOL1 PKG RELEASE SOLENOID,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012028_PE2 FULL PHOTOEYE 50%,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012028_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012028_SOL1 PKG RELEASE SOLENOID,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012026_PE1 FULL PHOTOEYE 100%,MCM05 -S012028_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012032_PE1 FULL PHOTOEYE 100%,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012030_PE2 FULL PHOTOEYE 50%,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012030_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012030_SOL1 PKG RELEASE SOLENOID,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012032_PE2 FULL PHOTOEYE 50%,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012032_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012032_SOL1 PKG RELEASE SOLENOID,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012030_PE1 FULL PHOTOEYE 100%,MCM05 -S012032_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012034_PE2 FULL PHOTOEYE 50%,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012034_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012034_SOL1 PKG RELEASE SOLENOID,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012034_PE1 FULL PHOTOEYE 100%,MCM05 -S012034_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012040_PE1 FULL PHOTOEYE 100%,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012038_PE2 FULL PHOTOEYE 50%,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012038_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012038_SOL1 PKG RELEASE SOLENOID,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012040_PE2 FULL PHOTOEYE 50%,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012040_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012040_SOL1 PKG RELEASE SOLENOID,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012038_PE1 FULL PHOTOEYE 100%,MCM05 -S012040_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012044_PE1 FULL PHOTOEYE 100%,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012042_PE2 FULL PHOTOEYE 50%,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012042_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012042_SOL1 PKG RELEASE SOLENOID,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012044_PE2 FULL PHOTOEYE 50%,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012044_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012044_SOL1 PKG RELEASE SOLENOID,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012042_PE1 FULL PHOTOEYE 100%,MCM05 -S012044_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012048_PE1 FULL PHOTOEYE 100%,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012046_PE2 FULL PHOTOEYE 50%,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012046_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012046_SOL1 PKG RELEASE SOLENOID,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012048_PE2 FULL PHOTOEYE 50%,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012048_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012048_SOL1 PKG RELEASE SOLENOID,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012046_PE1 FULL PHOTOEYE 100%,MCM05 -S012048_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012052_PE1 FULL PHOTOEYE 100%,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012050_PE2 FULL PHOTOEYE 50%,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012050_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012050_SOL1 PKG RELEASE SOLENOID,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012052_PE2 FULL PHOTOEYE 50%,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012052_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012052_SOL1 PKG RELEASE SOLENOID,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012050_PE1 FULL PHOTOEYE 100%,MCM05 -S012052_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012056_PE1 FULL PHOTOEYE 100%,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012054_PE2 FULL PHOTOEYE 50%,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012054_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012054_SOL1 PKG RELEASE SOLENOID,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012056_PE2 FULL PHOTOEYE 50%,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012056_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012056_SOL1 PKG RELEASE SOLENOID,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012054_PE1 FULL PHOTOEYE 100%,MCM05 -S012056_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012060_PE1 FULL PHOTOEYE 100%,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012058_PE2 FULL PHOTOEYE 50%,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012058_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012058_SOL1 PKG RELEASE SOLENOID,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012060_PE2 FULL PHOTOEYE 50%,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012060_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012060_SOL1 PKG RELEASE SOLENOID,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012058_PE1 FULL PHOTOEYE 100%,MCM05 -S012060_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012064_PE1 FULL PHOTOEYE 100%,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012062_PE2 FULL PHOTOEYE 50%,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012062_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012062_SOL1 PKG RELEASE SOLENOID,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012064_PE2 FULL PHOTOEYE 50%,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012064_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012064_SOL1 PKG RELEASE SOLENOID,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012062_PE1 FULL PHOTOEYE 100%,MCM05 -S012064_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012066_PE2 FULL PHOTOEYE 50%,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012066_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012066_SOL1 PKG RELEASE SOLENOID,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012066_PE1 FULL PHOTOEYE 100%,MCM05 -S012066_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012072_PE1 FULL PHOTOEYE 100%,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012070_PE2 FULL PHOTOEYE 50%,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012070_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012070_SOL1 PKG RELEASE SOLENOID,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012072_PE2 FULL PHOTOEYE 50%,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012072_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012072_SOL1 PKG RELEASE SOLENOID,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012070_PE1 FULL PHOTOEYE 100%,MCM05 -S012072_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012080_PE1 FULL PHOTOEYE 100%,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL2078_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -S012080_FIOH1:O.ProcessDataOut.Connector_3_B_Pin_2,FL2078_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL2078_1CH_PE1 FULL PHOTOEYE,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012080_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL2078_2_BCN2_A AMBER BEACON LIGHT,MCM05 -S012080_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL2078_2_BCN2_B BLUE BEACON LIGHT,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012080_PE2 FULL PHOTOEYE 50%,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012080_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012080_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012080_SOL1 PKG RELEASE SOLENOID,MCM05 -S012080_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012080_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL2078_2_BCN1_A AMBER BEACON LIGHT,MCM05 -S012080_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL2078_2_BCN1_B BLUE BEACON LIGHT,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012084_PE1 FULL PHOTOEYE 100%,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012082_PE2 FULL PHOTOEYE 50%,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012082_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012082_SOL1 PKG RELEASE SOLENOID,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012084_PE2 FULL PHOTOEYE 50%,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012084_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012084_SOL1 PKG RELEASE SOLENOID,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012082_PE1 FULL PHOTOEYE 100%,MCM05 -S012084_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012096_PE1 FULL PHOTOEYE 100%,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL2094_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -S012096_FIOH1:O.ProcessDataOut.Connector_3_B_Pin_2,FL2094_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL2094_1CH_PE1 FULL PHOTOEYE,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S012096_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL2094_2_BCN2_A ,MCM05 -S012096_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL2094_2_BCN2_B BLUE BEACON LIGHT,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012096_PE2 FULL PHOTOEYE 50%,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012096_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S012096_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012096_SOL1 PKG RELEASE SOLENOID,MCM05 -S012096_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S012096_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL2094_2_BCN1_A ,MCM05 -S012096_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL2094_2_BCN1_B BLUE BEACON LIGHT,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014002_PE1 FULL PHOTOEYE 100%,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014004_PE2 FULL PHOTOEYE 50%,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014004_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014004_SOL1 PKG RELEASE SOLENOID,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014002_PE2 FULL PHOTOEYE 50%,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014002_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014002_SOL1 PKG RELEASE SOLENOID,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014004_PE1 FULL PHOTOEYE 100%,MCM05 -S014004_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014006_PE1 FULL PHOTOEYE 100%,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014008_PE2 FULL PHOTOEYE 50%,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014008_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014008_SOL1 PKG RELEASE SOLENOID,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014006_PE2 FULL PHOTOEYE 50%,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014006_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014006_SOL1 PKG RELEASE SOLENOID,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014008_PE1 FULL PHOTOEYE 100%,MCM05 -S014008_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014010_PE1 FULL PHOTOEYE 100%,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014012_PE2 FULL PHOTOEYE 50%,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014012_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014012_SOL1 PKG RELEASE SOLENOID,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014010_PE2 FULL PHOTOEYE 50%,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014010_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014010_SOL1 PKG RELEASE SOLENOID,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014012_PE1 FULL PHOTOEYE 100%,MCM05 -S014012_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014014_PE1 FULL PHOTOEYE 100%,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014016_PE2 FULL PHOTOEYE 50%,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014016_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014016_SOL1 PKG RELEASE SOLENOID,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014014_PE2 FULL PHOTOEYE 50%,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014014_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014014_SOL1 PKG RELEASE SOLENOID,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014016_PE1 FULL PHOTOEYE 100%,MCM05 -S014016_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014018_PE1 FULL PHOTOEYE 100%,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014020_PE2 FULL PHOTOEYE 50%,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014020_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014020_SOL1 PKG RELEASE SOLENOID,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014018_PE2 FULL PHOTOEYE 50%,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014018_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014018_SOL1 PKG RELEASE SOLENOID,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014020_PE1 FULL PHOTOEYE 100%,MCM05 -S014020_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014022_PE1 FULL PHOTOEYE 100%,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014024_PE2 FULL PHOTOEYE 50%,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014024_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014024_SOL1 PKG RELEASE SOLENOID,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014022_PE2 FULL PHOTOEYE 50%,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014022_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014022_SOL1 PKG RELEASE SOLENOID,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014024_PE1 FULL PHOTOEYE 100%,MCM05 -S014024_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014026_PE1 FULL PHOTOEYE 100%,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014026_PE2 FULL PHOTOEYE 50%,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014026_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014026_SOL1 PKG RELEASE SOLENOID,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 -S014026_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014030_PE1 FULL PHOTOEYE 100%,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014032_PE2 FULL PHOTOEYE 50%,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014032_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014032_SOL1 PKG RELEASE SOLENOID,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014030_PE2 FULL PHOTOEYE 50%,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014030_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014030_SOL1 PKG RELEASE SOLENOID,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014032_PE1 FULL PHOTOEYE 100%,MCM05 -S014032_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014034_PE1 FULL PHOTOEYE 100%,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014036_PE2 FULL PHOTOEYE 50%,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014036_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014036_SOL1 PKG RELEASE SOLENOID,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014034_PE2 FULL PHOTOEYE 50%,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014034_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014034_SOL1 PKG RELEASE SOLENOID,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014036_PE1 FULL PHOTOEYE 100%,MCM05 -S014036_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014038_PE1 FULL PHOTOEYE 100%,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014040_PE2 FULL PHOTOEYE 50%,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014040_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014040_SOL1 PKG RELEASE SOLENOID,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014038_PE2 FULL PHOTOEYE 50%,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014038_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014038_SOL1 PKG RELEASE SOLENOID,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014040_PE1 FULL PHOTOEYE 100%,MCM05 -S014040_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014042_PE1 FULL PHOTOEYE 100%,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014044_PE2 FULL PHOTOEYE 50%,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014044_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014044_SOL1 PKG RELEASE SOLENOID,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014042_PE2 FULL PHOTOEYE 50%,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014042_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014042_SOL1 PKG RELEASE SOLENOID,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014044_PE1 FULL PHOTOEYE 100%,MCM05 -S014044_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014046_PE1 FULL PHOTOEYE 100%,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014048_PE2 FULL PHOTOEYE 50%,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014048_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014048_SOL1 PKG RELEASE SOLENOID,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014046_PE2 FULL PHOTOEYE 50%,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014046_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014046_SOL1 PKG RELEASE SOLENOID,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014048_PE1 FULL PHOTOEYE 100%,MCM05 -S014048_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014050_PE1 FULL PHOTOEYE 100%,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014052_PE2 FULL PHOTOEYE 50%,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014052_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014052_SOL1 PKG RELEASE SOLENOID,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014050_PE2 FULL PHOTOEYE 50%,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014050_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014050_SOL1 PKG RELEASE SOLENOID,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014052_PE1 FULL PHOTOEYE 100%,MCM05 -S014052_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014054_PE1 FULL PHOTOEYE 100%,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014056_PE2 FULL PHOTOEYE 50%,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014056_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014056_SOL1 PKG RELEASE SOLENOID,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014054_PE2 FULL PHOTOEYE 50%,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014054_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014054_SOL1 PKG RELEASE SOLENOID,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014056_PE1 FULL PHOTOEYE 100%,MCM05 -S014056_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014058_PE1 FULL PHOTOEYE 100%,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014058_PE2 FULL PHOTOEYE 50%,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014058_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014058_SOL1 PKG RELEASE SOLENOID,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 -S014058_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014062_PE1 FULL PHOTOEYE 100%,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014064_PE2 FULL PHOTOEYE 50%,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014064_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014064_SOL1 PKG RELEASE SOLENOID,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014062_PE2 FULL PHOTOEYE 50%,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014062_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014062_SOL1 PKG RELEASE SOLENOID,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014064_PE1 FULL PHOTOEYE 100%,MCM05 -S014064_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014072_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL4070_2_BCN1_A AMBER BEACON LIGHT,MCM05 -S014072_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL4070_2_BCN1_B BLUE BEACON LIGHT,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014072_PE2 FULL PHOTOEYE 50%,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014072_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014072_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014072_SOL1 PKG RELEASE SOLENOID,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL4070_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -S014072_FIOH1:O.ProcessDataOut.Connector_4_B_Pin_2,FL4070_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL4070_1CH_PE1 FULL PHOTOEYE,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014072_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL4070_2_BCN2_A ,MCM05 -S014072_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL4070_2_BCN2_B BLUE BEACON LIGHT,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014072_PE1 FULL PHOTOEYE 100%,MCM05 -S014072_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S014080_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL4078_2_BCN1_A AMBER BEACON LIGHT,MCM05 -S014080_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL4078_2_BCN1_B BLUE BEACON LIGHT,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014080_PE2 FULL PHOTOEYE 50%,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014080_PR1 PKG RELEASE PUSHBUTTON,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 -S014080_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014080_SOL1 PKG RELEASE SOLENOID,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL4078_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 -S014080_FIOH1:O.ProcessDataOut.Connector_4_B_Pin_2,FL4078_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL4078_1CH_PE1 FULL PHOTOEYE,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 -S014080_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL4078_2_BCN2_A ,MCM05 -S014080_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL4078_2_BCN2_B BLUE BEACON LIGHT,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014080_PE1 FULL PHOTOEYE 100%,MCM05 -S014080_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 -S012001_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012001_BCN1 GREEN SEGMENT,MCM05 -S012001_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012001_BCN1 AMBER SEGMENT,MCM05 -S012001_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012001_BCN1 BLUE SEGMENT,MCM05 -S012002_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012002_BCN1 GREEN SEGMENT,MCM05 -S012002_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012002_BCN1 AMBER SEGMENT,MCM05 -S012002_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012002_BCN1 BLUE SEGMENT,MCM05 -S012009_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012009_BCN1 GREEN SEGMENT,MCM05 -S012009_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012009_BCN1 AMBER SEGMENT,MCM05 -S012009_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012009_BCN1 BLUE SEGMENT,MCM05 -S012010_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012010_BCN1 GREEN SEGMENT,MCM05 -S012010_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012010_BCN1 AMBER SEGMENT,MCM05 -S012010_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012010_BCN1 BLUE SEGMENT,MCM05 -S012019_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012019_BCN1 GREEN SEGMENT,MCM05 -S012019_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012019_BCN1 AMBER SEGMENT,MCM05 -S012019_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012019_BCN1 BLUE SEGMENT,MCM05 -S012018_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012018_BCN1 GREEN SEGMENT,MCM05 -S012018_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012018_BCN1 AMBER SEGMENT,MCM05 -S012018_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012018_BCN1 BLUE SEGMENT,MCM05 -S012027_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012027_BCN1 GREEN SEGMENT,MCM05 -S012027_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012027_BCN1 AMBER SEGMENT,MCM05 -S012027_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012027_BCN1 BLUE SEGMENT,MCM05 -S012026_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012026_BCN1 GREEN SEGMENT,MCM05 -S012026_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012026_BCN1 AMBER SEGMENT,MCM05 -S012026_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012026_BCN1 BLUE SEGMENT,MCM05 -S012037_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012037_BCN1 GREEN SEGMENT,MCM05 -S012037_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012037_BCN1 AMBER SEGMENT,MCM05 -S012037_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012037_BCN1 BLUE SEGMENT,MCM05 -S012034_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012034_BCN1 GREEN SEGMENT,MCM05 -S012034_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012034_BCN1 AMBER SEGMENT,MCM05 -S012034_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012034_BCN1 BLUE SEGMENT,MCM05 -S012045_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012045_BCN1 GREEN SEGMENT,MCM05 -S012045_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012045_BCN1 AMBER SEGMENT,MCM05 -S012045_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012045_BCN1 BLUE SEGMENT,MCM05 -S012044_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012044_BCN1 GREEN SEGMENT,MCM05 -S012044_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012044_BCN1 AMBER SEGMENT,MCM05 -S012044_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012044_BCN1 BLUE SEGMENT,MCM05 -S012055_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012055_BCN1 GREEN SEGMENT,MCM05 -S012055_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012055_BCN1 AMBER SEGMENT,MCM05 -S012055_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012055_BCN1 BLUE SEGMENT,MCM05 -S012052_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012052_BCN1 GREEN SEGMENT,MCM05 -S012052_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012052_BCN1 AMBER SEGMENT,MCM05 -S012052_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012052_BCN1 BLUE SEGMENT,MCM05 -S012063_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012063_BCN1 GREEN SEGMENT,MCM05 -S012063_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012063_BCN1 AMBER SEGMENT,MCM05 -S012063_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012063_BCN1 BLUE SEGMENT,MCM05 -S012060_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012060_BCN1 GREEN SEGMENT,MCM05 -S012060_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012060_BCN1 AMBER SEGMENT,MCM05 -S012060_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012060_BCN1 BLUE SEGMENT,MCM05 -S012070_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012070_BCN1 GREEN SEGMENT,MCM05 -S012070_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012070_BCN1 AMBER SEGMENT,MCM05 -S012070_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012070_BCN1 BLUE SEGMENT,MCM05 -S012073_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012073_BCN1 GREEN SEGMENT,MCM05 -S012073_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012073_BCN1 AMBER SEGMENT,MCM05 -S012073_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012073_BCN1 BLUE SEGMENT,MCM05 -S014008_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014008_BCN1 GREEN SEGMENT,MCM05 -S014008_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014008_BCN1 AMBER SEGMENT,MCM05 -S014008_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014008_BCN1 BLUE SEGMENT,MCM05 -S014016_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014016_BCN1 GREEN SEGMENT,MCM05 -S014016_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014016_BCN1 AMBER SEGMENT,MCM05 -S014016_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014016_BCN1 BLUE SEGMENT,MCM05 -S014009_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014009_BCN1 GREEN SEGMENT,MCM05 -S014009_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014009_BCN1 AMBER SEGMENT,MCM05 -S014009_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014009_BCN1 BLUE SEGMENT,MCM05 -S014024_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014024_BCN1 GREEN SEGMENT,MCM05 -S014024_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014024_BCN1 AMBER SEGMENT,MCM05 -S014024_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014024_BCN1 BLUE SEGMENT,MCM05 -S014017_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014017_BCN1 GREEN SEGMENT,MCM05 -S014017_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014017_BCN1 AMBER SEGMENT,MCM05 -S014017_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014017_BCN1 BLUE SEGMENT,MCM05 -S014026_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014026_BCN1 GREEN SEGMENT,MCM05 -S014026_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014026_BCN1 AMBER SEGMENT,MCM05 -S014026_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014026_BCN1 BLUE SEGMENT,MCM05 -S014025_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014025_BCN1 GREEN SEGMENT,MCM05 -S014025_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014025_BCN1 AMBER SEGMENT,MCM05 -S014025_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014025_BCN1 BLUE SEGMENT,MCM05 -S014036_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014036_BCN1 GREEN SEGMENT,MCM05 -S014036_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014036_BCN1 AMBER SEGMENT,MCM05 -S014036_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014036_BCN1 BLUE SEGMENT,MCM05 -S014033_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014033_BCN1 GREEN SEGMENT,MCM05 -S014033_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014033_BCN1 AMBER SEGMENT,MCM05 -S014033_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014033_BCN1 BLUE SEGMENT,MCM05 -S014044_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014044_BCN1 GREEN SEGMENT,MCM05 -S014044_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014044_BCN1 AMBER SEGMENT,MCM05 -S014044_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014044_BCN1 BLUE SEGMENT,MCM05 -S014043_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014043_BCN1 GREEN SEGMENT,MCM05 -S014043_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014043_BCN1 AMBER SEGMENT,MCM05 -S014043_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014043_BCN1 BLUE SEGMENT,MCM05 -S014052_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014052_BCN1 GREEN SEGMENT,MCM05 -S014052_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014052_BCN1 AMBER SEGMENT,MCM05 -S014052_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014052_BCN1 BLUE SEGMENT,MCM05 -S014049_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014049_BCN1 GREEN SEGMENT,MCM05 -S014049_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014049_BCN1 AMBER SEGMENT,MCM05 -S014049_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014049_BCN1 BLUE SEGMENT,MCM05 -S014058_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014058_BCN1 GREEN SEGMENT,MCM05 -S014058_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014058_BCN1 AMBER SEGMENT,MCM05 -S014058_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014058_BCN1 BLUE SEGMENT,MCM05 -S014064_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014064_BCN1 GREEN SEGMENT,MCM05 -S014064_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014064_BCN1 AMBER SEGMENT,MCM05 -S014064_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014064_BCN1 BLUE SEGMENT,MCM05 -S014057_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014057_BCN1 GREEN SEGMENT,MCM05 -S014057_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014057_BCN1 AMBER SEGMENT,MCM05 -S014057_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014057_BCN1 BLUE SEGMENT,MCM05 -S014072_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014072_BCN1 GREEN SEGMENT,MCM05 -S014072_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014072_BCN1 AMBER SEGMENT,MCM05 -S014072_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014072_BCN1 BLUE SEGMENT,MCM05 -S014065_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014065_BCN1 GREEN SEGMENT,MCM05 -S014065_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014065_BCN1 AMBER SEGMENT,MCM05 -S014065_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014065_BCN1 BLUE SEGMENT,MCM05 -S014080_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014080_BCN1 GREEN SEGMENT,MCM05 -S014080_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014080_BCN1 AMBER SEGMENT,MCM05 -S014080_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014080_BCN1 BLUE SEGMENT,MCM05 -S014073_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014073_BCN1 GREEN SEGMENT,MCM05 -S014073_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014073_BCN1 AMBER SEGMENT,MCM05 -S014073_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014073_BCN1 BLUE SEGMENT,MCM05 -S014081_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014081_BCN1 GREEN SEGMENT,MCM05 -S014081_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014081_BCN1 AMBER SEGMENT,MCM05 -S014081_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014081_BCN1 BLUE SEGMENT,MCM05 -S012007_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012007_BCN1 GREEN SEGMENT,MCM05 -S012007_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012007_BCN1 BLUE SEGMENT,MCM05 -S012003_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012003_BCN1 GREEN SEGMENT,MCM05 -S012003_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012003_BCN1 BLUE SEGMENT,MCM05 -S012005_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012005_BCN1 GREEN SEGMENT,MCM05 -S012005_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012005_BCN1 BLUE SEGMENT,MCM05 -S012008_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012008_BCN1 GREEN SEGMENT,MCM05 -S012008_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012008_BCN1 BLUE SEGMENT,MCM05 -S012006_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012006_BCN1 GREEN SEGMENT,MCM05 -S012006_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012006_BCN1 BLUE SEGMENT,MCM05 -S012004_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012004_BCN1 GREEN SEGMENT,MCM05 -S012004_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012004_BCN1 BLUE SEGMENT,MCM05 -S012015_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012015_BCN1 GREEN SEGMENT,MCM05 -S012015_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012015_BCN1 BLUE SEGMENT,MCM05 -S012011_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012011_BCN1 GREEN SEGMENT,MCM05 -S012011_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012011_BCN1 BLUE SEGMENT,MCM05 -S012013_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012013_BCN1 GREEN SEGMENT,MCM05 -S012013_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012013_BCN1 BLUE SEGMENT,MCM05 -S012016_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012016_BCN1 GREEN SEGMENT,MCM05 -S012016_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012016_BCN1 BLUE SEGMENT,MCM05 -S012014_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012014_BCN1 GREEN SEGMENT,MCM05 -S012014_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012014_BCN1 BLUE SEGMENT,MCM05 -S012012_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012012_BCN1 GREEN SEGMENT,MCM05 -S012012_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012012_BCN1 BLUE SEGMENT,MCM05 -S012023_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012023_BCN1 GREEN SEGMENT,MCM05 -S012023_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012023_BCN1 BLUE SEGMENT,MCM05 -S012021_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012021_BCN1 GREEN SEGMENT,MCM05 -S012021_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012021_BCN1 BLUE SEGMENT,MCM05 -S012024_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012024_BCN1 GREEN SEGMENT,MCM05 -S012024_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012024_BCN1 BLUE SEGMENT,MCM05 -S012022_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012022_BCN1 GREEN SEGMENT,MCM05 -S012022_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012022_BCN1 BLUE SEGMENT,MCM05 -S012020_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012020_BCN1 GREEN SEGMENT,MCM05 -S012020_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012020_BCN1 BLUE SEGMENT,MCM05 -S012025_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012025_BCN1 GREEN SEGMENT,MCM05 -S012025_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012025_BCN1 BLUE SEGMENT,MCM05 -S012031_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012031_BCN1 GREEN SEGMENT,MCM05 -S012031_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012031_BCN1 BLUE SEGMENT,MCM05 -S012029_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012029_BCN1 GREEN SEGMENT,MCM05 -S012029_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012029_BCN1 BLUE SEGMENT,MCM05 -S012032_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012032_BCN1 GREEN SEGMENT,MCM05 -S012032_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012032_BCN1 BLUE SEGMENT,MCM05 -S012030_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012030_BCN1 GREEN SEGMENT,MCM05 -S012030_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012030_BCN1 BLUE SEGMENT,MCM05 -S012028_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012028_BCN1 GREEN SEGMENT,MCM05 -S012028_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012028_BCN1 BLUE SEGMENT,MCM05 -S012039_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012039_BCN1 GREEN SEGMENT,MCM05 -S012039_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012039_BCN1 BLUE SEGMENT,MCM05 -S012040_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012040_BCN1 GREEN SEGMENT,MCM05 -S012040_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012040_BCN1 BLUE SEGMENT,MCM05 -S012038_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012038_BCN1 GREEN SEGMENT,MCM05 -S012038_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012038_BCN1 BLUE SEGMENT,MCM05 -S012041_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012041_BCN1 GREEN SEGMENT,MCM05 -S012041_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012041_BCN1 BLUE SEGMENT,MCM05 -S012047_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012047_BCN1 GREEN SEGMENT,MCM05 -S012047_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012047_BCN1 BLUE SEGMENT,MCM05 -S012043_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012043_BCN1 GREEN SEGMENT,MCM05 -S012043_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012043_BCN1 BLUE SEGMENT,MCM05 -S012048_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012048_BCN1 GREEN SEGMENT,MCM05 -S012048_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012048_BCN1 BLUE SEGMENT,MCM05 -S012042_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012042_BCN1 GREEN SEGMENT,MCM05 -S012042_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012042_BCN1 BLUE SEGMENT,MCM05 -S012046_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012046_BCN1 GREEN SEGMENT,MCM05 -S012046_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012046_BCN1 BLUE SEGMENT,MCM05 -S012049_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012049_BCN1 GREEN SEGMENT,MCM05 -S012049_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012049_BCN1 BLUE SEGMENT,MCM05 -S012053_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012053_BCN1 GREEN SEGMENT,MCM05 -S012053_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012053_BCN1 BLUE SEGMENT,MCM05 -S012056_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012056_BCN1 GREEN SEGMENT,MCM05 -S012056_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012056_BCN1 BLUE SEGMENT,MCM05 -S012050_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012050_BCN1 GREEN SEGMENT,MCM05 -S012050_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012050_BCN1 BLUE SEGMENT,MCM05 -S012054_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012054_BCN1 GREEN SEGMENT,MCM05 -S012054_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012054_BCN1 BLUE SEGMENT,MCM05 -S012057_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012057_BCN1 GREEN SEGMENT,MCM05 -S012057_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012057_BCN1 BLUE SEGMENT,MCM05 -S012059_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012059_BCN1 GREEN SEGMENT,MCM05 -S012059_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012059_BCN1 BLUE SEGMENT,MCM05 -S012061_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012061_BCN1 GREEN SEGMENT,MCM05 -S012061_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012061_BCN1 BLUE SEGMENT,MCM05 -S012064_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012064_BCN1 GREEN SEGMENT,MCM05 -S012064_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012064_BCN1 BLUE SEGMENT,MCM05 -S012058_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012058_BCN1 GREEN SEGMENT,MCM05 -S012058_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012058_BCN1 BLUE SEGMENT,MCM05 -S012062_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012062_BCN1 GREEN SEGMENT,MCM05 -S012062_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012062_BCN1 BLUE SEGMENT,MCM05 -S012065_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012065_BCN1 GREEN SEGMENT,MCM05 -S012065_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012065_BCN1 BLUE SEGMENT,MCM05 -S012071_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012071_BCN1 GREEN SEGMENT,MCM05 -S012071_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012071_BCN1 BLUE SEGMENT,MCM05 -S012069_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012069_BCN1 GREEN SEGMENT,MCM05 -S012069_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012069_BCN1 BLUE SEGMENT,MCM05 -S012072_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012072_BCN1 GREEN SEGMENT,MCM05 -S012072_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012072_BCN1 BLUE SEGMENT,MCM05 -S012066_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012066_BCN1 GREEN SEGMENT,MCM05 -S012066_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012066_BCN1 BLUE SEGMENT,MCM05 -S012079_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012079_BCN1 GREEN SEGMENT,MCM05 -S012079_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012079_BCN1 BLUE SEGMENT,MCM05 -S012075_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012075_BCN1 GREEN SEGMENT,MCM05 -S012075_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012075_BCN1 BLUE SEGMENT,MCM05 -S012077_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012077_BCN1 GREEN SEGMENT,MCM05 -S012077_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012077_BCN1 BLUE SEGMENT,MCM05 -S012080_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012080_BCN1 GREEN SEGMENT,MCM05 -S012080_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012080_BCN1 BLUE SEGMENT,MCM05 -S012082_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012082_BCN1 GREEN SEGMENT,MCM05 -S012082_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012082_BCN1 BLUE SEGMENT,MCM05 -S012084_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012084_BCN1 GREEN SEGMENT,MCM05 -S012084_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012084_BCN1 BLUE SEGMENT,MCM05 -S012096_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012096_BCN1 GREEN SEGMENT,MCM05 -S012096_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012096_BCN1 BLUE SEGMENT,MCM05 -S014002_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014002_BCN1 GREEN SEGMENT,MCM05 -S014002_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014002_BCN1 BLUE SEGMENT,MCM05 -S014004_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014004_BCN1 GREEN SEGMENT,MCM05 -S014004_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014004_BCN1 BLUE SEGMENT,MCM05 -S014006_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014006_BCN1 GREEN SEGMENT,MCM05 -S014006_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014006_BCN1 BLUE SEGMENT,MCM05 -S014007_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014007_BCN1 GREEN SEGMENT,MCM05 -S014007_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014007_BCN1 BLUE SEGMENT,MCM05 -S014005_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014005_BCN1 GREEN SEGMENT,MCM05 -S014005_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014005_BCN1 BLUE SEGMENT,MCM05 -S014003_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014003_BCN1 GREEN SEGMENT,MCM05 -S014003_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014003_BCN1 BLUE SEGMENT,MCM05 -S014010_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014010_BCN1 GREEN SEGMENT,MCM05 -S014010_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014010_BCN1 BLUE SEGMENT,MCM05 -S014012_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014012_BCN1 GREEN SEGMENT,MCM05 -S014012_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014012_BCN1 BLUE SEGMENT,MCM05 -S014014_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014014_BCN1 GREEN SEGMENT,MCM05 -S014014_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014014_BCN1 BLUE SEGMENT,MCM05 -S014015_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014015_BCN1 GREEN SEGMENT,MCM05 -S014015_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014015_BCN1 BLUE SEGMENT,MCM05 -S014013_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014013_BCN1 GREEN SEGMENT,MCM05 -S014013_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014013_BCN1 BLUE SEGMENT,MCM05 -S014011_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014011_BCN1 GREEN SEGMENT,MCM05 -S014011_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014011_BCN1 BLUE SEGMENT,MCM05 -S014018_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014018_BCN1 GREEN SEGMENT,MCM05 -S014018_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014018_BCN1 BLUE SEGMENT,MCM05 -S014020_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014020_BCN1 GREEN SEGMENT,MCM05 -S014020_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014020_BCN1 BLUE SEGMENT,MCM05 -S014022_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014022_BCN1 GREEN SEGMENT,MCM05 -S014022_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014022_BCN1 BLUE SEGMENT,MCM05 -S014023_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014023_BCN1 GREEN SEGMENT,MCM05 -S014023_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014023_BCN1 BLUE SEGMENT,MCM05 -S014021_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014021_BCN1 GREEN SEGMENT,MCM05 -S014021_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014021_BCN1 BLUE SEGMENT,MCM05 -S014032_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014032_BCN1 GREEN SEGMENT,MCM05 -S014032_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014032_BCN1 BLUE SEGMENT,MCM05 -S014030_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014030_BCN1 GREEN SEGMENT,MCM05 -S014030_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014030_BCN1 BLUE SEGMENT,MCM05 -S014031_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014031_BCN1 GREEN SEGMENT,MCM05 -S014031_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014031_BCN1 BLUE SEGMENT,MCM05 -S014029_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014029_BCN1 GREEN SEGMENT,MCM05 -S014029_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014029_BCN1 BLUE SEGMENT,MCM05 -S014034_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014034_BCN1 GREEN SEGMENT,MCM05 -S014034_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014034_BCN1 BLUE SEGMENT,MCM05 -S014040_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014040_BCN1 GREEN SEGMENT,MCM05 -S014040_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014040_BCN1 BLUE SEGMENT,MCM05 -S014038_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014038_BCN1 GREEN SEGMENT,MCM05 -S014038_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014038_BCN1 BLUE SEGMENT,MCM05 -S014039_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014039_BCN1 GREEN SEGMENT,MCM05 -S014039_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014039_BCN1 BLUE SEGMENT,MCM05 -S014037_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014037_BCN1 GREEN SEGMENT,MCM05 -S014037_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014037_BCN1 BLUE SEGMENT,MCM05 -S014042_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014042_BCN1 GREEN SEGMENT,MCM05 -S014042_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014042_BCN1 BLUE SEGMENT,MCM05 -S014048_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014048_BCN1 GREEN SEGMENT,MCM05 -S014048_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014048_BCN1 BLUE SEGMENT,MCM05 -S014046_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014046_BCN1 GREEN SEGMENT,MCM05 -S014046_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014046_BCN1 BLUE SEGMENT,MCM05 -S014047_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014047_BCN1 GREEN SEGMENT,MCM05 -S014047_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014047_BCN1 BLUE SEGMENT,MCM05 -S014041_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014041_BCN1 GREEN SEGMENT,MCM05 -S014041_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014041_BCN1 BLUE SEGMENT,MCM05 -S014045_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014045_BCN1 GREEN SEGMENT,MCM05 -S014045_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014045_BCN1 BLUE SEGMENT,MCM05 -S014050_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014050_BCN1 GREEN SEGMENT,MCM05 -S014050_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014050_BCN1 BLUE SEGMENT,MCM05 -S014056_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014056_BCN1 GREEN SEGMENT,MCM05 -S014056_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014056_BCN1 BLUE SEGMENT,MCM05 -S014054_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014054_BCN1 GREEN SEGMENT,MCM05 -S014054_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014054_BCN1 BLUE SEGMENT,MCM05 -S014055_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014055_BCN1 GREEN SEGMENT,MCM05 -S014055_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014055_BCN1 BLUE SEGMENT,MCM05 -S014053_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014053_BCN1 GREEN SEGMENT,MCM05 -S014053_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014053_BCN1 BLUE SEGMENT,MCM05 -S014062_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014062_BCN1 GREEN SEGMENT,MCM05 -S014062_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014062_BCN1 BLUE SEGMENT,MCM05 -S014063_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014063_BCN1 GREEN SEGMENT,MCM05 -S014063_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014063_BCN1 BLUE SEGMENT,MCM05 -S014061_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014061_BCN1 GREEN SEGMENT,MCM05 -S014061_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014061_BCN1 BLUE SEGMENT,MCM05 -S014071_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014071_BCN1 GREEN SEGMENT,MCM05 -S014071_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014071_BCN1 BLUE SEGMENT,MCM05 -S014069_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014069_BCN1 GREEN SEGMENT,MCM05 -S014069_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014069_BCN1 BLUE SEGMENT,MCM05 -S014079_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014079_BCN1 GREEN SEGMENT,MCM05 -S014079_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014079_BCN1 BLUE SEGMENT,MCM05 -S014077_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014077_BCN1 GREEN SEGMENT,MCM05 -S014077_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014077_BCN1 BLUE SEGMENT,MCM05 +Name,Description,Subsystem +FL2074_2_VFD1:I.In_0,FL2074_2_DISC DISCONNECT AUX,MCM05 +FL2074_2_VFD1:I.In_1,SPARE,MCM05 +FL2074_2_VFD1:I.In_2,FL2074_2_PE1 TRACKING PHOTOEYE,MCM05 +FL2074_2_VFD1:I.In_3,FL2074_3CH_PE1 FULL PHOTOEYE,MCM05 +FL2074_2_VFD1:I.IO_0,FL2074_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL2074_2_VFD1:O.IO_1,FL2074_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2074_2_VFD1:SI.In00Data,FL2074_1_ESTOP ESTOP OK,MCM05 +FL2074_2_VFD1:SI.In01Data,SPARE,MCM05 +FL2074_2_VFD1:SI.In02Data,SPARE,MCM05 +FL2074_2_VFD1:SI.In03Data,SPARE,MCM05 +FL2074_2_VFD1:SO.Out00Output,FL2074_1_STO1 ESTOP OK,MCM05 +FL2078_2_VFD1:I.In_0,FL2078_2_DISC DISCONNECT AUX,MCM05 +FL2078_2_VFD1:I.In_1,SPARE,MCM05 +FL2078_2_VFD1:I.In_2,FL2078_2_PE1 TRACKING PHOTOEYE,MCM05 +FL2078_2_VFD1:I.In_3,FL2078_3CH_PE1 FULL PHOTOEYE,MCM05 +FL2078_2_VFD1:I.IO_0,FL2078_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL2078_2_VFD1:O.IO_1,FL2078_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2078_2_VFD1:SI.In00Data,FL2078_1_ESTOP ESTOP OK,MCM05 +FL2078_2_VFD1:SI.In01Data,SPARE,MCM05 +FL2078_2_VFD1:SI.In02Data,SPARE,MCM05 +FL2078_2_VFD1:SI.In03Data,SPARE,MCM05 +FL2078_2_VFD1:SO.Out00Output,FL2078_1_STO1 ESTOP OK,MCM05 +FL2086_2_VFD1:I.In_0,FL2086_2_DISC DISCONNECT AUX,MCM05 +FL2086_2_VFD1:I.In_1,SPARE,MCM05 +FL2086_2_VFD1:I.In_2,FL2086_2_PE1 TRACKING PHOTOEYE,MCM05 +FL2086_2_VFD1:I.In_3,FL2086_3CH_PE1 FULL PHOTOEYE,MCM05 +FL2086_2_VFD1:I.IO_0,FL2086_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL2086_2_VFD1:O.IO_1,FL2086_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2086_2_VFD1:SI.In00Data,FL2086_1_ESTOP ESTOP OK,MCM05 +FL2086_2_VFD1:SI.In01Data,SPARE,MCM05 +FL2086_2_VFD1:SI.In02Data,SPARE,MCM05 +FL2086_2_VFD1:SI.In03Data,SPARE,MCM05 +FL2086_2_VFD1:SO.Out00Output,FL2086_1_STO1 ESTOP OK,MCM05 +FL2090_2_VFD1:I.In_0,FL2090_2_DISC DISCONNECT AUX,MCM05 +FL2090_2_VFD1:I.In_1,SPARE,MCM05 +FL2090_2_VFD1:I.In_2,FL2090_2_PE1 TRACKING PHOTOEYE,MCM05 +FL2090_2_VFD1:I.In_3,FL2090_3CH_PE1 FULL PHOTOEYE,MCM05 +FL2090_2_VFD1:I.IO_0,FL2090_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL2090_2_VFD1:O.IO_1,FL2090_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2090_2_VFD1:SI.In00Data,FL2090_1_ESTOP ESTOP OK,MCM05 +FL2090_2_VFD1:SI.In01Data,SPARE,MCM05 +FL2090_2_VFD1:SI.In02Data,SPARE,MCM05 +FL2090_2_VFD1:SI.In03Data,SPARE,MCM05 +FL2090_2_VFD1:SO.Out00Output,FL2090_1_STO1 ESTOP OK,MCM05 +FL2094_2_VFD1:I.In_0,FL2094_2_DISC DISCONNECT AUX,MCM05 +FL2094_2_VFD1:I.In_1,SPARE,MCM05 +FL2094_2_VFD1:I.In_2,FL2094_2_PE1 TRACKING PHOTOEYE,MCM05 +FL2094_2_VFD1:I.In_3,FL2094_3CH_PE1 FULL PHOTOEYE,MCM05 +FL2094_2_VFD1:I.IO_0,FL2094_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL2094_2_VFD1:O.IO_1,FL2094_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2094_2_VFD1:SI.In00Data,FL2094_1_ESTOP ESTOP OK,MCM05 +FL2094_2_VFD1:SI.In01Data,SPARE,MCM05 +FL2094_2_VFD1:SI.In02Data,SPARE,MCM05 +FL2094_2_VFD1:SI.In03Data,SPARE,MCM05 +FL2094_2_VFD1:SO.Out00Output,FL2094_1_STO1 ESTOP OK,MCM05 +FL4066_2_VFD1:I.In_0,FL4066_2_DISC DISCONNECT AUX,MCM05 +FL4066_2_VFD1:I.In_1,SPARE,MCM05 +FL4066_2_VFD1:I.In_2,FL4066_2_PE1 TRACKING PHOTOEYE,MCM05 +FL4066_2_VFD1:I.In_3,FL4066_3CH_PE1 FULL PHOTOEYE,MCM05 +FL4066_2_VFD1:I.IO_0,FL4066_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL4066_2_VFD1:O.IO_1,FL4066_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4066_2_VFD1:SI.In00Data,FL4066_1_ESTOP ESTOP OK,MCM05 +FL4066_2_VFD1:SI.In01Data,SPARE,MCM05 +FL4066_2_VFD1:SI.In02Data,SPARE,MCM05 +FL4066_2_VFD1:SI.In03Data,SPARE,MCM05 +FL4066_2_VFD1:SO.Out00Output,FL4066_1_STO1 ESTOP OK,MCM05 +FL4070_2_VFD1:I.In_0,FL4070_2_DISC DISCONNECT AUX,MCM05 +FL4070_2_VFD1:I.In_1,SPARE,MCM05 +FL4070_2_VFD1:I.In_2,FL4070_2_PE1 TRACKING PHOTOEYE,MCM05 +FL4070_2_VFD1:I.In_3,FL4070_3CH_PE1 FULL PHOTOEYE,MCM05 +FL4070_2_VFD1:I.IO_0,FL4070_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL4070_2_VFD1:O.IO_1,FL4070_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4070_2_VFD1:SI.In00Data,FL4070_1_ESTOP ESTOP OK,MCM05 +FL4070_2_VFD1:SI.In01Data,SPARE,MCM05 +FL4070_2_VFD1:SI.In02Data,SPARE,MCM05 +FL4070_2_VFD1:SI.In03Data,SPARE,MCM05 +FL4070_2_VFD1:SO.Out00Output,FL4070_1_STO1 ESTOP OK,MCM05 +FL4074_2_VFD1:I.In_0,FL4074_2_DISC DISCONNECT AUX,MCM05 +FL4074_2_VFD1:I.In_1,SPARE,MCM05 +FL4074_2_VFD1:I.In_2,FL4074_2_PE1 TRACKING PHOTOEYE,MCM05 +FL4074_2_VFD1:I.In_3,FL4074_3CH_PE1 FULL PHOTOEYE,MCM05 +FL4074_2_VFD1:I.IO_0,FL4074_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL4074_2_VFD1:O.IO_1,FL4074_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4074_2_VFD1:SI.In00Data,FL4074_1_ESTOP ESTOP OK,MCM05 +FL4074_2_VFD1:SI.In01Data,SPARE,MCM05 +FL4074_2_VFD1:SI.In02Data,SPARE,MCM05 +FL4074_2_VFD1:SI.In03Data,SPARE,MCM05 +FL4074_2_VFD1:SO.Out00Output,FL4074_1_STO1 ESTOP OK,MCM05 +FL4078_2_VFD1:I.In_0,FL4078_2_DISC DISCONNECT AUX,MCM05 +FL4078_2_VFD1:I.In_1,SPARE,MCM05 +FL4078_2_VFD1:I.In_2,FL4078_2_PE1 TRACKING PHOTOEYE,MCM05 +FL4078_2_VFD1:I.In_3,FL4078_3CH_PE1 FULL PHOTOEYE,MCM05 +FL4078_2_VFD1:I.IO_0,FL4078_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL4078_2_VFD1:O.IO_1,FL4078_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4078_2_VFD1:SI.In00Data,FL4078_1_ESTOP ESTOP OK,MCM05 +FL4078_2_VFD1:SI.In01Data,SPARE,MCM05 +FL4078_2_VFD1:SI.In02Data,SPARE,MCM05 +FL4078_2_VFD1:SI.In03Data,SPARE,MCM05 +FL4078_2_VFD1:SO.Out00Output,FL4078_1_STO1 ESTOP OK,MCM05 +FL4082_2_VFD1:I.In_0,FL4082_2_DISC DISCONNECT AUX,MCM05 +FL4082_2_VFD1:I.In_1,SPARE,MCM05 +FL4082_2_VFD1:I.In_2,FL4082_2_PE1 TRACKING PHOTOEYE,MCM05 +FL4082_2_VFD1:I.In_3,FL4082_3CH_PE1 FULL PHOTOEYE,MCM05 +FL4082_2_VFD1:I.IO_0,FL4082_2_JR2_PB JAM RESET PUSHBUTTON,MCM05 +FL4082_2_VFD1:O.IO_1,FL4082_2_JR2_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4082_2_VFD1:SI.In00Data,FL4082_1_ESTOP ESTOP OK,MCM05 +FL4082_2_VFD1:SI.In01Data,SPARE,MCM05 +FL4082_2_VFD1:SI.In02Data,SPARE,MCM05 +FL4082_2_VFD1:SI.In03Data,SPARE,MCM05 +FL4082_2_VFD1:SO.Out00Output,FL4082_1_STO1 ESTOP OK,MCM05 +PDP11_FIO1:I.Pt00.Data,PDP11_CB1 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt01.Data,PDP11_CB2 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt02.Data,PDP11_CB3 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt03.Data,PDP11_CB4 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt04.Data,PDP11_CB5 CIRCUIT BREAKER MONITORING,MCM05 +Local:7:I.Pt01.Data,SPARE,MCM05 +PDP11_FIO1:I.Pt06.Data,PDP11_CB6 CIRCUIT BREAKER MONITORING,MCM05 +Local:7:I.Pt04.Data,SPARE,MCM05 +PDP11_FIO1:I.Pt08.Data,PDP11_CB7 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt09.Data,PDP11_CB8 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt10.Data,PDP11_CB9 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt11.Data,PDP11_CB10 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIO1:I.Pt12.Data,PDP11_PWM1 PHASE MONITOR,MCM05 +Local:7:I.Pt05.Data,SPARE,MCM05 +PDP14_FIO1:I.Pt00.Data,PDP14_CB1 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt01.Data,PDP14_CB2 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt02.Data,PDP14_CB3 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt03.Data,PDP14_CB4 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt04.Data,PDP14_CB5 CIRCUIT BREAKER MONITORING,MCM05 +Local:7:I.Pt06.Data,SPARE,MCM05 +PDP14_FIO1:I.Pt06.Data,PDP14_CB6 CIRCUIT BREAKER MONITORING,MCM05 +Local:7:I.Pt07.Data,SPARE,MCM05 +PDP14_FIO1:I.Pt08.Data,PDP14_CB7 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt09.Data,PDP14_CB8 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt10.Data,PDP14_CB9 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt11.Data,PDP14_CB10 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIO1:I.Pt12.Data,PDP14_PWM1 PHASE MONITOR,MCM05 +Local:7:I.Pt08.Data,SPARE,MCM05 +Local:7:I.Pt09.Data,SPARE,MCM05 +Local:7:I.Pt10.Data,SPARE,MCM05 +VSB_DPM1_FIO1:I.Pt12.Data,S012005_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO1:O.Pt13.Data,S012005_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO1:I.Pt14.Data,S012007_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO1:O.Pt15.Data,S012007_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +Local:7:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO1:I.Pt04.Data,S012001_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO1:O.Pt05.Data,S012001_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO1:I.Pt06.Data,S012003_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO1:O.Pt07.Data,S012003_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +Local:7:I.Pt12.Data,SPARE,MCM05 +Local:7:I.Pt13.Data,SPARE,MCM05 +Local:7:I.Pt14.Data,SPARE,MCM05 +VSB_DPM1_FIO2:I.Pt14.Data,S012001_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO2:O.Pt15.Data,S012001_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +Local:7:I.Pt15.Data,SPARE,MCM05 +VSB_DPM1_FIO2:I.Pt06.Data,S012002_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO2:O.Pt07.Data,S012002_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +PDP11_FIO1:O.Pt05.Data,SPARE,MCM05 +PDP11_FIO1:O.Pt07.Data,SPARE,MCM05 +PDP11_FIO1:O.Pt13.Data,SPARE,MCM05 +VSB_DPM1_FIO3:I.Pt12.Data,S012013_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO3:O.Pt13.Data,S012013_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO3:I.Pt14.Data,S012015_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO3:O.Pt15.Data,S012015_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +PDP14_FIO1:O.Pt05.Data,SPARE,MCM05 +VSB_DPM1_FIO3:I.Pt04.Data,S012009_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO3:O.Pt05.Data,S012009_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO3:I.Pt06.Data,S012011_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO3:O.Pt07.Data,S012011_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +PDP14_FIO1:O.Pt07.Data,SPARE,MCM05 +PDP14_FIO1:O.Pt13.Data,SPARE,MCM05 +VSB_DPM1_FIO1:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO4:I.Pt14.Data,S012009_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO4:O.Pt15.Data,S012009_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO1:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO4:I.Pt06.Data,S012010_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO4:O.Pt07.Data,S012010_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO1:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO1:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO2:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO2:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO5:I.Pt12.Data,S012021_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO5:O.Pt13.Data,S012021_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO5:I.Pt14.Data,S012023_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO5:O.Pt15.Data,S012023_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO2:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO2:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO3:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO5:I.Pt06.Data,S012019_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO5:O.Pt07.Data,S012019_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO3:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO3:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO3:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO6:I.Pt14.Data,S012019_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO6:O.Pt15.Data,S012019_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO4:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO6:I.Pt06.Data,S012018_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO6:O.Pt07.Data,S012018_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO4:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO4:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO4:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO7:I.Pt12.Data,S012029_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO7:O.Pt13.Data,S012029_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO7:I.Pt14.Data,S012031_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO7:O.Pt15.Data,S012031_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO7:I.Pt04.Data,S012025_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO7:O.Pt05.Data,S012025_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO7:I.Pt06.Data,S012027_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM1_FIO7:O.Pt07.Data,S012027_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO5:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO5:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO5:I.Pt04.Data,SPARE,MCM05 +VSB_DPM1_FIO8:I.Pt14.Data,S012027_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO8:O.Pt15.Data,S012027_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO5:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO8:I.Pt06.Data,S012026_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM1_FIO8:O.Pt07.Data,S012026_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO5:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO5:O.Pt05.Data,SPARE,MCM05 +VSB_DPM1_FIO6:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO6:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO1:I.Pt12.Data,S012037_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO1:O.Pt13.Data,S012037_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO1:I.Pt14.Data,S012039_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO1:O.Pt15.Data,S012039_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO6:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO6:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO7:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO7:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO7:I.Pt09.Data,SPARE,MCM05 +VSB_DPM1_FIO7:I.Pt11.Data,SPARE,MCM05 +VSB_DPM1_FIO8:I.Pt01.Data,SPARE,MCM05 +VSB_DPM1_FIO8:I.Pt03.Data,SPARE,MCM05 +VSB_DPM1_FIO8:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO2:I.Pt14.Data,S012037_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO2:O.Pt15.Data,S012037_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM1_FIO8:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO2:I.Pt06.Data,S012034_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO2:O.Pt07.Data,S012034_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO1:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO1:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO3:I.Pt12.Data,S012045_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO3:O.Pt13.Data,S012045_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO3:I.Pt14.Data,S012047_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO3:O.Pt15.Data,S012047_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO1:I.Pt04.Data,SPARE,MCM05 +VSB_DPM2_FIO3:I.Pt04.Data,S012041_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO3:O.Pt05.Data,S012041_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO3:I.Pt06.Data,S012043_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO3:O.Pt07.Data,S012043_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO1:I.Pt06.Data,SPARE,MCM05 +VSB_DPM2_FIO1:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO1:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO4:I.Pt14.Data,S012045_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO4:O.Pt15.Data,S012045_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO1:O.Pt05.Data,SPARE,MCM05 +VSB_DPM2_FIO4:I.Pt06.Data,S012044_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO4:O.Pt07.Data,S012044_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO1:O.Pt07.Data,SPARE,MCM05 +VSB_DPM2_FIO2:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO2:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO5:I.Pt12.Data,S012053_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO5:O.Pt13.Data,S012053_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO5:I.Pt14.Data,S012055_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO5:O.Pt15.Data,S012055_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO2:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO5:I.Pt04.Data,S012049_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO5:O.Pt05.Data,S012049_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO2:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO3:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO3:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO3:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO3:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO6:I.Pt14.Data,S012055_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO6:O.Pt15.Data,S012055_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO4:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO6:I.Pt06.Data,S012052_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO6:O.Pt07.Data,S012052_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO4:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO4:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO4:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO7:I.Pt12.Data,S012061_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO7:O.Pt13.Data,S012061_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO7:I.Pt14.Data,S012063_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO7:O.Pt15.Data,S012063_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO5:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO7:I.Pt04.Data,S012057_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO7:O.Pt05.Data,S012057_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO7:I.Pt06.Data,S012059_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM2_FIO7:O.Pt07.Data,S012059_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO5:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO5:I.Pt06.Data,SPARE,MCM05 +VSB_DPM2_FIO8:I.Pt14.Data,S012063_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO8:O.Pt15.Data,S012063_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO5:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO8:I.Pt06.Data,S012060_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM2_FIO8:O.Pt07.Data,S012060_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO5:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO5:O.Pt07.Data,SPARE,MCM05 +VSB_DPM2_FIO6:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO1:I.Pt12.Data,S012069_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO1:O.Pt13.Data,S012069_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO1:I.Pt14.Data,S012071_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO1:O.Pt15.Data,S012071_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO6:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO6:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO1:I.Pt04.Data,S012065_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO1:O.Pt05.Data,S012065_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO6:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO7:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO7:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO7:I.Pt09.Data,SPARE,MCM05 +VSB_DPM2_FIO7:I.Pt11.Data,SPARE,MCM05 +VSB_DPM2_FIO8:I.Pt01.Data,SPARE,MCM05 +VSB_DPM2_FIO8:I.Pt03.Data,SPARE,MCM05 +VSB_DPM2_FIO8:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO2:I.Pt06.Data,S012070_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM3_FIO2:O.Pt07.Data,S012070_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM2_FIO8:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO1:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO1:I.Pt03.Data,SPARE,MCM05 +VSB_DPM3_FIO3:I.Pt12.Data,S012077_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO3:O.Pt13.Data,S012077_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO3:I.Pt14.Data,S012079_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO3:O.Pt15.Data,S012079_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO1:I.Pt06.Data,SPARE,MCM05 +VSB_DPM3_FIO3:I.Pt04.Data,S012073_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO3:O.Pt05.Data,S012073_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO3:I.Pt06.Data,S012075_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSB_DPM3_FIO3:O.Pt07.Data,S012075_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO1:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO1:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO1:O.Pt07.Data,SPARE,MCM05 +VSB_DPM3_FIO2:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO4:I.Pt14.Data,S012073_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSB_DPM3_FIO4:O.Pt15.Data,S012073_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO2:I.Pt03.Data,SPARE,MCM05 +VSB_DPM3_FIO2:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO2:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO2:I.Pt14.Data,SPARE,MCM05 +VSB_DPM3_FIO2:O.Pt15.Data,SPARE,MCM05 +VSB_DPM3_FIO3:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO3:I.Pt03.Data,SPARE,MCM05 +VSB_DPM3_FIO3:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO3:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO4:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO4:I.Pt03.Data,SPARE,MCM05 +VSB_DPM3_FIO4:I.Pt06.Data,SPARE,MCM05 +VSB_DPM3_FIO4:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO4:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO4:O.Pt07.Data,SPARE,MCM05 +VSB_DPM3_FIO6:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO6:I.Pt03.Data,SPARE,MCM05 +VSB_DPM3_FIO6:I.Pt06.Data,SPARE,MCM05 +VSB_DPM3_FIO6:I.Pt09.Data,SPARE,MCM05 +VSB_DPM3_FIO6:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO6:I.Pt14.Data,SPARE,MCM05 +VSB_DPM3_FIO6:O.Pt07.Data,SPARE,MCM05 +VSB_DPM3_FIO6:O.Pt15.Data,SPARE,MCM05 +VSB_DPM3_FIO8:I.Pt01.Data,SPARE,MCM05 +VSB_DPM3_FIO8:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO1:I.Pt06.Data,S014008_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO1:O.Pt07.Data,S014008_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO8:I.Pt06.Data,SPARE,MCM05 +VSB_DPM3_FIO8:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO2:I.Pt12.Data,S014003_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO2:O.Pt13.Data,S014003_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO8:I.Pt11.Data,SPARE,MCM05 +VSB_DPM3_FIO8:I.Pt14.Data,SPARE,MCM05 +VSB_DPM3_FIO8:O.Pt07.Data,SPARE,MCM05 +VSD_DPM1_FIO2:I.Pt04.Data,S014007_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO2:O.Pt05.Data,S014007_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO2:I.Pt06.Data,S014005_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO2:O.Pt07.Data,S014005_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSB_DPM3_FIO8:O.Pt15.Data,SPARE,MCM05 +VSD_DPM1_FIO1:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO1:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO3:I.Pt14.Data,S014009_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO3:O.Pt15.Data,S014009_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO1:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO3:I.Pt06.Data,S014016_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO3:O.Pt07.Data,S014016_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO1:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO1:I.Pt14.Data,SPARE,MCM05 +VSD_DPM1_FIO1:O.Pt15.Data,SPARE,MCM05 +VSD_DPM1_FIO4:I.Pt12.Data,S014011_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO4:O.Pt13.Data,S014011_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO4:I.Pt14.Data,S014009_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO4:O.Pt15.Data,S014009_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO2:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO4:I.Pt04.Data,S014015_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO4:O.Pt05.Data,S014015_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO4:I.Pt06.Data,S014013_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO4:O.Pt07.Data,S014013_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO2:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO2:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO5:I.Pt14.Data,S014017_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO5:O.Pt15.Data,S014017_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO2:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO5:I.Pt06.Data,S014024_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO5:O.Pt07.Data,S014024_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO2:I.Pt14.Data,SPARE,MCM05 +VSD_DPM1_FIO2:O.Pt15.Data,SPARE,MCM05 +VSD_DPM1_FIO3:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO3:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO3:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO6:I.Pt14.Data,S014017_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO6:O.Pt15.Data,S014017_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO3:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO6:I.Pt04.Data,S014023_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO6:O.Pt05.Data,S014023_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO6:I.Pt06.Data,S014021_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO6:O.Pt07.Data,S014021_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO4:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO4:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO4:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO4:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO7:I.Pt14.Data,S014025_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO7:O.Pt15.Data,S014025_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO5:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO5:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO7:I.Pt06.Data,S014026_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM1_FIO7:O.Pt07.Data,S014026_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO5:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO5:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO6:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO6:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO8:I.Pt14.Data,S014025_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO8:O.Pt15.Data,S014025_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO6:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO8:I.Pt04.Data,S014031_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO8:O.Pt05.Data,S014031_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO8:I.Pt06.Data,S014029_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM1_FIO8:O.Pt07.Data,S014029_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO6:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO6:O.Pt13.Data,SPARE,MCM05 +VSD_DPM1_FIO7:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO1:I.Pt14.Data,S014033_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO1:O.Pt15.Data,S014033_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO1:I.Pt06.Data,S014036_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO1:O.Pt07.Data,S014036_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO7:I.Pt03.Data,SPARE,MCM05 +VSD_DPM1_FIO7:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO7:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO8:I.Pt01.Data,SPARE,MCM05 +VSD_DPM1_FIO8:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO2:I.Pt14.Data,S014033_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO2:O.Pt15.Data,S014033_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO2:I.Pt04.Data,S014039_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO2:O.Pt05.Data,S014039_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO2:I.Pt06.Data,S014037_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO2:O.Pt07.Data,S014037_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM1_FIO8:I.Pt09.Data,SPARE,MCM05 +VSD_DPM1_FIO8:I.Pt11.Data,SPARE,MCM05 +VSD_DPM1_FIO8:O.Pt13.Data,SPARE,MCM05 +VSD_DPM2_FIO3:I.Pt14.Data,S014043_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO3:O.Pt15.Data,S014043_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO1:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO3:I.Pt06.Data,S014044_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO3:O.Pt07.Data,S014044_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO1:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO1:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO1:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO4:I.Pt12.Data,S014043_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO4:O.Pt13.Data,S014043_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO4:I.Pt14.Data,S014041_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO4:O.Pt15.Data,S014041_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO2:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO4:I.Pt04.Data,S014047_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO4:O.Pt05.Data,S014047_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO4:I.Pt06.Data,S014045_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO4:O.Pt07.Data,S014045_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO2:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO2:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO5:I.Pt14.Data,S014049_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO5:O.Pt15.Data,S014049_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO2:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO5:I.Pt06.Data,S014052_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO5:O.Pt07.Data,S014052_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO2:O.Pt13.Data,SPARE,MCM05 +VSD_DPM2_FIO3:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO3:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO3:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO6:I.Pt14.Data,S014049_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO6:O.Pt15.Data,S014049_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO3:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO6:I.Pt04.Data,S014055_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO6:O.Pt05.Data,S014055_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO6:I.Pt06.Data,S014053_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO6:O.Pt07.Data,S014053_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO4:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO4:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO4:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO4:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO7:I.Pt14.Data,S014058_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO7:O.Pt15.Data,S014058_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO7:I.Pt06.Data,S014064_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM2_FIO7:O.Pt07.Data,S014064_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO5:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO5:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO5:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO5:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO6:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO8:I.Pt14.Data,S014057_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO8:O.Pt15.Data,S014057_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO6:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO8:I.Pt04.Data,S014063_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO8:O.Pt05.Data,S014063_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO8:I.Pt06.Data,S014061_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM2_FIO8:O.Pt07.Data,S014061_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO6:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO6:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO6:O.Pt13.Data,SPARE,MCM05 +VSD_DPM3_FIO1:I.Pt14.Data,S014065_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM3_FIO1:O.Pt15.Data,S014065_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO7:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO7:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO1:I.Pt06.Data,S014072_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM3_FIO1:O.Pt07.Data,S014072_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO7:I.Pt11.Data,SPARE,MCM05 +VSD_DPM2_FIO8:I.Pt01.Data,SPARE,MCM05 +VSD_DPM2_FIO8:I.Pt03.Data,SPARE,MCM05 +VSD_DPM2_FIO8:I.Pt09.Data,SPARE,MCM05 +VSD_DPM2_FIO8:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO2:I.Pt14.Data,S014065_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO2:O.Pt15.Data,S014065_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO2:I.Pt04.Data,S014071_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO2:O.Pt05.Data,S014071_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO2:I.Pt06.Data,S014069_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO2:O.Pt07.Data,S014069_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM2_FIO8:O.Pt13.Data,SPARE,MCM05 +VSD_DPM3_FIO1:I.Pt01.Data,SPARE,MCM05 +VSD_DPM3_FIO1:I.Pt03.Data,SPARE,MCM05 +VSD_DPM3_FIO3:I.Pt14.Data,S014073_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM3_FIO3:O.Pt15.Data,S014073_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO1:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO3:I.Pt06.Data,S014080_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM3_FIO3:O.Pt07.Data,S014080_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO1:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO2:I.Pt01.Data,SPARE,MCM05 +VSD_DPM3_FIO2:I.Pt03.Data,SPARE,MCM05 +VSD_DPM3_FIO2:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO2:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO4:I.Pt14.Data,S014073_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO4:O.Pt15.Data,S014073_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO4:I.Pt04.Data,S014079_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO4:O.Pt05.Data,S014079_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO4:I.Pt06.Data,S014077_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO4:O.Pt07.Data,S014077_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO2:O.Pt13.Data,SPARE,MCM05 +VSD_DPM3_FIO3:I.Pt01.Data,SPARE,MCM05 +VSD_DPM3_FIO3:I.Pt03.Data,SPARE,MCM05 +VSD_DPM3_FIO3:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO3:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO5:I.Pt14.Data,S014081_JR1_PB JAM RESET PUSHBUTTON,MCM05 +VSD_DPM3_FIO5:O.Pt15.Data,S014081_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO4:I.Pt01.Data,SPARE,MCM05 +VSD_DPM3_FIO4:I.Pt03.Data,SPARE,MCM05 +VSD_DPM3_FIO4:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO4:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO4:O.Pt13.Data,SPARE,MCM05 +VSD_DPM3_FIO5:I.Pt01.Data,SPARE,MCM05 +VSD_DPM3_FIO5:I.Pt03.Data,SPARE,MCM05 +VSD_DPM3_FIO6:I.Pt14.Data,S014081_GS1_PB CHUTE ENABLE PUSHBUTTON,MCM05 +VSD_DPM3_FIO6:O.Pt15.Data,S014081_GS1_PB_LT CHUTE ENABLE PUSHBUTTON LIGHT,MCM05 +VSD_DPM3_FIO5:I.Pt06.Data,SPARE,MCM05 +VSD_DPM3_FIO5:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO5:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO5:O.Pt07.Data,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL2074_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +FL2074_FIOH1:O.ProcessDataOut.Connector_6_B_Pin_2,FL2074_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL2074_1CH_PE1 FULL PHOTOEYE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +FL2074_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL2074_2_BCN2_A AMBER BEACON LIGHT,MCM05 +FL2074_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL2074_2_BCN2_B BLUE BEACON LIGHT,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 +FL2074_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +FL2074_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL2074_2_BCN1_A AMBER BEACON LIGHT,MCM05 +FL2074_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL2074_2_BCN1_B BLUE BEACON LIGHT,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL2086_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +FL2086_FIOH1:O.ProcessDataOut.Connector_6_B_Pin_2,FL2086_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL2086_1CH_PE1 FULL PHOTOEYE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +FL2086_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL2086_2_BCN2_A AMBER BEACON LIGHT,MCM05 +FL2086_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL2086_2_BCN2_B BLUE BEACON LIGHT,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 +FL2086_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +FL2086_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL2086_2_BCN1_A AMBER BEACON LIGHT,MCM05 +FL2086_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL2086_2_BCN1_B BLUE BEACON LIGHT,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL2090_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +FL2090_FIOH1:O.ProcessDataOut.Connector_6_B_Pin_2,FL2090_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL2090_1CH_PE1 FULL PHOTOEYE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +FL2090_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL2090_2_BCN2_A AMBER BEACON LIGHT,MCM05 +FL2090_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL2090_2_BCN2_B BLUE BEACON LIGHT,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 +FL2090_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +FL2090_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL2090_2_BCN1_A AMBER BEACON LIGHT,MCM05 +FL2090_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL2090_2_BCN1_B BLUE BEACON LIGHT,MCM05 +FL4066_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL4066_2_BCN1_A AMBER BEACON LIGHT,MCM05 +FL4066_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL4066_2_BCN1_B BLUE BEACON LIGHT,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL4066_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +FL4066_FIOH1:O.ProcessDataOut.Connector_5_B_Pin_2,FL4066_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL4066_1CH_PE1 FULL PHOTOEYE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +FL4066_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL4066_2_BCN2_A AMBER BEACON LIGHT,MCM05 +FL4066_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL4066_2_BCN2_B BLUE BEACON LIGHT,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 +FL4066_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +FL4074_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL4074_2_BCN1_A AMBER BEACON LIGHT,MCM05 +FL4074_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL4074_2_BCN1_B BLUE BEACON LIGHT,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL4074_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +FL4074_FIOH1:O.ProcessDataOut.Connector_5_B_Pin_2,FL4074_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL4074_1CH_PE1 FULL PHOTOEYE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +FL4074_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL4074_2_BCN2_A AMBER BEACON LIGHT,MCM05 +FL4074_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL4074_2_BCN2_B BLUE BEACON LIGHT,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 +FL4074_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +FL4082_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL4082_2_BCN1_A AMBER BEACON LIGHT,MCM05 +FL4082_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL4082_2_BCN1_B BLUE BEACON LIGHT,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL4082_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +FL4082_FIOH1:O.ProcessDataOut.Connector_5_B_Pin_2,FL4082_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL4082_1CH_PE1 FULL PHOTOEYE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +FL4082_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL4082_2_BCN2_A AMBER BEACON LIGHT,MCM05 +FL4082_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL4082_2_BCN2_B BLUE BEACON LIGHT,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 +FL4082_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,PDP11_CB11 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,PDP11_CB12 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,PDP11_CB13 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,PDP11_CB14 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,PDP11_CB15 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,PDP11_CB16 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,PDP11_CB17 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,PDP11_CB18 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,PDP11_CB19 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,PDP11_CB20 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,PDP11_CB21 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,PDP11_CB22 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,PDP11_CB23 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,PDP11_CB24 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,PDP11_CB25 CIRCUIT BREAKER MONITORING,MCM05 +PDP11_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,PDP11_CB26 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,PDP14_CB11 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,PDP14_CB12 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,PDP14_CB13 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,PDP14_CB14 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,PDP14_CB15 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,PDP14_CB16 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,PDP14_CB17 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,PDP14_CB18 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,PDP14_CB19 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,PDP14_CB20 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,PDP14_CB21 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,PDP14_CB22 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,PDP14_CB23 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,PDP14_CB24 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,PDP14_CB25 CIRCUIT BREAKER MONITORING,MCM05 +PDP14_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,PDP14_CB26 CIRCUIT BREAKER MONITORING,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012004_PE1 FULL PHOTOEYE 100%,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012002_PE2 FULL PHOTOEYE 50%,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012002_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012002_SOL1 PKG RELEASE SOLENOID,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012004_PE2 FULL PHOTOEYE 50%,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012004_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012004_SOL1 PKG RELEASE SOLENOID,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012002_PE1 FULL PHOTOEYE 100%,MCM05 +S012004_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012008_PE1 FULL PHOTOEYE 100%,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012006_PE2 FULL PHOTOEYE 50%,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012006_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012006_SOL1 PKG RELEASE SOLENOID,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012008_PE2 FULL PHOTOEYE 50%,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012008_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012008_SOL1 PKG RELEASE SOLENOID,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012006_PE1 FULL PHOTOEYE 100%,MCM05 +S012008_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012012_PE1 FULL PHOTOEYE 100%,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012010_PE2 FULL PHOTOEYE 50%,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012010_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012010_SOL1 PKG RELEASE SOLENOID,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012012_PE2 FULL PHOTOEYE 50%,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012012_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012012_SOL1 PKG RELEASE SOLENOID,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012010_PE1 FULL PHOTOEYE 100%,MCM05 +S012012_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012016_PE1 FULL PHOTOEYE 100%,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012014_PE2 FULL PHOTOEYE 50%,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012014_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012014_SOL1 PKG RELEASE SOLENOID,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012016_PE2 FULL PHOTOEYE 50%,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012016_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012016_SOL1 PKG RELEASE SOLENOID,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012014_PE1 FULL PHOTOEYE 100%,MCM05 +S012016_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012020_PE1 FULL PHOTOEYE 100%,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012018_PE2 FULL PHOTOEYE 50%,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012018_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012018_SOL1 PKG RELEASE SOLENOID,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012020_PE2 FULL PHOTOEYE 50%,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012020_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012020_SOL1 PKG RELEASE SOLENOID,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012018_PE1 FULL PHOTOEYE 100%,MCM05 +S012020_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012024_PE1 FULL PHOTOEYE 100%,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012022_PE2 FULL PHOTOEYE 50%,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012022_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012022_SOL1 PKG RELEASE SOLENOID,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012024_PE2 FULL PHOTOEYE 50%,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012024_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012024_SOL1 PKG RELEASE SOLENOID,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012022_PE1 FULL PHOTOEYE 100%,MCM05 +S012024_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012028_PE1 FULL PHOTOEYE 100%,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012026_PE2 FULL PHOTOEYE 50%,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012026_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012026_SOL1 PKG RELEASE SOLENOID,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012028_PE2 FULL PHOTOEYE 50%,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012028_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012028_SOL1 PKG RELEASE SOLENOID,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012026_PE1 FULL PHOTOEYE 100%,MCM05 +S012028_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012032_PE1 FULL PHOTOEYE 100%,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012030_PE2 FULL PHOTOEYE 50%,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012030_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012030_SOL1 PKG RELEASE SOLENOID,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012032_PE2 FULL PHOTOEYE 50%,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012032_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012032_SOL1 PKG RELEASE SOLENOID,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012030_PE1 FULL PHOTOEYE 100%,MCM05 +S012032_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012034_PE2 FULL PHOTOEYE 50%,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012034_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012034_SOL1 PKG RELEASE SOLENOID,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012034_PE1 FULL PHOTOEYE 100%,MCM05 +S012034_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012040_PE1 FULL PHOTOEYE 100%,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012038_PE2 FULL PHOTOEYE 50%,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012038_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012038_SOL1 PKG RELEASE SOLENOID,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012040_PE2 FULL PHOTOEYE 50%,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012040_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012040_SOL1 PKG RELEASE SOLENOID,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012038_PE1 FULL PHOTOEYE 100%,MCM05 +S012040_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012044_PE1 FULL PHOTOEYE 100%,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012042_PE2 FULL PHOTOEYE 50%,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012042_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012042_SOL1 PKG RELEASE SOLENOID,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012044_PE2 FULL PHOTOEYE 50%,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012044_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012044_SOL1 PKG RELEASE SOLENOID,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012042_PE1 FULL PHOTOEYE 100%,MCM05 +S012044_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012048_PE1 FULL PHOTOEYE 100%,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012046_PE2 FULL PHOTOEYE 50%,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012046_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012046_SOL1 PKG RELEASE SOLENOID,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012048_PE2 FULL PHOTOEYE 50%,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012048_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012048_SOL1 PKG RELEASE SOLENOID,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012046_PE1 FULL PHOTOEYE 100%,MCM05 +S012048_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012052_PE1 FULL PHOTOEYE 100%,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012050_PE2 FULL PHOTOEYE 50%,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012050_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012050_SOL1 PKG RELEASE SOLENOID,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012052_PE2 FULL PHOTOEYE 50%,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012052_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012052_SOL1 PKG RELEASE SOLENOID,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012050_PE1 FULL PHOTOEYE 100%,MCM05 +S012052_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012056_PE1 FULL PHOTOEYE 100%,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012054_PE2 FULL PHOTOEYE 50%,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012054_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012054_SOL1 PKG RELEASE SOLENOID,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012056_PE2 FULL PHOTOEYE 50%,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012056_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012056_SOL1 PKG RELEASE SOLENOID,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012054_PE1 FULL PHOTOEYE 100%,MCM05 +S012056_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012060_PE1 FULL PHOTOEYE 100%,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012058_PE2 FULL PHOTOEYE 50%,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012058_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012058_SOL1 PKG RELEASE SOLENOID,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012060_PE2 FULL PHOTOEYE 50%,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012060_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012060_SOL1 PKG RELEASE SOLENOID,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012058_PE1 FULL PHOTOEYE 100%,MCM05 +S012060_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012064_PE1 FULL PHOTOEYE 100%,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012062_PE2 FULL PHOTOEYE 50%,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012062_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012062_SOL1 PKG RELEASE SOLENOID,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012064_PE2 FULL PHOTOEYE 50%,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012064_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012064_SOL1 PKG RELEASE SOLENOID,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012062_PE1 FULL PHOTOEYE 100%,MCM05 +S012064_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012066_PE2 FULL PHOTOEYE 50%,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012066_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012066_SOL1 PKG RELEASE SOLENOID,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_8_A_Pin_4,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012066_PE1 FULL PHOTOEYE 100%,MCM05 +S012066_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012072_PE1 FULL PHOTOEYE 100%,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012070_PE2 FULL PHOTOEYE 50%,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012070_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012070_SOL1 PKG RELEASE SOLENOID,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012072_PE2 FULL PHOTOEYE 50%,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012072_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012072_SOL1 PKG RELEASE SOLENOID,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012070_PE1 FULL PHOTOEYE 100%,MCM05 +S012072_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012080_PE1 FULL PHOTOEYE 100%,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL2078_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +S012080_FIOH1:O.ProcessDataOut.Connector_3_B_Pin_2,FL2078_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL2078_1CH_PE1 FULL PHOTOEYE,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012080_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL2078_2_BCN2_A AMBER BEACON LIGHT,MCM05 +S012080_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL2078_2_BCN2_B BLUE BEACON LIGHT,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012080_PE2 FULL PHOTOEYE 50%,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012080_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012080_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012080_SOL1 PKG RELEASE SOLENOID,MCM05 +S012080_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012080_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL2078_2_BCN1_A AMBER BEACON LIGHT,MCM05 +S012080_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL2078_2_BCN1_B BLUE BEACON LIGHT,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012084_PE1 FULL PHOTOEYE 100%,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S012082_PE2 FULL PHOTOEYE 50%,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S012082_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S012082_SOL1 PKG RELEASE SOLENOID,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012084_PE2 FULL PHOTOEYE 50%,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012084_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012084_SOL1 PKG RELEASE SOLENOID,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S012082_PE1 FULL PHOTOEYE 100%,MCM05 +S012084_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S012096_PE1 FULL PHOTOEYE 100%,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,FL2094_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +S012096_FIOH1:O.ProcessDataOut.Connector_3_B_Pin_2,FL2094_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,FL2094_1CH_PE1 FULL PHOTOEYE,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S012096_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,FL2094_2_BCN2_A ,MCM05 +S012096_FIOH1:O.ProcessDataOut.Connector_7_B_Pin_2,FL2094_2_BCN2_B BLUE BEACON LIGHT,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S012096_PE2 FULL PHOTOEYE 50%,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S012096_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S012096_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S012096_SOL1 PKG RELEASE SOLENOID,MCM05 +S012096_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S012096_FIOH1:O.ProcessDataOut.Connector_1_A_Pin_4,FL2094_2_BCN1_A ,MCM05 +S012096_FIOH1:O.ProcessDataOut.Connector_1_B_Pin_2,FL2094_2_BCN1_B BLUE BEACON LIGHT,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014002_PE1 FULL PHOTOEYE 100%,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014004_PE2 FULL PHOTOEYE 50%,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014004_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014004_SOL1 PKG RELEASE SOLENOID,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014002_PE2 FULL PHOTOEYE 50%,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014002_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014002_SOL1 PKG RELEASE SOLENOID,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014004_PE1 FULL PHOTOEYE 100%,MCM05 +S014004_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014006_PE1 FULL PHOTOEYE 100%,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014008_PE2 FULL PHOTOEYE 50%,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014008_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014008_SOL1 PKG RELEASE SOLENOID,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014006_PE2 FULL PHOTOEYE 50%,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014006_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014006_SOL1 PKG RELEASE SOLENOID,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014008_PE1 FULL PHOTOEYE 100%,MCM05 +S014008_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014010_PE1 FULL PHOTOEYE 100%,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014012_PE2 FULL PHOTOEYE 50%,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014012_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014012_SOL1 PKG RELEASE SOLENOID,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014010_PE2 FULL PHOTOEYE 50%,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014010_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014010_SOL1 PKG RELEASE SOLENOID,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014012_PE1 FULL PHOTOEYE 100%,MCM05 +S014012_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014014_PE1 FULL PHOTOEYE 100%,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014016_PE2 FULL PHOTOEYE 50%,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014016_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014016_SOL1 PKG RELEASE SOLENOID,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014014_PE2 FULL PHOTOEYE 50%,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014014_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014014_SOL1 PKG RELEASE SOLENOID,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014016_PE1 FULL PHOTOEYE 100%,MCM05 +S014016_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014018_PE1 FULL PHOTOEYE 100%,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014020_PE2 FULL PHOTOEYE 50%,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014020_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014020_SOL1 PKG RELEASE SOLENOID,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014018_PE2 FULL PHOTOEYE 50%,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014018_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014018_SOL1 PKG RELEASE SOLENOID,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014020_PE1 FULL PHOTOEYE 100%,MCM05 +S014020_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014022_PE1 FULL PHOTOEYE 100%,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014024_PE2 FULL PHOTOEYE 50%,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014024_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014024_SOL1 PKG RELEASE SOLENOID,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014022_PE2 FULL PHOTOEYE 50%,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014022_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014022_SOL1 PKG RELEASE SOLENOID,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014024_PE1 FULL PHOTOEYE 100%,MCM05 +S014024_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014026_PE1 FULL PHOTOEYE 100%,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014026_PE2 FULL PHOTOEYE 50%,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014026_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014026_SOL1 PKG RELEASE SOLENOID,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 +S014026_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014030_PE1 FULL PHOTOEYE 100%,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014032_PE2 FULL PHOTOEYE 50%,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014032_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014032_SOL1 PKG RELEASE SOLENOID,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014030_PE2 FULL PHOTOEYE 50%,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014030_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014030_SOL1 PKG RELEASE SOLENOID,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014032_PE1 FULL PHOTOEYE 100%,MCM05 +S014032_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014034_PE1 FULL PHOTOEYE 100%,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014036_PE2 FULL PHOTOEYE 50%,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014036_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014036_SOL1 PKG RELEASE SOLENOID,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014034_PE2 FULL PHOTOEYE 50%,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014034_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014034_SOL1 PKG RELEASE SOLENOID,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014036_PE1 FULL PHOTOEYE 100%,MCM05 +S014036_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014038_PE1 FULL PHOTOEYE 100%,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014040_PE2 FULL PHOTOEYE 50%,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014040_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014040_SOL1 PKG RELEASE SOLENOID,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014038_PE2 FULL PHOTOEYE 50%,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014038_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014038_SOL1 PKG RELEASE SOLENOID,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014040_PE1 FULL PHOTOEYE 100%,MCM05 +S014040_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014042_PE1 FULL PHOTOEYE 100%,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014044_PE2 FULL PHOTOEYE 50%,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014044_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014044_SOL1 PKG RELEASE SOLENOID,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014042_PE2 FULL PHOTOEYE 50%,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014042_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014042_SOL1 PKG RELEASE SOLENOID,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014044_PE1 FULL PHOTOEYE 100%,MCM05 +S014044_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014046_PE1 FULL PHOTOEYE 100%,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014048_PE2 FULL PHOTOEYE 50%,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014048_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014048_SOL1 PKG RELEASE SOLENOID,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014046_PE2 FULL PHOTOEYE 50%,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014046_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014046_SOL1 PKG RELEASE SOLENOID,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014048_PE1 FULL PHOTOEYE 100%,MCM05 +S014048_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014050_PE1 FULL PHOTOEYE 100%,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014052_PE2 FULL PHOTOEYE 50%,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014052_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014052_SOL1 PKG RELEASE SOLENOID,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014050_PE2 FULL PHOTOEYE 50%,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014050_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014050_SOL1 PKG RELEASE SOLENOID,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014052_PE1 FULL PHOTOEYE 100%,MCM05 +S014052_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014054_PE1 FULL PHOTOEYE 100%,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014056_PE2 FULL PHOTOEYE 50%,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014056_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014056_SOL1 PKG RELEASE SOLENOID,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014054_PE2 FULL PHOTOEYE 50%,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014054_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014054_SOL1 PKG RELEASE SOLENOID,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014056_PE1 FULL PHOTOEYE 100%,MCM05 +S014056_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014058_PE1 FULL PHOTOEYE 100%,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_7_A_Pin_4,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014058_PE2 FULL PHOTOEYE 50%,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014058_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014058_SOL1 PKG RELEASE SOLENOID,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,SPARE,MCM05 +S014058_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_2_A_Pin_4,S014062_PE1 FULL PHOTOEYE 100%,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_2_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014064_PE2 FULL PHOTOEYE 50%,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014064_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014064_SOL1 PKG RELEASE SOLENOID,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,S014062_PE2 FULL PHOTOEYE 50%,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_4_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,S014062_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,S014062_SOL1 PKG RELEASE SOLENOID,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_8_B_Pin_2,SPARE,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014064_PE1 FULL PHOTOEYE 100%,MCM05 +S014064_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014072_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL4070_2_BCN1_A AMBER BEACON LIGHT,MCM05 +S014072_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL4070_2_BCN1_B BLUE BEACON LIGHT,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014072_PE2 FULL PHOTOEYE 50%,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014072_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014072_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014072_SOL1 PKG RELEASE SOLENOID,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL4070_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +S014072_FIOH1:O.ProcessDataOut.Connector_4_B_Pin_2,FL4070_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL4070_1CH_PE1 FULL PHOTOEYE,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014072_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL4070_2_BCN2_A ,MCM05 +S014072_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL4070_2_BCN2_B BLUE BEACON LIGHT,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014072_PE1 FULL PHOTOEYE 100%,MCM05 +S014072_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S014080_FIOH1:O.ProcessDataOut.Connector_2_A_Pin_4,FL4078_2_BCN1_A AMBER BEACON LIGHT,MCM05 +S014080_FIOH1:O.ProcessDataOut.Connector_2_B_Pin_2,FL4078_2_BCN1_B BLUE BEACON LIGHT,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_3_A_Pin_4,S014080_PE2 FULL PHOTOEYE 50%,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_3_B_Pin_2,SPARE,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_5_A_Pin_4,S014080_PR1 PKG RELEASE PUSHBUTTON,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_5_B_Pin_2,SPARE,MCM05 +S014080_FIOH1:O.ProcessDataOut.Connector_7_A_Pin_4,S014080_SOL1 PKG RELEASE SOLENOID,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_7_B_Pin_2,SPARE,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_4_A_Pin_4,FL4078_2_JR1_PB JAM RESET PUSHBUTTON,MCM05 +S014080_FIOH1:O.ProcessDataOut.Connector_4_B_Pin_2,FL4078_2_JR1_PB_LT JAM RESET PUSHBUTTON LIGHT,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_6_A_Pin_4,FL4078_1CH_PE1 FULL PHOTOEYE,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_6_B_Pin_2,SPARE,MCM05 +S014080_FIOH1:O.ProcessDataOut.Connector_8_A_Pin_4,FL4078_2_BCN2_A ,MCM05 +S014080_FIOH1:O.ProcessDataOut.Connector_8_B_Pin_2,FL4078_2_BCN2_B BLUE BEACON LIGHT,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_1_A_Pin_4,S014080_PE1 FULL PHOTOEYE 100%,MCM05 +S014080_FIOH1:I.ProcessDataIn.Connector_1_B_Pin_2,SPARE,MCM05 +S012001_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012001_BCN1 GREEN SEGMENT,MCM05 +S012001_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012001_BCN1 AMBER SEGMENT,MCM05 +S012001_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012001_BCN1 BLUE SEGMENT,MCM05 +S012002_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012002_BCN1 GREEN SEGMENT,MCM05 +S012002_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012002_BCN1 AMBER SEGMENT,MCM05 +S012002_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012002_BCN1 BLUE SEGMENT,MCM05 +S012009_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012009_BCN1 GREEN SEGMENT,MCM05 +S012009_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012009_BCN1 AMBER SEGMENT,MCM05 +S012009_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012009_BCN1 BLUE SEGMENT,MCM05 +S012010_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012010_BCN1 GREEN SEGMENT,MCM05 +S012010_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012010_BCN1 AMBER SEGMENT,MCM05 +S012010_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012010_BCN1 BLUE SEGMENT,MCM05 +S012019_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012019_BCN1 GREEN SEGMENT,MCM05 +S012019_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012019_BCN1 AMBER SEGMENT,MCM05 +S012019_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012019_BCN1 BLUE SEGMENT,MCM05 +S012018_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012018_BCN1 GREEN SEGMENT,MCM05 +S012018_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012018_BCN1 AMBER SEGMENT,MCM05 +S012018_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012018_BCN1 BLUE SEGMENT,MCM05 +S012027_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012027_BCN1 GREEN SEGMENT,MCM05 +S012027_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012027_BCN1 AMBER SEGMENT,MCM05 +S012027_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012027_BCN1 BLUE SEGMENT,MCM05 +S012026_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012026_BCN1 GREEN SEGMENT,MCM05 +S012026_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012026_BCN1 AMBER SEGMENT,MCM05 +S012026_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012026_BCN1 BLUE SEGMENT,MCM05 +S012037_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012037_BCN1 GREEN SEGMENT,MCM05 +S012037_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012037_BCN1 AMBER SEGMENT,MCM05 +S012037_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012037_BCN1 BLUE SEGMENT,MCM05 +S012034_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012034_BCN1 GREEN SEGMENT,MCM05 +S012034_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012034_BCN1 AMBER SEGMENT,MCM05 +S012034_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012034_BCN1 BLUE SEGMENT,MCM05 +S012045_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012045_BCN1 GREEN SEGMENT,MCM05 +S012045_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012045_BCN1 AMBER SEGMENT,MCM05 +S012045_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012045_BCN1 BLUE SEGMENT,MCM05 +S012044_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012044_BCN1 GREEN SEGMENT,MCM05 +S012044_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012044_BCN1 AMBER SEGMENT,MCM05 +S012044_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012044_BCN1 BLUE SEGMENT,MCM05 +S012055_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012055_BCN1 GREEN SEGMENT,MCM05 +S012055_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012055_BCN1 AMBER SEGMENT,MCM05 +S012055_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012055_BCN1 BLUE SEGMENT,MCM05 +S012052_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012052_BCN1 GREEN SEGMENT,MCM05 +S012052_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012052_BCN1 AMBER SEGMENT,MCM05 +S012052_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012052_BCN1 BLUE SEGMENT,MCM05 +S012063_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012063_BCN1 GREEN SEGMENT,MCM05 +S012063_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012063_BCN1 AMBER SEGMENT,MCM05 +S012063_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012063_BCN1 BLUE SEGMENT,MCM05 +S012060_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012060_BCN1 GREEN SEGMENT,MCM05 +S012060_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012060_BCN1 AMBER SEGMENT,MCM05 +S012060_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012060_BCN1 BLUE SEGMENT,MCM05 +S012070_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012070_BCN1 GREEN SEGMENT,MCM05 +S012070_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012070_BCN1 AMBER SEGMENT,MCM05 +S012070_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012070_BCN1 BLUE SEGMENT,MCM05 +S012073_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012073_BCN1 GREEN SEGMENT,MCM05 +S012073_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012073_BCN1 AMBER SEGMENT,MCM05 +S012073_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S012073_BCN1 BLUE SEGMENT,MCM05 +S014008_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014008_BCN1 GREEN SEGMENT,MCM05 +S014008_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014008_BCN1 AMBER SEGMENT,MCM05 +S014008_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014008_BCN1 BLUE SEGMENT,MCM05 +S014016_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014016_BCN1 GREEN SEGMENT,MCM05 +S014016_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014016_BCN1 AMBER SEGMENT,MCM05 +S014016_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014016_BCN1 BLUE SEGMENT,MCM05 +S014009_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014009_BCN1 GREEN SEGMENT,MCM05 +S014009_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014009_BCN1 AMBER SEGMENT,MCM05 +S014009_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014009_BCN1 BLUE SEGMENT,MCM05 +S014024_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014024_BCN1 GREEN SEGMENT,MCM05 +S014024_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014024_BCN1 AMBER SEGMENT,MCM05 +S014024_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014024_BCN1 BLUE SEGMENT,MCM05 +S014017_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014017_BCN1 GREEN SEGMENT,MCM05 +S014017_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014017_BCN1 AMBER SEGMENT,MCM05 +S014017_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014017_BCN1 BLUE SEGMENT,MCM05 +S014026_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014026_BCN1 GREEN SEGMENT,MCM05 +S014026_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014026_BCN1 AMBER SEGMENT,MCM05 +S014026_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014026_BCN1 BLUE SEGMENT,MCM05 +S014025_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014025_BCN1 GREEN SEGMENT,MCM05 +S014025_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014025_BCN1 AMBER SEGMENT,MCM05 +S014025_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014025_BCN1 BLUE SEGMENT,MCM05 +S014036_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014036_BCN1 GREEN SEGMENT,MCM05 +S014036_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014036_BCN1 AMBER SEGMENT,MCM05 +S014036_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014036_BCN1 BLUE SEGMENT,MCM05 +S014033_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014033_BCN1 GREEN SEGMENT,MCM05 +S014033_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014033_BCN1 AMBER SEGMENT,MCM05 +S014033_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014033_BCN1 BLUE SEGMENT,MCM05 +S014044_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014044_BCN1 GREEN SEGMENT,MCM05 +S014044_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014044_BCN1 AMBER SEGMENT,MCM05 +S014044_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014044_BCN1 BLUE SEGMENT,MCM05 +S014043_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014043_BCN1 GREEN SEGMENT,MCM05 +S014043_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014043_BCN1 AMBER SEGMENT,MCM05 +S014043_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014043_BCN1 BLUE SEGMENT,MCM05 +S014052_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014052_BCN1 GREEN SEGMENT,MCM05 +S014052_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014052_BCN1 AMBER SEGMENT,MCM05 +S014052_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014052_BCN1 BLUE SEGMENT,MCM05 +S014049_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014049_BCN1 GREEN SEGMENT,MCM05 +S014049_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014049_BCN1 AMBER SEGMENT,MCM05 +S014049_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014049_BCN1 BLUE SEGMENT,MCM05 +S014058_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014058_BCN1 GREEN SEGMENT,MCM05 +S014058_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014058_BCN1 AMBER SEGMENT,MCM05 +S014058_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014058_BCN1 BLUE SEGMENT,MCM05 +S014064_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014064_BCN1 GREEN SEGMENT,MCM05 +S014064_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014064_BCN1 AMBER SEGMENT,MCM05 +S014064_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014064_BCN1 BLUE SEGMENT,MCM05 +S014057_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014057_BCN1 GREEN SEGMENT,MCM05 +S014057_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014057_BCN1 AMBER SEGMENT,MCM05 +S014057_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014057_BCN1 BLUE SEGMENT,MCM05 +S014072_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014072_BCN1 GREEN SEGMENT,MCM05 +S014072_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014072_BCN1 AMBER SEGMENT,MCM05 +S014072_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014072_BCN1 BLUE SEGMENT,MCM05 +S014065_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014065_BCN1 GREEN SEGMENT,MCM05 +S014065_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014065_BCN1 AMBER SEGMENT,MCM05 +S014065_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014065_BCN1 BLUE SEGMENT,MCM05 +S014080_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014080_BCN1 GREEN SEGMENT,MCM05 +S014080_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014080_BCN1 AMBER SEGMENT,MCM05 +S014080_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014080_BCN1 BLUE SEGMENT,MCM05 +S014073_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014073_BCN1 GREEN SEGMENT,MCM05 +S014073_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014073_BCN1 AMBER SEGMENT,MCM05 +S014073_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014073_BCN1 BLUE SEGMENT,MCM05 +S014081_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014081_BCN1 GREEN SEGMENT,MCM05 +S014081_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014081_BCN1 AMBER SEGMENT,MCM05 +S014081_BCN1:O.ProcessDataOut.Segment_3_Animation_Type.0,S014081_BCN1 BLUE SEGMENT,MCM05 +S012007_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012007_BCN1 GREEN SEGMENT,MCM05 +S012007_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012007_BCN1 BLUE SEGMENT,MCM05 +S012003_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012003_BCN1 GREEN SEGMENT,MCM05 +S012003_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012003_BCN1 BLUE SEGMENT,MCM05 +S012005_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012005_BCN1 GREEN SEGMENT,MCM05 +S012005_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012005_BCN1 BLUE SEGMENT,MCM05 +S012008_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012008_BCN1 GREEN SEGMENT,MCM05 +S012008_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012008_BCN1 BLUE SEGMENT,MCM05 +S012006_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012006_BCN1 GREEN SEGMENT,MCM05 +S012006_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012006_BCN1 BLUE SEGMENT,MCM05 +S012004_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012004_BCN1 GREEN SEGMENT,MCM05 +S012004_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012004_BCN1 BLUE SEGMENT,MCM05 +S012015_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012015_BCN1 GREEN SEGMENT,MCM05 +S012015_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012015_BCN1 BLUE SEGMENT,MCM05 +S012011_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012011_BCN1 GREEN SEGMENT,MCM05 +S012011_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012011_BCN1 BLUE SEGMENT,MCM05 +S012013_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012013_BCN1 GREEN SEGMENT,MCM05 +S012013_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012013_BCN1 BLUE SEGMENT,MCM05 +S012016_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012016_BCN1 GREEN SEGMENT,MCM05 +S012016_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012016_BCN1 BLUE SEGMENT,MCM05 +S012014_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012014_BCN1 GREEN SEGMENT,MCM05 +S012014_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012014_BCN1 BLUE SEGMENT,MCM05 +S012012_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012012_BCN1 GREEN SEGMENT,MCM05 +S012012_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012012_BCN1 BLUE SEGMENT,MCM05 +S012023_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012023_BCN1 GREEN SEGMENT,MCM05 +S012023_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012023_BCN1 BLUE SEGMENT,MCM05 +S012021_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012021_BCN1 GREEN SEGMENT,MCM05 +S012021_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012021_BCN1 BLUE SEGMENT,MCM05 +S012024_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012024_BCN1 GREEN SEGMENT,MCM05 +S012024_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012024_BCN1 BLUE SEGMENT,MCM05 +S012022_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012022_BCN1 GREEN SEGMENT,MCM05 +S012022_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012022_BCN1 BLUE SEGMENT,MCM05 +S012020_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012020_BCN1 GREEN SEGMENT,MCM05 +S012020_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012020_BCN1 BLUE SEGMENT,MCM05 +S012025_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012025_BCN1 GREEN SEGMENT,MCM05 +S012025_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012025_BCN1 BLUE SEGMENT,MCM05 +S012031_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012031_BCN1 GREEN SEGMENT,MCM05 +S012031_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012031_BCN1 BLUE SEGMENT,MCM05 +S012029_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012029_BCN1 GREEN SEGMENT,MCM05 +S012029_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012029_BCN1 BLUE SEGMENT,MCM05 +S012032_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012032_BCN1 GREEN SEGMENT,MCM05 +S012032_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012032_BCN1 BLUE SEGMENT,MCM05 +S012030_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012030_BCN1 GREEN SEGMENT,MCM05 +S012030_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012030_BCN1 BLUE SEGMENT,MCM05 +S012028_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012028_BCN1 GREEN SEGMENT,MCM05 +S012028_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012028_BCN1 BLUE SEGMENT,MCM05 +S012039_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012039_BCN1 GREEN SEGMENT,MCM05 +S012039_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012039_BCN1 BLUE SEGMENT,MCM05 +S012040_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012040_BCN1 GREEN SEGMENT,MCM05 +S012040_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012040_BCN1 BLUE SEGMENT,MCM05 +S012038_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012038_BCN1 GREEN SEGMENT,MCM05 +S012038_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012038_BCN1 BLUE SEGMENT,MCM05 +S012041_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012041_BCN1 GREEN SEGMENT,MCM05 +S012041_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012041_BCN1 BLUE SEGMENT,MCM05 +S012047_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012047_BCN1 GREEN SEGMENT,MCM05 +S012047_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012047_BCN1 BLUE SEGMENT,MCM05 +S012043_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012043_BCN1 GREEN SEGMENT,MCM05 +S012043_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012043_BCN1 BLUE SEGMENT,MCM05 +S012048_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012048_BCN1 GREEN SEGMENT,MCM05 +S012048_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012048_BCN1 BLUE SEGMENT,MCM05 +S012042_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012042_BCN1 GREEN SEGMENT,MCM05 +S012042_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012042_BCN1 BLUE SEGMENT,MCM05 +S012046_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012046_BCN1 GREEN SEGMENT,MCM05 +S012046_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012046_BCN1 BLUE SEGMENT,MCM05 +S012049_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012049_BCN1 GREEN SEGMENT,MCM05 +S012049_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012049_BCN1 BLUE SEGMENT,MCM05 +S012053_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012053_BCN1 GREEN SEGMENT,MCM05 +S012053_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012053_BCN1 BLUE SEGMENT,MCM05 +S012056_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012056_BCN1 GREEN SEGMENT,MCM05 +S012056_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012056_BCN1 BLUE SEGMENT,MCM05 +S012050_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012050_BCN1 GREEN SEGMENT,MCM05 +S012050_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012050_BCN1 BLUE SEGMENT,MCM05 +S012054_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012054_BCN1 GREEN SEGMENT,MCM05 +S012054_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012054_BCN1 BLUE SEGMENT,MCM05 +S012057_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012057_BCN1 GREEN SEGMENT,MCM05 +S012057_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012057_BCN1 BLUE SEGMENT,MCM05 +S012059_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012059_BCN1 GREEN SEGMENT,MCM05 +S012059_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012059_BCN1 BLUE SEGMENT,MCM05 +S012061_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012061_BCN1 GREEN SEGMENT,MCM05 +S012061_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012061_BCN1 BLUE SEGMENT,MCM05 +S012064_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012064_BCN1 GREEN SEGMENT,MCM05 +S012064_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012064_BCN1 BLUE SEGMENT,MCM05 +S012058_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012058_BCN1 GREEN SEGMENT,MCM05 +S012058_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012058_BCN1 BLUE SEGMENT,MCM05 +S012062_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012062_BCN1 GREEN SEGMENT,MCM05 +S012062_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012062_BCN1 BLUE SEGMENT,MCM05 +S012065_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012065_BCN1 GREEN SEGMENT,MCM05 +S012065_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012065_BCN1 BLUE SEGMENT,MCM05 +S012071_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012071_BCN1 GREEN SEGMENT,MCM05 +S012071_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012071_BCN1 BLUE SEGMENT,MCM05 +S012069_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012069_BCN1 GREEN SEGMENT,MCM05 +S012069_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012069_BCN1 BLUE SEGMENT,MCM05 +S012072_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012072_BCN1 GREEN SEGMENT,MCM05 +S012072_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012072_BCN1 BLUE SEGMENT,MCM05 +S012066_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012066_BCN1 GREEN SEGMENT,MCM05 +S012066_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012066_BCN1 BLUE SEGMENT,MCM05 +S012079_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012079_BCN1 GREEN SEGMENT,MCM05 +S012079_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012079_BCN1 BLUE SEGMENT,MCM05 +S012075_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012075_BCN1 GREEN SEGMENT,MCM05 +S012075_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012075_BCN1 BLUE SEGMENT,MCM05 +S012077_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012077_BCN1 GREEN SEGMENT,MCM05 +S012077_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012077_BCN1 BLUE SEGMENT,MCM05 +S012080_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012080_BCN1 GREEN SEGMENT,MCM05 +S012080_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012080_BCN1 BLUE SEGMENT,MCM05 +S012082_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012082_BCN1 GREEN SEGMENT,MCM05 +S012082_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012082_BCN1 BLUE SEGMENT,MCM05 +S012084_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012084_BCN1 GREEN SEGMENT,MCM05 +S012084_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012084_BCN1 BLUE SEGMENT,MCM05 +S012096_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S012096_BCN1 GREEN SEGMENT,MCM05 +S012096_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S012096_BCN1 BLUE SEGMENT,MCM05 +S014002_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014002_BCN1 GREEN SEGMENT,MCM05 +S014002_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014002_BCN1 BLUE SEGMENT,MCM05 +S014004_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014004_BCN1 GREEN SEGMENT,MCM05 +S014004_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014004_BCN1 BLUE SEGMENT,MCM05 +S014006_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014006_BCN1 GREEN SEGMENT,MCM05 +S014006_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014006_BCN1 BLUE SEGMENT,MCM05 +S014007_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014007_BCN1 GREEN SEGMENT,MCM05 +S014007_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014007_BCN1 BLUE SEGMENT,MCM05 +S014005_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014005_BCN1 GREEN SEGMENT,MCM05 +S014005_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014005_BCN1 BLUE SEGMENT,MCM05 +S014003_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014003_BCN1 GREEN SEGMENT,MCM05 +S014003_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014003_BCN1 BLUE SEGMENT,MCM05 +S014010_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014010_BCN1 GREEN SEGMENT,MCM05 +S014010_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014010_BCN1 BLUE SEGMENT,MCM05 +S014012_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014012_BCN1 GREEN SEGMENT,MCM05 +S014012_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014012_BCN1 BLUE SEGMENT,MCM05 +S014014_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014014_BCN1 GREEN SEGMENT,MCM05 +S014014_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014014_BCN1 BLUE SEGMENT,MCM05 +S014015_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014015_BCN1 GREEN SEGMENT,MCM05 +S014015_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014015_BCN1 BLUE SEGMENT,MCM05 +S014013_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014013_BCN1 GREEN SEGMENT,MCM05 +S014013_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014013_BCN1 BLUE SEGMENT,MCM05 +S014011_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014011_BCN1 GREEN SEGMENT,MCM05 +S014011_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014011_BCN1 BLUE SEGMENT,MCM05 +S014018_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014018_BCN1 GREEN SEGMENT,MCM05 +S014018_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014018_BCN1 BLUE SEGMENT,MCM05 +S014020_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014020_BCN1 GREEN SEGMENT,MCM05 +S014020_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014020_BCN1 BLUE SEGMENT,MCM05 +S014022_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014022_BCN1 GREEN SEGMENT,MCM05 +S014022_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014022_BCN1 BLUE SEGMENT,MCM05 +S014023_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014023_BCN1 GREEN SEGMENT,MCM05 +S014023_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014023_BCN1 BLUE SEGMENT,MCM05 +S014021_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014021_BCN1 GREEN SEGMENT,MCM05 +S014021_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014021_BCN1 BLUE SEGMENT,MCM05 +S014032_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014032_BCN1 GREEN SEGMENT,MCM05 +S014032_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014032_BCN1 BLUE SEGMENT,MCM05 +S014030_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014030_BCN1 GREEN SEGMENT,MCM05 +S014030_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014030_BCN1 BLUE SEGMENT,MCM05 +S014031_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014031_BCN1 GREEN SEGMENT,MCM05 +S014031_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014031_BCN1 BLUE SEGMENT,MCM05 +S014029_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014029_BCN1 GREEN SEGMENT,MCM05 +S014029_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014029_BCN1 BLUE SEGMENT,MCM05 +S014034_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014034_BCN1 GREEN SEGMENT,MCM05 +S014034_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014034_BCN1 BLUE SEGMENT,MCM05 +S014040_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014040_BCN1 GREEN SEGMENT,MCM05 +S014040_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014040_BCN1 BLUE SEGMENT,MCM05 +S014038_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014038_BCN1 GREEN SEGMENT,MCM05 +S014038_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014038_BCN1 BLUE SEGMENT,MCM05 +S014039_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014039_BCN1 GREEN SEGMENT,MCM05 +S014039_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014039_BCN1 BLUE SEGMENT,MCM05 +S014037_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014037_BCN1 GREEN SEGMENT,MCM05 +S014037_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014037_BCN1 BLUE SEGMENT,MCM05 +S014042_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014042_BCN1 GREEN SEGMENT,MCM05 +S014042_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014042_BCN1 BLUE SEGMENT,MCM05 +S014048_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014048_BCN1 GREEN SEGMENT,MCM05 +S014048_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014048_BCN1 BLUE SEGMENT,MCM05 +S014046_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014046_BCN1 GREEN SEGMENT,MCM05 +S014046_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014046_BCN1 BLUE SEGMENT,MCM05 +S014047_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014047_BCN1 GREEN SEGMENT,MCM05 +S014047_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014047_BCN1 BLUE SEGMENT,MCM05 +S014041_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014041_BCN1 GREEN SEGMENT,MCM05 +S014041_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014041_BCN1 BLUE SEGMENT,MCM05 +S014045_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014045_BCN1 GREEN SEGMENT,MCM05 +S014045_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014045_BCN1 BLUE SEGMENT,MCM05 +S014050_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014050_BCN1 GREEN SEGMENT,MCM05 +S014050_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014050_BCN1 BLUE SEGMENT,MCM05 +S014056_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014056_BCN1 GREEN SEGMENT,MCM05 +S014056_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014056_BCN1 BLUE SEGMENT,MCM05 +S014054_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014054_BCN1 GREEN SEGMENT,MCM05 +S014054_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014054_BCN1 BLUE SEGMENT,MCM05 +S014055_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014055_BCN1 GREEN SEGMENT,MCM05 +S014055_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014055_BCN1 BLUE SEGMENT,MCM05 +S014053_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014053_BCN1 GREEN SEGMENT,MCM05 +S014053_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014053_BCN1 BLUE SEGMENT,MCM05 +S014062_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014062_BCN1 GREEN SEGMENT,MCM05 +S014062_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014062_BCN1 BLUE SEGMENT,MCM05 +S014063_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014063_BCN1 GREEN SEGMENT,MCM05 +S014063_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014063_BCN1 BLUE SEGMENT,MCM05 +S014061_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014061_BCN1 GREEN SEGMENT,MCM05 +S014061_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014061_BCN1 BLUE SEGMENT,MCM05 +S014071_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014071_BCN1 GREEN SEGMENT,MCM05 +S014071_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014071_BCN1 BLUE SEGMENT,MCM05 +S014069_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014069_BCN1 GREEN SEGMENT,MCM05 +S014069_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014069_BCN1 BLUE SEGMENT,MCM05 +S014079_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014079_BCN1 GREEN SEGMENT,MCM05 +S014079_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014079_BCN1 BLUE SEGMENT,MCM05 +S014077_BCN1:O.ProcessDataOut.Segment_1_Animation_Type.0,S014077_BCN1 GREEN SEGMENT,MCM05 +S014077_BCN1:O.ProcessDataOut.Segment_2_Animation_Type.0,S014077_BCN1 BLUE SEGMENT,MCM05 +Local:5:I.Data.0,MCM05 S_PBLT START,MCM05 +Local:5:I.Data.1,MCM05 ST_PB STOP,MCM05 +Local:5:I.Data.10,SPARE,MCM05 +Local:5:I.Data.11,SPARE,MCM05 +Local:5:I.Data.12,SPARE,MCM05 +Local:5:I.Data.13,SPARE,MCM05 +Local:5:I.Data.14,SPARE,MCM05 +Local:5:I.Data.15,SPARE,MCM05 +Local:5:I.Data.2,MCM05 MF_PBLT MOTOR FAULT RESET,MCM05 +Local:5:I.Data.3,MCM05 JR_PBLT JAM RESET,MCM05 +Local:5:I.Data.4,MCM05 LAP_PBLT LOW AIR PRESSURE,MCM05 +Local:5:I.Data.5,MCM05 PBFR_PBLT POWER BRANCH FAULT RESET,MCM05 +Local:5:I.Data.6,MCM05 BATTERY_FAULT_UPS BATTERY FAULT,MCM05 +Local:5:I.Data.7,MCM05 ON_BATTERY ON BATTERY,MCM05 +Local:5:I.Data.8,MCM05 UPS_BATTERY_LOW UPS BATTERY LOW,MCM05 +Local:5:I.Data.9,MCM05 NAT_SWITCH ALARM NAT SWITCH,MCM05 +Local:7:I.Pt00.Data,MCM05 FIRE_ALARM FIRE ALARM,MCM05 +VSD_DPM3_FIO6:I.Pt01.Data,SPARE,MCM05 +VSD_DPM3_FIO6:I.Pt03.Data,SPARE,MCM05 +VSD_DPM3_FIO6:I.Pt04.Data,SPARE,MCM05 +VSD_DPM3_FIO6:I.Pt06.Data,SPARE,MCM05 +Local:7:I.Pt02.Data,MCM05 ES_PB ES_PB_CH1,MCM05 +Local:7:I.Pt03.Data,MCM05 ES_PB ES_PB_CH2,MCM05 +VSD_DPM3_FIO6:I.Pt09.Data,SPARE,MCM05 +VSD_DPM3_FIO6:I.Pt11.Data,SPARE,MCM05 +VSD_DPM3_FIO6:O.Pt05.Data,SPARE,MCM05 +VSD_DPM3_FIO6:O.Pt07.Data,SPARE,MCM05 +VSD_DPM3_FIO6:O.Pt13.Data,SPARE,MCM05 +Local:6:O.Data.0,MCM05 ES_LT EMERGENCY STOP ACTUATED,MCM05 +Local:6:O.Data.1,MCM05 S_PBLT START LIGHT,MCM05 +Local:6:O.Data.10,SPARE,MCM05 +Local:6:O.Data.11,SPARE,MCM05 +Local:6:O.Data.12,SPARE,MCM05 +Local:6:O.Data.13,SPARE,MCM05 +Local:6:O.Data.14,SPARE,MCM05 +Local:6:O.Data.15,SPARE,MCM05 +Local:6:O.Data.2,MCM05 MF_PBLT MOTOR FAULT LIGHT,MCM05 +Local:6:O.Data.3,MCM05 JR_PBLT JAM RESTART LIGHT,MCM05 +Local:6:O.Data.4,MCM05 LAP_PBLT LOW AIR PRESSURE LIGHT,MCM05 +Local:6:O.Data.5,MCM05 PBFR_PBLT POWER BRANCH FAULT RESET LIGHT,MCM05 +Local:6:O.Data.6,SPARE,MCM05 +Local:6:O.Data.7,SPARE,MCM05 +Local:6:O.Data.8,SPARE,MCM05 +Local:6:O.Data.9,SPARE,MCM05 diff --git a/PLC Data Generator/__pycache__/classifiers.cpython-312.pyc b/PLC Data Generator/__pycache__/classifiers.cpython-312.pyc index 1b570f4..3732016 100644 Binary files a/PLC Data Generator/__pycache__/classifiers.cpython-312.pyc and b/PLC Data Generator/__pycache__/classifiers.cpython-312.pyc differ diff --git a/PLC Data Generator/classifiers.py b/PLC Data Generator/classifiers.py index 1bed950..334024e 100644 --- a/PLC Data Generator/classifiers.py +++ b/PLC Data Generator/classifiers.py @@ -30,6 +30,8 @@ def classify_signal(desca, tagname, descb=None): return 'O' elif re.search(r'BCN', desca_str) and 'FIOH' in tagname_str: return 'O' + elif re.search(r'_H', desca_str): + return 'O' # IOLink patterns (check FIOH before FIO due to priority) elif re.search(r'SOL', desca_str) and re.search(r'DIVERT', descb_str): @@ -52,7 +54,7 @@ def classify_signal(desca, tagname, descb=None): return 'I' elif re.search(r'FPE', desca_str): return 'I' - elif re.search(r'ENC', desca_str): + elif re.search(r'EN', desca_str): return 'I' elif re.search(r'PS', desca_str): return 'I' diff --git a/PLC Data Generator/data/IO Assignment_MTN6_MCM01_COMPLETE_UL1_UL6.xlsm b/PLC Data Generator/data/IO Assignment_MTN6_MCM01_COMPLETE_UL1_UL6.xlsm new file mode 100644 index 0000000..6eaa4d3 Binary files /dev/null and b/PLC Data Generator/data/IO Assignment_MTN6_MCM01_COMPLETE_UL1_UL6.xlsm differ diff --git a/PLC Data Generator/data/SAT9_MCM01.xlsx b/PLC Data Generator/data/SAT9_MCM01.xlsx new file mode 100644 index 0000000..893e42d Binary files /dev/null and b/PLC Data Generator/data/SAT9_MCM01.xlsx differ diff --git a/PLC Data Generator/data/SAT9_MCM02.xlsx b/PLC Data Generator/data/SAT9_MCM02.xlsx new file mode 100644 index 0000000..ddfa6ca Binary files /dev/null and b/PLC Data Generator/data/SAT9_MCM02.xlsx differ diff --git a/PLC Data Generator/data/~$IO Assignment_MTN6_MCM05_COMPLETE_CHUTE_LOAD.xlsm b/PLC Data Generator/data/~$IO Assignment_MTN6_MCM05_COMPLETE_CHUTE_LOAD.xlsm deleted file mode 100644 index da0d98d..0000000 Binary files a/PLC Data Generator/data/~$IO Assignment_MTN6_MCM05_COMPLETE_CHUTE_LOAD.xlsm and /dev/null differ diff --git a/PLC Data Generator/~$DESC_IP_MERGED.xlsx b/PLC Data Generator/~$DESC_IP_MERGED.xlsx deleted file mode 100644 index da0d98d..0000000 Binary files a/PLC Data Generator/~$DESC_IP_MERGED.xlsx and /dev/null differ diff --git a/Routines Generator/DESC_IP_MERGED copy.xlsx b/Routines Generator/DESC_IP_MERGED copy.xlsx deleted file mode 100644 index 94f404e..0000000 Binary files a/Routines Generator/DESC_IP_MERGED copy.xlsx and /dev/null differ diff --git a/Routines Generator/DESC_IP_MERGED.xlsx b/Routines Generator/DESC_IP_MERGED.xlsx index 20c81fb..26ef655 100644 Binary files a/Routines Generator/DESC_IP_MERGED.xlsx and b/Routines Generator/DESC_IP_MERGED.xlsx differ diff --git a/Routines Generator/SafetyTagMapping.txt b/Routines Generator/SafetyTagMapping.txt index d1897fc..341be36 100644 --- a/Routines Generator/SafetyTagMapping.txt +++ b/Routines Generator/SafetyTagMapping.txt @@ -1 +1 @@ -MCM01_S_PB=SFT_MCM01_S_PB, PS1_1_S1_PB=SFT_PS1_1_S1_PB, PS1_1_S2_PB=SFT_PS1_1_S2_PB, PS1_5_S1_PB=SFT_PS1_5_S1_PB, PS1_5_S2_PB=SFT_PS1_5_S2_PB, PS2_1_S1_PB=SFT_PS2_1_S1_PB, PS2_1_S2_PB=SFT_PS2_1_S2_PB, PS2_6_S1_PB=SFT_PS2_6_S1_PB, PS2_6_S2_PB=SFT_PS2_6_S2_PB, PS3_12_S1_PB=SFT_PS3_12_S1_PB, PS3_12_S2_PB=SFT_PS3_12_S2_PB, PS3_1_S1_PB=SFT_PS3_1_S1_PB, PS3_1_S2_PB=SFT_PS3_1_S2_PB, PS4_14_S1_PB=SFT_PS4_14_S1_PB, PS4_14_S2_PB=SFT_PS4_14_S2_PB, PS4_1_S1_PB=SFT_PS4_1_S1_PB, PS4_1_S2_PB=SFT_PS4_1_S2_PB, UL10_1_SS1_SPB=SFT_UL10_1_SS1_SPB, UL10_3_S1_PB=SFT_UL10_3_S1_PB, UL10_3_S2_PB=SFT_UL10_3_S2_PB, UL11_3_S1_PB=SFT_UL11_3_S1_PB, UL11_3_S2_PB=SFT_UL11_3_S2_PB, UL11_4_S1_PB=SFT_UL11_4_S1_PB, UL11_4_S2_PB=SFT_UL11_4_S2_PB, UL11_9_S1_PB=SFT_UL11_9_S1_PB, UL11_9_S2_PB=SFT_UL11_9_S2_PB, UL12_3_S1_PB=SFT_UL12_3_S1_PB, UL12_3_S2_PB=SFT_UL12_3_S2_PB, UL12_4_S1_PB=SFT_UL12_4_S1_PB, UL12_4_S2_PB=SFT_UL12_4_S2_PB, UL1_3_S1_PB=SFT_UL1_3_S1_PB, UL1_3_S2_PB=SFT_UL1_3_S2_PB, UL1_4_S1_PB=SFT_UL1_4_S1_PB, UL1_4_S2_PB=SFT_UL1_4_S2_PB, UL1_9_S1_PB=SFT_UL1_9_S1_PB, UL1_9_S2_PB=SFT_UL1_9_S2_PB, UL2_3_S1_PB=SFT_UL2_3_S1_PB, UL2_3_S2_PB=SFT_UL2_3_S2_PB, UL2_4_S1_PB=SFT_UL2_4_S1_PB, UL2_4_S2_PB=SFT_UL2_4_S2_PB, UL3_1_SS1_SPB=SFT_UL3_1_SS1_SPB, UL3_2_S1_PB=SFT_UL3_2_S1_PB, UL3_2_S2_PB=SFT_UL3_2_S2_PB, UL4_3_S1_PB=SFT_UL4_3_S1_PB, UL4_3_S2_PB=SFT_UL4_3_S2_PB, UL4_4_S1_PB=SFT_UL4_4_S1_PB, UL4_4_S2_PB=SFT_UL4_4_S2_PB, UL4_9_S1_PB=SFT_UL4_9_S1_PB, UL4_9_S2_PB=SFT_UL4_9_S2_PB, UL5_3_S1_PB=SFT_UL5_3_S1_PB, UL5_3_S2_PB=SFT_UL5_3_S2_PB, UL5_4_S1_PB=SFT_UL5_4_S1_PB, UL5_4_S2_PB=SFT_UL5_4_S2_PB, UL6_1_SS1_SPB=SFT_UL6_1_SS1_SPB, UL6_2_S1_PB=SFT_UL6_2_S1_PB, UL6_2_S2_PB=SFT_UL6_2_S2_PB, UL7_3_S1_PB=SFT_UL7_3_S1_PB, UL7_3_S2_PB=SFT_UL7_3_S2_PB, UL7_4_S1_PB=SFT_UL7_4_S1_PB, UL7_4_S2_PB=SFT_UL7_4_S2_PB, UL7_9_S1_PB=SFT_UL7_9_S1_PB, UL7_9_S2_PB=SFT_UL7_9_S2_PB, UL8_1_SS1_SPB=SFT_UL8_1_SS1_SPB, UL8_3_S1_PB=SFT_UL8_3_S1_PB, UL8_3_S2_PB=SFT_UL8_3_S2_PB, UL9_3_S1_PB=SFT_UL9_3_S1_PB, UL9_3_S2_PB=SFT_UL9_3_S2_PB, UL9_4_S1_PB=SFT_UL9_4_S1_PB, UL9_4_S2_PB=SFT_UL9_4_S2_PB \ No newline at end of file +MCM_S_PB=SFT_MCM_S_PB, PS1_1_S1_PB=SFT_PS1_1_S1_PB, PS1_1_S2_PB=SFT_PS1_1_S2_PB, PS1_5_S1_PB=SFT_PS1_5_S1_PB, PS1_5_S2_PB=SFT_PS1_5_S2_PB, PS2_1_S1_PB=SFT_PS2_1_S1_PB, PS2_1_S2_PB=SFT_PS2_1_S2_PB, PS2_6_S1_PB=SFT_PS2_6_S1_PB, PS2_6_S2_PB=SFT_PS2_6_S2_PB, PS3_12_S1_PB=SFT_PS3_12_S1_PB, PS3_12_S2_PB=SFT_PS3_12_S2_PB, PS3_1_S1_PB=SFT_PS3_1_S1_PB, PS3_1_S2_PB=SFT_PS3_1_S2_PB, PS4_14_S1_PB=SFT_PS4_14_S1_PB, PS4_14_S2_PB=SFT_PS4_14_S2_PB, PS4_1_S1_PB=SFT_PS4_1_S1_PB, PS4_1_S2_PB=SFT_PS4_1_S2_PB, UL10_1_SS1_SPB=SFT_UL10_1_SS1_SPB, UL10_3_S1_PB=SFT_UL10_3_S1_PB, UL10_3_S2_PB=SFT_UL10_3_S2_PB, UL11_3_S1_PB=SFT_UL11_3_S1_PB, UL11_3_S2_PB=SFT_UL11_3_S2_PB, UL11_4_S1_PB=SFT_UL11_4_S1_PB, UL11_4_S2_PB=SFT_UL11_4_S2_PB, UL11_9_S1_PB=SFT_UL11_9_S1_PB, UL11_9_S2_PB=SFT_UL11_9_S2_PB, UL12_3_S1_PB=SFT_UL12_3_S1_PB, UL12_3_S2_PB=SFT_UL12_3_S2_PB, UL12_4_S1_PB=SFT_UL12_4_S1_PB, UL12_4_S2_PB=SFT_UL12_4_S2_PB, UL1_3_S1_PB=SFT_UL1_3_S1_PB, UL1_3_S2_PB=SFT_UL1_3_S2_PB, UL1_4_S1_PB=SFT_UL1_4_S1_PB, UL1_4_S2_PB=SFT_UL1_4_S2_PB, UL1_9_S1_PB=SFT_UL1_9_S1_PB, UL1_9_S2_PB=SFT_UL1_9_S2_PB, UL2_3_S1_PB=SFT_UL2_3_S1_PB, UL2_3_S2_PB=SFT_UL2_3_S2_PB, UL2_4_S1_PB=SFT_UL2_4_S1_PB, UL2_4_S2_PB=SFT_UL2_4_S2_PB, UL3_1_SS1_SPB=SFT_UL3_1_SS1_SPB, UL3_2_S1_PB=SFT_UL3_2_S1_PB, UL3_2_S2_PB=SFT_UL3_2_S2_PB, UL4_3_S1_PB=SFT_UL4_3_S1_PB, UL4_3_S2_PB=SFT_UL4_3_S2_PB, UL4_4_S1_PB=SFT_UL4_4_S1_PB, UL4_4_S2_PB=SFT_UL4_4_S2_PB, UL4_9_S1_PB=SFT_UL4_9_S1_PB, UL4_9_S2_PB=SFT_UL4_9_S2_PB, UL5_3_S1_PB=SFT_UL5_3_S1_PB, UL5_3_S2_PB=SFT_UL5_3_S2_PB, UL5_4_S1_PB=SFT_UL5_4_S1_PB, UL5_4_S2_PB=SFT_UL5_4_S2_PB, UL6_1_SS1_SPB=SFT_UL6_1_SS1_SPB, UL6_2_S1_PB=SFT_UL6_2_S1_PB, UL6_2_S2_PB=SFT_UL6_2_S2_PB, UL7_3_S1_PB=SFT_UL7_3_S1_PB, UL7_3_S2_PB=SFT_UL7_3_S2_PB, UL7_4_S1_PB=SFT_UL7_4_S1_PB, UL7_4_S2_PB=SFT_UL7_4_S2_PB, UL7_9_S1_PB=SFT_UL7_9_S1_PB, UL7_9_S2_PB=SFT_UL7_9_S2_PB, UL8_1_SS1_SPB=SFT_UL8_1_SS1_SPB, UL8_3_S1_PB=SFT_UL8_3_S1_PB, UL8_3_S2_PB=SFT_UL8_3_S2_PB, UL9_3_S1_PB=SFT_UL9_3_S1_PB, UL9_3_S2_PB=SFT_UL9_3_S2_PB, UL9_4_S1_PB=SFT_UL9_4_S1_PB, UL9_4_S2_PB=SFT_UL9_4_S2_PB \ No newline at end of file diff --git a/Routines Generator/SafetyTagMapping_Limited.txt b/Routines Generator/SafetyTagMapping_Limited.txt deleted file mode 100644 index 9a593e3..0000000 --- a/Routines Generator/SafetyTagMapping_Limited.txt +++ /dev/null @@ -1 +0,0 @@ -MCM01_S_PB=SFT_MCM01_S_PB \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/CB_MONITOR_Boilerplate.xml b/Routines Generator/UDTs_Tags/CB_MONITOR_Boilerplate.xml new file mode 100644 index 0000000..b447143 --- /dev/null +++ b/Routines Generator/UDTs_Tags/CB_MONITOR_Boilerplate.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/D2C_Boilerplate.xml b/Routines Generator/UDTs_Tags/D2C_Boilerplate.xml new file mode 100644 index 0000000..870d506 --- /dev/null +++ b/Routines Generator/UDTs_Tags/D2C_Boilerplate.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/EXTENDO_Boilerplate.xml b/Routines Generator/UDTs_Tags/EXTENDO_Boilerplate.xml new file mode 100644 index 0000000..a7674a8 --- /dev/null +++ b/Routines Generator/UDTs_Tags/EXTENDO_Boilerplate.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/FPE_Boilerplate.xml b/Routines Generator/UDTs_Tags/FPE_Boilerplate.xml new file mode 100644 index 0000000..d489b87 --- /dev/null +++ b/Routines Generator/UDTs_Tags/FPE_Boilerplate.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/JPE_Boilerplate.xml b/Routines Generator/UDTs_Tags/JPE_Boilerplate.xml new file mode 100644 index 0000000..a86fdb1 --- /dev/null +++ b/Routines Generator/UDTs_Tags/JPE_Boilerplate.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/MCM_Boilerplate.xml b/Routines Generator/UDTs_Tags/MCM_Boilerplate.xml new file mode 100644 index 0000000..618682a --- /dev/null +++ b/Routines Generator/UDTs_Tags/MCM_Boilerplate.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/PB_CHUTE_Boilerplate.xml b/Routines Generator/UDTs_Tags/PB_CHUTE_Boilerplate.xml new file mode 100644 index 0000000..52cdf47 --- /dev/null +++ b/Routines Generator/UDTs_Tags/PB_CHUTE_Boilerplate.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/PMM_Boilerplate.xml b/Routines Generator/UDTs_Tags/PMM_Boilerplate.xml new file mode 100644 index 0000000..7d8ce77 --- /dev/null +++ b/Routines Generator/UDTs_Tags/PMM_Boilerplate.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/RACK_Boilerplate.xml b/Routines Generator/UDTs_Tags/RACK_Boilerplate.xml new file mode 100644 index 0000000..c1eb1d8 --- /dev/null +++ b/Routines Generator/UDTs_Tags/RACK_Boilerplate.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/STATION_JR_CHUTE_Boilerplate.xml b/Routines Generator/UDTs_Tags/STATION_JR_CHUTE_Boilerplate.xml new file mode 100644 index 0000000..599af5e --- /dev/null +++ b/Routines Generator/UDTs_Tags/STATION_JR_CHUTE_Boilerplate.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/UDTs_Tags/STATION_JR_PB_Boilerplate.xml b/Routines Generator/UDTs_Tags/STATION_JR_PB_Boilerplate.xml new file mode 100644 index 0000000..2c3d8c6 --- /dev/null +++ b/Routines Generator/UDTs_Tags/STATION_JR_PB_Boilerplate.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Routines Generator/__pycache__/complete_workflow.cpython-312.pyc b/Routines Generator/__pycache__/complete_workflow.cpython-312.pyc new file mode 100644 index 0000000..e552f80 Binary files /dev/null and b/Routines Generator/__pycache__/complete_workflow.cpython-312.pyc differ diff --git a/Routines Generator/complete_workflow.py b/Routines Generator/complete_workflow.py index ac3f8fc..f0fa82c 100644 --- a/Routines Generator/complete_workflow.py +++ b/Routines Generator/complete_workflow.py @@ -14,6 +14,10 @@ import sys import argparse import subprocess from pathlib import Path +import io +import contextlib +import xml.etree.ElementTree as ET +import re def get_project_paths(): """Get standardized paths for all project components.""" @@ -27,7 +31,7 @@ def get_project_paths(): 'io_tree_generator': project_root / "IO Tree Configuration Generator" } -def run_plc_data_generator(raw_excel_file: Path, paths: dict) -> bool: +def run_plc_data_generator(raw_excel_file: Path, paths: dict, verbose: bool = False) -> bool: """Run the PLC Data Generator to create DESC_IP_MERGED.xlsx.""" data_gen_dir = paths['data_generator'] data_gen_script = data_gen_dir / "main.py" @@ -40,10 +44,6 @@ def run_plc_data_generator(raw_excel_file: Path, paths: dict) -> bool: print(f"ERROR: Raw Excel file not found at {raw_excel_file}") return False - print(f"=== Step 1: Processing Raw Excel Data ===") - print(f"Input: {raw_excel_file}") - print(f"Processing with PLC Data Generator...") - try: # Run the PLC Data Generator with the Excel file path as argument result = subprocess.run([ @@ -63,11 +63,10 @@ def run_plc_data_generator(raw_excel_file: Path, paths: dict) -> bool: # Consider it successful if the essential files were created, even with permission errors if result.returncode == 0 or (any(success_indicators) and "[Errno 1] Operation not permitted" in result.stdout): - if result.returncode != 0: - print("WARNING: Permission error at end of processing, but core processing completed successfully") - else: - print("SUCCESS: PLC Data Generator completed successfully") - print(result.stdout) + if verbose and result.returncode != 0: + print("Warning: Permission error at end of processing, core processing completed") + if verbose: + print(result.stdout) # Copy DESC_IP_MERGED.xlsx from data generator output (it already has safety sheets) dest = paths['routines_generator'] / "DESC_IP_MERGED.xlsx" @@ -75,71 +74,72 @@ def run_plc_data_generator(raw_excel_file: Path, paths: dict) -> bool: if source.exists(): import shutil shutil.copy2(source, dest) - print(f"SUCCESS: DESC_IP_MERGED.xlsx (with safety sheets) copied to {dest}") return True else: - print("ERROR: DESC_IP_MERGED.xlsx not found after data generation") return False else: - print("ERROR: PLC Data Generator failed") - print("STDOUT:", result.stdout) - print("STDERR:", result.stderr) + if verbose: + print("Error: Data processing failed") + print("STDOUT:", result.stdout) + print("STDERR:", result.stderr) return False except Exception as e: - print(f"ERROR: Error running PLC Data Generator: {e}") + if verbose: + print(f"Error: Exception in data processing: {e}") return False -def run_routines_generator(paths: dict, project_name: str = None, ignore_estop1ok: bool = False) -> bool: - """Run the Routines Generator using generate_main_with_dpm.py script.""" - print(f"\n=== Step 2: Generating L5X Programs ===") - +def run_routines_generator(paths: dict, project_name: str = None, ignore_estop1ok: bool = False, safety_only: bool = False, verbose: bool = False) -> bool: + """Run the Routines Generator. + + When safety_only is True, runs safety-only generation (inputs, outputs, resets, + estops, zones, estop_check, safety tag map). Otherwise runs the standard + generator with DPM and other routines. + """ routines_dir = paths['routines_generator'] - main_dpm_script = routines_dir / "generate_main_with_dpm.py" - - if not main_dpm_script.exists(): - print(f"ERROR: generate_main_with_dpm.py script not found at {main_dpm_script}") - return False try: - # Build command arguments for generate_main_with_dpm.py script + # Build command arguments to use unified, config-driven CLI + config_path = paths['project_root'] / 'generator_config.json' + excel_path = routines_dir / 'DESC_IP_MERGED.xlsx' + subcmd = 'safety' if safety_only else 'all' + # Build args with global flags BEFORE the subcommand cmd_args = [ - sys.executable, - str(main_dpm_script), - "--desc-ip-mode" # Use DESC_IP data extraction mode + sys.executable, + '-m', 'src.unified_cli', + '--config', str(config_path), + '--excel-file', str(excel_path), ] - - # Add project name if provided - if project_name: - cmd_args.extend(["--project-name", project_name]) - print(f"INFO: Running with project name: {project_name}") - - # Add ignore-estop1ok flag if set - if ignore_estop1ok: - cmd_args.append("--ignore-estop1ok") - print("INFO: Running with --ignore-estop1ok flag") + if verbose: + cmd_args.extend(['--log-level', 'DEBUG']) + cmd_args.append(subcmd) + # Note: routine inclusion/exclusion is driven by config; project_name and ignore-estop1ok are configured in JSON - # Run the main program generator script with DPM routines + # Run the unified CLI result = subprocess.run(cmd_args, cwd=routines_dir, capture_output=True, text=True) - print(result.stdout) - if result.stderr: - print("STDERR:", result.stderr) + if verbose: + print(result.stdout) + if result.stderr: + print("[generator stderr]", result.stderr) if result.returncode == 0: - print("SUCCESS: Routines generation completed successfully") return True else: - print("ERROR: Routines generation failed") return False except Exception as e: - print(f"ERROR: Error running Routines Generator: {e}") + if verbose: + print(f"Error: Exception in routine generation: {e}") return False -def run_io_tree_generator(paths: dict, project_name: str) -> bool: - """Run the IO Tree Configuration Generator.""" - print(f"\n=== Step 3: Generating Complete Project L5X ===") +def run_io_tree_generator(paths: dict, project_name: str, safety_only: bool = False, verbose: bool = False) -> bool: + """Run the IO Tree Configuration Generator. + + If safety_only is True, skip this step to avoid generating non-safety routines. + """ + if safety_only: + return True io_tree_dir = paths['io_tree_generator'] enhanced_mcm_script = io_tree_dir / "enhanced_mcm_generator.py" @@ -147,34 +147,10 @@ def run_io_tree_generator(paths: dict, project_name: str) -> bool: desc_ip_file = paths['data_generator'] / "DESC_IP_MERGED.xlsx" if not enhanced_mcm_script.exists(): - print(f"ERROR: enhanced_mcm_generator.py not found at {enhanced_mcm_script}") return False - # Load zones configuration for the project + # Zones fully removed: do not attempt to load or pass zones zones_json = None - if project_name: - try: - import sys - sys.path.append(str(paths['project_root'])) - from zones_config import ZONES_CONFIGS, DEFAULT_ZONES - - # Determine project type from project name (MCM01, MCM04, MCM05, etc.) - if 'MCM01' in project_name.upper(): - zones_to_use = ZONES_CONFIGS.get("MCM01", DEFAULT_ZONES) - elif 'MCM04' in project_name.upper(): - zones_to_use = ZONES_CONFIGS.get("MCM04", DEFAULT_ZONES) - elif 'MCM05' in project_name.upper(): - zones_to_use = ZONES_CONFIGS.get("MCM05", DEFAULT_ZONES) - else: - zones_to_use = DEFAULT_ZONES - - import json - zones_json = json.dumps(zones_to_use) - print(f"Using zones configuration for IO Tree Generator: {project_name}") - - except Exception as e: - print(f"WARNING: Could not load zones configuration for IO Tree: {e}") - print("Proceeding without zones...") try: # Build command arguments @@ -185,56 +161,70 @@ def run_io_tree_generator(paths: dict, project_name: str) -> bool: project_name ] - # Add zones if available - if zones_json: - cmd_args.extend(["--zones", zones_json]) + # Zones removed; no additional args # Run the IO Tree Configuration Generator result = subprocess.run(cmd_args, cwd=io_tree_dir, capture_output=True, text=True) - print(result.stdout) - if result.stderr: - print("STDERR:", result.stderr) + if verbose: + print(result.stdout) + if result.stderr: + print("[io-tree stderr]", result.stderr) if result.returncode == 0: - print("SUCCESS: Complete project L5X generated successfully") return True else: - print("ERROR: IO Tree generation failed") return False except Exception as e: - print(f"ERROR: Error running IO Tree Generator: {e}") + if verbose: + print(f"Error: Exception in IO tree generation: {e}") return False -def run_l5x_to_acd_compiler(paths: dict, project_name: str) -> bool: - """Prepare for L5X2ACD Compilation using dynamic compilation manager.""" - print(f"\n=== Step 4: Preparing for L5X to ACD Compilation ===") +def run_l5x_to_acd_compiler(paths: dict, project_name: str, safety_only: bool = False, verbose: bool = False) -> bool: + """Prepare for L5X2ACD Compilation using dynamic compilation manager. + + If safety_only is True, skip this step since a full project L5X wasn't generated. + """ + if safety_only: + return True # Find the generated complete project L5X file io_tree_dir = paths['io_tree_generator'] generated_projects_dir = io_tree_dir / "generated_projects" if not generated_projects_dir.exists(): - print(f"ERROR: Generated projects directory not found at {generated_projects_dir}") return False # Look for L5X files that start with the project name l5x_files = list(generated_projects_dir.glob(f"{project_name}*.L5X")) if not l5x_files: - print(f"ERROR: No L5X files found starting with '{project_name}' in {generated_projects_dir}") - available_files = list(generated_projects_dir.glob("*.L5X")) - if available_files: - print(f"Available L5X files: {[f.name for f in available_files]}") + if verbose: + available_files = list(generated_projects_dir.glob("*.L5X")) + if available_files: + print(f"Available L5X files: {[f.name for f in available_files]}") return False - if len(l5x_files) > 1: - print(f"WARNING: Multiple L5X files found: {[f.name for f in l5x_files]}") - print(f"Using the first one: {l5x_files[0].name}") + if len(l5x_files) > 1 and verbose: + print(f"Warning: Multiple L5X files found, using first: {l5x_files[0].name}") complete_l5x = l5x_files[0] - print(f"Found generated L5X file: {complete_l5x.name}") + if verbose: + print(f"Found generated L5X file: {complete_l5x.name}") + + # Inject SafetyTagMap from SafetyTagMapping.txt before compilation (if available) + try: + mapping_file = paths['routines_generator'] / 'SafetyTagMapping.txt' + if mapping_file.exists(): + if verbose: + print("Injecting SafetyTagMap from SafetyTagMapping.txt into L5X ...") + _inject_safety_tag_map_into_l5x(complete_l5x, mapping_file, verbose) + elif verbose: + print("SafetyTagMapping.txt not found; skipping SafetyTagMap injection") + except Exception as e: + if verbose: + print(f"Warning: Failed to inject SafetyTagMap: {e}") # Use the dynamic compilation manager l5x2acd_dir = paths['project_root'] / "L5X2ACD Compiler" @@ -260,40 +250,121 @@ def run_l5x_to_acd_compiler(paths: dict, project_name: str) -> bool: project_type = "MCM04" options['enable_feeder_optimization'] = True - print(f"Using dynamic compilation setup for project type: {project_type}") + if verbose: + print(f"- Project type: {project_type}") # Setup compilation with wipe and dynamic generation - result = manager.setup_compilation( - source_l5x=complete_l5x, - project_name=project_name or complete_l5x.stem, - compilation_options=options, - wipe_existing=True - ) + if verbose: + result = manager.setup_compilation( + source_l5x=complete_l5x, + project_name=project_name or complete_l5x.stem, + compilation_options=options, + wipe_existing=True + ) + else: + _buf = io.StringIO() + with contextlib.redirect_stdout(_buf), contextlib.redirect_stderr(_buf): + result = manager.setup_compilation( + source_l5x=complete_l5x, + project_name=project_name or complete_l5x.stem, + compilation_options=options, + wipe_existing=True + ) - print("SUCCESS: Dynamic compilation setup completed") - print("=" * 60) - print("⚠️ COMPILATION STEP REQUIRES WINDOWS:") - print(f" L5X File: {result['l5x_file']}") - print(f" Batch File: {result['batch_file']}") - print() - print("🪟 To compile on Windows:") - print(f" 1. Run: {result['batch_file']}") - print(" 2. Or double-click: {result['batch_file'].name}") - print(" 3. Or manually run:") - l5x2acd_windows_path = str(l5x2acd_dir).replace('/mnt/c/', 'C:\\').replace('/', '\\') - l5x_windows_path = str(result['l5x_file']).replace('/mnt/c/', 'C:\\').replace('/', '\\') - print(f" cd \"{l5x2acd_windows_path}\"") - print(f" python l5x_to_acd.py \"{l5x_windows_path}\"") - print("=" * 60) + if verbose: + print("OK: Compilation setup completed") + l5x2acd_windows_path = str(l5x2acd_dir).replace('/mnt/c/', 'C:\\').replace('/', '\\') + l5x_windows_path = str(result['l5x_file']).replace('/mnt/c/', 'C:\\').replace('/', '\\') + print("To compile on Windows:") + print(f"- cd \"{l5x2acd_windows_path}\"") + print(f"- python l5x_to_acd.py \"{l5x_windows_path}\"") return True except Exception as e: - print(f"ERROR: Failed to setup dynamic compilation: {e}") - import traceback - traceback.print_exc() + if verbose: + print(f"Error: Exception in compilation setup: {e}") + import traceback + traceback.print_exc() return False + +def _inject_safety_tag_map_into_l5x(l5x_path: Path, mapping_file: Path, verbose: bool = False) -> None: + """Inject or replace inside the existing Controller/SafetyInfo using text edits. + + - Preserves the original XML header exactly + - Does not create additional SafetyInfo blocks + - Formats SafetyTagMap on its own line between SafetyInfo open/close tags + """ + mapping_text = mapping_file.read_text(encoding='utf-8').strip() + if not mapping_text: + if verbose: + print("SafetyTagMapping.txt is empty; skipping injection") + return + + xml_text = l5x_path.read_text(encoding='utf-8') + + # Find Controller block + ctrl_match = re.search(r"", xml_text) + if not ctrl_match: + if verbose: + print("No found; skipping injection") + return + ctrl_start, ctrl_end = ctrl_match.span() + ctrl_text = xml_text[ctrl_start:ctrl_end] + + # Locate first SafetyInfo (body or self-closing) + m_body = re.search(r"]*)>([\s\S]*?)", ctrl_text) + m_self = re.search(r"]*)/>", ctrl_text) + + if not m_body and not m_self: + if verbose: + print("No under ; skipping injection") + return + + # Determine indentation based on the SafetyInfo line + first_match = m_body if (m_body and (not m_self or m_body.start() < m_self.start())) else m_self + safety_start = first_match.start() + line_start = ctrl_text.rfind('\n', 0, safety_start) + indent = ctrl_text[line_start+1:safety_start] if line_start != -1 else '' + map_line = f"\n{indent} {mapping_text} \n" + + def dedup_safety_infos(text: str) -> str: + seen = False + def repl(match: re.Match) -> str: + nonlocal seen + if seen: + return '' + seen = True + return match.group(0) + pat = re.compile(r"(]*/>)|(\n?\s*]*>[\s\S]*?)") + return pat.sub(repl, text) + + if m_body and (not m_self or m_body.start() < m_self.start()): + # Replace or insert SafetyTagMap inside existing body + attrs = m_body.group(1) + inner = m_body.group(2) + # Replace existing map if present + if re.search(r"[\s\S]*?", inner): + new_inner = re.sub(r"[\s\S]*?", map_line.strip('\n'), inner, count=1) + # Remove any additional maps + new_inner = re.sub(r"[\s\S]*?", '', new_inner) + else: + new_inner = map_line + inner + new_block = f"{new_inner}" + new_ctrl_text = ctrl_text[:m_body.start()] + new_block + ctrl_text[m_body.end():] + else: + # Convert self-closing to body with map + attrs = m_self.group(1) + new_block = f"{map_line}" + new_ctrl_text = ctrl_text[:m_self.start()] + new_block + ctrl_text[m_self.end():] + + new_ctrl_text = dedup_safety_infos(new_ctrl_text) + new_xml = xml_text[:ctrl_start] + new_ctrl_text + xml_text[ctrl_end:] + l5x_path.write_text(new_xml, encoding='utf-8') + if verbose: + print("SafetyTagMap injection OK") + def main() -> None: """Main entry point for complete workflow.""" @@ -301,44 +372,61 @@ def main() -> None: parser.add_argument('--excel-file', type=Path, required=True, help='Raw Excel file to process') parser.add_argument('--project-name', help='Project name (for compatibility)') parser.add_argument('--ignore-estop1ok', action='store_true', help='Ignore ESTOP1OK tags in safety routines generation') + parser.add_argument('--safety-only', action='store_true', help='Generate only safety routines and safety checks') + parser.add_argument('--verbose', action='store_true', help='Print detailed logs for each step') args = parser.parse_args() # Get project paths paths = get_project_paths() - print("Complete PLC Generation Workflow") - print("=" * 60) - print(f"Project Root: {paths['project_root']}") - print(f"Input Excel: {args.excel_file}") - print(f"Project: {args.project_name}") - print() + print("PLC Generation Workflow") # Step 1: Process raw Excel data - if not run_plc_data_generator(args.excel_file, paths): - print("ERROR: Workflow failed at Step 1 (Data Processing)") + print("Step 1: Data processing ...", end=" ") + ok = run_plc_data_generator(args.excel_file, paths, verbose=args.verbose) + print("OK" if ok else "FAIL") + if not ok: + if not args.verbose: + print("(details suppressed; re-run with --verbose)") sys.exit(1) # Step 2: Generate L5X programs (Routines Generator) - if not run_routines_generator(paths, args.project_name, args.ignore_estop1ok): - print("ERROR: Workflow failed at Step 2 (Routines Generation)") + print("Step 2: Routine generation ...", end=" ") + ok = run_routines_generator(paths, args.project_name, args.ignore_estop1ok, args.safety_only, verbose=args.verbose) + print("OK" if ok else "FAIL") + if not ok: + if not args.verbose: + print("(details suppressed; re-run with --verbose)") sys.exit(1) # Step 3: Generate complete project L5X (IO Tree Generator) - if not run_io_tree_generator(paths, args.project_name): - print("ERROR: Workflow failed at Step 3 (IO Tree Generation)") - sys.exit(1) + if args.safety_only: + print("Step 3: IO tree generation ... SKIPPED") + else: + print("Step 3: IO tree generation ...", end=" ") + ok = run_io_tree_generator(paths, args.project_name, args.safety_only, verbose=args.verbose) + print("OK" if ok else "FAIL") + if not ok: + if not args.verbose: + print("(details suppressed; re-run with --verbose)") + sys.exit(1) # Step 4: Compile L5X to ACD - if not run_l5x_to_acd_compiler(paths, args.project_name): - print("ERROR: Workflow failed at Step 4 (L5X2ACD Compilation)") - sys.exit(1) + if args.safety_only: + print("Step 4: Prepare compilation ... SKIPPED") + else: + print("Step 4: Prepare compilation ...", end=" ") + ok = run_l5x_to_acd_compiler(paths, args.project_name, args.safety_only, verbose=args.verbose) + print("OK" if ok else "FAIL") + if not ok: + if not args.verbose: + print("(details suppressed; re-run with --verbose)") + sys.exit(1) - print("\n" + "="*60) - print("SUCCESS: Complete PLC Generation Workflow completed!") - print(f"Generated L5X file: IO Tree Configuration Generator/generated_projects/{args.project_name}.L5X") - print("🪟 Run the generated batch file on Windows to compile to ACD") - print("="*60) + print("Workflow complete") + if args.verbose and not args.safety_only and args.project_name: + print(f"L5X: IO Tree Configuration Generator/generated_projects/{args.project_name}.L5X") if __name__ == '__main__': main() \ No newline at end of file diff --git a/Routines Generator/generate_all.py b/Routines Generator/generate_all.py index 3aefb09..1b29fa8 100644 --- a/Routines Generator/generate_all.py +++ b/Routines Generator/generate_all.py @@ -1,170 +1,8 @@ -#!/usr/bin/env python3 -"""generate_all.py – build SafetyProgram L5X, MainProgram L5X, full tag CSV, and safety-tag mapping. - -This is the updated version using the new configuration and logging systems. -Run from repository root: - python generate_all.py [options] -""" -from __future__ import annotations - -from pathlib import Path -import sys -import argparse - -# If repo root is current dir, add nested "src" folder to sys.path -pkg_dir = Path(__file__).resolve().parent / "src" -if pkg_dir.is_dir(): - sys.path.insert(0, str(pkg_dir)) - -# Import new configuration and logging systems -from src.config import GeneratorConfig, get_config, set_config -from src.logging_config import setup_logging, get_logger, FileOperationContext - -# Import existing generators and writers (these will be updated in later phases) -from src.generators.main_program import LimitedMainProgramGenerator as MainProgramGenerator -from src.generators.safety_program import LimitedSafetyProgramGenerator as SafetyProgramGenerator -from src.data_loader import DataLoader -from src.writers.csv_writer import create_limited_csv_with_tags -from src.writers.mapping_writer import create_safety_tag_mapping - -def main() -> None: - parser = argparse.ArgumentParser(description="Generate safety-focused PLC program") - parser.add_argument( - '--config', - type=Path, - default=Path(__file__).parent.parent / 'generator_config.json', - help='Configuration file path' - ) - parser.add_argument( - '--excel-file', - type=Path, - help='Override Excel file path from config' - ) - parser.add_argument( - '--output-dir', - type=Path, - help='Override output directory from config' - ) - parser.add_argument( - '--log-level', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], - default='INFO', - help='Set logging level' - ) - parser.add_argument( - '--log-file', - type=Path, - help='Optional log file path' - ) - parser.add_argument( - '--desc-ip-mode', - action='store_true', - help='Legacy flag - DESC_IP mode is now the default' - ) - args = parser.parse_args() - - # Setup logging first - setup_logging( - level=args.log_level, - log_file=args.log_file, - use_colors=True - ) - logger = get_logger(__name__) - - try: - logger.info("=== Starting PLC Generation Process ===") - - # Load configuration - logger.debug("Loading configuration", config_file=str(args.config)) - config = GeneratorConfig.from_file(args.config) - - # Override config with command line arguments - if args.excel_file: - config.files.excel_file = args.excel_file - logger.debug("Overridden Excel file", file_path=str(args.excel_file)) - - if args.output_dir: - config.files.output_dir = args.output_dir - logger.debug("Overridden output directory", output_dir=str(args.output_dir)) - - # Set global config - set_config(config) - - # Validate Excel file exists - if not config.files.excel_file.exists(): - logger.error("Excel file not found", file_path=str(config.files.excel_file)) - sys.exit(1) - - logger.info("Configuration loaded", - excel_file=str(config.files.excel_file), - controller_name=config.xml.controller_name) - - # Show deprecation warning for desc-ip-mode flag - if args.desc_ip_mode: - logger.warning("--desc-ip-mode flag is deprecated. DESC_IP extraction is now the default mode.") - - # Safety program generation - with FileOperationContext(logger, "SafetyProgram generation", config.files.safety_l5x): - safety_gen = SafetyProgramGenerator(config.files.excel_file) - safety_gen.write(config.files.safety_l5x) - logger.info("SafetyProgram generation completed", output_file=config.files.safety_l5x) - - # Main program generation - logger.info("Starting MainProgram generation", stage="main_generation") - with FileOperationContext(logger, "MainProgram generation", config.files.main_l5x): - main_gen = MainProgramGenerator(config.files.excel_file) - main_gen.write(config.files.main_l5x) - logger.info("MainProgram generation completed", output_file=config.files.main_l5x) - - # Generate CSV tags - logger.progress("csv_generation", "Starting CSV tags generation") - if not config.files.original_csv.exists(): - logger.warning("Original CSV file not found, skipping CSV generation", - file_path=str(config.files.original_csv)) - else: - with FileOperationContext(logger, "CSV generation", config.files.complete_csv): - beacon_so_tags = create_limited_csv_with_tags( - config.files.excel_file, - config.files.original_csv, - config.files.complete_csv - ) - logger.info("CSV generation completed", - output_file=config.files.complete_csv, - beacon_tags=len(beacon_so_tags) if beacon_so_tags else 0) - - # Generate Safety Tag Mapping - logger.progress("mapping_generation", "Starting safety tag mapping generation") - with FileOperationContext(logger, "Safety mapping generation", config.files.mapping_txt): - # Load data for safety tag extraction - loader = DataLoader.from_excel(config.files.excel_file) - safety_tags = loader.safety_tags_from_pb - - logger.data_stats("safety_tags", len(safety_tags)) - - create_safety_tag_mapping( - safety_tags, - set(), # No beacon SO tags in limited mode - set(), # No beacon SFT tags in limited mode - config.files.mapping_txt - ) - - logger.info("Safety tag mapping completed", - output_file=config.files.mapping_txt, - safety_tag_count=len(safety_tags)) - - logger.info("=== PLC Generation Process Completed Successfully ===") - logger.info("Generated files:", - safety_l5x=config.files.safety_l5x, - main_l5x=config.files.main_l5x, - csv_file=config.files.complete_csv, - mapping_file=config.files.mapping_txt) - - except Exception as e: - logger.error("Generation process failed", exception=str(e)) - if args.log_level == 'DEBUG': - logger.exception("Full traceback") - sys.exit(1) - - -if __name__ == '__main__': - main() \ No newline at end of file +#!/usr/bin/env python3 +""" +This script is deprecated. Use the unified CLI: + python -m src.unified_cli --config ../generator_config.json --excel-file DESC_IP_MERGED.xlsx all +""" +import sys +print("generate_all.py is deprecated. Please use the unified CLI (src.unified_cli).", file=sys.stderr) +sys.exit(1) \ No newline at end of file diff --git a/Routines Generator/generate_fiom.py b/Routines Generator/generate_fiom.py deleted file mode 100644 index 4c78ac9..0000000 --- a/Routines Generator/generate_fiom.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -"""generate_fiom.py – FIOM routine generation (DEPRECATED). - -This script is no longer functional after the removal of legacy routines. -FIOM routine generation was part of the legacy system that has been removed -in favor of safety-focused generation. - -For safety-only generation, use: - python generate_safety_only.py -""" -from __future__ import annotations - -from pathlib import Path -import sys - -print("ERROR: FIOM Generation No Longer Available") -print("=" * 50) -print("The FIOM routine generation feature has been removed as part of the") -print("legacy system cleanup. Only safety-focused routines are now supported.") -print() -print("Available alternatives:") -print(" • Safety-only generation: python generate_safety_only.py") -print(" • Full safety generation: python generate_all.py") -print() -print("These scripts generate:") -print(" • Safety routines (inputs, outputs, estops, zones, resets)") -print(" • Safety tag mappings") -print(" • Essential main program routines (safety_tag_map, estop_check)") -print() -print("If you need FIOM functionality, you would need to:") -print("1. Recreate the FIOM routine generator") -print("2. Add it to the src/routines/ directory") -print("3. Update the generators to include it") - -sys.exit(1) \ No newline at end of file diff --git a/Routines Generator/generate_main_with_dpm.py b/Routines Generator/generate_main_with_dpm.py deleted file mode 100644 index f61081b..0000000 --- a/Routines Generator/generate_main_with_dpm.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate MainProgram with DPM routines from DESC_IP data. - -This script creates a complete MainProgram.L5X file with: -- R000_SAFETY_TAG_MAP routine -- R020_DPM routine (with AOI_DPM calls) -- R100_ESTOP_CHECK routine -- Complete controller tags - -Usage: - python generate_main_with_dpm.py --desc-ip-mode - python generate_main_with_dpm.py --desc-ip-mode --project-name MTN6_MCM05_CHUTE_LOAD -""" - -from __future__ import annotations - -import argparse -import sys -from pathlib import Path - -# If repo root is current dir, add nested "src" folder to sys.path -pkg_dir = Path(__file__).resolve().parent / "src" -if pkg_dir.is_dir(): - sys.path.insert(0, str(pkg_dir)) - -from src.generators import FullMainProgramGenerator, LimitedSafetyProgramGenerator - - -def parse_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Generate MainProgram L5X with DPM routines from DESC_IP data" - ) - - parser.add_argument( - '--desc-ip-mode', - action='store_true', - help='Use DESC_IP extraction mode (default: true for this script)' - ) - - parser.add_argument( - '--project-name', - type=str, - help='Project name for output files (e.g., MTN6_MCM05_CHUTE_LOAD)' - ) - - parser.add_argument( - '--ignore-estop1ok', - action='store_true', - help='Ignore ESTOP1_OK validation errors' - ) - - return parser.parse_args() - - -def main(): - """Generate MainProgram and SafetyProgram L5X files with DPM routines.""" - args = parse_args() - - print("=== MainProgram Generator with DPM Routines ===") - print("Using DESC_IP extraction mode for device data") - - # Use DESC_IP_MERGED.xlsx from current directory - excel_file = Path("DESC_IP_MERGED.xlsx") - - if not excel_file.exists(): - print(f"ERROR: {excel_file} not found in current directory") - print("Please ensure DESC_IP_MERGED.xlsx is available") - sys.exit(1) - - print(f"Using Excel file: {excel_file}") - - # Load zones configuration based on project type - zones_dict = None - try: - # Add parent directory to sys.path to find zones_config.py - sys.path.insert(0, str(Path(__file__).parent.parent)) - from zones_config import ZONES_CONFIGS, DEFAULT_ZONES - - # Detect project type from project name argument - import re - - if args.project_name: - mcm_match = re.search(r"(MCM\d+)", args.project_name, re.IGNORECASE) - if mcm_match: - mcm_type = mcm_match.group(1).upper() - zones_dict = ZONES_CONFIGS.get(mcm_type, DEFAULT_ZONES) - print(f"Detected {mcm_type} project from '{args.project_name}', loaded {len(zones_dict)} zones from zones_config.py") - else: - zones_dict = DEFAULT_ZONES - print(f"No MCM type detected in '{args.project_name}', loaded {len(zones_dict)} default zones from zones_config.py") - else: - zones_dict = DEFAULT_ZONES - print(f"No project name provided, loaded {len(zones_dict)} default zones from zones_config.py") - except ImportError as e: - print(f"Warning: Could not load zones_config.py: {e}") - print("Proceeding without zones configuration...") - - try: - # Generate MainProgram with DPM routines - print("\n--- Generating MainProgram L5X ---") - main_generator = FullMainProgramGenerator(excel_path=excel_file, zones_dict=zones_dict) - - # Determine output filename - if args.project_name: - main_output = f"MainProgram_{args.project_name}.L5X" - safety_output = f"SafetyProgram_{args.project_name}.L5X" - else: - main_output = "MainProgram_Generated.L5X" - safety_output = "SafetyProgram_Generated.L5X" - - main_generator.write(main_output) - print(f"SUCCESS: MainProgram written to {main_output}") - - # Generate SafetyProgram - print("\n--- Generating SafetyProgram L5X ---") - safety_generator = LimitedSafetyProgramGenerator(excel_path=excel_file, zones_dict=zones_dict, ignore_estop1ok=args.ignore_estop1ok) - safety_generator.write(safety_output) - print(f"SUCCESS: SafetyProgram written to {safety_output}") - - print("\n=== Generation Complete ===") - print(f"Generated files:") - print(f" • {main_output}") - print(f" • {safety_output}") - - # Summary of generated content - print(f"\nMainProgram includes:") - print(f" • R000_SAFETY_TAG_MAP routine") - print(f" • R020_DPM routine with AOI_DPM calls") - print(f" • R100_ESTOP_CHECK routine") - print(f" • Complete controller tags") - - except Exception as e: - print(f"ERROR: Generation failed: {e}") - import traceback - traceback.print_exc() - sys.exit(1) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/Routines Generator/generate_safety_only.py b/Routines Generator/generate_safety_only.py deleted file mode 100644 index a3925ff..0000000 --- a/Routines Generator/generate_safety_only.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -"""generate_safety_only.py – build limited SafetyProgram and MainProgram L5X with essential safety routines only. - -This mode only generates: -- Safety routines: inputs, outputs, estops, zones, resets -- Main routines: safety_tag_map, estop_check (safety checkout) -- Controller tags embedded in MainProgram L5X -- Safety tag mapping - -Extracts safety data (RST, STO, EPC) from DESC_IP sheet and uses zones_config.py for zone configuration. - -Run from repository root: - python generate_safety_only.py [--desc-ip-mode] -The outputs are written to *_Limited.* filenames in the current directory. -""" -from __future__ import annotations - -from pathlib import Path -import sys -import argparse - -# If repo root is current dir, add nested "src" folder to sys.path -pkg_dir = Path(__file__).resolve().parent / "src" -if pkg_dir.is_dir(): - sys.path.insert(0, str(pkg_dir)) - -from src.generators import LimitedSafetyProgramGenerator, LimitedMainProgramGenerator -from src.data_loader import DataLoader -from src.writers.mapping_writer import create_safety_tag_mapping - -EXCEL_FILE = Path('MCM01_UL1_UL3.xlsx') -DESC_IP_FILE = Path('DESC_IP_MERGED.xlsx') - -SAFETY_L5X = 'SafetyProgram_Limited.L5X' -MAIN_L5X = 'MainProgram_Limited.L5X' -MAPPING_TXT = 'SafetyTagMapping_Limited.txt' - - -def main() -> None: - parser = argparse.ArgumentParser(description="Generate safety-only routines") - parser.add_argument('--desc-ip-mode', action='store_true', - help='Extract safety devices from DESC_IP data instead of separate sheets') - parser.add_argument('--ignore-estop1ok', action='store_true', - help='Ignore ESTOP1OK tags in inputs, estops, and zones routines') - parser.add_argument('--project-name', type=str, - help='Project name to detect MCM type for zones configuration') - args = parser.parse_args() - - print("Safety-Only Mode") - print("================") - - # Determine which Excel file to use - excel_file = DESC_IP_FILE if args.desc_ip_mode and DESC_IP_FILE.exists() else EXCEL_FILE - - if args.desc_ip_mode: - print("Using DESC_IP data extraction mode") - print(f"Using Excel file: {excel_file}") - print("Extracting safety devices from DESC_IP sheet using safety rules\n") - else: - print("Using separate safety sheets mode") - print(f"Using Excel file: {excel_file}") - print("Generating essential safety routines using DESC_IP data extraction and zones_config.py\n") - - if args.ignore_estop1ok: - print("INFO: Ignoring ESTOP1OK tags in safety routines generation\n") - - # Load zones configuration based on project type - zones_dict = None - try: - # Add parent directory to sys.path to find zones_config.py - sys.path.insert(0, str(Path(__file__).parent.parent)) - from zones_config import ZONES_CONFIGS, DEFAULT_ZONES - - # Detect project type from project name argument - import re - - if args.project_name: - mcm_match = re.search(r"(MCM\d+)", args.project_name, re.IGNORECASE) - if mcm_match: - mcm_type = mcm_match.group(1).upper() - zones_dict = ZONES_CONFIGS.get(mcm_type, DEFAULT_ZONES) - print(f"Detected {mcm_type} project from '{args.project_name}', loaded {len(zones_dict)} zones from zones_config.py") - else: - zones_dict = DEFAULT_ZONES - print(f"No MCM type detected in '{args.project_name}', loaded {len(zones_dict)} default zones from zones_config.py") - else: - zones_dict = DEFAULT_ZONES - print(f"No project name provided, loaded {len(zones_dict)} default zones from zones_config.py") - except ImportError as e: - print(f"Warning: Could not load zones_config.py: {e}") - print("Proceeding without zones configuration...") - - # Limited Safety program (inputs, outputs, estops, zones, resets) - print("1. Generating SafetyProgram...") - LimitedSafetyProgramGenerator(excel_file, zones_dict=zones_dict, ignore_estop1ok=args.ignore_estop1ok).write(SAFETY_L5X) - print(f'[SUCCESS] Wrote {SAFETY_L5X}') - print(' - Contains: inputs, outputs, estops, zones, resets routines') - - # Limited Main program (safety_tag_map, estop_check) with embedded controller tags - print("\n2. Generating MainProgram with embedded controller tags...") - LimitedMainProgramGenerator(excel_file, zones_dict=zones_dict).write(MAIN_L5X) - print(f'[SUCCESS] Wrote {MAIN_L5X}') - print(' - Contains: safety_tag_map, estop_check routines') - print(' - Contains: Controller-level tags embedded in L5X') - - # Safety tag mapping - print("\n3. Generating safety tag mapping...") - loader = DataLoader(excel_file, zones_dict=zones_dict) - - # Collect safety tags from appropriate source - safety_tags = loader.safety_tags_from_pb - - # For limited mode, we don't have beacon tags, so pass empty sets - create_safety_tag_mapping(safety_tags, set(), set(), MAPPING_TXT) - print(f'[SUCCESS] Wrote {MAPPING_TXT}') - print(f' - Contains: {len(safety_tags)} safety tags ({len(safety_tags) - 1} push buttons + MCM)') - - print(f"\nSafety-only generation complete!") - - if args.desc_ip_mode: - print(f"Data sources: DESC_IP sheet with safety device extraction") - print(f" - RST devices: DESCA contains 'START' but NOT 'LIGHT'") - print(f" - STO devices: TAGNAME contains 'VFD' or DESCA contains 'STO'") - print(f" - EPC devices: DESCA contains 'EPC' or 'ESTOP'") - else: - print(f"Data sources: DESC_IP extraction (RST, STO, EPC) + zones_config.py") - - print(f"Main routines: safety_tag_map, estop_check") - print(f"Output files:") - print(f" - {SAFETY_L5X}") - print(f" - {MAIN_L5X} (with embedded controller tags)") - print(f" - {MAPPING_TXT}") - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/Routines Generator/plc_generate.py b/Routines Generator/plc_generate.py index 8b03bb2..d0ca1a8 100644 --- a/Routines Generator/plc_generate.py +++ b/Routines Generator/plc_generate.py @@ -68,7 +68,7 @@ def main() -> None: parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default='INFO', help='Log level') parser.add_argument('--log-file', type=Path, help='Log file path') parser.add_argument('--project-name', help='Project name (for compatibility, not used)') - parser.add_argument('--zones', help='JSON string with zones data') + # Zones option removed # New compilation option parser.add_argument('--compile-acd', action='store_true', help='Compile L5X files to ACD after generation') @@ -90,8 +90,7 @@ def main() -> None: if args.excel_file: unified_args.extend(['--excel-file', str(args.excel_file)]) - if args.zones: - unified_args.extend(['--zones', args.zones]) + # Zones option removed # Add the 'all' command unified_args.append('all') diff --git a/Routines Generator/src/__pycache__/base_generator.cpython-312.pyc b/Routines Generator/src/__pycache__/base_generator.cpython-312.pyc new file mode 100644 index 0000000..464229d Binary files /dev/null and b/Routines Generator/src/__pycache__/base_generator.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/cli.cpython-312.pyc b/Routines Generator/src/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000..f226467 Binary files /dev/null and b/Routines Generator/src/__pycache__/cli.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/config.cpython-312.pyc b/Routines Generator/src/__pycache__/config.cpython-312.pyc index 9de0df0..506a9bf 100644 Binary files a/Routines Generator/src/__pycache__/config.cpython-312.pyc and b/Routines Generator/src/__pycache__/config.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/container.cpython-312.pyc b/Routines Generator/src/__pycache__/container.cpython-312.pyc index d67f2f0..2a9564d 100644 Binary files a/Routines Generator/src/__pycache__/container.cpython-312.pyc and b/Routines Generator/src/__pycache__/container.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/data_loader.cpython-312.pyc b/Routines Generator/src/__pycache__/data_loader.cpython-312.pyc index 5058a18..bd311da 100644 Binary files a/Routines Generator/src/__pycache__/data_loader.cpython-312.pyc and b/Routines Generator/src/__pycache__/data_loader.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/plugin_system.cpython-312.pyc b/Routines Generator/src/__pycache__/plugin_system.cpython-312.pyc index 28b4d08..e2beb18 100644 Binary files a/Routines Generator/src/__pycache__/plugin_system.cpython-312.pyc and b/Routines Generator/src/__pycache__/plugin_system.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/unified_cli.cpython-312.pyc b/Routines Generator/src/__pycache__/unified_cli.cpython-312.pyc index 8927014..3256f7b 100644 Binary files a/Routines Generator/src/__pycache__/unified_cli.cpython-312.pyc and b/Routines Generator/src/__pycache__/unified_cli.cpython-312.pyc differ diff --git a/Routines Generator/src/__pycache__/xml_builder.cpython-312.pyc b/Routines Generator/src/__pycache__/xml_builder.cpython-312.pyc new file mode 100644 index 0000000..c43a7c0 Binary files /dev/null and b/Routines Generator/src/__pycache__/xml_builder.cpython-312.pyc differ diff --git a/Routines Generator/src/base_generator.py b/Routines Generator/src/base_generator.py index 07ffd03..65c7e7e 100644 --- a/Routines Generator/src/base_generator.py +++ b/Routines Generator/src/base_generator.py @@ -1,251 +1,310 @@ -"""Base Generator Classes using Template Method Pattern. - -Provides structured, extensible base classes for PLC program generation -using XML Builder and Plugin systems. -""" - -from __future__ import annotations - -import xml.etree.ElementTree as ET -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Optional, List, Dict, Any - -from .config import GeneratorConfig -from .data_loader import DataLoader -from .xml_builder import L5XBuilder, L5XBuilderFactory -from .plugin_system import RoutineManager, RoutineContext -from .logging_config import get_logger - -class BaseGenerator(ABC): - """Base class implementing the Template Method pattern for PLC generation.""" - - def __init__(self, config: GeneratorConfig, data_loader: DataLoader, - xml_builder_factory: L5XBuilderFactory): - self.config = config - self.data_loader = data_loader - self.xml_builder_factory = xml_builder_factory - self.logger = get_logger(self.__class__.__name__) - - # Will be set during generation - self.builder: Optional[L5XBuilder] = None - self.routine_manager: Optional[RoutineManager] = None - - def generate(self) -> ET.Element: - """Template method for generating PLC programs. - - This defines the algorithm structure while allowing subclasses - to customize specific steps. - """ - self.logger.info(f"Starting {self.__class__.__name__} generation") - - try: - # Step 1: Create XML structure - self.builder = self.create_xml_structure() - - # Step 2: Add controller-level elements - self.add_controller_elements() - - # Step 3: Create routine context and manager - self.setup_routine_manager() - - # Step 4: Generate routines - self.generate_routines() - - # Step 5: Add program-level elements - self.add_program_elements() - - # Step 6: Finalize - self.finalize_generation() - - xml_tree = self.builder.build() - self.logger.info(f"Successfully completed {self.__class__.__name__} generation") - return xml_tree - - except Exception as e: - self.logger.error(f"Failed to generate {self.__class__.__name__}: {e}") - raise - - @abstractmethod - def create_xml_structure(self) -> L5XBuilder: - """Create the basic XML structure for this program type.""" - pass - - def add_controller_elements(self) -> None: - """Add controller-level elements. Override if needed.""" - pass - - def setup_routine_manager(self) -> None: - """Set up the routine manager with proper context.""" - if not self.builder: - raise ValueError("XML builder must be created first") - - context = RoutineContext( - data_loader=self.data_loader, - config=self.config, - routines_element=self.builder.get_routines_section(), - program_element=self.builder.get_program_element(), - metadata=self.get_context_metadata() - ) - - from .plugin_system import RoutineManager, get_default_registry - self.routine_manager = RoutineManager(context, get_default_registry()) - - @abstractmethod - def generate_routines(self) -> None: - """Generate the routines for this program type.""" - pass - - def add_program_elements(self) -> None: - """Add program-level elements. Override if needed.""" - pass - - def finalize_generation(self) -> None: - """Finalize the generation process. Override if needed.""" - pass - - def get_context_metadata(self) -> Dict[str, Any]: - """Get metadata for routine context. Override to add custom metadata.""" - return { - 'generator_type': self.__class__.__name__, - 'config': self.config, - 'excel_file': str(self.data_loader.excel_path) - } - - def build_xml_string(self) -> str: - """Build and return the formatted XML string.""" - if not self.builder: - raise ValueError("Generation must be completed first") - return self.builder.build_xml_string() - - def write(self, output_file: str | Path) -> None: - """Write the generated XML to a file.""" - if not self.builder: - # Generate if not already done - self.generate() - self.builder.write_to_file(output_file) - self.logger.info(f"Written {self.__class__.__name__} to {output_file}") - -class SafetyProgramGenerator(BaseGenerator): - """Template Method implementation for SafetyProgram generation.""" - - def create_xml_structure(self) -> L5XBuilder: - """Create SafetyProgram XML structure.""" - self.logger.debug("Creating SafetyProgram XML structure") - return self.xml_builder_factory.create_safety_program_builder("SafetyProgram") - - def generate_routines(self) -> None: - """Generate safety routines.""" - if not self.routine_manager: - raise ValueError("Routine manager must be set up first") - - self.logger.info("Generating safety routines...") - - # Generate safety-specific routines - safety_routines = [ - 'inputs', - 'outputs', - 'resets', - 'estops', - 'zones' - ] - - results = {} - for routine_name in safety_routines: - results[routine_name] = self.routine_manager.generate_routine(routine_name) - - self.logger.info(f"Safety routine generation results: {results}") - - def add_program_elements(self) -> None: - """Add safety-specific program elements.""" - if not self.builder: - raise ValueError("XML builder must be created first") - - # Add safety signatures - self.builder.add_safety_signatures() - - # Add safety tag map if needed - self._add_safety_tag_map() - - def _add_safety_tag_map(self) -> None: - """Add safety tag map to the program.""" - program_element = self.builder.get_program_element() - - # Get safety tags from data - safety_tags = self.data_loader.safety_tags_from_pb - - if safety_tags: - from .generators.safety_program import create_safety_tag_map - create_safety_tag_map(program_element, safety_tags, set()) - self.logger.debug(f"Added safety tag map with {len(safety_tags)} tags") - -class MainProgramGenerator(BaseGenerator): - """Template Method implementation for MainProgram generation.""" - - def create_xml_structure(self) -> L5XBuilder: - """Create MainProgram XML structure.""" - self.logger.debug("Creating MainProgram XML structure") - return self.xml_builder_factory.create_main_program_builder("MainProgram") - - def add_controller_elements(self) -> None: - """Add controller tags for MainProgram.""" - if not self.builder: - raise ValueError("XML builder must be created first") - - self.logger.debug("Adding controller tags...") - - # Generate and add controller tags - from .writers.xml_tag_writer import create_limited_tag_xml_elements - tag_elements = create_limited_tag_xml_elements( - self.data_loader.excel_path, - data_loader=self.data_loader - ) - - self.builder.add_controller_tags(tag_elements) - self.logger.info(f"Added {len(tag_elements)} controller tags") - - def generate_routines(self) -> None: - """Generate main program routines.""" - if not self.routine_manager: - raise ValueError("Routine manager must be set up first") - - self.logger.info("Generating main program routines...") - - # Generate main program routines - main_routines = [ - 'main_routine', - 'safety_tag_map', - 'estop_check' - ] - - results = {} - for routine_name in main_routines: - results[routine_name] = self.routine_manager.generate_routine(routine_name) - - self.logger.info(f"Main routine generation results: {results}") - -class LegacyCompatibilityMixin: - """Mixin to provide backward compatibility with legacy generators.""" - - def __init__(self, excel_path: str | Path, zones_dict: Optional[List[Dict[str, str]]] = None): - """Legacy constructor interface.""" - # This will be mixed with the new generators to maintain compatibility - from .config import get_config - from .data_loader import DataLoader - from .xml_builder import L5XBuilderFactory - - config = get_config() - data_loader = DataLoader(excel_path=excel_path, zones_dict=zones_dict) - xml_builder_factory = L5XBuilderFactory(config) - - # Call the parent constructor - super().__init__(config, data_loader, xml_builder_factory) - -# Backward compatible generator classes -class ModernSafetyProgramGenerator(LegacyCompatibilityMixin, SafetyProgramGenerator): - """Modern SafetyProgram generator with legacy interface.""" - pass - -class ModernMainProgramGenerator(LegacyCompatibilityMixin, MainProgramGenerator): - """Modern MainProgram generator with legacy interface.""" +"""Base Generator Classes using Template Method Pattern. + +Provides structured, extensible base classes for PLC program generation +using XML Builder and Plugin systems. +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional, List, Dict, Any + +from .config import GeneratorConfig +from .data_loader import DataLoader +from .xml_builder import L5XBuilder, L5XBuilderFactory +from .plugin_system import RoutineManager, RoutineContext +from .logging_config import get_logger + +class BaseGenerator(ABC): + """Base class implementing the Template Method pattern for PLC generation.""" + + def __init__(self, config: GeneratorConfig, data_loader: DataLoader, + xml_builder_factory: L5XBuilderFactory): + self.config = config + self.data_loader = data_loader + self.xml_builder_factory = xml_builder_factory + self.logger = get_logger(self.__class__.__name__) + + # Will be set during generation + self.builder: Optional[L5XBuilder] = None + self.routine_manager: Optional[RoutineManager] = None + + def generate(self) -> ET.Element: + """Template method for generating PLC programs. + + This defines the algorithm structure while allowing subclasses + to customize specific steps. + """ + self.logger.info(f"Starting {self.__class__.__name__} generation") + + try: + # Step 1: Create XML structure + self.builder = self.create_xml_structure() + + # Step 2: Add controller-level elements + self.add_controller_elements() + + # Step 3: Create routine context and manager + self.setup_routine_manager() + + # Step 4: Generate routines + self.generate_routines() + + # Step 5: Add program-level elements + self.add_program_elements() + + # Step 6: Finalize + self.finalize_generation() + + xml_tree = self.builder.build() + self.logger.info(f"Successfully completed {self.__class__.__name__} generation") + return xml_tree + + except Exception as e: + self.logger.error(f"Failed to generate {self.__class__.__name__}: {e}") + raise + + @abstractmethod + def create_xml_structure(self) -> L5XBuilder: + """Create the basic XML structure for this program type.""" + pass + + def add_controller_elements(self) -> None: + """Add controller-level elements. Override if needed.""" + pass + + def setup_routine_manager(self) -> None: + """Set up the routine manager with proper context.""" + if not self.builder: + raise ValueError("XML builder must be created first") + + context = RoutineContext( + data_loader=self.data_loader, + config=self.config, + routines_element=self.builder.get_routines_section(), + program_element=self.builder.get_program_element(), + metadata=self.get_context_metadata() + ) + + from .plugin_system import RoutineManager, get_default_registry + self.routine_manager = RoutineManager(context, get_default_registry()) + + @abstractmethod + def generate_routines(self) -> None: + """Generate the routines for this program type.""" + pass + + def add_program_elements(self) -> None: + """Add program-level elements. Override if needed.""" + pass + + def finalize_generation(self) -> None: + """Finalize the generation process. Override if needed.""" + pass + + def get_context_metadata(self) -> Dict[str, Any]: + """Get metadata for routine context. Override to add custom metadata.""" + return { + 'generator_type': self.__class__.__name__, + 'config': self.config, + 'excel_file': str(self.data_loader.excel_path) + } + + def build_xml_string(self) -> str: + """Build and return the formatted XML string.""" + if not self.builder: + raise ValueError("Generation must be completed first") + return self.builder.build_xml_string() + + def write(self, output_file: str | Path) -> None: + """Write the generated XML to a file.""" + if not self.builder: + # Generate if not already done + self.generate() + self.builder.write_to_file(output_file) + self.logger.info(f"Written {self.__class__.__name__} to {output_file}") + +class SafetyProgramGenerator(BaseGenerator): + """Template Method implementation for SafetyProgram generation.""" + + def create_xml_structure(self) -> L5XBuilder: + """Create SafetyProgram XML structure.""" + self.logger.debug("Creating SafetyProgram XML structure") + return self.xml_builder_factory.create_safety_program_builder("SafetyProgram") + + def generate_routines(self) -> None: + """Generate safety routines.""" + if not self.routine_manager: + raise ValueError("Routine manager must be set up first") + + self.logger.info("Generating safety routines...") + + # If a config-driven routine plan is provided, use only entries targeting SafetyProgram + if getattr(self.config, 'routine_plan', None): + results = {} + for entry in sorted( + [e for e in self.config.routine_plan if e.enabled and e.program == 'SafetyProgram'], + key=lambda e: e.order + ): + # enrich context with per-routine params/filters + self.routine_manager.context.metadata['params'] = entry.params or {} + self.routine_manager.context.metadata['filters'] = self.config.filters.for_routine(entry.name) + results[entry.name] = self.routine_manager.generate_routine(entry.plugin) + self.logger.info(f"Safety routine generation results: {results}") + return + + # Fallback: default fixed set + safety_routines = ['inputs', 'outputs', 'resets', 'estops'] + results = {name: self.routine_manager.generate_routine(name) for name in safety_routines} + self.logger.info(f"Safety routine generation results: {results}") + + def add_program_elements(self) -> None: + """Add safety-specific program elements.""" + if not self.builder: + raise ValueError("XML builder must be created first") + + # Add safety signatures + self.builder.add_safety_signatures() + + # Add safety tag map if needed + self._add_safety_tag_map() + + # Add zones routine from configuration if available + try: + zones_df = self.data_loader.zones + if zones_df is not None: + from .routines.zones import create_zones_routine + create_zones_routine(self.builder.get_routines_section(), zones_df, self.data_loader.epc) + self.logger.info("Added R030_ZONES routine from zones.json") + except Exception: + # Zones are optional; proceed without blocking generation + pass + + # Ensure a MainRoutine exists and references generated safety routines + # The ProgramAttributes set MainRoutineName to config.routines.main_routine_name + # so we must create that routine here. + program_el = self.builder.get_program_element() + routines_el = self.builder.get_routines_section() + + from .config import get_config + cfg_local = get_config() + main_routine_name = cfg_local.routines.main_routine_name + import xml.etree.ElementTree as ET + + # Create MainRoutine only if it does not already exist + if not any(r.get('Name') == main_routine_name for r in routines_el.findall('Routine')): + routine = ET.SubElement(routines_el, 'Routine', Name=main_routine_name, Type='RLL') + rll_content = ET.SubElement(routine, 'RLLContent') + rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') + text = ET.SubElement(rung, 'Text') + nm = cfg_local.routines.name_map + # Ensure ESTOPS before ZONES so MCM_EPB_DCS_CTRL exists before ZONES references .O1 + calls = [ + nm.get('inputs', 'R010_INPUTS'), + nm.get('outputs', 'R011_OUTPUTS'), + nm.get('resets', 'R012_RESETS'), + nm.get('estops', 'R020_ESTOPS'), + nm.get('zones', 'R030_ZONES'), + ] + text.text = '[' + ' ,'.join(f'JSR({c},0)' for c in calls) + ' ];' + + def _add_safety_tag_map(self) -> None: + """Add safety tag map to the program.""" + program_element = self.builder.get_program_element() + + # Get safety tags from data + safety_tags = self.data_loader.safety_tags_from_pb + + if safety_tags: + from .generators.safety_program import create_safety_tag_map + create_safety_tag_map(program_element, safety_tags, set()) + self.logger.debug(f"Added safety tag map with {len(safety_tags)} tags") + +class MainProgramGenerator(BaseGenerator): + """Template Method implementation for MainProgram generation.""" + + def create_xml_structure(self) -> L5XBuilder: + """Create MainProgram XML structure.""" + self.logger.debug("Creating MainProgram XML structure") + return self.xml_builder_factory.create_main_program_builder("MainProgram") + + def add_controller_elements(self) -> None: + """Add controller tags for MainProgram.""" + if not self.builder: + raise ValueError("XML builder must be created first") + + self.logger.debug("Adding controller tags...") + + # Generate and add controller tags + from .writers.xml_tag_writer import create_limited_tag_xml_elements + tag_elements = create_limited_tag_xml_elements( + self.data_loader.excel_path, + data_loader=self.data_loader + ) + + self.builder.add_controller_tags(tag_elements) + self.logger.info(f"Added {len(tag_elements)} controller tags") + + def generate_routines(self) -> None: + """Generate main program routines.""" + if not self.routine_manager: + raise ValueError("Routine manager must be set up first") + + self.logger.info("Generating main program routines...") + + # Config-driven plan if available (MainProgram only) + if getattr(self.config, 'routine_plan', None): + results = {} + # Sort entries by order, but always run main_routine last so it can JSR to generated routines + entries = sorted( + [e for e in self.config.routine_plan if e.enabled and e.program == 'MainProgram'], + key=lambda e: e.order + ) + non_main = [e for e in entries if getattr(e, 'plugin', '') != 'main_routine' and getattr(e, 'name', '') != 'main_routine'] + mains = [e for e in entries if getattr(e, 'plugin', '') == 'main_routine' or getattr(e, 'name', '') == 'main_routine'] + + def _run(entry): + self.routine_manager.context.metadata['params'] = entry.params or {} + self.routine_manager.context.metadata['filters'] = self.config.filters.for_routine(entry.name) + results[entry.name] = self.routine_manager.generate_routine(entry.plugin) + + for entry in non_main: + _run(entry) + for entry in mains: + _run(entry) + + self.logger.info(f"Main routine generation results: {results}") + return + + # Fallback default set (ensure MainRoutine is generated last so it can JSR to routines that exist) + main_routines = ['safety_tag_map', 'rack', 'estop_check', 'main_routine'] + results = {name: self.routine_manager.generate_routine(name) for name in main_routines} + self.logger.info(f"Main routine generation results: {results}") + +class LegacyCompatibilityMixin: + """Mixin to provide backward compatibility with legacy generators.""" + + def __init__(self, excel_path: str | Path, zones_dict: Optional[List[Dict[str, str]]] = None): + """Legacy constructor interface.""" + # This will be mixed with the new generators to maintain compatibility + from .config import get_config + from .data_loader import DataLoader + from .xml_builder import L5XBuilderFactory + + config = get_config() + data_loader = DataLoader(excel_path=excel_path, zones_dict=zones_dict) + xml_builder_factory = L5XBuilderFactory(config) + + # Call the parent constructor + super().__init__(config, data_loader, xml_builder_factory) + +# Backward compatible generator classes +class ModernSafetyProgramGenerator(LegacyCompatibilityMixin, SafetyProgramGenerator): + """Modern SafetyProgram generator with legacy interface.""" + pass + +class ModernMainProgramGenerator(LegacyCompatibilityMixin, MainProgramGenerator): + """Modern MainProgram generator with legacy interface.""" pass \ No newline at end of file diff --git a/Routines Generator/src/cli.py b/Routines Generator/src/cli.py index ed4826c..c987d05 100644 --- a/Routines Generator/src/cli.py +++ b/Routines Generator/src/cli.py @@ -7,8 +7,7 @@ from generators import ( SafetyProgramGenerator, MainProgramGenerator, ) -from writers import create_new_csv_with_tags, create_safety_tag_mapping -from writers.csv_writer import create_limited_csv_with_tags +from writers import create_safety_tag_mapping def _cmd_safety(args: argparse.Namespace) -> None: @@ -25,9 +24,7 @@ def _cmd_main(args: argparse.Namespace) -> None: print(f"Main L5X written to {args.output}") -def _cmd_csv(args: argparse.Namespace) -> None: - std, saf, dcs = create_new_csv_with_tags(args.excel, args.original, args.output) - print(f"CSV generated → {args.output} (Std {std}, Safety {saf}, DCS {dcs})") +# CSV generation removed (deprecated) def _cmd_mapping(args: argparse.Namespace) -> None: @@ -36,7 +33,7 @@ def _cmd_mapping(args: argparse.Namespace) -> None: def _cmd_safety_only(args: argparse.Namespace) -> None: - """Generate only essential safety routines using DESC_IP data extraction (RST, STO, EPC) and zones_config.py.""" + """Generate only essential safety routines using DESC_IP data extraction (RST, STO, EPC).""" # Check if ignore_estop1ok flag is set ignore_estop1ok = getattr(args, 'ignore_estop1ok', False) @@ -55,14 +52,7 @@ def _cmd_safety_only(args: argparse.Namespace) -> None: main_gen.write(main_output) print(f"Limited Main L5X written to {main_output}") - # Generate limited CSV with tags (requires original CSV) - csv_output = args.csv_output or 'MTN6_MCM01_Controller_Tags_Limited.CSV' - original_csv = args.original_csv or 'MTN6_MCM01_Controller_Tags_Original.CSV' - if Path(original_csv).exists(): - std_count, saf_count, dcs_count = create_limited_csv_with_tags(args.excel, original_csv, csv_output) - print(f"Limited CSV written to {csv_output} (Std {std_count}, Safety {saf_count}, DCS {dcs_count})") - else: - print(f"Warning: Original CSV file {original_csv} not found - skipping CSV generation") + # CSV generation removed # Create safety tag mapping for the limited mode from data_loader import LimitedDataLoader @@ -83,15 +73,14 @@ def _cmd_safety_only(args: argparse.Namespace) -> None: print(f"Limited safety tag mapping written to {mapping_output}") print(f"\n[SUCCESS] Safety-only mode complete:") - print(f" - Safety routines: inputs, outputs, estops, zones, resets") + print(f" - Safety routines: inputs, outputs, estops, resets") print(f" - Main routines: safety_tag_map, estop_check") - print(f" - Data sources: DESC_IP extraction (RST, STO, EPC) + zones_config.py") + print(f" - Data sources: DESC_IP extraction (RST, STO, EPC)") _DEF_OUTPUTS = { 'safety': 'SafetyProgram_Generated.L5X', 'main': 'MainProgram_Generated.L5X', - 'csv': 'MTN6_MCM01_Controller_Tags_Complete.CSV', 'mapping': 'SafetyTagMapping.txt', } @@ -112,12 +101,7 @@ def build_parser() -> argparse.ArgumentParser: m.add_argument('-o', '--output', default=_DEF_OUTPUTS['main']) m.set_defaults(func=_cmd_main) - # CSV - c = sub.add_parser('csv', help='Generate complete CSV tags list') - c.add_argument('excel') - c.add_argument('original', help='Original CSV file with existing tags') - c.add_argument('-o', '--output', default=_DEF_OUTPUTS['csv']) - c.set_defaults(func=_cmd_csv) + # CSV subcommand removed # Mapping mp = sub.add_parser('mapping', help='Generate SafetyTag mapping file') @@ -126,14 +110,13 @@ def build_parser() -> argparse.ArgumentParser: mp.set_defaults(func=_cmd_mapping) # Safety-only mode - so = sub.add_parser('safety-only', help='Generate only essential safety routines (inputs, outputs, estops, zones, resets, safety_tag_map, estop_check) using DESC_IP data extraction and zones_config.py') + so = sub.add_parser('safety-only', help='Generate only essential safety routines (inputs, outputs, estops, resets, safety_tag_map, estop_check) using DESC_IP data extraction') so.add_argument('excel', help='Merged descriptor Excel file') so.add_argument('--safety-output', help='Safety L5X output file (default: SafetyProgram_Limited.L5X)') so.add_argument('--main-output', help='Main L5X output file (default: MainProgram_Limited.L5X)') - so.add_argument('--csv-output', help='CSV tags output file (default: MTN6_MCM01_Controller_Tags_Limited.CSV)') - so.add_argument('--original-csv', help='Original CSV file (default: MTN6_MCM01_Controller_Tags_Original.CSV)') + # CSV options removed so.add_argument('--mapping-output', help='Safety tag mapping output file (default: SafetyTagMapping_Limited.txt)') - so.add_argument('--ignore-estop1ok', action='store_true', help='Ignore ESTOP1OK tags in inputs, estops, and zones routines') + so.add_argument('--ignore-estop1ok', action='store_true', help='Ignore ESTOP1OK tags in inputs and estops routines') so.set_defaults(func=_cmd_safety_only) return p diff --git a/Routines Generator/src/config.py b/Routines Generator/src/config.py index 11414d3..3a664c6 100644 --- a/Routines Generator/src/config.py +++ b/Routines Generator/src/config.py @@ -2,20 +2,18 @@ from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path -from typing import Dict, Optional +from typing import Dict, Optional, List, Any import json @dataclass class FileConfig: """File path configuration.""" excel_file: Path = Path('DESC_IP_MERGED.xlsx') - original_csv: Path = Path('MTN6_MCM01_Controller_Tags_Original.CSV') output_dir: Path = Path('.') # Output file names safety_l5x: str = 'SafetyProgram_Generated.L5X' main_l5x: str = 'MainProgram_Generated.L5X' - complete_csv: str = 'MTN6_MCM01_Controller_Tags_Complete.CSV' mapping_txt: str = 'SafetyTagMapping.txt' @dataclass @@ -36,6 +34,8 @@ class ExtractionConfig: rst_desc_excludes: list[str] = field(default_factory=lambda: ['LIGHT']) rst_desca_patterns: list[str] = field(default_factory=lambda: ['S1_PB', 'S2_PB']) rst_desca_endings: list[str] = field(default_factory=lambda: ['SPB']) + + rst_desca_exclude_patterns: list[str] = field(default_factory=lambda: ['GS1']) # STO extraction rules sto_tagname_patterns: list[str] = field(default_factory=lambda: ['VFD']) @@ -44,14 +44,88 @@ class ExtractionConfig: # EPC extraction rules epc_desca_patterns: list[str] = field(default_factory=lambda: ['EPC', 'ESTOP']) + # Additional device extraction rules (override defaults when provided) + dpm_partnumber_contains: list[str] = field(default_factory=lambda: ['OS30-002404-2S']) + fiom_partnumber_contains: list[str] = field(default_factory=lambda: ['5032-8IOLM12DR']) + fioh_partnumber_contains: list[str] = field(default_factory=lambda: ['5032-8IOLM12DR']) + fioh_desca_contains: list[str] = field(default_factory=lambda: ['FIOH']) + + # NETWORK sheet extraction rules + apf_partnumber_prefix: list[str] = field(default_factory=lambda: ['35S']) + extendo_partnumber_exact: list[str] = field(default_factory=lambda: ['CALJAN']) + pmm_partnumber_exact: list[str] = field(default_factory=lambda: ['1420-V2-ENT']) + speed_ctrl_partnumber_prefix: list[str] = field(default_factory=lambda: ['35S']) + + # CB monitor extraction rules + cb_desca_include: list[str] = field(default_factory=lambda: ['CB']) + cb_desca_exclude: list[str] = field(default_factory=lambda: ['BCN']) + + # D2C_CHUTE patterns + s0_prefix: str = 'S0' + d2c_gs1_pb_token: str = 'GS1_PB' + d2c_gs1_pb_lt_token: str = 'GS1_PB_LT' + d2c_bcn_token: str = 'BCN' + d2c_zmx_suffix: str = '_ZMX' + + # PB_CHUTE components + pb_chute_components: list[str] = field(default_factory=lambda: ['PE1', 'PE2', 'PR1', 'SOL1']) + fioh_token: str = 'FIOH' + bcn_token: str = 'BCN' + beacon_stack_3_tokens: list[str] = field(default_factory=lambda: ['3-STACK', '3 STACK']) + beacon_segment_a_pin4: str = 'Connector_1_A_Pin_4' + beacon_segment_b_pin2: str = 'Connector_1_B_Pin_2' + + # STATION_JR_CHUTE / STATION_JR_PB + jr1_token: str = 'JR1' + jr1_pb_token: str = 'JR1_PB' + jr1_pb_lt_token: str = 'JR1_PB_LT' + jr2_token: str = 'JR2' + jr2_pb_token: str = 'JR2_PB' + # Optional: accept JR2 PB light token from config for forward compatibility + jr2_pb_lt_token: str = 'JR2_PB_LT' + jr1_exclude_tokens: list[str] = field(default_factory=lambda: ['FL', 'S0']) + vfd_prefix_regex: str = r'(FL\d+_\d+)' + vfd_suffix_default: str = '_VFD1' + + # JPE / FPE + jpe_include_tokens: list[str] = field(default_factory=lambda: ['JPE', '_2_PE']) + jpe_exclude_tokens: list[str] = field(default_factory=lambda: ['3CH_PE']) + jpe_input_default: str = 'In_2' + fpe_include_tokens: list[str] = field(default_factory=lambda: ['FPE', '3CH_PE']) + + # FLOW_CTRL generation controls + # Regex to extract lane grouping and position from VFD names (generic: __VFDn) + flow_ctrl_vfd_name_regex: str = r'^(?P[^_]+)_(?P\d+[A-Za-z]?)_VFD\d*' + # Regex to extract lane grouping and position from EXTENDO names (generic: __EXn) + flow_ctrl_extendo_name_regex: str = r'^(?P[^_]+)_(?P\d+[A-Za-z]?)_EX\d*' + # Chain order strategy: 'lane' chains per lane using extracted numeric positions; 'natural' uses natural sort + flow_ctrl_chain_order: str = 'lane' + # Whether to emit EXTENDO interlocks based on discovered dependencies + flow_ctrl_enable_extendo_interlocks: bool = True + # Whether to emit an unconditional OTE for the first VFD in each lane + flow_ctrl_emit_first_vfd_ote: bool = True + # Chain direction: 'downstream_to_upstream' or 'upstream_to_downstream' + flow_ctrl_chain_direction: str = 'downstream_to_upstream' + # Whether to emit an unconditional OTE for the last VFD in each lane (always interlock last in lane) + flow_ctrl_emit_last_vfd_ote: bool = True + # Whether to emit an unconditional OTE for the last EXTENDO in each lane + flow_ctrl_emit_last_extendo_ote: bool = True + # Selection: choose VFDs by Name regex instead of PartNumber prefixes + flow_ctrl_select_vfds_by_name: bool = True + # Lane grouping: number of leading underscore-separated tokens to use as lane key (e.g., 3 -> UL5_3_VFD1 base UL5_3_VFD) + flow_ctrl_lane_tokens: int = 3 + @dataclass class RoutineConfig: """Routine generation configuration.""" # Routine naming patterns main_routine_name: str = 'MainRoutine' - safety_tag_map_name: str = 'R000_SAFETY_TAG_MAP' - estop_check_name: str = 'R100_ESTOP_CHECK' + safety_tag_map_name: str = 'R130_SAFETY_TAG_MAP' + estop_check_name: str = 'R120_ESTOP_CHECK' + # Safety tag mapping prefix and MCM input address + safety_tag_prefix: str = 'SFT_' + mcm_input_address: str = 'Local:5:I.Data.0' # Safety routine names inputs_routine: str = 'R010_INPUTS' @@ -64,6 +138,61 @@ class RoutineConfig: mcm_safety_tag: str = 'MCM_S_PB' mcm_epb_tag: str = 'MCM_EPB_DCS_CTRL.O1' + # Routine name map for all known plugins (can be overridden in config) + name_map: Dict[str, str] = field(default_factory=lambda: { + 'main_routine': 'MainRoutine', + 'safety_tag_map': 'R130_SAFETY_TAG_MAP', + 'estop_check': 'R120_ESTOP_CHECK', + 'dpm': 'R020_DPM', + 'fiom': 'R030_FIOM', + 'fioh': 'R031_FIOH', + 'apf': 'R040_APF', + 'extendo': 'R041_EXTENDO', + 'flow_ctrl': 'R050_FLOW_CTRL', + 'speed_ctrl': 'R051_SPEED_CTRL', + 'd2c_chute': 'R042_D2C_CHUTE', + 'pb_chute': 'R043_PB_CHUTE', + 'station_jr_chute': 'R044_STATION_JR_CHUTE', + 'station_jr_pb': 'R090_STATION_JR_PB', + 'jpe': 'R100_JPE', + 'fpe': 'R101_FPE', + 'pmm': 'R060_PMM', + 'cb_monitor': 'R070_CB_MONITOR', + 'rack': 'R011_RACK', + 'mcm': 'R010_MCM', + 'inputs': 'R010_INPUTS', + 'outputs': 'R011_OUTPUTS', + 'resets': 'R012_RESETS', + 'estops': 'R020_ESTOPS', + 'zones': 'R030_ZONES', + }) + +@dataclass +class RoutineEntry: + """Config entry for a single routine in the config-driven plan.""" + name: str + plugin: str + enabled: bool = True + program: str = "MainProgram" # or "SafetyProgram" + order: int = 100 + params: Dict[str, Any] = field(default_factory=dict) + +@dataclass +class FiltersConfig: + """Global and per-routine filters. Values are simple include/exclude lists.""" + global_filters: Dict[str, List[str]] = field(default_factory=dict) + per_routine: Dict[str, Dict[str, List[str]]] = field(default_factory=dict) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 'FiltersConfig': + # Support keys: "global" and "per_routine" + global_raw = data.get('global', {}) + per_routine_raw = data.get('per_routine', {}) + return cls(global_filters=global_raw, per_routine=per_routine_raw) + + def for_routine(self, routine_name: str) -> Dict[str, List[str]]: + return self.per_routine.get(routine_name, {}) + @dataclass class GeneratorConfig: """Complete configuration for the generator system.""" @@ -72,6 +201,12 @@ class GeneratorConfig: xml: XMLConfig = field(default_factory=XMLConfig) extraction: ExtractionConfig = field(default_factory=ExtractionConfig) routines: RoutineConfig = field(default_factory=RoutineConfig) + # Optional config-driven routine plan and filters + routine_plan: List[RoutineEntry] = field(default_factory=list) + filters: FiltersConfig = field(default_factory=FiltersConfig) + # Tags control (per-program module inclusion). Example: + # {"MainProgram": {"DPM": true, "FIOM": false}} + tags: Dict[str, Dict[str, bool]] = field(default_factory=dict) @classmethod def from_file(cls, config_path: Path) -> 'GeneratorConfig': @@ -90,17 +225,44 @@ class GeneratorConfig: # Convert specific path fields to Path objects if 'excel_file' in files_data: files_data['excel_file'] = Path(files_data['excel_file']) - if 'original_csv' in files_data: - files_data['original_csv'] = Path(files_data['original_csv']) if 'output_dir' in files_data: files_data['output_dir'] = Path(files_data['output_dir']) files = FileConfig(**files_data) xml = XMLConfig(**config_data.get('xml', {})) extraction = ExtractionConfig(**config_data.get('extraction', {})) - routines = RoutineConfig(**config_data.get('routines', {})) - return cls(files=files, xml=xml, extraction=extraction, routines=routines) + # Support both legacy dict-based routines config and new list-based plan + routines_section = config_data.get('routines', {}) + routine_plan: List[RoutineEntry] = [] + routines: RoutineConfig + if isinstance(routines_section, list): + # New plan format under key 'routines' + for entry in routines_section: + try: + routine_plan.append(RoutineEntry(**entry)) + except Exception: + # Skip invalid entries silently to keep robustness + continue + routines = RoutineConfig() + else: + routines = RoutineConfig(**routines_section) + + # Optional filters section + filters_section = config_data.get('filters', {}) + filters = FiltersConfig.from_dict(filters_section) if isinstance(filters_section, dict) else FiltersConfig() + # Tags section + tags = config_data.get('tags', {}) or {} + + return cls( + files=files, + xml=xml, + extraction=extraction, + routines=routines, + routine_plan=routine_plan, + filters=filters, + tags=tags, + ) def save_to_file(self, config_path: Path) -> None: """Save configuration to JSON file.""" @@ -108,8 +270,11 @@ class GeneratorConfig: 'files': self.files.__dict__, 'xml': self.xml.__dict__, 'extraction': self.extraction.__dict__, - 'routines': self.routines.__dict__ + 'routines': self.routines.__dict__, + 'tags': self.tags, } + # Intentionally do not overwrite user's list-based routines if present; save only legacy schema by default. + # Advanced users can manage JSON manually for routine_plan/filters. # Convert Path objects to strings for JSON serialization def convert_paths(obj): diff --git a/Routines Generator/src/container.py b/Routines Generator/src/container.py index 4c89669..d7dc362 100644 --- a/Routines Generator/src/container.py +++ b/Routines Generator/src/container.py @@ -26,27 +26,41 @@ class GeneratorContainer: self._logger.debug("Creating DataLoader instance", excel_file=str(self.config.files.excel_file)) self._instances['data_loader'] = DataLoader( excel_path=self.config.files.excel_file, - zones_dict=self.zones_dict + zones_dict=self.zones_dict or [] ) return self._instances['data_loader'] + + # Zones support removed: no config file or inline loading def get_safety_program_generator(self): - """Get or create SafetyProgramGenerator instance.""" + """Get or create SafetyProgramGenerator instance (modern by default). + + Note: Limited generators are deprecated. This method now returns the + modern generator implementation. + """ if 'safety_program_generator' not in self._instances: - from .generators.safety_program import LimitedSafetyProgramGenerator - self._logger.debug("Creating SafetyProgramGenerator instance") - self._instances['safety_program_generator'] = LimitedSafetyProgramGenerator( + from .base_generator import ModernSafetyProgramGenerator + self._logger.warning( + "LimitedSafetyProgramGenerator is deprecated; using ModernSafetyProgramGenerator" + ) + self._instances['safety_program_generator'] = ModernSafetyProgramGenerator( excel_path=self.config.files.excel_file, zones_dict=self.zones_dict ) return self._instances['safety_program_generator'] def get_main_program_generator(self): - """Get or create MainProgramGenerator instance.""" + """Get or create MainProgramGenerator instance (modern by default). + + Note: Limited generators are deprecated. This method now returns the + modern generator implementation. + """ if 'main_program_generator' not in self._instances: - from .generators.main_program import LimitedMainProgramGenerator - self._logger.debug("Creating MainProgramGenerator instance") - self._instances['main_program_generator'] = LimitedMainProgramGenerator( + from .base_generator import ModernMainProgramGenerator + self._logger.warning( + "LimitedMainProgramGenerator is deprecated; using ModernMainProgramGenerator" + ) + self._instances['main_program_generator'] = ModernMainProgramGenerator( excel_path=self.config.files.excel_file, zones_dict=self.zones_dict ) @@ -109,12 +123,7 @@ class GeneratorContainer: return self._instances['tag_writer'] def get_csv_writer(self): - """Get or create CSVWriter instance.""" - if 'csv_writer' not in self._instances: - from .writers.csv_writer import create_limited_csv_with_tags - self._logger.debug("Creating CSVWriter wrapper") - self._instances['csv_writer'] = create_limited_csv_with_tags - return self._instances['csv_writer'] + raise NotImplementedError("CSV generation is removed. Use config-driven XML generation only.") def get_mapping_writer(self): """Get or create MappingWriter instance.""" diff --git a/Routines Generator/src/data_loader.py b/Routines Generator/src/data_loader.py index 3202d60..7e4ebe6 100644 --- a/Routines Generator/src/data_loader.py +++ b/Routines Generator/src/data_loader.py @@ -2,8 +2,10 @@ from __future__ import annotations from dataclasses import dataclass from pathlib import Path -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Any import pandas as pd +import re +from .utils.common import natural_sort_key @dataclass class DataLoader: @@ -25,52 +27,51 @@ class DataLoader: # Internal cache for loaded data _desc_ip_df: Optional[pd.DataFrame] = None _extracted_data: Dict[str, pd.DataFrame] = None + _cache: Dict[str, Any] = None def __post_init__(self): """Convert string path to Path object and set default zones if needed.""" if isinstance(self.excel_path, str): self.excel_path = Path(self.excel_path) self._extracted_data = {} + self._cache = {} - # Set default zones from zones_config.py if not provided - if self.zones_dict is None: - try: - from zones_config import DEFAULT_ZONES - self.zones_dict = DEFAULT_ZONES - except ImportError: - # Fallback to empty zones if zones_config.py not available - self.zones_dict = [] - - # Validate zones configuration - self._validate_zones_dict() + # Zones deprecated: ensure zones_dict is an empty list + self.zones_dict = [] def _validate_zones_dict(self) -> None: - """Validate zones_dict has required structure.""" - if not self.zones_dict: - # Empty zones list is acceptable - return - - required_keys = {'name', 'start', 'stop', 'interlock'} - for i, zone in enumerate(self.zones_dict): - if not isinstance(zone, dict): - raise ValueError(f"Invalid zone configuration at index {i}: expected dict, got {type(zone)}") - - missing_keys = required_keys - set(zone.keys()) - if missing_keys: - raise ValueError(f"Zone at index {i} missing required keys {missing_keys}: {zone}") - - # Validate that values are strings (or None/empty) - for key in required_keys: - value = zone[key] - if value is not None and not isinstance(value, str): - raise ValueError(f"Zone at index {i}, key '{key}': expected string, got {type(value)}") + """Zones validation removed (zones deprecated).""" + return @property def desc_ip(self) -> pd.DataFrame: """Load and cache the DESC_IP sheet.""" if self._desc_ip_df is None: - self._desc_ip_df = pd.read_excel(self.excel_path, sheet_name='DESC_IP') + df = pd.read_excel(self.excel_path, sheet_name='DESC_IP') + # Coerce commonly used text columns to string to allow safe .str usage + for col in ['DESC', 'DESCA', 'TAGNAME', 'TERM', 'IO_PATH', 'PARTNUMBER', 'CONTROLLERS', 'DEVICE_TYPE']: + if col in df.columns: + df[col] = df[col].astype(str) + # Ensure TAGNAME exists to avoid KeyErrors downstream + if 'TAGNAME' not in df.columns: + df['TAGNAME'] = '' + self._desc_ip_df = df return self._desc_ip_df + + @property + def network(self) -> pd.DataFrame: + """Get NETWORK sheet data.""" + if 'network' not in self._cache: + try: + df = pd.read_excel(self.excel_path, sheet_name='NETWORK') + # Coerce commonly used text columns to string to allow safe .str usage + for col in ['Name', 'PartNumber', 'DPM']: + if col in df.columns: + df[col] = df[col].astype(str) + self._cache['network'] = df + except: + self._cache['network'] = pd.DataFrame() + return self._cache['network'] @property def rst(self) -> pd.DataFrame: @@ -116,18 +117,79 @@ class DataLoader: @property def zones(self) -> pd.DataFrame: - """Get zones data from zones_dict configuration.""" - # Convert dictionary to DataFrame with proper column names - # Map 'stop' to 'END' for backward compatibility - zones_data = [] - for zone in self.zones_dict: - zones_data.append({ - 'NAME': zone.get('name', ''), - 'START': zone.get('start', ''), - 'END': zone.get('stop', ''), # Map 'stop' to 'END' - 'INTERLOCK': zone.get('interlock', '') - }) - return pd.DataFrame(zones_data) + """Load zones from zones.json configuration. + + The JSON file is expected at project root as `zones.json` with a + top-level object keyed by subsystem (e.g., MCM01, MCM04) and a + `DEFAULT` fallback. Each entry is a list of objects having + fields: name, start, stop, interlock. + """ + if 'zones' in self._cache: + return self._cache['zones'] + + import json + import os + # Determine subsystem from Excel path + import re + excel_path_str = str(self.excel_path) + m = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) + subsystem = (m.group(1).upper() if m else 'DEFAULT') + + json_path = Path(__file__).parent.parent.parent / 'zones copy.json' + zones_df = pd.DataFrame(columns=['name', 'start', 'stop', 'interlock']) + try: + with open(json_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + # Pick group: prefer explicit subsystem; otherwise, choose the best + # matching group by checking how many 'start' tokens appear in DESC_IP + # Only take explicit match when subsystem was detected (not DEFAULT) + entries = data.get(subsystem) if subsystem != 'DEFAULT' else None + from .logging_config import get_logger + _log = get_logger(__name__) + _log.debug("Zones: subsystem key", subsystem=subsystem, found=bool(entries)) + if not entries: + # Heuristic match + candidates = {k: v for k, v in data.items() if k not in ('DEFAULT',)} + best_key = None + best_score = -1 + try: + desc = self.desc_ip + except Exception: + desc = pd.DataFrame() + for key, lst in candidates.items(): + score = 0 + for item in lst: + start_tok = str(item.get('start', '')).strip() + if not start_tok: + continue + if desc.empty or 'DESCA' not in desc.columns: + continue + # Substring match for any prefix (UL/FL/PS/...) + import re as _re + if desc['DESCA'].astype(str).str.contains(_re.escape(start_tok), regex=True, na=False).any(): + score += 2 + # Generic prefix match using token before underscore, not tied to a specific prefix + prefix = start_tok.split('_')[0] + if prefix and desc['DESCA'].astype(str).str.contains(fr'\b{_re.escape(prefix)}_', regex=True, na=False).any(): + score += 1 + if score > best_score: + best_key, best_score = key, score + _log.debug("Zones: heuristic best match", best_key=best_key, score=best_score) + entries = candidates.get(best_key) if best_score > 0 else None + + if not entries: + entries = data.get('DEFAULT') or [] + _log.debug("Zones: using DEFAULT group", default_count=len(entries)) + + zones_df = pd.DataFrame(entries, columns=['name', 'start', 'stop', 'interlock']) + _log.debug("Zones: loaded", rows=len(zones_df)) + except Exception: + # Keep empty + zones_df = pd.DataFrame(columns=['name', 'start', 'stop', 'interlock']) + + self._cache['zones'] = zones_df + return zones_df @property def safety_tags_from_pb(self) -> set[str]: @@ -138,7 +200,7 @@ class DataLoader: import re excel_path_str = str(self.excel_path) subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) - subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM01" + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" # Add subsystem-specific MCM tag (always needed) safety_tags.add(f"{subsystem}_S_PB") @@ -162,14 +224,21 @@ class DataLoader: """Extract RST (Reset) data from DESC_IP based on defined rules.""" desc_ip = self.desc_ip - # RST extraction rules from config (we'll use hardcoded for now) - rst_desc_contains = ['START'] - rst_desc_excludes = ['LIGHT'] - rst_desca_patterns = ['S1_PB', 'S2_PB'] - rst_desca_endings = ['SPB'] - + # RST extraction rules from config with sensible defaults + cfg = getattr(self, 'config', None) + if cfg is None: + try: + from .config import get_config + cfg = get_config() + except Exception: + cfg = None + ex_cfg = getattr(cfg, 'extraction', None) + rst_desc_contains = (ex_cfg.rst_desc_contains if ex_cfg else ['START']) + rst_desc_excludes = (ex_cfg.rst_desc_excludes if ex_cfg else ['LIGHT']) + rst_desca_patterns = (ex_cfg.rst_desca_patterns if ex_cfg else ['S1_PB', 'S2_PB']) + rst_desca_endings = (ex_cfg.rst_desca_endings if ex_cfg else ['SPB']) # Exclusion patterns for push buttons to exclude - rst_desca_exclude_patterns = ['GS1'] # Exclude GS1 patterns + rst_desca_exclude_patterns = (ex_cfg.rst_desca_exclude_patterns if ex_cfg else ['GS1']) # Filter by DESC column desc_mask = desc_ip['DESC'].str.contains('|'.join(rst_desc_contains), case=False, na=False) @@ -200,8 +269,16 @@ class DataLoader: desc_ip = self.desc_ip # STO extraction rules from config - sto_tagname_patterns = ['VFD'] - sto_desca_patterns = ['STO'] + cfg = getattr(self, 'config', None) + if cfg is None: + try: + from .config import get_config + cfg = get_config() + except Exception: + cfg = None + ex_cfg = getattr(cfg, 'extraction', None) + sto_tagname_patterns = (ex_cfg.sto_tagname_patterns if ex_cfg else ['VFD']) + sto_desca_patterns = (ex_cfg.sto_desca_patterns if ex_cfg else ['STO']) # Filter by unique TAGNAME containing patterns tagname_mask = desc_ip['TAGNAME'].str.contains('|'.join(sto_tagname_patterns), case=False, na=False) @@ -221,7 +298,15 @@ class DataLoader: desc_ip = self.desc_ip # EPC extraction rules from config - epc_desca_patterns = ['EPC', 'ESTOP'] + cfg = getattr(self, 'config', None) + if cfg is None: + try: + from .config import get_config + cfg = get_config() + except Exception: + cfg = None + ex_cfg = getattr(cfg, 'extraction', None) + epc_desca_patterns = (ex_cfg.epc_desca_patterns if ex_cfg else ['EPC', 'ESTOP']) # Filter by DESCA patterns desca_mask = desc_ip['DESCA'].str.contains('|'.join(epc_desca_patterns), case=False, na=False) @@ -233,8 +318,16 @@ class DataLoader: """Extract DPM (Data Power Module) data from DESC_IP based on PARTNUMBER.""" desc_ip = self.desc_ip - # DPM extraction rule: Filter by PARTNUMBER containing OS30-002404-2S - dpm_filter = desc_ip['PARTNUMBER'].str.contains('OS30-002404-2S', case=False, na=False) + # DPM extraction rule: Filter by PARTNUMBER containing any configured token + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + tokens = (getattr(cfg.extraction, 'dpm_partnumber_contains', None) if cfg else None) or ['OS30-002404-2S'] + pattern = '|'.join(map(re.escape, tokens)) + dpm_filter = desc_ip['PARTNUMBER'].astype(str).str.contains(pattern, case=False, na=False) dpm_df = desc_ip[dpm_filter].copy() # Remove duplicates based on TAGNAME for deterministic output @@ -246,8 +339,15 @@ class DataLoader: """Extract FIOM (Field IO Master) data from DESC_IP based on PARTNUMBER.""" desc_ip = self.desc_ip - # FIOM extraction rule: Filter by PARTNUMBER containing 5032-8IOLM12DR - fiom_filter = desc_ip['PARTNUMBER'].str.contains('5032-8IOLM12DR', case=False, na=False) + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + tokens = (getattr(cfg.extraction, 'fiom_partnumber_contains', None) if cfg else None) or ['5032-8IOLM12DR'] + pattern = '|'.join(map(re.escape, tokens)) + fiom_filter = desc_ip['PARTNUMBER'].astype(str).str.contains(pattern, case=False, na=False) fiom_df = desc_ip[fiom_filter].copy() # Remove duplicates based on Name or TAGNAME for deterministic output @@ -262,9 +362,18 @@ class DataLoader: """Extract FIOH (Field IO Hub) data from DESC_IP based on PARTNUMBER and DESCA.""" desc_ip = self.desc_ip - # FIOH extraction rule: Filter by PARTNUMBER containing 5032-8IOLM12DR AND DESCA containing FIOH - fioh_filter = (desc_ip['PARTNUMBER'].str.contains('5032-8IOLM12DR', case=False, na=False) & - desc_ip['DESCA'].str.contains('FIOH', case=False, na=False)) + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + part_tokens = (getattr(cfg.extraction, 'fioh_partnumber_contains', None) if cfg else None) or ['5032-8IOLM12DR'] + desca_tokens = (getattr(cfg.extraction, 'fioh_desca_contains', None) if cfg else None) or ['FIOH'] + part_pattern = '|'.join(map(re.escape, part_tokens)) + desca_pattern = '|'.join(map(re.escape, desca_tokens)) + fioh_filter = (desc_ip['PARTNUMBER'].astype(str).str.contains(part_pattern, case=False, na=False) & + desc_ip['DESCA'].astype(str).str.contains(desca_pattern, case=False, na=False)) fioh_df = desc_ip[fioh_filter].copy() # Remove duplicates based on DESCA (since that's the FIOH device name) @@ -278,6 +387,924 @@ class DataLoader: fioh_df = fioh_df.drop_duplicates(subset=['TAGNAME']) return fioh_df + + @property + def apf(self) -> pd.DataFrame: + """Get APF data from NETWORK sheet.""" + return self._extract_apf() + + def _extract_apf(self) -> pd.DataFrame: + """Extract APF (Variable Frequency Drive) data from NETWORK sheet.""" + # Use centralized NETWORK accessor which normalizes dtypes + network_df = self.network + if network_df.empty: + return pd.DataFrame() + + # Filter via config: PartNumber startswith any configured prefixes + if 'PartNumber' not in network_df.columns: + return pd.DataFrame() + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + prefixes = (getattr(cfg.extraction, 'apf_partnumber_prefix', None) if cfg else None) or ['35S'] + apf_mask = False + for p in prefixes: + apf_mask = apf_mask | network_df['PartNumber'].astype(str).str.startswith(p, na=False) + apf_filter = apf_mask + apf_df = network_df[apf_filter].copy() + + # Remove duplicates based on Name + if 'Name' in apf_df.columns: + apf_df = apf_df.drop_duplicates(subset=['Name']) + else: + apf_df = apf_df.drop_duplicates() + + return apf_df + + @property + def extendo(self) -> pd.DataFrame: + """Get EXTENDO data from NETWORK sheet.""" + return self._extract_extendo() + + def _extract_extendo(self) -> pd.DataFrame: + """Extract EXTENDO (Siemens ET 200SP) data from NETWORK sheet.""" + # Use centralized NETWORK accessor which normalizes dtypes + network_df = self.network + if network_df.empty: + return pd.DataFrame() + + # Filter via config for exact tokens in PartNumber + if 'PartNumber' not in network_df.columns: + return pd.DataFrame() + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + exacts = (getattr(cfg.extraction, 'extendo_partnumber_exact', None) if cfg else None) or ['CALJAN'] + pattern = '|'.join(map(re.escape, exacts)) + extendo_filter = network_df['PartNumber'].astype(str).str.contains(pattern, case=False, na=False) + extendo_df = network_df[extendo_filter].copy() + + # Remove duplicates based on Name + if 'Name' in extendo_df.columns: + extendo_df = extendo_df.drop_duplicates(subset=['Name']) + else: + extendo_df = extendo_df.drop_duplicates() + + return extendo_df + + @property + def d2c_data(self) -> dict: + """Get D2C data combining DESC_IP and NETWORK sheets.""" + if 'd2c_data' not in self._cache: + self._cache['d2c_data'] = self._extract_d2c_data() + return self._cache['d2c_data'] + + @property + def pb_chute_data(self) -> dict: + """Extract PB_CHUTE data from DESC_IP.""" + if 'pb_chute_data' not in self._cache: + self._cache['pb_chute_data'] = self._extract_pb_chute_data() + return self._cache['pb_chute_data'] + + @property + def station_jr_chute_data(self) -> dict: + """Extract STATION_JR_CHUTE data from DESC_IP.""" + if 'station_jr_chute_data' not in self._cache: + self._cache['station_jr_chute_data'] = self._extract_station_jr_chute_data() + return self._cache['station_jr_chute_data'] + + @property + def station_jr_pb_data(self) -> dict: + """Extract STATION_JR_PB data from DESC_IP.""" + if 'station_jr_pb_data' not in self._cache: + self._cache['station_jr_pb_data'] = self._extract_station_jr_pb_data() + return self._cache['station_jr_pb_data'] + + @property + def jpe_data(self) -> dict: + """Extract JPE data from DESC_IP.""" + if 'jpe_data' not in self._cache: + self._cache['jpe_data'] = self._extract_jpe_data() + return self._cache['jpe_data'] + + @property + def fpe_data(self) -> Dict[str, Dict[str, str]]: + """Extract FPE configurations from DESC_IP data.""" + if 'fpe_data' in self._cache: + return self._cache['fpe_data'] + + fpe_data = self._extract_fpe_data() + self._cache['fpe_data'] = fpe_data + return fpe_data + + def _extract_d2c_data(self) -> dict: + """Extract D2C data from DESC_IP and NETWORK sheets.""" + import re + # Get config tokens + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + s0_prefix = (getattr(cfg.extraction, 's0_prefix', None) if cfg else None) or 'S0' + gs1_pb_token = (getattr(cfg.extraction, 'd2c_gs1_pb_token', None) if cfg else None) or 'GS1_PB' + gs1_pb_lt_token = (getattr(cfg.extraction, 'd2c_gs1_pb_lt_token', None) if cfg else None) or 'GS1_PB_LT' + bcn_token = (getattr(cfg.extraction, 'd2c_bcn_token', None) if cfg else None) or 'BCN' + zmx_suffix = (getattr(cfg.extraction, 'd2c_zmx_suffix', None) if cfg else None) or '_ZMX' + + # Get DESC_IP and NETWORK dataframes + desc_ip_df = self.desc_ip + network_df = self.network + if desc_ip_df.empty or network_df.empty: + return {} + + # Extract unique S0 numbers based on s0_prefix + s0_pattern = rf'({re.escape(s0_prefix)}\d+)' + s0_entries = desc_ip_df[desc_ip_df['DESCA'].astype(str).str.contains(s0_prefix, na=False)] + s0_numbers = s0_entries['DESCA'].astype(str).str.extract(s0_pattern)[0].dropna().unique() + s0_numbers = sorted(set(s0_numbers)) + + s0_data = {} + for s0 in s0_numbers: + s0_data[s0] = {} + # GS1 pushbutton (not LT) + gs_pb = desc_ip_df[ + desc_ip_df['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(gs1_pb_token)}', na=False, regex=True) & + ~desc_ip_df['DESCA'].astype(str).str.contains('_LT', na=False) + ] + if not gs_pb.empty: + row = gs_pb.iloc[0] + io_path = row.get('IO_PATH', '') + fio = io_path.split(':')[0] if pd.notna(io_path) and ':' in str(io_path) else '' + s0_data[s0]['gs1_pb'] = {'io_path': io_path, 'fio': fio} + # GS1 PB LT + gs_pb_lt = desc_ip_df[desc_ip_df['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(gs1_pb_lt_token)}', na=False, regex=True)] + if not gs_pb_lt.empty: + s0_data[s0]['gs1_pb_lt'] = {'io_path': gs_pb_lt.iloc[0].get('IO_PATH', '')} + # Beacon + bcn = desc_ip_df[desc_ip_df['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(bcn_token)}', na=False, regex=True)] + if not bcn.empty: + bcn_row = bcn.iloc[0] + desca_upper = str(bcn_row.get('DESCA', '')).upper() + stack_type = '3 STACK' if '3 STACK' in desca_upper or '3-STACK' in desca_upper else '2 STACK' + bcn_name = bcn_row['DESCA'].split()[0] if pd.notna(bcn_row.get('DESCA')) else f"{s0}_{bcn_token}1" + s0_data[s0]['bcn'] = { 'tagname': bcn_name, 'stack_type': stack_type } + # ZMX in NETWORK + zmx = network_df[network_df['Name'].astype(str).str.contains(f'{re.escape(s0)}{re.escape(zmx_suffix)}', na=False, regex=True)] + if not zmx.empty: + zmx_row = zmx.iloc[0] + s0_data[s0]['zmx'] = { 'name': zmx_row['Name'], 'dpm': zmx_row.get('DPM', '') } + + # Keep only complete sets + return { s0: data for s0, data in s0_data.items() if all(k in data for k in ['gs1_pb','gs1_pb_lt','bcn','zmx']) } + + def _extract_pb_chute_data(self) -> dict: + """Extract PB_CHUTE data from DESC_IP sheet.""" + import re + desc_ip = self.desc_ip + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + s0_prefix = (getattr(cfg.extraction, 's0_prefix', None) if cfg else None) or 'S0' + components = (getattr(cfg.extraction, 'pb_chute_components', None) if cfg else None) or ['PE1','PE2','PR1','SOL1'] + bcn_token = (getattr(cfg.extraction, 'bcn_token', None) if cfg else None) or 'BCN' + # Find S0 numbers (even only) + s0_pattern = rf'{re.escape(s0_prefix)}(\d+)' + s0_numbers: set[str] = set() + for desca in desc_ip['DESCA'].dropna().astype(str): + match = re.match(s0_pattern, str(desca)) + if match: + num = int(match.group(1)) + if num % 2 == 0: + s0_numbers.add(f'{s0_prefix}{num}') + + pb_chute_configs = {} + for s0 in sorted(s0_numbers): + s0_data = {'s0': s0} + complete = True + for comp in components: + comp_pattern = f'{re.escape(s0)}_{re.escape(comp)}(?!\\d)' + comp_rows = desc_ip[desc_ip['DESCA'].astype(str).str.contains(comp_pattern, case=False, na=False, regex=True)] + if comp_rows.empty: + complete = False + break + row = comp_rows.iloc[0] + s0_data[f'{comp.lower()}_path'] = row.get('IO_PATH', '') + if 'fioh_tagname' not in s0_data and pd.notna(row.get('TAGNAME')): + s0_data['fioh_tagname'] = row['TAGNAME'] + if complete and 'fioh_tagname' in s0_data: + bcn_rows = desc_ip[desc_ip['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(bcn_token)}', case=False, na=False, regex=True)] + if not bcn_rows.empty: + bcn_row = bcn_rows.iloc[0] + bcn_name = bcn_row['DESCA'].split()[0] if pd.notna(bcn_row.get('DESCA')) else f"{s0}_{bcn_token}1" + if 'DESCB' in bcn_row and pd.notna(bcn_row['DESCB']): + descb = str(bcn_row['DESCB']).upper() + stack_type = '3-stack' if ('3-STACK' in descb or '3 STACK' in descb) else '2-stack' + else: + stack_type = '3-stack' + s0_data['bcn'] = { 'tagname': bcn_name, 'stack_type': stack_type } + pb_chute_configs[s0] = s0_data + print(f" Found PB_CHUTE config for {s0}") + return pb_chute_configs + + def _extract_station_jr_chute_data(self) -> dict: + """Extract STATION_JR_CHUTE data from DESC_IP sheet.""" + import re + desc_ip = self.desc_ip + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + s0_prefix = (getattr(cfg.extraction, 's0_prefix', None) if cfg else None) or 'S0' + jr1_pb = (getattr(cfg.extraction, 'jr1_pb_token', None) if cfg else None) or 'JR1_PB' + jr1_pb_lt = (getattr(cfg.extraction, 'jr1_pb_lt_token', None) if cfg else None) or 'JR1_PB_LT' + bcn_token = (getattr(cfg.extraction, 'bcn_token', None) if cfg else None) or 'BCN' + beacon_3_tokens = (getattr(cfg.extraction, 'beacon_stack_3_tokens', None) if cfg else None) or ['3-STACK','3 STACK'] + s0_pattern = rf'{re.escape(s0_prefix)}(\d+)' + s0_numbers = set() + for desca in desc_ip['DESCA'].dropna().astype(str): + match = re.match(s0_pattern, str(desca)) + if match: + s0_numbers.add(f'{s0_prefix}{match.group(1)}') + + jr_chute_configs = {} + for s0 in sorted(s0_numbers): + s0_data = {'s0': s0} + jr_pb_rows = desc_ip[desc_ip['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(jr1_pb)}(?!_LT)', case=False, na=False, regex=True)] + jr_pb_lt_rows = desc_ip[desc_ip['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(jr1_pb_lt)}', case=False, na=False, regex=True)] + if jr_pb_rows.empty or jr_pb_lt_rows.empty: + continue + s0_data['jr_pb_path'] = jr_pb_rows.iloc[0].get('IO_PATH', '') + s0_data['jr_pb_lt_path'] = jr_pb_lt_rows.iloc[0].get('IO_PATH', '') + bcn_rows = desc_ip[desc_ip['DESCA'].astype(str).str.contains(f'{re.escape(s0)}_{re.escape(bcn_token)}', case=False, na=False, regex=True)] + if bcn_rows.empty: + continue + bcn_row = bcn_rows.iloc[0] + descb = str(bcn_row.get('DESCB', '')).upper() + if not any(tok in descb for tok in beacon_3_tokens): + continue + bcn_name = bcn_row['DESCA'].split()[0] if pd.notna(bcn_row.get('DESCA')) else f"{s0}_{bcn_token}1" + s0_data['bcn_tagname'] = bcn_name + jr_chute_configs[s0] = s0_data + print(f" Found STATION_JR_CHUTE config for {s0}") + return jr_chute_configs + + def _extract_station_jr_pb_data(self) -> dict: + """Extract STATION_JR_PB data by pairing JR*_PB with matching JR*_PB_LT entries (config-driven).""" + import re + desc_ip = self.desc_ip + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + jr1_pb = (getattr(cfg.extraction, 'jr1_pb_token', None) if cfg else None) or 'JR1_PB' + jr2_pb = (getattr(cfg.extraction, 'jr2_pb_token', None) if cfg else None) or 'JR2_PB' + jr1_pb_lt = (getattr(cfg.extraction, 'jr1_pb_lt_token', None) if cfg else None) or 'JR1_PB_LT' + jr2_pb_lt = (getattr(cfg.extraction, 'jr2_pb_lt_token', None) if cfg else None) or 'JR2_PB_LT' + + jr_pb_configs: dict[str, dict[str, str]] = {} + seen: set[str] = set() + + # Regex to capture base for PB rows (e.g., UL4_9 from UL4_9_JR1_PB) + pb_regex = re.compile(rf'^(?P.+)_(?:{re.escape(jr1_pb)}|{re.escape(jr2_pb)})$', re.IGNORECASE) + + for desca in desc_ip['DESCA'].dropna().astype(str): + m = pb_regex.match(desca.split()[0]) + if not m: + continue + base = m.group('base') + # Determine exact PB name we matched + jr_name = desca.split()[0] + if jr_name in seen: + continue + + # Locate PB row (input) + pb_row = desc_ip[desc_ip['DESCA'] == jr_name] + if pb_row.empty: + continue + input_path = str(pb_row.iloc[0].get('IO_PATH', '') or '') + if not input_path: + continue + + # Determine LT counterpart based on which PB token was used + if jr_name.endswith(jr1_pb): + lt_desca = f"{base}_{jr1_pb_lt}" + else: + lt_desca = f"{base}_{jr2_pb_lt}" + + lt_row = desc_ip[desc_ip['DESCA'] == lt_desca] + if lt_row.empty: + continue + output_path = str(lt_row.iloc[0].get('IO_PATH', '') or '') + if not output_path: + continue + + jr_pb_configs[jr_name] = {'input_path': input_path, 'output_path': output_path} + seen.add(jr_name) + print(f" Found STATION_JR_PB config for {jr_name}") + + return jr_pb_configs + + def _extract_jpe_data(self) -> dict: + """Extract JPE data from DESC_IP sheet.""" + import re + desc_ip = self.desc_ip + network = self.network + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + inc_tokens = (getattr(cfg.extraction, 'jpe_include_tokens', None) if cfg else None) or ['JPE','_2_PE'] + exc_tokens = (getattr(cfg.extraction, 'jpe_exclude_tokens', None) if cfg else None) or ['3CH_PE'] + inc_pat = '|'.join(map(re.escape, inc_tokens)) + exc_pat = '|'.join(map(re.escape, exc_tokens)) + jpe_entries = desc_ip[desc_ip['DESCA'].str.contains(inc_pat, case=False, na=False, regex=True) & ~desc_ip['DESCA'].str.contains(exc_pat, case=False, na=False, regex=True)] + + # Collect configs here + jpe_configs: dict[str, dict[str, str]] = {} + + for _, row in jpe_entries.iterrows(): + desca = str(row['DESCA']) + jpe_name = desca + + # Extract base name purely via include tokens (no hardcoded default regex) + positions: list[int] = [] + desca_upper = desca.upper() + for tok in inc_tokens: + if not tok: + continue + t = str(tok).upper() + pos = desca_upper.find(t) + if pos != -1: + positions.append(pos) + if not positions: + continue + cut_index = min(positions) + base_name = desca[:cut_index].rstrip('_ ') + + # Find associated VFD with same base + vfd_mask = network['Name'].astype(str).str.contains(f'{base_name}_VFD', case=False, na=False) + vfd_rows = network[vfd_mask] + + if vfd_rows.empty: + continue + + vfd_name = vfd_rows.iloc[0]['Name'] + conveyor_ctrl = f"{vfd_name}.CTRL" + + # Find station (JR) with same base + station_mask = desc_ip['DESCA'].astype(str).str.contains(f'{base_name}_JR', case=False, na=False) + station_entries = desc_ip[station_mask] + + station_ctrl = None + for _, station_row in station_entries.iterrows(): + station_desca = str(station_row['DESCA']) + if 'PB' in station_desca and 'LT' not in station_desca: + station_ctrl = f"{station_desca}.CTRL" + break + + if not station_ctrl: + continue + + # Find parent communication fault + parent_comm_fault = None + if pd.notna(row['IO_PATH']): + io_path = str(row['IO_PATH']) + # Extract the device name from IO path (e.g., FL1014_2_VFD1 from FL1014_2_VFD1:I.In_2) + if ':' in io_path: + device_name = io_path.split(':')[0] + parent_comm_fault = f"{device_name}:I.ConnectionFaulted" + + if not parent_comm_fault: + # Default to VFD connection fault + parent_comm_fault = f"{vfd_name}:I.ConnectionFaulted" + + # Extract input path (configurable fallback) + try: + input_fallback = getattr(cfg.extraction, 'jpe_input_default', None) if cfg else None + except Exception: + input_fallback = None + input_fallback = input_fallback or 'In_2' + input_path = row['IO_PATH'] if pd.notna(row['IO_PATH']) else f"{vfd_name}:I.{input_fallback}" + + # Find beacon with same base and _A suffix + beacon_mask = desc_ip['DESCA'].str.contains(f'{base_name}.*_A(?!\\w)', case=False, na=False, regex=True) + beacon_entries = desc_ip[beacon_mask] + + beacon_output = None + if not beacon_entries.empty: + beacon_row = beacon_entries.iloc[0] + if pd.notna(beacon_row['IO_PATH']): + beacon_output = beacon_row['IO_PATH'] + + # If no beacon found, try to find FIOH with same base + if not beacon_output: + fioh_mask = desc_ip['TAGNAME'].astype(str).str.contains(f'{base_name}.*FIOH', case=False, na=False) + fioh_entries = desc_ip[fioh_mask] + + if not fioh_entries.empty: + fioh_name = fioh_entries.iloc[0]['TAGNAME'] + # Choose pin from config (prefer A_Pin_4 here to align with JPE default behavior) + try: + from .config import get_config as _get_cfg + pin = _get_cfg().extraction.beacon_segment_a_pin4 + except Exception: + pin = 'Connector_1_A_Pin_4' + beacon_output = f"{fioh_name}:O.ProcessDataOut.{pin}" + + if not beacon_output: + continue + + jpe_configs[jpe_name] = { + 'conveyor_ctrl': conveyor_ctrl, + 'station_ctrl': station_ctrl, + 'parent_comm_fault': parent_comm_fault, + 'input_path': input_path, + 'beacon_output': beacon_output + } + print(f" Found JPE config for {jpe_name}") + + return jpe_configs + + def _extract_fpe_data(self) -> Dict[str, Dict[str, str]]: + """Extract FPE (Full Photo Eye) data.""" + print("\n [DataLoader] Extracting FPE data...") + + desc_ip = self.desc_ip + network = self.network + + # Find FPE entries (configurable tokens) + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + fpe_tokens = (getattr(cfg.extraction, 'fpe_include_tokens', None) if cfg else None) or ['FPE','3CH_PE'] + fpe_pat = '|'.join(map(re.escape, fpe_tokens)) + fpe_mask = desc_ip['DESCA'].astype(str).str.contains(fpe_pat, na=False, case=False) + fpe_entries = desc_ip[fpe_mask] + + if fpe_entries.empty: + print(" No FPE entries found") + return {} + + print(f" Found {len(fpe_entries)} FPE entries") + + fpe_data = {} + + for _, row in fpe_entries.iterrows(): + desca = row['DESCA'] + fpe_name = desca.split()[0] # Get the FPE tag name + + # Extract base name (e.g., FL1014_2 from FL1014_3CH_PE1) + # We need to get FL1014_2, not FL1014_3CH + if '_3CH_' in fpe_name: + # Extract everything before _3CH_ + base_part = fpe_name.split('_3CH_')[0] + # Now extract the numeric suffix before 3CH (e.g., FL1014_2) + # Typically pattern is FL1014_2_3CH_PE1, so we need FL1014_2 + parts = base_part.split('_') + if len(parts) >= 2: + base_name = base_part # e.g., FL1014_2 + else: + base_name = parts[0] + '_2' # Default to _2 suffix + else: + # For FPE entries, extract base + parts = fpe_name.split('_') + if len(parts) >= 3 and parts[-1].startswith('PE'): + base_name = '_'.join(parts[:-1]) # Remove PE suffix + else: + base_name = '_'.join(parts[:2]) # Default pattern + + if not base_name: + print(f" [WARNING] Could not extract base name from {fpe_name}") + continue + + print(f"\n Processing {fpe_name} (base: {base_name})") + + # Find associated VFD from network sheet + vfd_name = f"{base_name}_VFD1" + vfd_row = network[network['Name'] == vfd_name] + + if vfd_row.empty: + # Try alternate pattern + vfd_name = f"{base_name}_VFD" + vfd_row = network[network['Name'] == vfd_name] + + if not vfd_row.empty: + conveyor_ctrl = f"{vfd_name}.CTRL" + else: + print(f" [WARNING] No VFD found for {base_name}") + vfd_name = f"{base_name}_VFD1" # Use default VFD name + conveyor_ctrl = f"{vfd_name}.CTRL" + + # Parent comm fault - should be the same VFD + parent_comm_fault = f"{vfd_name}:I.ConnectionFaulted" + + # Input path - extract from FPE's IO_PATH + io_path = row.get('IO_PATH', '') + if pd.notna(io_path) and io_path: + input_path = io_path + else: + # Default pattern + input_path = f"{vfd_name}:I.In_3" + + # Find beacon output with same base and "_B" in DESCA + beacon_mask = (desc_ip['DESCA'].astype(str).str.contains(base_name, na=False, case=False) & + desc_ip['DESCA'].astype(str).str.contains('_B', na=False, case=False)) + beacon_entries = desc_ip[beacon_mask] + + beacon_output = None + if not beacon_entries.empty: + # Use the first matching beacon + beacon_row = beacon_entries.iloc[0] + beacon_io_path = beacon_row.get('IO_PATH', '') + if pd.notna(beacon_io_path) and beacon_io_path: + beacon_output = beacon_io_path + else: + # Try to construct from beacon name + beacon_name = beacon_row['DESCA'].split()[0] + beacon_output = f"{beacon_name}:O.ProcessDataOut.Segment_1_Color_1" + + if not beacon_output: + # Default to FIOH output pattern (configurable pin) + fioh_name = f"{base_name}_FIOH1" + # Check if there's an S0 pattern FIOH + s0_match = desc_ip[desc_ip['DESCA'].astype(str).str.contains(f"S0.*{base_name[2:]}", regex=True, na=False)] + if not s0_match.empty: + s0_fioh = s0_match.iloc[0]['DESCA'].split()[0] + if 'FIOH' in s0_fioh: + fioh_name = s0_fioh + try: + from .config import get_config + pin = get_config().extraction.beacon_segment_b_pin2 + except Exception: + pin = 'Connector_1_B_Pin_2' + beacon_output = f"{fioh_name}:O.ProcessDataOut.{pin}" + + # Store configuration + fpe_data[fpe_name] = { + 'conveyor_ctrl': conveyor_ctrl, + 'parent_comm_fault': parent_comm_fault, + 'input_path': input_path, + 'beacon_output': beacon_output + } + + print(f" Conveyor: {conveyor_ctrl}") + print(f" Parent Fault: {parent_comm_fault}") + print(f" Input Path: {input_path}") + print(f" Beacon Output: {beacon_output}") + + print(f"\n Extracted {len(fpe_data)} FPE configurations") + return fpe_data + + @property + def pmm_data(self) -> Dict[str, Dict[str, Any]]: + """Extract PMM (Power Monitoring Module) data.""" + if 'pmm_data' in self._cache: + return self._cache['pmm_data'] + + pmm_data = self._extract_pmm_data() + self._cache['pmm_data'] = pmm_data + return pmm_data + + def _extract_pmm_data(self) -> Dict[str, Dict[str, Any]]: + """Extract PMM data from NETWORK and DESC sheets.""" + pmm_data = {} + + print("\n=== Extracting PMM Data ===") + + # Get PMM entries from NETWORK sheet (part number 1420-V2-ENT) + network = self.network + desc_ip = self.desc_ip + + # Find PMM entries via config token + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + target = (getattr(cfg.extraction, 'pmm_partnumber_exact', None) if cfg else None) or ['1420-V2-ENT'] + pmm_entries = network[network['PartNumber'].isin(target)] + + print(f"Found {len(pmm_entries)} PMM entries") + + for _, pmm in pmm_entries.iterrows(): + pmm_name = pmm['Name'] + print(f"\n Processing PMM: {pmm_name}") + + # Get DPM association + dpm_name = pmm.get('DPM', 'MCM') + print(f" DPM: {dpm_name}") + + # Find PWM (Phase/Power Monitor) fault input + # Extract base name (e.g., PDP11 from PDP11_PMM1) + base_name = pmm_name.split('_')[0] if '_' in pmm_name else pmm_name + + # Look for PWM fault input (e.g., PDP11_PWM1) + pwm_pattern = f"{base_name}_PWM" + pwm_entries = desc_ip[desc_ip['DESCA'].astype(str).str.contains(pwm_pattern, case=False, na=False)] + + pwm_fault_io = None + if not pwm_entries.empty: + # Get the IO path for PWM fault + pwm_row = pwm_entries.iloc[0] + io_path = pwm_row.get('IO_PATH', '') + if io_path: + pwm_fault_io = io_path + print(f" PWM Fault IO: {pwm_fault_io}") + else: + # Build from TAGNAME and TERM + tagname = pwm_row['TAGNAME'] + term = pwm_row['TERM'] + if tagname and term: + # Convert term format (IO12 -> Pt12) + pt_num = term.replace('IO', 'Pt') + pwm_fault_io = f"{tagname}:I.{pt_num}.Data" + print(f" PWM Fault IO (constructed): {pwm_fault_io}") + + # Store PMM configuration + pmm_data[pmm_name] = { + 'dpm': dpm_name, + 'pmm_fault_io': pwm_fault_io or f"{base_name}_FIO1:I.Pt12.Data", # Default to Pt12 + 'parent_comm_fault': f"{dpm_name}:I.ConnectionFaulted" + } + + print(f" Configuration stored for {pmm_name}") + + print(f"\n Extracted {len(pmm_data)} PMM configurations") + return pmm_data + + @property + def cb_monitor_data(self) -> Dict[str, Dict[str, Any]]: + """Extract CB_MONITOR data.""" + if 'cb_monitor_data' in self._cache: + return self._cache['cb_monitor_data'] + + cb_monitor_data = self._extract_cb_monitor_data() + self._cache['cb_monitor_data'] = cb_monitor_data + return cb_monitor_data + + def _extract_cb_monitor_data(self) -> Dict[str, Dict[str, Any]]: + """Extract CB_MONITOR data from DESC sheets.""" + cb_monitor_data = {} + + print("\n=== Extracting CB_MONITOR Data ===") + + desc_ip = self.desc_ip + + # Find CB entries (config-driven include/exclude) + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + inc = (getattr(cfg.extraction, 'cb_desca_include', None) if cfg else None) or ['CB'] + exc = (getattr(cfg.extraction, 'cb_desca_exclude', None) if cfg else None) or ['BCN'] + inc_pat = '|'.join(map(re.escape, inc)) + exc_pat = '|'.join(map(re.escape, exc)) + cb_entries = desc_ip[desc_ip['DESCA'].str.contains(inc_pat, case=False, na=False) & + ~desc_ip['DESCA'].str.contains(exc_pat, case=False, na=False)] + + # First, collect all CBs and group by PDP base name + pdp_cbs = {} + pdp_fios = {} # Track FIOs for each PDP to determine connection fault + + for _, cb in cb_entries.iterrows(): + # Extract PDP base name from DESCA (e.g., PDP11_CB1 -> PDP11) + desca = cb['DESCA'] + pdp_match = re.match(r'(PDP\d+)', desca) + if not pdp_match: + continue + + pdp_base = pdp_match.group(1) + + # Extract CB number from DESCA (e.g., PDP11_CB1 -> 1) + cb_match = re.search(r'CB(\d+)', desca) + if not cb_match: + continue + + cb_num = int(cb_match.group(1)) + + # Use IO_PATH from Excel if available, otherwise construct it + io_path = cb.get('IO_PATH', '') + if pd.isna(io_path) or not io_path: + # Fallback: construct IO path from TAGNAME and TERM + if pd.notna(cb['TERM']) and pd.notna(cb['TAGNAME']): + tagname = cb['TAGNAME'] + term = cb['TERM'] + if term.startswith('IO'): + pt_num = term.replace('IO', 'Pt') + io_path = f"{tagname}:I.{pt_num}.Data" + else: + # Skip if we can't construct a proper path + continue + else: + # Clean up the IO_PATH + io_path = str(io_path).strip() + + if io_path: + # Initialize PDP entry if needed + if pdp_base not in pdp_cbs: + pdp_cbs[pdp_base] = {} + pdp_fios[pdp_base] = set() + + # Store CB at its position + pdp_cbs[pdp_base][cb_num] = io_path + + # Track which FIO this PDP uses + if pd.notna(cb['TAGNAME']): + pdp_fios[pdp_base].add(cb['TAGNAME']) + + print(f"Found CB entries for {len(pdp_cbs)} PDPs") + + # Now create the CB_MONITOR configurations + for pdp_base in sorted(pdp_cbs.keys()): + cb_ios = pdp_cbs[pdp_base] + fios = pdp_fios[pdp_base] + + print(f"\n Processing {pdp_base} with {len(cb_ios)} CBs from {len(fios)} FIO(s)") + + # Create ordered list of CB IO paths (CB1 through CB26) + cb_list = [] + for i in range(1, 27): # CB1 through CB26 + if i in cb_ios: + cb_list.append(cb_ios[i]) + else: + cb_list.append('0') # Use 0 for missing CBs + + # Determine connection fault - use the first FIO alphabetically + if fios: + primary_fio = sorted(fios)[0] + connection_fault = f"{primary_fio}:I.ConnectionFaulted" + else: + # Fallback + connection_fault = f"{pdp_base}_FIO1:I.ConnectionFaulted" + + # Store configuration + cb_monitor_data[pdp_base] = { + 'cb_inputs': cb_list, + 'connection_fault': connection_fault + } + + print(f" Configured {pdp_base} with {len([cb for cb in cb_list if cb != '0'])} CBs") + print(f" CB positions: {sorted([i for i in cb_ios.keys()])}") + print(f" Connection fault: {connection_fault}") + + print(f"\n Extracted {len(cb_monitor_data)} CB_MONITOR configurations") + return cb_monitor_data + + @property + def flow_ctrl_data(self) -> Dict[str, Dict[str, Any]]: + """Extract FLOW_CTRL data.""" + if 'flow_ctrl_data' in self._cache: + return self._cache['flow_ctrl_data'] + + flow_ctrl_data = self._extract_flow_ctrl_data() + self._cache['flow_ctrl_data'] = flow_ctrl_data + return flow_ctrl_data + + def _extract_flow_ctrl_data(self) -> Dict[str, Dict[str, Any]]: + """Extract FLOW_CTRL data from NETWORK sheet.""" + flow_ctrl_data = {} + + print("\n=== Extracting FLOW_CTRL Data ===") + + network = self.network + + # Find entries by Name suffix only (no regex complexity) + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + lane_tokens = int(getattr(cfg.extraction, 'flow_ctrl_lane_tokens', 1)) if cfg else 1 + name_series = network['Name'].astype(str) + extendo_entries = network[name_series.str.endswith('_EX1', na=False)] + vfd_entries = network[name_series.str.endswith('_VFD1', na=False)] + + print(f"Found {len(extendo_entries)} EXTENDO devices") + print(f"Found {len(vfd_entries)} VFD devices") + + # Group by union of DPMs from EXTENDO and VFD so lanes without EXTENDO aren't dropped + dpm_keys = sorted(set(extendo_entries['DPM'].dropna().unique()).union(set(vfd_entries['DPM'].dropna().unique()))) + for dpm in dpm_keys: + dpm_extendos = extendo_entries[extendo_entries['DPM'] == dpm] if 'DPM' in extendo_entries.columns else extendo_entries.iloc[0:0] + dpm_vfds = vfd_entries[vfd_entries['DPM'] == dpm] if 'DPM' in vfd_entries.columns else vfd_entries.iloc[0:0] + + print(f"\n Processing DPM: {dpm}") + print(f" EXTENDOs: {len(dpm_extendos)}") + print(f" VFDs: {len(dpm_vfds)}") + + # Create a list of devices for this DPM + devices = [] + + # Add EXTENDOs + for _, extendo in dpm_extendos.iterrows(): + devices.append({ + 'name': extendo['Name'], + 'type': 'EXTENDO', + 'dpm': dpm + }) + + # Add VFDs and match them with their EXTENDO using simple lane tokens + + # Pre-index extendos by lane + ext_by_lane: dict[str, list[str]] = {} + for _, ext in dpm_extendos.iterrows(): + name = str(ext['Name']) + lane = '_'.join(name.split('_')[:lane_tokens]) if lane_tokens > 0 else '' + ext_by_lane.setdefault(lane, []).append(name) + + for _, vfd in dpm_vfds.iterrows(): + vfd_name = vfd['Name'] + lane = '_'.join(vfd_name.split('_')[:lane_tokens]) if lane_tokens > 0 else '' + # Prefer first extendo in same lane if available + matching_extendo = None + if lane and lane in ext_by_lane and ext_by_lane[lane]: + matching_extendo = sorted(ext_by_lane[lane], key=lambda n: n)[0] + + devices.append({ + 'name': vfd_name, + 'type': 'VFD', + 'dpm': dpm, + 'depends_on': matching_extendo + }) + + # continue collecting; store after loop + + # Store configuration by DPM (once per DPM) + flow_ctrl_data[dpm] = { + 'devices': devices + } + + print(f"\n Extracted FLOW_CTRL configurations for {len(flow_ctrl_data)} DPMs") + return flow_ctrl_data + + @property + def speed_ctrl_data(self) -> List[str]: + """Extract SPEED_CTRL data (list of VFDs).""" + if 'speed_ctrl_data' in self._cache: + return self._cache['speed_ctrl_data'] + + speed_ctrl_data = self._extract_speed_ctrl_data() + self._cache['speed_ctrl_data'] = speed_ctrl_data + return speed_ctrl_data + + def _extract_speed_ctrl_data(self) -> List[str]: + """Extract VFD names for SPEED_CTRL from NETWORK sheet.""" + vfd_names = [] + + print("\n=== Extracting SPEED_CTRL Data ===") + + network = self.network + + # Find all VFD entries by configurable prefix + cfg = None + try: + from .config import get_config + cfg = get_config() + except Exception: + pass + prefixes = (getattr(cfg.extraction, 'speed_ctrl_partnumber_prefix', None) if cfg else None) or ['35S'] + vfd_mask = False + for p in prefixes: + vfd_mask = vfd_mask | network['PartNumber'].str.startswith(p, na=False) + vfd_entries = network[vfd_mask] + + print(f"Found {len(vfd_entries)} VFD devices for speed control") + + # Extract VFD names + for _, vfd in vfd_entries.iterrows(): + vfd_name = vfd['Name'] + vfd_names.append(vfd_name) + + # Sort for consistent output + vfd_names.sort(key=natural_sort_key) + + print(f" VFDs: {', '.join(vfd_names[:5])}..." if len(vfd_names) > 5 else f" VFDs: {', '.join(vfd_names)}") + print(f"\n Extracted {len(vfd_names)} VFDs for SPEED_CTRL") + return vfd_names @classmethod def from_excel(cls, excel_path: str | Path, zones_dict: Optional[List[Dict[str, str]]] = None) -> "DataLoader": diff --git a/Routines Generator/src/generators/__pycache__/main_program.cpython-312.pyc b/Routines Generator/src/generators/__pycache__/main_program.cpython-312.pyc index 1615831..2092764 100644 Binary files a/Routines Generator/src/generators/__pycache__/main_program.cpython-312.pyc and b/Routines Generator/src/generators/__pycache__/main_program.cpython-312.pyc differ diff --git a/Routines Generator/src/generators/__pycache__/safety_program.cpython-312.pyc b/Routines Generator/src/generators/__pycache__/safety_program.cpython-312.pyc index 03d8f44..5b511a1 100644 Binary files a/Routines Generator/src/generators/__pycache__/safety_program.cpython-312.pyc and b/Routines Generator/src/generators/__pycache__/safety_program.cpython-312.pyc differ diff --git a/Routines Generator/src/generators/main_program.py b/Routines Generator/src/generators/main_program.py index f080a02..2262128 100644 --- a/Routines Generator/src/generators/main_program.py +++ b/Routines Generator/src/generators/main_program.py @@ -2,16 +2,30 @@ import xml.etree.ElementTree as ET from datetime import datetime +import re from pathlib import Path from typing import Optional, List, Dict import pandas as pd from ..data_loader import DataLoader +from ..config import get_config from ..routines.estop_check import create_estop_check_routine from ..routines.dpm import create_dpm_routine from ..routines.fiom import create_fiom_routine from ..routines.fioh import create_fioh_routine +from ..routines.apf import generate_apf_routine +from ..routines.extendo import generate_extendo_routine +from ..routines.d2c_chute import generate_d2c_chute_routine +from ..routines.pb_chute import generate_pb_chute_routine +from ..routines.station_jr_chute import generate_station_jr_chute_routine +from ..routines.station_jr_pb import generate_station_jr_pb_routine +from ..routines.jpe import generate_jpe_routine +from ..routines.fpe import generate_fpe_routine +from ..routines.pmm import generate_pmm_routine +from ..routines.cb_monitor import generate_cb_monitor_routine +from ..routines.flow_control import generate_flow_control_routine +from ..routines.speed_control import generate_speed_control_routine from ..utils.common import format_xml_to_match_original from ..writers.xml_tag_writer import create_limited_tag_xml_elements @@ -36,26 +50,31 @@ class LimitedMainProgramGenerator: else: xl_time = current_date.strftime('%a %b %d %H:%M:%S %Y') + cfg = get_config() root = ET.Element('RSLogix5000Content', attrib={ - 'SchemaRevision': '1.0', - 'SoftwareRevision': '36.00', + 'SchemaRevision': cfg.xml.schema_revision, + 'SoftwareRevision': cfg.xml.software_revision, 'TargetName': 'MainProgram', 'TargetType': 'Program', - 'TargetClass': 'Standard', + 'TargetClass': cfg.xml.target_class, 'ContainsContext': 'true', 'ExportDate': xl_time, - 'ExportOptions': 'References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans', + 'ExportOptions': cfg.xml.export_options, }) - controller = ET.SubElement(root, 'Controller', Use='Context', Name='MTN6_MCM01') + controller = ET.SubElement(root, 'Controller', Use='Context', Name=cfg.xml.controller_name) # Add empty AddOnInstructionDefinitions element ET.SubElement(controller, 'AddOnInstructionDefinitions') # Add Tags section at controller level tags_section = ET.SubElement(controller, 'Tags') - print(" Generating limited controller tags...", flush=True) - tag_elements = create_limited_tag_xml_elements(self.loader.excel_path, data_loader=self.loader) + print(" Generating limited controller tags (safety-only)...", flush=True) + # Build tags using config-driven toggles only + tag_elements = create_limited_tag_xml_elements( + self.loader.excel_path, + data_loader=self.loader, + ) # Add all tag elements to the Tags section for tag_elem in tag_elements: @@ -65,54 +84,118 @@ class LimitedMainProgramGenerator: programs = ET.SubElement(controller, 'Programs', Use='Context') program = ET.SubElement(programs, 'Program', Use='Target', Name='MainProgram', - TestEdits='false', MainRoutineName='MainRoutine', Disabled='false', - Class='Standard', UseAsFolder='false') + TestEdits='false', MainRoutineName=cfg.routines.main_routine_name, Disabled='false', + Class=cfg.xml.target_class, UseAsFolder='false') ET.SubElement(program, 'Tags') routines_el = ET.SubElement(program, 'Routines') print(" Creating limited MainProgram routines...", flush=True) - self._create_main_routine(routines_el) - print(" [SUCCESS] Created MainRoutine", flush=True) + # Config-driven routine plan support (optional) + cfg = get_config() + plan_entries = [ + e for e in getattr(cfg, 'routine_plan', []) + if e.enabled and e.program == 'MainProgram' + ] + plan_entries.sort(key=lambda e: e.order) - self._create_safety_tag_map_routine(routines_el) - print(" [SUCCESS] Created R000_SAFETY_TAG_MAP routine", flush=True) + def _apply_filters(df, flt: dict): + if df is None or not isinstance(flt, dict) or df.empty: + return df + result = df + # DESCA filters + inc = flt.get('desca_include') + if inc: + result = result[result['DESCA'].str.contains('|'.join(inc), case=False, na=False)] + exc = flt.get('desca_exclude') + if exc: + result = result[~result['DESCA'].str.contains('|'.join(exc), case=False, na=False)] + # TAGNAME filters + inc_t = flt.get('tagname_include') + if inc_t and 'TAGNAME' in result.columns: + result = result[result['TAGNAME'].str.contains('|'.join(inc_t), case=False, na=False)] + exc_t = flt.get('tagname_exclude') + if exc_t and 'TAGNAME' in result.columns: + result = result[~result['TAGNAME'].str.contains('|'.join(exc_t), case=False, na=False)] + return result + + def run_default(): + self._create_main_routine(routines_el) + print(" [SUCCESS] Created MainRoutine", flush=True) + self._create_safety_tag_map_routine(routines_el) + print(" [SUCCESS] Created R000_SAFETY_TAG_MAP routine", flush=True) + create_estop_check_routine(routines_el, self.loader.epc, self.loader.sto, None) + print(" [SUCCESS] Created R100_ESTOP_CHECK routine", flush=True) - create_estop_check_routine(routines_el, self.loader.epc, self.loader.sto, self.loader.zones) - print(" [SUCCESS] Created R100_ESTOP_CHECK routine", flush=True) + # Respect routine plan strictly: if a plan exists in config, do NOT fall back to defaults + if getattr(cfg, 'routine_plan', None): + if plan_entries: + # Map config plugin names to concrete generators + generated = [] + for entry in plan_entries: + plugin = entry.plugin + if plugin == 'main_routine': + self._create_main_routine(routines_el) + generated.append('main_routine') + elif plugin == 'safety_tag_map': + # Optional filtering for RST via filters + flt = getattr(cfg, 'filters', None).for_routine(entry.name) if 'cfg' in locals() else {} + rst_df = _apply_filters(self.loader.rst, flt) + self._create_safety_tag_map_routine(routines_el, rst_df=rst_df) + generated.append('safety_tag_map') + elif plugin == 'estop_check': + flt = getattr(cfg, 'filters', None).for_routine(entry.name) if 'cfg' in locals() else {} + epc = _apply_filters(self.loader.epc, flt) + sto = _apply_filters(self.loader.sto, flt) + create_estop_check_routine(routines_el, epc, sto, None) + generated.append('estop_check') + else: + # Unknown plugin for MainProgram – skip silently + continue + print(f" [SUCCESS] Created MainProgram routines (config-driven): {', '.join(generated)}", flush=True) + else: + print(" [INFO] Routine plan present; no MainProgram routines enabled -> skipping defaults", flush=True) + else: + run_default() return root @staticmethod def _create_main_routine(routines_el: ET.Element) -> None: - routine = ET.SubElement(routines_el, 'Routine', Name='MainRoutine', Type='RLL') + from ..config import get_config + cfg_local = get_config() + routine = ET.SubElement(routines_el, 'Routine', Name=cfg_local.routines.main_routine_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') text = ET.SubElement(rung, 'Text') - # Only JSR calls for safety_tag_map and estop_check - text.text = '[JSR(R000_SAFETY_TAG_MAP,0) ,JSR(R100_ESTOP_CHECK,0) ];' + # Only JSR calls for safety_tag_map and estop_check (config-driven names) + safety_tag_map_name = cfg_local.routines.name_map.get('safety_tag_map', 'R130_SAFETY_TAG_MAP') + estop_check_name = cfg_local.routines.name_map.get('estop_check', 'R120_ESTOP_CHECK') + text.text = f'[JSR({safety_tag_map_name},0) ,JSR({estop_check_name},0) ];' - def _create_safety_tag_map_routine(self, routines_el: ET.Element) -> None: + def _create_safety_tag_map_routine(self, routines_el: ET.Element, rst_df: Optional[pd.DataFrame] = None) -> None: """Create the R000_SAFETY_TAG_MAP routine with JSR to SafetyProgram ESTOP ZONES.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R000_SAFETY_TAG_MAP', Type='RLL') + cfg_local = get_config() + routine = ET.SubElement(routines_el, 'Routine', Name=cfg_local.routines.safety_tag_map_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') # Determine subsystem from Excel file path to get the correct MCM tag name import re excel_path_str = str(self.loader.excel_path) subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) - subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM01" + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" # MCM mapping rung with subsystem-specific naming rung_num = 0 rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') text = ET.SubElement(rung, 'Text') - text.text = f'XIC(Local:5:I.Data.0)OTE({subsystem}_S_PB);' + text.text = f'XIC({cfg_local.routines.mcm_input_address})OTE({subsystem}_S_PB);' rung_num += 1 - # Push buttons from RST sheet + # Push buttons from RST sheet (allow override via filtered df) + df = rst_df if rst_df is not None else self.loader.rst pb_count = 0 - for _, row in self.loader.rst.iterrows(): + for _, row in df.iterrows(): if not isinstance(row['DESCA'], str): continue # Skip GS1 patterns @@ -153,18 +236,22 @@ class DescIPMainProgramGenerator: def _build_xml_tree(self) -> ET.Element: """Build MainProgram XML tree using DESC_IP extracted data.""" - # Build basic XML structure - root = ET.Element('RSLogix5000Content') - root.set('SchemaRevision', '1.0') - root.set('SoftwareRevision', '36.00') - root.set('TargetName', 'MainProgram') - root.set('TargetType', 'Program') - root.set('ContainsContext', 'true') - root.set('ExportDate', 'Wed Jul 03 11:47:56 2024') - - controller = ET.SubElement(root, 'Controller', Use='Context', Name='MainProgram') + # Build basic XML structure (config-driven) + cfg = get_config() + from datetime import datetime + xl_time = datetime.now().strftime('%a %b %d %H:%M:%S %Y') + root = ET.Element('RSLogix5000Content', attrib={ + 'SchemaRevision': cfg.xml.schema_revision, + 'SoftwareRevision': cfg.xml.software_revision, + 'TargetName': 'MainProgram', + 'TargetType': 'Program', + 'ContainsContext': 'true', + 'ExportDate': xl_time, + 'ExportOptions': cfg.xml.export_options, + }) + controller = ET.SubElement(root, 'Controller', Use='Context', Name=cfg.xml.controller_name) programs = ET.SubElement(controller, 'Programs') - program = ET.SubElement(programs, 'Program', Use='Target', Name='MainProgram', Type='Normal') + program = ET.SubElement(programs, 'Program', Use='Target', Name='MainProgram', Type=cfg.xml.target_class) routines_el = ET.SubElement(program, 'Routines') # Main routine with JSRs (safety-only) @@ -178,24 +265,35 @@ class DescIPMainProgramGenerator: @staticmethod def _create_main_routine(routines_el: ET.Element) -> None: - routine = ET.SubElement(routines_el, 'Routine', Name='MainRoutine', Type='RLL') + from ..config import get_config + cfg_local = get_config() + routine = ET.SubElement(routines_el, 'Routine', Name=cfg_local.routines.main_routine_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') text = ET.SubElement(rung, 'Text') - # Only JSR calls for safety_tag_map and estop_check - text.text = '[JSR(R000_SAFETY_TAG_MAP,0) ,JSR(R100_ESTOP_CHECK,0) ];' + # Only JSR calls for safety_tag_map and estop_check (config-driven) + safety_tag_map_name = cfg_local.routines.name_map.get('safety_tag_map', 'R000_SAFETY_TAG_MAP') + estop_check_name = cfg_local.routines.name_map.get('estop_check', 'R100_ESTOP_CHECK') + text.text = f'[JSR({safety_tag_map_name},0) ,JSR({estop_check_name},0) ];' def _create_desc_ip_safety_tag_map_routine(self, routines_el: ET.Element) -> None: """Create R000_SAFETY_TAG_MAP routine from DESC_IP extracted data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R000_SAFETY_TAG_MAP', Type='RLL') + cfg_local = get_config() + safety_tag_map_name = cfg_local.routines.name_map.get('safety_tag_map', 'R000_SAFETY_TAG_MAP') + routine = ET.SubElement(routines_el, 'Routine', Name=safety_tag_map_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung_num = 0 - # MCM mapping rung (always the same) + # MCM mapping rung (config-driven address and subsystem tag) rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') text = ET.SubElement(rung, 'Text') - text.text = 'XIC(Local:5:I.Data.0)OTE(MCM_S_PB);' + import re + excel_path_str = str(self.loader.excel_path) + subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" + mcm_addr = cfg_local.routines.mcm_input_address + text.text = f'XIC({mcm_addr})OTE({subsystem}_S_PB);' rung_num += 1 # Extract RST devices for push button mappings @@ -222,7 +320,9 @@ class DescIPMainProgramGenerator: def _create_desc_ip_estop_check_routine(self, routines_el: ET.Element) -> None: """Create R100_ESTOP_CHECK routine from DESC_IP extracted data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R100_ESTOP_CHECK', Type='RLL') + cfg_local = get_config() + estop_check_name = cfg_local.routines.name_map.get('estop_check', 'R100_ESTOP_CHECK') + routine = ET.SubElement(routines_el, 'Routine', Name=estop_check_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung_num = 0 @@ -283,7 +383,7 @@ class FullMainProgramGenerator(LimitedMainProgramGenerator): 'ExportOptions': 'References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans', }) - controller = ET.SubElement(root, 'Controller', Use='Context', Name='MTN6_MCM01') + controller = ET.SubElement(root, 'Controller', Use='Context', Name='MTN6_MCM') # Add empty AddOnInstructionDefinitions element ET.SubElement(controller, 'AddOnInstructionDefinitions') @@ -307,35 +407,172 @@ class FullMainProgramGenerator(LimitedMainProgramGenerator): routines_el = ET.SubElement(program, 'Routines') print(" Creating full MainProgram routines...", flush=True) - - self._create_main_routine_extended(routines_el) - print(" [SUCCESS] Created MainRoutine with DPM calls", flush=True) - - self._create_safety_tag_map_routine(routines_el) - print(" [SUCCESS] Created R000_SAFETY_TAG_MAP routine", flush=True) - - create_estop_check_routine(routines_el, self.loader.epc, self.loader.sto, self.loader.zones) - print(" [SUCCESS] Created R100_ESTOP_CHECK routine", flush=True) - - # Add DPM routine - create_dpm_routine(routines_el, self.loader.desc_ip) - print(" [SUCCESS] Created R020_DPM routine", flush=True) - - # Add FIOM routine - create_fiom_routine(routines_el, self.loader.desc_ip) - print(" [SUCCESS] Created R030_FIOM routine", flush=True) - - # Add FIOH routine - create_fioh_routine(routines_el, self.loader.desc_ip) - print(" [SUCCESS] Created R031_FIOH routine", flush=True) + + # Config-driven support: if routine_plan is provided, honor entries for MainProgram + cfg = get_config() + plan_entries = [ + e for e in getattr(cfg, 'routine_plan', []) + if e.enabled and e.program == 'MainProgram' + ] + plan_entries.sort(key=lambda e: e.order) + + def run_defaults(): + self._create_main_routine_extended(routines_el) + print(" [SUCCESS] Created MainRoutine with DPM calls", flush=True) + self._create_safety_tag_map_routine(routines_el) + print(" [SUCCESS] Created R000_SAFETY_TAG_MAP routine", flush=True) + create_estop_check_routine(routines_el, self.loader.epc, self.loader.sto, None) + print(" [SUCCESS] Created R100_ESTOP_CHECK routine", flush=True) + create_dpm_routine(routines_el, self.loader.desc_ip) + print(" [SUCCESS] Created R020_DPM routine", flush=True) + create_fiom_routine(routines_el, self.loader.desc_ip) + print(" [SUCCESS] Created R030_FIOM routine", flush=True) + create_fioh_routine(routines_el, self.loader.desc_ip) + print(" [SUCCESS] Created R031_FIOH routine", flush=True) + apf_routine_xml = generate_apf_routine(self.loader) + if apf_routine_xml: + routines_el.append(ET.fromstring(apf_routine_xml)) + print(" [SUCCESS] Created R040_APF routine", flush=True) + extendo_routine_xml = generate_extendo_routine(self.loader) + if extendo_routine_xml: + routines_el.append(ET.fromstring(extendo_routine_xml)) + print(" [SUCCESS] Created R041_EXTENDO routine", flush=True) + flow_ctrl_routine = generate_flow_control_routine(self.loader) + if flow_ctrl_routine is not None: + routines_el.append(flow_ctrl_routine) + print(" [SUCCESS] Created R050_FLOW_CTRL routine", flush=True) + speed_ctrl_routine = generate_speed_control_routine(self.loader) + if speed_ctrl_routine is not None: + routines_el.append(speed_ctrl_routine) + print(" [SUCCESS] Created R051_SPEED_CTRL routine", flush=True) + d2c_routine = generate_d2c_chute_routine(self.loader) + if d2c_routine is not None: + routines_el.append(d2c_routine) + print(" [SUCCESS] Created R042_D2C_CHUTE routine", flush=True) + pb_chute_routine = generate_pb_chute_routine(self.loader) + if pb_chute_routine is not None: + routines_el.append(pb_chute_routine) + print(" [SUCCESS] Created R043_PB_CHUTE routine", flush=True) + station_jr_chute_routine = generate_station_jr_chute_routine(self.loader) + if station_jr_chute_routine is not None: + routines_el.append(station_jr_chute_routine) + print(" [SUCCESS] Created R044_STATION_JR_CHUTE routine", flush=True) + station_jr_pb_routine = generate_station_jr_pb_routine(self.loader) + if station_jr_pb_routine is not None: + routines_el.append(station_jr_pb_routine) + print(" [SUCCESS] Created R090_STATION_JR_PB routine", flush=True) + jpe_routine = generate_jpe_routine(self.loader) + if jpe_routine is not None: + routines_el.append(jpe_routine) + print(" [SUCCESS] Created R100_JPE routine", flush=True) + fpe_routine = generate_fpe_routine(self.loader) + if fpe_routine is not None: + routines_el.append(fpe_routine) + print(" [SUCCESS] Created R101_FPE routine", flush=True) + pmm_routine = generate_pmm_routine(self.loader) + if pmm_routine is not None: + routines_el.append(pmm_routine) + print(" [SUCCESS] Created R060_PMM routine", flush=True) + cb_monitor_routine = generate_cb_monitor_routine(self.loader) + if cb_monitor_routine is not None: + routines_el.append(cb_monitor_routine) + print(" [SUCCESS] Created R070_CB_MONITOR routine", flush=True) + + # Respect routine plan strictly: if a plan exists in config, do NOT fall back to defaults + if getattr(cfg, 'routine_plan', None): + if plan_entries: + # Helper to apply routine-level filters to a DataFrame + def _apply_filters(df: pd.DataFrame, flt: dict) -> pd.DataFrame: + if df is None or not isinstance(flt, dict) or df.empty: + return df + result = df + inc = flt.get('desca_include') + if inc and 'DESCA' in result.columns: + result = result[result['DESCA'].str.contains('|'.join(inc), case=False, na=False)] + exc = flt.get('desca_exclude') + if exc and 'DESCA' in result.columns: + result = result[~result['DESCA'].str.contains('|'.join(exc), case=False, na=False)] + inc_t = flt.get('tagname_include') + if inc_t and 'TAGNAME' in result.columns: + result = result[result['TAGNAME'].str.contains('|'.join(inc_t), case=False, na=False)] + exc_t = flt.get('tagname_exclude') + if exc_t and 'TAGNAME' in result.columns: + result = result[~result['TAGNAME'].str.contains('|'.join(exc_t), case=False, na=False)] + return result + + generated = [] + # Prepare plugin manager context once + from ..plugin_system import get_default_registry, RoutineManager, RoutineContext + plugin_context = RoutineContext(self.loader, cfg, routines_el, program, {'params': {}, 'filters': {}}) + plugin_manager = RoutineManager(plugin_context, get_default_registry()) + for entry in plan_entries: + plugin = entry.plugin + # Routine-specific filter set (from config) + flt = getattr(cfg, 'filters', None).for_routine(entry.name) if 'cfg' in locals() else {} + if plugin == 'main_routine': + self._create_main_routine_extended(routines_el) + generated.append('main_routine') + elif plugin == 'safety_tag_map': + self._create_safety_tag_map_routine(routines_el) + generated.append('safety_tag_map') + elif plugin == 'estop_check': + # Use plugin path for ESTOP_CHECK + plugin_context.metadata['params'] = entry.params or {} + plugin_context.metadata['filters'] = flt or {} + ok = plugin_manager.generate_routine('estop_check') + if ok: + generated.append('estop_check') + else: + # Fallback to direct call + epc_df = _apply_filters(self.loader.epc, flt) + sto_df = _apply_filters(self.loader.sto, flt) + create_estop_check_routine(routines_el, epc_df, sto_df, None) + generated.append('estop_check') + else: + # Plugin-based generation for all other entries + plugin_context.metadata['params'] = entry.params or {} + plugin_context.metadata['filters'] = flt or {} + ok = plugin_manager.generate_routine(plugin) + if ok: + generated.append(plugin) + else: + # Unknown/missing plugin – skip + continue + print(f" [SUCCESS] Created MainProgram routines (config-driven): {', '.join(generated)}", flush=True) + else: + print(" [INFO] Routine plan present; no MainProgram routines enabled -> skipping defaults", flush=True) + else: + run_defaults() return root def _create_main_routine_extended(self, routines_el: ET.Element) -> None: """Create extended MainRoutine with JSR calls for all routines including DPM.""" - routine = ET.SubElement(routines_el, 'Routine', Name='MainRoutine', Type='RLL') + from ..config import get_config + cfg_local = get_config() + routine = ET.SubElement(routines_el, 'Routine', Name=cfg_local.routines.main_routine_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') text = ET.SubElement(rung, 'Text') - # Extended JSR calls including DPM, FIOM, and FIOH routines - text.text = '[JSR(R000_SAFETY_TAG_MAP,0) ,JSR(R020_DPM,0) ,JSR(R030_FIOM,0) ,JSR(R031_FIOH,0) ,JSR(R100_ESTOP_CHECK,0) ];' \ No newline at end of file + # Extended JSR calls including DPM, FIOM, FIOH, APF, EXTENDO, and D2C routines using config names + nm = cfg_local.routines.name_map + calls = [ + nm.get('safety_tag_map', 'R000_SAFETY_TAG_MAP'), + nm.get('dpm', 'R020_DPM'), + nm.get('fiom', 'R030_FIOM'), + nm.get('fioh', 'R031_FIOH'), + nm.get('apf', 'R040_APF'), + nm.get('extendo', 'R041_EXTENDO'), + nm.get('flow_ctrl', 'R050_FLOW_CTRL'), + nm.get('speed_ctrl', 'R051_SPEED_CTRL'), + nm.get('d2c_chute', 'R042_D2C_CHUTE'), + nm.get('pb_chute', 'R043_PB_CHUTE'), + nm.get('station_jr_chute', 'R044_STATION_JR_CHUTE'), + nm.get('pmm', 'R060_PMM'), + nm.get('cb_monitor', 'R070_CB_MONITOR'), + nm.get('station_jr_pb', 'R090_STATION_JR_PB'), + nm.get('jpe', 'R100_JPE'), + nm.get('fpe', 'R101_FPE'), + nm.get('estop_check', 'R100_ESTOP_CHECK'), + ] + text.text = '[' + ' ,'.join(f'JSR({c},0)' for c in calls) + ' ];' \ No newline at end of file diff --git a/Routines Generator/src/generators/safety_program.py b/Routines Generator/src/generators/safety_program.py index 587f9db..e68ba27 100644 --- a/Routines Generator/src/generators/safety_program.py +++ b/Routines Generator/src/generators/safety_program.py @@ -2,16 +2,17 @@ from __future__ import annotations import xml.etree.ElementTree as ET from datetime import datetime +import re from pathlib import Path from typing import Optional, List, Dict import pandas as pd from ..data_loader import DataLoader +from ..config import get_config from ..routines.inputs import create_inputs_routine from ..routines.outputs import create_outputs_routine from ..routines.resets import create_resets_routine from ..routines.estops import create_estops_routine -from ..routines.zones import create_zones_routine from ..utils.common import format_xml_to_match_original, natural_sort_key from ..utils.tag_utils import device_base_from_desca @@ -30,7 +31,9 @@ def create_safety_tag_map(program_el: ET.Element, safety_tags: set[str], beacon_ print(" - WARNING: No safety tags found for SafetyTagMap.", flush=True) return - map_text = ",".join(f"{tag}=SFT_{tag}" for tag in all_tags) + from ..config import get_config + prefix = get_config().routines.safety_tag_prefix + map_text = ",".join(f"{tag}={prefix}{tag}" for tag in all_tags) safety_map = ET.SubElement(program_el, "SafetyTagMap") safety_map.text = map_text @@ -55,21 +58,22 @@ class LimitedSafetyProgramGenerator: else: xl_time = current_date.strftime('%a %b %d %H:%M:%S %Y') + cfg = get_config() root = ET.Element('RSLogix5000Content', attrib={ - 'SchemaRevision': '1.0', - 'SoftwareRevision': '36.00', + 'SchemaRevision': cfg.xml.schema_revision, + 'SoftwareRevision': cfg.xml.software_revision, 'TargetName': 'SafetyProgram', 'TargetType': 'Program', 'TargetClass': 'Safety', 'ContainsContext': 'true', 'ExportDate': xl_time, - 'ExportOptions': 'References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans', + 'ExportOptions': cfg.xml.export_options, }) - controller = ET.SubElement(root, 'Controller', Use='Context', Name='MTN6_MCM01') + controller = ET.SubElement(root, 'Controller', Use='Context', Name=cfg.xml.controller_name) programs = ET.SubElement(controller, 'Programs', Use='Context') program = ET.SubElement(programs, 'Program', Use='Target', Name='SafetyProgram', - TestEdits='false', MainRoutineName='MainRoutine', Disabled='false', + TestEdits='false', MainRoutineName=cfg.routines.main_routine_name, Disabled='false', Class='Safety', UseAsFolder='false') ET.SubElement(program, 'Tags') routines_el = ET.SubElement(program, 'Routines') @@ -82,7 +86,7 @@ class LimitedSafetyProgramGenerator: create_safety_tag_map( program, self.loader.safety_tags_from_pb, - set() # No beacon tags in limited mode + set() ) print(" [SUCCESS] Created SafetyTagMap", flush=True) @@ -90,21 +94,96 @@ class LimitedSafetyProgramGenerator: import re excel_path_str = str(self.loader.excel_path) subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) - subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM01" + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" - create_inputs_routine(routines_el, self.loader.epc, ignore_estop1ok=self.ignore_estop1ok) - print(" [SUCCESS] Created R010_INPUTS routine", flush=True) - - create_resets_routine(routines_el, self.loader.rst, self.loader.epc, subsystem) - print(" [SUCCESS] Created R012_RESETS routine", flush=True) + # Config-driven support: if routine_plan is provided, honor entries for SafetyProgram + cfg = get_config() + plan_entries = [ + e for e in getattr(cfg, 'routine_plan', []) + if e.enabled and e.program == 'SafetyProgram' + ] + plan_entries.sort(key=lambda e: e.order) - # Only the 5 essential safety routines - create_outputs_routine(routines_el, self.loader.zones, self.loader.sto) - create_estops_routine(routines_el, self.loader.epc, self.loader.rst, subsystem, ignore_estop1ok=self.ignore_estop1ok) - print(" [SUCCESS] Created R020_ESTOPS routine", flush=True) - - create_zones_routine(routines_el, self.loader.zones, self.loader.epc, ignore_estop1ok=self.ignore_estop1ok) - print(" [SUCCESS] Created R030_ZONES routine", flush=True) + def _apply_filters(df, flt: dict): + if df is None or not isinstance(flt, dict) or df.empty: + return df + result = df + # Global-style includes/excludes + inc = flt.get('desca_include') + if inc and 'DESCA' in result.columns: + result = result[result['DESCA'].str.contains('|'.join(inc), case=False, na=False)] + exc = flt.get('desca_exclude') + if exc and 'DESCA' in result.columns: + result = result[~result['DESCA'].str.contains('|'.join(exc), case=False, na=False)] + inc_t = flt.get('tagname_include') + if inc_t and 'TAGNAME' in result.columns: + result = result[result['TAGNAME'].str.contains('|'.join(inc_t), case=False, na=False)] + exc_t = flt.get('tagname_exclude') + if exc_t and 'TAGNAME' in result.columns: + result = result[~result['TAGNAME'].str.contains('|'.join(exc_t), case=False, na=False)] + # Column-specific filters: { columns: { COL: {include:[], exclude:[]} } } + cols = flt.get('columns', {}) or {} + for col, specs in cols.items(): + if col in result.columns: + col_inc = specs.get('include') if isinstance(specs, dict) else None + col_exc = specs.get('exclude') if isinstance(specs, dict) else None + if col_inc: + result = result[result[col].astype(str).str.contains('|'.join(col_inc), case=False, na=False)] + if col_exc: + result = result[~result[col].astype(str).str.contains('|'.join(col_exc), case=False, na=False)] + # Drop duplicates by configured columns + dd = flt.get('drop_duplicates_by') + if dd: + result = result.drop_duplicates(subset=[c for c in dd if c in result.columns]) + return result + + def run_defaults(): + # Apply global filters when no routine plan is defined + global_flt = getattr(cfg, 'filters', None).global_filters if 'cfg' in locals() else {} + epc = _apply_filters(self.loader.epc, global_flt or {}) + rst = _apply_filters(self.loader.rst, global_flt or {}) + sto = _apply_filters(self.loader.sto, global_flt or {}) + + create_inputs_routine(routines_el, epc, ignore_estop1ok=self.ignore_estop1ok) + print(" [SUCCESS] Created R010_INPUTS routine", flush=True) + create_resets_routine(routines_el, rst, epc, subsystem) + print(" [SUCCESS] Created R012_RESETS routine", flush=True) + create_outputs_routine(routines_el, pd.DataFrame(), sto) + create_estops_routine(routines_el, epc, rst, subsystem, ignore_estop1ok=self.ignore_estop1ok) + print(" [SUCCESS] Created R020_ESTOPS routine", flush=True) + # ZONES routine removed + + # Respect routine plan strictly: if a plan exists in config, do NOT fall back to defaults + if getattr(cfg, 'routine_plan', None): + if plan_entries: + generated = [] + for entry in plan_entries: + flt = getattr(cfg, 'filters', None).for_routine(entry.name) if 'cfg' in locals() else {} + if entry.plugin == 'inputs': + epc = _apply_filters(self.loader.epc, flt) + create_inputs_routine(routines_el, epc, ignore_estop1ok=self.ignore_estop1ok) + generated.append('inputs') + elif entry.plugin == 'resets': + rst = _apply_filters(self.loader.rst, flt) + epc = _apply_filters(self.loader.epc, flt) + create_resets_routine(routines_el, rst, epc, subsystem) + generated.append('resets') + elif entry.plugin == 'outputs': + sto = _apply_filters(self.loader.sto, flt) + create_outputs_routine(routines_el, pd.DataFrame(), sto) + generated.append('outputs') + elif entry.plugin == 'estops': + epc = _apply_filters(self.loader.epc, flt) + rst = _apply_filters(self.loader.rst, flt) + create_estops_routine(routines_el, epc, rst, subsystem, ignore_estop1ok=self.ignore_estop1ok) + generated.append('estops') + else: + continue + print(f" [SUCCESS] Created SafetyProgram routines (config-driven): {', '.join(generated)}", flush=True) + else: + print(" [INFO] Routine plan present; no SafetyProgram routines enabled -> skipping defaults", flush=True) + else: + run_defaults() ET.SubElement(controller, 'SafetySignaturesHmac') @@ -112,12 +191,21 @@ class LimitedSafetyProgramGenerator: @staticmethod def _create_main_routine(routines_el: ET.Element) -> None: - routine = ET.SubElement(routines_el, 'Routine', Name='MainRoutine', Type='RLL') + from ..config import get_config + cfg_local = get_config() + routine = ET.SubElement(routines_el, 'Routine', Name=cfg_local.routines.main_routine_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') text = ET.SubElement(rung, 'Text') - # Only JSR calls for the 5 essential safety routines - text.text = '[JSR(R010_INPUTS,0) ,JSR(R011_OUTPUTS,0) ,JSR(R012_RESETS,0) ,JSR(R020_ESTOPS,0) ,JSR(R030_ZONES,0) ];' + # Only JSR calls for the 5 essential safety routines (config-driven names) + nm = cfg_local.routines.name_map + calls = [ + nm.get('inputs', 'R010_INPUTS'), + nm.get('outputs', 'R011_OUTPUTS'), + nm.get('resets', 'R012_RESETS'), + nm.get('estops', 'R020_ESTOPS'), + ] + text.text = '[' + ' ,'.join(f'JSR({c},0)' for c in calls) + ' ];' # ------------------------------------------------------------------ def build_xml_string(self) -> str: @@ -171,27 +259,33 @@ class DescIPSafetyProgramGenerator: self._create_desc_ip_resets_routine(routines_el, rst_data) self._create_desc_ip_estops_routine(routines_el, epc_data) - # Use zones from file if available (zones typically aren't extracted from DESC_IP) - try: - zones_data = self.loader.zones - self._create_desc_ip_zones_routine(routines_el, epc_data) - except Exception as e: - print(f" - Warning: Could not load zones data: {e}") + # ZONES routine removed return root @staticmethod def _create_main_routine(routines_el: ET.Element) -> None: - routine = ET.SubElement(routines_el, 'Routine', Name='MainRoutine', Type='RLL') + from ..config import get_config + cfg_local = get_config() + routine = ET.SubElement(routines_el, 'Routine', Name=cfg_local.routines.main_routine_name, Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') text = ET.SubElement(rung, 'Text') - # Only JSR calls for essential safety routines - text.text = '[JSR(R010_INPUTS,0) ,JSR(R011_OUTPUTS,0) ,JSR(R012_RESETS,0) ,JSR(R020_ESTOPS,0) ,JSR(R030_ZONES,0) ];' + # Only JSR calls for essential safety routines (config-driven names) + nm = cfg_local.routines.name_map + calls = [ + nm.get('inputs', 'R010_INPUTS'), + nm.get('outputs', 'R011_OUTPUTS'), + nm.get('resets', 'R012_RESETS'), + nm.get('estops', 'R020_ESTOPS'), + ] + text.text = '[' + ' ,'.join(f'JSR({c},0)' for c in calls) + ' ];' def _create_desc_ip_inputs_routine(self, routines_el: ET.Element, epc_data: pd.DataFrame) -> None: """Create R010_INPUTS routine from DESC_IP extracted EPC data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R010_INPUTS', Type='RLL') + from ..config import get_config + nm = get_config().routines.name_map + routine = ET.SubElement(routines_el, 'Routine', Name=nm.get('inputs', 'R010_INPUTS'), Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung_num = 0 @@ -217,14 +311,13 @@ class DescIPSafetyProgramGenerator: def _create_desc_ip_outputs_routine(self, routines_el: ET.Element, sto_data: pd.DataFrame) -> None: """Create R011_OUTPUTS routine from DESC_IP extracted STO data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R011_OUTPUTS', Type='RLL') + from ..config import get_config + nm = get_config().routines.name_map + routine = ET.SubElement(routines_el, 'Routine', Name=nm.get('outputs', 'R011_OUTPUTS'), Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung_num = 0 - # Get zones data to determine proper zone OK tags - zones_df = self.data_loader.zones - # Process STO data for safety outputs for _, row in sto_data.iterrows(): if pd.notna(row['IO_PATH']) and 'VFD' in str(row['TAGNAME']): @@ -233,13 +326,8 @@ class DescIPSafetyProgramGenerator: vfd_name = str(row['TAGNAME']) sto_tag = f"STO_{vfd_name}" - # Use proper zone OK tag - for MCM zone it should be EStop_MCM_OK - zone_ok_tag = "EStop_MCM_OK" # Default to MCM zone - if not zones_df.empty: - # Get the first zone name and create proper tag - first_zone = zones_df.iloc[0] - zone_name = first_zone["NAME"].replace(" ", "_").replace("-", "_") - zone_ok_tag = f"EStop_{zone_name}_OK" + # Zone logic removed: gate by MCM OK + zone_ok_tag = "EStop_MCM_OK" text.text = f"XIC({zone_ok_tag})OTE({sto_tag});" rung_num += 1 @@ -248,7 +336,9 @@ class DescIPSafetyProgramGenerator: def _create_desc_ip_resets_routine(self, routines_el: ET.Element, rst_data: pd.DataFrame) -> None: """Create R012_RESETS routine from DESC_IP extracted RST data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R012_RESETS', Type='RLL') + from ..config import get_config + nm = get_config().routines.name_map + routine = ET.SubElement(routines_el, 'Routine', Name=nm.get('resets', 'R012_RESETS'), Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung_num = 0 @@ -268,7 +358,9 @@ class DescIPSafetyProgramGenerator: def _create_desc_ip_estops_routine(self, routines_el: ET.Element, epc_data: pd.DataFrame) -> None: """Create R020_ESTOPS routine from DESC_IP extracted EPC data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R020_ESTOPS', Type='RLL') + from ..config import get_config + nm = get_config().routines.name_map + routine = ET.SubElement(routines_el, 'Routine', Name=nm.get('estops', 'R020_ESTOPS'), Type='RLL') rll_content = ET.SubElement(routine, 'RLLContent') rung_num = 0 @@ -285,42 +377,7 @@ class DescIPSafetyProgramGenerator: print(f" - Added {rung_num} E-stop rungs from DESC_IP data", flush=True) - def _create_desc_ip_zones_routine(self, routines_el: ET.Element, epc_data: pd.DataFrame) -> None: - """Create R030_ZONES routine from DESC_IP extracted data.""" - routine = ET.SubElement(routines_el, 'Routine', Name='R030_ZONES', Type='RLL') - rll_content = ET.SubElement(routine, 'RLLContent') - - # Get zones data from data loader - zones_df = self.data_loader.zones - rung_num = 0 - - # Handle MCM zone first (special case) - mcm_zone = zones_df[zones_df["NAME"].isin(["MCM", "MCM01"])] - if not mcm_zone.empty: - rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') - text = ET.SubElement(rung, 'Text') - text.text = "XIC(MCM_EPB_DCS_CTRL.O1)OTE(EStop_MCM_OK);" - rung_num += 1 - - # Handle other zones if any - for _, zone in zones_df.sort_values("NAME").iterrows(): - # Skip MCM zones (already handled above) - if zone["NAME"] in ["MCM", "MCM01"]: - continue - - if pd.isna(zone["START"]) or pd.isna(zone["END"]) or zone["START"] == "" or zone["END"] == "": - continue - - zone_name = zone["NAME"].replace(" ", "_").replace("-", "_") - zone_ok_tag = f"EStop_{zone_name}_OK" - - rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') - text = ET.SubElement(rung, 'Text') - # This would need more sophisticated logic for actual device ranges - text.text = f"XIC(SYSTEM_OK)OTE({zone_ok_tag});" - rung_num += 1 - - print(f" - Added {rung_num} zone logic rungs from zones data", flush=True) + # ZONES routine removed def build_xml_string(self) -> str: """Build and return the complete XML string.""" diff --git a/Routines Generator/src/plugin_system.py b/Routines Generator/src/plugin_system.py index 27d4bf6..c3885d5 100644 --- a/Routines Generator/src/plugin_system.py +++ b/Routines Generator/src/plugin_system.py @@ -42,6 +42,10 @@ class RoutinePlugin(ABC): def __init__(self, context: RoutineContext): self.context = context self.logger = get_logger(f"{self.__class__.__module__}.{self.__class__.__name__}") + # Extract optional per-plugin parameters and filters from context metadata + meta = context.metadata or {} + self.params: Dict[str, Any] = meta.get('params', {}) + self.filters: Dict[str, Any] = meta.get('filters', {}) @abstractmethod def can_generate(self) -> bool: @@ -236,7 +240,12 @@ def get_default_registry() -> RoutineRegistry: """Get the default plugin registry, creating it if necessary.""" global _default_registry if _default_registry is None: - _default_registry = RoutineRegistry() + # Auto-discover plugins in src.routines + try: + _default_registry = auto_discover_plugins("src.routines") + except Exception: + # Fallback to empty registry if discovery fails + _default_registry = RoutineRegistry() return _default_registry def register_plugin(plugin_class: Type[RoutinePlugin]) -> None: diff --git a/Routines Generator/src/routines/__pycache__/apf.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/apf.cpython-312.pyc index 389a202..c04f388 100644 Binary files a/Routines Generator/src/routines/__pycache__/apf.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/apf.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/cb_monitor.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/cb_monitor.cpython-312.pyc index 24d4c6e..02d3077 100644 Binary files a/Routines Generator/src/routines/__pycache__/cb_monitor.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/cb_monitor.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/d2c_chute.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/d2c_chute.cpython-312.pyc new file mode 100644 index 0000000..6d12d90 Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/d2c_chute.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/dpm.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/dpm.cpython-312.pyc index 5fdc257..cafff77 100644 Binary files a/Routines Generator/src/routines/__pycache__/dpm.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/dpm.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/estop_check.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/estop_check.cpython-312.pyc index 7ec7bb7..23a27e0 100644 Binary files a/Routines Generator/src/routines/__pycache__/estop_check.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/estop_check.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/estops.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/estops.cpython-312.pyc index 7d3ef20..257a559 100644 Binary files a/Routines Generator/src/routines/__pycache__/estops.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/estops.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/extendo.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/extendo.cpython-312.pyc index c79f6f6..1999856 100644 Binary files a/Routines Generator/src/routines/__pycache__/extendo.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/extendo.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/fioh.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/fioh.cpython-312.pyc index c27f7d0..716d48b 100644 Binary files a/Routines Generator/src/routines/__pycache__/fioh.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/fioh.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/fiom.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/fiom.cpython-312.pyc index 1ca8388..f01f80a 100644 Binary files a/Routines Generator/src/routines/__pycache__/fiom.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/fiom.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/flow_control.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/flow_control.cpython-312.pyc index 1108d68..b642726 100644 Binary files a/Routines Generator/src/routines/__pycache__/flow_control.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/flow_control.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/fpe.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/fpe.cpython-312.pyc new file mode 100644 index 0000000..ac91546 Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/fpe.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/inputs.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/inputs.cpython-312.pyc index 8825e25..03377d0 100644 Binary files a/Routines Generator/src/routines/__pycache__/inputs.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/inputs.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/jpe.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/jpe.cpython-312.pyc index 860db49..a0e651d 100644 Binary files a/Routines Generator/src/routines/__pycache__/jpe.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/jpe.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/main_routine_plugin.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/main_routine_plugin.cpython-312.pyc index 6db019b..d94dbfc 100644 Binary files a/Routines Generator/src/routines/__pycache__/main_routine_plugin.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/main_routine_plugin.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/mcm.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/mcm.cpython-312.pyc new file mode 100644 index 0000000..eb36682 Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/mcm.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/outputs.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/outputs.cpython-312.pyc index 9ffbbd2..e10223e 100644 Binary files a/Routines Generator/src/routines/__pycache__/outputs.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/outputs.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/pb_chute.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/pb_chute.cpython-312.pyc new file mode 100644 index 0000000..d3786cc Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/pb_chute.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/pmm.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/pmm.cpython-312.pyc index 38f2bbb..b9e6e2f 100644 Binary files a/Routines Generator/src/routines/__pycache__/pmm.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/pmm.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/rack.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/rack.cpython-312.pyc new file mode 100644 index 0000000..c53fcad Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/rack.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/resets.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/resets.cpython-312.pyc index 116d3e5..d628773 100644 Binary files a/Routines Generator/src/routines/__pycache__/resets.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/resets.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/safety_tag_map.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/safety_tag_map.cpython-312.pyc new file mode 100644 index 0000000..f1adf62 Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/safety_tag_map.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/speed_control.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/speed_control.cpython-312.pyc index 59f422d..f3b02a5 100644 Binary files a/Routines Generator/src/routines/__pycache__/speed_control.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/speed_control.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/station_jr_chute.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/station_jr_chute.cpython-312.pyc new file mode 100644 index 0000000..c9e47e2 Binary files /dev/null and b/Routines Generator/src/routines/__pycache__/station_jr_chute.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/station_jr_pb.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/station_jr_pb.cpython-312.pyc index 3ced2bf..45992f3 100644 Binary files a/Routines Generator/src/routines/__pycache__/station_jr_pb.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/station_jr_pb.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/__pycache__/zones.cpython-312.pyc b/Routines Generator/src/routines/__pycache__/zones.cpython-312.pyc index 31b9ddf..cbcd757 100644 Binary files a/Routines Generator/src/routines/__pycache__/zones.cpython-312.pyc and b/Routines Generator/src/routines/__pycache__/zones.cpython-312.pyc differ diff --git a/Routines Generator/src/routines/apf.py b/Routines Generator/src/routines/apf.py new file mode 100644 index 0000000..c90d325 --- /dev/null +++ b/Routines Generator/src/routines/apf.py @@ -0,0 +1,116 @@ +""" +Generate R040_APF routine from DESC_IP data. +""" +import xml.etree.ElementTree as ET +import pandas as pd +from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin + + +def generate_apf_routine(data_loader) -> str: + """Generate R040_APF routine from NETWORK sheet data. + + Args: + data_loader: DataLoader instance with access to NETWORK sheet + + Returns: + Formatted XML string for R040_APF routine + """ + # Get APF data from NETWORK sheet + apf_df = data_loader.apf + + # Sort by Name for deterministic output + if 'Name' in apf_df.columns: + apf_df = apf_df.sort_values('Name', key=lambda x: [natural_sort_key(name) for name in x]) + else: + # This shouldn't happen with NETWORK sheet data + return format_xml_to_match_original(ET.tostring(ET.Element("Routine", attrib={"Name": "R040_APF", "Type": "RLL"}), encoding='unicode')) + + # Create routine XML structure (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('apf', 'R040_APF') + except Exception: + routine_name = 'R040_APF' + routine = ET.Element("Routine", attrib={"Name": routine_name, "Type": "RLL"}) + rll_content = ET.SubElement(routine, "RLLContent") + + # Rung 0: Comment rung + rung0 = ET.SubElement(rll_content, "Rung", attrib={"Number": "0", "Type": "N"}) + comment = ET.SubElement(rung0, "Comment") + comment.text = """APF (Variable Frequency Drive) Instantiation Routine + +These VFDs are connected to the DPMs for their Ethernet Communication""" + + text0 = ET.SubElement(rung0, "Text") + text0.text = "NOP();" + + # Generate AOI_APF calls for each module + rung_number = 1 + for _, apf_row in apf_df.iterrows(): + # APF name comes from Name column in NETWORK sheet + if 'Name' not in apf_row or pd.isna(apf_row['Name']): + continue + + apf_name = str(apf_row['Name']).strip() + if not apf_name: + continue + + # Extract DPM name from DPM column (from NETWORK sheet) + dpm_name = None + if 'DPM' in apf_row and pd.notna(apf_row['DPM']) and str(apf_row['DPM']).strip(): + dpm_name = str(apf_row['DPM']).strip() + else: + # This should not happen with proper NETWORK sheet data + print(f"Warning: No DPM specified for APF {apf_name}, using MCM") + dpm_name = "MCM" + + print(f" APF {apf_name} -> DPM {dpm_name}") + + # Create rung for this APF module + rung = ET.SubElement(rll_content, "Rung", attrib={"Number": str(rung_number), "Type": "N"}) + text = ET.SubElement(rung, "Text") + + # Generate AOI_APF call with MOVE instruction + # Pattern: AOI_APF({APF}.AOI,{APF}.HMI,{APF}.CTRL,{APF},{APF}:I,{APF}:O,MCM01.CTRL,{DPM}.CTRL,{APF}:I.In_0,1,NO_Horn)MOVE({APF}.CTRL.STS.Log,{APF}.CTRL.STS.Log); + aoi_call = f"AOI_APF({apf_name}.AOI,{apf_name}.HMI,{apf_name}.CTRL,{apf_name},{apf_name}:I,{apf_name}:O,MCM.CTRL,{dpm_name}.CTRL,{apf_name}:I.In_0,1,NO_Horn)MOVE({apf_name}.CTRL.STS.Log,{apf_name}.CTRL.STS.Log);" + text.text = aoi_call + + rung_number += 1 + + # Convert to string and format to match Rockwell L5X format + xml_str = ET.tostring(routine, encoding='unicode') + return format_xml_to_match_original(xml_str) + + +def _get_apf_data(data_loader) -> pd.DataFrame: + """Helper to get APF data from DataLoader.""" + return data_loader.apf + + +def append_apf_routine(routines_element: ET.Element, data_loader): + """Append APF routine to routines element.""" + routine_content = generate_apf_routine(data_loader) + routine_element = ET.fromstring(routine_content) + + # Append to routines element + routines_element.append(routine_element) + + +class ApfRoutinePlugin(RoutinePlugin): + name = "apf" + description = "Generates the APF routine" + category = "device" + + def can_generate(self) -> bool: + return not self.context.data_loader.apf.empty + + def generate(self) -> bool: + xml_text = generate_apf_routine(self.context.data_loader) + if not xml_text: + return False + self.context.routines_element.append(ET.fromstring(xml_text)) + return True + + +register_plugin(ApfRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/cb_monitor.py b/Routines Generator/src/routines/cb_monitor.py new file mode 100644 index 0000000..e96fa44 --- /dev/null +++ b/Routines Generator/src/routines/cb_monitor.py @@ -0,0 +1,83 @@ +""" +CB_MONITOR routine generator. +Generates AOI_CB_MONITOR calls for circuit breaker monitoring at PDPs. +""" + +import xml.etree.ElementTree as ET +from typing import Any, Dict +import pandas as pd +from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin + + +def generate_cb_monitor_routine(data_loader) -> ET.Element: + """Generate the R070_CB_MONITOR routine XML element.""" + cb_monitor_data = data_loader.cb_monitor_data + # Config-driven routine name + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('cb_monitor', 'R070_CB_MONITOR') + except Exception: + routine_name = 'R070_CB_MONITOR' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + + # Create RLLContent + rll_content = ET.SubElement(routine, 'RLLContent') + + # Rung 0: Comment explaining the routine + rung0 = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') + comment0 = ET.SubElement(rung0, 'Comment') + comment0.text = ('CB (Circuit Breaker) Monitoring Routine\n\n' + 'This routine monitors the status of up to 26 circuit breakers per PDP.') + text0 = ET.SubElement(rung0, 'Text') + text0.text = 'NOP();' + + # Generate AOI calls for each PDP + rung_number = 1 + for pdp_name in sorted(cb_monitor_data.keys(), key=natural_sort_key): + config = cb_monitor_data[pdp_name] + + # Create rung for this CB_MONITOR + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + + # Build AOI_CB_MONITOR call + # Format: AOI_CB_MONITOR(tag.AOI, tag.HMI, tag.CTRL, MCM.CTRL, ConnectionFault, CB1...CB26) + cb_params = ','.join(config['cb_inputs']) + + # Note: Using {pdp_name}_CB_MONITOR as the tag name to match the boilerplate + tag_name = f"{pdp_name}_CB_MONITOR" + + aoi_call = (f"AOI_CB_MONITOR({tag_name}.AOI," + f"{tag_name}.HMI," + f"{tag_name}.CTRL," + f"MCM.CTRL," + f"{config['connection_fault']}," + f"{cb_params});") + + text.text = aoi_call + rung_number += 1 + + # Convert to string and format + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + return ET.fromstring(formatted_xml) + + +class CbMonitorRoutinePlugin(RoutinePlugin): + name = "cb_monitor" + description = "Generates the CB_MONITOR routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.cb_monitor_data) + + def generate(self) -> bool: + elem = generate_cb_monitor_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(CbMonitorRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/d2c_chute.py b/Routines Generator/src/routines/d2c_chute.py new file mode 100644 index 0000000..c9bc01c --- /dev/null +++ b/Routines Generator/src/routines/d2c_chute.py @@ -0,0 +1,184 @@ +""" +R042_D2C_CHUTE routine generator + +Generates D2C_CHUTE routine with AOI_D2C_CHUTE calls grouped by S0 numbers. +Each S0 group includes ZMX device, DPM controller, FIO I/O points, and beacon configurations. +""" + +import xml.etree.ElementTree as ET +import pandas as pd +import re +from typing import Dict, List, Optional, Tuple +from ..plugin_system import RoutinePlugin, register_plugin + + +def extract_d2c_data(desc_ip_df: pd.DataFrame, network_df: pd.DataFrame) -> Dict[str, Dict]: + """ + Extract D2C-related data for each unique S0 number. + + Returns dict with structure: + { + 'S012001': { + 'gs1_pb': {'io_path': 'VSB_DPM1_FIO1:I.Pt04.Data', 'fio': 'VSB_DPM1_FIO1'}, + 'gs1_pb_lt': {'io_path': 'VSB_DPM1_FIO1:O.Pt05.Data'}, + 'bcn': {'tagname': 'VSB_DPM1_FIO1', 'stack_type': '2 STACK'}, + 'zmx': {'name': 'S012001_ZMX1', 'dpm': 'VSB_DPM1'} + } + } + """ + # Extract unique S0 numbers + s0_pattern = r'(S0\d+)' + s0_entries = desc_ip_df[desc_ip_df['DESCA'].str.contains('S0', na=False)] + s0_numbers = s0_entries['DESCA'].str.extract(s0_pattern)[0].dropna().unique() + s0_numbers = sorted(set(s0_numbers)) + + s0_data = {} + + for s0 in s0_numbers: + s0_data[s0] = {} + + # Find GS1_PB entries (not _LT) + gs_pb = desc_ip_df[ + desc_ip_df['DESCA'].str.contains(f'{s0}_GS1_PB', na=False) & + ~desc_ip_df['DESCA'].str.contains('_LT', na=False) + ] + if not gs_pb.empty: + row = gs_pb.iloc[0] + io_path = row['IO_PATH'] + fio = io_path.split(':')[0] if ':' in io_path else '' + s0_data[s0]['gs1_pb'] = { + 'io_path': io_path, + 'fio': fio + } + + # Find GS1_PB_LT entries + gs_pb_lt = desc_ip_df[desc_ip_df['DESCA'].str.contains(f'{s0}_GS1_PB_LT', na=False)] + if not gs_pb_lt.empty: + s0_data[s0]['gs1_pb_lt'] = { + 'io_path': gs_pb_lt.iloc[0]['IO_PATH'] + } + + # Find BCN entries + bcn = desc_ip_df[desc_ip_df['DESCA'].str.contains(f'{s0}_BCN', na=False)] + if not bcn.empty: + bcn_row = bcn.iloc[0] + stack_type = "3 STACK" if "3 STACK" in bcn_row['DESCA'] else "2 STACK" + s0_data[s0]['bcn'] = { + 'tagname': bcn_row['TAGNAME'], + 'stack_type': stack_type + } + + # Find ZMX in NETWORK + zmx = network_df[network_df['Name'].str.contains(f'{s0}_ZMX', na=False)] + if not zmx.empty: + zmx_row = zmx.iloc[0] + s0_data[s0]['zmx'] = { + 'name': zmx_row['Name'], + 'dpm': zmx_row['DPM'] + } + + # Filter to only include S0s with all required components + complete_s0_data = { + s0: data for s0, data in s0_data.items() + if all(key in data for key in ['gs1_pb', 'gs1_pb_lt', 'bcn', 'zmx']) + } + + return complete_s0_data + + +def generate_d2c_chute_routine(data_loader) -> ET.Element: + """Generate the R042_D2C_CHUTE routine XML element.""" + + # Get D2C data from DataLoader + d2c_data = data_loader.d2c_data + + # Create routine element (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('d2c_chute', 'R042_D2C_CHUTE') + except Exception: + routine_name = 'R042_D2C_CHUTE' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + description = ET.SubElement(routine, 'Description') + description.text = ''#'D2C Chute Control - Divert to Chute logic for S0 stations' + + # Create RLLContent + rll_content = ET.SubElement(routine, 'RLLContent') + + rung_number = 0 + + # Group S0s by their properties for efficient rung generation + for s0, data in sorted(d2c_data.items()): + rung_number += 1 + + # Create rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + comment = ET.SubElement(rung, 'Comment') + comment.text = f'{s0} - Divert to Chute Control' + + text = ET.SubElement(rung, 'Text') + + # Build AOI_D2C_CHUTE call + zmx_name = data['zmx']['name'] + dpm_name = data['zmx']['dpm'] + gs_pb_path = data['gs1_pb']['io_path'] + gs_pb_lt_path = data['gs1_pb_lt']['io_path'] + bcn_tagname = data['bcn']['tagname'] + stack_type = data['bcn']['stack_type'] + + # Build beacon parameters based on stack type + if stack_type == "3 STACK": + # For 3-stack: use segments 1 and 3 + beacon_params = ( + f"{bcn_tagname}:O.ProcessDataOut.Segment_1_Color_1," + f"{bcn_tagname}:O.ProcessDataOut.Segment_1_Animation_Type," + f"{bcn_tagname}:O.ProcessDataOut.Segment_3_Color_1," + f"{bcn_tagname}:O.ProcessDataOut.Segment_3_Animation_Type" + ) + else: + # For 2-stack: use segments 1 and 2 + beacon_params = ( + f"{bcn_tagname}:O.ProcessDataOut.Segment_1_Color_1," + f"{bcn_tagname}:O.ProcessDataOut.Segment_1_Animation_Type," + f"{bcn_tagname}:O.ProcessDataOut.Segment_2_Color_1," + f"{bcn_tagname}:O.ProcessDataOut.Segment_2_Animation_Type" + ) + + # Build complete AOI call + aoi_call = ( + f"AOI_D2C_CHUTE(" + f"{s0}_D2C_CHUTE.AOI," + f"{s0}_D2C_CHUTE.HMI," + f"{s0}_D2C_CHUTE.CTRL," + f"Station.CTRL," # Using Station as placeholder for JR + f"{zmx_name}:I.Data," + f"{zmx_name}:O.Data," + f"{dpm_name}.CTRL," + f"{gs_pb_path}," + f"{gs_pb_lt_path}," + f"{beacon_params}" + ");" + ) + + text.text = aoi_call + + return routine + + +class D2cChuteRoutinePlugin(RoutinePlugin): + name = "d2c_chute" + description = "Generates the D2C_CHUTE routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.d2c_data) + + def generate(self) -> bool: + elem = generate_d2c_chute_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(D2cChuteRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/dpm.py b/Routines Generator/src/routines/dpm.py index 0686c2c..c20ecde 100644 --- a/Routines Generator/src/routines/dpm.py +++ b/Routines Generator/src/routines/dpm.py @@ -7,6 +7,7 @@ DPM modules are identified by PARTNUMBER containing 'OS30-002404-2S'. import xml.etree.ElementTree as ET import pandas as pd from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin def generate_dpm_routine(desc_ip_df: pd.DataFrame) -> str: @@ -26,8 +27,13 @@ def generate_dpm_routine(desc_ip_df: pd.DataFrame) -> str: dpm_df = dpm_df.drop_duplicates(subset=['TAGNAME']) dpm_df = dpm_df.sort_values('TAGNAME', key=lambda x: [natural_sort_key(name) for name in x]) - # Create routine XML structure - routine = ET.Element("Routine", attrib={"Name": "R020_DPM", "Type": "RLL"}) + # Create routine XML structure (name from config) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('dpm', 'R020_DPM') + except Exception: + routine_name = 'R020_DPM' + routine = ET.Element("Routine", attrib={"Name": routine_name, "Type": "RLL"}) rll_content = ET.SubElement(routine, "RLLContent") # Rung 0: Documentation comment @@ -57,8 +63,7 @@ VFDs, and Field IO are connected to the switch that is part of the DPM""" text = ET.SubElement(rung, "Text") # Generate AOI_DPM call with standard parameters - # Pattern: AOI_DPM({TAGNAME}.AOI,{TAGNAME}.HMI,{TAGNAME}.CTRL,MCM01.CTRL,Rack.AOI.Slot2_EN4TR_Faulted,{TAGNAME}:I.ConnectionFaulted); - aoi_call = f"AOI_DPM({tagname}.AOI,{tagname}.HMI,{tagname}.CTRL,MCM01.CTRL,Rack.AOI.Slot2_EN4TR_Faulted,{tagname}:I.ConnectionFaulted);" + aoi_call = f"AOI_DPM({tagname}.AOI,{tagname}.HMI,{tagname}.CTRL,MCM.CTRL,Rack.AOI.Slot2_EN4TR_Faulted,{tagname}:I.ConnectionFaulted);" text.text = aoi_call rung_number += 1 @@ -104,4 +109,27 @@ def extract_dpm_from_desc_ip(desc_ip_df: pd.DataFrame) -> pd.DataFrame: # Sort for deterministic output dpm_df = dpm_df.sort_values('TAGNAME', key=lambda x: [natural_sort_key(name) for name in x]) - return dpm_df \ No newline at end of file + return dpm_df + + +class DpmRoutinePlugin(RoutinePlugin): + """Plugin wrapper for DPM routine generation.""" + name = "dpm" + description = "Generates the DPM routine" + category = "device" + + def can_generate(self) -> bool: + # Ensure there is at least one DPM entry + return not self.context.data_loader.dpm.empty + + def generate(self) -> bool: + desc_ip_df = self.context.data_loader.desc_ip + xml_text = generate_dpm_routine(desc_ip_df) + if not xml_text: + return False + self.context.routines_element.append(ET.fromstring(xml_text)) + return True + + +# Register plugin in default registry +register_plugin(DpmRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/estop_check.py b/Routines Generator/src/routines/estop_check.py index 937c4a3..2ffde30 100644 --- a/Routines Generator/src/routines/estop_check.py +++ b/Routines Generator/src/routines/estop_check.py @@ -11,6 +11,7 @@ from ..utils.tag_utils import ( device_base_from_desca, is_estop_desca, ) +from ..plugin_system import RoutinePlugin, register_plugin __all__ = ["create_estop_check_routine"] @@ -30,49 +31,54 @@ def _collect_all_stos(sto_df: pd.DataFrame) -> list[tuple[str, str]]: return all_stos -def _split_stos_by_zone( +def _split_stos( all_stos: list[tuple[str, str]], - device_zone: pd.Series, + device_prefix: str, + device_num: int, ) -> tuple[list[str], list[str]]: - """Return (zone_stos, other_stos) IO paths based on zone boundaries.""" - start_prefix = device_zone["START"].split("_")[0] - start_num = int(device_zone["START"].split("_")[1]) - end_prefix = device_zone["END"].split("_")[0] - end_num = int(device_zone["END"].split("_")[1]) + """Split STOs into same-device-group vs other-device-group based on prefix/number proximity. - zone_stos: list[str] = [] - other_stos: list[str] = [] + Heuristic: same group if same prefix and |sto_num - device_num| <= 50. Otherwise other. + This replaces explicit zone ranges. + """ + same_group: list[str] = [] + other_group: list[str] = [] for sto_tag, sto_path in all_stos: - sto_parts = sto_tag.split("_") - sto_prefix = sto_parts[0] - sto_num_part = sto_parts[1] - sto_num = int(sto_num_part[:-1]) if sto_num_part[-1] in ["A", "B"] else int(sto_num_part) - - in_zone = False - if start_prefix == end_prefix: - if sto_prefix == start_prefix and start_num <= sto_num <= end_num: - in_zone = True + parts = sto_tag.split('_') + if len(parts) < 2: + other_group.append(sto_path) + continue + sto_prefix = parts[0] + num_part = parts[1] + try: + sto_num = int(num_part[:-1]) if num_part[-1] in ('A', 'B') else int(num_part) + except ValueError: + other_group.append(sto_path) + continue + if sto_prefix == device_prefix and abs(sto_num - device_num) <= 50: + same_group.append(sto_path) else: - if ( - (sto_prefix == start_prefix and sto_num >= start_num) - or (sto_prefix == end_prefix and sto_num <= end_num) - or (start_prefix < sto_prefix < end_prefix) - ): - in_zone = True - (zone_stos if in_zone else other_stos).append(sto_path) - return zone_stos, other_stos + other_group.append(sto_path) + return same_group, other_group def create_estop_check_routine( routines: ET.Element, epc_df: pd.DataFrame, sto_df: pd.DataFrame, - zones_df: pd.DataFrame, + zones_df: pd.DataFrame | None, ) -> None: """Append the ESTOP_CHECK routine (R100_ESTOP_CHECK) to the `` element.""" + # Use config-driven routine name + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('estop_check', 'R100_ESTOP_CHECK') + except Exception: + routine_name = 'R100_ESTOP_CHECK' + routine = ET.SubElement(routines, "Routine") - routine.set("Name", "R100_ESTOP_CHECK") + routine.set("Name", routine_name) routine.set("Type", "RLL") rll_content = ET.SubElement(routine, "RLLContent") @@ -91,48 +97,10 @@ def create_estop_check_routine( except (ValueError, IndexError): continue # Skip devices that don't follow numeric pattern - # Identify the zone this device belongs to - device_zone = None - for _, zone in zones_df.iterrows(): - # Skip MCM zone and zones that don't have proper START/END values - if zone["NAME"] in ["MCM", "MCM01"]: - continue - if pd.isna(zone["START"]) or pd.isna(zone["END"]) or zone["START"] == "" or zone["END"] == "": - continue - - try: - start_parts = zone["START"].split("_") - end_parts = zone["END"].split("_") - - if len(start_parts) < 2 or len(end_parts) < 2: - continue - - start_prefix = start_parts[0] - start_num = int(start_parts[1]) - end_prefix = end_parts[0] - end_num = int(end_parts[1]) - except (ValueError, IndexError): - continue # Skip zones with non-numeric device patterns - - in_zone = False - if start_prefix == end_prefix: - if device_prefix == start_prefix and start_num <= device_num <= end_num: - in_zone = True - else: - if ( - (device_prefix == start_prefix and device_num >= start_num) - or (device_prefix == end_prefix and device_num <= end_num) - or (start_prefix < device_prefix < end_prefix) - ): - in_zone = True - if in_zone: - device_zone = zone - break - if device_zone is None: - continue + # Determine related STOs without zones (heuristic proximity) + zone_stos, other_stos = _split_stos(all_stos, device_prefix, device_num) estop_device = is_estop_desca(group.iloc[0]["DESCA"]) - zone_stos, other_stos = _split_stos_by_zone(all_stos, device_zone) # Deduplicate STOs to ensure each appears only once zone_stos = sorted(list(set(zone_stos))) @@ -184,4 +152,25 @@ def create_estop_check_routine( text.text = f"{raw_inputs}{sto_checks}OTL({checked_tag});" rung_num += 1 - print(f" - Added {rung_num} E-stop check rungs", flush=True) \ No newline at end of file + print(f" - Added {rung_num} E-stop check rungs", flush=True) + + +class EstopCheckRoutinePlugin(RoutinePlugin): + name = "estop_check" + description = "Generates the ESTOP_CHECK routine" + category = "safety" + + def can_generate(self) -> bool: + return not self.context.data_loader.epc.empty and not self.context.data_loader.sto.empty + + def generate(self) -> bool: + create_estop_check_routine( + self.context.routines_element, + self.context.data_loader.epc, + self.context.data_loader.sto, + None, + ) + return True + + +register_plugin(EstopCheckRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/estops.py b/Routines Generator/src/routines/estops.py index 4a8dfe4..abdfd02 100644 --- a/Routines Generator/src/routines/estops.py +++ b/Routines Generator/src/routines/estops.py @@ -34,7 +34,7 @@ def create_estops_routine(routines: ET.Element, epc_df: pd.DataFrame, rst_df: pd text = ET.SubElement(rung, "Text") text.text = ( f"DCS(MCM_EPB_DCS_CTRL,EMERGENCY STOP,EQUIVALENT - ACTIVE HIGH,500,MANUAL," - f"AUTOMATIC,Local:7:I.Pt02.Data,Local:7:I.Pt03.Data,MCM_EPB_STATUS,SFT_{subsystem}_S_PB);" + f"AUTOMATIC,Local:7:I.Pt02.Data,Local:7:I.Pt03.Data,MCM_EPB_STATUS,SFT_MCM_S_PB);" ) rung_num = 1 @@ -69,7 +69,7 @@ def create_estops_routine(routines: ET.Element, epc_df: pd.DataFrame, rst_df: pd if not rst_row.empty: reset_tag = f"SFT_{rst_row.iloc[0]['DESCA']}" else: - reset_tag = f"SFT_{subsystem}_S_PB" + reset_tag = f"SFT_MCM_S_PB" text.text = ( f"DCS({dcs_tag},EMERGENCY STOP,EQUIVALENT - ACTIVE HIGH,500,MANUAL," @@ -111,4 +111,36 @@ def create_estops_routine(routines: ET.Element, epc_df: pd.DataFrame, rst_df: pd f"DCS({dcs_tag},EMERGENCY STOP,EQUIVALENT - ACTIVE HIGH,500,MANUAL," f"AUTOMATIC,{input1},{input2},{status_tag},{reset_tag});" ) - rung_num += 1 \ No newline at end of file + rung_num += 1 + +# --- Plugin wrapper so modern generator can execute this routine --- +from ..plugin_system import RoutinePlugin + + +class EstopsRoutinePlugin(RoutinePlugin): + name = "estops" + description = "Generates the R020_ESTOPS routine" + category = "safety" + + def can_generate(self) -> bool: + try: + return not self.context.data_loader.epc.empty + except Exception: + return False + + def generate(self) -> bool: + # Determine subsystem from excel path + import re + excel_path_str = str(self.context.data_loader.excel_path) + subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM01" + params = self.context.metadata.get("params", {}) if isinstance(self.context.metadata, dict) else {} + ignore_flag = bool(params.get("ignore_estop1ok", False)) + create_estops_routine( + self.context.routines_element, + self.context.data_loader.epc, + self.context.data_loader.rst, + subsystem=subsystem, + ignore_estop1ok=ignore_flag, + ) + return True \ No newline at end of file diff --git a/Routines Generator/src/routines/extendo.py b/Routines Generator/src/routines/extendo.py new file mode 100644 index 0000000..9d1f044 --- /dev/null +++ b/Routines Generator/src/routines/extendo.py @@ -0,0 +1,116 @@ +""" +Generate R041_EXTENDO routine from NETWORK sheet data. +""" +import xml.etree.ElementTree as ET +import pandas as pd +from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin + + +def generate_extendo_routine(data_loader) -> str: + """Generate R041_EXTENDO routine from NETWORK sheet data. + + Args: + data_loader: DataLoader instance with access to NETWORK sheet + + Returns: + Formatted XML string for R041_EXTENDO routine + """ + # Get EXTENDO data from NETWORK sheet + extendo_df = data_loader.extendo + + # Sort by Name for deterministic output + if 'Name' in extendo_df.columns: + extendo_df = extendo_df.sort_values('Name', key=lambda x: [natural_sort_key(name) for name in x]) + else: + # This shouldn't happen with NETWORK sheet data + return format_xml_to_match_original(ET.tostring(ET.Element("Routine", attrib={"Name": "R041_EXTENDO", "Type": "RLL"}), encoding='unicode')) + + # Create routine XML structure (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('extendo', 'R041_EXTENDO') + except Exception: + routine_name = 'R041_EXTENDO' + routine = ET.Element("Routine", attrib={"Name": routine_name, "Type": "RLL"}) + rll_content = ET.SubElement(routine, "RLLContent") + + # Rung 0: Comment rung + rung0 = ET.SubElement(rll_content, "Rung", attrib={"Number": "0", "Type": "N"}) + comment = ET.SubElement(rung0, "Comment") + comment.text = """EXTENDO (Siemens ET 200SP) Instantiation Routine + +These Extendo modules are connected to the DPMs for their Ethernet Communication""" + + text0 = ET.SubElement(rung0, "Text") + text0.text = "NOP();" + + # Generate AOI_EXTENDO calls for each module + rung_number = 1 + for _, extendo_row in extendo_df.iterrows(): + # EXTENDO name comes from Name column in NETWORK sheet + if 'Name' not in extendo_row or pd.isna(extendo_row['Name']): + continue + + extendo_name = str(extendo_row['Name']).strip() + if not extendo_name: + continue + + # Extract DPM name from DPM column (from NETWORK sheet) + dpm_name = None + if 'DPM' in extendo_row and pd.notna(extendo_row['DPM']) and str(extendo_row['DPM']).strip(): + dpm_name = str(extendo_row['DPM']).strip() + else: + # This should not happen with proper NETWORK sheet data + print(f"Warning: No DPM specified for EXTENDO {extendo_name}, using MCM") + dpm_name = "MCM" + + print(f" EXTENDO {extendo_name} -> DPM {dpm_name}") + + # Create rung for this EXTENDO module + rung = ET.SubElement(rll_content, "Rung", attrib={"Number": str(rung_number), "Type": "N"}) + text = ET.SubElement(rung, "Text") + + # Generate AOI_EXTENDO call + # Pattern: AOI_EXTENDO({EXTENDO}.AOI,{EXTENDO}.HMI,{EXTENDO}.CTRL,{EXTENDO}:I,{EXTENDO}:O,MCM01.CTRL,{DPM}.CTRL); + aoi_call = f"AOI_EXTENDO({extendo_name}.AOI,{extendo_name}.HMI,{extendo_name}.CTRL,{extendo_name}:I,{extendo_name}:O,MCM.CTRL,{dpm_name}.CTRL);" + text.text = aoi_call + + rung_number += 1 + + # Convert to string and format to match Rockwell L5X format + xml_str = ET.tostring(routine, encoding='unicode') + return format_xml_to_match_original(xml_str) + + +def _get_extendo_data(data_loader) -> pd.DataFrame: + """Helper to get EXTENDO data from DataLoader.""" + return data_loader.extendo + + +def append_extendo_routine(routines_element: ET.Element, data_loader): + """Append EXTENDO routine to routines element.""" + routine_content = generate_extendo_routine(data_loader) + routine_element = ET.fromstring(routine_content) + + # Append to routines element + routines_element.append(routine_element) + + +class ExtendoRoutinePlugin(RoutinePlugin): + name = "extendo" + description = "Generates the EXTENDO routine" + category = "device" + + def can_generate(self) -> bool: + return not self.context.data_loader.extendo.empty + + def generate(self) -> bool: + xml_text = generate_extendo_routine(self.context.data_loader) + if not xml_text: + return False + self.context.routines_element.append(ET.fromstring(xml_text)) + return True + + +register_plugin(ExtendoRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/fioh.py b/Routines Generator/src/routines/fioh.py index 496c06e..3887149 100644 --- a/Routines Generator/src/routines/fioh.py +++ b/Routines Generator/src/routines/fioh.py @@ -7,6 +7,7 @@ FIOH modules are identified by PARTNUMBER containing '5032-8IOLM12DR' and DESCA import xml.etree.ElementTree as ET import pandas as pd from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin def generate_fioh_routine(desc_ip_df: pd.DataFrame) -> str: @@ -36,8 +37,13 @@ def generate_fioh_routine(desc_ip_df: pd.DataFrame) -> str: fioh_df = fioh_df.drop_duplicates(subset=['TAGNAME']) fioh_df = fioh_df.sort_values('TAGNAME', key=lambda x: [natural_sort_key(name) for name in x]) - # Create routine XML structure - routine = ET.Element("Routine", attrib={"Name": "R031_FIOH", "Type": "RLL"}) + # Create routine XML structure (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('fioh', 'R031_FIOH') + except Exception: + routine_name = 'R031_FIOH' + routine = ET.Element("Routine", attrib={"Name": routine_name, "Type": "RLL"}) rll_content = ET.SubElement(routine, "RLLContent") # Rung 0: Documentation comment @@ -66,7 +72,7 @@ FIOHs are dependent on the masters for communication back to the DPM""" elif 'TAGNAME' in fioh_row and pd.notna(fioh_row['TAGNAME']): fio_name = str(fioh_row['TAGNAME']).strip() else: - fio_name = "MCM01" # Default fallback + fio_name = "MCM" # Default fallback # Validate both names exist and are not empty if not fioh_name or not fio_name: @@ -80,7 +86,7 @@ FIOHs are dependent on the masters for communication back to the DPM""" # Generate AOI_IO_BLOCK call with FIO controller reference # Pattern: AOI_IO_BLOCK({FIOH_NAME}.AOI,{FIOH_NAME}.HMI,{FIOH_NAME}.CTRL,MCM01.CTRL,{FIO_NAME}.CTRL,{FIOH_NAME}:I.ConnectionFaulted); - aoi_call = f"AOI_IO_BLOCK({fioh_name}.AOI,{fioh_name}.HMI,{fioh_name}.CTRL,MCM01.CTRL,{fio_name}.CTRL,{fioh_name}:I.ConnectionFaulted);" + aoi_call = f"AOI_IO_BLOCK({fioh_name}.AOI,{fioh_name}.HMI,{fioh_name}.CTRL,MCM.CTRL,{fio_name}.CTRL,{fioh_name}:I.ConnectionFaulted);" text.text = aoi_call rung_number += 1 @@ -134,4 +140,24 @@ def extract_fioh_from_desc_ip(desc_ip_df: pd.DataFrame) -> pd.DataFrame: fioh_df = fioh_df.drop_duplicates(subset=['TAGNAME']) fioh_df = fioh_df.sort_values('TAGNAME', key=lambda x: [natural_sort_key(name) for name in x]) - return fioh_df \ No newline at end of file + return fioh_df + + +class FiohRoutinePlugin(RoutinePlugin): + name = "fioh" + description = "Generates the FIOH routine" + category = "device" + + def can_generate(self) -> bool: + return not self.context.data_loader.fioh.empty + + def generate(self) -> bool: + desc_ip_df = self.context.data_loader.desc_ip + xml_text = generate_fioh_routine(desc_ip_df) + if not xml_text: + return False + self.context.routines_element.append(ET.fromstring(xml_text)) + return True + + +register_plugin(FiohRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/fiom.py b/Routines Generator/src/routines/fiom.py index dcb9063..ebe2c2e 100644 --- a/Routines Generator/src/routines/fiom.py +++ b/Routines Generator/src/routines/fiom.py @@ -7,6 +7,7 @@ FIOM modules are identified by PARTNUMBER containing '5032-8IOLM12DR'. import xml.etree.ElementTree as ET import pandas as pd from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin def generate_fiom_routine(desc_ip_df: pd.DataFrame) -> str: @@ -30,8 +31,13 @@ def generate_fiom_routine(desc_ip_df: pd.DataFrame) -> str: fiom_df = fiom_df.drop_duplicates(subset=['TAGNAME']) fiom_df = fiom_df.sort_values('TAGNAME', key=lambda x: [natural_sort_key(name) for name in x]) - # Create routine XML structure - routine = ET.Element("Routine", attrib={"Name": "R030_FIOM", "Type": "RLL"}) + # Create routine XML structure (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('fiom', 'R030_FIOM') + except Exception: + routine_name = 'R030_FIOM' + routine = ET.Element("Routine", attrib={"Name": routine_name, "Type": "RLL"}) rll_content = ET.SubElement(routine, "RLLContent") # Rung 0: Documentation comment @@ -70,7 +76,7 @@ FIOMs are ethernet network devices that have an IP address and connect directly dpm_name = '_'.join(dpm_parts) + '_DPM1' else: # Default fallback - dpm_name = "MCM01" + dpm_name = "MCM" print(f" FIOM {fiom_name} -> DPM {dpm_name}") @@ -80,7 +86,7 @@ FIOMs are ethernet network devices that have an IP address and connect directly # Generate AOI_IO_BLOCK call with DPM controller reference # Pattern: AOI_IO_BLOCK({FIOM_NAME}.AOI,{FIOM_NAME}.HMI,{FIOM_NAME}.CTRL,MCM01.CTRL,{DPM_NAME}.CTRL,{FIOM_NAME}:I.ConnectionFaulted); - aoi_call = f"AOI_IO_BLOCK({fiom_name}.AOI,{fiom_name}.HMI,{fiom_name}.CTRL,MCM01.CTRL,{dpm_name}.CTRL,{fiom_name}:I.ConnectionFaulted);" + aoi_call = f"AOI_IO_BLOCK({fiom_name}.AOI,{fiom_name}.HMI,{fiom_name}.CTRL,MCM.CTRL,{dpm_name}.CTRL,{fiom_name}:I.ConnectionFaulted);" text.text = aoi_call rung_number += 1 @@ -128,4 +134,24 @@ def extract_fiom_from_desc_ip(desc_ip_df: pd.DataFrame) -> pd.DataFrame: fiom_df = fiom_df.drop_duplicates(subset=['TAGNAME']) fiom_df = fiom_df.sort_values('TAGNAME', key=lambda x: [natural_sort_key(name) for name in x]) - return fiom_df \ No newline at end of file + return fiom_df + + +class FiomRoutinePlugin(RoutinePlugin): + name = "fiom" + description = "Generates the FIOM routine" + category = "device" + + def can_generate(self) -> bool: + return not self.context.data_loader.fiom.empty + + def generate(self) -> bool: + desc_ip_df = self.context.data_loader.desc_ip + xml_text = generate_fiom_routine(desc_ip_df) + if not xml_text: + return False + self.context.routines_element.append(ET.fromstring(xml_text)) + return True + + +register_plugin(FiomRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/flow_control.py b/Routines Generator/src/routines/flow_control.py new file mode 100644 index 0000000..216ee35 --- /dev/null +++ b/Routines Generator/src/routines/flow_control.py @@ -0,0 +1,189 @@ +""" +FLOW_CTRL routine generator. +Generates flow control logic with EXTENDO interlock. +""" + +import xml.etree.ElementTree as ET +from typing import Any, Dict +import pandas as pd +from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin + + +def generate_flow_control_routine(data_loader) -> ET.Element: + """Generate the R050_FLOW_CTRL routine XML element.""" + flow_ctrl_data = data_loader.flow_ctrl_data + # Config-driven routine name + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('flow_ctrl', 'R050_FLOW_CTRL') + except Exception: + routine_name = 'R050_FLOW_CTRL' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + + # Create RLLContent + rll_content = ET.SubElement(routine, 'RLLContent') + # Start at rung 0 with no NOP/comment rungs + rung_number = 0 + + for dpm_name in sorted(flow_ctrl_data.keys(), key=natural_sort_key): + config = flow_ctrl_data[dpm_name] + devices = config['devices'] + + # No comment/NOP rung per group + + # Partition devices + extendos = [d for d in devices if d.get('type') == 'EXTENDO'] + vfds = [d for d in devices if d.get('type') == 'VFD'] + + # Sort per config: lane-based numeric sort or natural + try: + from ..config import get_config + cfg = get_config() + name_regex = getattr(cfg.extraction, 'flow_ctrl_vfd_name_regex', None) + chain_order = getattr(cfg.extraction, 'flow_ctrl_chain_order', 'lane') + enable_extendo = bool(getattr(cfg.extraction, 'flow_ctrl_enable_extendo_interlocks', True)) + emit_first_vfd = bool(getattr(cfg.extraction, 'flow_ctrl_emit_first_vfd_ote', True)) + chain_dir = getattr(cfg.extraction, 'flow_ctrl_chain_direction', 'downstream_to_upstream') + emit_last_vfd = bool(getattr(cfg.extraction, 'flow_ctrl_emit_last_vfd_ote', False)) + except Exception: + name_regex = None + chain_order = 'lane' + enable_extendo = True + emit_first_vfd = True + chain_dir = 'downstream_to_upstream' + emit_last_vfd = False + + import re + extendos.sort(key=lambda x: natural_sort_key(x['name'])) + if chain_order == 'lane' and name_regex: + rx = re.compile(name_regex) + def vfd_order_key(d): + m = rx.match(d['name']) + if not m: + return (natural_sort_key(d['name']),) + lane = m.group('lane') + pos = int(m.group('pos')) if m.group('pos').isdigit() else 0 + return (natural_sort_key(lane), pos) + vfds.sort(key=vfd_order_key) + else: + vfds.sort(key=lambda x: natural_sort_key(x['name'])) + + # 1) Chain VFDs per lane: do not cross lanes when building pairs + if vfds: + # Group VFDs by lane using the same regex + lane_groups: dict[str, list[str]] = {} + if name_regex: + rx_lane = re.compile(name_regex) + for v in vfds: + nm = v['name'] + m = rx_lane.match(nm) + lane = m.group('lane') if m else '' + lane_groups.setdefault(lane, []).append(nm) + else: + # Fallback: single group with all names + lane_groups[''] = [v['name'] for v in vfds] + + for lane, names in lane_groups.items(): + # names are already ordered per above sorting + if len(names) == 0: + continue + # Build chain pairs based on direction, within this lane only + if chain_dir == 'downstream_to_upstream': + pairs = [(names[i], names[i-1]) for i in range(1, len(names))] + first_name = names[0] + last_name = names[-1] + else: + pairs = [(names[i-1], names[i]) for i in range(1, len(names))] + first_name = names[-1] + last_name = names[0] + + for src, dst in pairs: + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"XIC({src}.CTRL.STS.Enabled)OTE({dst}.CTRL.CMD.Interlock);" + rung_number += 1 + + # Optional unconditional OTE for first/last VFD in lane + if emit_first_vfd: + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"OTE({first_name}.CTRL.CMD.Interlock);" + rung_number += 1 + + if emit_last_vfd: + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"OTE({last_name}.CTRL.CMD.Interlock);" + rung_number += 1 + + # 2) EXTENDO interlocks: VFD.Enabled -> EXTENDO.Interlock using explicit depends_on if present + # Use data_loader-provided mapping from VFD to extendo when available + if enable_extendo: + extendo_set = {e['name'] for e in extendos} + for vfd in vfds: + dep = vfd.get('depends_on') + if dep and dep in extendo_set: + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"XIC({vfd['name']}.CTRL.STS.Enabled)OTE({dep}.CTRL.CMD.Interlock);" + rung_number += 1 + + # Always interlock last EXTENDO per lane if configured + try: + from ..config import get_config as _gc + cfg2 = _gc() + emit_last_ext = bool(getattr(cfg2.extraction, 'flow_ctrl_emit_last_extendo_ote', True)) + except Exception: + emit_last_ext = True + + if emit_last_ext and extendos: + # Group extendos by lane using extendo regex + try: + from ..config import get_config as _gc2 + ext_rx_pat = getattr(_gc2().extraction, 'flow_ctrl_extendo_name_regex', None) + except Exception: + ext_rx_pat = None + import re as _re + ext_rx = _re.compile(ext_rx_pat) if ext_rx_pat else None + lane_to_ext = {} + for e in extendos: + nm = e['name'] + lane = '' + if ext_rx: + m = ext_rx.match(nm) + if m: + lane = m.group('lane') + lane_to_ext.setdefault(lane, []).append(nm) + + for lane, names in lane_to_ext.items(): + # names already sorted by natural + last_ext = names[-1] + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"OTE({last_ext}.CTRL.CMD.Interlock);" + rung_number += 1 + + # Convert to string and format + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + return ET.fromstring(formatted_xml) + + +class FlowCtrlRoutinePlugin(RoutinePlugin): + name = "flow_ctrl" + description = "Generates the FLOW_CTRL routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.flow_ctrl_data) + + def generate(self) -> bool: + elem = generate_flow_control_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(FlowCtrlRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/fpe.py b/Routines Generator/src/routines/fpe.py new file mode 100644 index 0000000..1c18192 --- /dev/null +++ b/Routines Generator/src/routines/fpe.py @@ -0,0 +1,98 @@ +""" +FPE routine generator (R101_FPE). + +Generates AOI_FPE calls for: +- Entries containing "FPE" in DESCA +- Entries containing "3CH_PE" pattern in DESCA +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING + +import pandas as pd + +from ..utils.common import format_xml_to_match_original +from ..plugin_system import RoutinePlugin, register_plugin + +if TYPE_CHECKING: + from ..data_loader import DataLoader + + +def generate_fpe_routine(loader: DataLoader) -> ET.Element | None: + """Generate R101_FPE routine from DESC_IP data.""" + print("\n [R101_FPE] Starting FPE routine generation...") + + # Extract FPE data + fpe_data = loader.fpe_data + + if not fpe_data: + print(" [WARNING] No FPE configurations found") + return None + + print(f" Found {len(fpe_data)} FPE configurations") + + # Create routine element (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('fpe', 'R101_FPE') + except Exception: + routine_name = 'R101_FPE' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + desc = ET.SubElement(routine, 'Description') + desc.text = 'Full Photo Eye Control Routine' + + rll_content = ET.SubElement(routine, 'RLLContent') + + # Generate rungs for each FPE configuration + rung_number = 0 + for fpe_name, config in sorted(fpe_data.items()): + # Build AOI call - FPE has fewer parameters than JPE + aoi_params = [ + f"{fpe_name}.AOI", + f"{fpe_name}.HMI", + f"{fpe_name}.CTRL", + config['conveyor_ctrl'], # Conveyor/VFD controller + config['parent_comm_fault'], # Parent communication fault + config['input_path'], # Clear_I input path + config['beacon_output'] # Beacon_Light_O output path + ] + + # Create rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + comment = ET.SubElement(rung, 'Comment') + comment.text = f"Full Photo Eye Control for {fpe_name}" + + text = ET.SubElement(rung, 'Text') + text.text = f"AOI_FPE({','.join(aoi_params)});" + + rung_number += 1 + + print(f" Generated {rung_number} FPE rungs") + + # Convert to string and format XML to match Rockwell standards + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + + # Parse back to Element + return ET.fromstring(formatted_xml) + + +class FpeRoutinePlugin(RoutinePlugin): + name = "fpe" + description = "Generates the FPE routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.fpe_data) + + def generate(self) -> bool: + elem = generate_fpe_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(FpeRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/inputs.py b/Routines Generator/src/routines/inputs.py index d27fbc5..9e99510 100644 --- a/Routines Generator/src/routines/inputs.py +++ b/Routines Generator/src/routines/inputs.py @@ -119,4 +119,29 @@ def create_inputs_routine(routines: ET.Element, epc_df: pd.DataFrame, ignore_est norm_desca = normalize_desca(str(row["DESCA"])) estop_ok_tag = f"{norm_desca}_ESTOP_OK" text.text = f"XIC({row['IO_PATH']})OTE({estop_ok_tag});" - rung_num += 1 \ No newline at end of file + rung_num += 1 + +# --- Plugin wrapper so modern generator can execute this routine --- +from ..plugin_system import RoutinePlugin + + +class InputsRoutinePlugin(RoutinePlugin): + name = "inputs" + description = "Generates the R010_INPUTS routine" + category = "safety" + + def can_generate(self) -> bool: + try: + return not self.context.data_loader.epc.empty + except Exception: + return False + + def generate(self) -> bool: + params = self.context.metadata.get("params", {}) if isinstance(self.context.metadata, dict) else {} + ignore_flag = bool(params.get("ignore_estop1ok", False)) + create_inputs_routine( + self.context.routines_element, + self.context.data_loader.epc, + ignore_estop1ok=ignore_flag, + ) + return True \ No newline at end of file diff --git a/Routines Generator/src/routines/jpe.py b/Routines Generator/src/routines/jpe.py new file mode 100644 index 0000000..5f6a91d --- /dev/null +++ b/Routines Generator/src/routines/jpe.py @@ -0,0 +1,100 @@ +""" +JPE routine generator (R0100_JPE). + +Generates AOI_JPE calls for: +- Entries containing "JPE" in DESCA +- Entries containing "_2_PE" pattern in DESCA +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING + +import pandas as pd + +from ..utils.common import format_xml_to_match_original +from ..plugin_system import RoutinePlugin, register_plugin + +if TYPE_CHECKING: + from ..data_loader import DataLoader + + +def generate_jpe_routine(loader: DataLoader) -> ET.Element | None: + """Generate R0100_JPE routine from DESC_IP data.""" + print("\n [R0100_JPE] Starting JPE routine generation...") + + # Extract JPE data + jpe_data = loader.jpe_data + + if not jpe_data: + print(" [WARNING] No JPE configurations found") + return None + + print(f" Found {len(jpe_data)} JPE configurations") + + # Create routine element (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('jpe', 'R100_JPE') + except Exception: + routine_name = 'R100_JPE' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + desc = ET.SubElement(routine, 'Description') + desc.text = 'Jam Photo Eye Control Routine' + + rll_content = ET.SubElement(routine, 'RLLContent') + + # Generate rungs for each JPE configuration + rung_number = 0 + for jpe_name, config in sorted(jpe_data.items()): + # Build AOI call + aoi_params = [ + f"{jpe_name}.AOI", + f"{jpe_name}.HMI", + f"{jpe_name}.CTRL", + config['conveyor_ctrl'], # Conveyor/VFD controller + config['station_ctrl'], # Station controller (JR) + config['parent_comm_fault'], # Parent communication fault + config['input_path'], # Input path + "YES", # Run up PE - always YES + config['beacon_output'] # Beacon output path + ] + + # Create rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + comment = ET.SubElement(rung, 'Comment') + comment.text = f"Jam Photo Eye Control for {jpe_name}" + + text = ET.SubElement(rung, 'Text') + text.text = f"AOI_JPE({','.join(aoi_params)});" + + rung_number += 1 + + print(f" Generated {rung_number} JPE rungs") + + # Convert to string and format XML to match Rockwell standards + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + + # Parse back to Element + return ET.fromstring(formatted_xml) + + +class JpeRoutinePlugin(RoutinePlugin): + name = "jpe" + description = "Generates the JPE routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.jpe_data) + + def generate(self) -> bool: + elem = generate_jpe_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(JpeRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/main_routine_plugin.py b/Routines Generator/src/routines/main_routine_plugin.py index 0154551..3c07828 100644 --- a/Routines Generator/src/routines/main_routine_plugin.py +++ b/Routines Generator/src/routines/main_routine_plugin.py @@ -31,29 +31,85 @@ class MainRoutinePlugin(RoutinePlugin): return errors def generate(self) -> bool: - """Generate the MainRoutine.""" + """Generate the MainRoutine with JSR calls to created routines.""" try: - self.logger.info("Generating MainRoutine") - + self.logger.info("Generating MainRoutine with JSR calls") + + # Resolve routine names from config mapping + try: + nm = self.context.config.routines.name_map + except Exception: + nm = {} + + # If a routine plan is present, honor its order for MainProgram + calls: List[str] = [] + try: + plan = getattr(self.context.config, 'routine_plan', []) or [] + ordered = [e for e in plan if getattr(e, 'enabled', True) and getattr(e, 'program', 'MainProgram') == 'MainProgram'] + ordered.sort(key=lambda e: getattr(e, 'order', 100)) + for entry in ordered: + # Skip generating a JSR to ourselves + if getattr(entry, 'plugin', '') == 'main_routine' or getattr(entry, 'name', '') == 'main_routine': + continue + # Map plugin/name to configured routine name + plugin_key = getattr(entry, 'plugin', '') or getattr(entry, 'name', '') + routine_name = nm.get(plugin_key, nm.get(getattr(entry, 'name', ''), getattr(entry, 'name', ''))) + if routine_name: + calls.append(routine_name) + except Exception: + calls = [] + + # Fallback to comprehensive default order if no plan provided + if not calls: + defaults = [ + nm.get('safety_tag_map', 'R130_SAFETY_TAG_MAP'), + nm.get('rack', 'R011_RACK'), + nm.get('mcm', 'R010_MCM'), + nm.get('dpm', 'R020_DPM'), + nm.get('fiom', 'R030_FIOM'), + nm.get('fioh', 'R031_FIOH'), + nm.get('apf', 'R040_APF'), + nm.get('extendo', 'R041_EXTENDO'), + nm.get('flow_ctrl', 'R050_FLOW_CTRL'), + nm.get('speed_ctrl', 'R051_SPEED_CTRL'), + nm.get('d2c_chute', 'R042_D2C_CHUTE'), + nm.get('pb_chute', 'R043_PB_CHUTE'), + nm.get('station_jr_chute', 'R044_STATION_JR_CHUTE'), + nm.get('pmm', 'R060_PMM'), + nm.get('cb_monitor', 'R070_CB_MONITOR'), + nm.get('station_jr_pb', 'R090_STATION_JR_PB'), + nm.get('jpe', 'R100_JPE'), + nm.get('fpe', 'R101_FPE'), + nm.get('estop_check', 'R120_ESTOP_CHECK'), + ] + calls = [c for c in defaults if c] + + # Filter to only include JSRs for routines that actually exist in the program + existing = {r.get('Name') for r in self.context.routines_element.findall('Routine')} + calls = [c for c in calls if c in existing] + # Create the MainRoutine routine = ET.SubElement(self.context.routines_element, "Routine") - routine.set("Name", "MainRoutine") + routine.set("Name", self.context.config.routines.main_routine_name if hasattr(self.context.config, 'routines') else "MainRoutine") routine.set("Type", "RLL") - + # Create RLL content rll_content = ET.SubElement(routine, "RLLContent") - - # Add a simple rung that enables output + + # Single rung with JSR calls in the configured order rung = ET.SubElement(rll_content, "Rung") rung.set("Number", "0") rung.set("Type", "N") - + text = ET.SubElement(rung, "Text") - text.text = "XIC(GlobalEnable)OTE(SystemReady);" - + if calls: + text.text = '[' + ' ,'.join(f'JSR({c},0)' for c in calls) + ' ];' + else: + text.text = 'NOP();' + self.logger.info("Successfully generated MainRoutine") return True - + except Exception as e: self.logger.error(f"Failed to generate MainRoutine: {e}") - return False \ No newline at end of file + return False \ No newline at end of file diff --git a/Routines Generator/src/routines/mcm.py b/Routines Generator/src/routines/mcm.py new file mode 100644 index 0000000..638e3a1 --- /dev/null +++ b/Routines Generator/src/routines/mcm.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import xml.etree.ElementTree as ET + +from ..plugin_system import RoutinePlugin + + +class McmRoutinePlugin(RoutinePlugin): + name = "mcm" + description = "Generates the R010_MCM routine (fixed content)" + category = "core" + + def can_generate(self) -> bool: + return True + + def generate(self) -> bool: + routine_name = ( + getattr(self.context.config.routines, "name_map", {}).get("mcm", "R010_MCM") + if hasattr(self.context, "config") and hasattr(self.context, "config") + else "R010_MCM" + ) + + routines_el = self.context.routines_element + + routine = ET.SubElement(routines_el, "Routine", Name=routine_name, Type="RLL") + rll_content = ET.SubElement(routine, "RLLContent") + + # Rung 0: comment + NOP + rung0 = ET.SubElement(rll_content, "Rung", Number="0", Type="N") + comment = ET.SubElement(rung0, "Comment") + comment.text = ( + "MCM Instantiation Routine \n\n" + "The MCM is the module that houses the PLC and communication modules for communicating with the building network and DPMs\n\n" + "Within it is the primary supervisor for the MCM/DPM Ring \n\n" + "Note: The current architecture does not have more than one MCM per PLC program" + ) + text0 = ET.SubElement(rung0, "Text") + text0.text = "NOP();" + + # Rung 1: AOI call as fixed text + rung1 = ET.SubElement(rll_content, "Rung", Number="1", Type="N") + text1 = ET.SubElement(rung1, "Text") + text1.text = ( + "AOI_MCM(MCM.AOI,MCM.HMI,MCM.CTRL," + "Local:5:I.Data.2,Local:5:I.Data.5,Local:5:I.Data.4,Local:5:I.Data.0," + "Local:5:I.Data.3,Local:7:I.Pt02.Data,Local:7:I.Pt03.Data,Local:5:I.Data.1," + "Local:7:I.Pt00.Data,Local:5:I.Data.7,Local:5:I.Data.8,Local:5:I.Data.6,Local:5:I.Data.9," + "Local:6:O.Data.2,Local:6:O.Data.5,Local:6:O.Data.4,Local:6:O.Data.0,Local:6:O.Data.1,Local:6:O.Data.3)" + "MOVE(MCM.CTRL.STS.Log,MCM.CTRL.STS.Log);" + ) + + return True + + diff --git a/Routines Generator/src/routines/outputs.py b/Routines Generator/src/routines/outputs.py index 20ef67d..81e9431 100644 --- a/Routines Generator/src/routines/outputs.py +++ b/Routines Generator/src/routines/outputs.py @@ -10,138 +10,160 @@ __all__ = ["create_outputs_routine"] def create_outputs_routine(routines: ET.Element, zones_df: pd.DataFrame, sto_df: pd.DataFrame) -> None: - """Append OUTPUTS routine (R011_OUTPUTS) to the given `` element. + """Append OUTPUTS routine (R011_OUTPUTS) grouped by zones. - Logic copied from original generate_l5x.py implementation with no - behavioural changes; only external dependencies (natural_sort_key) - imported from utilities. + For each zone in zones_df (excluding the root MCMx row), generate a rung: + XIC(EStop_MCM_OK) [XIC(EStop__OK) ] XIC(EStop__OK) [OTE(), ...]; + The outputs are selected from sto_df where the VFD tag falls within the + zone's [start, stop] UL range (numeric range, allowing letter suffixes). """ - routine = ET.SubElement(routines, "Routine") - routine.set("Use", "Target") - routine.set("Name", "R011_OUTPUTS") - routine.set("Type", "RLL") - + routine = ET.SubElement(routines, "Routine", Use="Target", Name="R011_OUTPUTS", Type="RLL") rll_content = ET.SubElement(routine, "RLLContent") - rung_num = 0 - - # Check if zones_df is empty or missing NAME column - if zones_df.empty: - print("Warning: zones DataFrame is empty, skipping outputs routine") - return - - if "NAME" not in zones_df.columns: - print(f"Warning: zones DataFrame missing NAME column. Available columns: {list(zones_df.columns)}") - return - - # Process each zone (sorted alphabetically) - for _, zone in zones_df.sort_values("NAME").iterrows(): - # Skip MCM zone (no conventional device range) - if zone["NAME"] in ["MCM", "MCM01"]: - continue - - if pd.isna(zone["START"]) or pd.isna(zone["END"]) or zone["START"] == "" or zone["END"] == "": - continue + def norm_zone(s: str) -> str: + return str(s).strip().replace('-', '_') - zone_name = zone["NAME"].replace(" ", "_").replace("-", "_") - zone_ok_tag = f"EStop_{zone_name}_OK" - - # Check if START and END follow numeric device pattern + # Helper to parse tokens like UL1_13 -> (prefix='UL1', index=13) + def parse_ul_token(token: str) -> tuple[str, int] | None: try: - start_parts = zone["START"].split("_") - end_parts = zone["END"].split("_") - - if len(start_parts) < 2 or len(end_parts) < 2: - print(f"Warning: Skipping zone {zone_name} in outputs - invalid device name format") + parts = token.split('_') + prefix = parts[0] + num_part = parts[1] + # strip trailing letters (e.g., '10A' -> 10) + digits = ''.join(ch for ch in num_part if ch.isdigit()) + return prefix, int(digits) + except Exception: + return None + + # Build VFD outputs catalog from sto_df + # Map base 'ULx_y' -> list of concrete outputs to write (e.g., + # 'UL1_3' -> ['UL1_3_VFD1:SO.Out00Output', 'UL1_3_VFD1:SO.STOOutput']) + base_to_outputs: dict[str, set[str]] = {} + for _, sto_row in sto_df.iterrows(): + tag = str(sto_row.get('TAGNAME', '')).strip() + if not tag: + continue + # Consider any VFD tag regardless of prefix (UL/FL/others) + if 'VFD' not in tag: + continue + parts = tag.split('_') + if len(parts) < 2: + continue + base = f"{parts[0]}_{parts[1]}" # e.g., UL1_3 from UL1_3_VFD1 + outputs_for_tag: set[str] = set() + + # Prefer explicit IO_PATH if it already contains STO/Out00 outputs + path = str(sto_row.get('IO_PATH', '')).strip() + if path and (':SO.STOOutput' in path or ':SO.Out00Output' in path): + outputs_for_tag.add(path) + else: + # Derive standard STO output + outputs_for_tag.add(f"{tag}:SO.STOOutput") + # If there is a specific flag in DESCA indicating extra output, include Out00Output + desca = str(sto_row.get('DESCA', '')).upper() + if 'OUT00' in desca or 'OUT0' in desca: + outputs_for_tag.add(f"{tag}:SO.Out00Output") + + cell = base_to_outputs.setdefault(base, set()) + cell.update(outputs_for_tag) + + # Order zones: ensure parents before children + rows: list[dict] = [] + for _, zr in zones_df.iterrows(): + name = str(zr.get('name', '')).strip() + if not name or name.upper().startswith('MCM'): + continue + rows.append({ + 'name': name, + 'name_norm': norm_zone(name), + 'start': str(zr.get('start', '')).strip(), + 'stop': str(zr.get('stop', '')).strip(), + 'interlock': str(zr.get('interlock', '')).strip(), + 'interlock_norm': norm_zone(zr.get('interlock', '')) if str(zr.get('interlock', '')).strip() else '' + }) + + ordered: list[dict] = [] + placed = set() + remaining = rows.copy() + for _ in range(len(rows) + 5): + progressed = False + next_remaining = [] + for r in remaining: + if not r['interlock'] or r['interlock_norm'] in placed or r['interlock'].upper().startswith('MCM'): + ordered.append(r) + placed.add(r['name_norm']) + progressed = True + else: + next_remaining.append(r) + remaining = next_remaining + if not remaining or not progressed: + ordered.extend(remaining) + break + + # Emit one rung per zone + rung_num = 0 + for r in ordered: + bounds_s = parse_ul_token(r['start']) + bounds_e = parse_ul_token(r['stop']) + if not bounds_s or not bounds_e or bounds_s[0] != bounds_e[0]: + # Skip ranges that span different prefixes; config should keep a single prefix per zone + continue + prefix = bounds_s[0] + lo, hi = sorted([bounds_s[1], bounds_e[1]]) + + # Collect outputs whose base falls within [lo, hi] for this prefix + selected_outputs: set[str] = set() + for base, outs in base_to_outputs.items(): + if not base.startswith(prefix + '_'): continue - - start_prefix = start_parts[0] - start_num = int(start_parts[1]) - end_prefix = end_parts[0] - end_num = int(end_parts[1]) - except (ValueError, IndexError): - print(f"Warning: Skipping zone {zone_name} in outputs - unable to parse device numbers") + try: + num_part = base.split('_')[1] + digits = ''.join(ch for ch in num_part if ch.isdigit()) + idx = int(digits) + except Exception: + continue + if lo <= idx <= hi: + selected_outputs.update(outs) + + if not selected_outputs: continue - # Get unique device outputs for this zone - zone_outputs: list[str] = [] + # Build rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') + text = ET.SubElement(rung, 'Text') - # Collect all STO devices from the dataframe - for _, sto_row in sto_df.iterrows(): - try: - device_parts = sto_row["TAGNAME"].split("_") - if len(device_parts) < 2: - continue - - device_prefix = device_parts[0] + xics = ["XIC(EStop_MCM_OK)"] + if r['interlock'] and not r['interlock'].upper().startswith('MCM'): + xics.append(f"XIC(EStop_{norm_zone(r['interlock'])}_OK)") + xics.append(f"XIC(EStop_{r['name_norm']}_OK)") - # Extract device number, handling A/B suffixes - device_num_str = device_parts[1] - device_num = int(device_num_str[:-1]) if device_num_str[-1] in ["A", "B"] else int(device_num_str) - except (ValueError, IndexError): - continue - - # Check if this device is within the zone range - in_zone = False - if start_prefix == end_prefix: - if device_prefix == start_prefix and start_num <= device_num <= end_num: - in_zone = True - else: - if ( - (device_prefix == start_prefix and device_num >= start_num) - or (device_prefix == end_prefix and device_num <= end_num) - or (device_prefix > start_prefix and device_prefix < end_prefix) - ): - in_zone = True - - if in_zone: - if "STO" in sto_row["DESCA"]: - zone_outputs.append(sto_row["IO_PATH"]) - else: - zone_outputs.append(f"{sto_row['TAGNAME']}:SO.STOOutput") - - # Create rung for zone - if zone_outputs: - # Zone has devices - create rung with interlock logic and device outputs - rung = ET.SubElement(rll_content, "Rung") - rung.set("Number", str(rung_num)) - rung.set("Type", "N") - text = ET.SubElement(rung, "Text") - - # Build XIC conditions - always include EStop_MCM01_OK first - xic_conditions = "XIC(EStop_MCM01_OK)" - - # Add interlock if specified and different from MCM01 - if pd.notna(zone["INTERLOCK"]) and zone["INTERLOCK"] != "": - interlock_zone = zone["INTERLOCK"].replace(" ", "_").replace("-", "_") - interlock_tag = f"EStop_{interlock_zone}_OK" - # Only add interlock if it's different from the hardcoded MCM01 - if interlock_tag != "EStop_MCM01_OK": - xic_conditions = f"XIC({interlock_tag}){xic_conditions}" - - # Add the zone's own OK tag - xic_conditions = f"{xic_conditions}XIC({zone_ok_tag})" - - # Remove duplicates and filter out non-STO outputs - unique_outputs: list[str] = [] - for output in zone_outputs: - if (":SO.STOOutput" in output or ":SO.Out00Output" in output) and output not in unique_outputs: - unique_outputs.append(output) - - if len(unique_outputs) == 1: - output = unique_outputs[0] - text.text = f"{xic_conditions}OTE({output});" - elif len(unique_outputs) > 1: - ote_list = [f"OTE({output})" for output in sorted(unique_outputs, key=natural_sort_key)] - ote_chain = "[" + ",".join(ote_list) + "]" - text.text = f"{xic_conditions}{ote_chain};" - - rung_num += 1 + ote_list = [f"OTE({o})" for o in sorted(selected_outputs, key=natural_sort_key)] + if len(ote_list) == 1: + text.text = ''.join(xics) + ote_list[0] + ';' else: - # Zone has no devices - just set the zone OK tag (no interlock conditions) - rung = ET.SubElement(rll_content, "Rung") - rung.set("Number", str(rung_num)) - rung.set("Type", "N") - text = ET.SubElement(rung, "Text") - text.text = f"OTE({zone_ok_tag});" - rung_num += 1 \ No newline at end of file + text.text = ''.join(xics) + '[' + ','.join(ote_list) + '];' + + rung_num += 1 + +# --- Plugin wrapper so modern generator can execute this routine --- +from ..plugin_system import RoutinePlugin + + +class OutputsRoutinePlugin(RoutinePlugin): + name = "outputs" + description = "Generates the R011_OUTPUTS routine" + category = "safety" + + def can_generate(self) -> bool: + try: + return not self.context.data_loader.sto.empty + except Exception: + return False + + def generate(self) -> bool: + create_outputs_routine( + self.context.routines_element, + self.context.data_loader.zones, # zones are deprecated; returns empty DF + self.context.data_loader.sto, + ) + return True \ No newline at end of file diff --git a/Routines Generator/src/routines/pb_chute.py b/Routines Generator/src/routines/pb_chute.py new file mode 100644 index 0000000..692a79d --- /dev/null +++ b/Routines Generator/src/routines/pb_chute.py @@ -0,0 +1,116 @@ +""" +PB_CHUTE routine generator (R043_PB_CHUTE). + +Generates AOI_PB_CHUTE calls for each S0 group that has PE1, PE2, PR1, and SOL1 components. +Groups components by S0 number (even numbers only) and uses the associated FIOH device. +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING + +import pandas as pd + +from ..utils.common import format_xml_to_match_original +from ..plugin_system import RoutinePlugin, register_plugin + +if TYPE_CHECKING: + from ..data_loader import DataLoader + + +def generate_pb_chute_routine(loader: DataLoader) -> ET.Element | None: + """Generate R043_PB_CHUTE routine from DESC_IP data.""" + print("\n [R043_PB_CHUTE] Starting PB_CHUTE routine generation...") + + # Extract PB_CHUTE data + pb_chute_data = loader.pb_chute_data + + if not pb_chute_data: + print(" [WARNING] No PB_CHUTE configurations found") + return None + + print(f" Found {len(pb_chute_data)} PB_CHUTE configurations") + + # Create routine element (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('pb_chute', 'R043_PB_CHUTE') + except Exception: + routine_name = 'R043_PB_CHUTE' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + desc = ET.SubElement(routine, 'Description') + desc.text = 'PB_CHUTE Control Routine' + + rll_content = ET.SubElement(routine, 'RLLContent') + + # Generate rungs for each PB_CHUTE configuration + rung_number = 0 + for s0, config in sorted(pb_chute_data.items()): + # Build AOI call + aoi_params = [ + f"{s0}_PB_Chute.AOI", + f"{s0}_PB_Chute.HMI", + f"{s0}_PB_Chute.CTRL", + "Station.CTRL", # Always use Station.CTRL + f"{config['fioh_tagname']}:I.ConnectionFaulted", + config['pe2_path'], # PE2 first (swapped) + config['pe1_path'], # PE1 second (swapped) + config['pr1_path'], + config['sol1_path'] + ] + + # Add beacon parameters if available + if 'bcn' in config and config['bcn']: + bcn_tagname = config['bcn']['tagname'] + stack_type = config['bcn']['stack_type'] + + # For 3-stack beacons use segments 1 and 3, for 2-stack use 1 and 2 + if stack_type == '3-stack': + segments = [1, 3] + else: # 2-stack + segments = [1, 2] + + for segment in segments: + aoi_params.extend([ + f"{bcn_tagname}:O.ProcessDataOut.Segment_{segment}_Color_1", + f"{bcn_tagname}:O.ProcessDataOut.Segment_{segment}_Animation_Type" + ]) + + # Create rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + comment = ET.SubElement(rung, 'Comment') + comment.text = f"PB_CHUTE Control for {s0}" + + text = ET.SubElement(rung, 'Text') + text.text = f"AOI_PB_CHUTE({','.join(aoi_params)});" + + rung_number += 1 + + print(f" Generated {rung_number} PB_CHUTE rungs") + + # Convert to string and format XML to match Rockwell standards + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + + # Parse back to Element + return ET.fromstring(formatted_xml) + + +class PbChuteRoutinePlugin(RoutinePlugin): + name = "pb_chute" + description = "Generates the PB_CHUTE routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.pb_chute_data) + + def generate(self) -> bool: + elem = generate_pb_chute_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(PbChuteRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/pmm.py b/Routines Generator/src/routines/pmm.py new file mode 100644 index 0000000..af5558e --- /dev/null +++ b/Routines Generator/src/routines/pmm.py @@ -0,0 +1,80 @@ +""" +PMM (Power Monitoring Module) routine generator. +Generates AOI_PMM calls for power monitoring at PDPs. +""" + +import xml.etree.ElementTree as ET +from typing import Any, Dict +import pandas as pd +from ..utils.common import format_xml_to_match_original, natural_sort_key +from ..plugin_system import RoutinePlugin, register_plugin + + +def generate_pmm_routine(data_loader) -> ET.Element: + """Generate the R060_PMM routine XML element.""" + pmm_data = data_loader.pmm_data + # Config-driven routine name + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('pmm', 'R060_PMM') + except Exception: + routine_name = 'R060_PMM' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + + # Create RLLContent + rll_content = ET.SubElement(routine, 'RLLContent') + + # Rung 0: Comment explaining the routine + rung0 = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') + comment0 = ET.SubElement(rung0, 'Comment') + comment0.text = ('PMM (Power Monitoring Module) Instantiation Routine\n\n' + 'These modules monitor the power at the PDP, every PDP has one.') + text0 = ET.SubElement(rung0, 'Text') + text0.text = 'NOP();' + + # Generate AOI calls for each PMM + rung_number = 1 + for pmm_name in sorted(pmm_data.keys(), key=natural_sort_key): + config = pmm_data[pmm_name] + + # Create rung for this PMM + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + text = ET.SubElement(rung, 'Text') + + # Build AOI_PMM call + # AOI_PMM(tag.AOI, PMM_Input1, PMM_Input2, PMM_Input3, PMM_Input4, Parent_Comm_Fault, PMM_Fault_I) + # The PMM inputs are the 4 monitoring inputs :I1, :I2, :I3, :I4 + aoi_call = (f"AOI_PMM({pmm_name}.AOI," + f"{pmm_name}:I1," + f"{pmm_name}:I2," + f"{pmm_name}:I3," + f"{pmm_name}:I4," + f"{config['parent_comm_fault']}," + f"{config['pmm_fault_io']});") + + text.text = aoi_call + rung_number += 1 + + # Convert to string and format + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + return ET.fromstring(formatted_xml) + + +class PmmRoutinePlugin(RoutinePlugin): + name = "pmm" + description = "Generates the PMM routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.pmm_data) + + def generate(self) -> bool: + elem = generate_pmm_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(PmmRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/rack.py b/Routines Generator/src/routines/rack.py new file mode 100644 index 0000000..dc30fc8 --- /dev/null +++ b/Routines Generator/src/routines/rack.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import xml.etree.ElementTree as ET + +from ..plugin_system import RoutinePlugin + + +class RackRoutinePlugin(RoutinePlugin): + name = "rack" + description = "Generates the R011_RACK routine (fixed content)" + category = "core" + + def can_generate(self) -> bool: + # Always generate; routine is static and data-agnostic + return True + + def generate(self) -> bool: + # Resolve routine name from config mapping with a safe default + routine_name = ( + getattr(self.context.config.routines, "name_map", {}).get("rack", "R011_RACK") + if hasattr(self.context, "config") and hasattr(self.context, "config") + else "R011_RACK" + ) + + routines_el = self.context.routines_element + + routine = ET.SubElement(routines_el, "Routine", Name=routine_name, Type="RLL") + rll_content = ET.SubElement(routine, "RLLContent") + + # Rung 0: comment + NOP + rung0 = ET.SubElement(rll_content, "Rung", Number="0", Type="N") + comment = ET.SubElement(rung0, "Comment") + comment.text = ( + "Rack instantiation routine\n\n" + "This is the Rack for the MCM.\n\n" + "Note: The current architecture does not have more than one rack per PLC program" + ) + text0 = ET.SubElement(rung0, "Text") + text0.text = "NOP();" + + # Rung 1: AOI call + rung1 = ET.SubElement(rll_content, "Rung", Number="1", Type="N") + text1 = ET.SubElement(rung1, "Text") + text1.text = ( + "AOI_RACK(Rack.AOI,Rack.HMI,SLOT2_EN4TR,SLOT5_IB16,SLOT6_OB16E,SLOT7_IB16S);" + ) + + return True + + diff --git a/Routines Generator/src/routines/resets.py b/Routines Generator/src/routines/resets.py index e6f5adf..be80b65 100644 --- a/Routines Generator/src/routines/resets.py +++ b/Routines Generator/src/routines/resets.py @@ -75,11 +75,11 @@ def create_resets_routine(routines: ET.Element, rst_df: pd.DataFrame, epc_df: pd reset_button = f"SFT_{rst_row['DESCA']}" break - # Build instruction pieces with subsystem-specific MCM tag + # Build instruction pieces with global MCM PB tag (non-subsystem specific) if reset_button: - reset_logic = f"[XIC({reset_button}) ,XIC(SFT_{subsystem}_S_PB)]" + reset_logic = f"[XIC({reset_button}) ,XIC(SFT_MCM_S_PB)]" else: - reset_logic = f"XIC(SFT_{subsystem}_S_PB)" + reset_logic = f"XIC(SFT_MCM_S_PB)" reset_tag = f"RST_{epc_group}_ESTOP" text.text = f"{reset_logic}OTE({reset_tag});" @@ -88,4 +88,29 @@ def create_resets_routine(routines: ET.Element, rst_df: pd.DataFrame, epc_df: pd # NOTE: Additional ESTOP-specific reset rungs were removed to avoid # duplicate or incorrect logic. EPC reset rungs above already cover # all emergency-stop circuits via their associated start buttons or - # fallback to the MCM push-button. \ No newline at end of file + # fallback to the MCM push-button. + +# --- Plugin wrapper so modern generator can execute this routine --- +from ..plugin_system import RoutinePlugin + + +class ResetsRoutinePlugin(RoutinePlugin): + name = "resets" + description = "Generates the R012_RESETS routine" + category = "safety" + + def can_generate(self) -> bool: + try: + return not self.context.data_loader.epc.empty + except Exception: + return False + + def generate(self) -> bool: + # Subsystem no longer affects the MCM PB tag; always use SFT_MCM_S_PB + create_resets_routine( + self.context.routines_element, + self.context.data_loader.rst, + self.context.data_loader.epc, + subsystem="MCM01", + ) + return True \ No newline at end of file diff --git a/Routines Generator/src/routines/safety_tag_map.py b/Routines Generator/src/routines/safety_tag_map.py new file mode 100644 index 0000000..100a679 --- /dev/null +++ b/Routines Generator/src/routines/safety_tag_map.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import re +import xml.etree.ElementTree as ET +from typing import List + +import pandas as pd + +from ..plugin_system import RoutinePlugin, register_plugin +from ..config import get_config + + +class SafetyTagMapRoutinePlugin(RoutinePlugin): + """Generates the R000_SAFETY_TAG_MAP routine in MainProgram. + + Maps MCM input to subsystem S_PB and creates push-button mappings + from RST data extracted via DataLoader (DESC_IP). + """ + + name = "safety_tag_map" + description = "Generates the R000_SAFETY_TAG_MAP routine" + category = "core" + + def can_generate(self) -> bool: + # Always generate this routine; at minimum it contains the MCM S_PB mapping + return True + + def generate(self) -> bool: + try: + cfg_local = get_config() + + # Resolve routine name from config map + routine_name = cfg_local.routines.name_map.get('safety_tag_map', 'R000_SAFETY_TAG_MAP') + + routine = ET.SubElement(self.context.routines_element, 'Routine', Name=routine_name, Type='RLL') + rll_content = ET.SubElement(routine, 'RLLContent') + + # Determine subsystem from Excel path + excel_path_str = str(self.context.data_loader.excel_path) + subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" + # Use config-driven MCM tag (no numeric suffix) to match sample + mcm_tag = cfg_local.routines.mcm_safety_tag + + # Rung 0: Map MCM input to subsystem S_PB + rung_num = 0 + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"XIC({cfg_local.routines.mcm_input_address})OTE({mcm_tag});" + rung_num += 1 + + # RST-derived push-button tags + rst_df: pd.DataFrame = self.context.data_loader.rst + for _, row in rst_df.iterrows(): + desca = row.get('DESCA') + io_path = row.get('IO_PATH') + if not isinstance(desca, str) or not isinstance(io_path, (str,)): + continue + # Skip GS1 and lights + if 'GS1' in desca: + continue + if (('S1_PB' in desca) or ('S2_PB' in desca) or desca.endswith('SPB')) and not desca.endswith('_LT'): + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_num), Type='N') + text = ET.SubElement(rung, 'Text') + text.text = f"XIC({io_path})OTE({desca});" + rung_num += 1 + + return True + + except Exception as e: + self.logger.error(f"Failed to generate safety_tag_map: {e}") + return False + + +register_plugin(SafetyTagMapRoutinePlugin) + + diff --git a/Routines Generator/src/routines/speed_control.py b/Routines Generator/src/routines/speed_control.py new file mode 100644 index 0000000..3f786d7 --- /dev/null +++ b/Routines Generator/src/routines/speed_control.py @@ -0,0 +1,64 @@ +""" +SPEED_CTRL routine generator. +Generates speed control logic that moves Speed_350_FPM to all VFDs. +""" + +import xml.etree.ElementTree as ET +from typing import List +from ..utils.common import format_xml_to_match_original +from ..plugin_system import RoutinePlugin, register_plugin + + +def generate_speed_control_routine(data_loader) -> ET.Element: + """Generate the R051_SPEED_CTRL routine XML element.""" + vfd_names = data_loader.speed_ctrl_data + # Config-driven routine name + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('speed_ctrl', 'R051_SPEED_CTRL') + except Exception: + routine_name = 'R051_SPEED_CTRL' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + + # Create RLLContent + rll_content = ET.SubElement(routine, 'RLLContent') + + # Create a single rung with all MOVE instructions + rung = ET.SubElement(rll_content, 'Rung', Number='0', Type='N') + text = ET.SubElement(rung, 'Text') + + # Build MOVE instructions for each VFD + move_instructions = [] + for vfd_name in vfd_names: + move_instructions.append(f"MOVE(Speed_350_FPM,{vfd_name}.CTRL.CMD.Speed_FPM)") + + # Join all MOVE instructions with commas and wrap in brackets + if move_instructions: + text.text = '[' + ' ,'.join(move_instructions) + ' ];' + else: + # No VFDs found, just NOP + text.text = 'NOP();' + + # Convert to string and format + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + return ET.fromstring(formatted_xml) + + +class SpeedCtrlRoutinePlugin(RoutinePlugin): + name = "speed_ctrl" + description = "Generates the SPEED_CTRL routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.speed_ctrl_data) + + def generate(self) -> bool: + elem = generate_speed_control_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(SpeedCtrlRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/station_jr_chute.py b/Routines Generator/src/routines/station_jr_chute.py new file mode 100644 index 0000000..4987e41 --- /dev/null +++ b/Routines Generator/src/routines/station_jr_chute.py @@ -0,0 +1,99 @@ +""" +STATION_JR_CHUTE routine generator (R044_STATION_JR_CHUTE). + +Generates AOI_STATION_JR_CHUTE calls for each S0 that has: +- JR1_PB (push button input) +- JR1_PB_LT (push button light output) +- 3-stack beacon (DESCB contains '3') +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING + +import pandas as pd + +from ..utils.common import format_xml_to_match_original +from ..plugin_system import RoutinePlugin, register_plugin + +if TYPE_CHECKING: + from ..data_loader import DataLoader + + +def generate_station_jr_chute_routine(loader: DataLoader) -> ET.Element | None: + """Generate R044_STATION_JR_CHUTE routine from DESC_IP data.""" + print("\n [R044_STATION_JR_CHUTE] Starting STATION_JR_CHUTE routine generation...") + + # Extract STATION_JR_CHUTE data + jr_chute_data = loader.station_jr_chute_data + + if not jr_chute_data: + print(" [WARNING] No STATION_JR_CHUTE configurations found") + return None + + print(f" Found {len(jr_chute_data)} STATION_JR_CHUTE configurations") + + # Create routine element (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('station_jr_chute', 'R044_STATION_JR_CHUTE') + except Exception: + routine_name = 'R044_STATION_JR_CHUTE' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + desc = ET.SubElement(routine, 'Description') + desc.text = 'Station JR Chute Control Routine' + + rll_content = ET.SubElement(routine, 'RLLContent') + + # Generate rungs for each STATION_JR_CHUTE configuration + rung_number = 0 + for s0, config in sorted(jr_chute_data.items()): + # Build AOI call + aoi_params = [ + f"{s0}_JR.AOI", + f"{s0}_JR.HMI", + f"{s0}_JR.CTRL", + config['jr_pb_path'], # JR push button input + config['jr_pb_lt_path'], # JR push button light output + f"{config['bcn_tagname']}:O.ProcessDataOut.Segment_2_Color_1", + f"{config['bcn_tagname']}:O.ProcessDataOut.Segment_2_Animation_Type" + ] + + # Create rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + comment = ET.SubElement(rung, 'Comment') + comment.text = f"Station JR Chute Control for {s0}" + + text = ET.SubElement(rung, 'Text') + text.text = f"AOI_STATION_JR_CHUTE({','.join(aoi_params)});" + + rung_number += 1 + + print(f" Generated {rung_number} STATION_JR_CHUTE rungs") + + # Convert to string and format XML to match Rockwell standards + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + + # Parse back to Element + return ET.fromstring(formatted_xml) + + +class StationJrChuteRoutinePlugin(RoutinePlugin): + name = "station_jr_chute" + description = "Generates the STATION_JR_CHUTE routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.station_jr_chute_data) + + def generate(self) -> bool: + elem = generate_station_jr_chute_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(StationJrChuteRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/station_jr_pb.py b/Routines Generator/src/routines/station_jr_pb.py new file mode 100644 index 0000000..81a8105 --- /dev/null +++ b/Routines Generator/src/routines/station_jr_pb.py @@ -0,0 +1,100 @@ +""" +STATION_JR_PB routine generator (R090_STATION_JR_PB). + +Generates AOI_STATION_JR_PB calls for: +- JR2 entries (all JR2 entries) +- JR1 entries that do NOT contain FL or S0 in DESCA +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from typing import TYPE_CHECKING + +import pandas as pd + +from ..utils.common import format_xml_to_match_original +from ..plugin_system import RoutinePlugin, register_plugin + +if TYPE_CHECKING: + from ..data_loader import DataLoader + + +def generate_station_jr_pb_routine(loader: DataLoader) -> ET.Element | None: + """Generate R090_STATION_JR_PB routine from DESC_IP data.""" + print("\n [R090_STATION_JR_PB] Starting STATION_JR_PB routine generation...") + + # Extract STATION_JR_PB data + jr_pb_data = loader.station_jr_pb_data + + if not jr_pb_data: + print(" [WARNING] No STATION_JR_PB configurations found") + return None + + print(f" Found {len(jr_pb_data)} STATION_JR_PB configurations") + + # Create routine element (config-driven name) + try: + from ..config import get_config + routine_name = get_config().routines.name_map.get('station_jr_pb', 'R090_STATION_JR_PB') + except Exception: + routine_name = 'R090_STATION_JR_PB' + routine = ET.Element('Routine', Name=routine_name, Type='RLL') + desc = ET.SubElement(routine, 'Description') + desc.text = 'Station JR Push Button Control Routine' + + rll_content = ET.SubElement(routine, 'RLLContent') + + # Generate rungs for each STATION_JR_PB configuration + rung_number = 0 + for jr_name, config in sorted(jr_pb_data.items()): + # Build AOI call + aoi_params = [ + f"{jr_name}.AOI", + f"{jr_name}.HMI", + f"{jr_name}.CTRL", + "NO_PARTNER", # Always NO_PARTNER + config['input_path'], # Input path from DESC_IP + config['output_path'] # Output path from DESC_IP + ] + + # Create rung + rung = ET.SubElement(rll_content, 'Rung', Number=str(rung_number), Type='N') + comment = ET.SubElement(rung, 'Comment') + comment.text = f"Station JR Push Button Control for {jr_name}" + + text = ET.SubElement(rung, 'Text') + # Build the complete rung text with AOI call and MOVE instruction + aoi_call = f"AOI_STATION_JR_PB({','.join(aoi_params)})" + move_instruction = f"MOVE({jr_name}.CTRL.STS.Log,{jr_name}.CTRL.STS.Log)" + text.text = f"{aoi_call}{move_instruction};" + + rung_number += 1 + + print(f" Generated {rung_number} STATION_JR_PB rungs") + + # Convert to string and format XML to match Rockwell standards + xml_str = ET.tostring(routine, encoding='unicode') + formatted_xml = format_xml_to_match_original(xml_str) + + # Parse back to Element + return ET.fromstring(formatted_xml) + + +class StationJrPbRoutinePlugin(RoutinePlugin): + name = "station_jr_pb" + description = "Generates the STATION_JR_PB routine" + category = "device" + + def can_generate(self) -> bool: + return bool(self.context.data_loader.station_jr_pb_data) + + def generate(self) -> bool: + elem = generate_station_jr_pb_routine(self.context.data_loader) + if elem is None: + return False + self.context.routines_element.append(elem) + return True + + +register_plugin(StationJrPbRoutinePlugin) \ No newline at end of file diff --git a/Routines Generator/src/routines/zones.py b/Routines Generator/src/routines/zones.py index 5b7ee24..d441867 100644 --- a/Routines Generator/src/routines/zones.py +++ b/Routines Generator/src/routines/zones.py @@ -1,148 +1,187 @@ from __future__ import annotations import xml.etree.ElementTree as ET +from typing import List import pandas as pd +from ..plugin_system import RoutinePlugin +from ..logging_config import get_logger +from ..utils.tag_utils import device_base_from_desca, epc_base_from_term, is_estop_desca from ..utils.common import natural_sort_key -from ..utils.tag_utils import ( - device_base_from_desca, - epc_base_from_term, - is_estop_desca, -) - -__all__ = ["create_zones_routine"] -def create_zones_routine(routines: ET.Element, zones_df: pd.DataFrame, epc_df: pd.DataFrame, ignore_estop1ok: bool = False) -> None: - """Append ZONES routine (R030_ZONES) to the given `` element. - - Args: - routines: The XML element to append the routine to - zones_df: DataFrame containing zone data - epc_df: DataFrame containing EPC data - ignore_estop1ok: If True, skip ESTOP devices in zone DCS generation +def create_zones_routine(routines: ET.Element, zones_df: pd.DataFrame, epc_df: pd.DataFrame) -> None: + """Create R030_ZONES routine using zones.json and EPC-derived DCS outputs. + + For each zone entry (name like 01-01) we generate a rung: + AND of DCS outputs (.O1) for all EPC bases within the zone's start/stop + range (e.g., UL1_1..UL1_13 => UL1_3, UL1_4, UL1_9...). + OTE(EStop__OK) with zone name normalized to underscores. + + Then we add a master rung combining top-level zones (those with interlock + equal to the root like MCM01): + XIC(EStop_01_01_OK)XIC(EStop_01_06_OK)... OTE(EStop_MCM_OK) """ + routine = ET.SubElement(routines, "Routine", Name="R030_ZONES", Type="RLL") + rll = ET.SubElement(routine, "RLLContent") - routine = ET.SubElement(routines, "Routine") - routine.set("Name", "R030_ZONES") - routine.set("Type", "RLL") + # Normalize and build dependency order + def norm(s: str) -> str: + return str(s).strip().replace('-', '_') - rll_content = ET.SubElement(routine, "RLLContent") - - rung_num = 0 - - # Handle MCM zone first (special case with no start/end range) - mcm_zone = zones_df[zones_df["NAME"].isin(["MCM", "MCM01"])] # Handle both MCM and MCM01 - if not mcm_zone.empty: - # Use the actual zone name to determine the correct EStop tag - mcm_zone_name = mcm_zone.iloc[0]["NAME"] - mcm_estop_tag = f"EStop_{mcm_zone_name}_OK" - - rung = ET.SubElement(rll_content, "Rung") - rung.set("Number", str(rung_num)) - rung.set("Type", "N") - text = ET.SubElement(rung, "Text") - text.text = f"XIC(MCM_EPB_DCS_CTRL.O1)OTE({mcm_estop_tag});" - rung_num += 1 - - for _, zone in zones_df.sort_values("NAME").iterrows(): - # Skip MCM zones (already handled above) - if zone["NAME"] in ["MCM", "MCM01"]: - continue - - if pd.isna(zone["START"]) or pd.isna(zone["END"]) or zone["START"] == "" or zone["END"] == "": + rows = [] + for _, row in zones_df.iterrows(): + name = str(row.get('name', '')).strip() + if not name: continue + rows.append({ + 'name': name, + 'name_norm': norm(name), + 'interlock': str(row.get('interlock', '')).strip(), + 'interlock_norm': norm(row.get('interlock', '')) if str(row.get('interlock', '')).strip() else '' + }) - zone_name = zone["NAME"].replace(" ", "_").replace("-", "_") - zone_ok_tag = f"EStop_{zone_name}_OK" + logger = get_logger(__name__) - # Check if START and END follow numeric device pattern (prefix_number) - try: - start_parts = zone["START"].split("_") - end_parts = zone["END"].split("_") - - if len(start_parts) < 2 or len(end_parts) < 2: - print(f"Warning: Skipping zone {zone_name} - invalid device name format") - continue - - start_prefix, start_num = start_parts[0], int(start_parts[1]) - end_prefix, end_num = end_parts[0], int(end_parts[1]) - except (ValueError, IndexError): - print(f"Warning: Skipping zone {zone_name} - unable to parse device numbers from START='{zone['START']}' END='{zone['END']}'") - continue - - zone_dcs_tags: set[str] = set() - - si_epc_df = epc_df[epc_df["TERM"].str.startswith("SI", na=False)] - - # Group by TAGNAME to process device groups like estops routine does - epc_groups = si_epc_df.groupby("TAGNAME") - - for tagname, group in epc_groups: - try: - epc_device_parts = tagname.split("_") - if len(epc_device_parts) < 2: - continue - - epc_device_prefix = epc_device_parts[0] - epc_device_num = int(epc_device_parts[1]) - except (ValueError, IndexError): - continue - - # Determine zone membership using the same logic - in_zone = False - if start_prefix == end_prefix: - if epc_device_prefix == start_prefix and start_num <= epc_device_num <= end_num: - in_zone = True + # Topological-ish order: repeatedly add items whose interlock already placed or empty + ordered: List[dict] = [] + placed = set() + remaining = rows.copy() + # guard against cycles with max iterations + for _ in range(len(rows) + 5): + progressed = False + next_remaining = [] + for r in remaining: + if not r['interlock'] or r['interlock_norm'] in placed: + ordered.append(r) + placed.add(r['name_norm']) + progressed = True else: - # Handle cross-prefix zones if needed - pass + next_remaining.append(r) + remaining = next_remaining + if not remaining or not progressed: + # append any unresolved to keep moving + ordered.extend(remaining) + break - if in_zone: - # Use the exact same logic as estops routine to generate DCS tag names - base_name = device_base_from_desca(group.iloc[0]["DESCA"]) - estop_device = is_estop_desca(group.iloc[0]["DESCA"]) - - if estop_device: - # Skip ESTOP devices if ignore_estop1ok flag is set - if not ignore_estop1ok: - # ESTOP devices: same as estops routine line 48 - dcs_tag = f"{base_name}_ESTOP1_DCS_CTRL" - zone_dcs_tags.add(f"{dcs_tag}.O1") - else: - # EPC devices: same as estops routine lines 69-90 - # Bucket by EPC base using epc_base_from_term() - epc_pairs: dict[str, list] = {} - for _, row in group.iterrows(): - epc_base = epc_base_from_term(str(row["TERM"])) - if epc_base is None: - continue - epc_pairs.setdefault(epc_base, []).append(row) - - # Create DCS tags for each EPC base (EPC1, EPC2, etc.) - for epc_base in epc_pairs.keys(): - dcs_tag = f"{base_name}_{epc_base}_DCS_CTRL" - zone_dcs_tags.add(f"{dcs_tag}.O1") + # Build available DCS controllers from EPC dataframe (matches what ESTOPS created) + available_dcs: dict[str, set[str]] = {} + try: + si_epc_df = epc_df[epc_df["TERM"].str.startswith("SI", na=False)] if "TERM" in epc_df.columns else epc_df.iloc[0:0] + epc_groups = si_epc_df.groupby("TAGNAME") if "TAGNAME" in si_epc_df.columns else [] + for tagname, group in epc_groups: + if group.empty: + continue + first_desca = str(group.iloc[0]["DESCA"]) + base_name = device_base_from_desca(first_desca) + present: set[str] = set() + # If this is an ESTOP device, ESTOPS routine created _ESTOP1_DCS_CTRL + if is_estop_desca(first_desca): + present.add("ESTOP1") + else: + # Otherwise, gather EPC1/EPC2 per SI terminals present + for _, row in group.iterrows(): + eb = epc_base_from_term(str(row.get("TERM", ""))) + if eb: + present.add(eb) + if present: + available_dcs[base_name] = present + except Exception: + # If anything goes wrong, leave available_dcs empty (no rungs will be emitted) + available_dcs = {} - # Always create a rung for each zone, regardless of whether it has EPC devices - rung = ET.SubElement(rll_content, "Rung") - rung.set("Number", str(rung_num)) - rung.set("Type", "N") + # Generate rungs using available EPC DCS tags only + rung_num = 0 + # We no longer aggregate top-level zone OKs into EStop_MCM_OK; that is driven by MCM_EPB_DCS_CTRL.O1 + + # Helper to parse token like UL1_13 into (prefix='UL1', num=13) + def parse_ul(token: str) -> tuple[str, int] | None: + try: + parts = token.split('_') + return parts[0], int(parts[1]) + except Exception: + return None + + # Build an index of EPC bases from the DCS tags that will exist + # We infer bases from the EPC dataframe via plugin path; here create a lazy accessor + epc_bases: List[str] = [] + try: + # Import via local to avoid cycles + from ..data_loader import DataLoader as _DL + # We cannot instantiate here; the plugin generate() will re-call this + # function so as a fallback we create a broad set from zones tokens + for _, zr in zones_df.iterrows(): + st = str(zr.get('start', '')).strip() + sp = str(zr.get('stop', '')).strip() + for tok in (st, sp): + if '_' in tok: + epc_bases.append(tok) + except Exception: + pass + + # For each zone, assemble the DCS XICs + for r in ordered: + zone_name = r['name'] + if zone_name == 'MCM01': + # root marker; skip rung here, we'll compute master later + continue + start_tok = str(zones_df[zones_df['name'] == zone_name].iloc[0].get('start', '')).strip() + stop_tok = str(zones_df[zones_df['name'] == zone_name].iloc[0].get('stop', '')).strip() + bounds_s = parse_ul(start_tok) + bounds_e = parse_ul(stop_tok) + if not bounds_s or not bounds_e or bounds_s[0] != bounds_e[0]: + continue + prefix = bounds_s[0] + lo, hi = sorted([bounds_s[1], bounds_e[1]]) + + # Determine candidate bases by scanning all possible indices in [lo, hi] + # Do not constrain prefix to 'UL'; respect whatever prefix zones.json provides + candidates = [f"{prefix}_{i}" for i in range(lo, hi + 1)] + + # Build XIC chain for EPC1/EPC2 DCS outputs that actually exist per ESTOPS + xic_parts: List[str] = [] + included_dcs: List[str] = [] + for base in candidates: + dc_set = available_dcs.get(base, set()) + for label in sorted(dc_set): # deterministic order + dcs_ref = f"{base}_{label}_DCS_CTRL.O1" + xic_parts.append(f"XIC({dcs_ref})") + included_dcs.append(dcs_ref) + + if not xic_parts: + continue + ok_tag = f"EStop_{zone_name.replace('-', '_')}_OK" + rung = ET.SubElement(rll, "Rung", Number=str(rung_num), Type="N") text = ET.SubElement(rung, "Text") + text.text = ''.join(xic_parts) + f"OTE({ok_tag});" + rung_num += 1 + logger.debug("Zones: rung", zone=zone_name, interlock=r['interlock'], dcs_list=included_dcs) + + # Master EStop_MCM_OK is tied directly to the MCM EPB DCS output bit + rung = ET.SubElement(rll, "Rung", Number=str(rung_num), Type="N") + text = ET.SubElement(rung, "Text") + text.text = "XIC(MCM_EPB_DCS_CTRL.O1)OTE(EStop_MCM_OK);" + + +class ZonesRoutinePlugin(RoutinePlugin): + name = "zones" + description = "Generates the R030_ZONES routine from configured zones" + category = "safety" + + def can_generate(self) -> bool: + try: + # Generate if zones exist (including default single MCM row) + return self.context.data_loader.zones is not None + except Exception: + return False + + def generate(self) -> bool: + create_zones_routine( + self.context.routines_element, + self.context.data_loader.zones, + self.context.data_loader.epc, + ) + return True - if zone_dcs_tags: - # Zone has EPC devices - create XIC conditions for all devices in the zone - sorted_tags = sorted(zone_dcs_tags, key=natural_sort_key) - ladder_text = "" - for tag in sorted_tags: - ladder_text += f"XIC({tag})" - ladder_text += f"OTE({zone_ok_tag});" - print(f" Created zone rung for {zone_name}: {len(sorted_tags)} EPC devices") - else: - # Zone has no EPC devices - just set the zone OK tag unconditionally - ladder_text = f"OTE({zone_ok_tag});" - print(f" Created zone rung for {zone_name}: no EPC devices (unconditional OTE)") - text.text = ladder_text - rung_num += 1 \ No newline at end of file diff --git a/Routines Generator/src/unified_cli.py b/Routines Generator/src/unified_cli.py index a35acd0..da91833 100644 --- a/Routines Generator/src/unified_cli.py +++ b/Routines Generator/src/unified_cli.py @@ -1,274 +1,227 @@ -#!/usr/bin/env python3 -"""Unified CLI for PLC Routines Generator. - -Replaces generate_all.py, generate_safety_only.py, generate_fiom.py, and cli.py -with a single, consistent interface. -""" - -from __future__ import annotations - -import argparse -import sys -from pathlib import Path -from typing import Optional, List, Dict - -# Import our refactored components -from .config import GeneratorConfig, get_config, set_config -from .container import GeneratorContainer -from .logging_config import setup_logging, get_logger - -logger = get_logger(__name__) - -class GenerateCommand: - """Base class for generation commands.""" - - def __init__(self, container: GeneratorContainer): - self.container = container - self.config = container.config - - def execute(self, args: argparse.Namespace) -> None: - """Execute the command with given arguments.""" - raise NotImplementedError - -class GenerateSafetyCommand(GenerateCommand): - """Generate SafetyProgram L5X file.""" - - def execute(self, args: argparse.Namespace) -> None: - logger.info("Generating SafetyProgram L5X...") - - safety_gen = self.container.get_safety_program_generator() - output_file = args.output or self.config.files.safety_l5x - - safety_gen.write(output_file) - logger.info(f"[SUCCESS] SafetyProgram written to {output_file}") - -class GenerateMainCommand(GenerateCommand): - """Generate MainProgram L5X file.""" - - def execute(self, args: argparse.Namespace) -> None: - logger.info("Generating MainProgram L5X...") - - main_gen = self.container.get_main_program_generator() - output_file = args.output or self.config.files.main_l5x - - main_gen.write(output_file) - logger.info(f"[SUCCESS] MainProgram written to {output_file}") - -class GenerateCSVCommand(GenerateCommand): - """Generate complete CSV tags file.""" - - def execute(self, args: argparse.Namespace) -> None: - logger.info("Generating CSV tags file...") - - csv_writer = self.container.get_csv_writer() - output_file = args.output or self.config.files.complete_csv - original_csv = args.original or self.config.files.original_csv - - if not Path(original_csv).exists(): - logger.warning(f"Original CSV file not found: {original_csv}, skipping CSV generation") - return - - beacon_tags = csv_writer( - excel_file=self.config.files.excel_file, - original_csv=original_csv, - output_file=output_file - ) - - logger.info(f"[SUCCESS] CSV written to {output_file}") - -class GenerateMappingCommand(GenerateCommand): - """Generate safety tag mapping file.""" - - def execute(self, args: argparse.Namespace) -> None: - logger.info("Generating safety tag mapping...") - - mapping_writer = self.container.get_mapping_writer() - output_file = args.output or self.config.files.mapping_txt - - data_loader = self.container.get_data_loader() - safety_tags = data_loader.safety_tags_from_pb - - mapping_writer( - safety_tags=safety_tags, - beacon_so_tags=set(), # No beacon tags in current system - beacon_sft_mappings=set(), - output_file=output_file - ) - - logger.info(f"[SUCCESS] Safety tag mapping written to {output_file}") - logger.info(f" - Safety tags: {len(safety_tags)}") - -class GenerateAllCommand(GenerateCommand): - """Generate all files (Safety L5X, Main L5X, CSV, Mapping).""" - - def execute(self, args: argparse.Namespace) -> None: - logger.info("=== Generating All PLC Artifacts ===") - - # Update output directory if specified - if args.output_dir: - output_dir = Path(args.output_dir) - output_dir.mkdir(exist_ok=True) - - # Update config with new output directory - config = self.container.config - config.files.output_dir = output_dir - - # Generate Safety Program - safety_cmd = GenerateSafetyCommand(self.container) - safety_cmd.execute(argparse.Namespace(output=None)) - - # Generate Main Program - main_cmd = GenerateMainCommand(self.container) - main_cmd.execute(argparse.Namespace(output=None)) - - # Generate CSV - csv_cmd = GenerateCSVCommand(self.container) - csv_cmd.execute(argparse.Namespace(output=None, original=None)) - - # Generate Mapping - mapping_cmd = GenerateMappingCommand(self.container) - mapping_cmd.execute(argparse.Namespace(output=None)) - - logger.info("=== All artifacts generated successfully! ===") - -def create_parser() -> argparse.ArgumentParser: - """Create the unified argument parser.""" - - parser = argparse.ArgumentParser( - prog='plc-generate', - description='Generate PLC routine artifacts from DESC_IP data' - ) - - # Global options - parser.add_argument( - '--config', - type=Path, - default=Path(__file__).parent.parent.parent / 'generator_config.json', - help='Configuration file path' - ) - parser.add_argument( - '--excel-file', - type=Path, - help='Override Excel file path from config' - ) - parser.add_argument( - '--zones', - type=str, - help='JSON string with zones data (overrides Excel ZONES sheet)' - ) - parser.add_argument( - '--log-level', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], - default='INFO', - help='Set logging level' - ) - - # Subcommands - subparsers = parser.add_subparsers(dest='command', required=True) - - # Safety command - safety_parser = subparsers.add_parser( - 'safety', - help='Generate SafetyProgram L5X' - ) - safety_parser.add_argument( - '-o', '--output', - help='Output file path' - ) - - # Main command - main_parser = subparsers.add_parser( - 'main', - help='Generate MainProgram L5X' - ) - main_parser.add_argument( - '-o', '--output', - help='Output file path' - ) - - # CSV command - csv_parser = subparsers.add_parser( - 'csv', - help='Generate complete CSV tags file' - ) - csv_parser.add_argument( - '--original', - help='Original CSV file path' - ) - csv_parser.add_argument( - '-o', '--output', - help='Output file path' - ) - - # Mapping command - mapping_parser = subparsers.add_parser( - 'mapping', - help='Generate safety tag mapping file' - ) - mapping_parser.add_argument( - '-o', '--output', - help='Output file path' - ) - - # All command - all_parser = subparsers.add_parser( - 'all', - help='Generate all artifacts (Safety L5X, Main L5X, CSV, Mapping)' - ) - all_parser.add_argument( - '--output-dir', - help='Output directory for all files' - ) - - return parser - -def main(argv: Optional[List[str]] = None) -> None: - """Main entry point.""" - - parser = create_parser() - args = parser.parse_args(argv) - - # Setup logging - setup_logging(level=args.log_level) - - try: - # Load configuration - config = GeneratorConfig.from_file(args.config) - - # Override Excel file if specified - if args.excel_file: - config.files.excel_file = args.excel_file - - # Parse zones if specified - zones_dict = None - if args.zones: - import json - zones_dict = json.loads(args.zones) - - # Set global config - set_config(config) - - # Create dependency injection container - container = GeneratorContainer(config, zones_dict) - - # Execute command - command_map = { - 'safety': GenerateSafetyCommand, - 'main': GenerateMainCommand, - 'csv': GenerateCSVCommand, - 'mapping': GenerateMappingCommand, - 'all': GenerateAllCommand, - } - - command_class = command_map[args.command] - command = command_class(container) - command.execute(args) - - except Exception as e: - logger.error(f"Generation failed: {e}") - if args.log_level == 'DEBUG': - logger.exception("Full traceback:") - sys.exit(1) - -if __name__ == '__main__': +#!/usr/bin/env python3 +"""Unified CLI for PLC Routines Generator. + +Replaces generate_all.py, generate_safety_only.py, generate_fiom.py, and cli.py +with a single, consistent interface. +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path +from typing import Optional, List, Dict + +# Import our refactored components +from .config import GeneratorConfig, get_config, set_config +from .container import GeneratorContainer +from .logging_config import setup_logging, get_logger + +logger = get_logger(__name__) + +class GenerateCommand: + """Base class for generation commands.""" + + def __init__(self, container: GeneratorContainer): + self.container = container + self.config = container.config + + def execute(self, args: argparse.Namespace) -> None: + """Execute the command with given arguments.""" + raise NotImplementedError + +class GenerateSafetyCommand(GenerateCommand): + """Generate SafetyProgram L5X file.""" + + def execute(self, args: argparse.Namespace) -> None: + logger.info("Generating SafetyProgram L5X...") + + safety_gen = self.container.get_safety_program_generator() + output_file = args.output or self.config.files.safety_l5x + + safety_gen.write(output_file) + logger.info(f"[SUCCESS] SafetyProgram written to {output_file}") + +class GenerateMainCommand(GenerateCommand): + """Generate MainProgram L5X file.""" + + def execute(self, args: argparse.Namespace) -> None: + logger.info("Generating MainProgram L5X...") + + main_gen = self.container.get_main_program_generator() + output_file = args.output or self.config.files.main_l5x + + main_gen.write(output_file) + logger.info(f"[SUCCESS] MainProgram written to {output_file}") + +# NOTE: CSV generation has been deprecated and removed from the unified CLI. + +class GenerateMappingCommand(GenerateCommand): + """Generate safety tag mapping file.""" + + def execute(self, args: argparse.Namespace) -> None: + logger.info("Generating safety tag mapping...") + + mapping_writer = self.container.get_mapping_writer() + output_file = args.output or self.config.files.mapping_txt + + data_loader = self.container.get_data_loader() + safety_tags = data_loader.safety_tags_from_pb + + mapping_writer( + safety_tags=safety_tags, + beacon_so_tags=set(), # No beacon tags in current system + beacon_sft_mappings=set(), + output_file=output_file + ) + + logger.info(f"[SUCCESS] Safety tag mapping written to {output_file}") + logger.info(f" - Safety tags: {len(safety_tags)}") + +class GenerateAllCommand(GenerateCommand): + """Generate all files (Safety L5X, Main L5X, CSV, Mapping).""" + + def execute(self, args: argparse.Namespace) -> None: + logger.info("=== Generating All PLC Artifacts ===") + + # Update output directory if specified + if args.output_dir: + output_dir = Path(args.output_dir) + output_dir.mkdir(exist_ok=True) + + # Update config with new output directory + config = self.container.config + config.files.output_dir = output_dir + + # Generate Safety Program + safety_cmd = GenerateSafetyCommand(self.container) + safety_cmd.execute(argparse.Namespace(output=None)) + + # Generate Main Program + main_cmd = GenerateMainCommand(self.container) + main_cmd.execute(argparse.Namespace(output=None)) + + # Generate Mapping + mapping_cmd = GenerateMappingCommand(self.container) + mapping_cmd.execute(argparse.Namespace(output=None)) + + logger.info("=== All artifacts generated successfully! ===") + +def create_parser() -> argparse.ArgumentParser: + """Create the unified argument parser.""" + + parser = argparse.ArgumentParser( + prog='plc-generate', + description='Generate PLC routine artifacts from DESC_IP data' + ) + + # Global options + parser.add_argument( + '--config', + type=Path, + default=Path(__file__).parent.parent.parent / 'generator_config.json', + help='Configuration file path' + ) + parser.add_argument( + '--excel-file', + type=Path, + help='Override Excel file path from config' + ) + # Zones CLI option removed + parser.add_argument( + '--log-level', + choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], + default='INFO', + help='Set logging level' + ) + + # Subcommands + subparsers = parser.add_subparsers(dest='command', required=True) + + # Safety command + safety_parser = subparsers.add_parser( + 'safety', + help='Generate SafetyProgram L5X' + ) + safety_parser.add_argument( + '-o', '--output', + help='Output file path' + ) + + # Main command + main_parser = subparsers.add_parser( + 'main', + help='Generate MainProgram L5X' + ) + main_parser.add_argument( + '-o', '--output', + help='Output file path' + ) + + # Mapping command + mapping_parser = subparsers.add_parser( + 'mapping', + help='Generate safety tag mapping file' + ) + mapping_parser.add_argument( + '-o', '--output', + help='Output file path' + ) + + # All command + all_parser = subparsers.add_parser( + 'all', + help='Generate all artifacts (Safety L5X, Main L5X, CSV, Mapping)' + ) + all_parser.add_argument( + '--output-dir', + help='Output directory for all files' + ) + + return parser + +def main(argv: Optional[List[str]] = None) -> None: + """Main entry point.""" + + parser = create_parser() + args = parser.parse_args(argv) + + # Setup logging + setup_logging(level=args.log_level) + + try: + # Load configuration + config = GeneratorConfig.from_file(args.config) + + # Override Excel file if specified + if args.excel_file: + config.files.excel_file = args.excel_file + + # Zones support removed + + # Set global config + set_config(config) + + # Create dependency injection container + container = GeneratorContainer(config, None) + + # Execute command + command_map = { + 'safety': GenerateSafetyCommand, + 'main': GenerateMainCommand, + 'mapping': GenerateMappingCommand, + 'all': GenerateAllCommand, + } + + command_class = command_map[args.command] + command = command_class(container) + command.execute(args) + + except Exception as e: + logger.error(f"Generation failed: {e}") + if args.log_level == 'DEBUG': + logger.exception("Full traceback:") + sys.exit(1) + +if __name__ == '__main__': main() \ No newline at end of file diff --git a/Routines Generator/src/writers/__init__.py b/Routines Generator/src/writers/__init__.py index 94ac1f7..4b32ee0 100644 --- a/Routines Generator/src/writers/__init__.py +++ b/Routines Generator/src/writers/__init__.py @@ -1,7 +1,5 @@ -from .csv_writer import create_new_csv_with_tags -from .mapping_writer import create_safety_tag_mapping - -__all__ = [ - 'create_new_csv_with_tags', - 'create_safety_tag_mapping', +from .mapping_writer import create_safety_tag_mapping + +__all__ = [ + 'create_safety_tag_mapping', ] \ No newline at end of file diff --git a/Routines Generator/src/writers/__pycache__/__init__.cpython-312.pyc b/Routines Generator/src/writers/__pycache__/__init__.cpython-312.pyc index ea36e67..69acd7a 100644 Binary files a/Routines Generator/src/writers/__pycache__/__init__.cpython-312.pyc and b/Routines Generator/src/writers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Routines Generator/src/writers/__pycache__/csv_writer.cpython-312.pyc b/Routines Generator/src/writers/__pycache__/csv_writer.cpython-312.pyc index 44b734e..3fa0f30 100644 Binary files a/Routines Generator/src/writers/__pycache__/csv_writer.cpython-312.pyc and b/Routines Generator/src/writers/__pycache__/csv_writer.cpython-312.pyc differ diff --git a/Routines Generator/src/writers/__pycache__/xml_tag_writer.cpython-312.pyc b/Routines Generator/src/writers/__pycache__/xml_tag_writer.cpython-312.pyc index ab3662d..a52cb89 100644 Binary files a/Routines Generator/src/writers/__pycache__/xml_tag_writer.cpython-312.pyc and b/Routines Generator/src/writers/__pycache__/xml_tag_writer.cpython-312.pyc differ diff --git a/Routines Generator/src/writers/csv_writer.py b/Routines Generator/src/writers/csv_writer.py deleted file mode 100644 index 3b01d70..0000000 --- a/Routines Generator/src/writers/csv_writer.py +++ /dev/null @@ -1,583 +0,0 @@ -from __future__ import annotations - -import pandas as pd -import re -from pathlib import Path -from typing import Tuple - -import xml.etree.ElementTree as ET - - -# -- helpers ----------------------------------------------------------------- - -def _natural_sort_key(s: str) -> tuple: - """Produce a key for human-friendly sorting (e.g. UL2 < UL10).""" - return tuple(int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)) - -# --------------------------------------------------------------------------- - -__all__ = [ - 'create_new_csv_with_tags', - 'create_limited_csv_with_tags', -] - - -def create_limited_csv_with_tags( - excel_file: str | Path, - original_csv_file: str | Path, - new_csv_file: str | Path, -) -> Tuple[int, int, int]: - """Build a limited controller-tags CSV using DESC_IP extraction for safety-only mode. - - Returns: - (count_standard, count_safety, count_dcs) - """ - - excel_file = Path(excel_file) - original_csv_file = Path(original_csv_file) - new_csv_file = Path(new_csv_file) - - # ✅ Use DataLoader for DESC_IP extraction instead of reading separate sheets - from ..data_loader import DataLoader - loader = DataLoader(excel_path=excel_file) - - rst_df = loader.rst # ← DESC_IP extraction - sto_df = loader.sto # ← DESC_IP extraction - epc_df = loader.epc # ← DESC_IP extraction - zones_df = loader.zones # ← From zones_config.py or zones_dict - - safety_tags: set[str] = set() - standard_tags: set[str] = set() - dcs_tags: set[str] = set() - module_tags: set[tuple[str, str]] = set() - - # Static MCM tags - safety_tags.update({'MCM_EPB_STATUS', 'SFT_MCM_S_PB', 'EStop_MCM01_OK'}) - standard_tags.add('MCM_S_PB') - dcs_tags.add('MCM_EPB_DCS_CTRL') - - # --- EPC-derived tags ---------------------------------------------------- - si_epc_df = epc_df[epc_df['TERM'].str.startswith('SI', na=False)] - - for tagname, group in si_epc_df.groupby('TAGNAME'): - # EPC1/EPC2 pairing - epc_pairs: dict[str, list[pd.Series]] = {} - for _, row in group.iterrows(): - term_num = int(row['TERM'][2:]) - epc_base = 'EPC1' if term_num in (0, 1) else 'EPC2' if term_num in (2, 3) else None - if epc_base: - epc_pairs.setdefault(epc_base, []).append(row) - - # Status tags (EPC devices only) - is_estop_device = any('ESTOP' in row.DESCA for row in group.itertuples()) - if not is_estop_device: - for epc_base, epc_rows in epc_pairs.items(): - if len(epc_rows) >= 2: - device_base = '_'.join(epc_rows[0].DESCA.split('_')[:2]) - safety_tags.add(f"{device_base}_{epc_base}_ESTOP_STATUS") - - # OK tags (ESTOP and EPC) - for _, row in group.iterrows(): - desca = row['DESCA'] - if re.match(r'.*_EPC\d+$', desca): - desca += '_1' - elif re.match(r'.*ESTOP\d+$', desca) and '_2' not in desca: - desca += '_1' - ok_tag = f"{desca}_OK" if 'ESTOP' in desca else f"{desca}_ESTOP_OK" - safety_tags.add(ok_tag) - - # --- RST push-button tags ---------------------------------------------- - for _, row in rst_df.iterrows(): - if pd.notna(row['DESCA']) and (any(k in row['DESCA'] for k in ('S1_PB', 'S2_PB')) or row['DESCA'].endswith('SPB')): - safety_tags.add(f"SFT_{row['DESCA']}") - standard_tags.add(row['DESCA']) - - # --- Reset tags for EPC devices ---------------------------------------- - epc_groups_for_resets: set[str] = set() - for _, row in si_epc_df.iterrows(): - if pd.isna(row['TERM']) or not str(row['TERM']).startswith('SI'): - continue - if 'ESTOP' in row['DESCA']: - continue - epc_prefix = '_'.join(row['DESCA'].split('_')[:2]) - epc_group = f"{epc_prefix}_EPC1" if 'EPC1' in row['DESCA'] else f"{epc_prefix}_EPC2" - epc_groups_for_resets.add(epc_group) - for epc_group in epc_groups_for_resets: - safety_tags.add(f"RST_{epc_group}_ESTOP") - - # --- DCS tags ----------------------------------------------------------- - for tagname, group in si_epc_df.groupby('TAGNAME'): - base_name = '_'.join(group.iloc[0]['DESCA'].split('_')[:2]) - if 'ESTOP' in group.iloc[0]['DESCA']: - dcs_tags.add(f"{base_name}_ESTOP1_DCS_CTRL") - else: - epc_bases = {'EPC1' if int(r['TERM'][2:]) in (0,1) else 'EPC2' for _, r in group.iterrows() if int(r['TERM'][2:]) in (0,1,2,3)} - for epc_base in epc_bases: - dcs_tags.add(f"{base_name}_{epc_base}_DCS_CTRL") - - # --- Zone OK tags ------------------------------------------------------- - for _, zone in zones_df.iterrows(): - if pd.isna(zone['START']) or pd.isna(zone['END']): - continue - zone_name = zone['NAME'].replace(' ', '_').replace('-', '_') - safety_tags.add(f"EStop_{zone_name}_OK") - - # --- CHECKED tags ------------------------------------------------------- - for tagname, group in si_epc_df.groupby('TAGNAME'): - base_name = '_'.join(group.iloc[0]['DESCA'].split('_')[:2]) - if 'ESTOP' in group.iloc[0]['DESCA']: - standard_tags.add(f"{base_name}_ESTOP1_CHECKED") - else: - epc_bases = {'EPC1' if int(r['TERM'][2:]) in (0,1) else 'EPC2' for _, r in group.iterrows() if int(r['TERM'][2:]) in (0,1,2,3)} - for epc_base in epc_bases: - standard_tags.add(f"{base_name}_{epc_base}_CHECKED") - - # --- Limited Module tags ----------------------------------------------- - # Only basic tags for safety-only mode - module_tags.add(('Rack', 'UDT_AOI_RACK')) - module_tags.add(('MCM01', 'UDT_AOI_MCM')) - - original_lines = Path(original_csv_file).read_text('utf-8').splitlines(keepends=True) - - # Find the header line (TYPE,SCOPE,NAME,DESCRIPTION,DATATYPE,SPECIFIER,ATTRIBUTES) - header_line_idx = None - for i, line in enumerate(original_lines): - if line.strip().startswith('TYPE,SCOPE,NAME,DESCRIPTION,DATATYPE,SPECIFIER,ATTRIBUTES'): - header_line_idx = i - break - - if header_line_idx is None: - raise ValueError("Could not find header line in original CSV") - - # Find the first TAG entry after the header to insert AOI tags after it - first_tag_line_idx = None - for i in range(header_line_idx + 1, len(original_lines)): - line = original_lines[i].strip() - if line.startswith('TAG,,') and not line.startswith('TAG,,MCM01,'): # Skip MCM01 tag if present - first_tag_line_idx = i - break - - if first_tag_line_idx is None: - # If no existing TAG found, insert after header - first_tag_line_idx = header_line_idx - - # Find where to stop including original content (exclude previously generated tags) - # Also find where RCOMMENT section starts to place generated tags before it - last_original_line = len(original_lines) - rcomment_start_line = None - - # Look for the start of RCOMMENT section or previously generated tags - for i in range(len(original_lines)-1, -1, -1): - line = original_lines[i].strip() - - # If we find previously generated tags, mark this as the cutoff - if any(k in line for k in ('EStop_MCM01_OK','EStop_ZONE_','_ESTOP_STATUS','_DCS_CTRL','RST_')): - last_original_line = i - continue - - # Look for the start of RCOMMENT section (first TYPE,SCOPE,ROUTINE,COMMENT header) - if line.startswith('TYPE,SCOPE,ROUTINE,COMMENT,OWNING_ELEMENT,LOCATION') and rcomment_start_line is None: - # Find the actual start by looking backward to find the last TAG/COMMENT before this header - for j in range(i-1, -1, -1): - prev_line = original_lines[j].strip() - if prev_line.startswith(('TAG,,', 'COMMENT,,')): - rcomment_start_line = j + 1 # Insert after the last TAG/COMMENT - break - break - - # If no RCOMMENT section found, use the end of file - if rcomment_start_line is not None and rcomment_start_line < last_original_line: - last_original_line = rcomment_start_line - - # Write the new CSV file - new_csv_file = Path(new_csv_file) - with open(str(new_csv_file), 'w', encoding='utf-8') as f: - # Write lines up to and including the first TAG line - f.writelines(original_lines[:first_tag_line_idx + 1]) - - # Insert AOI module tags immediately after the first TAG line (sorted by module name) - for tag_name, udt_type in sorted(module_tags, key=lambda x: _natural_sort_key(x[0])): - f.write(f'TAG,,{tag_name},"","{udt_type}","","(Class := Standard, Constant := false, ExternalAccess := Read/Write)"\n') - - # Continue with the rest of the original content (after first TAG line, up to last_original_line) - f.writelines(original_lines[first_tag_line_idx + 1:last_original_line]) - - # Append other generated tags at the end (stable alphabetical order) - for tag in sorted(standard_tags): - f.write(f'TAG,,{tag},"","BOOL","","(Class := Standard, RADIX := Decimal, Constant := false, ExternalAccess := Read/Write)"\n') - for tag in sorted(safety_tags): - f.write(f'TAG,,{tag},"","BOOL","","(Class := Safety, RADIX := Decimal, Constant := false, ExternalAccess := Read/Write)"\n') - for tag in sorted(dcs_tags): - f.write(f'TAG,,{tag},"","DCI_STOP","","(Class := Safety, Constant := false, ExternalAccess := Read/Write)"\n') - - print(f"Created limited CSV file: {new_csv_file}") - print(f"Added {len(standard_tags)} Standard BOOL tags, {len(safety_tags)} Safety BOOL tags, {len(dcs_tags)} DCS tags, and {len(module_tags)} Module UDT tags (limited to safety-only mode)") - return (len(standard_tags), len(safety_tags), len(dcs_tags)) - - -def create_new_csv_with_tags( - excel_file: str | Path, - original_csv_file: str | Path, - new_csv_file: str | Path, -) -> set[tuple[str, str]]: - """Build a fresh controller-tags CSV identical to legacy implementation. - - Returns: - (count_standard, count_safety, count_dcs) - """ - - excel_file = Path(excel_file) - original_csv_file = Path(original_csv_file) - new_csv_file = Path(new_csv_file) - - # ✅ Use DataLoader for DESC_IP extraction instead of reading 19 separate sheets - from ..data_loader import DataLoader - loader = DataLoader(excel_path=excel_file) - - # Safety devices extracted from DESC_IP - rst_df = loader.rst # ← DESC_IP extraction - sto_df = loader.sto # ← DESC_IP extraction - epc_df = loader.epc # ← DESC_IP extraction - zones_df = loader.zones # ← From zones_config.py or zones_dict - desc_ip_df = loader.desc_ip # ← Direct DESC_IP access - - # Device-specific data: Filter DESC_IP by patterns instead of separate sheets - apf_df = desc_ip_df[desc_ip_df['TAGNAME'].str.contains('VFD', na=False)] - dpm_df = desc_ip_df[desc_ip_df['PARTNUMBER'].str.contains('DPM', na=False)] - fiom_df = desc_ip_df[desc_ip_df['TAGNAME'].str.contains('FIO', na=False) & ~desc_ip_df['TAGNAME'].str.contains('FIOH', na=False)] - fioh_df = desc_ip_df[desc_ip_df['TAGNAME'].str.contains('FIOH', na=False)] - pmm_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('PMM', na=False)] - extendo_df = desc_ip_df[desc_ip_df['PARTNUMBER'].str.contains('EXTENDO', na=False)] - cb_monitor_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('CB', na=False)] - encoder_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('ENC', na=False)] - ss_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('SS', na=False)] - s_pb_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('S_PB', na=False)] - jr_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('JR', na=False)] - jpe_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('JPE', na=False)] - epc_station_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('EPC_STATION', na=False)] - epc_bcn_df = desc_ip_df[desc_ip_df['DESCA'].str.contains('EPC_BCN', na=False)] - - safety_tags: set[str] = set() - standard_tags: set[str] = set() - dcs_tags: set[str] = set() - module_tags: set[tuple[str, str]] = set() - beacon_so_tags: set[tuple[str, str]] = set() # Track beacon SO tags for alias mapping - - # Static MCM tags - safety_tags.update({'MCM_EPB_STATUS', 'SFT_MCM_S_PB', 'EStop_MCM01_OK'}) - standard_tags.add('MCM_S_PB') - dcs_tags.add('MCM_EPB_DCS_CTRL') - - # --- EPC-derived tags ---------------------------------------------------- - si_epc_df = epc_df[epc_df['TERM'].str.startswith('SI', na=False)] - - for tagname, group in si_epc_df.groupby('TAGNAME'): - # EPC1/EPC2 pairing - epc_pairs: dict[str, list[pd.Series]] = {} - for _, row in group.iterrows(): - term_num = int(row['TERM'][2:]) - epc_base = 'EPC1' if term_num in (0, 1) else 'EPC2' if term_num in (2, 3) else None - if epc_base: - epc_pairs.setdefault(epc_base, []).append(row) - - # Status tags (EPC devices only) - is_estop_device = any('ESTOP' in row.DESCA for row in group.itertuples()) - if not is_estop_device: - for epc_base, epc_rows in epc_pairs.items(): - if len(epc_rows) >= 2: - device_base = '_'.join(epc_rows[0].DESCA.split('_')[:2]) - safety_tags.add(f"{device_base}_{epc_base}_ESTOP_STATUS") - - # OK tags (ESTOP and EPC) - for _, row in group.iterrows(): - desca = row['DESCA'] - if re.match(r'.*_EPC\d+$', desca): - desca += '_1' - elif re.match(r'.*ESTOP\d+$', desca) and '_2' not in desca: - desca += '_1' - ok_tag = f"{desca}_OK" if 'ESTOP' in desca else f"{desca}_ESTOP_OK" - safety_tags.add(ok_tag) - - # --- RST push-button tags ---------------------------------------------- - for _, row in rst_df.iterrows(): - if pd.notna(row['DESCA']) and (any(k in row['DESCA'] for k in ('S1_PB', 'S2_PB')) or row['DESCA'].endswith('SPB')): - safety_tags.add(f"SFT_{row['DESCA']}") - standard_tags.add(row['DESCA']) - - # --- Beacon SFT tags --------------------------------------------------- - for _, row in desc_ip_df.iterrows(): - desca = str(row['DESCA']) - signal = str(row['SIGNAL']) - # Only process beacon outputs - if 'BCN' in desca and signal == 'O': - safety_tags.add(f"SFT_{desca}") - - # --- Reset tags for EPC devices ---------------------------------------- - epc_groups_for_resets: set[str] = set() - for _, row in si_epc_df.iterrows(): - if pd.isna(row['TERM']) or not str(row['TERM']).startswith('SI'): - continue - if 'ESTOP' in row['DESCA']: - continue - epc_prefix = '_'.join(row['DESCA'].split('_')[:2]) - epc_group = f"{epc_prefix}_EPC1" if 'EPC1' in row['DESCA'] else f"{epc_prefix}_EPC2" - epc_groups_for_resets.add(epc_group) - for epc_group in epc_groups_for_resets: - safety_tags.add(f"RST_{epc_group}_ESTOP") - - # --- DCS tags ----------------------------------------------------------- - for tagname, group in si_epc_df.groupby('TAGNAME'): - base_name = '_'.join(group.iloc[0]['DESCA'].split('_')[:2]) - if 'ESTOP' in group.iloc[0]['DESCA']: - dcs_tags.add(f"{base_name}_ESTOP1_DCS_CTRL") - else: - epc_bases = {'EPC1' if int(r['TERM'][2:]) in (0,1) else 'EPC2' for _, r in group.iterrows() if int(r['TERM'][2:]) in (0,1,2,3)} - for epc_base in epc_bases: - dcs_tags.add(f"{base_name}_{epc_base}_DCS_CTRL") - - # --- Zone OK tags ------------------------------------------------------- - for _, zone in zones_df.iterrows(): - if pd.isna(zone['START']) or pd.isna(zone['END']): - continue - zone_name = zone['NAME'].replace(' ', '_').replace('-', '_') - safety_tags.add(f"EStop_{zone_name}_OK") - - # --- CHECKED tags ------------------------------------------------------- - for tagname, group in si_epc_df.groupby('TAGNAME'): - base_name = '_'.join(group.iloc[0]['DESCA'].split('_')[:2]) - if 'ESTOP' in group.iloc[0]['DESCA']: - standard_tags.add(f"{base_name}_ESTOP1_CHECKED") - else: - epc_bases = {'EPC1' if int(r['TERM'][2:]) in (0,1) else 'EPC2' for _, r in group.iterrows() if int(r['TERM'][2:]) in (0,1,2,3)} - for epc_base in epc_bases: - standard_tags.add(f"{base_name}_{epc_base}_CHECKED") - - # --- Module tags ------------------------------------------------------- - # Static module tags - module_tags.add(('Rack', 'UDT_AOI_RACK')) - - # MCM tag - Main Control Module - module_tags.add(('MCM01', 'UDT_AOI_MCM')) - - # APF module tags - apf_modules = apf_df['TAGNAME'].dropna().unique() - for module in apf_modules: - module_tags.add((module, 'UDT_AOI_APF')) - - # DPM module tags - dpm_modules = dpm_df['TAGNAME'].dropna().unique() - for module in dpm_modules: - module_tags.add((module, 'UDT_AOI_DPM')) - - # FIOM module tags (uses 'Name' column) - fiom_modules = fiom_df['Name'].dropna().unique() - for module in fiom_modules: - module_tags.add((module, 'UDT_AOI_IO_BLOCK')) - - # FIOH module tags (uses 'DESCA' column from FIOH data) - fioh_modules = fioh_df['DESCA'].dropna().unique() - for module in fioh_modules: - module_tags.add((module, 'UDT_AOI_IO_BLOCK')) - - # PMM module tags (uses 'Name' column from PMM data) - pmm_modules = pmm_df['Name'].dropna().unique() - for module in pmm_modules: - module_tags.add((module, 'UDT_AOI_PMM')) - - # CB_MONITOR module tags (extract PDP numbers from CB_MONITOR data) - pdps = set() - for _, row in cb_monitor_df.iterrows(): - tagname = str(row['TAGNAME']) - if 'PDP' in tagname: - match = re.search(r'PDP(\d+)', tagname) - if match: - pdp_num = match.group(1) - pdps.add(f'PDP{pdp_num}_CB_MONITOR') - - for module in sorted(pdps): - module_tags.add((module, 'UDT_AOI_CB_MONITOR')) - - # EXTENDO module tags (uses 'Name' column from EXTENDO data) - extendo_modules = extendo_df['Name'].dropna().unique() - for module in extendo_modules: - module_tags.add((module, 'UDT_AOI_EXTENDO')) - - # ENCODER module tags (extract encoder names from ENCODER data) - encoder_modules = set() - for _, row in encoder_df.iterrows(): - desca = str(row['DESCA']) - if 'ENC' in desca: - match = re.search(r'(UL\d+_\d+)_ENC', desca) - if match: - encoder_tag = match.group(1) + '_ENCODER' - encoder_modules.add(encoder_tag) - - for module in sorted(encoder_modules): - module_tags.add((module, 'UDT_AOI_ENCODER')) - - # STATION_SS_PB module tags (extract station names from SS data - include all stations) - ss_stations = set() - for _, row in ss_df.iterrows(): - desca = str(row['DESCA']) - - # Extract base station name from DESCA (e.g., UL10_1_SS1_SPB -> UL10_1_SS1) - base_match = re.search(r'(UL\d+_\d+_SS\d+)', desca) - if base_match: - base_station = base_match.group(1) - # Add _STATION suffix - station_tag = f"{base_station}_STATION" - ss_stations.add(station_tag) - - for module in sorted(ss_stations): - module_tags.add((module, 'UDT_AOI_STATION_SS_PB')) - - # STATION_S_PB module tags (extract pushbutton station names from S_PB data) - s_pb_stations = set() - for _, row in s_pb_df.iterrows(): - desca = str(row['DESCA']) - - # Extract base pushbutton name from DESCA (e.g., PS1_1_S1_PB_LT -> PS1_1_S1_PB) - base_pb = desca.replace('_LT', '') if '_LT' in desca else desca - # Add _STATION suffix - station_tag = f"{base_pb}_STATION" - s_pb_stations.add(station_tag) - - for module in sorted(s_pb_stations): - module_tags.add((module, 'UDT_AOI_STATION_S_PB')) - - # STATION_JR_PB module tags (extract jam reset pushbutton station names from JR data) - base_jr_stations = {} - for _, row in jr_df.iterrows(): - desca = str(row['DESCA']) - full_jr = desca.replace('_LT', '') if '_LT' in desca else desca - base_match = re.search(r'^(.+)_(JR\d+)_PB$', full_jr) - if base_match: - base_station = base_match.group(1) - jr_num = base_match.group(2) - if base_station not in base_jr_stations: - base_jr_stations[base_station] = set() - base_jr_stations[base_station].add(jr_num) - - # Generate UDT tags only for JR stations that actually exist in the data - for base_station, jr_nums in sorted(base_jr_stations.items()): - for jr_num in sorted(jr_nums): - module_tags.add((f"{base_station}_{jr_num}_PB", 'UDT_AOI_STATION_JR_PB')) - - # JPE module tags (extract tracking photoeye names from JPE data) - jpe_stations = set() - for _, row in jpe_df.iterrows(): - desca = str(row['DESCA']) - # Extract TPE name (e.g., PS1_2_TPE1) - if 'TPE' in desca: - jpe_stations.add(desca) - - for module in sorted(jpe_stations): - module_tags.add((module, 'UDT_AOI_JPE')) - - # STATION_EPC module tags (extract E-stop pullcord station names from EPC_STATION data) - epc_stations = set() - for _, row in epc_station_df.iterrows(): - desca = str(row['DESCA']) - # Extract base EPC name (e.g., PS1_1_EPC1 from PS1_1_EPC1_2) - base_match = re.search(r'^(.+_EPC\d+)', desca) - if base_match: - base_epc = base_match.group(1) - epc_station_tag = f"{base_epc}_STATION" - epc_stations.add(epc_station_tag) - - for module in sorted(epc_stations): - module_tags.add((module, 'UDT_AOI_STATION_EPC')) - - # Collect beacon SO tags for alias mapping - for _, row in epc_bcn_df.iterrows(): - desca = str(row['DESCA']) - io_path = str(row['IO_PATH']) - if 'BCN' in desca and ':SO.' in io_path: - beacon_so_tags.add((desca, io_path)) - - for _, row in desc_ip_df.iterrows(): - desca = str(row['DESCA']) - io_path = str(row['IO_PATH']) - if 'BCN' in desca and ':SO.' in io_path: - beacon_so_tags.add((desca, io_path)) - - original_lines = Path(original_csv_file).read_text('utf-8').splitlines(keepends=True) - - # Find the header line (TYPE,SCOPE,NAME,DESCRIPTION,DATATYPE,SPECIFIER,ATTRIBUTES) - header_line_idx = None - for i, line in enumerate(original_lines): - if line.strip().startswith('TYPE,SCOPE,NAME,DESCRIPTION,DATATYPE,SPECIFIER,ATTRIBUTES'): - header_line_idx = i - break - - if header_line_idx is None: - raise ValueError("Could not find header line in original CSV") - - # Find the first TAG entry after the header to insert AOI tags after it - first_tag_line_idx = None - for i in range(header_line_idx + 1, len(original_lines)): - line = original_lines[i].strip() - if line.startswith('TAG,,') and not line.startswith('TAG,,MCM01,'): # Skip MCM01 tag if present - first_tag_line_idx = i - break - - if first_tag_line_idx is None: - # If no existing TAG found, insert after header - first_tag_line_idx = header_line_idx - - # Find where to stop including original content (exclude previously generated tags) - # Also find where RCOMMENT section starts to place generated tags before it - last_original_line = len(original_lines) - rcomment_start_line = None - - # Look for the start of RCOMMENT section or previously generated tags - for i in range(len(original_lines)-1, -1, -1): - line = original_lines[i].strip() - - # If we find previously generated tags, mark this as the cutoff - if any(k in line for k in ('EStop_MCM01_OK','EStop_ZONE_','_ESTOP_STATUS','_DCS_CTRL','RST_')): - last_original_line = i - continue - - # Look for the start of RCOMMENT section (first TYPE,SCOPE,ROUTINE,COMMENT header) - if line.startswith('TYPE,SCOPE,ROUTINE,COMMENT,OWNING_ELEMENT,LOCATION') and rcomment_start_line is None: - # Find the actual start by looking backward to find the last TAG/COMMENT before this header - for j in range(i-1, -1, -1): - prev_line = original_lines[j].strip() - if prev_line.startswith(('TAG,,', 'COMMENT,,')): - rcomment_start_line = j + 1 # Insert after the last TAG/COMMENT - break - break - - # If no RCOMMENT section found, use the end of file - if rcomment_start_line is not None and rcomment_start_line < last_original_line: - last_original_line = rcomment_start_line - - # Write the new CSV file - new_csv_file = Path(new_csv_file) - with open(str(new_csv_file), 'w', encoding='utf-8') as f: - # Write lines up to and including the first TAG line - f.writelines(original_lines[:first_tag_line_idx + 1]) - - # Insert AOI module tags immediately after the first TAG line (sorted by module name) - for tag_name, udt_type in sorted(module_tags, key=lambda x: _natural_sort_key(x[0])): - f.write(f'TAG,,{tag_name},"","{udt_type}","","(Class := Standard, Constant := false, ExternalAccess := Read/Write)"\n') - - # Add beacon SO alias tags to CSV - for beacon_name, so_path in sorted(beacon_so_tags): - f.write(f'ALIAS,,{beacon_name},{so_path},"","","(Class := Standard, Constant := false, ExternalAccess := Read/Write)"\n') - - # Continue with the rest of the original content (after first TAG line, up to last_original_line) - f.writelines(original_lines[first_tag_line_idx + 1:last_original_line]) - - # Append other generated tags at the end (stable alphabetical order) - for tag in sorted(standard_tags): - f.write(f'TAG,,{tag},"","BOOL","","(Class := Standard, RADIX := Decimal, Constant := false, ExternalAccess := Read/Write)"\n') - for tag in sorted(safety_tags): - f.write(f'TAG,,{tag},"","BOOL","","(Class := Safety, RADIX := Decimal, Constant := false, ExternalAccess := Read/Write)"\n') - for tag in sorted(dcs_tags): - f.write(f'TAG,,{tag},"","DCI_STOP","","(Class := Safety, Constant := false, ExternalAccess := Read/Write)"\n') - - print(f"Created new CSV file: {new_csv_file}") - print(f"Added {len(standard_tags)} Standard BOOL tags, {len(safety_tags)} Safety BOOL tags, {len(dcs_tags)} DCS tags, and {len(module_tags)} Module UDT tags") - return beacon_so_tags \ No newline at end of file diff --git a/Routines Generator/src/writers/xml_tag_writer.py b/Routines Generator/src/writers/xml_tag_writer.py index 524b42b..d6145f3 100644 --- a/Routines Generator/src/writers/xml_tag_writer.py +++ b/Routines Generator/src/writers/xml_tag_writer.py @@ -46,6 +46,34 @@ def _create_bool_tag(name: str, tag_class: str = "Standard") -> ET.Element: return tag +def _create_dint_tag(name: str, value: int, tag_class: str = "Standard") -> ET.Element: + """Create a DINT tag XML element with an initial value.""" + tag = ET.Element("Tag") + tag.set("Name", name) + tag.set("Class", tag_class) + tag.set("TagType", "Base") + tag.set("DataType", "DINT") + tag.set("Radix", "Decimal") + tag.set("Constant", "false") + tag.set("ExternalAccess", "Read/Write") + tag.set("OpcUaAccess", "None") + + # L5K Data format + data_l5k = ET.SubElement(tag, "Data") + data_l5k.set("Format", "L5K") + data_l5k.text = f"\n{int(value)}\n" + + # Decorated Data format + data_decorated = ET.SubElement(tag, "Data") + data_decorated.set("Format", "Decorated") + data_value = ET.SubElement(data_decorated, "DataValue") + data_value.set("DataType", "DINT") + data_value.set("Radix", "Decimal") + data_value.set("Value", str(int(value))) + + return tag + + def _create_dci_stop_tag(name: str) -> ET.Element: """Create a DCI_STOP tag XML element.""" tag = ET.Element("Tag") @@ -116,6 +144,38 @@ def _create_module_tag(name: str, udt_type: str) -> ET.Element: return tag +def _create_rack_tag_from_boilerplate(name: str) -> ET.Element: + """Create a Rack tag from RACK_Boilerplate.xml with the provided name. + + The boilerplate contains a tag named Rack; we replace its Name attribute. + """ + from pathlib import Path + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "RACK_Boilerplate.xml" + try: + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + # Replace the first Name="Rack" occurrence + import re + content = re.sub(r'Name="Rack"', f'Name="{name}"', content, count=1) + return ET.fromstring(content) + except Exception: + # Fallback to simple module tag if boilerplate missing + return _create_module_tag(name, 'UDT_AOI_RACK') + + +def _create_mcm_tag_from_boilerplate(name: str) -> ET.Element: + """Create an MCM tag from MCM_Boilerplate.xml with the provided name.""" + from pathlib import Path + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "MCM_Boilerplate.xml" + try: + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + import re + content = re.sub(r'Name="MCM"', f'Name="{name}"', content, count=1) + return ET.fromstring(content) + except Exception: + return _create_module_tag(name, 'UDT_AOI_MCM') + def _create_dpm_tag_from_boilerplate(name: str) -> ET.Element: """Create a DPM tag using the boilerplate XML template.""" from pathlib import Path @@ -172,6 +232,301 @@ def _create_fiom_tag_from_boilerplate(name: str) -> ET.Element: return _create_module_tag(name, 'UDT_AOI_IO_BLOCK') +def _create_apf_tag_from_boilerplate(name: str) -> ET.Element: + """Create an APF tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the APF boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "APF_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_APF') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace the template name with the actual module name + # Find the first instance of Name= and replace it + import re + content = re.sub(r'Name="[^"]*"', f'Name="{name}"', content, count=1) + + # Parse the XML and return the element + return ET.fromstring(content) + + except Exception as e: + print(f"Warning: Could not load APF boilerplate for {name}: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_APF') + + +def _create_extendo_tag_from_boilerplate(name: str) -> ET.Element: + """Create an EXTENDO tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the EXTENDO boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "EXTENDO_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_EXTENDO') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace the template name with the actual module name + # Find the first instance of Name= and replace it + import re + content = re.sub(r'Name="[^"]*"', f'Name="{name}"', content, count=1) + + # Parse the XML and return the element + return ET.fromstring(content) + + except Exception as e: + print(f"Warning: Could not load EXTENDO boilerplate for {name}: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_EXTENDO') + + +def _create_d2c_tag_from_boilerplate(name: str) -> ET.Element: + """Create a D2C_CHUTE tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the D2C boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "D2C_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_D2C_CHUTE') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace the template name with the actual module name + # Find the first instance of Name= and replace it + import re + content = re.sub(r'Name="[^"]*"', f'Name="{name}"', content, count=1) + + # Parse the XML and return the element + return ET.fromstring(content) + + except Exception as e: + print(f"Warning: Could not load D2C boilerplate for {name}: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_D2C_CHUTE') + + +def _create_pb_chute_tag_from_boilerplate(name: str) -> ET.Element: + """Create a PB_CHUTE tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the PB_CHUTE boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "PB_CHUTE_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_PB_CHUTE') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + content = content.replace('S011004_PB_Chute', name) + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load PB_CHUTE boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_PB_CHUTE') + + +def _create_station_jr_chute_tag_from_boilerplate(name: str) -> ET.Element: + """Create a STATION_JR_CHUTE tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the STATION_JR_CHUTE boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "STATION_JR_CHUTE_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_STATION_JR_CHUTE') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + content = content.replace('S011003_JR', name) + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load STATION_JR_CHUTE boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_STATION_JR_CHUTE') + + +def _create_station_jr_pb_tag_from_boilerplate(name: str) -> ET.Element: + """Create a STATION_JR_PB tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the STATION_JR_PB boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "STATION_JR_PB_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_STATION_JR_PB') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + content = content.replace('FL1014_2_JR2_PB', name) + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load STATION_JR_PB boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_STATION_JR_PB') + + +def _create_jpe_tag_from_boilerplate(name: str) -> ET.Element: + """Create a JPE tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the JPE boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "JPE_Boilerplate.xml" + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_JPE') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + content = content.replace('FL1014_2_PE1', name) + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load JPE boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_JPE') + + +def _create_fpe_tag_from_boilerplate(name: str) -> ET.Element: + """Create a FPE tag using the boilerplate XML template.""" + from pathlib import Path + + # Load the FPE boilerplate template + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "FPE_Boilerplate.xml" + print(f" [DEBUG] Looking for FPE boilerplate at: {boilerplate_path}") + + if not boilerplate_path.exists(): + # Fallback to simple module tag if boilerplate not found + return _create_module_tag(name, 'UDT_AOI_FPE') + + try: + # Read and parse the boilerplate XML + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + content = content.replace('FL1018_3CH_PE1', name) + + # Update the string length if needed + old_len = 14 # Length of 'FL1018_3CH_PE1' + new_len = len(name) + if old_len != new_len: + content = content.replace(f"[{old_len},'", f"[{new_len},'") + content = content.replace(f'Value="{old_len}"', f'Value="{new_len}"') + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load FPE boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_FPE') + + +def _create_pmm_tag_from_boilerplate(name: str) -> ET.Element: + """Create a PMM tag from boilerplate XML.""" + try: + # Load boilerplate + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "PMM_Boilerplate.xml" + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + content = content.replace('PDP1_PMM1', name) + + # Update the string length if needed (PDP1_PMM1 is 9 characters) + old_len = 9 + new_len = len(name) + if old_len != new_len: + # This might need adjustment based on actual boilerplate content + pass + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load PMM boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_PMM') + + +def _create_cb_monitor_tag_from_boilerplate(name: str) -> ET.Element: + """Create a CB_MONITOR tag from boilerplate XML.""" + try: + # Load boilerplate + boilerplate_path = Path(__file__).parent.parent.parent / "UDTs_Tags" / "CB_MONITOR_Boilerplate.xml" + with open(boilerplate_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace placeholder name with actual name + # The boilerplate uses PDP1_CB_MONITOR, we receive PDP1 and need to create PDP1_CB_MONITOR + full_name = f"{name}_CB_MONITOR" + content = content.replace('PDP1_CB_MONITOR', full_name) + content = content.replace('PDP1', name) # Replace any standalone PDP1 references + + # Parse the modified XML + root = ET.fromstring(content) + return root + + except Exception as e: + print(f"[WARNING] Failed to load CB_MONITOR boilerplate: {e}") + # Fallback to simple module tag + return _create_module_tag(name, 'UDT_AOI_CB_MONITOR') + + def _format_tag_xml(tag_element: ET.Element) -> str: """Format a tag XML element with proper CDATA sections.""" xml_str = ET.tostring(tag_element, encoding='unicode') @@ -193,7 +548,10 @@ def _format_tag_xml(tag_element: ET.Element) -> str: return xml_str -def create_limited_tag_xml_elements(excel_file: str | Path, data_loader=None) -> List[ET.Element]: +def create_limited_tag_xml_elements( + excel_file: str | Path, + data_loader=None, +) -> List[ET.Element]: """Create tag XML elements for safety-only mode. Parameters: @@ -221,7 +579,7 @@ def create_limited_tag_xml_elements(excel_file: str | Path, data_loader=None) -> rst_df = loader.rst sto_df = loader.sto epc_df = loader.epc - zones_df = loader.zones + zones_df = pd.DataFrame() dpm_df = loader.dpm fiom_df = loader.fiom fioh_df = loader.fioh @@ -232,19 +590,30 @@ def create_limited_tag_xml_elements(excel_file: str | Path, data_loader=None) -> import re excel_path_str = str(excel_file) subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) - subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM01" + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" - # Static MCM tags with subsystem-specific naming + # Static MCM tags with config-driven naming (no numeric suffix) + from ..config import get_config as _get_cfg + _cfg = _get_cfg() + mcm_tag = getattr(_cfg.routines, 'mcm_safety_tag', 'MCM_S_PB') + safety_prefix = getattr(_cfg.routines, 'safety_tag_prefix', 'SFT_') + tags.append(_create_bool_tag("MCM_EPB_STATUS", "Safety")) - tags.append(_create_bool_tag(f"SFT_{subsystem}_S_PB", "Safety")) + tags.append(_create_bool_tag(f"{safety_prefix}{mcm_tag}", "Safety")) tags.append(_create_bool_tag(f"EStop_{subsystem}_OK", "Safety")) - tags.append(_create_bool_tag(f"{subsystem}_S_PB", "Standard")) + tags.append(_create_bool_tag(mcm_tag, "Standard")) tags.append(_create_dci_stop_tag("MCM_EPB_DCS_CTRL")) + + # Speed control setpoint tag used by R051_SPEED_CTRL routine + tags.append(_create_dint_tag("Speed_350_FPM", 350, "Standard")) + + # Global inhibit for horn used by APF routine + tags.append(_create_bool_tag("NO_Horn", "Standard")) - # Module tags - # Skip these for now as they reference undefined UDTs - # tags.append(_create_module_tag("Rack", "UDT_AOI_RACK")) - # tags.append(_create_module_tag("MCM01", "UDT_AOI_MCM")) + # Module tags (config-driven toggles) + from ..config import get_config + cfg = get_config() + program_toggles = cfg.tags.get('MainProgram', {}) if isinstance(cfg.tags, dict) else {} safety_tags: set[str] = set() standard_tags: set[str] = set() @@ -310,11 +679,17 @@ def create_limited_tag_xml_elements(excel_file: str | Path, data_loader=None) -> dcs_tags.add(f"{base_name}_{epc_base}_DCS_CTRL") # --- Zone OK tags ------------------------------------------------------- - for _, zone in zones_df.iterrows(): - if pd.isna(zone['START']) or pd.isna(zone['END']) or zone['START'] == "" or zone['END'] == "": - continue - zone_name = zone['NAME'].replace(' ', '_').replace('-', '_') - safety_tags.add(f"EStop_{zone_name}_OK") + # Create Safety BOOLs for each configured zone (excluding root like MCM01) + try: + if zones_df is not None and not zones_df.empty: + for _, zr in zones_df.iterrows(): + name = str(zr.get('name', '')).strip() + if not name or name.upper().startswith('MCM'): + continue + norm = name.replace('-', '_') + safety_tags.add(f"EStop_{norm}_OK") + except Exception: + pass # --- CHECKED tags ------------------------------------------------------- for tagname, group in si_epc_df.groupby('TAGNAME'): @@ -338,42 +713,115 @@ def create_limited_tag_xml_elements(excel_file: str | Path, data_loader=None) -> # --- Module UDT tags ---------------------------------------------------- module_tags = [] - + # Always use config toggles to decide inclusion # Add DPM module tags using boilerplate template - dpm_modules = dpm_df['TAGNAME'].dropna().unique() - for module in sorted(dpm_modules): - module_tags.append(_create_dpm_tag_from_boilerplate(module)) - + if program_toggles.get('DPM', True): + dpm_modules = dpm_df['TAGNAME'].dropna().unique() + for module in sorted(dpm_modules): + module_tags.append(_create_dpm_tag_from_boilerplate(module)) + # Add FIOM module tags using boilerplate template - if 'Name' in fiom_df.columns: - fiom_modules = fiom_df['Name'].dropna().unique() - else: - fiom_modules = fiom_df['TAGNAME'].dropna().unique() - for module in sorted(fiom_modules): - module_tags.append(_create_fiom_tag_from_boilerplate(module)) - + if program_toggles.get('FIOM', True): + if 'Name' in fiom_df.columns: + fiom_modules = fiom_df['Name'].dropna().unique() + else: + fiom_modules = fiom_df['TAGNAME'].dropna().unique() + for module in sorted(fiom_modules): + module_tags.append(_create_fiom_tag_from_boilerplate(module)) + # Add FIOH module tags using same boilerplate template as FIOM (both use UDT_AOI_IO_BLOCK) # FIOH tag names come from DESCA column (not Name/TAGNAME) - if 'DESCA' in fioh_df.columns: - fioh_modules = fioh_df['DESCA'].dropna().unique() - elif 'Name' in fioh_df.columns: - fioh_modules = fioh_df['Name'].dropna().unique() - else: - fioh_modules = fioh_df['TAGNAME'].dropna().unique() - for module in sorted(fioh_modules): - module_tags.append(_create_fiom_tag_from_boilerplate(module)) # Use same boilerplate as FIOM - - # Add basic system module tags - module_tags.append(_create_module_tag("Rack", "UDT_AOI_RACK")) - module_tags.append(_create_module_tag("MCM01", "UDT_AOI_MCM")) - - # Add module tags to the main tags list + if program_toggles.get('FIOH', True): + if 'DESCA' in fioh_df.columns: + fioh_modules = fioh_df['DESCA'].dropna().unique() + elif 'Name' in fioh_df.columns: + fioh_modules = fioh_df['Name'].dropna().unique() + else: + fioh_modules = fioh_df['TAGNAME'].dropna().unique() + for module in sorted(fioh_modules): + module_tags.append(_create_fiom_tag_from_boilerplate(module)) # Use same boilerplate as FIOM + + # Load APF data and add APF module tags + if program_toggles.get('APF', True): + apf_df = data_loader.apf if data_loader else loader.apf + if 'Name' in apf_df.columns: + apf_modules = apf_df['Name'].dropna().unique() + else: + apf_modules = apf_df['TAGNAME'].dropna().unique() + for module in sorted(apf_modules): + module_tags.append(_create_apf_tag_from_boilerplate(module)) + + # Load EXTENDO data and add EXTENDO module tags + if program_toggles.get('EXTENDO', True): + extendo_df = data_loader.extendo if data_loader else loader.extendo + if 'Name' in extendo_df.columns: + extendo_modules = extendo_df['Name'].dropna().unique() + else: + extendo_modules = extendo_df['TAGNAME'].dropna().unique() + for module in sorted(extendo_modules): + module_tags.append(_create_extendo_tag_from_boilerplate(module)) + + # Load D2C data and add D2C_CHUTE module tags + if program_toggles.get('D2C', True): + d2c_data = data_loader.d2c_data if data_loader else loader.d2c_data + for s0_number in sorted(d2c_data.keys()): + d2c_tag_name = f"{s0_number}_D2C_CHUTE" + module_tags.append(_create_d2c_tag_from_boilerplate(d2c_tag_name)) + + # Load PB_CHUTE data and add PB_CHUTE module tags + if program_toggles.get('PB_CHUTE', True): + pb_chute_data = data_loader.pb_chute_data if data_loader else loader.pb_chute_data + for s0_number in sorted(pb_chute_data.keys()): + pb_chute_tag_name = f"{s0_number}_PB_Chute" + module_tags.append(_create_pb_chute_tag_from_boilerplate(pb_chute_tag_name)) + + # Load STATION_JR_CHUTE data and add STATION_JR_CHUTE module tags + if program_toggles.get('STATION_JR_CHUTE', True): + station_jr_chute_data = data_loader.station_jr_chute_data if data_loader else loader.station_jr_chute_data + for s0_number in sorted(station_jr_chute_data.keys()): + jr_tag_name = f"{s0_number}_JR" + module_tags.append(_create_station_jr_chute_tag_from_boilerplate(jr_tag_name)) + + # Load STATION_JR_PB data and add STATION_JR_PB module tags + if program_toggles.get('STATION_JR_PB', True): + station_jr_pb_data = data_loader.station_jr_pb_data if data_loader else loader.station_jr_pb_data + for jr_name in sorted(station_jr_pb_data.keys()): + module_tags.append(_create_station_jr_pb_tag_from_boilerplate(jr_name)) + + # Load JPE data and add JPE module tags + if program_toggles.get('JPE', True): + jpe_data = data_loader.jpe_data if data_loader else loader.jpe_data + for jpe_name in sorted(jpe_data.keys()): + module_tags.append(_create_jpe_tag_from_boilerplate(jpe_name)) + + # Load FPE data and add FPE module tags + if program_toggles.get('FPE', True): + fpe_data = data_loader.fpe_data if data_loader else loader.fpe_data + for fpe_name in sorted(fpe_data.keys()): + module_tags.append(_create_fpe_tag_from_boilerplate(fpe_name)) + + # Load PMM data and add PMM module tags + if program_toggles.get('PMM', True): + pmm_data = data_loader.pmm_data if data_loader else loader.pmm_data + for pmm_name in sorted(pmm_data.keys()): + module_tags.append(_create_pmm_tag_from_boilerplate(pmm_name)) + + # Load CB_MONITOR data and add CB_MONITOR module tags + if program_toggles.get('CB_MONITOR', True): + cb_monitor_data = data_loader.cb_monitor_data if data_loader else loader.cb_monitor_data + for pdp_name in sorted(cb_monitor_data.keys(), key=_natural_sort_key): + module_tags.append(_create_cb_monitor_tag_from_boilerplate(pdp_name)) + # Add system module tags unconditionally + tags.append(_create_rack_tag_from_boilerplate("Rack")) + tags.append(_create_mcm_tag_from_boilerplate("MCM")) + + # Append discovered module tags tags.extend(module_tags) - + print(f"Created {len(tags)} tag XML elements for limited mode") - print(f" - {len(standard_tags) + 1} Standard BOOL tags") # +1 for MCM_S_PB - print(f" - {len(safety_tags) + 3} Safety BOOL tags") # +3 for static MCM tags - print(f" - {len(dcs_tags) + 1} DCS tags") # +1 for MCM_EPB_DCS_CTRL + print(f" - {len(standard_tags) + 1} Standard BOOL tags") + print(f" - {len(safety_tags) + 3} Safety BOOL tags") + print(f" - {len(dcs_tags) + 1} DCS tags") print(f" - {len(module_tags)} Module UDT tags") return tags @@ -421,15 +869,26 @@ def create_tag_xml_elements(excel_file: str | Path) -> Tuple[List[ET.Element], S import re excel_path_str = str(excel_file) subsystem_match = re.search(r"(MCM\d+)", excel_path_str, re.IGNORECASE) - subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM01" + subsystem = subsystem_match.group(1).upper() if subsystem_match else "MCM" - # Static MCM tags with subsystem-specific naming + # Static MCM tags with config-driven naming (no numeric suffix) + from ..config import get_config as _get_cfg + _cfg = _get_cfg() + mcm_tag = getattr(_cfg.routines, 'mcm_safety_tag', 'MCM_S_PB') + safety_prefix = getattr(_cfg.routines, 'safety_tag_prefix', 'SFT_') + tags.append(_create_bool_tag("MCM_EPB_STATUS", "Safety")) - tags.append(_create_bool_tag(f"SFT_{subsystem}_S_PB", "Safety")) + tags.append(_create_bool_tag(f"{safety_prefix}{mcm_tag}", "Safety")) tags.append(_create_bool_tag(f"EStop_{subsystem}_OK", "Safety")) - tags.append(_create_bool_tag(f"{subsystem}_S_PB", "Standard")) + tags.append(_create_bool_tag(mcm_tag, "Standard")) tags.append(_create_dci_stop_tag("MCM_EPB_DCS_CTRL")) + # Speed control setpoint tag used by R051_SPEED_CTRL routine + tags.append(_create_dint_tag("Speed_350_FPM", 350, "Standard")) + + # Global inhibit for horn used by APF routine + tags.append(_create_bool_tag("NO_Horn", "Standard")) + safety_tags: set[str] = set() standard_tags: set[str] = set() dcs_tags: set[str] = set() @@ -444,9 +903,20 @@ def create_tag_xml_elements(excel_file: str | Path) -> Tuple[List[ET.Element], S # (This would be the same logic as in csv_writer.py but collecting into sets) # Add module UDT tags - # Skip these for now as they reference undefined UDTs - # tags.append(_create_module_tag("Rack", "UDT_AOI_RACK")) - # tags.append(_create_module_tag("MCM01", "UDT_AOI_MCM")) + tags.append(_create_rack_tag_from_boilerplate("Rack")) + tags.append(_create_mcm_tag_from_boilerplate("MCM")) + + # Add PMM module tags from NETWORK sheet + network_df = loader.network + pmm_entries = network_df[network_df['PartNumber'] == '1420-V2-ENT'] if 'PartNumber' in network_df.columns else pd.DataFrame() + for _, pmm in pmm_entries.iterrows(): + pmm_name = pmm['Name'] + tags.append(_create_pmm_tag_from_boilerplate(pmm_name)) + + # Add CB_MONITOR tags + cb_monitor_data = loader.cb_monitor_data + for pdp_name in sorted(cb_monitor_data.keys(), key=_natural_sort_key): + tags.append(_create_cb_monitor_tag_from_boilerplate(pdp_name)) # Add all collected tags in sorted order for tag in sorted(standard_tags): diff --git a/__pycache__/zones_config.cpython-312.pyc b/__pycache__/zones_config.cpython-312.pyc index cacbca4..6a794ae 100644 Binary files a/__pycache__/zones_config.cpython-312.pyc and b/__pycache__/zones_config.cpython-312.pyc differ diff --git a/generator_config.json b/generator_config.json index 0550289..ec1f010 100644 --- a/generator_config.json +++ b/generator_config.json @@ -1,13 +1,217 @@ { "files": { "excel_file": "DESC_IP_MERGED.xlsx", - "original_csv": "MTN6_MCM01_Controller_Tags_Original.CSV", "output_dir": ".", "safety_l5x": "SafetyProgram_Generated.L5X", "main_l5x": "MainProgram_Generated.L5X", - "complete_csv": "MTN6_MCM01_Controller_Tags_Complete.CSV", "mapping_txt": "SafetyTagMapping.txt" }, + "filters": { + "global": {}, + "per_routine": {} + }, + "routines": [ + { + "name": "main_routine", + "plugin": "main_routine", + "enabled": true, + "program": "MainProgram", + "order": 10, + "params": {} + }, + { + "name": "safety_tag_map", + "plugin": "safety_tag_map", + "enabled": true, + "program": "MainProgram", + "order": 130, + "params": {} + }, + { + "name": "mcm", + "plugin": "mcm", + "enabled": true, + "program": "MainProgram", + "order": 22, + "params": {} + }, + { + "name": "rack", + "plugin": "rack", + "enabled": true, + "program": "MainProgram", + "order": 25, + "params": {} + }, + { + "name": "estop_check", + "plugin": "estop_check", + "enabled": true, + "program": "MainProgram", + "order": 120, + "params": {} + }, + { + "name": "dpm", + "plugin": "dpm", + "enabled": false, + "program": "MainProgram", + "order": 40, + "params": {} + }, + { + "name": "fiom", + "plugin": "fiom", + "enabled": false, + "program": "MainProgram", + "order": 50, + "params": {} + }, + { + "name": "fioh", + "plugin": "fioh", + "enabled": false, + "program": "MainProgram", + "order": 60, + "params": {} + }, + { + "name": "apf", + "plugin": "apf", + "enabled": true, + "program": "MainProgram", + "order": 70, + "params": {} + }, + { + "name": "extendo", + "plugin": "extendo", + "enabled": false, + "program": "MainProgram", + "order": 80, + "params": {} + }, + { + "name": "flow_ctrl", + "plugin": "flow_ctrl", + "enabled": false, + "program": "MainProgram", + "order": 90, + "params": {} + }, + { + "name": "speed_ctrl", + "plugin": "speed_ctrl", + "enabled": false, + "program": "MainProgram", + "order": 100, + "params": {} + }, + { + "name": "d2c_chute", + "plugin": "d2c_chute", + "enabled": false, + "program": "MainProgram", + "order": 110, + "params": {} + }, + { + "name": "pb_chute", + "plugin": "pb_chute", + "enabled": false, + "program": "MainProgram", + "order": 120, + "params": {} + }, + { + "name": "station_jr_chute", + "plugin": "station_jr_chute", + "enabled": false, + "program": "MainProgram", + "order": 130, + "params": {} + }, + { + "name": "station_jr_pb", + "plugin": "station_jr_pb", + "enabled": false, + "program": "MainProgram", + "order": 140, + "params": {} + }, + { + "name": "jpe", + "plugin": "jpe", + "enabled": true, + "program": "MainProgram", + "order": 150, + "params": {} + }, + { + "name": "fpe", + "plugin": "fpe", + "enabled": false, + "program": "MainProgram", + "order": 160, + "params": {} + }, + { + "name": "pmm", + "plugin": "pmm", + "enabled": false, + "program": "MainProgram", + "order": 170, + "params": {} + }, + { + "name": "cb_monitor", + "plugin": "cb_monitor", + "enabled": false, + "program": "MainProgram", + "order": 180, + "params": {} + }, + { + "name": "inputs", + "plugin": "inputs", + "enabled": true, + "program": "SafetyProgram", + "order": 10, + "params": {} + }, + { + "name": "outputs", + "plugin": "outputs", + "enabled": true, + "program": "SafetyProgram", + "order": 11, + "params": {} + }, + { + "name": "resets", + "plugin": "resets", + "enabled": true, + "program": "SafetyProgram", + "order": 12, + "params": {} + }, + { + "name": "zones", + "plugin": "zones", + "enabled": true, + "program": "SafetyProgram", + "order": 13, + "params": {} + }, + { + "name": "estops", + "plugin": "estops", + "enabled": true, + "program": "SafetyProgram", + "order": 20, + "params": {} + } + ], "xml": { "schema_revision": "1.0", "software_revision": "36.00", @@ -16,40 +220,50 @@ "export_options": "References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans" }, "extraction": { - "rst_desc_contains": [ - "START" - ], - "rst_desc_excludes": [ - "LIGHT" - ], - "rst_desca_patterns": [ - "S1_PB", - "S2_PB" - ], - "rst_desca_endings": [ - "SPB" - ], - "sto_tagname_patterns": [ - "VFD" - ], - "sto_desca_patterns": [ - "STO" - ], - "epc_desca_patterns": [ - "EPC", - "ESTOP" - ] - }, - "routines": { - "main_routine_name": "MainRoutine", - "safety_tag_map_name": "R000_SAFETY_TAG_MAP", - "estop_check_name": "R100_ESTOP_CHECK", - "inputs_routine": "R010_INPUTS", - "outputs_routine": "R011_OUTPUTS", - "resets_routine": "R012_RESETS", - "estops_routine": "R020_ESTOPS", - "zones_routine": "R030_ZONES", - "mcm_safety_tag": "MCM_S_PB", - "mcm_epb_tag": "MCM_EPB_DCS_CTRL.O1" + "rst_desc_contains": [ "START" ], + "rst_desc_excludes": [ "LIGHT" ], + "rst_desca_exclude_patterns": [ "GS1" ], + "rst_desca_patterns": [ "S1_PB", "S2_PB" ], + "rst_desca_endings": [ "SPB" ], + "dpm_partnumber_contains": [ "OS30-002404-2S" ], + "fiom_partnumber_contains": [ "5032-8IOLM12DR" ], + "fioh_partnumber_contains": [ "5032-8IOLM12DR" ], + "fioh_desca_contains": [ "FIOH" ], + "sto_tagname_patterns": [ "VFD" ], + "sto_desca_patterns": [ "STO" ], + "epc_desca_patterns": [ "EPC", "ESTOP" ], + "apf_partnumber_prefix": [ "35S" ], + "extendo_partnumber_exact": [ "CALJAN" ], + "pmm_partnumber_exact": [ "1420-V2-ENT" ], + "speed_ctrl_partnumber_prefix": [ "35S" ] + , + "cb_desca_include": [ "CB" ], + "cb_desca_exclude": [ "BCN" ], + "s0_prefix": "S0", + "d2c_gs1_pb_token": "GS1_PB", + "d2c_gs1_pb_lt_token": "GS1_PB_LT", + "d2c_bcn_token": "BCN", + "d2c_zmx_suffix": "_ZMX", + "pb_chute_components": [ "PE1", "PE2", "PR1", "SOL1" ], + "fioh_token": "FIOH", + "bcn_token": "BCN", + "beacon_stack_3_tokens": [ "3-STACK", "3 STACK" ], + "beacon_segment_a_pin4": "Connector_1_A_Pin_4", + "beacon_segment_b_pin2": "Connector_1_B_Pin_2", + "jr1_pb_token": "JR1_PB", + "jr1_pb_lt_token": "JR1_PB_LT", + "jr2_token": "JR2_PB", + "jr2_pb_lt_token": "JR2_PB_LT", + "jr1_exclude_tokens": [], + "vfd_prefix_regex": "", + "vfd_suffix_default": "_VFD1", + "jpe_include_tokens": [ "TPE" ], + "jpe_exclude_tokens": [ ], + "jpe_input_default": "", + "fpe_include_tokens": [ "FPE", "3CH_PE" ], + "flow_ctrl_vfd_name_regex": "^(?P[^_]+)_(?P\\d+)_VFD\\d+", + "flow_ctrl_extendo_name_regex": "^(?P[^_]+)_(?P\\d+)_EX\\d+", + "flow_ctrl_chain_order": "natural", + "flow_ctrl_enable_extendo_interlocks": true } } \ No newline at end of file diff --git a/workflow_state.md b/workflow_state.md index 3ee28fc..9e0db9a 100644 --- a/workflow_state.md +++ b/workflow_state.md @@ -1,3 +1,43 @@ +## State +- Phase: BLUEPRINT +- Status: NEEDS_PLAN_APPROVAL + +## Plan +Goal: Make JPE routine generation purely config-driven so entries with TPE in DESCA are treated as JPEs without code hardcoding. + +Edits: +1) Config schema (Routines Generator/src/config.py) + - Add ExtractionConfig.jpe_base_suffix_regex: regex used to derive base name from DESCA for JPE detection. + - Default: r'(_PE\\d*|_JPE|_TPE\\d*)' (covers _PE1, _JPE, _TPE1). Fully override-able via generator_config.json. + +2) Data extraction (Routines Generator/src/data_loader.py) + - In _extract_jpe_data(): + a) Replace hardcoded base regex r'(.*?)(_PE\\d*|_JPE)' with config-driven suffix from ExtractionConfig.jpe_base_suffix_regex. + b) Use config.extraction.jpe_input_default for the input signal fallback (instead of always In_2). + c) Use config.extraction.beacon_segment_a_pin4 for FIOH beacon fallback (instead of hardcoded Connector_1_A_Pin_4). + +3) No change to generator_config.json structure required. + - Your current jpe_include_tokens ["TPE"] remains valid; with (1)-(2), JPE data will be produced from TPE rows. + - Optionally add/adjust jpe_base_suffix_regex in generator_config.json if your TPE naming differs from the default. + +Pseudocode (Python): +- config.py (ExtractionConfig) + jpe_base_suffix_regex: str = r'(_PE\\d*|_JPE|_TPE\\d*)' + +- data_loader.py (_extract_jpe_data) + suffix = cfg.extraction.jpe_base_suffix_regex or r'(_PE\\d*|_JPE|_TPE\\d*)' + base_match = re.search(rf'(.*?){suffix}', desca) + input_fallback = cfg.extraction.jpe_input_default or 'In_2' + input_path = row['IO_PATH'] if pd.notna(row['IO_PATH']) else f"{vfd_name}:I.{input_fallback}" + pin = cfg.extraction.beacon_segment_a_pin4 or 'Connector_1_A_Pin_4' + beacon_output = f"{fioh_name}:O.ProcessDataOut.{pin}" + +Acceptance criteria: +- With jpe_include_tokens ["TPE"], generator produces R100_JPE and logs “Found JPE config …”. +- No hardcoded suffixes remain; behavior adjustable solely via generator_config.json. + +## Log +- Prepared config-driven plan for JPE; awaiting approval. # Workflow State _Last updated: 2025-07-30 - LINUX MIGRATION COMPLETE ✅_ @@ -829,4 +869,298 @@ Refactor the Routines Generator so that the current *limited* generation workflo - **Next Phase**: Application integration and cross-platform path updates --- -State.Status = CORE_IMPLEMENTATION_COMPLETE \ No newline at end of file +State.Status = CORE_IMPLEMENTATION_COMPLETE + +## Title +Config-driven Routines Generator Architecture + +## Goal +Refactor the Routines Generator to be fully configurable via a single, extensible JSON file that controls: +- Which routines run, their order, and per-routine parameters +- Global and per-routine include/exclude filters for data extraction +- Tag generation templates and options (UDTs, program placement, safety/standard) +- Output artifacts and file names +- Backwards-compatible defaults when sections are omitted + +## Constraints & Assumptions +- Must remain compatible with existing entry points in `Routines Generator` and `complete_workflow.py`. +- Keep current plugin architecture (`src/plugin_system.py`) and route execution through a registry; config chooses enabled routines and passes parameters. +- No breaking changes to current CLI by default; add an optional `--config` path override. +- Preserve existing `generator_config.json`, extend it to be the single “big JSON” config. +- JSON must be easy to extend without code changes (unknown keys ignored, sensible defaults used). + +## Deliverables +- Extended `generator_config.json` schema (documented below) and validation (lightweight, clear errors). +- Code changes to load and apply config in: `src/config.py`, `src/unified_cli.py` / `src/cli.py`, `src/container.py`, `src/plugin_system.py`, `src/data_loader.py`, writers (`xml_tag_writer.py`, `mapping_writer.py`). +- Backwards-compatible behavior when new sections are absent. + +## Big JSON Schema (proposed) +High-level keys. Omitted sections use defaults. + +```json +{ + "files": { + "excel_file": "DESC_IP_MERGED.xlsx", + "original_csv": "MTN6_MCM01_Controller_Tags_Original.CSV", + "output_dir": ".", + "safety_l5x": "SafetyProgram_Generated.L5X", + "main_l5x": "MainProgram_Generated.L5X", + "complete_csv": "MTN6_MCM01_Controller_Tags_Complete.CSV", + "mapping_txt": "SafetyTagMapping.txt" + }, + "xml": { + "schema_revision": "1.0", + "software_revision": "36.00", + "controller_name": "MTN6_MCM01", + "target_class": "Standard", + "export_options": "References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans" + }, + "extraction": { + "rst": { + "desc_contains": ["START"], + "desc_excludes": ["LIGHT"], + "desca_patterns": ["S1_PB", "S2_PB"], + "desca_endings": ["SPB"], + "desca_exclude_patterns": ["GS1"] + }, + "sto": { "tagname_patterns": ["VFD"], "desca_patterns": ["STO"] }, + "epc": { "desca_patterns": ["EPC", "ESTOP"] }, + "dpm": { "partnumber_contains": ["OS30-002404-2S"] }, + "fiom": { "partnumber_contains": ["5032-8IOLM12DR"] }, + "fioh": { + "partnumber_contains": ["5032-8IOLM12DR"], + "desca_contains": ["FIOH"] + }, + "network": { + "apf_partnumber_prefix": ["35S"], + "extendo_partnumber_exact": ["CALJAN"] + } + }, + "filters": { + "global": { + "desca_include": [], + "desca_exclude": [], + "tagname_include": [], + "tagname_exclude": [] + }, + "per_routine": { + "R030_ZONES": { "desca_exclude": ["ESTOP1OK"] }, + "R100_ESTOP_CHECK": { "desca_include": ["ESTOP", "EPC"] } + } + }, + "routines": [ + { + "name": "inputs", + "plugin": "inputs", + "enabled": true, + "program": "SafetyProgram", + "order": 10, + "params": { "ignore_estop1ok": false } + }, + { + "name": "outputs", + "plugin": "outputs", + "enabled": true, + "program": "SafetyProgram", + "order": 11, + "params": {} + }, + { + "name": "resets", + "plugin": "resets", + "enabled": true, + "program": "SafetyProgram", + "order": 12, + "params": {} + }, + { + "name": "estops", + "plugin": "estops", + "enabled": true, + "program": "SafetyProgram", + "order": 20, + "params": { "subsystem": "MCM01", "ignore_estop1ok": false } + }, + { + "name": "zones", + "plugin": "zones", + "enabled": true, + "program": "SafetyProgram", + "order": 30, + "params": { "ignore_estop1ok": false } + }, + { + "name": "estop_check", + "plugin": "estop_check", + "enabled": true, + "program": "MainProgram", + "order": 100, + "params": {} + }, + { + "name": "cb_monitor", + "plugin": "cb_monitor", + "enabled": false, + "program": "MainProgram", + "order": 200, + "params": {} + } + ], + "tags": { + "program_tags": { + "SafetyProgram": { + "udt_templates": { + "DPM": { "udt": "DPM_UDT", "source": "network", "include": true }, + "FIOM": { "udt": "FIOM_UDT", "source": "desc_ip", "include": true }, + "FIOH": { "udt": "FIOH_UDT", "source": "desc_ip", "include": true } + } + }, + "MainProgram": { + "udt_templates": { + "APF": { "udt": "APF_UDT", "source": "network", "include": true }, + "EXTENDO": { "udt": "EXTENDO_UDT", "source": "network", "include": true } + } + } + }, + "safety_tag_map": { + "enabled": true, + "output_file": "SafetyTagMapping.txt", + "include_beacon_mappings": true + } + }, + "output": { + "write_safety_l5x": true, + "write_main_l5x": true, + "write_csv": true, + "write_mapping": true + } +} +``` + +Notes: +- `routines[*].plugin` must match a registered plugin in `RoutineRegistry` (e.g., `inputs`, `outputs`, `estops`, `zones`, `estop_check`, etc.). +- `params` is passed straight to the plugin instance. +- `filters.per_routine[]` overrides globals for that routine. +- `tags.program_tags.*.udt_templates` directs `xml_tag_writer` which UDT tag families to materialize. + +## Implementation Plan +Phase 1 — Config plumbing and routine activation +- Extend `src/config.py`: + - Add dataclasses: `FiltersConfig`, `RoutineEntry`, `RoutinesConfig`, `TagTemplate`, `TagsConfig`. + - Update `GeneratorConfig.from_file` to parse new sections; unknown keys preserved via dict fields where needed. +- Update CLI (`src/cli.py` and/or `src/unified_cli.py`) and `complete_workflow.py` to accept `--config` (optional), default to `generator_config.json`. +- Update `src/plugin_system.py`: + - Enhance `RoutineContext` to include `config`, `filters`, and per-routine `params`. + - Let `RoutinePlugin` store `self.params: dict` from context. +- Update `src/unified_cli.py` / execution: + - Add config-driven pathway: if `config.routines` present, sort by `order`, filter `enabled`, then for each, resolve plugin name in registry and call with `params`. + +Phase 2 — Data extraction by config +- Inject `GeneratorConfig` into `DataLoader` via `GeneratorContainer`. +- Modify `_extract_rst/_sto/_epc/_dpm/_fiom/_fioh` in `src/data_loader.py` to source patterns from `config.extraction` (with defaults when absent). +- Apply global/per-routine filters: + - Provide utilities to build inclusion/exclusion masks from `filters.global` when materializing DataFrames; per-routine filters applied within plugins using context. + +Phase 3 — Tag generation by config +- Update `src/writers/xml_tag_writer.py` to accept `TagsConfig` and only create UDT tags as directed under `tags.program_tags` for each Program. +- Update `src/writers/mapping_writer.py` to honor `tags.safety_tag_map` options. + +Phase 4 — Plugins accept params and filters +- For safety routines (`inputs`, `outputs`, `resets`, `estops`, `zones`, `estop_check`), read `context.metadata["params"]` and `context.metadata["filters"]`. +- Respect `ignore_estop1ok`, `subsystem`, etc., without additional code switches elsewhere. + +Phase 5 — Validation and defaults +- Add lightweight validation (missing plugin, duplicate `order`, wrong program name, bad regex) with clear error messages and fallbacks. +- Ensure all current tests/flows run with an empty or minimal config (defaults replicate current behavior). + +## Pseudocode (selected integration points) + +- Loading config and wiring params to plugins: +```python +# unified_cli / main entry +config = GeneratorConfig.from_file(Path(args.config or 'generator_config.json')) +container = GeneratorContainer(config) +registry = get_default_registry() + +if config.routines: # config-driven path + routines = [r for r in config.routines if r.enabled] + routines.sort(key=lambda r: r.order) + for r in routines: + plugin_cls = registry.get_plugin(r.plugin) + ctx = RoutineContext( + data_loader=container.get_data_loader(), + config=config, + routines_element=builder.get_routines_section(), + program_element=builder.get_program_element(), + metadata={"params": r.params, "filters": config.filters.for_routine(r.name)} + ) + plugin = plugin_cls(ctx) + assert plugin.can_generate() + plugin.generate() +else: + # fallback to current default generation + manager.generate_all_available() +``` + +- DataLoader using extraction rules from config: +```python +rules = self.config.extraction.rst or defaults +mask_contains = desc_ip['DESC'].str.contains('|'.join(rules.desc_contains), na=False, case=False) +mask_excludes = desc_ip['DESC'].str.contains('|'.join(rules.desc_excludes), na=False, case=False) +# ... +``` + +- xml_tag_writer honoring tag templates: +```python +templates = config.tags.program_tags.get(program_name, {}).get('udt_templates', {}) +if templates.get('FIOM', {}).get('include', True): + # create FIOM tags +``` + +## Migration & Backwards Compatibility +- With no `routines` section, behavior remains as today (existing routine manager order). +- With `routines` present, only enabled routines execute in the specified order. +- If a section is missing (e.g., `tags`), current tag behavior is used. + +## Risks & Mitigations +- Misconfigured regex/patterns: add validation and examples; fail with actionable messages. +- Plugin name mismatch: list available plugins and point to `src/routines/*` names. +- Ordering conflicts: detect duplicates and sort deterministically; warn and continue. + +## Testing Strategy +- Unit tests for config parsing and defaults. +- Golden-file comparisons on representative Excel inputs for: default run, safety-only run, and config-driven limited runs. +- Smoke run through `complete_workflow.py` with `--safety-only` true and false. + +## Next Actions +- Implement Phase 1 and introduce the config schema with minimal changes. +- Wire extraction patterns where they’re already hinted in `ExtractionConfig`. +- Iterate on `tags` section once routines path is stable. + +## State +- Status: NEEDS_PLAN_APPROVAL +- Phase: BLUEPRINT + +## Plan +- Add a new routine plugin `src/routines/rack.py` that always generates a fixed MainProgram routine named `R011_RACK` with two rungs: + - Rung 0: comment and `NOP()`. + - Rung 1: `AOI_RACK(Rack.AOI,Rack.HMI,SLOT2_EN4TR,SLOT5_IB16,SLOT6_OB16E,SLOT7_IB16S);` +- Extend default routine name mapping in `src/config.py` (`RoutineConfig.name_map`) with `'rack': 'R011_RACK'`. +- Ensure the RACK routine is generated by default in MainProgram: + - Update `MainProgramGenerator.generate_routines` default list to include `'rack'` before `'main_routine'`. + - Update `src/routines/main_routine_plugin.py` default JSR list to include `nm.get('rack', 'R011_RACK')` (after `safety_tag_map`). +- Integrate the Rack tag via boilerplate: + - Add `_create_rack_tag_from_boilerplate(name: str)` in `src/writers/xml_tag_writer.py` that loads `UDTs_Tags/RACK_Boilerplate.xml` and replaces the Name with the provided value. + - In `create_limited_tag_xml_elements` and `create_tag_xml_elements`, add the Rack tag using the boilerplate helper instead of a plain UDT tag, and add it unconditionally. +- Keep behavior config-driven; no UL constraints hardcoded; routine is static and data-agnostic. +- Verify by running unified CLI to generate MainProgram and confirm: + - `R011_RACK` routine exists with expected AOI call. + - `Rack` controller tag is present from boilerplate. + - MainRoutine contains a JSR to `R011_RACK`. + +## Notes +- Routine is constant; no DataLoader dependencies. +- No try/except in the new plugin; failures should bubble up. + +## Log +- Prepared blueprint to add static RACK routine and boilerplate-backed tag. \ No newline at end of file diff --git a/zones copy.json b/zones copy.json new file mode 100644 index 0000000..8a9f447 --- /dev/null +++ b/zones copy.json @@ -0,0 +1,91 @@ +{ + "MCM02": [ + { + "name": "MCM02", + "start": "", + "stop": "", + "interlock": "" + }, + { + "name": "ZONE_5", + "start": "PS2_1", + "stop": "PS2_7", + "interlock": "MCM02" + }, + + { + "name": "ZONE_6", + "start": "UL4_2", + "stop": "UL4_8", + "interlock": "MCM02" + }, + { + "name": "ZONE_7", + "start": "UL5_2", + "stop": "UL5_9", + "interlock": "MCM02" + }, + { + "name": "ZONE_8", + "start": "UL6_1", + "stop": "UL6_8", + "interlock": "MCM02" + } + ], + "MCM01": [ + { + "name": "MCM01", + "start": "", + "stop": "", + "interlock": "" + }, + { + "name": "ZONE_1", + "start": "ULC1_3", + "stop": "ULC1_6", + "interlock": "MCM01" + }, + { + "name": "ZONE_2", + "start": "ULC1_6", + "stop": "ULC1_12", + "interlock": "MCM01" + }, + { + "name": "ZONE_3", + "start": "ULC2_3", + "stop": "ULC2_6", + "interlock": "MCM01" + }, + { + "name": "ZONE_4", + "start": "ULC2_6", + "stop": "ULC2_12", + "interlock": "MCM01" + }, + { + "name": "ZONE_5", + "start": "ULC3_3", + "stop": "ULC3_4", + "interlock": "MCM01" + }, + { + "name": "ZONE_6", + "start": "ULC3_4", + "stop": "ULC3_6", + "interlock": "MCM01" + }, + { + "name": "ZONE_7", + "start": "ULC4_3", + "stop": "ULC4_4", + "interlock": "MCM01" + }, + { + "name": "ZONE_8", + "start": "ULC4_4", + "stop": "ULC4_6", + "interlock": "MCM01" + } + ] +} diff --git a/zones.json b/zones.json new file mode 100644 index 0000000..2ae0d8a --- /dev/null +++ b/zones.json @@ -0,0 +1,334 @@ +{ + "MCM01": [ + { + "name": "MCM01", + "start": "", + "stop": "", + "interlock": "" + }, + { + "name": "01-01", + "start": "UL1_1", + "stop": "UL1_13", + "interlock": "MCM01" + }, + { + "name": "01-06", + "start": "UL4_1", + "stop": "UL4_13", + "interlock": "MCM01" + }, + { + "name": "01-11", + "start": "UL7_1", + "stop": "UL7_13", + "interlock": "MCM01" + }, + { + "name": "01-17", + "start": "UL11_1", + "stop": "UL11_13", + "interlock": "MCM01" + }, + { + "name": "01-02", + "start": "UL2_1", + "stop": "UL2_10", + "interlock": "01-01" + }, + { + "name": "01-03", + "start": "UL3_1", + "stop": "UL3_9", + "interlock": "01-01" + }, + { + "name": "01-04", + "start": "PS1_1", + "stop": "PS1_4", + "interlock": "01-01" + }, + { + "name": "01-07", + "start": "UL5_1", + "stop": "UL5_10", + "interlock": "01-06" + }, + { + "name": "01-08", + "start": "UL6_1", + "stop": "UL6_9", + "interlock": "01-06" + }, + { + "name": "01-09", + "start": "PS2_1", + "stop": "PS2_4", + "interlock": "01-06" + }, + { + "name": "01-12", + "start": "UL8_1", + "stop": "UL8_9", + "interlock": "01-11" + }, + { + "name": "01-13", + "start": "UL9_1", + "stop": "UL9_11", + "interlock": "01-11" + }, + { + "name": "01-14", + "start": "PS3_1", + "stop": "PS3_3", + "interlock": "01-11" + }, + { + "name": "01-16", + "start": "UL10_1", + "stop": "UL10_10", + "interlock": "01-17" + }, + { + "name": "01-18", + "start": "UL12_1", + "stop": "UL12_10", + "interlock": "01-17" + }, + { + "name": "01-19", + "start": "PS4_1", + "stop": "PS4_5", + "interlock": "01-17" + }, + { + "name": "01-05", + "start": "PS1_5", + "stop": "PS1_5", + "interlock": "MCM01" + }, + { + "name": "01-10", + "start": "PS2_5", + "stop": "PS2_6", + "interlock": "MCM01" + }, + { + "name": "01-15", + "start": "PS3_8", + "stop": "PS3_12", + "interlock": "MCM01" + }, + { + "name": "01-20", + "start": "PS4_11", + "stop": "PS4_14", + "interlock": "MCM01" + }, + { + "name": "01-21", + "start": "PS3_4", + "stop": "PS3_7", + "interlock": "MCM01" + } + ], + "MCM04": [ + { + "name": "MCM04", + "start": "", + "stop": "", + "interlock": "" + }, + { + "name": "04-01", + "start": "ULC7_1", + "stop": "ULC7_3", + "interlock": "04-02" + }, + { + "name": "04-02", + "start": "PS10_1", + "stop": "PS10_3", + "interlock": "MCM04" + }, + { + "name": "04-03", + "start": "PS10_5", + "stop": "PS10_5", + "interlock": "MCM04" + }, + { + "name": "04-04", + "start": "ULC5_1", + "stop": "ULC5_3", + "interlock": "04-02" + }, + { + "name": "04-05", + "start": "PS11_6", + "stop": "PS11_7", + "interlock": "MCM04" + }, + { + "name": "04-06", + "start": "PS11_8", + "stop": "PS11_9", + "interlock": "MCM04" + }, + { + "name": "04-07", + "start": "PS11_11", + "stop": "PS11_11", + "interlock": "04-09" + }, + { + "name": "04-08", + "start": "PRS3_5", + "stop": "PRS3_6", + "interlock": "MCM04" + }, + { + "name": "04-09", + "start": "PRS4_1", + "stop": "PRS4_2", + "interlock": "MCM04" + }, + { + "name": "FL1014", + "start": "FL1014_2_VFD1", + "stop": "FL1014_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL1018", + "start": "FL1018_2_VFD1", + "stop": "FL1018_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL1022", + "start": "FL1022_2_VFD1", + "stop": "FL1022_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL1026", + "start": "FL1026_2_VFD1", + "stop": "FL1026_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL1034", + "start": "FL1034_2_VFD1", + "stop": "FL1034_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL1038", + "start": "FL1038_2_VFD1", + "stop": "FL1038_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL3012", + "start": "FL3012_2_VFD1", + "stop": "FL3012_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL3016", + "start": "FL3016_2_VFD1", + "stop": "FL3016_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL3020", + "start": "FL3020_2_VFD1", + "stop": "FL3020_4_EX1", + "interlock": "MCM04" + }, + { + "name": "FL3024", + "start": "FL3024_2_VFD1", + "stop": "FL3024_4_EX1", + "interlock": "MCM04" + } + ], + "MCM05": [ + { + "name": "MCM05", + "start": "", + "stop": "", + "interlock": "" + }, + { + "name": "FL2074", + "start": "FL2074_2_VFD1", + "stop": "FL2074_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL2078", + "start": "FL2078_2_VFD1", + "stop": "FL2078_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL2086", + "start": "FL2086_2_VFD1", + "stop": "FL2086_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL2090", + "start": "FL2090_2_VFD1", + "stop": "FL2090_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL2094", + "start": "FL2094_2_VFD1", + "stop": "FL2094_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL4082", + "start": "FL4082_2_VFD1", + "stop": "FL4082_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL4078", + "start": "FL4078_2_VFD1", + "stop": "FL4078_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL4074", + "start": "FL4074_2_VFD1", + "stop": "FL4074_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL4070", + "start": "FL4070_2_VFD1", + "stop": "FL4070_4_EX1", + "interlock": "MCM05" + }, + { + "name": "FL4066", + "start": "FL4066_2_VFD1", + "stop": "FL4066_4_EX1", + "interlock": "MCM05" + } + ], + "DEFAULT": [ + { + "name": "MCM", + "start": "", + "stop": "", + "interlock": "" + } + ] +} diff --git a/zones_config.py b/zones_config.py deleted file mode 100644 index 998aa13..0000000 --- a/zones_config.py +++ /dev/null @@ -1,402 +0,0 @@ -#!/usr/bin/env python3 -""" -Zones Configuration for PLC Generation System -============================================== - -MCM01 zone configuration with start/stop values based on first equipment line only: - -Start and stop values use only the FIRST equipment line in each zone: - -MCM01: Master zone (no direct equipment) -01-01: UL1_1 TO UL1_13 (first line of: UL1_1 TO UL1_13, UL2_1 TO UL2_10, UL3_1 TO UL3_9, PS1_1 TO PS1_4) -01-02: UL2_1 TO UL2_10 -01-03: UL3_1 TO UL3_9 -01-04: PS1_1 TO PS1_4 -01-05: PS1_5 -01-06: UL4_1 TO UL4_13 (first line of: UL4_1 TO UL4_13, UL5_1 TO UL5_10, UL6_1 TO UL6_9, PS2_1 TO PS2_4) -01-07: UL5_1 TO UL5_10 -01-08: UL6_1 TO UL6_9 -01-09: PS2_1 TO PS2_4 -01-10: PS2_5 TO PS2_6 -01-11: UL7_1 TO UL7_13 (first line of: UL7_1 TO UL7_13, UL8_1 TO UL8_9, UL9_1 TO UL9_11, PS3_1 TO PS3_3) -01-12: UL8_1 TO UL8_9 -01-13: UL9_1 TO UL9_11 -01-14: PS3_1 TO PS3_3 -01-15: PS3_8 TO PS3_12 -01-16: UL10_1 TO UL10_10 -01-17: UL11_1 TO UL11_13 (first line of: UL11_1 TO UL11_13, UL10_1 TO UL10_10, UL12_1 TO UL12_10, PS4_1 TO PS4_5) -01-18: UL12_1 TO UL12_10 -01-19: PS4_1 TO PS4_5 -01-20: PS4_11 TO PS4_14 -01-21: PS3_4 TO PS3_7 - -MCM04 zone configuration with start/stop values based on first equipment line only: - -MCM04: Master zone (no direct equipment) -04-01: ULC7_1 TO ULC7_3 (first line of: ULC7_1 TO ULC7_3, ULC8_1 TO ULC8_3, PS10_1 TO PS10_3) -04-02: PS10_1 TO PS10_3 (first line of: PS10_1 TO PS10_3, PS11_1 TO PS11_2) -04-03: PS10_5 -04-04: ULC5_1 TO ULC5_3 (first line of: ULC5_1 TO ULC5_3, ULC6_1 TO ULC6_3, PS11_1 TO PS11_4) -04-05: PS11_6 TO PS11_7 -04-06: PS11_8 TO PS11_9 -04-07: PS11_11 -04-08: PRS3_5 TO PRS3_6 -04-09: PRS4_1 TO PRS4_2 (first line of: PRS4_1 TO PRS4_2, PS11_3) -FL1014: FL1014 Unit Operation (incl. FL1014_EX) -FL1018: FL1018 Unit Operation (incl. FL1018_EX) -FL1022: FL1022 Unit Operation (incl. FL1022_EX) -FL1026: FL1026 Unit Operation (incl. FL1026_EX) -FL1034: FL1034 Unit Operation (incl. FL1034_EX) -FL1038: FL1038 Unit Operation (incl. FL1038_EX) -FL3012: FL3012 Unit Operation (incl. FL3012_EX) -FL3016: FL3016 Unit Operation (incl. FL3016_EX) -FL3020: FL3020 Unit Operation (incl. FL3020_EX) -FL3024: FL3024 Unit Operation (incl. FL3024_EX) -""" - -# MCM01 zones configuration - Updated with comprehensive zone structure -MCM01_ZONES = [ - { - 'name': 'MCM01', - 'start': '', - 'stop': '', - 'interlock': '' - }, - # Main zones with first equipment line only - { - 'name': '01-01', - 'start': 'UL1_1', - 'stop': 'UL1_13', - 'interlock': 'MCM01' - }, - { - 'name': '01-06', - 'start': 'UL4_1', - 'stop': 'UL4_13', - 'interlock': 'MCM01' - }, - { - 'name': '01-11', - 'start': 'UL7_1', - 'stop': 'UL7_13', - 'interlock': 'MCM01' - }, - { - 'name': '01-17', - 'start': 'UL11_1', - 'stop': 'UL11_13', - 'interlock': 'MCM01' - }, - # Individual zones with their specific equipment ranges - { - 'name': '01-02', - 'start': 'UL2_1', - 'stop': 'UL2_10', - 'interlock': '01-01' - }, - { - 'name': '01-03', - 'start': 'UL3_1', - 'stop': 'UL3_9', - 'interlock': '01-01' - }, - { - 'name': '01-04', - 'start': 'PS1_1', - 'stop': 'PS1_4', - 'interlock': '01-01' - }, - { - 'name': '01-07', - 'start': 'UL5_1', - 'stop': 'UL5_10', - 'interlock': '01-06' - }, - { - 'name': '01-08', - 'start': 'UL6_1', - 'stop': 'UL6_9', - 'interlock': '01-06' - }, - { - 'name': '01-09', - 'start': 'PS2_1', - 'stop': 'PS2_4', - 'interlock': '01-06' - }, - { - 'name': '01-12', - 'start': 'UL8_1', - 'stop': 'UL8_9', - 'interlock': '01-11' - }, - { - 'name': '01-13', - 'start': 'UL9_1', - 'stop': 'UL9_11', - 'interlock': '01-11' - }, - { - 'name': '01-14', - 'start': 'PS3_1', - 'stop': 'PS3_3', - 'interlock': '01-11' - }, - { - 'name': '01-16', - 'start': 'UL10_1', - 'stop': 'UL10_10', - 'interlock': '01-17' - }, - { - 'name': '01-18', - 'start': 'UL12_1', - 'stop': 'UL12_10', - 'interlock': '01-17' - }, - { - 'name': '01-19', - 'start': 'PS4_1', - 'stop': 'PS4_5', - 'interlock': '01-17' - }, - # Independent zones with their own equipment - { - 'name': '01-05', - 'start': 'PS1_5', - 'stop': 'PS1_5', - 'interlock': 'MCM01' - }, - { - 'name': '01-10', - 'start': 'PS2_5', - 'stop': 'PS2_6', - 'interlock': 'MCM01' - }, - { - 'name': '01-15', - 'start': 'PS3_8', - 'stop': 'PS3_12', - 'interlock': 'MCM01' - }, - { - 'name': '01-20', - 'start': 'PS4_11', - 'stop': 'PS4_14', - 'interlock': 'MCM01' - }, - { - 'name': '01-21', - 'start': 'PS3_4', - 'stop': 'PS3_7', - 'interlock': 'MCM01' - } -] - -# MCM04 zones configuration - Updated with comprehensive E-Stop zone structure -MCM04_ZONES = [ - { - 'name': 'MCM04', - 'start': '', - 'stop': '', - 'interlock': '' - }, - # Main E-Stop zones with equipment ranges - { - 'name': '04-01', - 'start': 'ULC7_1', - 'stop': 'ULC7_3', - 'interlock': '04-02' - }, - { - 'name': '04-02', - 'start': 'PS10_1', - 'stop': 'PS10_3', - 'interlock': 'MCM04' - }, - { - 'name': '04-03', - 'start': 'PS10_5', - 'stop': 'PS10_5', - 'interlock': 'MCM04' - }, - { - 'name': '04-04', - 'start': 'ULC5_1', - 'stop': 'ULC5_3', - 'interlock': '04-02' - }, - { - 'name': '04-05', - 'start': 'PS11_6', - 'stop': 'PS11_7', - 'interlock': 'MCM04' - }, - { - 'name': '04-06', - 'start': 'PS11_8', - 'stop': 'PS11_9', - 'interlock': 'MCM04' - }, - { - 'name': '04-07', - 'start': 'PS11_11', - 'stop': 'PS11_11', - 'interlock': '04-09' - }, - { - 'name': '04-08', - 'start': 'PRS3_5', - 'stop': 'PRS3_6', - 'interlock': 'MCM04' - }, - { - 'name': '04-09', - 'start': 'PRS4_1', - 'stop': 'PRS4_2', - 'interlock': 'MCM04' - }, - # Safety Relay zones - Unit operations - { - 'name': 'FL1014', - 'start': 'FL1014_2_VFD1', - 'stop': 'FL1014_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL1018', - 'start': 'FL1018_2_VFD1', - 'stop': 'FL1018_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL1022', - 'start': 'FL1022_2_VFD1', - 'stop': 'FL1022_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL1026', - 'start': 'FL1026_2_VFD1', - 'stop': 'FL1026_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL1034', - 'start': 'FL1034_2_VFD1', - 'stop': 'FL1034_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL1038', - 'start': 'FL1038_2_VFD1', - 'stop': 'FL1038_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL3012', - 'start': 'FL3012_2_VFD1', - 'stop': 'FL3012_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL3016', - 'start': 'FL3016_2_VFD1', - 'stop': 'FL3016_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL3020', - 'start': 'FL3020_2_VFD1', - 'stop': 'FL3020_4_EX1', - 'interlock': 'MCM04' - }, - { - 'name': 'FL3024', - 'start': 'FL3024_2_VFD1', - 'stop': 'FL3024_4_EX1', - 'interlock': 'MCM04' - } -] - -MCM05_ZONES = [ - { - 'name': 'MCM05', - 'start': '', - 'stop': '', - 'interlock': '' - }, - # Safety Relay zones - Unit operations - { - 'name': 'FL2074', - 'start': 'FL2074_2_VFD1', - 'stop': 'FL2074_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL2078', - 'start': 'FL2078_2_VFD1', - 'stop': 'FL2078_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL2086', - 'start': 'FL2086_2_VFD1', - 'stop': 'FL2086_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL2090', - 'start': 'FL2090_2_VFD1', - 'stop': 'FL2090_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL2094', - 'start': 'FL2094_2_VFD1', - 'stop': 'FL2094_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL4082', - 'start': 'FL4082_2_VFD1', - 'stop': 'FL4082_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL4078', - 'start': 'FL4078_2_VFD1', - 'stop': 'FL4078_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL4074', - 'start': 'FL4074_2_VFD1', - 'stop': 'FL4074_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL4070', - 'start': 'FL4070_2_VFD1', - 'stop': 'FL4070_4_EX1', - 'interlock': 'MCM05' - }, - { - 'name': 'FL4066', - 'start': 'FL4066_2_VFD1', - 'stop': 'FL4066_4_EX1', - 'interlock': 'MCM05' - } -] -# Default zones configuration -DEFAULT_ZONES = MCM01_ZONES - -# Main zones configuration mapping -ZONES_CONFIGS = { - 'MCM01': MCM01_ZONES, - 'MCM04': MCM04_ZONES, - 'MCM05': MCM05_ZONES -} - -# Export for backward compatibility -__all__ = ['MCM01_ZONES', 'MCM04_ZONES', 'MCM05_ZONES', 'DEFAULT_ZONES', 'ZONES_CONFIGS'] \ No newline at end of file