""" ZZZ_BeltTracking routine generator. Generates belt tracking AOI calls for every VFD in network: zzz_BeltTracking(zzz_UL1_3Tracking,UL1_3_VFD1:I,UL1_3_VFD1:O); """ import xml.etree.ElementTree as ET from typing import List from ..utils.common import format_xml_to_match_original, natural_sort_key from ..plugin_system import RoutinePlugin, register_plugin def generate_belt_tracking_routine(data_loader) -> ET.Element: """Generate the ZZZ_BeltTracking routine XML element.""" # Get all VFDs from network data network = data_loader.network # Config-driven routine name try: from ..config import get_config routine_name = get_config().routines.name_map.get('belt_tracking', 'ZZZ_BeltTracking') except Exception: routine_name = 'ZZZ_BeltTracking' # Create routine XML structure routine = ET.Element("Routine") routine.set("Name", routine_name) routine.set("Type", "RLL") rll_content = ET.SubElement(routine, "RLLContent") # Get VFDs from network sheet (filter by Name ending with _VFD1) if network.empty or 'Name' not in network.columns: return routine vfd_entries = network[network['Name'].astype(str).str.endswith('_VFD1', na=False)] if vfd_entries.empty: return routine # Sort VFDs by name for deterministic output vfd_entries = vfd_entries.sort_values('Name', key=lambda x: [natural_sort_key(name) for name in x]) # Rung 0: Comment rung rung0 = ET.SubElement(rll_content, "Rung") rung0.set("Number", "0") rung0.set("Type", "N") comment = ET.SubElement(rung0, "Comment") comment.text = """Belt Tracking AOI Calls Generates zzz_BeltTracking AOI calls for each VFD in the network. Each VFD gets its own tracking tag and AOI instantiation.""" text0 = ET.SubElement(rung0, "Text") text0.text = "NOP();" rung_number = 1 # Generate AOI call for each VFD for _, vfd_row in vfd_entries.iterrows(): vfd_name = str(vfd_row['Name']).strip() if not vfd_name: continue # Extract base name from VFD name (e.g., UL1_3 from UL1_3_VFD1) if vfd_name.endswith('_VFD1'): base_name = vfd_name[:-5] # Remove '_VFD1' else: # Fallback - shouldn't happen with our filter base_name = vfd_name.replace('_VFD', '') # Create tracking tag name (zzz_UL1_3Tracking) tracking_tag = f"zzz_{base_name}Tracking" # Create rung for this VFD rung = ET.SubElement(rll_content, "Rung") rung.set("Number", str(rung_number)) rung.set("Type", "N") text = ET.SubElement(rung, "Text") # Generate AOI call: zzz_BeltTracking(zzz_UL1_3Tracking,UL1_3_VFD1:I,UL1_3_VFD1:O); aoi_call = f"zzz_BeltTracking({tracking_tag},{vfd_name}:I,{vfd_name}:O);" text.text = aoi_call print(f" Belt tracking for {vfd_name} -> {tracking_tag}") rung_number += 1 print(f" - Added {rung_number - 1} belt tracking AOI calls") return routine def append_belt_tracking_routine(routines_element: ET.Element, data_loader): """Append belt tracking routine to routines element.""" routine_element = generate_belt_tracking_routine(data_loader) routines_element.append(routine_element) class BeltTrackingRoutinePlugin(RoutinePlugin): name = "belt_tracking" description = "Generates the ZZZ_BeltTracking routine" category = "device" def can_generate(self) -> bool: # Check if there are any VFDs in the network data network = self.context.data_loader.network if network.empty or 'Name' not in network.columns: return False vfd_entries = network[network['Name'].astype(str).str.endswith('_VFD1', na=False)] return not vfd_entries.empty def generate(self) -> bool: routine_element = generate_belt_tracking_routine(self.context.data_loader) self.context.routines_element.append(routine_element) return True register_plugin(BeltTrackingRoutinePlugin)