174 lines
6.5 KiB
Python
174 lines
6.5 KiB
Python
#!/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=True,
|
|
)
|
|
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 = True, 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)") |