Scripts/PLACE DPMS/multi_mcm_device_mapping.py
2025-09-19 10:39:48 +04:00

316 lines
13 KiB
Python

"""
Multi-MCM Device Mapping Script
This script processes ALL MCMs from a device_mapping.txt file simultaneously.
It creates separate folders for each MCM with their respective DPM configurations.
The input file should have the following format:
MCM01
DPM_NAME1
DEVICE_NAME1 DEVICE_IP1
DEVICE_NAME2 DEVICE_IP2
...
DPM_NAME2
...
MCM02
DPM_NAME1
...
"""
import json
import os
import shutil
def generate_config(num_vfds, num_fios, device_list, dpm_label, mcm_name):
if num_vfds + num_fios > 24:
raise ValueError("Error: Total number of elements cannot exceed 24 slots.")
if len(device_list) != num_vfds + num_fios:
raise ValueError(f"Error: Expected {num_vfds + num_fios} device names, but got {len(device_list)}.")
# Predefined positions and labels for devices (up to 24)
positions = [
{"x": 0.0232, "y": 0.4991}, {"x": 0.0230, "y": 0.6677}, {"x": 0.0216, "y": 0.8333}, # 3
{"x": 0.0229, "y": 0.3333}, {"x": 0.0229, "y": 0.1667}, {"x": 0.0244, "y": 0.0000}, # 6
{"x": 0.5000, "y": 0.0000}, {"x": 0.6250, "y": 0.0000}, {"x": 0.7500, "y": 0.0000}, # 9
{"x": 0.3750, "y": 0.0000}, {"x": 0.2516, "y": 0.0019}, {"x": 0.1245, "y": 0.0000}, # 12
{"x": 0.8509, "y": 0.3332}, {"x": 0.8514, "y": 0.1667}, {"x": 0.8528, "y": 0.0000}, # 15
{"x": 0.8509, "y": 0.4988}, {"x": 0.8501, "y": 0.6639}, {"x": 0.8515, "y": 0.8329}, # 18
{"x": 0.3740, "y": 0.8324}, {"x": 0.2481, "y": 0.8324}, {"x": 0.1222, "y": 0.8333}, # 21
{"x": 0.5026, "y": 0.8314}, {"x": 0.6245, "y": 0.8314}, {"x": 0.7536, "y": 0.8351} # 24
]
label_positions = [
{"x": 0.0094, "y": 0.5394}, {"x": 0.0068, "y": 0.7315}, {"x": 0.0068, "y": 0.8981}, # 3
{"x": 0.0068, "y": 0.3986}, {"x": 0.0068, "y": 0.2324}, {"x": 0.0068, "y": 0.0653}, # 6
{"x": 0.5117, "y": 0.1662}, {"x": 0.6312, "y": 0.1664}, {"x": 0.7500, "y": 0.1664}, # 9
{"x": 0.3864, "y": 0.1664}, {"x": 0.3150, "y": 0.1682}, {"x": 0.2072, "y": 0.1646}, # 12
{"x": 0.9470, "y": 0.3943}, {"x": 0.9470, "y": 0.2276}, {"x": 0.9470, "y": 0.0619}, # 15
{"x": 0.9470, "y": 0.5610}, {"x": 0.9470, "y": 0.7257}, {"x": 0.9470, "y": 0.8927}, # 18
{"x": 0.4587, "y": 0.8896}, {"x": 0.3311, "y": 0.8887}, {"x": 0.2071, "y": 0.8886}, # 21
{"x": 0.5887, "y": 0.8886}, {"x": 0.7106, "y": 0.8886}, {"x": 0.8344, "y": 0.9247} # 24
]
total_devices = num_vfds + num_fios
# Build tagProps per rules, using the provided MCM name and device types
# Index 0 must be the DPM tag; indices 1..N are device tags
dpm_tag = f"System/{mcm_name}/IO_BLOCK/DPM/{dpm_label}"
device_tags = []
for device_data in device_list:
name, _ip = device_data.split()
upper_name = name.upper()
if "VFD" in upper_name:
device_tags.append(f"System/{mcm_name}/Conveyor/VFD/{name}")
elif "TIPPER" in upper_name or "ST" in upper_name:
device_tags.append(f"System/{mcm_name}/Conveyor/Tipper/{name}")
elif "EX" in upper_name or "EXTENDO" in upper_name:
# For EXTENDO devices, ensure the tag name ends with EX1
tag_name = name.replace("_EXTENDO", "_EX1") if "_EXTENDO" in name else name
device_tags.append(f"System/{mcm_name}/Conveyor/EXTENDO/{tag_name}")
elif "ZMX" in upper_name:
device_tags.append(f"System/{mcm_name}/PE/ZMX/{name}")
elif "PMM" in upper_name:
device_tags.append(f"System/{mcm_name}/{name}")
elif "SIO" in upper_name:
device_tags.append(f"System/{mcm_name}/IO_BLOCK/SIO/{name}")
elif "PLC" in upper_name:
device_tags.append(f"System/{mcm_name}/Rack")
else:
# Default to FIO when not explicitly matched otherwise
device_tags.append(f"System/{mcm_name}/IO_BLOCK/FIO/{name}")
tag_props = [dpm_tag] + device_tags
con_lines_visible = [True] * total_devices + [False] * (24 - total_devices)
# Generate propConfig for con_lines bindings
prop_config = {}
for i in range(total_devices):
prop_config[f"props.params.con_lines[{i}]"] = {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
# Device tags start at index 1 in view.params.tagProps
"references": {"0": f"{{view.params.tagProps[{i+1}]}}", "fc": "{session.custom.fc}"},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Communication_Faulted"
},
"transforms": [
{"expression": "coalesce({value},{view.params.forceFaultStatus},false)", "type": "expression"},
{"fallback": False, "inputType": "scalar", "mappings": [{"input": False, "output": True}], "outputType": "scalar", "type": "map"}
],
"type": "tag"
}
}
# Base configuration
config = {
"custom": {},
"params": {"tagProps": tag_props},
"props": {"defaultSize": {"height": 1080, "width": 1920}},
"root": {
"children": [],
"meta": {"name": "root"},
"position": {"x": 0.6348, "y": -0.1546},
"props": {"mode": "percent"},
"type": "ia.container.coord"
}
}
# Add DPM component
config["root"]["children"].append({
"meta": {"name": "DPM"},
"position": {"x": 0, "y": 0, "height": 1, "width": 1},
"propConfig": prop_config,
"props": {
# Include tagProps with index 0 set to the DPM tag
"params": {"con_lines": [False] * 24, "con_lines_visible": con_lines_visible, "in": False, "out": False, "tagProps": [dpm_tag] + ["value"] * 9},
"path": "Windows/Tabs/Enternet Windows/Components/DPM_TO_HUB"
},
"type": "ia.display.view"
})
# Process all devices
for i, device_data in enumerate(device_list):
name, ip = device_data.split()
# Determine device type and component path
upper_name = name.upper()
is_vfd = "VFD" in upper_name
is_ex = "EX" in upper_name or "EXTENDO" in upper_name
is_zmx = "ZMX" in upper_name
is_pmm = "PMM" in upper_name
is_tipper = "TIPPER" in upper_name or "ST" in upper_name
is_plc = "PLC" in upper_name
# Set component name (remove common suffixes)
component_name = name.replace("_VFD1", "").replace("_EX1", "")
# Determine component path based on device type
if is_ex:
component_path = "EXTENDO"
elif is_vfd:
component_path = "APF"
elif is_zmx:
component_path = "ZMX"
elif is_pmm:
component_path = "PMM"
elif is_tipper:
component_path = "TIPPER"
elif is_plc:
component_path = "PLC"
else:
component_path = "FIO_SIO"
# Adjust X position for all non-VFD devices at positions 12-18 (1-based)
component_x = positions[i]["x"]
label_x = label_positions[i]["x"]
if not is_vfd and 11 <= i <= 17:
component_x += 0.007
label_x += 0.007
# Add device component with params.tagProps[0] set to this device's tag path
device_tag_props = [device_tags[i]] + ["value"] * 9
config["root"]["children"].append({
"meta": {"name": component_name},
"position": {"height": 0.1667, "width": 0.125, "x": component_x, "y": positions[i]["y"]},
"props": {
"params": {"tagProps": device_tag_props},
"path": f"Windows/Tabs/Enternet Windows/Components/{component_path}"
},
"type": "ia.display.view"
})
# Add device label
config["root"]["children"].append({
"meta": {"name": f"{component_name}_label"},
"position": {"height": 0.0358, "width": 0.0547 if is_vfd else 0.0427, "x": label_x, "y": label_positions[i]["y"]},
"props": {"text": f"{name} {ip}", "textStyle": {"fontSize": "1vmin", "key": "value"}},
"type": "ia.display.label"
})
# Add DPM label
config["root"]["children"].append({
"meta": {"name": "DPM_label"},
"position": {"height": 0.0694, "width": 0.101, "x": 0.5831, "y": 0.6342},
"props": {"text": dpm_label, "textStyle": {"fontSize": "2vmin"}},
"type": "ia.display.label"
})
return config
def parse_all_mcm_data(file_path):
"""Parse device mapping file containing multiple MCMs"""
all_mcms = {}
current_mcm = None
current_dpm = None
with open(file_path, 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
# Check if this is a device row (has tab or space separator with exactly 2 parts)
is_device_row = ('\t' in line and len(line.split('\t')) == 2) or (' ' in line and len(line.split(' ')) == 2)
if not is_device_row:
# Header row: either MCM name or DPM name
if line.upper().startswith('MCM'):
# New MCM section
current_mcm = line
all_mcms[current_mcm] = {}
current_dpm = None
else:
# DPM name
if current_mcm is None:
print(f"Warning: Found DPM '{line}' before any MCM header. Skipping.")
continue
current_dpm = line
all_mcms[current_mcm][current_dpm] = []
else:
# Device row
if current_mcm is None or current_dpm is None:
print(f"Warning: Found device '{line}' before MCM/DPM headers. Skipping.")
continue
parts = line.split('\t') if '\t' in line else line.split(' ')
all_mcms[current_mcm][current_dpm].append((parts[0].strip(), parts[1].strip()))
return all_mcms
def copy_files_to_dpm_folder(dpm_folder):
"""Copy resource.json and thumbnail.png to DPM folder"""
script_dir = os.path.dirname(os.path.abspath(__file__))
for file_name in ["resource.json", "thumbnail.png"]:
source = os.path.join(script_dir, file_name)
if os.path.exists(source):
shutil.copy2(source, os.path.join(dpm_folder, file_name))
def create_mcm_folders(all_mcms):
"""Create folders and configurations for all MCMs"""
total_mcms = len(all_mcms)
total_dpms = sum(len(dpms) for dpms in all_mcms.values())
total_devices = sum(len(devices) for mcm_dpms in all_mcms.values() for devices in mcm_dpms.values())
print(f"Processing {total_mcms} MCMs with {total_dpms} DPMs and {total_devices} total devices...")
print("=" * 70)
for mcm_name, dpms in all_mcms.items():
print(f"\nProcessing {mcm_name}...")
mcm_folder = mcm_name
os.makedirs(mcm_folder, exist_ok=True)
mcm_dpms = len(dpms)
mcm_devices = sum(len(devices) for devices in dpms.values())
print(f" {mcm_dpms} DPMs, {mcm_devices} devices")
for dpm_name, devices in dpms.items():
print(f" Processing {dpm_name} with {len(devices)} devices...")
dpm_folder = os.path.join(mcm_folder, dpm_name)
os.makedirs(dpm_folder, exist_ok=True)
try:
# Count device types and create config
num_vfds = sum(1 for name, _ in devices if '_VFD' in name)
num_fios = len(devices) - num_vfds
device_list = [f"{name} {ip}" for name, ip in devices]
config = generate_config(num_vfds, num_fios, device_list, dpm_name, mcm_name)
# Save view.json and copy files
with open(os.path.join(dpm_folder, "view.json"), 'w') as f:
json.dump(config, f, indent=2)
copy_files_to_dpm_folder(dpm_folder)
except Exception as e:
print(f" Error processing {dpm_name}: {e}")
print("\n" + "=" * 70)
print(f"Summary: Created {total_mcms} MCM folders with {total_dpms} DPM subfolders")
def main():
# Get the directory where this script is located
script_dir = os.path.dirname(os.path.abspath(__file__))
input_file = os.path.join(script_dir, "device_mapping.txt")
try:
if not os.path.exists(input_file):
print(f"Error: File '{input_file}' not found.")
print("Please ensure device_mapping.txt exists in the same directory as this script.")
return
print("Multi-MCM Device Mapping Script")
print("=" * 70)
print(f"Reading from: {input_file}")
all_mcms = parse_all_mcm_data(input_file)
if not all_mcms:
print("No MCM data found in the input file.")
return
create_mcm_folders(all_mcms)
print("Completed successfully! Check the MCM folders.")
except Exception as e:
print(f"An error occurred: {str(e)}")
if __name__ == "__main__":
main()