#!/usr/bin/env python3 """ DPM Module Boilerplate Model ============================ Model for DPM (Display Panel Module) modules - OS30-002404-2S. Supports configuring module name, parent module, IP address, and basic settings. """ from typing import Dict, Optional, TYPE_CHECKING import xml.etree.ElementTree as ET from dataclasses import dataclass from datetime import datetime import os if TYPE_CHECKING: from excel_data_processor import ModuleData @dataclass class DPMModuleConfig: """Configuration for a DPM module instance.""" name: str # Module name (e.g., "DPM1") ip_address: str = "192.168.1.100" # IP address for Ethernet connection parent_module: str = "Local" # Parent module (usually "Local" for Ethernet) parent_port_id: str = "2" # Port on the parent module inhibited: bool = False # Whether module starts inhibited major_fault: bool = False rpi: int = 100000 # Request Packet Interval in microseconds (100ms default) class DPMModuleGenerator: """Generator for DPM module XML.""" def __init__(self, config: DPMModuleConfig): self.config = config # Use project-specific boilerplate directory if set, otherwise default boilerplate_dir = os.environ.get('MCM_BOILERPLATE_DIR', 'boilerplate') self.boilerplate_path = os.path.join(boilerplate_dir, "DPM_Module.L5X") self.tree = None self.root = None def load_boilerplate(self): """Load the DPM 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.""" # Update in root attributes self.root.set("TargetName", self.config.name) # Update Module element module = self.root.find(".//Module[@Use='Target']") if module is not None: module.set("Name", self.config.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.config.parent_module) module.set("ParentModPortId", self.config.parent_port_id) def update_ip_address(self): """Update the IP address in the Ethernet port.""" port = self.root.find(".//Port[@Type='Ethernet']") if port is not None: port.set("Address", self.config.ip_address) def update_inhibited_status(self): """Update the inhibited and fault status.""" module = self.root.find(".//Module[@Use='Target']") if module is not None: module.set("Inhibited", str(self.config.inhibited).lower()) module.set("MajorFault", str(self.config.major_fault).lower()) def update_rpi(self): """Update the Request Packet Interval.""" connection = self.root.find(".//Connection") if connection is not None: connection.set("RPI", str(self.config.rpi)) 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_ip_address() self.update_inhibited_status() self.update_rpi() 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.") # Save with proper formatting 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') # ------------------------------------------------------------------ # Helper for generator refactor # ------------------------------------------------------------------ @classmethod def from_excel(cls, module_data: 'ModuleData') -> 'DPMModuleGenerator': """Create and configure generator from Excel ModuleData.""" cfg = create_dpm_module( name=module_data.tagname, ip_address=module_data.ip_address or "192.168.1.100", parent_module="SLOT2_EN4TR", parent_port_id="2", inhibited=False, ) gen = cls(cfg) gen.load_boilerplate() gen.apply_updates() return gen def create_dpm_module(name: str, ip_address: str = "192.168.1.100", parent_module: str = "Local", parent_port_id: str = "2", inhibited: bool = False, major_fault: bool = False, rpi: int = 100000) -> DPMModuleConfig: """Factory function to create a DPM module configuration.""" return DPMModuleConfig( name=name, ip_address=ip_address, parent_module=parent_module, parent_port_id=parent_port_id, inhibited=inhibited, major_fault=major_fault, rpi=rpi ) # Example usage if __name__ == "__main__": # Example: Create a DPM module with custom configuration config = create_dpm_module( name="DPM_STATION_1", ip_address="192.168.1.100", parent_module="Local", parent_port_id="2", inhibited=False, # Start enabled rpi=50000 # 50ms update rate ) generator = DPMModuleGenerator(config) generator.load_boilerplate() generator.apply_updates() generator.save("generated/DPM_STATION_1.L5X") print(f"Generated DPM module: {config.name}") print(f"IP Address: {config.ip_address}") print(f"Parent module: {config.parent_module}") print(f"Port: {config.parent_port_id}") print(f"Inhibited: {config.inhibited}") print(f"RPI: {config.rpi}µs ({config.rpi/1000}ms)")