136 lines
5.9 KiB
Python
136 lines
5.9 KiB
Python
#!/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() |