""" 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, natural_sort_key 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(), key=lambda x: natural_sort_key(x[0])): # 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)