Enhance IO Extraction and Markdown Generation in x3.py
- Updated directory structure in readme.md to include project-specific files. - Refactored find_io_recursively to include module context for better IO address mapping. - Modified generate_markdown_tree to generate PLC-specific hardware trees and improved markdown formatting. - Added sanitization function for filenames to ensure valid output paths. - Improved logging to provide detailed execution flow and output paths. - Updated process_aml_file to extract and save global outputs, returning project data for further processing. - Enhanced overall error handling and output messages for clarity.
This commit is contained in:
parent
88ff4a25a2
commit
bf75f6d4d0
|
@ -8,6 +8,7 @@ __pycache__/
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
|
temp/
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
--- Log de Ejecución: x2.py ---
|
--- Log de Ejecución: x2.py ---
|
||||||
Grupo: ObtainIOFromProjectTia
|
Grupo: ObtainIOFromProjectTia
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
Inicio: 2025-05-05 12:39:16
|
Inicio: 2025-05-12 13:33:41
|
||||||
Fin: 2025-05-05 12:40:41
|
Fin: 2025-05-12 13:36:50
|
||||||
Duración: 0:01:25.846312
|
Duración: 0:03:09.036254
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
Estado: SUCCESS (Código de Salida: 0)
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||||
--- TIA Portal Project CAx Exporter and Analyzer ---
|
--- TIA Portal Project CAx Exporter and Analyzer ---
|
||||||
|
|
||||||
Selected Project: C:/Trabajo/SIDEL/06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)/InLavoro/PLC/SAE196_c0.2/SAE196_c0.2.ap18
|
Selected Project: C:/Trabajo/SIDEL/06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)/InLavoro/PLC/_NEW/SAE196_c0.2/SAE196_c0.2.ap18
|
||||||
Using Output Directory (Working Directory): C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
Using Output Directory (Working Directory): C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
Will export CAx data to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
Will export CAx data to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
||||||
Will generate summary to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Summary.md
|
Will generate summary to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Summary.md
|
||||||
Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.log
|
Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.log
|
||||||
|
|
||||||
Connecting to TIA Portal V18.0...
|
Connecting to TIA Portal V18.0...
|
||||||
2025-05-05 12:39:20,828 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
2025-05-12 13:34:08,368 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
||||||
2025-05-05 12:39:20,847 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface
|
2025-05-12 13:34:08,393 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface
|
||||||
Connected.
|
Connected.
|
||||||
Opening project: SAE196_c0.2.ap18...
|
Opening project: SAE196_c0.2.ap18...
|
||||||
2025-05-05 12:39:43,534 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal OpenProject - Open project... C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\InLavoro\PLC\SAE196_c0.2\SAE196_c0.2.ap18
|
2025-05-12 13:34:56,050 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal OpenProject - Open project... C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\InLavoro\PLC\_NEW\SAE196_c0.2\SAE196_c0.2.ap18
|
||||||
Project opened.
|
Project opened.
|
||||||
Exporting CAx data for the project to C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml...
|
Exporting CAx data for the project to C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml...
|
||||||
CAx data exported successfully.
|
CAx data exported successfully.
|
||||||
|
|
||||||
Closing TIA Portal...
|
Closing TIA Portal...
|
||||||
2025-05-05 12:40:38,187 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal ClosePortal - Close TIA Portal
|
2025-05-12 13:36:46,602 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal ClosePortal - Close TIA Portal
|
||||||
TIA Portal closed.
|
TIA Portal closed.
|
||||||
Parsing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
Parsing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
||||||
Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Summary.md
|
Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Summary.md
|
||||||
|
|
|
@ -1,46 +1,48 @@
|
||||||
--- Log de Ejecución: x3.py ---
|
--- Log de Ejecución: x3.py ---
|
||||||
Grupo: ObtainIOFromProjectTia
|
Grupo: ObtainIOFromProjectTia
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
Inicio: 2025-05-05 12:48:16
|
Inicio: 2025-05-12 13:56:30
|
||||||
Fin: 2025-05-05 12:48:22
|
Fin: 2025-05-12 13:56:34
|
||||||
Duración: 0:00:06.125698
|
Duración: 0:00:03.887946
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
Estado: SUCCESS (Código de Salida: 0)
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||||
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v28 - Working Directory Integration) ---
|
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v30 - Enhanced Module Info in Hardware Tree) ---
|
||||||
Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
||||||
Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
||||||
Output Main Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
|
||||||
Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
||||||
Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
||||||
Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
||||||
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
||||||
Identified Network: PROFIBUS_1 (17667a38-6bbe-481a-a234-6c9ac582adb9) Type: Profibus
|
Identified Network: PROFIBUS_1 (d645659a-3704-4cd6-b2c8-6165ceeed6ee) Type: Profibus
|
||||||
Identified Network: ETHERNET_1 (4fa8d8c4-4fb5-4df5-a82e-ec6829530c2e) Type: Ethernet/Profinet
|
Identified Network: ETHERNET_1 (f0b1c852-7dc9-4748-888e-34c60b519a75) Type: Ethernet/Profinet
|
||||||
Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
||||||
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
||||||
Found 118 InternalLink(s).
|
Found 116 InternalLink(s).
|
||||||
Mapping Device/Node 'E1' (NodeID:e15ed19e-b5e1-4cc2-9690-ee5b2132ed74, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
Mapping Device/Node 'E1' (NodeID:439930b8-1bbc-4cb2-a93b-2eed931f4b12, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
||||||
--> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
--> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
||||||
Mapping Device/Node 'P1' (NodeID:d9426769-3159-4c09-af54-e00a677183fd, Addr:1) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'P1' (NodeID:904bb0f7-df2d-4c1d-ab65-f45480449db1, Addr:1) to Network 'PROFIBUS_1'
|
||||||
--> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
--> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
||||||
Mapping Device/Node 'PB1' (NodeID:086deb3e-1f8a-471c-8d00-879d11991c6d, Addr:12) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:2784bae8-9807-475f-89bd-bcf44282f5f4, Addr:12) to Network 'PROFIBUS_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:7cf9f331-96fd-4a89-bf31-7faf501077cd, Addr:20) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:e9c5f60a-1da2-4c9b-979e-7d03a5b58a44, Addr:20) to Network 'PROFIBUS_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:d5a8a086-5c97-4c7d-b488-823e7d75370e, Addr:21) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:dd7201c2-e127-4a9d-b6ae-7a74a4ffe418, Addr:21) to Network 'PROFIBUS_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:e2893de8-90e6-42e6-9e83-7838a57f5038, Addr:22) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:d8825919-3a6c-4f95-aef0-62c782cfdb51, Addr:22) to Network 'PROFIBUS_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:af0f7eb6-720e-42c3-9a97-bf75183d0dc2, Addr:10) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:27d0e31d-46dc-4fdd-ab82-cfb91899a27c, Addr:10) to Network 'PROFIBUS_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:bfff87c4-b07c-441c-b977-58e967b96587, Addr:8) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:d91d5905-aa1a-485e-b4eb-8333cc2133c2, Addr:8) to Network 'PROFIBUS_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:69c65f28-7810-44e6-aae6-fcecb035f91b, Addr:40) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'PB1' (NodeID:0c5dfe06-786d-4ab6-b57c-8dfede56c2aa, Addr:40) to Network 'PROFIBUS_1'
|
||||||
Data extraction and structuring complete.
|
Data extraction and structuring complete.
|
||||||
Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
||||||
JSON data written successfully.
|
JSON data written successfully.
|
||||||
|
|
||||||
Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
|
||||||
|
|
||||||
IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
||||||
|
|
||||||
|
Found 1 PLC(s). Generating individual hardware trees...
|
||||||
|
Generating Hardware Tree for PLC 'PLC' (ID: a48e038f-0bcc-4b48-8373-033da316c62b) at: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
||||||
|
|
||||||
|
Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
||||||
|
|
||||||
Script finished.
|
Script finished.
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
--- ERRORES (STDERR) ---
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
### Directory structure
|
### Directory structure
|
||||||
|
|
||||||
<working_directory>/
|
<working_directory>/
|
||||||
|
├── <Project_Name>_CAx_Export.aml
|
||||||
├── <PLC1_Name>/
|
├── <PLC1_Name>/
|
||||||
│ ├── ProgramBlocks_XML/
|
│ ├── ProgramBlocks_XML/
|
||||||
│ │ └── ... (archivos XML de bloques)
|
│ │ └── ... (archivos XML de bloques)
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
│ └── xref_db_usage_summary.md
|
│ └── xref_db_usage_summary.md
|
||||||
│ └── xref_plc_tags_summary.md
|
│ └── xref_plc_tags_summary.md
|
||||||
│ └── full_project_representation.md
|
│ └── full_project_representation.md
|
||||||
│ └── SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
│ └── <Project_Name>_CAx_Export_Hardware_Tree.md
|
||||||
|
|
||||||
├── <PLC2_Name>/
|
├── <PLC2_Name>/
|
||||||
│ ├── ProgramBlocks_XML/
|
│ ├── ProgramBlocks_XML/
|
||||||
|
|
|
@ -396,387 +396,423 @@ def extract_aml_data(root):
|
||||||
|
|
||||||
|
|
||||||
# --- Helper Function for Recursive IO Search (Unchanged from v20) ---
|
# --- Helper Function for Recursive IO Search (Unchanged from v20) ---
|
||||||
def find_io_recursively(device_id, project_data):
|
def find_io_recursively(device_id, project_data, module_context):
|
||||||
"""Recursively finds all IO addresses under a given device ID."""
|
"""
|
||||||
|
Recursively finds all IO addresses under a given device ID, using module_context
|
||||||
|
for details of the main hardware module.
|
||||||
|
module_context = {"id": ..., "name": ..., "order_number": ..., "type_name": ...}
|
||||||
|
"""
|
||||||
io_list = []
|
io_list = []
|
||||||
device_info = project_data.get("devices", {}).get(device_id)
|
device_info = project_data.get("devices", {}).get(device_id)
|
||||||
if not device_info:
|
if not device_info:
|
||||||
return io_list
|
return io_list
|
||||||
|
|
||||||
if device_info.get("io_addresses"):
|
if device_info.get("io_addresses"):
|
||||||
|
# Slot position is from the current device_info (which holds the IO)
|
||||||
|
# It's often found in attributes.PositionNumber for sub-elements.
|
||||||
|
slot_pos = device_info.get("attributes", {}).get("PositionNumber", device_info.get("position", "N/A"))
|
||||||
|
|
||||||
for addr in device_info["io_addresses"]:
|
for addr in device_info["io_addresses"]:
|
||||||
io_list.append(
|
io_list.append(
|
||||||
{
|
{
|
||||||
"module_name": device_info.get("name", device_id),
|
"module_id": module_context["id"],
|
||||||
"module_pos": device_info.get("position", "N/A"),
|
"module_name": module_context["name"],
|
||||||
|
"module_pos": slot_pos, # Slot of the IO sub-element
|
||||||
|
"module_order_number": module_context["order_number"],
|
||||||
|
"module_type_name": module_context["type_name"],
|
||||||
**addr,
|
**addr,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
children_ids = device_info.get("children_ids", [])
|
children_ids = device_info.get("children_ids", [])
|
||||||
for child_id in children_ids:
|
for child_id in children_ids:
|
||||||
if child_id != device_id: # Basic loop prevention
|
if child_id != device_id: # Basic loop prevention
|
||||||
io_list.extend(find_io_recursively(child_id, project_data))
|
# The module_context remains the same as we recurse *within* a main module's structure
|
||||||
|
io_list.extend(find_io_recursively(child_id, project_data, module_context))
|
||||||
return io_list
|
return io_list
|
||||||
|
|
||||||
|
|
||||||
# --- generate_markdown_tree function (v26 - Final Cleaned Version) ---
|
# --- generate_markdown_tree function (v26 - Final Cleaned Version) ---
|
||||||
def generate_markdown_tree(project_data, md_file_path):
|
def generate_markdown_tree(project_data, md_file_path, target_plc_id):
|
||||||
"""(v26) Generates final hierarchical Markdown with aesthetic improvements."""
|
"""(v29 Mod) Generates hierarchical Markdown for a specific PLC."""
|
||||||
markdown_lines = ["# Project Hardware & IO Summary (Tree View v26)", ""]
|
|
||||||
|
plc_info = project_data.get("plcs", {}).get(target_plc_id)
|
||||||
|
plc_name_for_title = "Unknown PLC"
|
||||||
|
if plc_info:
|
||||||
|
plc_name_for_title = plc_info.get('name', target_plc_id)
|
||||||
|
|
||||||
if not project_data or not project_data.get("plcs"):
|
markdown_lines = [f"# Hardware & IO Summary for PLC: {plc_name_for_title}", ""]
|
||||||
markdown_lines.append("*No PLC identified in the project data.*")
|
|
||||||
|
if not plc_info:
|
||||||
|
markdown_lines.append(f"*Details for PLC ID '{target_plc_id}' not found in the project data.*")
|
||||||
try:
|
try:
|
||||||
with open(md_file_path, "w", encoding="utf-8") as f:
|
with open(md_file_path, "w", encoding="utf-8") as f:
|
||||||
f.write("\n".join(markdown_lines))
|
f.write("\n".join(markdown_lines))
|
||||||
print(f"\nMarkdown summary written to: {md_file_path}")
|
print(f"Markdown summary (PLC not found) written to: {md_file_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR writing Markdown file {md_file_path}: {e}")
|
print(f"ERROR writing Markdown file {md_file_path}: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
markdown_lines.append(f"Identified {len(project_data['plcs'])} PLC(s).")
|
# Content previously in the loop now directly uses plc_info and target_plc_id
|
||||||
|
markdown_lines.append(f"\n## PLC: {plc_info.get('name', target_plc_id)}")
|
||||||
|
type_name = plc_info.get("type_name", "N/A")
|
||||||
|
order_num = plc_info.get("order_number", "N/A")
|
||||||
|
firmware = plc_info.get("firmware_version", "N/A")
|
||||||
|
if type_name and type_name != "N/A":
|
||||||
|
markdown_lines.append(f"- **Type Name:** `{type_name}`")
|
||||||
|
if order_num and order_num != "N/A":
|
||||||
|
markdown_lines.append(f"- **Order Number:** `{order_num}`")
|
||||||
|
if firmware and firmware != "N/A":
|
||||||
|
markdown_lines.append(f"- **Firmware:** `{firmware}`")
|
||||||
|
|
||||||
for plc_id, plc_info in project_data.get("plcs", {}).items():
|
plc_networks = plc_info.get("connected_networks", {})
|
||||||
markdown_lines.append(f"\n## PLC: {plc_info.get('name', plc_id)}")
|
markdown_lines.append("\n- **Networks:**")
|
||||||
type_name = plc_info.get("type_name", "N/A")
|
if not plc_networks:
|
||||||
order_num = plc_info.get("order_number", "N/A")
|
markdown_lines.append(
|
||||||
firmware = plc_info.get("firmware_version", "N/A")
|
" - *No network connections found associated with this PLC object.*"
|
||||||
if type_name and type_name != "N/A":
|
)
|
||||||
markdown_lines.append(f"- **Type Name:** `{type_name}`")
|
else:
|
||||||
if order_num and order_num != "N/A":
|
sorted_network_items = sorted(
|
||||||
markdown_lines.append(f"- **Order Number:** `{order_num}`")
|
plc_networks.items(),
|
||||||
if firmware and firmware != "N/A":
|
key=lambda item: project_data.get("networks", {})
|
||||||
markdown_lines.append(f"- **Firmware:** `{firmware}`")
|
.get(item[0], {})
|
||||||
# ID removed
|
.get("name", item[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
for net_id, plc_addr_on_net in sorted_network_items:
|
||||||
|
net_info = project_data.get("networks", {}).get(net_id)
|
||||||
|
if not net_info:
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - !!! Error: Network info missing for ID {net_id} !!!"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
plc_networks = plc_info.get("connected_networks", {})
|
|
||||||
markdown_lines.append("\n- **Networks:**")
|
|
||||||
if not plc_networks:
|
|
||||||
markdown_lines.append(
|
markdown_lines.append(
|
||||||
" - *No network connections found associated with this PLC object.*"
|
f" - ### {net_info.get('name', net_id)} ({net_info.get('type', 'Unknown')})"
|
||||||
)
|
)
|
||||||
else:
|
markdown_lines.append(
|
||||||
sorted_network_items = sorted(
|
f" - PLC Address on this Net: `{plc_addr_on_net}`"
|
||||||
plc_networks.items(),
|
)
|
||||||
key=lambda item: project_data.get("networks", {})
|
markdown_lines.append(f" - **Devices on Network:**")
|
||||||
.get(item[0], {})
|
|
||||||
.get("name", item[0]),
|
devices_on_this_net = net_info.get("devices_on_net", {})
|
||||||
|
|
||||||
|
def sort_key(item):
|
||||||
|
node_id, node_addr = item
|
||||||
|
try:
|
||||||
|
parts = [int(p) for p in re.findall(r"\d+", node_addr)]
|
||||||
|
return parts
|
||||||
|
except:
|
||||||
|
return [float("inf")]
|
||||||
|
|
||||||
|
plc_interface_and_node_ids = set()
|
||||||
|
for node in plc_info.get("network_nodes", []):
|
||||||
|
plc_interface_and_node_ids.add(node["id"])
|
||||||
|
interface_id_lookup = (
|
||||||
|
project_data["devices"].get(node["id"], {}).get("parent_id")
|
||||||
|
)
|
||||||
|
if interface_id_lookup:
|
||||||
|
plc_interface_and_node_ids.add(interface_id_lookup)
|
||||||
|
plc_interface_and_node_ids.add(target_plc_id) # Use target_plc_id here
|
||||||
|
|
||||||
|
other_device_items = sorted(
|
||||||
|
[
|
||||||
|
(node_id, node_addr)
|
||||||
|
for node_id, node_addr in devices_on_this_net.items()
|
||||||
|
if node_id not in plc_interface_and_node_ids
|
||||||
|
],
|
||||||
|
key=sort_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
for net_id, plc_addr_on_net in sorted_network_items:
|
if not other_device_items:
|
||||||
net_info = project_data.get("networks", {}).get(net_id)
|
markdown_lines.append(" - *None (besides PLC interfaces)*")
|
||||||
if not net_info:
|
else:
|
||||||
markdown_lines.append(
|
# --- Display Logic with Sibling IO Aggregation & Aesthetics ---
|
||||||
f" - !!! Error: Network info missing for ID {net_id} !!!"
|
for node_id, node_addr in other_device_items:
|
||||||
)
|
node_info = project_data.get("devices", {}).get(node_id)
|
||||||
continue
|
if not node_info:
|
||||||
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - ### {net_info.get('name', net_id)} ({net_info.get('type', 'Unknown')})"
|
|
||||||
)
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - PLC Address on this Net: `{plc_addr_on_net}`"
|
|
||||||
)
|
|
||||||
markdown_lines.append(f" - **Devices on Network:**")
|
|
||||||
|
|
||||||
devices_on_this_net = net_info.get("devices_on_net", {})
|
|
||||||
|
|
||||||
def sort_key(item):
|
|
||||||
node_id, node_addr = item
|
|
||||||
try:
|
|
||||||
parts = [int(p) for p in re.findall(r"\d+", node_addr)]
|
|
||||||
return parts
|
|
||||||
except:
|
|
||||||
return [float("inf")]
|
|
||||||
|
|
||||||
plc_interface_and_node_ids = set()
|
|
||||||
for node in plc_info.get("network_nodes", []):
|
|
||||||
plc_interface_and_node_ids.add(node["id"])
|
|
||||||
interface_id = (
|
|
||||||
project_data["devices"].get(node["id"], {}).get("parent_id")
|
|
||||||
)
|
|
||||||
if interface_id:
|
|
||||||
plc_interface_and_node_ids.add(interface_id)
|
|
||||||
plc_interface_and_node_ids.add(plc_id)
|
|
||||||
|
|
||||||
other_device_items = sorted(
|
|
||||||
[
|
|
||||||
(node_id, node_addr)
|
|
||||||
for node_id, node_addr in devices_on_this_net.items()
|
|
||||||
if node_id not in plc_interface_and_node_ids
|
|
||||||
],
|
|
||||||
key=sort_key,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not other_device_items:
|
|
||||||
markdown_lines.append(" - *None (besides PLC interfaces)*")
|
|
||||||
else:
|
|
||||||
# --- Display Logic with Sibling IO Aggregation & Aesthetics ---
|
|
||||||
for node_id, node_addr in other_device_items:
|
|
||||||
node_info = project_data.get("devices", {}).get(node_id)
|
|
||||||
if not node_info:
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - !!! Error: Node info missing for ID {node_id} Addr: {node_addr} !!!"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
interface_id = node_info.get("parent_id")
|
|
||||||
interface_info = None
|
|
||||||
actual_device_id = None
|
|
||||||
actual_device_info = None
|
|
||||||
rack_id = None
|
|
||||||
rack_info = None
|
|
||||||
if interface_id:
|
|
||||||
interface_info = project_data.get("devices", {}).get(
|
|
||||||
interface_id
|
|
||||||
)
|
|
||||||
if interface_info:
|
|
||||||
actual_device_id = interface_info.get("parent_id")
|
|
||||||
if actual_device_id:
|
|
||||||
actual_device_info = project_data.get(
|
|
||||||
"devices", {}
|
|
||||||
).get(actual_device_id)
|
|
||||||
if actual_device_info:
|
|
||||||
potential_rack_id = actual_device_info.get(
|
|
||||||
"parent_id"
|
|
||||||
)
|
|
||||||
if potential_rack_id:
|
|
||||||
potential_rack_info = project_data.get(
|
|
||||||
"devices", {}
|
|
||||||
).get(potential_rack_id)
|
|
||||||
if potential_rack_info and (
|
|
||||||
"Rack"
|
|
||||||
in potential_rack_info.get("name", "")
|
|
||||||
or potential_rack_info.get("position")
|
|
||||||
is None
|
|
||||||
):
|
|
||||||
rack_id = potential_rack_id
|
|
||||||
rack_info = potential_rack_info
|
|
||||||
|
|
||||||
display_info_title = (
|
|
||||||
actual_device_info
|
|
||||||
if actual_device_info
|
|
||||||
else (interface_info if interface_info else node_info)
|
|
||||||
)
|
|
||||||
display_id_title = (
|
|
||||||
actual_device_id
|
|
||||||
if actual_device_info
|
|
||||||
else (interface_id if interface_info else node_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
io_search_root_id = (
|
|
||||||
actual_device_id
|
|
||||||
if actual_device_info
|
|
||||||
else (interface_id if interface_info else node_id)
|
|
||||||
)
|
|
||||||
io_search_root_info = project_data.get("devices", {}).get(
|
|
||||||
io_search_root_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# Construct Title
|
|
||||||
display_name = display_info_title.get("name", display_id_title)
|
|
||||||
via_node_name = node_info.get("name", node_id)
|
|
||||||
title_str = f"#### {display_name}"
|
|
||||||
if display_id_title != node_id:
|
|
||||||
title_str += f" (via {via_node_name} @ `{node_addr}`)"
|
|
||||||
else:
|
|
||||||
title_str += f" (@ `{node_addr}`)"
|
|
||||||
markdown_lines.append(f" - {title_str}")
|
|
||||||
|
|
||||||
# Display Basic Details
|
|
||||||
markdown_lines.append(
|
markdown_lines.append(
|
||||||
f" - Address (on net): `{node_addr}`"
|
f" - !!! Error: Node info missing for ID {node_id} Addr: {node_addr} !!!"
|
||||||
)
|
)
|
||||||
type_name_disp = display_info_title.get("type_name", "N/A")
|
continue
|
||||||
order_num_disp = display_info_title.get("order_number", "N/A")
|
|
||||||
pos_disp = display_info_title.get("position", "N/A")
|
|
||||||
if type_name_disp and type_name_disp != "N/A":
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - Type Name: `{type_name_disp}`"
|
|
||||||
)
|
|
||||||
if order_num_disp and order_num_disp != "N/A":
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - Order No: `{order_num_disp}`"
|
|
||||||
)
|
|
||||||
if pos_disp and pos_disp != "N/A":
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - Pos (in parent): `{pos_disp}`"
|
|
||||||
)
|
|
||||||
ultimate_parent_id = rack_id
|
|
||||||
if not ultimate_parent_id and actual_device_info:
|
|
||||||
ultimate_parent_id = actual_device_info.get("parent_id")
|
|
||||||
if (
|
|
||||||
ultimate_parent_id
|
|
||||||
and ultimate_parent_id != display_id_title
|
|
||||||
):
|
|
||||||
ultimate_parent_info = project_data.get("devices", {}).get(
|
|
||||||
ultimate_parent_id
|
|
||||||
)
|
|
||||||
ultimate_parent_name = (
|
|
||||||
ultimate_parent_info.get("name", "?")
|
|
||||||
if ultimate_parent_info
|
|
||||||
else "?"
|
|
||||||
)
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - Parent Structure: `{ultimate_parent_name}`"
|
|
||||||
) # Removed ID here
|
|
||||||
|
|
||||||
# --- IO Aggregation Logic (from v24) ---
|
interface_id = node_info.get("parent_id")
|
||||||
aggregated_io_addresses = []
|
interface_info_dev = None # Renamed to avoid conflict
|
||||||
parent_structure_id = (
|
actual_device_id = None
|
||||||
io_search_root_info.get("parent_id")
|
actual_device_info = None
|
||||||
if io_search_root_info
|
rack_id = None
|
||||||
else None
|
# rack_info = None # rack_info was not used
|
||||||
|
if interface_id:
|
||||||
|
interface_info_dev = project_data.get("devices", {}).get(
|
||||||
|
interface_id
|
||||||
)
|
)
|
||||||
io_search_root_name_disp = (
|
if interface_info_dev:
|
||||||
io_search_root_info.get("name", "?")
|
actual_device_id = interface_info_dev.get("parent_id")
|
||||||
if io_search_root_info
|
if actual_device_id:
|
||||||
|
actual_device_info = project_data.get(
|
||||||
|
"devices", {}
|
||||||
|
).get(actual_device_id)
|
||||||
|
if actual_device_info:
|
||||||
|
potential_rack_id = actual_device_info.get(
|
||||||
|
"parent_id"
|
||||||
|
)
|
||||||
|
if potential_rack_id:
|
||||||
|
potential_rack_info = project_data.get(
|
||||||
|
"devices", {}
|
||||||
|
).get(potential_rack_id)
|
||||||
|
if potential_rack_info and (
|
||||||
|
"Rack"
|
||||||
|
in potential_rack_info.get("name", "")
|
||||||
|
or potential_rack_info.get("position")
|
||||||
|
is None
|
||||||
|
):
|
||||||
|
rack_id = potential_rack_id
|
||||||
|
# rack_info = potential_rack_info # Not used
|
||||||
|
|
||||||
|
display_info_title = (
|
||||||
|
actual_device_info
|
||||||
|
if actual_device_info
|
||||||
|
else (interface_info_dev if interface_info_dev else node_info)
|
||||||
|
)
|
||||||
|
display_id_title = (
|
||||||
|
actual_device_id
|
||||||
|
if actual_device_info
|
||||||
|
else (interface_id if interface_info_dev else node_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
io_search_root_id = (
|
||||||
|
actual_device_id
|
||||||
|
if actual_device_info
|
||||||
|
else (interface_id if interface_info_dev else node_id)
|
||||||
|
)
|
||||||
|
io_search_root_info = project_data.get("devices", {}).get(
|
||||||
|
io_search_root_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct Title
|
||||||
|
display_name = display_info_title.get("name", display_id_title)
|
||||||
|
via_node_name = node_info.get("name", node_id)
|
||||||
|
title_str = f"#### {display_name}"
|
||||||
|
if display_id_title != node_id:
|
||||||
|
title_str += f" (via {via_node_name} @ `{node_addr}`)"
|
||||||
|
else:
|
||||||
|
title_str += f" (@ `{node_addr}`)"
|
||||||
|
markdown_lines.append(f" - {title_str}")
|
||||||
|
|
||||||
|
# Display Basic Details
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - Address (on net): `{node_addr}`"
|
||||||
|
)
|
||||||
|
type_name_disp = display_info_title.get("type_name", "N/A")
|
||||||
|
order_num_disp = display_info_title.get("order_number", "N/A")
|
||||||
|
pos_disp = display_info_title.get("position", "N/A")
|
||||||
|
if type_name_disp and type_name_disp != "N/A":
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - Type Name: `{type_name_disp}`"
|
||||||
|
)
|
||||||
|
if order_num_disp and order_num_disp != "N/A":
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - Order No: `{order_num_disp}`"
|
||||||
|
)
|
||||||
|
if pos_disp and pos_disp != "N/A":
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - Pos (in parent): `{pos_disp}`"
|
||||||
|
)
|
||||||
|
ultimate_parent_id = rack_id
|
||||||
|
if not ultimate_parent_id and actual_device_info:
|
||||||
|
ultimate_parent_id = actual_device_info.get("parent_id")
|
||||||
|
if (
|
||||||
|
ultimate_parent_id
|
||||||
|
and ultimate_parent_id != display_id_title
|
||||||
|
):
|
||||||
|
ultimate_parent_info = project_data.get("devices", {}).get(
|
||||||
|
ultimate_parent_id
|
||||||
|
)
|
||||||
|
ultimate_parent_name = (
|
||||||
|
ultimate_parent_info.get("name", "?")
|
||||||
|
if ultimate_parent_info
|
||||||
else "?"
|
else "?"
|
||||||
)
|
)
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - Parent Structure: `{ultimate_parent_name}`"
|
||||||
|
)
|
||||||
|
|
||||||
if parent_structure_id:
|
# --- IO Aggregation Logic (from v24) ---
|
||||||
parent_structure_info = project_data.get("devices", {}).get(
|
aggregated_io_addresses = []
|
||||||
parent_structure_id
|
parent_structure_id = (
|
||||||
)
|
io_search_root_info.get("parent_id")
|
||||||
parent_structure_name = (
|
if io_search_root_info
|
||||||
parent_structure_info.get("name", "?")
|
else None
|
||||||
if parent_structure_info
|
)
|
||||||
else "?"
|
io_search_root_name_disp = (
|
||||||
)
|
io_search_root_info.get("name", "?")
|
||||||
search_title = f"parent '{parent_structure_name}'"
|
if io_search_root_info
|
||||||
sibling_found_io = False
|
else "?"
|
||||||
for dev_scan_id, dev_scan_info in project_data.get(
|
)
|
||||||
"devices", {}
|
|
||||||
).items():
|
if parent_structure_id:
|
||||||
if (
|
parent_structure_info = project_data.get("devices", {}).get(
|
||||||
dev_scan_info.get("parent_id")
|
parent_structure_id
|
||||||
== parent_structure_id
|
)
|
||||||
):
|
parent_structure_name = (
|
||||||
io_from_sibling = find_io_recursively(
|
parent_structure_info.get("name", "?")
|
||||||
dev_scan_id, project_data
|
if parent_structure_info
|
||||||
)
|
else "?"
|
||||||
if io_from_sibling:
|
)
|
||||||
aggregated_io_addresses.extend(io_from_sibling)
|
search_title = f"parent '{parent_structure_name}'"
|
||||||
sibling_found_io = True
|
sibling_found_io = False
|
||||||
|
for dev_scan_id, dev_scan_info in project_data.get(
|
||||||
|
"devices", {}
|
||||||
|
).items():
|
||||||
if (
|
if (
|
||||||
not sibling_found_io and not aggregated_io_addresses
|
dev_scan_info.get("parent_id") == parent_structure_id
|
||||||
): # Only show message if list still empty
|
):
|
||||||
markdown_lines.append(
|
# This dev_scan_info is the module
|
||||||
f" - *No IO Addresses found in modules under {search_title} (ID: {parent_structure_id}).*"
|
module_context_for_sibling = {
|
||||||
|
"id": dev_scan_id,
|
||||||
|
"name": dev_scan_info.get("name", dev_scan_id),
|
||||||
|
"order_number": dev_scan_info.get("order_number", "N/A"),
|
||||||
|
"type_name": dev_scan_info.get("type_name", "N/A")
|
||||||
|
}
|
||||||
|
io_from_sibling = find_io_recursively(
|
||||||
|
dev_scan_id, project_data, module_context_for_sibling
|
||||||
)
|
)
|
||||||
|
if io_from_sibling:
|
||||||
elif io_search_root_id:
|
aggregated_io_addresses.extend(io_from_sibling)
|
||||||
search_title = f"'{io_search_root_name_disp}'"
|
sibling_found_io = True
|
||||||
aggregated_io_addresses = find_io_recursively(
|
if (
|
||||||
io_search_root_id, project_data
|
not sibling_found_io and not aggregated_io_addresses
|
||||||
)
|
): # Only show message if list still empty
|
||||||
if not aggregated_io_addresses:
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - *No IO Addresses found in modules under {search_title} (ID: {io_search_root_id}).*"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
markdown_lines.append(
|
markdown_lines.append(
|
||||||
f" - *Could not determine structure to search for IO addresses.*"
|
f" - *No IO Addresses found in modules under {search_title} (ID: {parent_structure_id}).*"
|
||||||
)
|
)
|
||||||
# --- End IO Aggregation ---
|
|
||||||
|
|
||||||
# Display aggregated IO Addresses with Siemens format (Cleaned)
|
elif io_search_root_id:
|
||||||
if aggregated_io_addresses:
|
search_title = f"'{io_search_root_name_disp}'"
|
||||||
|
module_context_for_root = {
|
||||||
|
"id": io_search_root_id,
|
||||||
|
"name": io_search_root_info.get("name", io_search_root_id),
|
||||||
|
"order_number": io_search_root_info.get("order_number", "N/A"),
|
||||||
|
"type_name": io_search_root_info.get("type_name", "N/A")
|
||||||
|
}
|
||||||
|
aggregated_io_addresses = find_io_recursively(
|
||||||
|
io_search_root_id, project_data, module_context_for_root
|
||||||
|
)
|
||||||
|
if not aggregated_io_addresses:
|
||||||
markdown_lines.append(
|
markdown_lines.append(
|
||||||
f" - **IO Addresses (Aggregated from Structure):**"
|
f" - *No IO Addresses found in modules under {search_title} (ID: {io_search_root_id}).*"
|
||||||
) # Removed redundant search root name
|
)
|
||||||
sorted_agg_io = sorted(
|
else:
|
||||||
aggregated_io_addresses,
|
markdown_lines.append(
|
||||||
key=lambda x: (
|
f" - *Could not determine structure to search for IO addresses.*"
|
||||||
(
|
)
|
||||||
int(x.get("module_pos", "9999"))
|
# --- End IO Aggregation ---
|
||||||
if x.get("module_pos", "9999").isdigit()
|
|
||||||
else 9999
|
# Display aggregated IO Addresses with Siemens format (Cleaned)
|
||||||
),
|
if aggregated_io_addresses:
|
||||||
x.get("module_name", ""),
|
markdown_lines.append(
|
||||||
x.get("type", ""),
|
f" - **IO Addresses (Aggregated from Structure):**"
|
||||||
(
|
)
|
||||||
int(x.get("start", "0"))
|
sorted_agg_io = sorted(
|
||||||
if x.get("start", "0").isdigit()
|
aggregated_io_addresses,
|
||||||
else float("inf")
|
key=lambda x: (
|
||||||
),
|
(
|
||||||
|
int(x.get("module_pos", "9999"))
|
||||||
|
if str(x.get("module_pos", "9999")).isdigit() # Ensure it's a string before isdigit
|
||||||
|
else 9999
|
||||||
),
|
),
|
||||||
)
|
x.get("module_name", ""),
|
||||||
last_module_id_key = None
|
x.get("type", ""),
|
||||||
for addr_info in sorted_agg_io:
|
(
|
||||||
current_module_id_key = (
|
int(x.get("start", "0"))
|
||||||
addr_info.get("module_name", "?"),
|
if str(x.get("start", "0")).isdigit() # Ensure it's a string
|
||||||
addr_info.get("module_pos", "?"),
|
else float("inf")
|
||||||
)
|
),
|
||||||
if current_module_id_key != last_module_id_key:
|
),
|
||||||
markdown_lines.append(
|
|
||||||
f" - **From Module:** {addr_info.get('module_name','?')} (Pos: {addr_info.get('module_pos','?')})"
|
|
||||||
)
|
|
||||||
last_module_id_key = current_module_id_key
|
|
||||||
|
|
||||||
# --- Siemens IO Formatting (from v25.1 - keep fixes) ---
|
|
||||||
io_type = addr_info.get("type", "?")
|
|
||||||
start_str = addr_info.get("start", "?")
|
|
||||||
length_str = addr_info.get("length", "?")
|
|
||||||
area_str = addr_info.get("area", "?")
|
|
||||||
siemens_addr = f"FMT_ERROR" # Default error
|
|
||||||
length_bits = 0
|
|
||||||
try:
|
|
||||||
start_byte = int(start_str)
|
|
||||||
length_bits = int(length_str)
|
|
||||||
length_bytes = math.ceil(
|
|
||||||
length_bits / 8.0
|
|
||||||
) # Use float division
|
|
||||||
if length_bits > 0 and length_bytes == 0:
|
|
||||||
length_bytes = 1 # Handle len < 8 bits
|
|
||||||
end_byte = start_byte + length_bytes - 1
|
|
||||||
prefix = "P?"
|
|
||||||
if io_type.lower() == "input":
|
|
||||||
prefix = "EW"
|
|
||||||
elif io_type.lower() == "output":
|
|
||||||
prefix = "AW"
|
|
||||||
siemens_addr = f"{prefix} {start_byte}..{end_byte}"
|
|
||||||
except Exception: # Catch any error during calc/format
|
|
||||||
siemens_addr = (
|
|
||||||
f"FMT_ERROR({start_str},{length_str})"
|
|
||||||
)
|
|
||||||
|
|
||||||
markdown_lines.append(
|
|
||||||
f" - `{siemens_addr}` (Len={length_bits} bits)" # Simplified output
|
|
||||||
)
|
|
||||||
# --- End Siemens IO Formatting ---
|
|
||||||
|
|
||||||
# IO Connections logic remains the same...
|
|
||||||
links_from = project_data.get("links_by_source", {}).get(
|
|
||||||
display_id_title, []
|
|
||||||
)
|
)
|
||||||
links_to = project_data.get("links_by_target", {}).get(
|
last_module_id_processed = None # Use the actual module ID for grouping
|
||||||
display_id_title, []
|
for addr_info in sorted_agg_io:
|
||||||
)
|
current_module_id_for_grouping = addr_info.get("module_id")
|
||||||
io_conns = []
|
if current_module_id_for_grouping != last_module_id_processed:
|
||||||
for link in links_from:
|
module_name_disp = addr_info.get('module_name','?')
|
||||||
if "channel" in link["source_suffix"].lower():
|
module_type_name_disp = addr_info.get('module_type_name', 'N/A')
|
||||||
target_str = f"{link.get('target_device_name', link['target_id'])}:{link['target_suffix']}"
|
module_order_num_disp = addr_info.get('module_order_number', 'N/A')
|
||||||
if link["target_id"] == display_id_title:
|
|
||||||
target_str = link["target_suffix"]
|
module_line_parts = [f"**{module_name_disp}**"]
|
||||||
io_conns.append(
|
if module_type_name_disp and module_type_name_disp != 'N/A':
|
||||||
f"`{link['source_suffix']}` → `{target_str}`"
|
module_line_parts.append(f"Type: `{module_type_name_disp}`")
|
||||||
|
if module_order_num_disp and module_order_num_disp != 'N/A':
|
||||||
|
module_line_parts.append(f"OrderNo: `{module_order_num_disp}`")
|
||||||
|
|
||||||
|
# Removed (Pos: ...) from this line as requested
|
||||||
|
markdown_lines.append(f" - {', '.join(module_line_parts)}")
|
||||||
|
last_module_id_processed = current_module_id_for_grouping
|
||||||
|
|
||||||
|
|
||||||
|
# --- Siemens IO Formatting (from v25.1 - keep fixes) ---
|
||||||
|
io_type = addr_info.get("type", "?")
|
||||||
|
start_str = addr_info.get("start", "?")
|
||||||
|
length_str = addr_info.get("length", "?")
|
||||||
|
# area_str = addr_info.get("area", "?") # Not used in final output string
|
||||||
|
siemens_addr = f"FMT_ERROR" # Default error
|
||||||
|
length_bits = 0
|
||||||
|
try:
|
||||||
|
start_byte = int(start_str)
|
||||||
|
length_bits = int(length_str)
|
||||||
|
length_bytes = math.ceil(
|
||||||
|
length_bits / 8.0
|
||||||
|
) # Use float division
|
||||||
|
if length_bits > 0 and length_bytes == 0:
|
||||||
|
length_bytes = 1 # Handle len < 8 bits
|
||||||
|
end_byte = start_byte + length_bytes - 1
|
||||||
|
prefix = "P?"
|
||||||
|
if io_type.lower() == "input":
|
||||||
|
prefix = "EW"
|
||||||
|
elif io_type.lower() == "output":
|
||||||
|
prefix = "AW"
|
||||||
|
siemens_addr = f"{prefix} {start_byte}..{end_byte}"
|
||||||
|
except Exception:
|
||||||
|
siemens_addr = (
|
||||||
|
f"FMT_ERROR({start_str},{length_str})"
|
||||||
)
|
)
|
||||||
for link in links_to:
|
|
||||||
if "channel" in link["target_suffix"].lower():
|
|
||||||
source_str = f"{link.get('source_device_name', link['source_id'])}:{link['source_suffix']}"
|
|
||||||
if link["source_id"] == display_id_title:
|
|
||||||
source_str = link["source_suffix"]
|
|
||||||
io_conns.append(
|
|
||||||
f"`{source_str}` → `{link['target_suffix']}`"
|
|
||||||
)
|
|
||||||
if io_conns:
|
|
||||||
markdown_lines.append(
|
markdown_lines.append(
|
||||||
f" - **IO Connections (Channels):**"
|
f" - `{siemens_addr}` (Len={length_bits} bits)"
|
||||||
)
|
)
|
||||||
for conn in sorted(list(set(io_conns))):
|
# --- End Siemens IO Formatting ---
|
||||||
markdown_lines.append(f" - {conn}")
|
|
||||||
markdown_lines.append("") # Spacing
|
# IO Connections logic
|
||||||
# --- *** END Display Logic *** ---
|
links_from = project_data.get("links_by_source", {}).get(
|
||||||
|
display_id_title, []
|
||||||
|
)
|
||||||
|
links_to = project_data.get("links_by_target", {}).get(
|
||||||
|
display_id_title, []
|
||||||
|
)
|
||||||
|
io_conns = []
|
||||||
|
for link in links_from:
|
||||||
|
if "channel" in link["source_suffix"].lower():
|
||||||
|
target_str = f"{link.get('target_device_name', link['target_id'])}:{link['target_suffix']}"
|
||||||
|
if link["target_id"] == display_id_title:
|
||||||
|
target_str = link["target_suffix"]
|
||||||
|
io_conns.append(
|
||||||
|
f"`{link['source_suffix']}` → `{target_str}`"
|
||||||
|
)
|
||||||
|
for link in links_to:
|
||||||
|
if "channel" in link["target_suffix"].lower():
|
||||||
|
source_str = f"{link.get('source_device_name', link['source_id'])}:{link['source_suffix']}"
|
||||||
|
if link["source_id"] == display_id_title:
|
||||||
|
source_str = link["source_suffix"]
|
||||||
|
io_conns.append(
|
||||||
|
f"`{source_str}` → `{link['target_suffix']}`"
|
||||||
|
)
|
||||||
|
if io_conns:
|
||||||
|
markdown_lines.append(
|
||||||
|
f" - **IO Connections (Channels):**"
|
||||||
|
)
|
||||||
|
for conn in sorted(list(set(io_conns))):
|
||||||
|
markdown_lines.append(f" - {conn}")
|
||||||
|
markdown_lines.append("") # Spacing
|
||||||
|
# --- *** END Display Logic *** ---
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(md_file_path, "w", encoding="utf-8") as f:
|
with open(md_file_path, "w", encoding="utf-8") as f:
|
||||||
|
@ -923,10 +959,9 @@ def generate_io_upward_tree(project_data, md_file_path):
|
||||||
print(f"ERROR writing IO upward debug tree file {md_file_path}: {e}")
|
print(f"ERROR writing IO upward debug tree file {md_file_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
# --- process_aml_file function (unchanged from v22) ---
|
# --- extract_and_save_global_outputs function (Refactored from process_aml_file) ---
|
||||||
def process_aml_file(
|
def extract_and_save_global_outputs(aml_file_path, json_output_path, md_upward_output_path):
|
||||||
aml_file_path, json_output_path, md_output_path, md_upward_output_path
|
"""Extracts data from AML, saves global JSON and IO upward tree, returns project_data."""
|
||||||
):
|
|
||||||
# (Unchanged)
|
# (Unchanged)
|
||||||
print(f"Processing AML file: {aml_file_path}")
|
print(f"Processing AML file: {aml_file_path}")
|
||||||
if not os.path.exists(aml_file_path):
|
if not os.path.exists(aml_file_path):
|
||||||
|
@ -945,16 +980,20 @@ def process_aml_file(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR writing JSON file {json_output_path}: {e}")
|
print(f"ERROR writing JSON file {json_output_path}: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
generate_markdown_tree(project_data, md_output_path) # v26 MD generation
|
|
||||||
|
# Generate and save the IO upward tree (global)
|
||||||
generate_io_upward_tree(
|
generate_io_upward_tree(
|
||||||
project_data, md_upward_output_path
|
project_data, md_upward_output_path
|
||||||
) # v23 upward generation
|
)
|
||||||
|
return project_data
|
||||||
except ET.LxmlError as xml_err:
|
except ET.LxmlError as xml_err:
|
||||||
print(f"ERROR parsing XML file {aml_file_path} with lxml: {xml_err}")
|
print(f"ERROR parsing XML file {aml_file_path} with lxml: {xml_err}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR processing AML file {aml_file_path}: {e}")
|
print(f"ERROR processing AML file {aml_file_path}: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def select_cax_file(initial_dir=None): # Add initial_dir parameter
|
def select_cax_file(initial_dir=None): # Add initial_dir parameter
|
||||||
|
@ -987,12 +1026,20 @@ def select_output_directory():
|
||||||
return dir_path
|
return dir_path
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(name):
|
||||||
|
"""Sanitizes a string to be used as a valid filename or directory name."""
|
||||||
|
name = str(name) # Ensure it's a string
|
||||||
|
name = re.sub(r'[<>:"/\\|?*]', '_', name) # Replace forbidden characters
|
||||||
|
name = re.sub(r'\s+', '_', name) # Replace multiple whitespace with single underscore
|
||||||
|
name = name.strip('._') # Remove leading/trailing dots or underscores
|
||||||
|
return name if name else "Unnamed_Device"
|
||||||
|
|
||||||
# --- Main Execution ---
|
# --- Main Execution ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
configs = load_configuration()
|
configs = load_configuration()
|
||||||
working_directory = configs.get("working_directory")
|
working_directory = configs.get("working_directory")
|
||||||
|
|
||||||
script_version = "v28 - Working Directory Integration" # Updated version
|
script_version = "v30 - Enhanced Module Info in Hardware Tree"
|
||||||
print(
|
print(
|
||||||
f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---"
|
f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---"
|
||||||
)
|
)
|
||||||
|
@ -1031,21 +1078,40 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Construct output file paths within the selected output directory (working_directory)
|
# Construct output file paths within the selected output directory (working_directory)
|
||||||
output_json_file = output_path / input_path.with_suffix(".hierarchical.json").name
|
output_json_file = output_path / input_path.with_suffix(".hierarchical.json").name
|
||||||
output_md_file = output_path / input_path.with_name(f"{input_path.stem}_Hardware_Tree.md") # Simplified name
|
# Hardware tree MD name is now PLC-specific and handled below
|
||||||
output_md_upward_file = output_path / input_path.with_name(f"{input_path.stem}_IO_Upward_Debug.md") # Simplified name
|
output_md_upward_file = output_path / input_path.with_name(f"{input_path.stem}_IO_Upward_Debug.md")
|
||||||
|
|
||||||
print(f"Input AML: {input_path.resolve()}")
|
print(f"Input AML: {input_path.resolve()}")
|
||||||
print(f"Output Directory: {output_path.resolve()}")
|
print(f"Output Directory: {output_path.resolve()}")
|
||||||
print(f"Output JSON: {output_json_file.resolve()}")
|
print(f"Output JSON: {output_json_file.resolve()}")
|
||||||
print(f"Output Main Tree MD: {output_md_file.resolve()}")
|
|
||||||
print(f"Output IO Debug Tree MD: {output_md_upward_file.resolve()}")
|
print(f"Output IO Debug Tree MD: {output_md_upward_file.resolve()}")
|
||||||
|
|
||||||
# Process the selected file and save outputs to the selected directory
|
# Process the AML file to get project_data and save global files
|
||||||
process_aml_file(
|
project_data = extract_and_save_global_outputs(
|
||||||
str(input_path),
|
str(input_path),
|
||||||
str(output_json_file),
|
str(output_json_file),
|
||||||
str(output_md_file),
|
|
||||||
str(output_md_upward_file),
|
str(output_md_upward_file),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if project_data:
|
||||||
|
# Now, generate the hardware tree per PLC
|
||||||
|
if not project_data.get("plcs"):
|
||||||
|
print("\nNo PLCs found in the project data. Cannot generate PLC-specific hardware trees.")
|
||||||
|
else:
|
||||||
|
print(f"\nFound {len(project_data['plcs'])} PLC(s). Generating individual hardware trees...")
|
||||||
|
for plc_id, plc_data_for_plc in project_data.get("plcs", {}).items():
|
||||||
|
plc_name_original = plc_data_for_plc.get('name', plc_id)
|
||||||
|
plc_name_sanitized = sanitize_filename(plc_name_original)
|
||||||
|
|
||||||
|
plc_doc_dir = output_path / plc_name_sanitized / "Documentation"
|
||||||
|
plc_doc_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
hardware_tree_md_filename = f"{input_path.stem}_Hardware_Tree.md"
|
||||||
|
output_plc_md_file = plc_doc_dir / hardware_tree_md_filename
|
||||||
|
|
||||||
|
print(f" Generating Hardware Tree for PLC '{plc_name_original}' (ID: {plc_id}) at: {output_plc_md_file.resolve()}")
|
||||||
|
generate_markdown_tree(project_data, str(output_plc_md_file), plc_id)
|
||||||
|
else:
|
||||||
|
print("\nFailed to process AML data. Halting before generating PLC-specific trees.")
|
||||||
|
|
||||||
print("\nScript finished.")
|
print("\nScript finished.")
|
57
data/log.txt
57
data/log.txt
|
@ -1,21 +1,36 @@
|
||||||
[16:58:28] Iniciando ejecución de x1.py en C:\Trabajo\SIDEL\10 - E5.007095 - Modifica O&U - SAE463\Reporte\Email...
|
[13:56:30] Iniciando ejecución de x3.py en C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport...
|
||||||
[16:58:29] Working directory: C:\Trabajo\SIDEL\10 - E5.007095 - Modifica O&U - SAE463\Reporte\Email
|
[13:56:30] --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v30 - Enhanced Module Info in Hardware Tree) ---
|
||||||
[16:58:29] Input directory: C:\Trabajo\SIDEL\10 - E5.007095 - Modifica O&U - SAE463\Reporte\Email
|
[13:56:30] Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
[16:58:29] Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/10 - E5.007095 - Modifica O&U - SAE463
|
[13:56:34] Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
||||||
[16:58:29] Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/10 - E5.007095 - Modifica O&U - SAE463\cronologia.md
|
[13:56:34] Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
||||||
[16:58:29] Attachments directory: C:\Trabajo\SIDEL\10 - E5.007095 - Modifica O&U - SAE463\Reporte\Email\adjuntos
|
[13:56:34] Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
||||||
[16:58:29] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
[13:56:34] Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
||||||
[16:58:29] Found 1 .eml files
|
[13:56:34] Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
||||||
[16:58:29] Loaded 0 existing messages
|
[13:56:34] Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
||||||
[16:58:29] Processing C:\Trabajo\SIDEL\10 - E5.007095 - Modifica O&U - SAE463\Reporte\Email\R_ {EXT} E5.006894 - Modifica O&U - SAE463 New Analyzer.eml
|
[13:56:34] Pass 2: Identifying PLCs and Networks (Refined v2)...
|
||||||
[16:58:29] Aplicando reglas de prioridad 1
|
[13:56:34] Identified Network: PROFIBUS_1 (d645659a-3704-4cd6-b2c8-6165ceeed6ee) Type: Profibus
|
||||||
[16:58:29] Aplicando reglas de prioridad 2
|
[13:56:34] Identified Network: ETHERNET_1 (f0b1c852-7dc9-4748-888e-34c60b519a75) Type: Ethernet/Profinet
|
||||||
[16:58:29] Aplicando reglas de prioridad 3
|
[13:56:34] Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
||||||
[16:58:29] Aplicando reglas de prioridad 4
|
[13:56:34] Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
||||||
[16:58:29] Estadísticas de procesamiento:
|
[13:56:34] Found 116 InternalLink(s).
|
||||||
[16:58:29] - Total mensajes encontrados: 1
|
[13:56:34] Mapping Device/Node 'E1' (NodeID:439930b8-1bbc-4cb2-a93b-2eed931f4b12, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
||||||
[16:58:29] - Mensajes únicos añadidos: 1
|
[13:56:34] --> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
||||||
[16:58:29] - Mensajes duplicados ignorados: 0
|
[13:56:34] Mapping Device/Node 'P1' (NodeID:904bb0f7-df2d-4c1d-ab65-f45480449db1, Addr:1) to Network 'PROFIBUS_1'
|
||||||
[16:58:29] Writing 1 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/10 - E5.007095 - Modifica O&U - SAE463\cronologia.md
|
[13:56:34] --> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
||||||
[16:58:29] Ejecución de x1.py finalizada (success). Duración: 0:00:00.434600.
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:2784bae8-9807-475f-89bd-bcf44282f5f4, Addr:12) to Network 'PROFIBUS_1'
|
||||||
[16:58:29] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\log_x1.txt
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:e9c5f60a-1da2-4c9b-979e-7d03a5b58a44, Addr:20) to Network 'PROFIBUS_1'
|
||||||
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:dd7201c2-e127-4a9d-b6ae-7a74a4ffe418, Addr:21) to Network 'PROFIBUS_1'
|
||||||
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:d8825919-3a6c-4f95-aef0-62c782cfdb51, Addr:22) to Network 'PROFIBUS_1'
|
||||||
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:27d0e31d-46dc-4fdd-ab82-cfb91899a27c, Addr:10) to Network 'PROFIBUS_1'
|
||||||
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:d91d5905-aa1a-485e-b4eb-8333cc2133c2, Addr:8) to Network 'PROFIBUS_1'
|
||||||
|
[13:56:34] Mapping Device/Node 'PB1' (NodeID:0c5dfe06-786d-4ab6-b57c-8dfede56c2aa, Addr:40) to Network 'PROFIBUS_1'
|
||||||
|
[13:56:34] Data extraction and structuring complete.
|
||||||
|
[13:56:34] Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
||||||
|
[13:56:34] JSON data written successfully.
|
||||||
|
[13:56:34] IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
||||||
|
[13:56:34] Found 1 PLC(s). Generating individual hardware trees...
|
||||||
|
[13:56:34] Generating Hardware Tree for PLC 'PLC' (ID: a48e038f-0bcc-4b48-8373-033da316c62b) at: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
||||||
|
[13:56:34] Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
||||||
|
[13:56:34] Script finished.
|
||||||
|
[13:56:34] Ejecución de x3.py finalizada (success). Duración: 0:00:03.887946.
|
||||||
|
[13:56:34] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\log_x3.txt
|
||||||
|
|
Loading…
Reference in New Issue