2025-08-26 21:29:02 +04:00

123 lines
4.2 KiB
Python

"""
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)