""" EN4TR EtherNet/IP Module L5X Generator (Boilerplate-based) ========================================================== This module provides functionality to generate EN4TR module L5X files by loading a boilerplate template and modifying specific fields. """ import os from dataclasses import dataclass, field from typing import Optional from .base_boilerplate_model import BaseModuleConfig, BaseBoilerplateGenerator @dataclass class EN4TRModuleConfig(BaseModuleConfig): """Configuration for an EN4TR module.""" name: str ethernet_address: str # e.g., "11.200.1.1" slot_number: str = "2" # Primary slot specification (2, 3, 4, etc.) boilerplate_path: str = "" # Will be auto-determined based on slot if empty parent_module: str = "Local" parent_port_id: str = "1" slot_address: str = "" # Will be set to slot_number if empty (for backward compatibility) def __post_init__(self): """Post-initialization to set defaults based on slot specification.""" # Set slot_address to slot_number if not provided (backward compatibility) if not self.slot_address: self.slot_address = self.slot_number # Auto-determine boilerplate path if not provided if not self.boilerplate_path: boilerplate_dir = os.environ.get('MCM_BOILERPLATE_DIR', 'boilerplate') # Try slot-specific first, then fall back to generic SLOT2_EN4TR slot_specific_filename = f'SLOT{self.slot_number}_EN4TR_Module.L5X' generic_filename = 'SLOT2_EN4TR_Module.L5X' slot_specific_path = os.path.join(boilerplate_dir, slot_specific_filename) generic_path = os.path.join(boilerplate_dir, generic_filename) if os.path.exists(slot_specific_path): self.boilerplate_path = slot_specific_path elif os.path.exists(generic_path): self.boilerplate_path = generic_path print(f" Using generic EN4TR boilerplate for {self.name}: {generic_filename}") else: # Default to generic filename if neither exists self.boilerplate_path = generic_path # Auto-set name based on slot if not following standard pattern if not self.name or self.name == "EN4TR": self.name = f"SLOT{self.slot_number}_EN4TR" def get_updates(self): """Get dictionary of updates to apply.""" return { "name": self.name, "ethernet_address": self.ethernet_address, "parent_module": self.parent_module, "parent_port_id": self.parent_port_id, "slot_address": self.slot_address, "slot_number": self.slot_number } class EN4TRModuleGenerator(BaseBoilerplateGenerator): """Generator for EN4TR module L5X files using boilerplate template.""" def apply_updates(self): """Apply EN4TR-specific updates to the boilerplate.""" # Update TargetName in root element self.root.set("TargetName", self.config.name) # Update Module name module = self.root.find(".//Module[@Use='Target']") if module is not None: module.set("Name", self.config.name) module.set("ParentModule", self.config.parent_module) module.set("ParentModPortId", self.config.parent_port_id) # Update slot address (ICP port) icp_port = self.root.find(".//Module[@Use='Target']/Ports/Port[@Type='ICP']") if icp_port is not None: icp_port.set("Address", self.config.slot_address) # Update Ethernet address eth_port = self.root.find(".//Module[@Use='Target']/Ports/Port[@Type='Ethernet']") if eth_port is not None: eth_port.set("Address", self.config.ethernet_address) # Factory functions def create_en4tr_module( name: str, ethernet_address: str, slot_address: str = "2", parent_module: str = "Local", parent_port_id: str = "1" ) -> EN4TRModuleConfig: """ Create an EN4TR module configuration (legacy interface). Args: name: Module name ethernet_address: Ethernet IP address (e.g., "11.200.1.1") slot_address: Slot number in the chassis parent_module: Parent module name parent_port_id: Parent module port ID Returns: EN4TRModuleConfig object """ return EN4TRModuleConfig( name=name, ethernet_address=ethernet_address, slot_number=slot_address, # Use slot_number as primary slot_address=slot_address, # Keep for backward compatibility parent_module=parent_module, parent_port_id=parent_port_id ) def create_en4tr_for_slot( slot_number: str, ethernet_address: str, name: str = "", parent_module: str = "Local", parent_port_id: str = "1" ) -> EN4TRModuleConfig: """ Create an EN4TR module configuration by slot number (recommended interface). Args: slot_number: Slot number in the chassis (e.g., "2", "3", "4") ethernet_address: Ethernet IP address (e.g., "11.200.1.1") name: Module name (auto-generated as SLOTx_EN4TR if empty) parent_module: Parent module name parent_port_id: Parent module port ID Returns: EN4TRModuleConfig object """ return EN4TRModuleConfig( name=name or f"SLOT{slot_number}_EN4TR", ethernet_address=ethernet_address, slot_number=slot_number, parent_module=parent_module, parent_port_id=parent_port_id ) def extract_slot_from_name(module_name: str) -> str: """ Extract slot number from EN4TR module name. Args: module_name: Module name like "SLOT3_EN4TR" or "SLOT2_EN4TR" Returns: Slot number as string (e.g., "3", "2") """ import re match = re.search(r'SLOT(\d+)_EN4TR', module_name, re.IGNORECASE) if match: return match.group(1) return "2" # Default to slot 2 if __name__ == "__main__": # Example usage - new slot-centric approach (recommended) config = create_en4tr_for_slot( slot_number="3", ethernet_address="11.200.1.10" ) # Generate the module generator = EN4TRModuleGenerator(config) generator.save_to_file("generated/SLOT3_EN4TR.L5X") print(f"Generated EN4TR module configuration for slot {config.slot_number}: {config.name}") # Example: Creating multiple EN4TR modules for different slots slots_config = [ ("2", "11.200.1.1"), ("3", "11.200.1.2"), ("4", "11.200.1.3") ] for slot, ip in slots_config: config = create_en4tr_for_slot(slot_number=slot, ethernet_address=ip) print(f"Created config for {config.name} at IP {config.ethernet_address}") # generator = EN4TRModuleGenerator(config) # generator.save_to_file(f"generated/{config.name}.L5X")