363 lines
16 KiB
Python
363 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
MCM Pattern Assignment Utilities
|
|
================================
|
|
|
|
Shared utilities for determining parent module assignments based on
|
|
device name patterns from generator configuration files.
|
|
|
|
This allows flexible assignment of devices to different MCM parent modules
|
|
based on configurable patterns, supporting both simple contains matching
|
|
and advanced regex patterns.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
from typing import Optional
|
|
|
|
|
|
def determine_parent_module_from_config(device_name: str, config_file_path: str = None,
|
|
is_network_module: bool = False,
|
|
has_ip_address: bool = None) -> tuple[str, bool, str, str]:
|
|
"""Determine parent module based on MCM device assignment patterns from config.
|
|
|
|
Args:
|
|
device_name: Name of the device/module to assign
|
|
config_file_path: Path to the generator config JSON file. If None, looks for
|
|
common config files in the project root.
|
|
is_network_module: Whether this is a network module (deprecated, use has_ip_address)
|
|
has_ip_address: Whether the module has an IP address (more accurate than is_network_module)
|
|
|
|
Returns:
|
|
Tuple of (parent_module_name, should_auto_create_parent, slot_number, ip_address)
|
|
"""
|
|
# Default fallback
|
|
default_parent = "SLOT2_EN4TR"
|
|
default_auto_create = False
|
|
default_slot = "2"
|
|
default_ip = "11.200.1.10"
|
|
|
|
# Determine if this is a network module
|
|
module_has_ip = has_ip_address if has_ip_address is not None else is_network_module
|
|
|
|
# Try to find config file if not provided
|
|
if config_file_path is None:
|
|
config_file_path = _find_generator_config()
|
|
|
|
if not config_file_path or not os.path.exists(config_file_path):
|
|
print(f" No MCM config found for pattern matching, using default parent: {default_parent}")
|
|
return default_parent, default_auto_create, default_slot, default_ip
|
|
|
|
try:
|
|
with open(config_file_path, 'r') as f:
|
|
config = json.load(f)
|
|
|
|
assignment_config = config.get('mcm_device_assignment', {})
|
|
if not assignment_config:
|
|
return default_parent, default_auto_create, default_slot, default_ip
|
|
|
|
pattern_rules = assignment_config.get('pattern_rules', [])
|
|
fallback_parent = assignment_config.get('fallback_parent', default_parent)
|
|
fallback_auto_create = assignment_config.get('auto_create_fallback_parent', default_auto_create)
|
|
fallback_slot = assignment_config.get('fallback_slot', default_slot)
|
|
fallback_ip = assignment_config.get('fallback_ip', default_ip)
|
|
|
|
# Get EN4TR slot configuration
|
|
en4tr_config = assignment_config.get('en4tr_slot_config', {})
|
|
base_ip = en4tr_config.get('base_ip_pattern', '11.200.1.')
|
|
ip_offset = en4tr_config.get('ip_start_offset', 10)
|
|
|
|
# Process rules in order (first match wins)
|
|
for rule in pattern_rules:
|
|
if not rule.get('enabled', True):
|
|
continue
|
|
|
|
# Check if rule is for network modules only
|
|
network_only = rule.get('network_modules_only', False)
|
|
if network_only and not module_has_ip:
|
|
continue # Skip this rule for non-network modules
|
|
|
|
device_patterns = rule.get('device_contains_pattern', [])
|
|
parent_tag = rule.get('parent_tag', default_parent)
|
|
auto_create = rule.get('auto_create_parent', default_auto_create)
|
|
slot_number = rule.get('parent_slot', _extract_slot_from_parent_tag(parent_tag))
|
|
parent_ip = rule.get('parent_ip', f"{base_ip}{ip_offset + int(slot_number)}")
|
|
match_mode = rule.get('match_mode', 'contains')
|
|
|
|
# Check if device name matches any patterns
|
|
if match_mode == 'regex':
|
|
regex_pattern = rule.get('regex_pattern', '')
|
|
if regex_pattern and re.search(regex_pattern, device_name, re.IGNORECASE):
|
|
network_suffix = " (network module)" if network_only else ""
|
|
print(f" Device '{device_name}'{network_suffix} matched regex pattern '{regex_pattern}', assigned to: {parent_tag} (Slot {slot_number}, IP {parent_ip})")
|
|
return parent_tag, auto_create, slot_number, parent_ip
|
|
else:
|
|
# Default contains matching
|
|
for pattern in device_patterns:
|
|
if pattern.upper() in device_name.upper():
|
|
network_suffix = " (network module)" if network_only else ""
|
|
print(f" Device '{device_name}'{network_suffix} matched pattern '{pattern}', assigned to: {parent_tag} (Slot {slot_number}, IP {parent_ip})")
|
|
return parent_tag, auto_create, slot_number, parent_ip
|
|
|
|
# No patterns matched, use fallback
|
|
print(f" Device '{device_name}' matched no patterns, using fallback parent: {fallback_parent} (Slot {fallback_slot}, IP {fallback_ip})")
|
|
return fallback_parent, fallback_auto_create, fallback_slot, fallback_ip
|
|
|
|
except Exception as e:
|
|
print(f" Error reading MCM config from {config_file_path}: {e}")
|
|
return default_parent, default_auto_create, default_slot, default_ip
|
|
|
|
|
|
def _extract_slot_from_parent_tag(parent_tag: str) -> str:
|
|
"""Extract slot number from parent tag name like SLOT3_EN4TR."""
|
|
import re
|
|
match = re.search(r'SLOT(\d+)_EN4TR', parent_tag, re.IGNORECASE)
|
|
return match.group(1) if match else "2"
|
|
|
|
|
|
def _find_generator_config() -> Optional[str]:
|
|
"""Find generator config file by searching common locations and names."""
|
|
# Look for common config files in parent directories
|
|
possible_configs = [
|
|
"CNO8_generator_config.json",
|
|
"MTN6_generator_config.json",
|
|
"SAT9_generator_config.json",
|
|
"generator_config.json"
|
|
]
|
|
|
|
# Check current directory and parent directories
|
|
current_dir = os.getcwd()
|
|
for i in range(3): # Check up to 3 levels up
|
|
for config_name in possible_configs:
|
|
potential_path = os.path.join(current_dir, config_name)
|
|
if os.path.exists(potential_path):
|
|
return potential_path
|
|
current_dir = os.path.dirname(current_dir)
|
|
|
|
return None
|
|
|
|
|
|
def determine_parent_with_fallback(device_name: str, default_parent: str = "SLOT2_EN4TR",
|
|
config_file_path: str = None, has_ip_address: bool = None) -> tuple[str, bool]:
|
|
"""Determine parent module with custom default fallback.
|
|
|
|
Args:
|
|
device_name: Name of the device/module to assign
|
|
default_parent: Custom default parent if no patterns match
|
|
config_file_path: Path to the generator config JSON file
|
|
has_ip_address: Whether the module has an IP address
|
|
|
|
Returns:
|
|
Tuple of (parent_module_name, should_auto_create_parent)
|
|
"""
|
|
parent, auto_create, _, _ = determine_parent_module_from_config(
|
|
device_name,
|
|
config_file_path,
|
|
is_network_module=has_ip_address or False,
|
|
has_ip_address=has_ip_address
|
|
)
|
|
return parent, auto_create
|
|
|
|
|
|
def determine_parent_with_slot_info(device_name: str, default_parent: str = "SLOT2_EN4TR",
|
|
config_file_path: str = None, has_ip_address: bool = None) -> tuple[str, bool, str, str]:
|
|
"""Determine parent module with full slot and IP information.
|
|
|
|
Args:
|
|
device_name: Name of the device/module to assign
|
|
default_parent: Custom default parent if no patterns match
|
|
config_file_path: Path to the generator config JSON file
|
|
has_ip_address: Whether the module has an IP address
|
|
|
|
Returns:
|
|
Tuple of (parent_module_name, should_auto_create_parent, slot_number, ip_address)
|
|
"""
|
|
return determine_parent_module_from_config(
|
|
device_name,
|
|
config_file_path,
|
|
is_network_module=has_ip_address or False,
|
|
has_ip_address=has_ip_address
|
|
)
|
|
|
|
|
|
# Convenience functions for common module types
|
|
def get_parent_for_m12dr(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for M12DR modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_vfd(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for VFD modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_extendo(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for Extendo modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_apf(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for APF modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_zmx(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for ZMX modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_turck_hub(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for Turck Hub modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_dpm(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for DPM modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_sio(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for SIO modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_pmm(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for PMM modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_lpe(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for LPE modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_beacon(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for Beacon modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
def get_parent_for_solenoid(device_name: str, has_ip_address: bool = None) -> str:
|
|
"""Get parent module for Solenoid modules with appropriate default."""
|
|
parent, _ = determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
return parent
|
|
|
|
|
|
# Enhanced functions that return both parent and auto-create flag
|
|
def get_parent_and_create_flag_for_m12dr(device_name: str, has_ip_address: bool = None) -> tuple[str, bool]:
|
|
"""Get parent module and auto-create flag for M12DR modules."""
|
|
return determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
|
|
|
|
def get_parent_and_create_flag(device_name: str, has_ip_address: bool = None) -> tuple[str, bool]:
|
|
"""Get parent module and auto-create flag for any module type."""
|
|
return determine_parent_with_fallback(device_name, "SLOT2_EN4TR", has_ip_address=has_ip_address)
|
|
|
|
|
|
# Registry for tracking EN4TR modules that need to be created
|
|
_required_en4tr_modules = {} # Now stores {module_name: (slot, ip)}
|
|
|
|
|
|
def register_required_en4tr(parent_module_name: str, slot_number: str = None, ip_address: str = None):
|
|
"""Register an EN4TR module that needs to be created."""
|
|
global _required_en4tr_modules
|
|
if parent_module_name and parent_module_name.endswith("_EN4TR"):
|
|
_required_en4tr_modules[parent_module_name] = (slot_number, ip_address)
|
|
print(f" Registered EN4TR module for auto-creation: {parent_module_name} (Slot {slot_number}, IP {ip_address})")
|
|
|
|
|
|
def get_required_en4tr_modules() -> dict:
|
|
"""Get the dictionary of EN4TR modules that need to be created."""
|
|
global _required_en4tr_modules
|
|
return _required_en4tr_modules.copy()
|
|
|
|
|
|
def clear_required_en4tr_modules():
|
|
"""Clear the registry of required EN4TR modules."""
|
|
global _required_en4tr_modules
|
|
_required_en4tr_modules.clear()
|
|
|
|
|
|
def check_and_register_en4tr_for_device(device_name: str, has_ip_address: bool = None):
|
|
"""Check pattern matching for a device and register EN4TR if needed."""
|
|
parent, should_auto_create, slot_number, ip_address = determine_parent_with_slot_info(device_name, has_ip_address=has_ip_address)
|
|
if should_auto_create and parent:
|
|
register_required_en4tr(parent, slot_number, ip_address)
|
|
return parent
|
|
|
|
|
|
def create_en4tr_modules_from_registry(ethernet_base_ip: str = "11.200.1.") -> dict:
|
|
"""Create EN4TR module configs for all registered modules.
|
|
|
|
Args:
|
|
ethernet_base_ip: Base IP address pattern (e.g., "11.200.1.") - used as fallback only
|
|
|
|
Returns:
|
|
Dictionary mapping module names to EN4TRModuleConfig objects
|
|
"""
|
|
from .en4tr_boilerplate_model import create_en4tr_for_slot, extract_slot_from_name
|
|
|
|
global _required_en4tr_modules
|
|
created_configs = {}
|
|
|
|
ip_counter = 10 # Fallback IP counter
|
|
|
|
for en4tr_name, (slot_number, ip_address) in _required_en4tr_modules.items():
|
|
# Use config-specified slot and IP if available, otherwise extract/generate
|
|
slot = slot_number if slot_number else extract_slot_from_name(en4tr_name)
|
|
ip = ip_address if ip_address else f"{ethernet_base_ip}{ip_counter}"
|
|
|
|
config = create_en4tr_for_slot(
|
|
slot_number=slot,
|
|
ethernet_address=ip,
|
|
name=en4tr_name
|
|
)
|
|
|
|
created_configs[en4tr_name] = config
|
|
print(f" Created EN4TR config: {en4tr_name} (Slot {slot}) -> {ip}")
|
|
|
|
# Only increment counter if we used fallback IP
|
|
if not ip_address:
|
|
ip_counter += 1
|
|
|
|
return created_configs
|
|
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
# Test pattern matching with sample device names for slot specification
|
|
test_devices = [
|
|
"VS01C_FIOM18", # Should go to SLOT3_EN4TR (slot 3, IP 11.200.1.12)
|
|
"VS01B_FIOM1", # Should go to SLOT2_EN4TR (slot 2, IP 11.200.1.11)
|
|
"OTHER_MODULE" # Should use fallback
|
|
]
|
|
|
|
print("Testing slot-based pattern matching:")
|
|
for device in test_devices:
|
|
parent, auto_create, slot, ip = determine_parent_module_from_config(device, has_ip_address=True)
|
|
print(f"Device: {device} -> Parent: {parent}, Slot: {slot}, IP: {ip}, Auto-create: {auto_create}")
|
|
|
|
if auto_create:
|
|
register_required_en4tr(parent, slot, ip)
|
|
|
|
print("\nRegistered EN4TR modules:")
|
|
required_modules = get_required_en4tr_modules()
|
|
for module_name, (slot, ip) in required_modules.items():
|
|
print(f" {module_name}: Slot {slot}, IP {ip}")
|
|
|
|
print("\nCreating EN4TR configs from registry:")
|
|
configs = create_en4tr_modules_from_registry()
|
|
for name, config in configs.items():
|
|
print(f" {name}: Slot {config.slot_number}, IP {config.ethernet_address}")
|