#!/usr/bin/env python3 """ LPE Module Boilerplate Model ==================================== Model for LPE modules. Supports configuring module name and parent module. """ from typing import Dict, Optional import xml.etree.ElementTree as ET from dataclasses import dataclass from datetime import datetime import os @dataclass class LPEModuleConfig: """Configuration for an LPE module.""" name: str parent_module: str port_address: str = "0" class LPEBoilerplateGenerator: """Generator for LPE module XML.""" def __init__(self, name: str, parent_module: str, port_address: str = "0"): self.name = name self.parent_module = parent_module self.port_address = port_address self.boilerplate_path = os.path.join("boilerplate", "LPE_Module.L5X") self.tree = None self.root = None def load_boilerplate(self): """Load the LPE boilerplate template.""" if not os.path.exists(self.boilerplate_path): raise FileNotFoundError(f"Boilerplate file not found: {self.boilerplate_path}") self.tree = ET.parse(self.boilerplate_path) self.root = self.tree.getroot() def update_module_name(self): """Update the module name throughout the XML.""" self.root.set("TargetName", self.name) module = self.root.find(".//Module[@Use='Target']") if module is not None: module.set("Name", self.name) def update_parent_module(self): """Update parent module references.""" module = self.root.find(".//Module[@Use='Target']") if module is not None: module.set("ParentModule", self.parent_module) # All LPE modules are children of IOLM, so ParentModPortId is always 4 (IO-Link Port) module.set("ParentModPortId", "4") def update_port_address(self): """Update the IO-Link port address.""" port = self.root.find(".//Port[@Type='IO-Link']") if port is not None: port.set("Address", self.port_address) def update_export_date(self): """Update the export date to current time.""" export_date = datetime.now().strftime("%a %b %d %H:%M:%S %Y") self.root.set("ExportDate", export_date) def apply_updates(self): """Apply all updates to the boilerplate.""" self.update_module_name() self.update_parent_module() self.update_port_address() self.update_export_date() def save(self, output_path: str): """Save the updated module to file.""" if self.tree is None: raise RuntimeError("No boilerplate loaded. Call load_boilerplate() first.") self.tree.write(output_path, encoding='UTF-8', xml_declaration=True) def get_xml_string(self) -> str: """Get the XML as a string.""" if self.tree is None: raise RuntimeError("No boilerplate loaded. Call load_boilerplate() first.") return ET.tostring(self.root, encoding='unicode') @classmethod def from_mapping(cls, mapping: Dict[str, str]) -> "LPEBoilerplateGenerator": """Create, configure, and return a generator from an entry in `EnhancedMCMGenerator.lpe_modules`.""" gen = cls( name=mapping["name"], parent_module=mapping["parent_module"], port_address=mapping["port_address"], ) gen.load_boilerplate() gen.apply_updates() return gen def create_lpe_module(config: LPEModuleConfig) -> LPEBoilerplateGenerator: """Factory function to create and configure an LPE module generator. Args: config: Configuration for the LPE module Returns: Configured LPE module generator """ generator = LPEBoilerplateGenerator( name=config.name, parent_module=config.parent_module, port_address=config.port_address ) generator.load_boilerplate() generator.apply_updates() return generator