""" !!! CHANGE THE INPUT_FILE VARIABLE TO THE PATH OF THE TEXT FILE! LINE 17 !!! IF YOU WANT TO RUN THIS SCRIPT, YOU NEED TO HAVE A TXT FILE WITH THE FOLLOWING FORMAT: MCM_NAME DPM_NAME DEVICE_NAME1 DEVICE_IP1 DEVICE_NAME2 DEVICE_IP2 ... DEVICE_NAMEN DEVICE_IPN DPM_NAMEN ... """ import json import os import shutil # Get the directory where this script is located # input_file = r"C:\Users\your_username\Desktop\SCRIPTS\PLACE DPM DEVICES\device_mapping.txt" script_dir = os.path.dirname(os.path.abspath(__file__)) input_file = os.path.join(script_dir, "device_mapping.txt") mcm_folder_name = "MCM" 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_dpm_data(file_path): dpms = {} current_dpm = None mcm_name = None with open(file_path, 'r') as f: for line in f: line = line.strip() if not line: continue # A device row must have exactly 2 tokens (name and ip) separated by space or tab 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 (first) or DPM name if mcm_name is None and line.upper().startswith('MCM'): mcm_name = line continue current_dpm = line dpms[current_dpm] = [] else: if current_dpm is None: # If a device row appears before any DPM header, skip it safely continue parts = line.split('\t') if '\t' in line else line.split(' ') dpms[current_dpm].append((parts[0].strip(), parts[1].strip())) return dpms, mcm_name def copy_files_to_dpm_folder(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_dpm_folders(dpms, mcm_folder, mcm_name_for_tags=None): os.makedirs(mcm_folder, exist_ok=True) # Derive MCM name for tag paths from argument or the folder name if not mcm_name_for_tags: mcm_name_for_tags = os.path.basename(os.path.normpath(mcm_folder)) 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_for_tags) # 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}") def main(): try: if not input_file or not os.path.exists(input_file): print(f"Error: File '{input_file}' not found.") return dpms, parsed_mcm_name = parse_dpm_data(input_file) if not dpms: print("No DPM data found in the input file.") return # Use parsed MCM name if provided in the file; otherwise fall back to default folder name target_mcm_folder = parsed_mcm_name if parsed_mcm_name else mcm_folder_name print(f"Found {len(dpms)} DPMs. Creating MCM folder '{target_mcm_folder}'...") create_dpm_folders(dpms, target_mcm_folder, parsed_mcm_name) print("Completed successfully, check the MCM folder!") except Exception as e: print(f"An error occurred: {str(e)}") if __name__ == "__main__": main()