2025-09-02 11:13:29 +04:00

193 lines
7.1 KiB
Python

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