""" IB16S Safety Digital Input Module L5X Generator (Boilerplate-based) ================================================================== This module provides functionality to generate IB16S safety module L5X files by loading a boilerplate template and modifying specific fields. """ from dataclasses import dataclass from typing import Optional, Dict import xml.etree.ElementTree as ET from .base_boilerplate_model import BaseModuleConfig, BaseBoilerplateGenerator @dataclass class IB16SModuleConfig(BaseModuleConfig): """Configuration for an IB16S safety module.""" name: str boilerplate_path: str = "boilerplate/SLOT7_IB16S_Module.L5X" slot_address: str = "7" parent_module: str = "Local" parent_port_id: str = "1" safety_network: str = "16#0000_4c33_031d_8f1b" safety_input_device_names: Optional[Dict[int, str]] = None # Names for 16 safety input devices (0-15) def get_updates(self): """Get dictionary of updates to apply.""" return { "name": self.name, "slot_address": self.slot_address, "parent_module": self.parent_module, "parent_port_id": self.parent_port_id, "safety_network": self.safety_network, "safety_input_device_names": self.safety_input_device_names } class IB16SModuleGenerator(BaseBoilerplateGenerator): """Generator for IB16S safety module L5X files using boilerplate template.""" def apply_updates(self): """Apply IB16S-specific updates to the boilerplate.""" # Update TargetName in root element self.root.set("TargetName", self.config.name) # Update Module name and parent info 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) module.set("SafetyNetwork", self.config.safety_network) # 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 safety input device comments self.update_comments() def update_comments(self): """Update comments for safety input devices.""" safety_input_comments = self.root.find(".//Connection[@Name='SafetyInput']/InputTag/Comments") if safety_input_comments is None: return safety_input_comments.clear() if not self.config.safety_input_device_names: return for index, text in self.config.safety_input_device_names.items(): comment = ET.SubElement(safety_input_comments, "Comment") comment.set("Operand", f".PT{index:02d}.DATA") comment.text = text @classmethod def from_mapping(cls, mapping: Dict[str, str], comments: Optional[Dict[int, str]] = None) -> "IB16SModuleGenerator": """Create and fully configure an IB16S generator from the Excel-derived `modules` entry (a plain dict). The structure expected is the one produced in EnhancedMCMGenerator._organize_modules_by_type(). :param comments: Optional dict of input index (0-15) to comment text. """ cfg = IB16SModuleConfig( name=mapping["name"], slot_address=mapping["slot_address"], parent_module="Local", parent_port_id="1", safety_network=mapping.get("safety_network", "16#0000_4c33_031d_8f1b"), safety_input_device_names=comments, ) gen = cls(cfg) gen.load_boilerplate() gen.apply_updates() return gen # Factory function def create_ib16s_module( name: str, slot_address: str = "7", parent_module: str = "Local", parent_port_id: str = "1", safety_network: str = "16#0000_4c33_031d_8f1b", safety_input_device_names: Optional[Dict[int, str]] = None ) -> IB16SModuleConfig: """ Create an IB16S safety module configuration. Args: name: Module name slot_address: Slot number in the chassis parent_module: Parent module name parent_port_id: Parent module port ID safety_network: Safety network ID safety_input_device_names: Dictionary mapping safety input index (0-15) to device names Returns: IB16SModuleConfig object """ return IB16SModuleConfig( name=name, slot_address=slot_address, parent_module=parent_module, parent_port_id=parent_port_id, safety_network=safety_network, safety_input_device_names=safety_input_device_names ) if __name__ == "__main__": # Example usage config = create_ib16s_module( name="SLOT8_IB16S", slot_address="8", parent_module="Local", parent_port_id="1", safety_network="16#0000_4c33_031d_8f1b", safety_input_device_names={ 0: "Emergency Stop Circuit 1", 1: "Emergency Stop Circuit 2", 2: "Safety Door 1 Closed", 3: "Safety Door 2 Closed", 4: "Light Curtain Area 1", 5: "Light Curtain Area 2", 6: "Safety Mat Zone 1", 7: "Safety Mat Zone 2", 8: "Two Hand Control Left", 9: "Two Hand Control Right", 10: "Pull Cord Switch 1", 11: "Pull Cord Switch 2", 12: "Safety Interlock 1", 13: "Safety Interlock 2", 14: "Guard Door Position", 15: "Safety Reset Button" } ) # Generate the module generator = IB16SModuleGenerator(config) generator.save_to_file("generated/SLOT8_IB16S.L5X") print(f"Generated IB16S safety module configuration saved to generated/SLOT8_IB16S.L5X")