#!/usr/bin/env python3 """generate_safety_only.py – build limited SafetyProgram and MainProgram L5X with essential safety routines only. This mode only generates: - Safety routines: inputs, outputs, estops, zones, resets - Main routines: safety_tag_map, estop_check (safety checkout) - Controller tags embedded in MainProgram L5X - Safety tag mapping Extracts safety data (RST, STO, EPC) from DESC_IP sheet and uses zones_config.py for zone configuration. Run from repository root: python generate_safety_only.py [--desc-ip-mode] The outputs are written to *_Limited.* filenames in the current directory. """ from __future__ import annotations from pathlib import Path import sys import argparse # If repo root is current dir, add nested "src" folder to sys.path pkg_dir = Path(__file__).resolve().parent / "src" if pkg_dir.is_dir(): sys.path.insert(0, str(pkg_dir)) from src.generators import LimitedSafetyProgramGenerator, LimitedMainProgramGenerator from src.data_loader import DataLoader from src.writers.mapping_writer import create_safety_tag_mapping EXCEL_FILE = Path('MCM01_UL1_UL3.xlsx') DESC_IP_FILE = Path('DESC_IP_MERGED.xlsx') SAFETY_L5X = 'SafetyProgram_Limited.L5X' MAIN_L5X = 'MainProgram_Limited.L5X' MAPPING_TXT = 'SafetyTagMapping_Limited.txt' def main() -> None: parser = argparse.ArgumentParser(description="Generate safety-only routines") parser.add_argument('--desc-ip-mode', action='store_true', help='Extract safety devices from DESC_IP data instead of separate sheets') parser.add_argument('--ignore-estop1ok', action='store_true', help='Ignore ESTOP1OK tags in inputs, estops, and zones routines') parser.add_argument('--project-name', type=str, help='Project name to detect MCM type for zones configuration') args = parser.parse_args() print("Safety-Only Mode") print("================") # Determine which Excel file to use excel_file = DESC_IP_FILE if args.desc_ip_mode and DESC_IP_FILE.exists() else EXCEL_FILE if args.desc_ip_mode: print("Using DESC_IP data extraction mode") print(f"Using Excel file: {excel_file}") print("Extracting safety devices from DESC_IP sheet using safety rules\n") else: print("Using separate safety sheets mode") print(f"Using Excel file: {excel_file}") print("Generating essential safety routines using DESC_IP data extraction and zones_config.py\n") if args.ignore_estop1ok: print("INFO: Ignoring ESTOP1OK tags in safety routines generation\n") # Load zones configuration based on project type zones_dict = None try: # Add parent directory to sys.path to find zones_config.py sys.path.insert(0, str(Path(__file__).parent.parent)) from zones_config import ZONES_CONFIGS, DEFAULT_ZONES # Detect project type from project name argument import re if args.project_name: mcm_match = re.search(r"(MCM\d+)", args.project_name, re.IGNORECASE) if mcm_match: mcm_type = mcm_match.group(1).upper() zones_dict = ZONES_CONFIGS.get(mcm_type, DEFAULT_ZONES) print(f"Detected {mcm_type} project from '{args.project_name}', loaded {len(zones_dict)} zones from zones_config.py") else: zones_dict = DEFAULT_ZONES print(f"No MCM type detected in '{args.project_name}', loaded {len(zones_dict)} default zones from zones_config.py") else: zones_dict = DEFAULT_ZONES print(f"No project name provided, loaded {len(zones_dict)} default zones from zones_config.py") except ImportError as e: print(f"Warning: Could not load zones_config.py: {e}") print("Proceeding without zones configuration...") # Limited Safety program (inputs, outputs, estops, zones, resets) print("1. Generating SafetyProgram...") LimitedSafetyProgramGenerator(excel_file, zones_dict=zones_dict, ignore_estop1ok=args.ignore_estop1ok).write(SAFETY_L5X) print(f'[SUCCESS] Wrote {SAFETY_L5X}') print(' - Contains: inputs, outputs, estops, zones, resets routines') # Limited Main program (safety_tag_map, estop_check) with embedded controller tags print("\n2. Generating MainProgram with embedded controller tags...") LimitedMainProgramGenerator(excel_file, zones_dict=zones_dict).write(MAIN_L5X) print(f'[SUCCESS] Wrote {MAIN_L5X}') print(' - Contains: safety_tag_map, estop_check routines') print(' - Contains: Controller-level tags embedded in L5X') # Safety tag mapping print("\n3. Generating safety tag mapping...") loader = DataLoader(excel_file, zones_dict=zones_dict) # Collect safety tags from appropriate source safety_tags = loader.safety_tags_from_pb # For limited mode, we don't have beacon tags, so pass empty sets create_safety_tag_mapping(safety_tags, set(), set(), MAPPING_TXT) print(f'[SUCCESS] Wrote {MAPPING_TXT}') print(f' - Contains: {len(safety_tags)} safety tags ({len(safety_tags) - 1} push buttons + MCM)') print(f"\nSafety-only generation complete!") if args.desc_ip_mode: print(f"Data sources: DESC_IP sheet with safety device extraction") print(f" - RST devices: DESCA contains 'START' but NOT 'LIGHT'") print(f" - STO devices: TAGNAME contains 'VFD' or DESCA contains 'STO'") print(f" - EPC devices: DESCA contains 'EPC' or 'ESTOP'") else: print(f"Data sources: DESC_IP extraction (RST, STO, EPC) + zones_config.py") print(f"Main routines: safety_tag_map, estop_check") print(f"Output files:") print(f" - {SAFETY_L5X}") print(f" - {MAIN_L5X} (with embedded controller tags)") print(f" - {MAPPING_TXT}") if __name__ == '__main__': main()