Compare commits
4 Commits
ea35ae1211
...
be3b333491
Author | SHA1 | Date |
---|---|---|
|
be3b333491 | |
|
9ac769e2fc | |
|
88806ee4e4 | |
|
71a2a63de4 |
|
@ -28,6 +28,7 @@ Esta guía detalla la implementación de un nuevo launcher para scripts Python c
|
|||
- **Reutilización**: Aprovechar componentes existentes (logging, UI base, etc.)
|
||||
- **Modularidad**: Código separado y mantenible
|
||||
- **Flexibilidad**: Configuración manual de directorios y categorías
|
||||
- Mecanismo para seleccionar el entorno miniconda a utlizar para cada grupo de scripts.
|
||||
|
||||
### Diferencias con Sistema Actual
|
||||
|
||||
|
|
358
app.py
358
app.py
|
@ -8,6 +8,7 @@ from datetime import datetime
|
|||
import time # Added for shutdown delay
|
||||
import sys # Added for platform detection
|
||||
import subprocess # Add this to the imports at the top
|
||||
import shutil # For shutil.whichimport os
|
||||
|
||||
# --- Imports for System Tray Icon ---
|
||||
import threading
|
||||
|
@ -566,6 +567,41 @@ def get_launcher_scripts(group_id):
|
|||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-scripts-all/<group_id>")
|
||||
def get_all_launcher_scripts(group_id):
|
||||
"""Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gestión"""
|
||||
try:
|
||||
scripts = launcher_manager.get_all_group_scripts(group_id)
|
||||
return jsonify(scripts)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-script-metadata/<group_id>/<script_name>", methods=["GET", "POST"])
|
||||
def handle_launcher_script_metadata(group_id, script_name):
|
||||
"""Gestionar metadatos de un script específico"""
|
||||
if request.method == "GET":
|
||||
try:
|
||||
metadata = launcher_manager.get_script_metadata(group_id, script_name)
|
||||
return jsonify(metadata)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
else: # POST
|
||||
try:
|
||||
data = request.json
|
||||
result = launcher_manager.update_script_metadata(group_id, script_name, data)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/python-environments")
|
||||
def get_python_environments():
|
||||
"""Obtener entornos de Python disponibles"""
|
||||
try:
|
||||
envs = launcher_manager.get_available_python_envs()
|
||||
return jsonify(envs)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/execute-gui-script", methods=["POST"])
|
||||
def execute_gui_script():
|
||||
"""Ejecutar script GUI con argumentos opcionales"""
|
||||
|
@ -574,9 +610,11 @@ def execute_gui_script():
|
|||
group_id = data["group_id"]
|
||||
script_name = data["script_name"]
|
||||
script_args = data.get("args", [])
|
||||
working_dir = data.get("working_dir", None)
|
||||
use_pythonw = data.get("use_pythonw", False) # Por defecto python.exe para logging
|
||||
|
||||
result = launcher_manager.execute_gui_script(
|
||||
group_id, script_name, script_args, broadcast_message
|
||||
group_id, script_name, script_args, broadcast_message, working_dir, use_pythonw
|
||||
)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
|
@ -655,8 +693,326 @@ def get_group_icon(launcher_type, group_id):
|
|||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# Nuevas APIs para gestión de procesos y Markdown
|
||||
|
||||
@app.route("/api/launcher-process-focus/<int:pid>", methods=["POST"])
|
||||
def focus_launcher_process(pid):
|
||||
"""Activar foco de un proceso"""
|
||||
try:
|
||||
result = launcher_manager.focus_process(pid)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-process-terminate/<int:pid>", methods=["POST"])
|
||||
def terminate_launcher_process(pid):
|
||||
"""Cerrar un proceso"""
|
||||
try:
|
||||
result = launcher_manager.terminate_process(pid)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-running-processes")
|
||||
def get_launcher_running_processes():
|
||||
"""Obtener procesos en ejecución"""
|
||||
try:
|
||||
processes = launcher_manager.get_running_processes()
|
||||
return jsonify({"processes": processes})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-open-vscode/<group_id>", methods=["POST"])
|
||||
def open_launcher_group_in_vscode(group_id):
|
||||
"""Abrir grupo del launcher en VS Code"""
|
||||
try:
|
||||
group = launcher_manager.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return jsonify({"status": "error", "message": "Grupo no encontrado"}), 404
|
||||
|
||||
script_group_path = group["directory"]
|
||||
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo '{group['name']}' no encontrado"
|
||||
}), 404
|
||||
|
||||
# VS Code executable path
|
||||
vscode_path = r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe"
|
||||
|
||||
if not os.path.isfile(vscode_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"VS Code no encontrado en: {vscode_path}"
|
||||
}), 404
|
||||
|
||||
print(f"Launching VS Code for launcher group: {group['name']}")
|
||||
print(f"Opening directory: {script_group_path}")
|
||||
|
||||
process = subprocess.Popen(f'"{vscode_path}" "{script_group_path}"', shell=True)
|
||||
print(f"Process started with PID: {process.pid}")
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"message": f"VS Code abierto en: {script_group_path}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error opening VS Code for launcher group '{group_id}': {str(e)}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Error al abrir VS Code: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/launcher-markdown/<group_id>")
|
||||
def get_launcher_markdown_files(group_id):
|
||||
"""Obtener archivos Markdown de un grupo"""
|
||||
try:
|
||||
markdown_files = launcher_manager.get_markdown_files(group_id)
|
||||
return jsonify({"files": markdown_files})
|
||||
except Exception as e:
|
||||
print(f"Error getting markdown files for group {group_id}: {e}")
|
||||
# Devolver lista vacía en lugar de error para no interferir con scripts
|
||||
return jsonify({"files": []})
|
||||
|
||||
@app.route("/api/launcher-markdown-content/<group_id>/<path:relative_path>")
|
||||
def get_launcher_markdown_content(group_id, relative_path):
|
||||
"""Obtener contenido de un archivo Markdown"""
|
||||
try:
|
||||
result = launcher_manager.read_markdown_file(group_id, relative_path)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# --- Global Error Handler (for debugging unhandled exceptions) ---
|
||||
@app.errorhandler(Exception)
|
||||
def handle_unhandled_exception(e):
|
||||
# Log the error with traceback
|
||||
app.logger.error('Unhandled Exception: %s', e, exc_info=True)
|
||||
# Return a JSON response for API calls, or HTML for others
|
||||
if request.path.startswith('/api/'):
|
||||
return jsonify({"status": "error", "message": f"Internal Server Error: {str(e)}"}), 500
|
||||
else:
|
||||
return "<h1>Internal Server Error</h1><p>An unhandled error occurred.</p>", 500
|
||||
|
||||
|
||||
# === FIN LAUNCHER GUI APIs ===
|
||||
|
||||
# --- Helper function to find VS Code ---
|
||||
def find_vscode_executable():
|
||||
"""Intenta encontrar el ejecutable de VS Code en ubicaciones comunes y en el PATH."""
|
||||
# Comprobar la variable de entorno VSCODE_PATH primero (si la defines)
|
||||
vscode_env_path = os.getenv("VSCODE_PATH")
|
||||
if vscode_env_path and os.path.isfile(vscode_env_path):
|
||||
return vscode_env_path
|
||||
|
||||
common_paths = []
|
||||
local_app_data = os.getenv('LOCALAPPDATA')
|
||||
if local_app_data:
|
||||
common_paths.append(os.path.join(local_app_data, r"Programs\Microsoft VS Code\Code.exe"))
|
||||
|
||||
common_paths.extend([
|
||||
r"C:\Program Files\Microsoft VS Code\Code.exe",
|
||||
r"C:\Program Files (x86)\Microsoft VS Code\Code.exe",
|
||||
])
|
||||
for path in common_paths:
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
return shutil.which("code") # Busca 'code' en el PATH
|
||||
|
||||
@app.route("/api/open-editor/<editor>/<group_system>/<group_id>", methods=["POST"])
|
||||
def open_group_in_editor(editor, group_system, group_id):
|
||||
"""Ruta unificada para abrir grupos en diferentes editores"""
|
||||
try:
|
||||
# Validar editor
|
||||
if editor not in ['vscode', 'cursor']:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Editor '{editor}' no soportado. Usar 'vscode' o 'cursor'"
|
||||
}), 400
|
||||
|
||||
# Determinar directorio según el sistema
|
||||
if group_system == 'config':
|
||||
script_group_path = os.path.join(config_manager.script_groups_path, group_id)
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo config '{group_id}' no encontrado"
|
||||
}), 404
|
||||
elif group_system == 'launcher':
|
||||
group = launcher_manager.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Grupo launcher '{group_id}' no encontrado"
|
||||
}), 404
|
||||
script_group_path = group["directory"]
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo launcher '{group['name']}' no encontrado"
|
||||
}), 404
|
||||
else:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'"
|
||||
}), 400
|
||||
|
||||
# Definir rutas de ejecutables
|
||||
if editor == 'vscode':
|
||||
editor_path = r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe"
|
||||
editor_name = "VS Code"
|
||||
elif editor == 'cursor':
|
||||
# Rutas comunes donde se instala Cursor
|
||||
possible_cursor_paths = [
|
||||
r"C:\Users\migue\AppData\Local\Programs\cursor\Cursor.exe",
|
||||
r"C:\Program Files\Cursor\Cursor.exe",
|
||||
r"C:\Program Files (x86)\Cursor\Cursor.exe"
|
||||
]
|
||||
editor_path = None
|
||||
for path in possible_cursor_paths:
|
||||
if os.path.isfile(path):
|
||||
editor_path = path
|
||||
break
|
||||
|
||||
if not editor_path:
|
||||
# Intentar buscar en PATH
|
||||
editor_path = shutil.which("cursor")
|
||||
|
||||
if not editor_path:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Cursor no encontrado. Intenté en: {', '.join(possible_cursor_paths)}"
|
||||
}), 404
|
||||
editor_name = "Cursor"
|
||||
|
||||
# Verificar que el ejecutable existe
|
||||
if not os.path.isfile(editor_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"{editor_name} no encontrado en: {editor_path}"
|
||||
}), 404
|
||||
|
||||
print(f"Launching {editor_name} from: {editor_path}")
|
||||
print(f"Opening directory: {script_group_path}")
|
||||
|
||||
# Ejecutar el editor
|
||||
process = subprocess.Popen(f'"{editor_path}" "{script_group_path}"', shell=True)
|
||||
print(f"{editor_name} process started with PID: {process.pid}")
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"message": f"{editor_name} abierto en: {script_group_path}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error opening {editor} for {group_system} group '{group_id}': {str(e)}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Error al abrir {editor}: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/open-group-folder/<group_system>/<group_id>", methods=["POST"])
|
||||
def open_group_folder(group_system, group_id):
|
||||
"""Abrir carpeta de un grupo en el explorador de archivos"""
|
||||
try:
|
||||
# Determinar directorio según el sistema
|
||||
if group_system == 'config':
|
||||
script_group_path = os.path.join(config_manager.script_groups_path, group_id)
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo config '{group_id}' no encontrado"
|
||||
}), 404
|
||||
elif group_system == 'launcher':
|
||||
group = launcher_manager.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Grupo launcher '{group_id}' no encontrado"
|
||||
}), 404
|
||||
script_group_path = group["directory"]
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo launcher '{group['name']}' no encontrado"
|
||||
}), 404
|
||||
else:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'"
|
||||
}), 400
|
||||
|
||||
# Abrir en el explorador según el sistema operativo
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
os.startfile(script_group_path)
|
||||
elif sys.platform == "darwin": # macOS
|
||||
subprocess.Popen(["open", script_group_path])
|
||||
else: # linux variants
|
||||
subprocess.Popen(["xdg-open", script_group_path])
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"message": f"Abriendo '{script_group_path}' en el explorador",
|
||||
"path": script_group_path
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Error al abrir el explorador en '{script_group_path}': {str(e)}"
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error opening folder for {group_system} group '{group_id}': {str(e)}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Error al abrir carpeta: {str(e)}"
|
||||
}), 500
|
||||
|
||||
@app.route("/api/get-group-path/<group_system>/<group_id>", methods=["GET"])
|
||||
def get_group_path(group_system, group_id):
|
||||
"""Obtener el path completo de un grupo de scripts"""
|
||||
try:
|
||||
# Determinar directorio según el sistema
|
||||
if group_system == 'config':
|
||||
script_group_path = os.path.join(config_manager.script_groups_path, group_id)
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo config '{group_id}' no encontrado"
|
||||
}), 404
|
||||
elif group_system == 'launcher':
|
||||
group = launcher_manager.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Grupo launcher '{group_id}' no encontrado"
|
||||
}), 404
|
||||
script_group_path = group["directory"]
|
||||
if not os.path.isdir(script_group_path):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Directorio del grupo launcher '{group['name']}' no encontrado"
|
||||
}), 404
|
||||
else:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'"
|
||||
}), 400
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"path": script_group_path
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting path for {group_system} group '{group_id}': {str(e)}")
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Error al obtener path: {str(e)}"
|
||||
}), 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
# --- Start Flask in a background thread ---
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
--- Log de Ejecución: x1.py ---
|
||||
Grupo: EmailCrono
|
||||
Directorio de Trabajo: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails
|
||||
Inicio: 2025-05-18 16:00:44
|
||||
Fin: 2025-05-18 16:00:44
|
||||
Duración: 0:00:00.445734
|
||||
Directorio de Trabajo: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email
|
||||
Inicio: 2025-06-09 17:06:35
|
||||
Fin: 2025-06-09 17:06:36
|
||||
Duración: 0:00:00.370858
|
||||
Estado: SUCCESS (Código de Salida: 0)
|
||||
|
||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||
Working directory: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails
|
||||
Input directory: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails
|
||||
Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update
|
||||
Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update\cronologia.md
|
||||
Attachments directory: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails\adjuntos
|
||||
Working directory: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email
|
||||
Input directory: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email
|
||||
Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340
|
||||
Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340\cronologia.md
|
||||
Attachments directory: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email\adjuntos
|
||||
Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
||||
Found 2 .eml files
|
||||
Loaded 0 existing messages
|
||||
|
||||
Processing C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails\I_ Backup SAE052.eml
|
||||
Processing C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email\E5.007172.eml
|
||||
Aplicando reglas de prioridad 1
|
||||
Aplicando reglas de prioridad 2
|
||||
Aplicando reglas de prioridad 3
|
||||
Aplicando reglas de prioridad 4
|
||||
|
||||
Processing C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails\Parametri Modificati SAE052.eml
|
||||
Aplicando reglas de prioridad 1
|
||||
Aplicando reglas de prioridad 2
|
||||
Aplicando reglas de prioridad 3
|
||||
Aplicando reglas de prioridad 4
|
||||
Processing C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email\R_ NOTICE OF GOODS READY ASSIGN ORDER 169423 - Won Opportunity - Services- 169423.A.2.2 - AJETHAI CO., LTD. - Thailand - Filling - Service CRM_0037299==CHECK PAYMENT TERM==E5.007172.eml
|
||||
|
||||
Estadísticas de procesamiento:
|
||||
- Total mensajes encontrados: 2
|
||||
- Mensajes únicos añadidos: 2
|
||||
- Total mensajes encontrados: 1
|
||||
- Mensajes únicos añadidos: 1
|
||||
- Mensajes duplicados ignorados: 0
|
||||
|
||||
Writing 2 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update\cronologia.md
|
||||
Writing 1 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340\cronologia.md
|
||||
|
||||
--- ERRORES (STDERR) ---
|
||||
Ninguno
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"cronologia_file": "cronologia.md"
|
||||
},
|
||||
"level3": {
|
||||
"output_directory": "C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update"
|
||||
"output_directory": "C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340"
|
||||
},
|
||||
"working_directory": "C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails"
|
||||
"working_directory": "C:\\Trabajo\\SIDEL\\14 - E5.007172 - Modifica O&U - SAE340\\Reporte\\Email"
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"path": "C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails",
|
||||
"path": "C:\\Trabajo\\SIDEL\\14 - E5.007172 - Modifica O&U - SAE340\\Reporte\\Email",
|
||||
"history": [
|
||||
"C:\\Trabajo\\SIDEL\\14 - E5.007172 - Modifica O&U - SAE340\\Reporte\\Email",
|
||||
"C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails",
|
||||
"C:\\Trabajo\\SIDEL\\10 - E5.007095 - Modifica O&U - SAE463\\Reporte\\Email",
|
||||
"C:\\Trabajo\\SIDEL\\08 - Masselli TEST\\Reporte\\EMAILs",
|
||||
|
@ -9,7 +10,6 @@
|
|||
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\EmailTody",
|
||||
"C:\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Reporte\\Emails",
|
||||
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Emails",
|
||||
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails\\Trial",
|
||||
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails"
|
||||
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails\\Trial"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
# IO Summary Table for PLC: A40510
|
||||
|
||||
| Network | Type | Address | Device Name | Sub-Device | OrderNo | Type | IO Type | IO Address | Number of Bits |
|
||||
|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41110 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 0..0` | 8 |
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41111 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 1..1` | 8 |
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41120 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 2..2` | 8 |
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41121 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 3..3` | 8 |
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41130 | 6ES7 132-6BF01-0BA0 | DQ 8x24VDC/0.5A ST | Output | `AW 0..0` | 8 |
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41131 | 6ES7 132-6BF01-0BA0 | DQ 8x24VDC/0.5A ST | Output | `AW 1..1` | 8 |
|
||||
| PLC Local Modules | Local I/O | Local | PLC A40510 | A41140 | 6ES7 132-6BF01-0BA0 | DQ 8x24VDC/0.5A ST | Output | `AW 2..2` | 8 |
|
|
@ -1,37 +1,14 @@
|
|||
--- Log de Ejecución: x1_export_CAx.py ---
|
||||
Grupo: IO_adaptation
|
||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Inicio: 2025-05-15 10:41:11
|
||||
Fin: 2025-05-15 10:43:18
|
||||
Duración: 0:02:07.367695
|
||||
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Inicio: 2025-06-06 18:25:09
|
||||
Fin: 2025-06-06 18:25:20
|
||||
Duración: 0:00:11.673530
|
||||
Estado: SUCCESS (Código de Salida: 0)
|
||||
|
||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||
--- TIA Portal Project CAx Exporter and Analyzer ---
|
||||
|
||||
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\TAGsIO\v2
|
||||
Will export CAx data to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml
|
||||
Will generate summary to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Summary.md
|
||||
Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.log
|
||||
|
||||
Connecting to TIA Portal V18.0...
|
||||
2025-05-15 10:41:33,504 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
||||
2025-05-15 10:41:33,524 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface
|
||||
Connected.
|
||||
Opening project: SAE196_c0.2.ap18...
|
||||
2025-05-15 10:42:05,513 [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.
|
||||
Exporting CAx data for the project to C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml...
|
||||
CAx data exported successfully.
|
||||
|
||||
Closing TIA Portal...
|
||||
2025-05-15 10:43:15,299 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal ClosePortal - Close TIA Portal
|
||||
TIA Portal closed.
|
||||
Parsing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml
|
||||
Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Summary.md
|
||||
|
||||
Script finished.
|
||||
No project file selected. Exiting.
|
||||
|
||||
--- ERRORES (STDERR) ---
|
||||
Ninguno
|
||||
|
|
|
@ -1,48 +1,76 @@
|
|||
--- Log de Ejecución: x2_process_CAx.py ---
|
||||
Grupo: IO_adaptation
|
||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Inicio: 2025-05-15 11:01:47
|
||||
Fin: 2025-05-15 11:01:52
|
||||
Duración: 0:00:04.917359
|
||||
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Inicio: 2025-06-08 00:15:28
|
||||
Fin: 2025-06-08 00:15:33
|
||||
Duración: 0:00:04.525461
|
||||
Estado: SUCCESS (Código de Salida: 0)
|
||||
|
||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) ---
|
||||
Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml
|
||||
Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.hierarchical.json
|
||||
Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\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\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml
|
||||
Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
||||
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v32.4 - Fixed Excel Integer Format for IO Addresses) ---
|
||||
Using configured working directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml
|
||||
Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.hierarchical.json
|
||||
Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_IO_Upward_Debug.md
|
||||
Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml
|
||||
Pass 1: Found 363 InternalElement(s). Populating device dictionary...
|
||||
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
||||
Identified Network: PROFIBUS_1 (442e4d1d-7d42-4d59-bd77-ec619f883907) Type: Profibus
|
||||
Identified Network: ETHERNET_1 (26504433-7319-4b53-8f42-0ae24c9e88a2) Type: Ethernet/Profinet
|
||||
Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
||||
Identified Network: PN/IE_1 (f9f048af-beb4-4e14-8a03-dc27e564649c) Type: Ethernet/Profinet
|
||||
Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
|
||||
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
||||
Found 115 InternalLink(s).
|
||||
Mapping Device/Node 'E1' (NodeID:60d02ba8-54ea-4508-8a3a-986826e276c6, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
||||
--> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
||||
Mapping Device/Node 'P1' (NodeID:8cf403ec-810d-43de-826a-aa447f887ee3, Addr:1) to Network 'PROFIBUS_1'
|
||||
--> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
||||
Mapping Device/Node 'PB1' (NodeID:b618b2b1-9ea8-41c1-a87e-fb0a3627a51d, Addr:12) to Network 'PROFIBUS_1'
|
||||
Mapping Device/Node 'PB1' (NodeID:4e5d84a4-eb22-4d1f-8143-fc4d770eb2e7, Addr:20) to Network 'PROFIBUS_1'
|
||||
Mapping Device/Node 'PB1' (NodeID:e6f362b4-398b-4854-b15b-7435745e4650, Addr:21) to Network 'PROFIBUS_1'
|
||||
Mapping Device/Node 'PB1' (NodeID:1a5422d6-c2bf-4a1c-8cf9-7b89fbaf4090, Addr:22) to Network 'PROFIBUS_1'
|
||||
Mapping Device/Node 'PB1' (NodeID:6b3de492-236f-4894-9bcf-3ee6c851c230, Addr:10) to Network 'PROFIBUS_1'
|
||||
Mapping Device/Node 'PB1' (NodeID:bf8c18a3-aa60-4106-b779-afff78cdac47, Addr:8) to Network 'PROFIBUS_1'
|
||||
Mapping Device/Node 'PB1' (NodeID:5e2810b0-4018-4747-bba9-19b8f9b14994, Addr:40) to Network 'PROFIBUS_1'
|
||||
Found 103 InternalLink(s).
|
||||
Mapping Device/Node 'E1' (NodeID:49534400-9e59-4c19-996d-7ad00a2957e9, Addr:10.1.30.11) to Network 'PN/IE_1'
|
||||
--> Found PLC in children: A40510 (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8)
|
||||
--> Associating Network 'PN/IE_1' with PLC 'A40510' (via Node 'E1' Addr: 10.1.30.11)
|
||||
Mapping Device/Node 'IE1' (NodeID:051911c8-9591-4119-a3ee-d6be687b7f9d, Addr:10.1.30.58) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:83519e1a-0912-4ae5-845b-b82bc743a92c, Addr:10.1.30.59) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:2ba93b89-6b43-4a92-850b-8110b9c2efcc, Addr:10.1.30.60) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:b31787f3-2682-4d09-affc-69db1e2b89d3, Addr:10.1.30.61) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:ef087f66-fd57-4be7-93a7-19497f4f852c, Addr:10.1.30.62) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:6fe53dd5-7093-4245-923f-f4bbce67fabf, Addr:10.1.30.63) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:59f51321-8469-447f-ace9-05e1495bbafd, Addr:10.1.30.64) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:60c861c6-1889-4bde-9d16-1c625116aece, Addr:10.1.30.65) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:4f6ad448-3a75-4e48-8107-83634ba6d192, Addr:10.1.30.66) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:b2153a8e-6526-4831-b2a1-5cf3da07c062, Addr:10.1.30.31) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:08066980-9963-4ca3-9a5d-1d9de9b611a3, Addr:10.1.30.32) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:2acb9ba4-f0f9-4fc6-9fd9-4592c1c8ef5a, Addr:10.1.30.170) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:feda1ddb-816d-40bd-a88c-8b1bc310c424, Addr:10.1.30.33) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:80a42e65-b1b1-4a48-b4ea-f95ca4032021, Addr:10.1.30.34) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:082ca69e-92aa-43c5-982b-2b134e5ca9ea, Addr:10.1.30.35) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:96aef85c-66f1-4792-94fd-f9ad5bb41d62, Addr:10.1.30.36) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:9b791861-b139-4a7d-b7ba-ebe195bbaf2c, Addr:10.1.30.40) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:5085a882-dc3c-4794-9c0a-4821a9ecbf24, Addr:10.1.30.44) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:0ea5739f-aa85-46b8-958f-9b6ce2960d6d, Addr:10.1.30.41) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:1f6cec79-c24c-4b0f-8aec-47d49ea1ce81, Addr:10.1.30.42) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:ab6516fc-5061-437d-bdc1-529d79d3d42a, Addr:10.1.30.43) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:3f7bbecb-2bb9-42e8-8668-4a102c98873b, Addr:10.1.30.37) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:6807d26a-ff0f-4ce7-89b9-8785cf5310ae, Addr:10.1.30.45) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:4c58e397-5fda-4076-bd52-374ba80460cb, Addr:10.1.30.46) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:36da4ca9-6dc1-489a-bbdc-d124bf125dad, Addr:10.1.30.47) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:02213f0f-cc1f-4d97-ba86-eb3d11f7cb5b, Addr:10.1.30.48) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:628bab85-5b77-4335-9444-48f51dec6e66, Addr:10.1.30.49) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:5800b36d-59bb-43f6-8857-5b616730246d, Addr:10.1.30.70) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:b68a427c-e7f6-459a-9e6a-39b76a4497ad, Addr:10.1.30.71) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:d4bad7e1-0461-4dbb-b797-cb45a75225d9, Addr:10.1.30.72) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:145f1526-6b77-4ea7-968b-de89a7bbeda2, Addr:10.1.30.74) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:8cde4014-94bb-47be-aacd-32f1915152d2, Addr:10.1.30.73) to Network 'PN/IE_1'
|
||||
Data extraction and structuring complete.
|
||||
Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.hierarchical.json
|
||||
Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.hierarchical.json
|
||||
JSON data written successfully.
|
||||
|
||||
IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
||||
IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_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\TAGsIO\v2\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
||||
Markdown tree summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
||||
IO summary table written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Hardware.md
|
||||
IO summary table generated in separate file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Hardware.md
|
||||
Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_Hardware_Tree.md
|
||||
Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_Hardware_Tree.md
|
||||
IO summary table written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Hardware.md
|
||||
IO summary table generated in separate file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Hardware.md
|
||||
Generating Excel IO Report for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_IO_Report.xlsx
|
||||
Generating Excel IO report for PLC: A40510
|
||||
Excel IO report saved to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_IO_Report.xlsx
|
||||
Total rows in report: 33
|
||||
|
||||
Script finished.
|
||||
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
--- Log de Ejecución: x3_excel_to_md.py ---
|
||||
Grupo: IO_adaptation
|
||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Inicio: 2025-05-15 11:58:03
|
||||
Fin: 2025-05-15 11:58:05
|
||||
Duración: 0:00:01.664065
|
||||
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Inicio: 2025-06-08 13:21:43
|
||||
Fin: 2025-06-08 13:22:12
|
||||
Duración: 0:00:29.516302
|
||||
Estado: SUCCESS (Código de Salida: 0)
|
||||
|
||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||
Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Configuración de paths cargada desde: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\io_paths_config.json
|
||||
Usando archivo Excel predeterminado: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLCTags.xlsx
|
||||
Procesando archivo Excel: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLCTags.xlsx...
|
||||
Usando directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Configuración de paths cargada desde: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\io_paths_config.json
|
||||
Archivo PLCTags.xlsx no encontrado. Seleccione el archivo Excel exportado de TIA Portal:
|
||||
Procesando archivo Excel: D:/Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/PLCTagsv_02.xlsx...
|
||||
Paths configurados para procesar: ['Inputs', 'Outputs', 'OutputsFesto', 'IO Not in Hardware\\InputsMaster', 'IO Not in Hardware\\OutputsMaster']
|
||||
¡Éxito! Archivo Excel convertido a Markdown en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Master IO Tags.md
|
||||
No se encontraron entradas para el path: OutputsFesto
|
||||
No se encontraron entradas para el path: IO Not in Hardware\InputsMaster
|
||||
No se encontraron entradas para el path: IO Not in Hardware\OutputsMaster
|
||||
¡Éxito! Archivo Excel convertido a Markdown en: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Master IO Tags.md
|
||||
|
||||
--- ERRORES (STDERR) ---
|
||||
Ninguno
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
--- Log de Ejecución: x4_prompt_generator.py ---
|
||||
Grupo: IO_adaptation
|
||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Inicio: 2025-05-15 14:05:02
|
||||
Fin: 2025-05-15 14:05:04
|
||||
Duración: 0:00:01.643930
|
||||
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Inicio: 2025-06-08 11:05:58
|
||||
Fin: 2025-06-08 11:06:03
|
||||
Duración: 0:00:04.909042
|
||||
Estado: SUCCESS (Código de Salida: 0)
|
||||
|
||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||
Generador de prompt para adaptación de IO
|
||||
=========================================
|
||||
Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
||||
Usando directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Usando ruta de Obsidian desde configuración: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO
|
||||
Usando carpeta de equivalencias en Obsidian: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO
|
||||
¡Prompt generado y copiado al portapapeles con éxito!
|
||||
Prompt guardado en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\IO_Adaptation_Prompt.txt
|
||||
Prompt guardado en: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\IO_Adaptation_Prompt.txt
|
||||
|
||||
--- ERRORES (STDERR) ---
|
||||
Ninguno
|
||||
|
|
|
@ -0,0 +1,657 @@
|
|||
--- Log de Ejecución: x7_update_CAx.py ---
|
||||
Grupo: IO_adaptation
|
||||
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
Inicio: 2025-06-07 18:57:44
|
||||
Fin: 2025-06-07 18:57:53
|
||||
Duración: 0:00:08.646157
|
||||
Estado: SUCCESS (Código de Salida: 0)
|
||||
|
||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||
--- Actualizador de AML desde Excel Modificado (v1.4 - Enhanced Address Element Search with Debug) ---
|
||||
Directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||
|
||||
1. Seleccione el archivo AML original:
|
||||
|
||||
2. Seleccione el archivo Excel modificado:
|
||||
|
||||
Archivo AML original: D:/Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/98050_PLC_01.aml
|
||||
Archivo Excel modificado: D:/Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/A40510/Documentation/98050_PLC_01_IO_Report.xlsx
|
||||
|
||||
Usando directorio temporal: C:\Users\migue\AppData\Local\Temp\tmplzmtjbwx
|
||||
Generando Excel de referencia desde AML original...
|
||||
Pass 1: Found 363 InternalElement(s). Populating device dictionary...
|
||||
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
||||
Identified Network: PN/IE_1 (6ce86626-0043-4a58-b675-cc13ac87121c) Type: Ethernet/Profinet
|
||||
Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
|
||||
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
||||
Found 103 InternalLink(s).
|
||||
Mapping Device/Node 'E1' (NodeID:ab796923-4471-4a60-98f4-f8ea5920b3b9, Addr:10.1.30.11) to Network 'PN/IE_1'
|
||||
--> Found PLC in children: A40510 (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8)
|
||||
--> Associating Network 'PN/IE_1' with PLC 'A40510' (via Node 'E1' Addr: 10.1.30.11)
|
||||
Mapping Device/Node 'IE1' (NodeID:c53ae31d-bee4-47ba-950d-15c31d2599b9, Addr:10.1.30.58) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:1a8366c9-7d4c-4e49-b0a3-77d445eabc8b, Addr:10.1.30.59) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:deeda41b-54d0-4c86-82c3-a311f753021e, Addr:10.1.30.60) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:8a916a9d-895e-4a12-9190-516ef6a8f191, Addr:10.1.30.61) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:c51471e0-9621-4ef7-b050-09bf4d695ea1, Addr:10.1.30.62) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:77ca312c-3dd0-46f6-bd64-5da69a99cf6f, Addr:10.1.30.63) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:011019c6-5925-4544-87aa-27288c3aa70c, Addr:10.1.30.64) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:5ea1e894-b51c-44f9-8aff-80c58c6cb7ef, Addr:10.1.30.65) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:612916a9-7a26-4712-9de2-d3c7894db862, Addr:10.1.30.66) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:7be7cf9f-f7af-418c-8fe4-96b4a97a581d, Addr:10.1.30.31) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:135492a8-02ab-4236-92ce-7a5585538297, Addr:10.1.30.32) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:b7a06147-6428-4d95-ba0d-834fad49a1ae, Addr:10.1.30.170) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:7ce83bdf-dc4d-40f0-85c8-a246dd2c44be, Addr:10.1.30.33) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:e9a07506-2869-4c34-8541-ee021d1623f0, Addr:10.1.30.34) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:3dd0f886-c3d0-4628-804b-ae18cf5931e8, Addr:10.1.30.35) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:67c3fa72-a956-4363-9a4d-e3300d7d1429, Addr:10.1.30.36) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:b35e73e8-a9ad-47e2-8ad5-00442c7a2df7, Addr:10.1.30.40) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:867d0580-06cd-4722-a66f-8c5559b624f5, Addr:10.1.30.44) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:6f1b4062-80ac-4d1a-b351-546c9d0157e2, Addr:10.1.30.41) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:c0016a06-48cc-47af-83aa-d4a0a6cb44f6, Addr:10.1.30.42) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:07fa5e8f-ffcf-4d81-9897-5ff9f73d6125, Addr:10.1.30.43) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:5e8a4449-c958-4397-8b71-877af262333b, Addr:10.1.30.37) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:79096325-dcb8-4650-bc44-2c9735a93f52, Addr:10.1.30.45) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:957e5975-eafe-477c-a682-bebf330a2868, Addr:10.1.30.46) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:379ccd79-27b4-4b53-a552-3da783bc5b25, Addr:10.1.30.47) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:b53dd719-e8af-431f-b837-a0903ffb3a76, Addr:10.1.30.48) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:0f28d88a-5208-4fc4-8ee1-f1bb33a947e8, Addr:10.1.30.49) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:d1f8ea18-50d2-410e-9966-136d8a79471d, Addr:10.1.30.70) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:1c86a627-5646-45e7-9c21-5d18d6544568, Addr:10.1.30.71) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:e80a9939-59d7-44e0-9a46-1fade44e1b78, Addr:10.1.30.72) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:8c51fa26-883a-468c-8c36-c0e1b31852e4, Addr:10.1.30.74) to Network 'PN/IE_1'
|
||||
Mapping Device/Node 'IE1' (NodeID:17be2ccc-dede-4187-ba77-1ad8499a7349, Addr:10.1.30.73) to Network 'PN/IE_1'
|
||||
Data extraction and structuring complete.
|
||||
Generating Excel IO report for PLC: A40510
|
||||
Excel IO report saved to: C:\Users\migue\AppData\Local\Temp\tmplzmtjbwx\temp_plc_fc0d3bac-267e-488a-8dcf-7dc8599d80e8.xlsx
|
||||
Total rows in report: 33
|
||||
Comparando archivos Excel...
|
||||
Estructura básica validada correctamente.
|
||||
Detectados 22 dispositivos con cambios.
|
||||
A40510+U30210-AxisY: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+E44010-Encoder: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+U30310: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+U30410: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+U30510: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+U30610: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31010: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31410: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31110: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31210: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31310: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M30710: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31510: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31610: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31710: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31810: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M31910: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M34010: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M34110: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M34210: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M34410: ['IO Input Start Address', 'IO Output Start Address']
|
||||
A40510+M34310: ['IO Input Start Address', 'IO Output Start Address']
|
||||
|
||||
Cargando archivo AML original...
|
||||
Aplicando cambios al archivo AML...
|
||||
Procesando cambios para: A40510+U30210-AxisY
|
||||
Debug: Encontrado dispositivo - node_id: 135492a8-02ab-4236-92ce-7a5585538297, display_id: 0eef2113-ec3f-4c2f-8448-fa7456a6140d
|
||||
Debug: Usando elemento XML con ID 0eef2113-ec3f-4c2f-8448-fa7456a6140d
|
||||
Debug: Buscando elementos Address en dispositivo U30210-AxisY
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 120 -> 112
|
||||
Debug: Buscando elementos Address en dispositivo U30210-AxisY
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 120 -> 112
|
||||
Procesando cambios para: A40510+E44010-Encoder
|
||||
Debug: Encontrado dispositivo - node_id: b7a06147-6428-4d95-ba0d-834fad49a1ae, display_id: e59b0b92-6c37-45d5-aa9b-8dae625e5272
|
||||
Debug: Usando elemento XML con ID e59b0b92-6c37-45d5-aa9b-8dae625e5272
|
||||
Debug: Buscando elementos Address en dispositivo E44010-Encoder
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 140 -> 574
|
||||
Debug: Buscando elementos Address en dispositivo E44010-Encoder
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 140 -> 574
|
||||
Procesando cambios para: A40510+U30310
|
||||
Debug: Encontrado dispositivo - node_id: 7ce83bdf-dc4d-40f0-85c8-a246dd2c44be, display_id: 98a3e87d-176b-42b9-a606-fd97b54bc54b
|
||||
Debug: Usando elemento XML con ID 98a3e87d-176b-42b9-a606-fd97b54bc54b
|
||||
Debug: Buscando elementos Address en dispositivo U30310
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4 -> 124
|
||||
Debug: Buscando elementos Address en dispositivo U30310
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 3 -> 124
|
||||
Procesando cambios para: A40510+U30410
|
||||
Debug: Encontrado dispositivo - node_id: e9a07506-2869-4c34-8541-ee021d1623f0, display_id: 1fcdf8b4-7c90-48e0-bb72-f29eef363705
|
||||
Debug: Usando elemento XML con ID 1fcdf8b4-7c90-48e0-bb72-f29eef363705
|
||||
Debug: Buscando elementos Address en dispositivo U30410
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 52 -> 150
|
||||
Debug: Buscando elementos Address en dispositivo U30410
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 51 -> 150
|
||||
Procesando cambios para: A40510+U30510
|
||||
Debug: Encontrado dispositivo - node_id: 3dd0f886-c3d0-4628-804b-ae18cf5931e8, display_id: 44004215-6e6f-456c-9c97-164ee1f8e7b7
|
||||
Debug: Usando elemento XML con ID 44004215-6e6f-456c-9c97-164ee1f8e7b7
|
||||
Debug: Buscando elementos Address en dispositivo U30510
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 156 -> 176
|
||||
Debug: Buscando elementos Address en dispositivo U30510
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 144 -> 176
|
||||
Procesando cambios para: A40510+U30610
|
||||
Debug: Encontrado dispositivo - node_id: 67c3fa72-a956-4363-9a4d-e3300d7d1429, display_id: 2ea5ad51-0411-413d-8bb5-dfd0655592cf
|
||||
Debug: Usando elemento XML con ID 2ea5ad51-0411-413d-8bb5-dfd0655592cf
|
||||
Debug: Buscando elementos Address en dispositivo U30610
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 204 -> 202
|
||||
Debug: Buscando elementos Address en dispositivo U30610
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 192 -> 202
|
||||
Procesando cambios para: A40510+M31010
|
||||
Debug: Encontrado dispositivo - node_id: b35e73e8-a9ad-47e2-8ad5-00442c7a2df7, display_id: 4e0722c0-9114-40bf-8452-733e5d275591
|
||||
Debug: Usando elemento XML con ID 4e0722c0-9114-40bf-8452-733e5d275591
|
||||
Debug: Buscando elementos Address en dispositivo M31010
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 272 -> 252
|
||||
Debug: Buscando elementos Address en dispositivo M31010
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 272 -> 252
|
||||
Procesando cambios para: A40510+M31410
|
||||
Debug: Encontrado dispositivo - node_id: 867d0580-06cd-4722-a66f-8c5559b624f5, display_id: 6530fefc-0f00-45c5-b33a-9c009bf83e7b
|
||||
Debug: Usando elemento XML con ID 6530fefc-0f00-45c5-b33a-9c009bf83e7b
|
||||
Debug: Buscando elementos Address en dispositivo M31410
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 288 -> 334
|
||||
Debug: Buscando elementos Address en dispositivo M31410
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 288 -> 334
|
||||
Procesando cambios para: A40510+M31110
|
||||
Debug: Encontrado dispositivo - node_id: 6f1b4062-80ac-4d1a-b351-546c9d0157e2, display_id: 95cedabf-d2b0-4d17-a36d-668d1b6db6d8
|
||||
Debug: Usando elemento XML con ID 95cedabf-d2b0-4d17-a36d-668d1b6db6d8
|
||||
Debug: Buscando elementos Address en dispositivo M31110
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4000 -> 262
|
||||
Debug: Buscando elementos Address en dispositivo M31110
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4000 -> 262
|
||||
Procesando cambios para: A40510+M31210
|
||||
Debug: Encontrado dispositivo - node_id: c0016a06-48cc-47af-83aa-d4a0a6cb44f6, display_id: 1739a036-2231-4791-947b-9fa19eac922e
|
||||
Debug: Usando elemento XML con ID 1739a036-2231-4791-947b-9fa19eac922e
|
||||
Debug: Buscando elementos Address en dispositivo M31210
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4022 -> 286
|
||||
Debug: Buscando elementos Address en dispositivo M31210
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4044 -> 286
|
||||
Procesando cambios para: A40510+M31310
|
||||
Debug: Encontrado dispositivo - node_id: 07fa5e8f-ffcf-4d81-9897-5ff9f73d6125, display_id: 90c971fa-0bdd-4dba-8d04-f6eb4dd940cc
|
||||
Debug: Usando elemento XML con ID 90c971fa-0bdd-4dba-8d04-f6eb4dd940cc
|
||||
Debug: Buscando elementos Address en dispositivo M31310
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4044 -> 310
|
||||
Debug: Buscando elementos Address en dispositivo M31310
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4088 -> 310
|
||||
Procesando cambios para: A40510+M30710
|
||||
Debug: Encontrado dispositivo - node_id: 5e8a4449-c958-4397-8b71-877af262333b, display_id: b2f6e277-b969-4581-b3ed-4ca0bafe9994
|
||||
Debug: Usando elemento XML con ID b2f6e277-b969-4581-b3ed-4ca0bafe9994
|
||||
Debug: Buscando elementos Address en dispositivo M30710
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4066 -> 228
|
||||
Debug: Buscando elementos Address en dispositivo M30710
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4132 -> 228
|
||||
Procesando cambios para: A40510+M31510
|
||||
Debug: Encontrado dispositivo - node_id: 79096325-dcb8-4650-bc44-2c9735a93f52, display_id: d6fa2c62-51ab-4f68-a0f9-d86b2d52c1a9
|
||||
Debug: Usando elemento XML con ID d6fa2c62-51ab-4f68-a0f9-d86b2d52c1a9
|
||||
Debug: Buscando elementos Address en dispositivo M31510
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 304 -> 344
|
||||
Debug: Buscando elementos Address en dispositivo M31510
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 304 -> 344
|
||||
Procesando cambios para: A40510+M31610
|
||||
Debug: Encontrado dispositivo - node_id: 957e5975-eafe-477c-a682-bebf330a2868, display_id: 0a03a25f-121e-4e6a-ab81-f017d170ba52
|
||||
Debug: Usando elemento XML con ID 0a03a25f-121e-4e6a-ab81-f017d170ba52
|
||||
Debug: Buscando elementos Address en dispositivo M31610
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4088 -> 354
|
||||
Debug: Buscando elementos Address en dispositivo M31610
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4176 -> 354
|
||||
Procesando cambios para: A40510+M31710
|
||||
Debug: Encontrado dispositivo - node_id: 379ccd79-27b4-4b53-a552-3da783bc5b25, display_id: 8d6214bd-7a77-462f-a2bf-8de1dccc0db2
|
||||
Debug: Usando elemento XML con ID 8d6214bd-7a77-462f-a2bf-8de1dccc0db2
|
||||
Debug: Buscando elementos Address en dispositivo M31710
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4110 -> 378
|
||||
Debug: Buscando elementos Address en dispositivo M31710
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4220 -> 378
|
||||
Procesando cambios para: A40510+M31810
|
||||
Debug: Encontrado dispositivo - node_id: b53dd719-e8af-431f-b837-a0903ffb3a76, display_id: 9d4ba8bc-a7e7-40a0-99d1-f2fd88a6b00d
|
||||
Debug: Usando elemento XML con ID 9d4ba8bc-a7e7-40a0-99d1-f2fd88a6b00d
|
||||
Debug: Buscando elementos Address en dispositivo M31810
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4132 -> 402
|
||||
Debug: Buscando elementos Address en dispositivo M31810
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4264 -> 402
|
||||
Procesando cambios para: A40510+M31910
|
||||
Debug: Encontrado dispositivo - node_id: 0f28d88a-5208-4fc4-8ee1-f1bb33a947e8, display_id: 0c0c14f0-df2b-4c5e-8163-f7cb58788dae
|
||||
Debug: Usando elemento XML con ID 0c0c14f0-df2b-4c5e-8163-f7cb58788dae
|
||||
Debug: Buscando elementos Address en dispositivo M31910
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4154 -> 426
|
||||
Debug: Buscando elementos Address en dispositivo M31910
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4308 -> 426
|
||||
Procesando cambios para: A40510+M34010
|
||||
Debug: Encontrado dispositivo - node_id: d1f8ea18-50d2-410e-9966-136d8a79471d, display_id: a01c051b-9abc-4947-9632-6dcad7da84b8
|
||||
Debug: Usando elemento XML con ID a01c051b-9abc-4947-9632-6dcad7da84b8
|
||||
Debug: Buscando elementos Address en dispositivo M34010
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4176 -> 468
|
||||
Debug: Buscando elementos Address en dispositivo M34010
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4352 -> 468
|
||||
Procesando cambios para: A40510+M34110
|
||||
Debug: Encontrado dispositivo - node_id: 1c86a627-5646-45e7-9c21-5d18d6544568, display_id: d2440b5d-132e-4d66-986c-fe42a8ff4f74
|
||||
Debug: Usando elemento XML con ID d2440b5d-132e-4d66-986c-fe42a8ff4f74
|
||||
Debug: Buscando elementos Address en dispositivo M34110
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4198 -> 492
|
||||
Debug: Buscando elementos Address en dispositivo M34110
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4396 -> 492
|
||||
Procesando cambios para: A40510+M34210
|
||||
Debug: Encontrado dispositivo - node_id: e80a9939-59d7-44e0-9a46-1fade44e1b78, display_id: 041fea68-1b2b-4087-be60-b254def81cf6
|
||||
Debug: Usando elemento XML con ID 041fea68-1b2b-4087-be60-b254def81cf6
|
||||
Debug: Buscando elementos Address en dispositivo M34210
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4220 -> 516
|
||||
Debug: Buscando elementos Address en dispositivo M34210
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4440 -> 516
|
||||
Procesando cambios para: A40510+M34410
|
||||
Debug: Encontrado dispositivo - node_id: 8c51fa26-883a-468c-8c36-c0e1b31852e4, display_id: 28037fc6-dc45-4ec9-bea0-eafb922f6c6e
|
||||
Debug: Usando elemento XML con ID 28037fc6-dc45-4ec9-bea0-eafb922f6c6e
|
||||
Debug: Buscando elementos Address en dispositivo M34410
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 362 -> 564
|
||||
Debug: Buscando elementos Address en dispositivo M34410
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 340 -> 564
|
||||
Procesando cambios para: A40510+M34310
|
||||
Debug: Encontrado dispositivo - node_id: 17be2ccc-dede-4187-ba77-1ad8499a7349, display_id: 7a77a815-8c59-4cfd-81fe-d851a8e57aea
|
||||
Debug: Usando elemento XML con ID 7a77a815-8c59-4cfd-81fe-d851a8e57aea
|
||||
Debug: Buscando elementos Address en dispositivo M34310
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: ¡Encontrado elemento Input compatible!
|
||||
✓ IO Input Start Address actualizada: 4242 -> 540
|
||||
Debug: Buscando elementos Address en dispositivo M34310
|
||||
Debug: No se encontró Address directo, buscando en elementos hijos...
|
||||
Debug: No se encontró Address en hijos, buscando en elementos padre...
|
||||
Debug: Encontrados 1 elemento(s) Address
|
||||
Debug: Explorando Address element...
|
||||
Debug: Encontrados 2 sub-elementos en Address
|
||||
Debug: Revisando sub-elemento: 1
|
||||
Debug: Encontrado IoType: Input
|
||||
Debug: Revisando sub-elemento: 2
|
||||
Debug: Encontrado IoType: Output
|
||||
Debug: ¡Encontrado elemento Output compatible!
|
||||
✓ IO Output Start Address actualizada: 4484 -> 540
|
||||
Total de cambios aplicados: 44
|
||||
Archivo AML actualizado guardado en: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_updated.aml
|
||||
|
||||
¡Proceso completado exitosamente!
|
||||
Archivo AML actualizado: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_updated.aml
|
||||
|
||||
--- ERRORES (STDERR) ---
|
||||
D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\x7_update_CAx.py:532: FutureWarning: Truth-testing of elements was a source of confusion and will always return True in future versions. Use specific 'len(elem)' or 'elem is not None' test instead.
|
||||
if target_io_element:
|
||||
D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\x7_update_CAx.py:535: FutureWarning: Truth-testing of elements was a source of confusion and will always return True in future versions. Use specific 'len(elem)' or 'elem is not None' test instead.
|
||||
if not target_io_element:
|
||||
|
||||
--- FIN DEL LOG ---
|
|
@ -24,8 +24,29 @@ if __name__ == "__main__":
|
|||
working_dir = configs.get("working_directory", "")
|
||||
""""
|
||||
|
||||
try:
|
||||
configs = load_configuration()
|
||||
working_directory = configs.get("working_directory")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load configuration (frontend not running): {e}")
|
||||
configs = {}
|
||||
working_directory = None
|
||||
|
||||
# Validate working directory with .debug fallback for standalone execution
|
||||
if not working_directory or not os.path.isdir(working_directory):
|
||||
print("Working directory not set or invalid in configuration.")
|
||||
print("Using .debug directory as fallback for direct script execution.")
|
||||
|
||||
# Fallback to .debug directory under script location
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
debug_dir = os.path.join(script_dir, ".debug")
|
||||
|
||||
# Create .debug directory if it doesn't exist
|
||||
os.makedirs(debug_dir, exist_ok=True)
|
||||
working_directory = debug_dir
|
||||
print(f"Using debug directory: {working_directory}")
|
||||
else:
|
||||
print(f"Using configured working directory: {working_directory}")
|
||||
|
||||
# Acceder a la configuración específica del grupo
|
||||
group_config = configs.get("level2", {})
|
||||
|
@ -38,6 +59,34 @@ if __name__ == "__main__":
|
|||
|
||||
|
||||
|
||||
### Debug Directory Fallback
|
||||
|
||||
Cuando los scripts se ejecutan directamente (no desde el frontend), utilizan un sistema de fallback para determinar el directorio de trabajo:
|
||||
|
||||
1. **Con Frontend**: Los scripts utilizan el `working_directory` configurado dinámicamente a través de `load_configuration()`
|
||||
2. **Ejecución Directa (Debug)**: Si no hay configuración válida, los scripts crean automáticamente un directorio `.debug` en la ubicación del script
|
||||
|
||||
**Estructura del directorio de debug:**
|
||||
```
|
||||
IO_adaptation/
|
||||
├── .debug/ # ← Directorio creado automáticamente para debug
|
||||
│ ├── *.aml # Archivos AML procesados
|
||||
│ ├── *.hierarchical.json # Datos estructurados extraídos
|
||||
│ ├── *_IO_Upward_Debug.md # Archivo de debug de conexiones IO
|
||||
│ ├── Hardware.md # Tabla resumen de IO
|
||||
│ └── <PLC_Name>/
|
||||
│ └── Documentation/
|
||||
│ └── *_Hardware_Tree.md # Árbol de hardware por PLC
|
||||
├── x2_process_CAx.py # Script principal
|
||||
└── example/ # Archivos de ejemplo
|
||||
```
|
||||
|
||||
**Ventajas del directorio .debug:**
|
||||
- ✅ Separación clara entre archivos de debug y archivos de producción
|
||||
- ✅ Fácil limpieza (se puede eliminar todo el directorio `.debug`)
|
||||
- ✅ No interfiere con archivos del proyecto principal
|
||||
- ✅ Creación automática sin configuración manual
|
||||
|
||||
### Directory structure for Tia Portal scripts
|
||||
|
||||
<working_directory>/
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
"ObsideanProjectsBase": "\\04-SIDEL"
|
||||
},
|
||||
"level3": {},
|
||||
"working_directory": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2"
|
||||
"working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia"
|
||||
}
|
|
@ -29,10 +29,64 @@
|
|||
"long_description": "# Script de Adaptación IO para PLCTags\n\n## Descripción\n\nEste script automatiza la adaptación de tags PLC entre hardware físico Siemens TIA Portal y software master, actualizando las direcciones lógicas según un archivo de mapeo en formato Markdown.\n\n## Funcionamiento\n\n### Entradas\n- **Archivo Excel**: Exportación de tags desde TIA Portal\n- **Archivo Markdown**: Tabla de adaptación IO con mapeo entre hardware y master tags\n\n### Proceso\n1. **Análisis inicial**:\n - Lee la tabla Markdown de adaptación IO\n - Identifica las columnas de IO y Master Tag\n - Crea un diccionario de mapeo entre tags y direcciones IO\n\n2. **Procesamiento de tags**:\n - Filtra tags en los paths \"Inputs\", \"Outputs\", \"IO Not in Hardware\\InputsMaster\" y \"IO Not in Hardware\\OutputsMaster\"\n - Actualiza las direcciones lógicas de los tags encontrados en el mapeo\n - Reasigna paths según el tipo de señal (%E -> \"Inputs\", %A -> \"Outputs\")\n\n3. **Gestión de tags sin hardware**:\n - Asigna direcciones de memoria (%Mxxxx.x) para tags sin correspondencia\n - Tags de entrada: Asignados a \"IO Not in Hardware\\InputsMaster\" desde %M3600.0\n - Tags de salida: Asignados a \"IO Not in Hardware\\OutputsMaster\" desde %M3800.0\n\n4. **Manejo de direcciones**:\n - Convierte formatos (I0.0 → %E0.0, PEW100 → %EW100, etc.)\n - Gestiona bits incrementalmente (0.0, 0.1, ..., 0.7, luego 1.0)\n - Alinea words cada 2 bytes (%MW3600, %MW3602, etc.)\n\n### Salidas\n- **Archivo Excel actualizado**: Con las nuevas direcciones lógicas y paths\n- **Archivo de log**: Registro detallado del proceso con estadísticas\n\n## Uso\n1. Ejecute el script\n2. Seleccione el archivo Excel exportado de TIA Portal\n3. Seleccione el archivo Markdown con la tabla de adaptación\n4. El script generará automáticamente el archivo Excel actualizado\n\n## Detección de tipos\n- **Entradas**: Identificadas por prefijos (DI_, P_AI_, etc.)\n- **Salidas**: Identificadas por prefijos (DO_, P_AO_, etc.)\n- **Bits vs Words**: Determinados por el tipo de dato (Bool vs Word/Int)",
|
||||
"hidden": false
|
||||
},
|
||||
"x7_update_CAx.py": {
|
||||
"display_name": "7: Actualizar AML desde Excel modificado",
|
||||
"short_description": "Actualiza un archivo AML original basándose en las modificaciones hechas en el archivo Excel de IOs.",
|
||||
"long_description": "Este script permite hacer un 'round trip' de datos: extrae información del AML al Excel, permite ediciones manuales en el Excel, y luego aplica esos cambios de vuelta al AML original.\n***\n**Pipeline:**\n\n1. **Entrada:** Solicita el archivo AML original y el archivo Excel modificado (generado por x2_process_CAx.py)\n2. **Validación:** Genera un Excel de referencia desde el AML y compara con el Excel modificado para validar que la estructura base sea la misma (mismos nodos y nombres)\n3. **Detección de cambios:** Identifica modificaciones en:\n - Direcciones IP/nodos\n - Direcciones IO (inputs/outputs)\n - Tipos de dispositivos\n - Números de orden\n - Versiones de firmware\n4. **Aplicación:** Localiza los elementos correspondientes en el XML del AML y aplica los cambios detectados\n5. **Salida:** Genera un nuevo archivo AML con sufijo '_updated' que contiene todas las modificaciones\n\n**ID único:** Utiliza PLC+Device Name como identificador único para mapear cambios entre Excel y AML.\n\n**Restricciones:** Solo procede si el Excel modificado mantiene la misma cantidad de nodos y nombres que el Excel original.",
|
||||
"hidden": false
|
||||
},
|
||||
"x0_documentation.py": {
|
||||
"display_name": "0:Documentación",
|
||||
"short_description": "Descripción del flujo de trabajo",
|
||||
"long_description": "### Flujo de trabajo:\n***\n1. Se usa [x1] para exportar los datos de configuración desde el proyecto de Tia Portal, incluidos los IO configurados en el hardware. Esto genera un archivo AML.\n2. Con [x2] se procesa el archivo AML y se convierte en markdown. Si hay un solo PLC se genera el archivo [Hardware.md]\n3. Se deben procesar los IO **desde el esquema eléctrico y agregarlos** a [Hardware.md]\n4. Se debe exportar todos los tags como un archivo excel desde el Tia Portal.\n5. Con [x3] se convierte y se filtran los tags según los path definidos en [io_paths_config] y se genera un archivo Master IO Tags.md\n6. [x4] Genera el prompt a usar con Claude usando MCP para que pueda acceder a los archivos originales y evitar tener que hacer uploads.\n7. Una vez que se genera el archivo procesado por el LLM se puede usar [x5] que convierte las adaptaciones a un archivo que luego se puede importar en Tia Portal\n8. Importar en Tia Portal el archivo [PLCTags_Updated.xlsx]",
|
||||
"long_description": "### Flujo de trabajo:\n***\n1. Se usa [x1] para exportar los datos de configuración desde el proyecto de Tia Portal, incluidos los IO configurados en el hardware. Esto genera un archivo AML.\n2. Con [x2] se procesa el archivo AML y se convierte en markdown. Si hay un solo PLC se genera el archivo [Hardware.md]. **Además genera un archivo Excel con los IOs por nodos del PLC.**\n3. Se deben procesar los IO **desde el esquema eléctrico y agregarlos** a [Hardware.md]\n4. Se debe exportar todos los tags como un archivo excel desde el Tia Portal.\n5. Con [x3] se convierte y se filtran los tags según los path definidos en [io_paths_config] y se genera un archivo Master IO Tags.md\n6. [x4] Genera el prompt a usar con Claude usando MCP para que pueda acceder a los archivos originales y evitar tener que hacer uploads.\n7. Una vez que se genera el archivo procesado por el LLM se puede usar [x5] que convierte las adaptaciones a un archivo que luego se puede importar en Tia Portal\n8. Importar en Tia Portal el archivo [PLCTags_Updated.xlsx]\n\n**Nuevo flujo alternativo con Excel:**\nDesp¹és del paso 2, se puede usar [x7] para:\n- Modificar manualmente el archivo Excel de IOs generado\n- Aplicar los cambios de vuelta al archivo AML original\n- Generar un AML actualizado con las modificaciones",
|
||||
"hidden": false
|
||||
},
|
||||
"analyze_u32810_specific.py": {
|
||||
"display_name": "analyze_u32810_specific",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"compare_u32810_telegrams.py": {
|
||||
"display_name": "compare_u32810_telegrams",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"comprehensive_address_search.py": {
|
||||
"display_name": "comprehensive_address_search",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"find_address_differences.py": {
|
||||
"display_name": "find_address_differences",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"find_u32810_addresses.py": {
|
||||
"display_name": "find_u32810_addresses",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"debug_hierarchy.py": {
|
||||
"display_name": "debug_hierarchy",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"debug_io_processing.py": {
|
||||
"display_name": "debug_io_processing",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
},
|
||||
"debug_table_generation.py": {
|
||||
"display_name": "debug_table_generation",
|
||||
"short_description": "Sin descripción corta.",
|
||||
"long_description": "",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"path": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2",
|
||||
"path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
|
||||
"history": [
|
||||
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
|
||||
"D:\\Proyectos\\Scripts\\ParamManagerScripts\\backend\\script_groups\\IO_adaptation\\example",
|
||||
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2"
|
||||
]
|
||||
}
|
|
@ -17,7 +17,12 @@ sys.path.append(script_root)
|
|||
from backend.script_utils import load_configuration
|
||||
|
||||
# --- Configuration ---
|
||||
TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version
|
||||
# Supported TIA Portal versions mapping (extension -> version)
|
||||
SUPPORTED_TIA_VERSIONS = {
|
||||
".ap18": "18.0",
|
||||
".ap19": "19.0",
|
||||
".ap20": "20.0"
|
||||
}
|
||||
|
||||
# --- TIA Scripting Import Handling ---
|
||||
# (Same import handling as the previous script)
|
||||
|
@ -40,18 +45,43 @@ except Exception as e:
|
|||
# --- Functions ---
|
||||
|
||||
|
||||
def get_supported_filetypes():
|
||||
"""Returns the supported file types for TIA Portal projects."""
|
||||
filetypes = []
|
||||
for ext, version in SUPPORTED_TIA_VERSIONS.items():
|
||||
version_major = version.split('.')[0]
|
||||
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}"))
|
||||
|
||||
# Add option to show all supported files
|
||||
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()])
|
||||
filetypes.insert(0, ("All TIA Portal Projects", all_extensions))
|
||||
|
||||
return filetypes
|
||||
|
||||
|
||||
def detect_tia_version(project_file_path):
|
||||
"""Detects TIA Portal version based on file extension."""
|
||||
file_path = Path(project_file_path)
|
||||
file_extension = file_path.suffix.lower()
|
||||
|
||||
if file_extension in SUPPORTED_TIA_VERSIONS:
|
||||
detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
|
||||
print(f"Detected TIA Portal version: {detected_version} (from extension {file_extension})")
|
||||
return detected_version
|
||||
else:
|
||||
print(f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}")
|
||||
# Default to version 18.0 for backward compatibility
|
||||
print("Defaulting to TIA Portal V18.0")
|
||||
return "18.0"
|
||||
|
||||
|
||||
def select_project_file():
|
||||
"""Opens a dialog to select a TIA Portal project file."""
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="Select TIA Portal Project File",
|
||||
filetypes=[
|
||||
(
|
||||
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
|
||||
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
|
||||
)
|
||||
],
|
||||
filetypes=get_supported_filetypes(),
|
||||
)
|
||||
root.destroy()
|
||||
if not file_path:
|
||||
|
@ -242,6 +272,9 @@ if __name__ == "__main__":
|
|||
print(f"\nSelected Project: {project_file}")
|
||||
print(f"Using Output Directory (Working Directory): {output_dir}")
|
||||
|
||||
# 2. Detect TIA Portal version from project file
|
||||
tia_version = detect_tia_version(project_file)
|
||||
|
||||
# Define output file names using Path object
|
||||
project_path = Path(project_file)
|
||||
project_base_name = project_path.stem # Get filename without extension
|
||||
|
@ -260,15 +293,15 @@ if __name__ == "__main__":
|
|||
cax_export_successful = False
|
||||
|
||||
try:
|
||||
# 2. Connect to TIA Portal
|
||||
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
|
||||
# 3. Connect to TIA Portal with detected version
|
||||
print(f"\nConnecting to TIA Portal V{tia_version}...")
|
||||
portal_instance = ts.open_portal(
|
||||
version=TIA_PORTAL_VERSION,
|
||||
version=tia_version,
|
||||
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
|
||||
)
|
||||
print("Connected.")
|
||||
|
||||
# 3. Open Project
|
||||
# 4. Open Project
|
||||
print(
|
||||
f"Opening project: {project_path.name}..."
|
||||
) # Use Path object's name attribute
|
||||
|
@ -282,7 +315,7 @@ if __name__ == "__main__":
|
|||
raise Exception("Failed to open or get the specified project.")
|
||||
print("Project opened.")
|
||||
|
||||
# 4. Export CAx Data (Project Level)
|
||||
# 5. Export CAx Data (Project Level)
|
||||
print(f"Exporting CAx data for the project to {aml_file}...")
|
||||
# Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
@ -322,7 +355,7 @@ if __name__ == "__main__":
|
|||
except Exception as close_ex:
|
||||
print(f"Error during TIA Portal cleanup: {close_ex}")
|
||||
|
||||
# 5. Parse AML and Generate Markdown (only if export was successful)
|
||||
# 6. Parse AML and Generate Markdown (only if export was successful)
|
||||
if cax_export_successful:
|
||||
if aml_file.exists(): # Use Path object's exists() method
|
||||
parse_aml_to_markdown(aml_file, md_file)
|
||||
|
|
|
@ -12,6 +12,7 @@ import json
|
|||
from pathlib import Path
|
||||
import re
|
||||
import math # Needed for ceil
|
||||
import pandas as pd # For Excel generation
|
||||
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
@ -221,6 +222,7 @@ def extract_aml_data(root):
|
|||
is_subnet_by_role = any("SUBNET" in rc.upper() for rc in role_classes)
|
||||
if is_subnet_by_role:
|
||||
is_network = True
|
||||
# Check role classes first
|
||||
for rc in role_classes:
|
||||
rc_upper = rc.upper()
|
||||
if "PROFINET" in rc_upper or "ETHERNET" in rc_upper:
|
||||
|
@ -229,12 +231,23 @@ def extract_aml_data(root):
|
|||
elif "PROFIBUS" in rc_upper:
|
||||
net_type = "Profibus"
|
||||
break
|
||||
|
||||
# If still unknown, check the Type attribute (crucial for PROFINET)
|
||||
if net_type == "Unknown":
|
||||
type_attr = device.get("attributes", {}).get("Type", "")
|
||||
if type_attr.upper() == "ETHERNET":
|
||||
net_type = "Ethernet/Profinet"
|
||||
elif type_attr.upper() == "PROFIBUS":
|
||||
net_type = "Profibus"
|
||||
|
||||
# Finally, check device name as fallback
|
||||
if net_type == "Unknown":
|
||||
if "PROFIBUS" in device["name"].upper():
|
||||
net_type = "Profibus"
|
||||
elif (
|
||||
"ETHERNET" in device["name"].upper()
|
||||
or "PROFINET" in device["name"].upper()
|
||||
or "PN/IE" in device["name"].upper() # Add common PROFINET naming pattern
|
||||
):
|
||||
net_type = "Ethernet/Profinet"
|
||||
if is_network:
|
||||
|
@ -328,6 +341,33 @@ def extract_aml_data(root):
|
|||
and interface_info["parent_id"] in data["plcs"]
|
||||
):
|
||||
potential_plc_id = interface_info["parent_id"]
|
||||
# Enhanced PLC search: look for PLCs in the entire hierarchy
|
||||
if not potential_plc_id:
|
||||
# Search for PLCs in the hierarchy starting from linked_device_id
|
||||
current_search_id = linked_device_id
|
||||
search_depth = 0
|
||||
max_search_depth = 10
|
||||
|
||||
while current_search_id and search_depth < max_search_depth:
|
||||
# Check current device and all its children for PLCs
|
||||
device_to_check = data["devices"].get(current_search_id)
|
||||
if device_to_check:
|
||||
# Check if current device has PLCs as children
|
||||
for child_id in device_to_check.get("children_ids", []):
|
||||
if child_id in data["plcs"]:
|
||||
potential_plc_id = child_id
|
||||
print(f" --> Found PLC in children: {data['plcs'][child_id].get('name', 'Unknown PLC')} (ID: {child_id})")
|
||||
break
|
||||
|
||||
if potential_plc_id:
|
||||
break
|
||||
|
||||
# Move up to parent
|
||||
current_search_id = device_to_check.get("parent_id")
|
||||
search_depth += 1
|
||||
else:
|
||||
break
|
||||
|
||||
if potential_plc_id:
|
||||
plc_object = data["plcs"][potential_plc_id]
|
||||
if "connected_networks" not in plc_object:
|
||||
|
@ -497,6 +537,470 @@ def generate_io_summary_file(all_plc_io_for_table, md_file_path, plc_name, proje
|
|||
return hardware_file_path
|
||||
|
||||
|
||||
# --- generate_io_excel_report function ---
|
||||
def generate_io_excel_report(project_data, excel_file_path, target_plc_id, output_root_path):
|
||||
"""
|
||||
Genera un archivo Excel con información detallada de IOs por nodos del PLC.
|
||||
"""
|
||||
|
||||
plc_info = project_data.get("plcs", {}).get(target_plc_id)
|
||||
if not plc_info:
|
||||
print(f"PLC ID '{target_plc_id}' not found in project data.")
|
||||
return
|
||||
|
||||
plc_name = plc_info.get('name', target_plc_id)
|
||||
print(f"Generating Excel IO report for PLC: {plc_name}")
|
||||
|
||||
# Lista para almacenar todas las filas del Excel
|
||||
excel_rows = []
|
||||
|
||||
# v32.5: First, process PLC's local modules (modules in the same rack/structure as the PLC)
|
||||
plc_parent_id = plc_info.get("parent_id")
|
||||
if plc_parent_id:
|
||||
# Find sibling modules in the same parent structure (rack)
|
||||
for dev_id, dev_info in project_data.get("devices", {}).items():
|
||||
if dev_info.get("parent_id") == plc_parent_id and dev_id != target_plc_id:
|
||||
# This is a sibling module of the PLC
|
||||
module_context = {
|
||||
"id": dev_id,
|
||||
"name": dev_info.get("name", dev_id),
|
||||
"order_number": dev_info.get("order_number", "N/A"),
|
||||
"type_name": dev_info.get("type_name", "N/A")
|
||||
}
|
||||
module_ios = find_io_recursively(dev_id, project_data, module_context)
|
||||
if module_ios:
|
||||
# Agrupar IOs por módulo para este módulo local
|
||||
ios_by_module = {}
|
||||
for addr_info in module_ios:
|
||||
module_id = addr_info.get("module_id")
|
||||
if module_id not in ios_by_module:
|
||||
ios_by_module[module_id] = {
|
||||
'module_info': {
|
||||
'name': addr_info.get('module_name', '?'),
|
||||
'type': addr_info.get('module_type_name', 'N/A'),
|
||||
'order': addr_info.get('module_order_number', 'N/A'),
|
||||
'position': addr_info.get('module_pos', 'N/A')
|
||||
},
|
||||
'inputs': [],
|
||||
'outputs': []
|
||||
}
|
||||
|
||||
# Clasificar IO como input u output
|
||||
io_type = addr_info.get("type", "").lower()
|
||||
if io_type == "input":
|
||||
ios_by_module[module_id]['inputs'].append(addr_info)
|
||||
elif io_type == "output":
|
||||
ios_by_module[module_id]['outputs'].append(addr_info)
|
||||
|
||||
# Crear una fila por cada módulo local con IOs
|
||||
for module_id, module_data in ios_by_module.items():
|
||||
module_info = module_data['module_info']
|
||||
|
||||
# Calcular direcciones de entrada - Start + Word Count
|
||||
input_start_addr = 'N/A'
|
||||
input_word_count = 0
|
||||
total_input_bits = 0
|
||||
|
||||
for addr_info in module_data['inputs']:
|
||||
start_str = addr_info.get("start", "?")
|
||||
length_str = addr_info.get("length", "?")
|
||||
try:
|
||||
start_byte = int(start_str)
|
||||
length_bits = int(length_str)
|
||||
length_bytes = math.ceil(length_bits / 8.0)
|
||||
if length_bits > 0 and length_bytes == 0:
|
||||
length_bytes = 1
|
||||
|
||||
# Para múltiples rangos, tomar el primer inicio y sumar words
|
||||
if input_start_addr == 'N/A':
|
||||
input_start_addr = start_byte
|
||||
else:
|
||||
input_start_addr = min(input_start_addr, start_byte)
|
||||
|
||||
# Convertir bytes a words (asumiendo words de 2 bytes)
|
||||
word_count = math.ceil(length_bytes / 2.0)
|
||||
input_word_count += word_count
|
||||
total_input_bits += length_bits
|
||||
except:
|
||||
# En caso de error, mantener N/A
|
||||
pass
|
||||
|
||||
# Calcular direcciones de salida - Start + Word Count
|
||||
output_start_addr = 'N/A'
|
||||
output_word_count = 0
|
||||
total_output_bits = 0
|
||||
|
||||
for addr_info in module_data['outputs']:
|
||||
start_str = addr_info.get("start", "?")
|
||||
length_str = addr_info.get("length", "?")
|
||||
try:
|
||||
start_byte = int(start_str)
|
||||
length_bits = int(length_str)
|
||||
length_bytes = math.ceil(length_bits / 8.0)
|
||||
if length_bits > 0 and length_bytes == 0:
|
||||
length_bytes = 1
|
||||
|
||||
# Para múltiples rangos, tomar el primer inicio y sumar words
|
||||
if output_start_addr == 'N/A':
|
||||
output_start_addr = start_byte
|
||||
else:
|
||||
output_start_addr = min(output_start_addr, start_byte)
|
||||
|
||||
# Convertir bytes a words (asumiendo words de 2 bytes)
|
||||
word_count = math.ceil(length_bytes / 2.0)
|
||||
output_word_count += word_count
|
||||
total_output_bits += length_bits
|
||||
except:
|
||||
# En caso de error, mantener N/A
|
||||
pass
|
||||
|
||||
excel_rows.append({
|
||||
'PLC Name': plc_name,
|
||||
'Network Path': f"Local I/O -> {module_info['name']}",
|
||||
'Network Type': 'Local I/O',
|
||||
'Device Address': 'Local',
|
||||
'Device Name': f"PLC {plc_name}",
|
||||
'Device Type': module_info['type'],
|
||||
'Order Number': module_info['order'],
|
||||
'Firmware Version': 'N/A',
|
||||
'Position': module_info['position'],
|
||||
'IO Input Start Address': input_start_addr,
|
||||
'IO Input Word Count': input_word_count if input_word_count > 0 else 'N/A',
|
||||
'IO Output Start Address': output_start_addr,
|
||||
'IO Output Word Count': output_word_count if output_word_count > 0 else 'N/A',
|
||||
'Total Input Bits': total_input_bits,
|
||||
'Total Output Bits': total_output_bits,
|
||||
'Module Name': module_info['name'],
|
||||
'Module Type': module_info['type'],
|
||||
'Module Order Number': module_info['order']
|
||||
})
|
||||
|
||||
# Procesar las redes conectadas al PLC
|
||||
plc_networks = plc_info.get("connected_networks", {})
|
||||
|
||||
if not plc_networks and not excel_rows:
|
||||
# Si no hay redes, crear una fila básica del PLC
|
||||
excel_rows.append({
|
||||
'PLC Name': plc_name,
|
||||
'Network Path': 'No networks connected',
|
||||
'Network Type': 'N/A',
|
||||
'Device Address': 'N/A',
|
||||
'Device Name': plc_name,
|
||||
'Device Type': plc_info.get("type_name", "N/A"),
|
||||
'Order Number': plc_info.get("order_number", "N/A"),
|
||||
'Firmware Version': plc_info.get("firmware_version", "N/A"),
|
||||
'Position': plc_info.get("position", "N/A"),
|
||||
'IO Input Start Address': 'N/A',
|
||||
'IO Input Word Count': 'N/A',
|
||||
'IO Output Start Address': 'N/A',
|
||||
'IO Output Word Count': 'N/A',
|
||||
'Total Input Bits': 0,
|
||||
'Total Output Bits': 0,
|
||||
'Module Name': 'N/A',
|
||||
'Module Type': 'N/A',
|
||||
'Module Order Number': 'N/A'
|
||||
})
|
||||
else:
|
||||
# Procesar cada red conectada
|
||||
for net_id, plc_addr_on_net in plc_networks.items():
|
||||
net_info = project_data.get("networks", {}).get(net_id)
|
||||
if not net_info:
|
||||
continue
|
||||
|
||||
network_name = net_info.get('name', net_id)
|
||||
network_type = net_info.get('type', 'Unknown')
|
||||
devices_on_net = net_info.get("devices_on_net", {})
|
||||
|
||||
# Identificar nodos que pertenecen al PLC para excluirlos de la lista de dispositivos
|
||||
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)
|
||||
|
||||
# Filtrar dispositivos que no son interfaces del PLC
|
||||
other_devices = [
|
||||
(node_id, node_addr)
|
||||
for node_id, node_addr in devices_on_net.items()
|
||||
if node_id not in plc_interface_and_node_ids
|
||||
]
|
||||
|
||||
if not other_devices:
|
||||
# Si no hay otros dispositivos, crear fila solo para el PLC en esta red
|
||||
excel_rows.append({
|
||||
'PLC Name': plc_name,
|
||||
'Network Path': f"{network_name} -> {plc_name}",
|
||||
'Network Type': network_type,
|
||||
'Device Address': plc_addr_on_net,
|
||||
'Device Name': plc_name,
|
||||
'Device Type': plc_info.get("type_name", "N/A"),
|
||||
'Order Number': plc_info.get("order_number", "N/A"),
|
||||
'Firmware Version': plc_info.get("firmware_version", "N/A"),
|
||||
'Position': plc_info.get("position", "N/A"),
|
||||
'IO Input Start Address': 'N/A',
|
||||
'IO Input Word Count': 'N/A',
|
||||
'IO Output Start Address': 'N/A',
|
||||
'IO Output Word Count': 'N/A',
|
||||
'Total Input Bits': 0,
|
||||
'Total Output Bits': 0,
|
||||
'Module Name': 'PLC Main Unit',
|
||||
'Module Type': plc_info.get("type_name", "N/A"),
|
||||
'Module Order Number': plc_info.get("order_number", "N/A")
|
||||
})
|
||||
else:
|
||||
# Procesar cada dispositivo en la red
|
||||
for node_id, node_addr in other_devices:
|
||||
node_info = project_data.get("devices", {}).get(node_id)
|
||||
if not node_info:
|
||||
continue
|
||||
|
||||
# Determinar la estructura jerárquica del dispositivo
|
||||
interface_id = node_info.get("parent_id")
|
||||
interface_info = None
|
||||
actual_device_id = None
|
||||
actual_device_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)
|
||||
|
||||
# Determinar qué información mostrar
|
||||
display_info = actual_device_info if actual_device_info else (interface_info if interface_info else node_info)
|
||||
display_id = actual_device_id if actual_device_info else (interface_id if interface_info else node_id)
|
||||
|
||||
device_name = display_info.get("name", display_id)
|
||||
device_type = display_info.get("type_name", "N/A")
|
||||
device_order = display_info.get("order_number", "N/A")
|
||||
device_position = display_info.get("position", "N/A")
|
||||
firmware_version = display_info.get("firmware_version", "N/A")
|
||||
|
||||
# Construir el path de red
|
||||
network_path = f"{network_name} ({network_type}) -> {device_name} @ {node_addr}"
|
||||
|
||||
# Buscar IOs recursivamente
|
||||
io_search_root_id = display_id
|
||||
io_search_root_info = project_data.get("devices", {}).get(io_search_root_id)
|
||||
|
||||
aggregated_io_addresses = []
|
||||
|
||||
# Buscar IOs en la estructura padre si existe
|
||||
parent_structure_id = io_search_root_info.get("parent_id") if io_search_root_info else None
|
||||
|
||||
if parent_structure_id:
|
||||
# Buscar IOs en dispositivos hermanos bajo la misma estructura padre
|
||||
for dev_scan_id, dev_scan_info in project_data.get("devices", {}).items():
|
||||
if dev_scan_info.get("parent_id") == parent_structure_id:
|
||||
module_context = {
|
||||
"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)
|
||||
aggregated_io_addresses.extend(io_from_sibling)
|
||||
elif io_search_root_id:
|
||||
# Buscar IOs directamente en el dispositivo
|
||||
module_context = {
|
||||
"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)
|
||||
|
||||
# Procesar IOs por módulo
|
||||
if aggregated_io_addresses:
|
||||
# Agrupar IOs por módulo
|
||||
ios_by_module = {}
|
||||
for addr_info in aggregated_io_addresses:
|
||||
module_id = addr_info.get("module_id")
|
||||
if module_id not in ios_by_module:
|
||||
ios_by_module[module_id] = {
|
||||
'module_info': {
|
||||
'name': addr_info.get('module_name', '?'),
|
||||
'type': addr_info.get('module_type_name', 'N/A'),
|
||||
'order': addr_info.get('module_order_number', 'N/A'),
|
||||
'position': addr_info.get('module_pos', 'N/A')
|
||||
},
|
||||
'inputs': [],
|
||||
'outputs': []
|
||||
}
|
||||
|
||||
# Clasificar IO como input u output
|
||||
io_type = addr_info.get("type", "").lower()
|
||||
if io_type == "input":
|
||||
ios_by_module[module_id]['inputs'].append(addr_info)
|
||||
elif io_type == "output":
|
||||
ios_by_module[module_id]['outputs'].append(addr_info)
|
||||
|
||||
# Crear una fila por cada módulo con IOs
|
||||
for module_id, module_data in ios_by_module.items():
|
||||
module_info = module_data['module_info']
|
||||
|
||||
# Calcular direcciones de entrada - Start + Word Count
|
||||
input_start_addr = 'N/A'
|
||||
input_word_count = 0
|
||||
total_input_bits = 0
|
||||
|
||||
for addr_info in module_data['inputs']:
|
||||
start_str = addr_info.get("start", "?")
|
||||
length_str = addr_info.get("length", "?")
|
||||
try:
|
||||
start_byte = int(start_str)
|
||||
length_bits = int(length_str)
|
||||
length_bytes = math.ceil(length_bits / 8.0)
|
||||
if length_bits > 0 and length_bytes == 0:
|
||||
length_bytes = 1
|
||||
|
||||
# Para múltiples rangos, tomar el primer inicio y sumar words
|
||||
if input_start_addr == 'N/A':
|
||||
input_start_addr = start_byte
|
||||
else:
|
||||
input_start_addr = min(input_start_addr, start_byte)
|
||||
|
||||
# Convertir bytes a words (asumiendo words de 2 bytes)
|
||||
word_count = math.ceil(length_bytes / 2.0)
|
||||
input_word_count += word_count
|
||||
total_input_bits += length_bits
|
||||
except:
|
||||
# En caso de error, mantener N/A
|
||||
pass
|
||||
|
||||
# Calcular direcciones de salida - Start + Word Count
|
||||
output_start_addr = 'N/A'
|
||||
output_word_count = 0
|
||||
total_output_bits = 0
|
||||
|
||||
for addr_info in module_data['outputs']:
|
||||
start_str = addr_info.get("start", "?")
|
||||
length_str = addr_info.get("length", "?")
|
||||
try:
|
||||
start_byte = int(start_str)
|
||||
length_bits = int(length_str)
|
||||
length_bytes = math.ceil(length_bits / 8.0)
|
||||
if length_bits > 0 and length_bytes == 0:
|
||||
length_bytes = 1
|
||||
|
||||
# Para múltiples rangos, tomar el primer inicio y sumar words
|
||||
if output_start_addr == 'N/A':
|
||||
output_start_addr = start_byte
|
||||
else:
|
||||
output_start_addr = min(output_start_addr, start_byte)
|
||||
|
||||
# Convertir bytes a words (asumiendo words de 2 bytes)
|
||||
word_count = math.ceil(length_bytes / 2.0)
|
||||
output_word_count += word_count
|
||||
total_output_bits += length_bits
|
||||
except:
|
||||
# En caso de error, mantener N/A
|
||||
pass
|
||||
|
||||
excel_rows.append({
|
||||
'PLC Name': plc_name,
|
||||
'Network Path': network_path,
|
||||
'Network Type': network_type,
|
||||
'Device Address': node_addr,
|
||||
'Device Name': device_name,
|
||||
'Device Type': device_type,
|
||||
'Order Number': device_order,
|
||||
'Firmware Version': firmware_version,
|
||||
'Position': device_position,
|
||||
'IO Input Start Address': input_start_addr,
|
||||
'IO Input Word Count': input_word_count if input_word_count > 0 else 'N/A',
|
||||
'IO Output Start Address': output_start_addr,
|
||||
'IO Output Word Count': output_word_count if output_word_count > 0 else 'N/A',
|
||||
'Total Input Bits': total_input_bits,
|
||||
'Total Output Bits': total_output_bits,
|
||||
'Module Name': module_info['name'],
|
||||
'Module Type': module_info['type'],
|
||||
'Module Order Number': module_info['order']
|
||||
})
|
||||
else:
|
||||
# Dispositivo sin IOs
|
||||
excel_rows.append({
|
||||
'PLC Name': plc_name,
|
||||
'Network Path': network_path,
|
||||
'Network Type': network_type,
|
||||
'Device Address': node_addr,
|
||||
'Device Name': device_name,
|
||||
'Device Type': device_type,
|
||||
'Order Number': device_order,
|
||||
'Firmware Version': firmware_version,
|
||||
'Position': device_position,
|
||||
'IO Input Start Address': 'N/A',
|
||||
'IO Input Word Count': 'N/A',
|
||||
'IO Output Start Address': 'N/A',
|
||||
'IO Output Word Count': 'N/A',
|
||||
'Total Input Bits': 0,
|
||||
'Total Output Bits': 0,
|
||||
'Module Name': 'N/A',
|
||||
'Module Type': 'N/A',
|
||||
'Module Order Number': 'N/A'
|
||||
})
|
||||
|
||||
# Crear DataFrame y guardar Excel
|
||||
if excel_rows:
|
||||
df = pd.DataFrame(excel_rows)
|
||||
|
||||
# Agregar columna de ID único para compatibilidad con x7_update_CAx
|
||||
df['Unique_ID'] = df['PLC Name'] + "+" + df['Device Name']
|
||||
|
||||
# Reordenar columnas para mejor legibilidad
|
||||
column_order = [
|
||||
'PLC Name', 'Network Path', 'Network Type', 'Device Address', 'Device Name',
|
||||
'Device Type', 'Order Number', 'Firmware Version', 'Position',
|
||||
'Module Name', 'Module Type', 'Module Order Number',
|
||||
'IO Input Start Address', 'IO Input Word Count', 'IO Output Start Address', 'IO Output Word Count',
|
||||
'Total Input Bits', 'Total Output Bits', 'Unique_ID' # Agregar al final para compatibilidad
|
||||
]
|
||||
df = df.reindex(columns=column_order)
|
||||
|
||||
try:
|
||||
# Convertir columnas numéricas específicas a int para evitar decimales
|
||||
numeric_columns = [
|
||||
'IO Input Start Address', 'IO Input Word Count',
|
||||
'IO Output Start Address', 'IO Output Word Count',
|
||||
'Total Input Bits', 'Total Output Bits'
|
||||
]
|
||||
|
||||
for col in numeric_columns:
|
||||
if col in df.columns:
|
||||
# Convertir a int manteniendo N/A como string
|
||||
df[col] = df[col].apply(lambda x: int(x) if isinstance(x, (int, float)) and pd.notna(x) and x != 'N/A' else x)
|
||||
|
||||
# Guardar como Excel con formato
|
||||
with pd.ExcelWriter(excel_file_path, engine='openpyxl') as writer:
|
||||
df.to_excel(writer, sheet_name='IO Report', index=False)
|
||||
|
||||
# Ajustar ancho de columnas
|
||||
worksheet = writer.sheets['IO Report']
|
||||
for column in worksheet.columns:
|
||||
max_length = 0
|
||||
column_letter = column[0].column_letter
|
||||
for cell in column:
|
||||
try:
|
||||
if len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
adjusted_width = min(max_length + 2, 50) # Máximo 50 caracteres
|
||||
worksheet.column_dimensions[column_letter].width = adjusted_width
|
||||
|
||||
print(f"Excel IO report saved to: {excel_file_path}")
|
||||
print(f"Total rows in report: {len(excel_rows)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR saving Excel file {excel_file_path}: {e}")
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print("No data to write to Excel file.")
|
||||
|
||||
|
||||
# --- generate_markdown_tree function ---
|
||||
def generate_markdown_tree(project_data, md_file_path, target_plc_id, output_root_path):
|
||||
"""(Modified) Generates hierarchical Markdown for a specific PLC."""
|
||||
|
@ -533,6 +1037,92 @@ def generate_markdown_tree(project_data, md_file_path, target_plc_id, output_roo
|
|||
if firmware and firmware != "N/A":
|
||||
markdown_lines.append(f"- **Firmware:** `{firmware}`")
|
||||
|
||||
# v32.5: Process PLC's local modules (modules in the same rack/structure as the PLC)
|
||||
plc_local_modules_io = []
|
||||
plc_parent_id = plc_info.get("parent_id")
|
||||
if plc_parent_id:
|
||||
# Find sibling modules in the same parent structure (rack)
|
||||
for dev_id, dev_info in project_data.get("devices", {}).items():
|
||||
if dev_info.get("parent_id") == plc_parent_id and dev_id != target_plc_id:
|
||||
# This is a sibling module of the PLC
|
||||
module_context = {
|
||||
"id": dev_id,
|
||||
"name": dev_info.get("name", dev_id),
|
||||
"order_number": dev_info.get("order_number", "N/A"),
|
||||
"type_name": dev_info.get("type_name", "N/A")
|
||||
}
|
||||
module_ios = find_io_recursively(dev_id, project_data, module_context)
|
||||
if module_ios:
|
||||
for addr_info in module_ios:
|
||||
# Add to table data
|
||||
length_bits = 0
|
||||
siemens_addr = "FMT_ERROR"
|
||||
try:
|
||||
start_byte = int(addr_info.get("start", "0"))
|
||||
length_bits = int(addr_info.get("length", "0"))
|
||||
io_type = addr_info.get("type", "?")
|
||||
length_bytes = math.ceil(length_bits / 8.0)
|
||||
if length_bits > 0 and length_bytes == 0:
|
||||
length_bytes = 1
|
||||
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({addr_info.get('start', '?')},{addr_info.get('length', '?')})"
|
||||
|
||||
plc_local_modules_io.append({
|
||||
"Network": "PLC Local Modules",
|
||||
"Network Type": "Local I/O",
|
||||
"Device Address": "Local",
|
||||
"Device Name": f"PLC {plc_info.get('name', target_plc_id)}",
|
||||
"Sub-Device": addr_info.get('module_name', '?'),
|
||||
"Sub-Device OrderNo": addr_info.get('module_order_number', 'N/A'),
|
||||
"Sub-Device Type": addr_info.get('module_type_name', 'N/A'),
|
||||
"IO Type": addr_info.get("type", "?"),
|
||||
"IO Address": siemens_addr,
|
||||
"Number of Bits": length_bits,
|
||||
"SortKey": (
|
||||
"PLC Local Modules", # Network name for sorting
|
||||
[0], # Device sort key (always first)
|
||||
(
|
||||
int(addr_info.get("module_pos", "9999"))
|
||||
if str(addr_info.get("module_pos", "9999")).isdigit()
|
||||
else 9999
|
||||
),
|
||||
addr_info.get("module_name", ""),
|
||||
addr_info.get("type", ""),
|
||||
(
|
||||
int(addr_info.get("start", "0"))
|
||||
if str(addr_info.get("start", "0")).isdigit()
|
||||
else float("inf")
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
# Add PLC local modules to the main IO list
|
||||
all_plc_io_for_table.extend(plc_local_modules_io)
|
||||
|
||||
# Show PLC local modules in markdown
|
||||
if plc_local_modules_io:
|
||||
markdown_lines.append("\n- **Local I/O Modules (same rack as PLC):**")
|
||||
modules_by_name = {}
|
||||
for io_data in plc_local_modules_io:
|
||||
module_name = io_data["Sub-Device"]
|
||||
if module_name not in modules_by_name:
|
||||
modules_by_name[module_name] = []
|
||||
modules_by_name[module_name].append(io_data)
|
||||
|
||||
for module_name in sorted(modules_by_name.keys()):
|
||||
module_ios = modules_by_name[module_name]
|
||||
first_io = module_ios[0]
|
||||
markdown_lines.append(f" - **{module_name}** (Type: `{first_io['Sub-Device Type']}`, OrderNo: `{first_io['Sub-Device OrderNo']}`)")
|
||||
for io_data in sorted(module_ios, key=lambda x: x['SortKey']):
|
||||
markdown_lines.append(f" - `{io_data['IO Address']}` ({io_data['IO Type']}, {io_data['Number of Bits']} bits)")
|
||||
|
||||
plc_networks = plc_info.get("connected_networks", {})
|
||||
markdown_lines.append("\n- **Networks:**")
|
||||
if not plc_networks:
|
||||
|
@ -1151,25 +1741,34 @@ def sanitize_filename(name):
|
|||
|
||||
# --- Main Execution ---
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
configs = load_configuration()
|
||||
working_directory = configs.get("working_directory")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load configuration (frontend not running): {e}")
|
||||
configs = {}
|
||||
working_directory = None
|
||||
|
||||
script_version = "v31.1 - Corrected IO Summary Table Initialization" # Updated version
|
||||
script_version = "v32.5 - Include PLC Local Modules in IO Summary" # Updated version
|
||||
print(
|
||||
f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---"
|
||||
)
|
||||
|
||||
# Validate working directory
|
||||
# Validate working directory with .debug fallback
|
||||
if not working_directory or not os.path.isdir(working_directory):
|
||||
print("ERROR: Working directory not set or invalid in configuration.")
|
||||
print("Attempting to use script's directory as fallback.")
|
||||
# Fallback to script's directory or current directory if needed
|
||||
working_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
if not os.path.isdir(working_directory):
|
||||
working_directory = os.getcwd()
|
||||
print(f"Using fallback directory: {working_directory}")
|
||||
# Optionally, prompt user to select a working directory here if critical
|
||||
# output_dir = select_output_directory() # Keep this if you want user selection on failure
|
||||
print("Working directory not set or invalid in configuration.")
|
||||
print("Using .debug directory as fallback for direct script execution.")
|
||||
|
||||
# Fallback to .debug directory under script location
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
debug_dir = os.path.join(script_dir, ".debug")
|
||||
|
||||
# Create .debug directory if it doesn't exist
|
||||
os.makedirs(debug_dir, exist_ok=True)
|
||||
working_directory = debug_dir
|
||||
print(f"Using debug directory: {working_directory}")
|
||||
else:
|
||||
print(f"Using configured working directory: {working_directory}")
|
||||
|
||||
# Use working_directory as the output directory
|
||||
output_dir = working_directory
|
||||
|
@ -1227,6 +1826,12 @@ if __name__ == "__main__":
|
|||
print(f" Generating Hardware Tree for PLC '{plc_name_original}' (ID: {plc_id}) at: {output_plc_md_file.resolve()}")
|
||||
# Pass output_path as the root directory for Hardware.md placement
|
||||
generate_markdown_tree(project_data, str(output_plc_md_file), plc_id, str(output_path))
|
||||
|
||||
# Generate Excel IO report for this PLC
|
||||
excel_io_filename = f"{input_path.stem}_IO_Report.xlsx"
|
||||
output_excel_file = plc_doc_dir / excel_io_filename
|
||||
print(f" Generating Excel IO Report for PLC '{plc_name_original}' (ID: {plc_id}) at: {output_excel_file.resolve()}")
|
||||
generate_io_excel_report(project_data, str(output_excel_file), plc_id, str(output_path))
|
||||
else:
|
||||
print("\nFailed to process AML data. Halting before generating PLC-specific trees.")
|
||||
|
||||
|
|
|
@ -0,0 +1,696 @@
|
|||
"""
|
||||
x7_update_CAx.py : Script que actualiza un archivo AML original basándose en las modificaciones
|
||||
hechas en el archivo Excel de IOs generado por x2_process_CAx.py
|
||||
|
||||
Pipeline:
|
||||
1. Lee el AML original
|
||||
2. Lee el Excel modificado
|
||||
3. Genera Excel de referencia desde el AML para comparar
|
||||
4. Valida que la estructura base sea la misma (mismos nodos, nombres)
|
||||
5. Identifica cambios en IOs, direcciones IP, etc.
|
||||
6. Aplica los cambios al AML original
|
||||
7. Genera un nuevo archivo AML con sufijo "_updated"
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox
|
||||
import traceback
|
||||
from lxml import etree as ET
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
import re
|
||||
import math
|
||||
import tempfile
|
||||
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
# Import functions from x2_process_CAx
|
||||
from x2_process_CAx import (
|
||||
extract_aml_data,
|
||||
generate_io_excel_report,
|
||||
sanitize_filename
|
||||
)
|
||||
|
||||
|
||||
def select_aml_file(title="Select Original AML File", initial_dir=None):
|
||||
"""Abre un diálogo para seleccionar un archivo AML."""
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
file_path = filedialog.askopenfilename(
|
||||
title=title,
|
||||
filetypes=[("AML Files", "*.aml"), ("All Files", "*.*")],
|
||||
initialdir=initial_dir
|
||||
)
|
||||
root.destroy()
|
||||
if not file_path:
|
||||
return None
|
||||
return file_path
|
||||
|
||||
|
||||
def select_excel_file(title="Select Modified Excel File", initial_dir=None):
|
||||
"""Abre un diálogo para seleccionar un archivo Excel."""
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
file_path = filedialog.askopenfilename(
|
||||
title=title,
|
||||
filetypes=[("Excel Files", "*.xlsx"), ("Excel Files", "*.xls"), ("All Files", "*.*")],
|
||||
initialdir=initial_dir
|
||||
)
|
||||
root.destroy()
|
||||
if not file_path:
|
||||
return None
|
||||
return file_path
|
||||
|
||||
|
||||
def generate_reference_excel_from_aml(aml_file_path, temp_dir):
|
||||
"""
|
||||
Genera un Excel de referencia desde el AML para comparar con el Excel modificado.
|
||||
Retorna el path al Excel generado y los project_data.
|
||||
"""
|
||||
print("Generando Excel de referencia desde AML original...")
|
||||
|
||||
# Extraer datos del AML
|
||||
try:
|
||||
parser = ET.XMLParser(remove_blank_text=True, huge_tree=True)
|
||||
tree = ET.parse(aml_file_path, parser)
|
||||
root = tree.getroot()
|
||||
project_data = extract_aml_data(root)
|
||||
except Exception as e:
|
||||
print(f"ERROR procesando archivo AML {aml_file_path}: {e}")
|
||||
return None, None
|
||||
|
||||
if not project_data or not project_data.get("plcs"):
|
||||
print("No se encontraron PLCs en el archivo AML.")
|
||||
return None, None
|
||||
|
||||
# Generar Excel para cada PLC y combinar en uno solo
|
||||
all_excel_rows = []
|
||||
|
||||
for plc_id, plc_data in project_data.get("plcs", {}).items():
|
||||
# Crear archivo temporal para este PLC
|
||||
temp_excel_path = os.path.join(temp_dir, f"temp_plc_{plc_id}.xlsx")
|
||||
|
||||
# Generar Excel para este PLC (reutilizamos la función existente)
|
||||
generate_io_excel_report(project_data, temp_excel_path, plc_id, temp_dir)
|
||||
|
||||
# Leer el Excel generado y agregar a la lista combinada
|
||||
if os.path.exists(temp_excel_path):
|
||||
try:
|
||||
df_plc = pd.read_excel(temp_excel_path, sheet_name='IO Report')
|
||||
# Agregar columna de ID único
|
||||
df_plc['Unique_ID'] = df_plc['PLC Name'] + "+" + df_plc['Device Name']
|
||||
all_excel_rows.append(df_plc)
|
||||
os.remove(temp_excel_path) # Limpiar archivo temporal
|
||||
except Exception as e:
|
||||
print(f"ERROR leyendo Excel temporal para PLC {plc_id}: {e}")
|
||||
|
||||
if not all_excel_rows:
|
||||
print("No se pudieron generar datos de Excel de referencia.")
|
||||
return None, None
|
||||
|
||||
# Combinar todos los DataFrames
|
||||
combined_df = pd.concat(all_excel_rows, ignore_index=True)
|
||||
|
||||
# Guardar Excel de referencia
|
||||
reference_excel_path = os.path.join(temp_dir, "reference_excel.xlsx")
|
||||
combined_df.to_excel(reference_excel_path, sheet_name='IO Report', index=False)
|
||||
|
||||
return reference_excel_path, project_data
|
||||
|
||||
|
||||
def compare_excel_files(reference_excel_path, modified_excel_path):
|
||||
"""
|
||||
Compara el Excel de referencia con el Excel modificado.
|
||||
Retorna (is_valid, changes_dict) donde:
|
||||
- is_valid: True si la estructura básica es la misma
|
||||
- changes_dict: diccionario con los cambios detectados
|
||||
"""
|
||||
print("Comparando archivos Excel...")
|
||||
|
||||
try:
|
||||
# Leer ambos archivos
|
||||
df_ref = pd.read_excel(reference_excel_path, sheet_name='IO Report')
|
||||
df_mod = pd.read_excel(modified_excel_path, sheet_name='IO Report')
|
||||
|
||||
# Agregar columna de ID único si no existe
|
||||
if 'Unique_ID' not in df_ref.columns:
|
||||
df_ref['Unique_ID'] = df_ref['PLC Name'] + "+" + df_ref['Device Name']
|
||||
if 'Unique_ID' not in df_mod.columns:
|
||||
df_mod['Unique_ID'] = df_mod['PLC Name'] + "+" + df_mod['Device Name']
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR leyendo archivos Excel: {e}")
|
||||
return False, {}
|
||||
|
||||
# Validar estructura básica
|
||||
if len(df_ref) != len(df_mod):
|
||||
print(f"ERROR: Número de filas diferente. Referencia: {len(df_ref)}, Modificado: {len(df_mod)}")
|
||||
return False, {}
|
||||
|
||||
# Verificar que todos los IDs únicos coincidan
|
||||
ref_ids = set(df_ref['Unique_ID'].tolist())
|
||||
mod_ids = set(df_mod['Unique_ID'].tolist())
|
||||
|
||||
if ref_ids != mod_ids:
|
||||
missing_in_mod = ref_ids - mod_ids
|
||||
extra_in_mod = mod_ids - ref_ids
|
||||
print("ERROR: Los IDs únicos no coinciden entre archivos.")
|
||||
if missing_in_mod:
|
||||
print(f" Faltantes en modificado: {missing_in_mod}")
|
||||
if extra_in_mod:
|
||||
print(f" Extras en modificado: {extra_in_mod}")
|
||||
return False, {}
|
||||
|
||||
print("Estructura básica validada correctamente.")
|
||||
|
||||
# Detectar cambios
|
||||
changes = {}
|
||||
columns_to_monitor = [
|
||||
'Device Address', 'IO Input Start Address', 'IO Input Word Count',
|
||||
'IO Output Start Address', 'IO Output Word Count',
|
||||
'Network Type', 'Device Type', 'Order Number', 'Firmware Version'
|
||||
]
|
||||
|
||||
for index, row_ref in df_ref.iterrows():
|
||||
unique_id = row_ref['Unique_ID']
|
||||
row_mod = df_mod[df_mod['Unique_ID'] == unique_id].iloc[0]
|
||||
|
||||
row_changes = {}
|
||||
for col in columns_to_monitor:
|
||||
if col in df_ref.columns and col in df_mod.columns:
|
||||
# Manejo especial para valores numéricos
|
||||
ref_raw = row_ref[col]
|
||||
mod_raw = row_mod[col]
|
||||
|
||||
# Convertir a string, manejando floats apropiadamente
|
||||
if pd.notna(ref_raw):
|
||||
if isinstance(ref_raw, float) and ref_raw.is_integer():
|
||||
ref_val = str(int(ref_raw))
|
||||
else:
|
||||
ref_val = str(ref_raw).strip()
|
||||
else:
|
||||
ref_val = 'N/A'
|
||||
|
||||
if pd.notna(mod_raw):
|
||||
if isinstance(mod_raw, float) and mod_raw.is_integer():
|
||||
mod_val = str(int(mod_raw))
|
||||
else:
|
||||
mod_val = str(mod_raw).strip()
|
||||
else:
|
||||
mod_val = 'N/A'
|
||||
|
||||
if ref_val != mod_val:
|
||||
row_changes[col] = {
|
||||
'original': ref_val,
|
||||
'modified': mod_val
|
||||
}
|
||||
|
||||
if row_changes:
|
||||
changes[unique_id] = {
|
||||
'plc_name': row_ref['PLC Name'],
|
||||
'device_name': row_ref['Device Name'],
|
||||
'changes': row_changes
|
||||
}
|
||||
|
||||
print(f"Detectados {len(changes)} dispositivos con cambios.")
|
||||
for unique_id, change_info in changes.items():
|
||||
print(f" {unique_id}: {list(change_info['changes'].keys())}")
|
||||
# Debug: mostrar los primeros cambios para verificar valores
|
||||
if len(changes) <= 5: # Solo mostrar detalles si son pocos cambios
|
||||
for field, change_data in change_info['changes'].items():
|
||||
print(f" {field}: {change_data['original']} → {change_data['modified']}")
|
||||
|
||||
return True, changes
|
||||
|
||||
|
||||
def find_device_in_aml(project_data, plc_name, device_name):
|
||||
"""
|
||||
Encuentra el device_id correspondiente en los datos del AML basándose en PLC y device name.
|
||||
"""
|
||||
# Buscar el PLC por nombre
|
||||
target_plc_id = None
|
||||
for plc_id, plc_data in project_data.get("plcs", {}).items():
|
||||
if plc_data.get('name', plc_id) == plc_name:
|
||||
target_plc_id = plc_id
|
||||
break
|
||||
|
||||
if not target_plc_id:
|
||||
return None
|
||||
|
||||
# Buscar el dispositivo en las redes del PLC
|
||||
plc_info = project_data["plcs"][target_plc_id]
|
||||
plc_networks = plc_info.get("connected_networks", {})
|
||||
|
||||
for net_id, plc_addr_on_net in plc_networks.items():
|
||||
net_info = project_data.get("networks", {}).get(net_id)
|
||||
if not net_info:
|
||||
continue
|
||||
|
||||
devices_on_net = net_info.get("devices_on_net", {})
|
||||
|
||||
# Identificar nodos que pertenecen al PLC
|
||||
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)
|
||||
|
||||
# Buscar en dispositivos de la red
|
||||
for node_id, node_addr in devices_on_net.items():
|
||||
if node_id in plc_interface_and_node_ids:
|
||||
continue
|
||||
|
||||
node_info = project_data.get("devices", {}).get(node_id)
|
||||
if not node_info:
|
||||
continue
|
||||
|
||||
# Determinar información del dispositivo
|
||||
interface_id = node_info.get("parent_id")
|
||||
interface_info = None
|
||||
actual_device_id = None
|
||||
actual_device_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)
|
||||
|
||||
display_info = actual_device_info if actual_device_info else (interface_info if interface_info else node_info)
|
||||
display_name = display_info.get("name", "Unknown")
|
||||
|
||||
if display_name == device_name:
|
||||
return {
|
||||
'node_id': node_id,
|
||||
'display_id': actual_device_id if actual_device_info else (interface_id if interface_info else node_id),
|
||||
'node_info': node_info,
|
||||
'display_info': display_info,
|
||||
'network_id': net_id
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def apply_changes_to_aml(aml_tree, project_data, changes_dict):
|
||||
"""
|
||||
Aplica los cambios detectados al árbol XML del AML.
|
||||
"""
|
||||
print("Aplicando cambios al archivo AML...")
|
||||
|
||||
root = aml_tree.getroot()
|
||||
changes_applied = 0
|
||||
|
||||
for unique_id, change_info in changes_dict.items():
|
||||
plc_name = change_info['plc_name']
|
||||
device_name = change_info['device_name']
|
||||
changes = change_info['changes']
|
||||
|
||||
print(f" Procesando cambios para: {unique_id}")
|
||||
|
||||
# Encontrar el dispositivo en los datos del AML
|
||||
device_info = find_device_in_aml(project_data, plc_name, device_name)
|
||||
if not device_info:
|
||||
print(f" ERROR: No se pudo encontrar el dispositivo '{device_name}' del PLC '{plc_name}' en el AML")
|
||||
continue
|
||||
|
||||
print(f" Debug: Encontrado dispositivo - node_id: {device_info['node_id']}, display_id: {device_info['display_id']}")
|
||||
|
||||
# Encontrar el elemento XML correspondiente
|
||||
node_id = device_info['node_id']
|
||||
display_id = device_info['display_id']
|
||||
|
||||
# Intentar primero con el display_id (el dispositivo real que contiene IOs)
|
||||
xml_element = root.xpath(f".//*[@ID='{display_id}']")
|
||||
|
||||
if not xml_element:
|
||||
# Si no se encuentra, intentar con el node_id
|
||||
xml_element = root.xpath(f".//*[@ID='{node_id}']")
|
||||
|
||||
if not xml_element:
|
||||
print(f" ERROR: No se pudo encontrar el elemento XML con ID {display_id} o {node_id}")
|
||||
continue
|
||||
|
||||
device_element = xml_element[0]
|
||||
print(f" Debug: Usando elemento XML con ID {device_element.get('ID', 'N/A')}")
|
||||
|
||||
# Aplicar cambios específicos
|
||||
for field, change_data in changes.items():
|
||||
original_val = change_data['original']
|
||||
modified_val = change_data['modified']
|
||||
|
||||
if field == 'Device Address':
|
||||
# Cambiar dirección de red del dispositivo
|
||||
if apply_network_address_change(device_element, modified_val):
|
||||
print(f" ✓ Dirección de red actualizada: {original_val} -> {modified_val}")
|
||||
changes_applied += 1
|
||||
else:
|
||||
print(f" ✗ Error actualizando dirección de red")
|
||||
|
||||
elif field in ['IO Input Start Address', 'IO Input Word Count', 'IO Output Start Address', 'IO Output Word Count']:
|
||||
# Cambiar direcciones IO específicas
|
||||
if apply_io_address_change(device_element, field, original_val, modified_val, project_data, display_id):
|
||||
print(f" ✓ {field} actualizada: {original_val} -> {modified_val}")
|
||||
changes_applied += 1
|
||||
else:
|
||||
print(f" ✗ Error actualizando {field}")
|
||||
|
||||
elif field in ['Device Type', 'Order Number', 'Firmware Version']:
|
||||
# Cambiar atributos del dispositivo
|
||||
if apply_device_attribute_change(device_element, field, modified_val):
|
||||
print(f" ✓ {field} actualizado: {original_val} -> {modified_val}")
|
||||
changes_applied += 1
|
||||
else:
|
||||
print(f" ✗ Error actualizando {field}")
|
||||
|
||||
print(f"Total de cambios aplicados: {changes_applied}")
|
||||
return changes_applied > 0
|
||||
|
||||
|
||||
def apply_network_address_change(device_element, new_address):
|
||||
"""
|
||||
Aplica cambio de dirección de red a un elemento del dispositivo.
|
||||
"""
|
||||
try:
|
||||
# Convertir new_address a string si es numérico
|
||||
if isinstance(new_address, (int, float)):
|
||||
address_str = str(new_address).rstrip('0').rstrip('.')
|
||||
else:
|
||||
address_str = str(new_address)
|
||||
|
||||
# Buscar atributo NetworkAddress
|
||||
addr_attr = device_element.xpath("./*[local-name()='Attribute'][@Name='NetworkAddress']")
|
||||
if addr_attr:
|
||||
value_elem = addr_attr[0].xpath("./*[local-name()='Value']")
|
||||
if value_elem:
|
||||
value_elem[0].text = address_str
|
||||
return True
|
||||
|
||||
# Si no existe, crear el atributo
|
||||
attr_elem = ET.SubElement(device_element, "Attribute")
|
||||
attr_elem.set("Name", "NetworkAddress")
|
||||
value_elem = ET.SubElement(attr_elem, "Value")
|
||||
value_elem.text = address_str
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error aplicando cambio de dirección: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def apply_device_attribute_change(device_element, field, new_value):
|
||||
"""
|
||||
Aplica cambio de atributo del dispositivo.
|
||||
"""
|
||||
try:
|
||||
# Mapear campos a nombres de atributos XML
|
||||
attr_mapping = {
|
||||
'Device Type': 'TypeName',
|
||||
'Order Number': 'OrderNumber',
|
||||
'Firmware Version': 'FirmwareVersion'
|
||||
}
|
||||
|
||||
attr_name = attr_mapping.get(field)
|
||||
if not attr_name:
|
||||
return False
|
||||
|
||||
# Buscar atributo existente
|
||||
attr_elem = device_element.xpath(f"./*[local-name()='Attribute'][@Name='{attr_name}']")
|
||||
if attr_elem:
|
||||
value_elem = attr_elem[0].xpath("./*[local-name()='Value']")
|
||||
if value_elem:
|
||||
value_elem[0].text = new_value
|
||||
return True
|
||||
|
||||
# Si no existe, crear el atributo
|
||||
attr_elem = ET.SubElement(device_element, "Attribute")
|
||||
attr_elem.set("Name", attr_name)
|
||||
value_elem = ET.SubElement(attr_elem, "Value")
|
||||
value_elem.text = new_value
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error aplicando cambio de atributo {field}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def apply_io_address_change(device_element, field, original_val, modified_val, project_data, device_id):
|
||||
"""
|
||||
Aplica cambios a las direcciones IO usando Start Address + Word Count.
|
||||
Esto es más simple y directo que calcular End Addresses.
|
||||
"""
|
||||
try:
|
||||
# Validar que el nuevo valor sea numérico o N/A
|
||||
if modified_val != 'N/A' and modified_val != '':
|
||||
try:
|
||||
# Manejar tanto floats como integers (pandas puede leer como float)
|
||||
if isinstance(modified_val, str):
|
||||
# Si es string, intentar convertir
|
||||
new_value = int(float(modified_val))
|
||||
else:
|
||||
# Si ya es numérico, convertir directamente
|
||||
new_value = int(modified_val)
|
||||
except (ValueError, TypeError):
|
||||
print(f" Error: Valor no válido: {modified_val} (tipo: {type(modified_val)})")
|
||||
return False
|
||||
else:
|
||||
# Si es N/A, no hay dirección IO para este tipo
|
||||
return True
|
||||
|
||||
# Determinar tipo de IO y si es start address o word count
|
||||
is_input = "Input" in field
|
||||
is_start_address = "Start Address" in field
|
||||
is_word_count = "Word Count" in field
|
||||
io_type = "Input" if is_input else "Output"
|
||||
|
||||
# Buscar la estructura IO en el dispositivo - con múltiples estrategias
|
||||
print(f" Debug: Buscando elementos Address en dispositivo {device_element.get('Name', 'N/A')}")
|
||||
|
||||
# Estrategia 1: Buscar Address directamente en el elemento
|
||||
address_elements = device_element.xpath("./*[local-name()='Attribute'][@Name='Address']")
|
||||
|
||||
if not address_elements:
|
||||
# Estrategia 2: Buscar en elementos hijos
|
||||
print(f" Debug: No se encontró Address directo, buscando en elementos hijos...")
|
||||
address_elements = device_element.xpath(".//*[local-name()='Attribute'][@Name='Address']")
|
||||
|
||||
if not address_elements:
|
||||
# Estrategia 3: Buscar en elementos padre (tal vez los IOs están en el parent)
|
||||
print(f" Debug: No se encontró Address en hijos, buscando en elementos padre...")
|
||||
parent_elements = device_element.xpath("parent::*")
|
||||
if parent_elements:
|
||||
address_elements = parent_elements[0].xpath(".//*[local-name()='Attribute'][@Name='Address']")
|
||||
|
||||
if not address_elements:
|
||||
print(f" ERROR: No se encontró elemento Address en el dispositivo o sus alrededores")
|
||||
print(f" Debug: Atributos disponibles en este elemento:")
|
||||
for attr in device_element.xpath("./*[local-name()='Attribute']"):
|
||||
attr_name = attr.get('Name', 'N/A')
|
||||
print(f" - {attr_name}")
|
||||
return False
|
||||
|
||||
print(f" Debug: Encontrados {len(address_elements)} elemento(s) Address")
|
||||
|
||||
# Buscar dentro del Address el sub-atributo correcto
|
||||
target_io_element = None
|
||||
|
||||
# Buscar en todos los elementos Address encontrados
|
||||
for address_element in address_elements:
|
||||
print(f" Debug: Explorando Address element...")
|
||||
io_subelements = address_element.xpath(f"./*[local-name()='Attribute']")
|
||||
|
||||
print(f" Debug: Encontrados {len(io_subelements)} sub-elementos en Address")
|
||||
|
||||
for io_sub in io_subelements:
|
||||
sub_name = io_sub.get('Name', 'N/A')
|
||||
print(f" Debug: Revisando sub-elemento: {sub_name}")
|
||||
|
||||
# Verificar si es el tipo correcto (Input/Output)
|
||||
type_val = io_sub.xpath("./*[local-name()='Attribute'][@Name='IoType']/*[local-name()='Value']/text()")
|
||||
if type_val:
|
||||
found_type = type_val[0]
|
||||
print(f" Debug: Encontrado IoType: {found_type}")
|
||||
if found_type.lower() == io_type.lower():
|
||||
target_io_element = io_sub
|
||||
print(f" Debug: ¡Encontrado elemento {io_type} compatible!")
|
||||
break
|
||||
else:
|
||||
# También revisar si el nombre del sub-elemento indica el tipo
|
||||
if io_type.lower() in sub_name.lower():
|
||||
print(f" Debug: Sub-elemento {sub_name} parece ser de tipo {io_type}")
|
||||
target_io_element = io_sub
|
||||
break
|
||||
|
||||
if target_io_element:
|
||||
break
|
||||
|
||||
if not target_io_element:
|
||||
print(f" ERROR: No se encontró elemento {io_type} en ningún Address")
|
||||
print(f" Debug: Elementos Address disponibles:")
|
||||
for addr_elem in address_elements:
|
||||
sub_attrs = addr_elem.xpath(f"./*[local-name()='Attribute']")
|
||||
for sub in sub_attrs:
|
||||
sub_name = sub.get('Name', 'N/A')
|
||||
type_vals = sub.xpath("./*[local-name()='Attribute'][@Name='IoType']/*[local-name()='Value']/text()")
|
||||
type_info = type_vals[0] if type_vals else "No IoType"
|
||||
print(f" - {sub_name} (IoType: {type_info})")
|
||||
return False
|
||||
|
||||
if is_start_address:
|
||||
# Actualizar StartAddress
|
||||
start_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='StartAddress']")
|
||||
if start_attr:
|
||||
value_elem = start_attr[0].xpath("./*[local-name()='Value']")
|
||||
if value_elem:
|
||||
value_elem[0].text = str(new_value)
|
||||
return True
|
||||
else:
|
||||
# Crear StartAddress si no existe
|
||||
start_attr_elem = ET.SubElement(target_io_element, "Attribute")
|
||||
start_attr_elem.set("Name", "StartAddress")
|
||||
value_elem = ET.SubElement(start_attr_elem, "Value")
|
||||
value_elem.text = str(new_value)
|
||||
return True
|
||||
|
||||
elif is_word_count:
|
||||
# Actualizar Length basándose en Word Count
|
||||
# Convertir words a bits (1 word = 16 bits)
|
||||
length_bits = new_value * 16
|
||||
|
||||
# Actualizar Length
|
||||
length_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='Length']")
|
||||
if length_attr:
|
||||
value_elem = length_attr[0].xpath("./*[local-name()='Value']")
|
||||
if value_elem:
|
||||
value_elem[0].text = str(length_bits)
|
||||
return True
|
||||
else:
|
||||
# Crear Length si no existe
|
||||
length_attr_elem = ET.SubElement(target_io_element, "Attribute")
|
||||
length_attr_elem.set("Name", "Length")
|
||||
value_elem = ET.SubElement(length_attr_elem, "Value")
|
||||
value_elem.text = str(length_bits)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error aplicando cambio de dirección IO {field}: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def save_updated_aml(aml_tree, original_aml_path):
|
||||
"""
|
||||
Guarda el árbol XML modificado como un nuevo archivo AML con sufijo "_updated".
|
||||
"""
|
||||
original_path = Path(original_aml_path)
|
||||
updated_path = original_path.parent / f"{original_path.stem}_updated{original_path.suffix}"
|
||||
|
||||
try:
|
||||
# Escribir el XML actualizado
|
||||
aml_tree.write(
|
||||
str(updated_path),
|
||||
pretty_print=True,
|
||||
xml_declaration=True,
|
||||
encoding='utf-8'
|
||||
)
|
||||
print(f"Archivo AML actualizado guardado en: {updated_path}")
|
||||
return str(updated_path)
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR guardando archivo AML actualizado: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
"""Función principal del script."""
|
||||
try:
|
||||
configs = load_configuration()
|
||||
working_directory = configs.get("working_directory")
|
||||
except Exception as e:
|
||||
print(f"Warning: No se pudo cargar configuración: {e}")
|
||||
configs = {}
|
||||
working_directory = None
|
||||
|
||||
script_version = "v1.4 - Enhanced Address Element Search with Debug"
|
||||
print(f"--- Actualizador de AML desde Excel Modificado ({script_version}) ---")
|
||||
|
||||
# Validar directorio de trabajo
|
||||
if not working_directory or not os.path.isdir(working_directory):
|
||||
print("Directorio de trabajo no configurado. Usando directorio actual.")
|
||||
working_directory = os.getcwd()
|
||||
|
||||
print(f"Directorio de trabajo: {working_directory}")
|
||||
|
||||
# 1. Seleccionar archivo AML original
|
||||
print("\n1. Seleccione el archivo AML original:")
|
||||
aml_file_path = select_aml_file(initial_dir=working_directory)
|
||||
if not aml_file_path:
|
||||
print("No se seleccionó archivo AML. Saliendo.")
|
||||
return
|
||||
|
||||
# 2. Seleccionar archivo Excel modificado
|
||||
print("\n2. Seleccione el archivo Excel modificado:")
|
||||
excel_file_path = select_excel_file(initial_dir=working_directory)
|
||||
if not excel_file_path:
|
||||
print("No se seleccionó archivo Excel. Saliendo.")
|
||||
return
|
||||
|
||||
print(f"\nArchivo AML original: {aml_file_path}")
|
||||
print(f"Archivo Excel modificado: {excel_file_path}")
|
||||
|
||||
# 3. Crear directorio temporal para archivos intermedios
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
print(f"\nUsando directorio temporal: {temp_dir}")
|
||||
|
||||
# 4. Generar Excel de referencia desde AML
|
||||
reference_excel_path, project_data = generate_reference_excel_from_aml(aml_file_path, temp_dir)
|
||||
if not reference_excel_path or not project_data:
|
||||
print("ERROR: No se pudo generar Excel de referencia.")
|
||||
return
|
||||
|
||||
# 5. Comparar archivos Excel
|
||||
is_valid, changes_dict = compare_excel_files(reference_excel_path, excel_file_path)
|
||||
if not is_valid:
|
||||
print("ERROR: El archivo Excel modificado no es compatible con el AML original.")
|
||||
return
|
||||
|
||||
if not changes_dict:
|
||||
print("No se detectaron cambios en el Excel. No hay nada que actualizar.")
|
||||
return
|
||||
|
||||
# 6. Cargar y parsear AML original
|
||||
print("\nCargando archivo AML original...")
|
||||
try:
|
||||
parser = ET.XMLParser(remove_blank_text=True, huge_tree=True)
|
||||
aml_tree = ET.parse(aml_file_path, parser)
|
||||
except Exception as e:
|
||||
print(f"ERROR parseando archivo AML: {e}")
|
||||
return
|
||||
|
||||
# 7. Aplicar cambios al AML
|
||||
success = apply_changes_to_aml(aml_tree, project_data, changes_dict)
|
||||
if not success:
|
||||
print("No se pudieron aplicar cambios al AML.")
|
||||
return
|
||||
|
||||
# 8. Guardar AML actualizado
|
||||
updated_aml_path = save_updated_aml(aml_tree, aml_file_path)
|
||||
if updated_aml_path:
|
||||
print(f"\n¡Proceso completado exitosamente!")
|
||||
print(f"Archivo AML actualizado: {updated_aml_path}")
|
||||
else:
|
||||
print("ERROR: No se pudo guardar el archivo AML actualizado.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,324 @@
|
|||
"""
|
||||
export_logic_from_tia : Script para exportar el software de un PLC desde TIA Portal en archivos XML y SCL.
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
# --- Configuration ---
|
||||
TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version (e.g., "18.0")
|
||||
EXPORT_OPTIONS = None # Use default export options
|
||||
KEEP_FOLDER_STRUCTURE = (
|
||||
True # Replicate TIA project folder structure in export directory
|
||||
)
|
||||
|
||||
# --- TIA Scripting Import Handling ---
|
||||
# Check if the TIA_SCRIPTING environment variable is set
|
||||
if os.getenv("TIA_SCRIPTING"):
|
||||
sys.path.append(os.getenv("TIA_SCRIPTING"))
|
||||
else:
|
||||
# Optional: Define a fallback path if the environment variable isn't set
|
||||
# fallback_path = "C:\\path\\to\\your\\TIA_Scripting_binaries"
|
||||
# if os.path.exists(fallback_path):
|
||||
# sys.path.append(fallback_path)
|
||||
pass # Allow import to fail if not found
|
||||
|
||||
try:
|
||||
import siemens_tia_scripting as ts
|
||||
|
||||
EXPORT_OPTIONS = (
|
||||
ts.Enums.ExportOptions.WithDefaults
|
||||
) # Set default options now that 'ts' is imported
|
||||
except ImportError:
|
||||
print("ERROR: Failed to import 'siemens_tia_scripting'.")
|
||||
print("Ensure:")
|
||||
print(f"1. TIA Portal Openness for V{TIA_PORTAL_VERSION} is installed.")
|
||||
print(
|
||||
"2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or"
|
||||
)
|
||||
print(
|
||||
" the path to its binaries is set in the 'TIA_SCRIPTING' environment variable."
|
||||
)
|
||||
print(
|
||||
"3. You are using a compatible Python version (e.g., 3.12.X as per documentation)."
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred during import: {e}")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# --- Functions ---
|
||||
|
||||
|
||||
def select_project_file():
|
||||
"""Opens a dialog to select a TIA Portal project file."""
|
||||
root = tk.Tk()
|
||||
root.withdraw() # Hide the main tkinter window
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="Select TIA Portal Project File",
|
||||
filetypes=[
|
||||
(
|
||||
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
|
||||
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
|
||||
)
|
||||
], # e.g. *.ap18
|
||||
)
|
||||
root.destroy()
|
||||
if not file_path:
|
||||
print("No project file selected. Exiting.")
|
||||
sys.exit(0)
|
||||
return file_path
|
||||
|
||||
|
||||
def select_export_directory():
|
||||
"""Opens a dialog to select the export directory."""
|
||||
root = tk.Tk()
|
||||
root.withdraw() # Hide the main tkinter window
|
||||
dir_path = filedialog.askdirectory(title="Select Export Directory")
|
||||
root.destroy()
|
||||
if not dir_path:
|
||||
print("No export directory selected. Exiting.")
|
||||
sys.exit(0)
|
||||
return dir_path
|
||||
|
||||
|
||||
def export_plc_data(plc, export_base_dir):
|
||||
"""Exports Blocks, UDTs, and Tag Tables from a given PLC."""
|
||||
plc_name = plc.get_name()
|
||||
print(f"\n--- Processing PLC: {plc_name} ---")
|
||||
|
||||
# Define base export path for this PLC
|
||||
plc_export_dir = os.path.join(export_base_dir, plc_name)
|
||||
os.makedirs(plc_export_dir, exist_ok=True)
|
||||
|
||||
# --- Export Program Blocks ---
|
||||
blocks_exported = 0
|
||||
blocks_skipped = 0
|
||||
print(f"\n[PLC: {plc_name}] Exporting Program Blocks...")
|
||||
xml_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_XML")
|
||||
scl_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_SCL")
|
||||
os.makedirs(xml_blocks_path, exist_ok=True)
|
||||
os.makedirs(scl_blocks_path, exist_ok=True)
|
||||
print(f" XML Target: {xml_blocks_path}")
|
||||
print(f" SCL Target: {scl_blocks_path}")
|
||||
|
||||
try:
|
||||
program_blocks = plc.get_program_blocks() #
|
||||
print(f" Found {len(program_blocks)} program blocks.")
|
||||
for block in program_blocks:
|
||||
block_name = block.get_name() # Assuming get_name() exists
|
||||
print(f" Processing block: {block_name}...")
|
||||
try:
|
||||
if not block.is_consistent(): #
|
||||
print(f" Compiling block {block_name}...")
|
||||
block.compile() #
|
||||
if not block.is_consistent():
|
||||
print(
|
||||
f" WARNING: Block {block_name} inconsistent after compile. Skipping."
|
||||
)
|
||||
blocks_skipped += 1
|
||||
continue
|
||||
|
||||
print(f" Exporting {block_name} as XML...")
|
||||
block.export(
|
||||
target_directory_path=xml_blocks_path, #
|
||||
export_options=EXPORT_OPTIONS, #
|
||||
export_format=ts.Enums.ExportFormats.SimaticML, #
|
||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||
) #
|
||||
|
||||
try:
|
||||
prog_language = block.get_property(name="ProgrammingLanguage")
|
||||
if prog_language == "SCL":
|
||||
print(f" Exporting {block_name} as SCL...")
|
||||
block.export(
|
||||
target_directory_path=scl_blocks_path,
|
||||
export_options=EXPORT_OPTIONS,
|
||||
export_format=ts.Enums.ExportFormats.ExternalSource, #
|
||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||
)
|
||||
except Exception as prop_ex:
|
||||
print(
|
||||
f" Could not get ProgrammingLanguage for {block_name}. Skipping SCL. Error: {prop_ex}"
|
||||
)
|
||||
|
||||
blocks_exported += 1
|
||||
except Exception as block_ex:
|
||||
print(f" ERROR exporting block {block_name}: {block_ex}")
|
||||
blocks_skipped += 1
|
||||
print(
|
||||
f" Program Blocks Export Summary: Exported={blocks_exported}, Skipped/Errors={blocks_skipped}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f" ERROR processing Program Blocks: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
# --- Export PLC Data Types (UDTs) ---
|
||||
udts_exported = 0
|
||||
udts_skipped = 0
|
||||
print(f"\n[PLC: {plc_name}] Exporting PLC Data Types (UDTs)...")
|
||||
udt_export_path = os.path.join(plc_export_dir, "PlcDataTypes")
|
||||
os.makedirs(udt_export_path, exist_ok=True)
|
||||
print(f" Target: {udt_export_path}")
|
||||
|
||||
try:
|
||||
udts = plc.get_user_data_types() #
|
||||
print(f" Found {len(udts)} UDTs.")
|
||||
for udt in udts:
|
||||
udt_name = udt.get_name() #
|
||||
print(f" Processing UDT: {udt_name}...")
|
||||
try:
|
||||
if not udt.is_consistent(): #
|
||||
print(f" Compiling UDT {udt_name}...")
|
||||
udt.compile() #
|
||||
if not udt.is_consistent():
|
||||
print(
|
||||
f" WARNING: UDT {udt_name} inconsistent after compile. Skipping."
|
||||
)
|
||||
udts_skipped += 1
|
||||
continue
|
||||
|
||||
print(f" Exporting {udt_name}...")
|
||||
udt.export(
|
||||
target_directory_path=udt_export_path, #
|
||||
export_options=EXPORT_OPTIONS, #
|
||||
# export_format defaults to SimaticML for UDTs
|
||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||
) #
|
||||
udts_exported += 1
|
||||
except Exception as udt_ex:
|
||||
print(f" ERROR exporting UDT {udt_name}: {udt_ex}")
|
||||
udts_skipped += 1
|
||||
print(
|
||||
f" UDT Export Summary: Exported={udts_exported}, Skipped/Errors={udts_skipped}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f" ERROR processing UDTs: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
# --- Export PLC Tag Tables ---
|
||||
tags_exported = 0
|
||||
tags_skipped = 0
|
||||
print(f"\n[PLC: {plc_name}] Exporting PLC Tag Tables...")
|
||||
tags_export_path = os.path.join(plc_export_dir, "PlcTags")
|
||||
os.makedirs(tags_export_path, exist_ok=True)
|
||||
print(f" Target: {tags_export_path}")
|
||||
|
||||
try:
|
||||
tag_tables = plc.get_plc_tag_tables() #
|
||||
print(f" Found {len(tag_tables)} Tag Tables.")
|
||||
for table in tag_tables:
|
||||
table_name = table.get_name() #
|
||||
print(f" Processing Tag Table: {table_name}...")
|
||||
try:
|
||||
# Note: Consistency check might not be available/needed for tag tables like blocks/UDTs
|
||||
print(f" Exporting {table_name}...")
|
||||
table.export(
|
||||
target_directory_path=tags_export_path, #
|
||||
export_options=EXPORT_OPTIONS, #
|
||||
# export_format defaults to SimaticML for Tag Tables
|
||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||
) #
|
||||
tags_exported += 1
|
||||
except Exception as table_ex:
|
||||
print(f" ERROR exporting Tag Table {table_name}: {table_ex}")
|
||||
tags_skipped += 1
|
||||
print(
|
||||
f" Tag Table Export Summary: Exported={tags_exported}, Skipped/Errors={tags_skipped}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f" ERROR processing Tag Tables: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print(f"\n--- Finished processing PLC: {plc_name} ---")
|
||||
|
||||
|
||||
# --- Main Script ---
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
configs = load_configuration()
|
||||
working_directory = configs.get("working_directory")
|
||||
|
||||
print("--- TIA Portal Data Exporter (Blocks, UDTs, Tags) ---")
|
||||
|
||||
# Validate working directory
|
||||
if not working_directory or not os.path.isdir(working_directory):
|
||||
print("ERROR: Working directory not set or invalid in configuration.")
|
||||
print("Please configure the working directory using the main application.")
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Select Project File, Export Directory comes from config
|
||||
project_file = select_project_file()
|
||||
export_dir = working_directory # Use working directory from config
|
||||
|
||||
print(f"\nSelected Project: {project_file}")
|
||||
print(f"Using Export Directory (Working Directory): {export_dir}")
|
||||
|
||||
portal_instance = None
|
||||
project_object = None
|
||||
|
||||
try:
|
||||
# 2. Connect to TIA Portal
|
||||
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
|
||||
portal_instance = ts.open_portal(
|
||||
version=TIA_PORTAL_VERSION,
|
||||
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
|
||||
)
|
||||
print("Connected to TIA Portal.")
|
||||
print(f"Portal Process ID: {portal_instance.get_process_id()}") #
|
||||
|
||||
# 3. Open Project
|
||||
print(f"Opening project: {os.path.basename(project_file)}...")
|
||||
project_object = portal_instance.open_project(project_file_path=project_file) #
|
||||
if project_object is None:
|
||||
print("Project might already be open, attempting to get handle...")
|
||||
project_object = portal_instance.get_project() #
|
||||
if project_object is None:
|
||||
raise Exception("Failed to open or get the specified project.")
|
||||
print("Project opened successfully.")
|
||||
|
||||
# 4. Get PLCs
|
||||
plcs = project_object.get_plcs() #
|
||||
if not plcs:
|
||||
print("No PLC devices found in the project.")
|
||||
else:
|
||||
print(f"Found {len(plcs)} PLC(s). Starting export process...")
|
||||
|
||||
# 5. Iterate and Export Data for each PLC
|
||||
for plc_device in plcs:
|
||||
export_plc_data(
|
||||
plc=plc_device, export_base_dir=export_dir
|
||||
) # Pass export_dir
|
||||
|
||||
print("\nExport process completed.")
|
||||
|
||||
except ts.TiaException as tia_ex:
|
||||
print(f"\nTIA Portal Openness Error: {tia_ex}")
|
||||
traceback.print_exc()
|
||||
except FileNotFoundError:
|
||||
print(f"\nERROR: Project file not found at {project_file}")
|
||||
except Exception as e:
|
||||
print(f"\nAn unexpected error occurred: {e}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
# 6. Cleanup
|
||||
if portal_instance:
|
||||
try:
|
||||
print("\nClosing TIA Portal...")
|
||||
portal_instance.close_portal() #
|
||||
print("TIA Portal closed.")
|
||||
except Exception as close_ex:
|
||||
print(f"Error during TIA Portal cleanup: {close_ex}")
|
||||
|
||||
print("\nScript finished.")
|
|
@ -0,0 +1,371 @@
|
|||
"""
|
||||
export_CAx_from_tia : Script que exporta los datos CAx de un proyecto de TIA Portal y genera un resumen en Markdown.
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import xml.etree.ElementTree as ET # Library to parse XML (AML)
|
||||
from pathlib import Path # Import Path
|
||||
|
||||
script_root = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
)
|
||||
sys.path.append(script_root)
|
||||
from backend.script_utils import load_configuration
|
||||
|
||||
# --- Configuration ---
|
||||
# Supported TIA Portal versions mapping (extension -> version)
|
||||
SUPPORTED_TIA_VERSIONS = {
|
||||
".ap18": "18.0",
|
||||
".ap19": "19.0",
|
||||
".ap20": "20.0"
|
||||
}
|
||||
|
||||
# --- TIA Scripting Import Handling ---
|
||||
# (Same import handling as the previous script)
|
||||
if os.getenv("TIA_SCRIPTING"):
|
||||
sys.path.append(os.getenv("TIA_SCRIPTING"))
|
||||
else:
|
||||
pass
|
||||
|
||||
try:
|
||||
import siemens_tia_scripting as ts
|
||||
except ImportError:
|
||||
print("ERROR: Failed to import 'siemens_tia_scripting'.")
|
||||
print("Ensure TIA Openness, the module, and Python 3.12.X are set up.")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred during import: {e}")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# --- Functions ---
|
||||
|
||||
|
||||
def get_supported_filetypes():
|
||||
"""Returns the supported file types for TIA Portal projects."""
|
||||
filetypes = []
|
||||
for ext, version in SUPPORTED_TIA_VERSIONS.items():
|
||||
version_major = version.split('.')[0]
|
||||
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}"))
|
||||
|
||||
# Add option to show all supported files
|
||||
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()])
|
||||
filetypes.insert(0, ("All TIA Portal Projects", all_extensions))
|
||||
|
||||
return filetypes
|
||||
|
||||
|
||||
def detect_tia_version(project_file_path):
|
||||
"""Detects TIA Portal version based on file extension."""
|
||||
file_path = Path(project_file_path)
|
||||
file_extension = file_path.suffix.lower()
|
||||
|
||||
if file_extension in SUPPORTED_TIA_VERSIONS:
|
||||
detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
|
||||
print(f"Detected TIA Portal version: {detected_version} (from extension {file_extension})")
|
||||
return detected_version
|
||||
else:
|
||||
print(f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}")
|
||||
# Default to version 18.0 for backward compatibility
|
||||
print("Defaulting to TIA Portal V18.0")
|
||||
return "18.0"
|
||||
|
||||
|
||||
def select_project_file():
|
||||
"""Opens a dialog to select a TIA Portal project file."""
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="Select TIA Portal Project File",
|
||||
filetypes=get_supported_filetypes(),
|
||||
)
|
||||
root.destroy()
|
||||
if not file_path:
|
||||
print("No project file selected. Exiting.")
|
||||
sys.exit(0)
|
||||
return file_path
|
||||
|
||||
|
||||
def select_output_directory():
|
||||
"""Opens a dialog to select the output directory."""
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
dir_path = filedialog.askdirectory(
|
||||
title="Select Output Directory for AML and MD files"
|
||||
)
|
||||
root.destroy()
|
||||
if not dir_path:
|
||||
print("No output directory selected. Exiting.")
|
||||
sys.exit(0)
|
||||
return dir_path
|
||||
|
||||
|
||||
def find_elements(element, path):
|
||||
"""Helper to find elements using namespaces commonly found in AML."""
|
||||
# AutomationML namespaces often vary slightly or might be default
|
||||
# This basic approach tries common prefixes or no prefix
|
||||
namespaces = {
|
||||
"": (
|
||||
element.tag.split("}")[0][1:] if "}" in element.tag else ""
|
||||
), # Default namespace if present
|
||||
"caex": "http://www.dke.de/CAEX", # Common CAEX namespace
|
||||
# Add other potential namespaces if needed based on file inspection
|
||||
}
|
||||
# Try finding with common prefixes or the default namespace
|
||||
for prefix, uri in namespaces.items():
|
||||
# Construct path with namespace URI if prefix is defined
|
||||
namespaced_path = path
|
||||
if prefix:
|
||||
parts = path.split("/")
|
||||
namespaced_parts = [
|
||||
f"{{{uri}}}{part}" if part != "." else part for part in parts
|
||||
]
|
||||
namespaced_path = "/".join(namespaced_parts)
|
||||
|
||||
# Try findall with the constructed path
|
||||
found = element.findall(namespaced_path)
|
||||
if found:
|
||||
return found # Return first successful find
|
||||
|
||||
# Fallback: try finding without explicit namespace (might work if default ns is used throughout)
|
||||
# This might require adjusting the path string itself depending on the XML structure
|
||||
try:
|
||||
# Simple attempt without namespace handling if the above fails
|
||||
return element.findall(path)
|
||||
except (
|
||||
SyntaxError
|
||||
): # Handle potential errors if path isn't valid without namespaces
|
||||
return []
|
||||
|
||||
|
||||
def parse_aml_to_markdown(aml_file_path, md_file_path):
|
||||
"""Parses the AML file and generates a Markdown summary."""
|
||||
print(f"Parsing AML file: {aml_file_path}")
|
||||
try:
|
||||
tree = ET.parse(aml_file_path)
|
||||
root = tree.getroot()
|
||||
|
||||
markdown_lines = ["# Project CAx Data Summary (AutomationML)", ""]
|
||||
|
||||
# Find InstanceHierarchy - usually contains the project structure
|
||||
# Note: Namespace handling in ElementTree can be tricky. Adjust '{...}' part if needed.
|
||||
# We will use a helper function 'find_elements' to try common patterns
|
||||
instance_hierarchies = find_elements(
|
||||
root, ".//InstanceHierarchy"
|
||||
) # Common CAEX tag
|
||||
|
||||
if not instance_hierarchies:
|
||||
markdown_lines.append("Could not find InstanceHierarchy in the AML file.")
|
||||
print("Warning: Could not find InstanceHierarchy element.")
|
||||
else:
|
||||
# Assuming the first InstanceHierarchy is the main one
|
||||
ih = instance_hierarchies[0]
|
||||
markdown_lines.append(f"## Instance Hierarchy: {ih.get('Name', 'N/A')}")
|
||||
markdown_lines.append("")
|
||||
|
||||
# Look for InternalElements which represent devices/components
|
||||
internal_elements = find_elements(
|
||||
ih, ".//InternalElement"
|
||||
) # Common CAEX tag
|
||||
|
||||
if not internal_elements:
|
||||
markdown_lines.append(
|
||||
"No devices (InternalElement) found in InstanceHierarchy."
|
||||
)
|
||||
print("Info: No InternalElement tags found under InstanceHierarchy.")
|
||||
else:
|
||||
markdown_lines.append(
|
||||
f"Found {len(internal_elements)} device(s)/component(s):"
|
||||
)
|
||||
markdown_lines.append("")
|
||||
markdown_lines.append(
|
||||
"| Name | SystemUnitClass | RefBaseSystemUnitPath | Attributes |"
|
||||
)
|
||||
markdown_lines.append("|---|---|---|---|")
|
||||
|
||||
for elem in internal_elements:
|
||||
name = elem.get("Name", "N/A")
|
||||
ref_path = elem.get(
|
||||
"RefBaseSystemUnitPath", "N/A"
|
||||
) # Path to class definition
|
||||
|
||||
# Try to get the class name from the RefBaseSystemUnitPath or SystemUnitClassLib
|
||||
su_class_path = find_elements(
|
||||
elem, ".//SystemUnitClass"
|
||||
) # Check direct child first
|
||||
su_class = (
|
||||
su_class_path[0].get("Path", "N/A")
|
||||
if su_class_path
|
||||
else ref_path.split("/")[-1]
|
||||
) # Fallback to last part of path
|
||||
|
||||
attributes_md = ""
|
||||
attributes = find_elements(elem, ".//Attribute") # Find attributes
|
||||
attr_list = []
|
||||
for attr in attributes:
|
||||
attr_name = attr.get("Name", "")
|
||||
attr_value_elem = find_elements(
|
||||
attr, ".//Value"
|
||||
) # Get Value element
|
||||
attr_value = (
|
||||
attr_value_elem[0].text
|
||||
if attr_value_elem and attr_value_elem[0].text
|
||||
else "N/A"
|
||||
)
|
||||
|
||||
# Look for potential IP addresses (common attribute names)
|
||||
if "Address" in attr_name or "IP" in attr_name:
|
||||
attr_list.append(f"**{attr_name}**: {attr_value}")
|
||||
else:
|
||||
attr_list.append(f"{attr_name}: {attr_value}")
|
||||
|
||||
attributes_md = "<br>".join(attr_list) if attr_list else "None"
|
||||
|
||||
markdown_lines.append(
|
||||
f"| {name} | {su_class} | `{ref_path}` | {attributes_md} |"
|
||||
)
|
||||
|
||||
# Write to Markdown file
|
||||
with open(md_file_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(markdown_lines))
|
||||
print(f"Markdown summary written to: {md_file_path}")
|
||||
|
||||
except ET.ParseError as xml_err:
|
||||
print(f"ERROR parsing XML file {aml_file_path}: {xml_err}")
|
||||
with open(md_file_path, "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
f"# Error\n\nFailed to parse AML file: {os.path.basename(aml_file_path)}\n\nError: {xml_err}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"ERROR processing AML file {aml_file_path}: {e}")
|
||||
traceback.print_exc()
|
||||
with open(md_file_path, "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
f"# Error\n\nAn unexpected error occurred while processing AML file: {os.path.basename(aml_file_path)}\n\nError: {e}"
|
||||
)
|
||||
|
||||
|
||||
# --- Main Script ---
|
||||
|
||||
if __name__ == "__main__":
|
||||
configs = load_configuration()
|
||||
working_directory = configs.get("working_directory")
|
||||
|
||||
print("--- TIA Portal Project CAx Exporter and Analyzer ---")
|
||||
|
||||
# Validate working directory
|
||||
if not working_directory or not os.path.isdir(working_directory):
|
||||
print("ERROR: Working directory not set or invalid in configuration.")
|
||||
print("Please configure the working directory using the main application.")
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Select Project File, Output Directory comes from config
|
||||
project_file = select_project_file()
|
||||
output_dir = Path(
|
||||
working_directory
|
||||
) # Use working directory from config, ensure it's a Path object
|
||||
|
||||
print(f"\nSelected Project: {project_file}")
|
||||
print(f"Using Output Directory (Working Directory): {output_dir}")
|
||||
|
||||
# 2. Detect TIA Portal version from project file
|
||||
tia_version = detect_tia_version(project_file)
|
||||
|
||||
# Define output file names using Path object
|
||||
project_path = Path(project_file)
|
||||
project_base_name = project_path.stem # Get filename without extension
|
||||
aml_file = output_dir / f"{project_base_name}_CAx_Export.aml"
|
||||
md_file = output_dir / f"{project_base_name}_CAx_Summary.md"
|
||||
log_file = (
|
||||
output_dir / f"{project_base_name}_CAx_Export.log"
|
||||
) # Log file for the export process
|
||||
|
||||
print(f"Will export CAx data to: {aml_file}")
|
||||
print(f"Will generate summary to: {md_file}")
|
||||
print(f"Export log file: {log_file}")
|
||||
|
||||
portal_instance = None
|
||||
project_object = None
|
||||
cax_export_successful = False
|
||||
|
||||
try:
|
||||
# 3. Connect to TIA Portal with detected version
|
||||
print(f"\nConnecting to TIA Portal V{tia_version}...")
|
||||
portal_instance = ts.open_portal(
|
||||
version=tia_version,
|
||||
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
|
||||
)
|
||||
print("Connected.")
|
||||
|
||||
# 4. Open Project
|
||||
print(
|
||||
f"Opening project: {project_path.name}..."
|
||||
) # Use Path object's name attribute
|
||||
project_object = portal_instance.open_project(
|
||||
project_file_path=str(project_path)
|
||||
) # Pass path as string
|
||||
if project_object is None:
|
||||
print("Project might already be open, attempting to get handle...")
|
||||
project_object = portal_instance.get_project()
|
||||
if project_object is None:
|
||||
raise Exception("Failed to open or get the specified project.")
|
||||
print("Project opened.")
|
||||
|
||||
# 5. Export CAx Data (Project Level)
|
||||
print(f"Exporting CAx data for the project to {aml_file}...")
|
||||
# Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Pass paths as strings to the TIA function
|
||||
export_result = project_object.export_cax_data(
|
||||
export_file_path=str(aml_file), log_file_path=str(log_file)
|
||||
)
|
||||
|
||||
if export_result:
|
||||
print("CAx data exported successfully.")
|
||||
cax_export_successful = True
|
||||
else:
|
||||
print("CAx data export failed. Check the log file for details:")
|
||||
print(f" Log file: {log_file}")
|
||||
# Write basic error message to MD file if export fails
|
||||
with open(md_file, "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
f"# Error\n\nCAx data export failed. Check log file: {log_file}"
|
||||
)
|
||||
|
||||
except ts.TiaException as tia_ex:
|
||||
print(f"\nTIA Portal Openness Error: {tia_ex}")
|
||||
traceback.print_exc()
|
||||
except FileNotFoundError:
|
||||
print(f"\nERROR: Project file not found at {project_file}")
|
||||
except Exception as e:
|
||||
print(f"\nAn unexpected error occurred during TIA interaction: {e}")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
# Close TIA Portal before processing the file (or detach)
|
||||
if portal_instance:
|
||||
try:
|
||||
print("\nClosing TIA Portal...")
|
||||
portal_instance.close_portal()
|
||||
print("TIA Portal closed.")
|
||||
except Exception as close_ex:
|
||||
print(f"Error during TIA Portal cleanup: {close_ex}")
|
||||
|
||||
# 6. Parse AML and Generate Markdown (only if export was successful)
|
||||
if cax_export_successful:
|
||||
if aml_file.exists(): # Use Path object's exists() method
|
||||
parse_aml_to_markdown(aml_file, md_file)
|
||||
else:
|
||||
print(
|
||||
f"ERROR: Export was reported successful, but AML file not found at {aml_file}"
|
||||
)
|
||||
with open(md_file, "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
f"# Error\n\nExport was reported successful, but AML file not found:\n{aml_file}"
|
||||
)
|
||||
|
||||
print("\nScript finished.")
|
File diff suppressed because it is too large
Load Diff
|
@ -5,5 +5,5 @@
|
|||
},
|
||||
"level2": {},
|
||||
"level3": {},
|
||||
"working_directory": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD"
|
||||
"working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source"
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"path": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD",
|
||||
"path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
|
||||
"history": [
|
||||
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
|
||||
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD",
|
||||
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML"
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -15,5 +15,5 @@
|
|||
"xref_source_subdir": "source"
|
||||
},
|
||||
"level3": {},
|
||||
"working_directory": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML"
|
||||
"working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source"
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"path": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML",
|
||||
"path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
|
||||
"history": [
|
||||
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
|
||||
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML",
|
||||
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
|
||||
]
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Grupo de Ejemplo
|
||||
|
||||
Este es un archivo markdown de prueba para el **Launcher GUI**.
|
||||
|
||||
## Características
|
||||
|
||||
- Soporte para archivos `.md`
|
||||
- Renderizado con markdown-it
|
||||
- Visualización en modal
|
||||
|
||||
## Símbolos Unicode
|
||||
|
||||
Aquí tienes algunos símbolos que pueden causar problemas de encoding:
|
||||
|
||||
- Flecha derecha: →
|
||||
- Flecha izquierda: ←
|
||||
- Flecha doble: ⇒
|
||||
- Caracteres especiales: ñ, á, é, í, ó, ú
|
||||
- Emojis: 🚀 📄 ⚙️
|
||||
|
||||
## Código de Ejemplo
|
||||
|
||||
```python
|
||||
def test_unicode():
|
||||
print("Prueba de unicode → éxito")
|
||||
return "Todo funcionando ✅"
|
||||
```
|
||||
|
||||
## Lista de Verificación
|
||||
|
||||
- [x] Encoding UTF-8
|
||||
- [x] Renderizado markdown
|
||||
- [ ] Pruebas adicionales
|
|
@ -7,6 +7,14 @@
|
|||
"added_date": "2025-06-03T11:47:24.757186Z",
|
||||
"execution_count": 0,
|
||||
"last_executed": null
|
||||
},
|
||||
{
|
||||
"id": "2_main.py",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"added_date": "2025-06-03T12:00:41.084138Z",
|
||||
"execution_count": 0,
|
||||
"last_executed": null
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,409 @@
|
|||
{
|
||||
"history": [],
|
||||
"history": [
|
||||
{
|
||||
"id": "a599effd",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-12T19:59:02.082348Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 36824,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "d722b721",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-12T09:27:11.172727Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 24964,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "0a84ec87",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-12T09:24:00.527248Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 15548,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "6297e689",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-12T09:16:08.148925Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 16892,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "79f5a9e6",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-12T09:07:06.392550Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 45652,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "22e264cd",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-12T09:07:06.280903Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 25984,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "589be793",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-11T23:17:19.621521Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "success",
|
||||
"pid": 25652,
|
||||
"execution_time": 389.689278
|
||||
},
|
||||
{
|
||||
"id": "6ca3fbde",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-11T23:17:06.940558Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 29496,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "6f646ca2",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-11T23:16:59.013936Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "success",
|
||||
"pid": 38516,
|
||||
"execution_time": 3.471176
|
||||
},
|
||||
{
|
||||
"id": "88e35e8b",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-11T14:00:34.786377Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 404,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "b373c77d",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-11T13:51:11.750064Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 24472,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "455a8271",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-10T15:45:56.713179Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 16480,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "a8b813b0",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-10T15:40:39.728099Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 36640,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "7924a173",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-10T15:36:55.303738Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 15144,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "c3796f4e",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-10T15:36:18.822898Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 30656,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "1040f2b6",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-10T11:03:59.233659Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 26152,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "6d4e8908",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-05T19:00:05.863426Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 14212,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "bc11fb82",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-05T18:55:33.372642Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 30456,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "7dda414a",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-05T18:55:25.022607Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 31800,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "ca0fdba4",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-05T18:55:20.165639Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 27436,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "8eeccfaf",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-05T18:36:56.208761Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 11436,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "be30fe5c",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-05T11:40:29.568449Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 34348,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "de046db1",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-04T22:07:51.493125Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "success",
|
||||
"pid": 20460,
|
||||
"execution_time": 37.787662
|
||||
},
|
||||
{
|
||||
"id": "76b18be0",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-04T22:06:04.365756Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 1036,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "d3ad9738",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-04T18:56:27.720254Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 43496,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "b7796abb",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-04T18:56:17.950282Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "success",
|
||||
"pid": 36312,
|
||||
"execution_time": 8.35251
|
||||
},
|
||||
{
|
||||
"id": "c29e9b02",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-04T12:20:30.402310Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 31416,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "2a2caf05",
|
||||
"group_id": "1",
|
||||
"script_name": "test_symbolic.py",
|
||||
"executed_date": "2025-06-04T11:15:06.230442Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "success",
|
||||
"pid": 15052,
|
||||
"execution_time": 3.768344
|
||||
},
|
||||
{
|
||||
"id": "5e008836",
|
||||
"group_id": "1",
|
||||
"script_name": "test_symbolic.py",
|
||||
"executed_date": "2025-06-04T11:15:00.297979Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 47804,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "b7b7f4da",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-04T11:13:44.475321Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "error",
|
||||
"pid": 41924,
|
||||
"execution_time": 27.585175
|
||||
},
|
||||
{
|
||||
"id": "a8bf1045",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-04T11:13:24.997521Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "python.exe",
|
||||
"status": "error",
|
||||
"pid": 29848,
|
||||
"execution_time": 8.158545
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"max_entries": 100,
|
||||
"auto_cleanup_days": 30,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -7,11 +7,45 @@
|
|||
"description": "",
|
||||
"category": "Herramientas",
|
||||
"version": "1.0",
|
||||
"python_env": "tia_scripting",
|
||||
"directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"created_date": "2025-06-03T11:46:28.443910Z",
|
||||
"updated_date": "2025-06-03T12:08:06.571266Z"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "ADAM Emulator - Gateway",
|
||||
"description": "",
|
||||
"category": "Desarrollo",
|
||||
"version": "1.0",
|
||||
"python_env": "tia_scripting",
|
||||
"directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"created_date": "2025-06-03T11:57:31.622922Z",
|
||||
"updated_date": "2025-06-03T12:08:15.119223Z"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "Traducir Textos usando LLM",
|
||||
"description": "",
|
||||
"category": "Otros",
|
||||
"version": "1.0",
|
||||
"python_env": "tia_scripting",
|
||||
"directory": "D:/Proyectos/Scripts/HMI Translate",
|
||||
"created_date": "2025-06-03T12:31:19.529046Z",
|
||||
"updated_date": "2025-06-03T12:44:24.651659Z"
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"name": "Tia Portal Utils",
|
||||
"description": "",
|
||||
"category": "Otros",
|
||||
"version": "1.0",
|
||||
"python_env": "tia_scripting",
|
||||
"directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"author": "",
|
||||
"tags": [],
|
||||
"created_date": "2025-06-03T11:46:28.443910Z",
|
||||
"updated_date": "2025-06-03T11:46:28.443910Z"
|
||||
"created_date": "2025-06-11T22:55:41.635081Z",
|
||||
"updated_date": "2025-06-11T22:55:41.635081Z"
|
||||
}
|
||||
],
|
||||
"categories": {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[20:03:36] Iniciando ejecución de x1.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Source...
|
||||
[20:03:37] --- TIA Portal Data Exporter (Blocks, UDTs, Tags) ---
|
||||
[20:03:47] No project file selected. Exiting.
|
||||
[20:03:47] Ejecución de x1.py finalizada (success). Duración: 0:00:10.497365.
|
||||
[20:03:47] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\log_x1.txt
|
|
@ -2,6 +2,8 @@ import os
|
|||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
@ -12,6 +14,7 @@ class LauncherManager:
|
|||
self.launcher_config_path = os.path.join(data_path, "launcher_scripts.json")
|
||||
self.favorites_path = os.path.join(data_path, "launcher_favorites.json")
|
||||
self.history_path = os.path.join(data_path, "launcher_history.json")
|
||||
self.script_metadata_path = os.path.join(data_path, "launcher_script_metadata.json")
|
||||
|
||||
# Inicializar archivos si no existen
|
||||
self._initialize_files()
|
||||
|
@ -85,6 +88,15 @@ class LauncherManager:
|
|||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_history, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Inicializar launcher_script_metadata.json
|
||||
if not os.path.exists(self.script_metadata_path):
|
||||
default_metadata = {
|
||||
"version": "1.0",
|
||||
"script_metadata": {}
|
||||
}
|
||||
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_metadata, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def get_launcher_groups(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener todos los grupos de scripts GUI"""
|
||||
try:
|
||||
|
@ -131,6 +143,7 @@ class LauncherManager:
|
|||
group_data.setdefault("version", "1.0")
|
||||
group_data.setdefault("author", "")
|
||||
group_data.setdefault("tags", [])
|
||||
group_data.setdefault("python_env", "base") # Entorno por defecto
|
||||
group_data.setdefault("created_date", current_time)
|
||||
group_data["updated_date"] = current_time
|
||||
|
||||
|
@ -197,13 +210,72 @@ class LauncherManager:
|
|||
# Limpiar favoritos del grupo eliminado
|
||||
self._cleanup_favorites_for_group(group_id)
|
||||
|
||||
# Limpiar metadatos de scripts del grupo eliminado
|
||||
self._cleanup_script_metadata_for_group(group_id)
|
||||
|
||||
return {"status": "success", "message": "Grupo eliminado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error eliminando grupo: {str(e)}"}
|
||||
|
||||
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener scripts de un grupo específico"""
|
||||
"""Obtener scripts de un grupo específico con metadatos"""
|
||||
try:
|
||||
print(f"[DEBUG] Loading scripts for group: {group_id}")
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
print(f"[DEBUG] Group {group_id} not found")
|
||||
return []
|
||||
|
||||
directory = group["directory"]
|
||||
print(f"[DEBUG] Group directory: {directory}")
|
||||
if not os.path.isdir(directory):
|
||||
print(f"[DEBUG] Directory {directory} does not exist or is not a directory")
|
||||
return []
|
||||
|
||||
# Cargar metadatos de scripts
|
||||
script_metadata = self._load_script_metadata()
|
||||
|
||||
scripts = []
|
||||
python_files = [f for f in os.listdir(directory) if f.endswith('.py') and not f.startswith('_')]
|
||||
print(f"[DEBUG] Found {len(python_files)} Python files: {python_files}")
|
||||
|
||||
for file in python_files:
|
||||
script_path = os.path.join(directory, file)
|
||||
if os.path.isfile(script_path):
|
||||
# Clave para metadatos
|
||||
metadata_key = f"{group_id}_{file}"
|
||||
metadata = script_metadata.get(metadata_key, {})
|
||||
|
||||
# Verificar si está oculto
|
||||
if metadata.get("hidden", False):
|
||||
print(f"[DEBUG] Script {file} is hidden, skipping")
|
||||
continue
|
||||
|
||||
script_info = {
|
||||
"name": file,
|
||||
"display_name": metadata.get("display_name", file[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
"long_description": metadata.get("long_description", ""),
|
||||
"path": script_path,
|
||||
"size": os.path.getsize(script_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||
"hidden": metadata.get("hidden", False)
|
||||
}
|
||||
scripts.append(script_info)
|
||||
print(f"[DEBUG] Added script: {script_info['display_name']} ({file})")
|
||||
|
||||
print(f"[DEBUG] Returning {len(scripts)} scripts for group {group_id}")
|
||||
return sorted(scripts, key=lambda x: x["display_name"])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting scripts for group {group_id}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return []
|
||||
|
||||
def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gestión"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
|
@ -213,28 +285,115 @@ class LauncherManager:
|
|||
if not os.path.isdir(directory):
|
||||
return []
|
||||
|
||||
# Cargar metadatos de scripts
|
||||
script_metadata = self._load_script_metadata()
|
||||
|
||||
scripts = []
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.py') and not file.startswith('_'):
|
||||
script_path = os.path.join(directory, file)
|
||||
if os.path.isfile(script_path):
|
||||
# Clave para metadatos
|
||||
metadata_key = f"{group_id}_{file}"
|
||||
metadata = script_metadata.get(metadata_key, {})
|
||||
|
||||
scripts.append({
|
||||
"name": file,
|
||||
"display_name": file[:-3], # Sin extensión .py
|
||||
"display_name": metadata.get("display_name", file[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
"long_description": metadata.get("long_description", ""),
|
||||
"path": script_path,
|
||||
"size": os.path.getsize(script_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat()
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||
"hidden": metadata.get("hidden", False)
|
||||
})
|
||||
|
||||
return sorted(scripts, key=lambda x: x["name"])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting scripts for group {group_id}: {e}")
|
||||
print(f"Error getting all scripts for group {group_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_script_metadata(self, group_id: str, script_name: str) -> Dict[str, Any]:
|
||||
"""Obtener metadatos de un script específico"""
|
||||
try:
|
||||
script_metadata = self._load_script_metadata()
|
||||
metadata_key = f"{group_id}_{script_name}"
|
||||
return script_metadata.get(metadata_key, {
|
||||
"display_name": script_name[:-3] if script_name.endswith('.py') else script_name,
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": False
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"Error getting script metadata for {group_id}/{script_name}: {e}")
|
||||
return {}
|
||||
|
||||
def update_script_metadata(self, group_id: str, script_name: str, metadata: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Actualizar metadatos de un script"""
|
||||
try:
|
||||
script_metadata = self._load_script_metadata()
|
||||
metadata_key = f"{group_id}_{script_name}"
|
||||
|
||||
# Actualizar metadatos
|
||||
script_metadata[metadata_key] = {
|
||||
"display_name": metadata.get("display_name", script_name[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
"long_description": metadata.get("long_description", ""),
|
||||
"hidden": metadata.get("hidden", False),
|
||||
"updated_date": datetime.now().isoformat() + "Z"
|
||||
}
|
||||
|
||||
# Guardar
|
||||
self._save_script_metadata(script_metadata)
|
||||
|
||||
return {"status": "success", "message": "Metadatos actualizados exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}
|
||||
|
||||
def get_available_python_envs(self) -> List[Dict[str, str]]:
|
||||
"""Obtener lista de entornos de Python/Miniconda disponibles"""
|
||||
try:
|
||||
envs = [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||
|
||||
# Intentar encontrar Miniconda
|
||||
miniconda_paths = [
|
||||
r"C:\Users\migue\miniconda3",
|
||||
r"C:\ProgramData\miniconda3",
|
||||
r"C:\miniconda3",
|
||||
os.path.expanduser("~/miniconda3"),
|
||||
os.path.expanduser("~/anaconda3")
|
||||
]
|
||||
|
||||
for base_path in miniconda_paths:
|
||||
if os.path.exists(base_path):
|
||||
envs_path = os.path.join(base_path, "envs")
|
||||
if os.path.exists(envs_path):
|
||||
for env_name in os.listdir(envs_path):
|
||||
env_path = os.path.join(envs_path, env_name)
|
||||
python_exe = os.path.join(env_path, "python.exe")
|
||||
if os.path.exists(python_exe):
|
||||
envs.append({
|
||||
"name": env_name,
|
||||
"display_name": f"{env_name} (Miniconda)",
|
||||
"path": python_exe
|
||||
})
|
||||
break # Solo usar el primer Miniconda encontrado
|
||||
|
||||
return envs
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting Python environments: {e}")
|
||||
return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||
|
||||
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
||||
broadcast_func) -> Dict[str, Any]:
|
||||
"""Ejecutar script GUI con argumentos opcionales"""
|
||||
broadcast_func, working_dir: str = None, use_pythonw: bool = False) -> Dict[str, Any]:
|
||||
"""Ejecutar script GUI con argumentos opcionales y entorno específico
|
||||
|
||||
Args:
|
||||
use_pythonw: Si True, usa pythonw.exe (sin logging), si False usa python.exe (con logging)
|
||||
"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
|
@ -244,25 +403,152 @@ class LauncherManager:
|
|||
if not os.path.isfile(script_path):
|
||||
return {"status": "error", "message": "Script no encontrado"}
|
||||
|
||||
# Construir comando
|
||||
cmd = [sys.executable, script_path] + script_args
|
||||
working_dir = group["directory"] # Por defecto directorio del script
|
||||
# Determinar el ejecutable de Python a usar
|
||||
python_env = group.get("python_env", "base")
|
||||
python_executable = self._get_python_executable(python_env, use_pythonw)
|
||||
|
||||
# Determinar directorio de trabajo
|
||||
if working_dir and os.path.isdir(working_dir):
|
||||
exec_working_dir = working_dir
|
||||
else:
|
||||
exec_working_dir = group["directory"] # Por defecto directorio del script
|
||||
|
||||
# Configurar variables de entorno para UTF-8
|
||||
env = os.environ.copy()
|
||||
env['PYTHONIOENCODING'] = 'utf-8'
|
||||
env['PYTHONLEGACYWINDOWSSTDIO'] = '0'
|
||||
# Variables adicionales para encoding
|
||||
env['LANG'] = 'en_US.UTF-8'
|
||||
env['LC_ALL'] = 'en_US.UTF-8'
|
||||
env['PYTHONUNBUFFERED'] = '1'
|
||||
# Forzar UTF-8 en consola de Windows
|
||||
if sys.platform == "win32":
|
||||
env['PYTHONUTF8'] = '1'
|
||||
env['PYTHONLEGACYWINDOWSFSENCODING'] = '0'
|
||||
|
||||
# Construir comando con flag UTF-8
|
||||
if use_pythonw:
|
||||
cmd = [python_executable, script_path] + script_args
|
||||
else:
|
||||
# Para python.exe, agregar flag UTF-8 explícitamente
|
||||
if python_executable.endswith('python.exe'):
|
||||
cmd = [python_executable, '-X', 'utf8', script_path] + script_args
|
||||
else:
|
||||
cmd = [python_executable, script_path] + script_args
|
||||
|
||||
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
||||
broadcast_func(f"Entorno Python: {python_env}")
|
||||
broadcast_func(f"Ejecutable: {'pythonw.exe (sin logging)' if use_pythonw else 'python.exe (con logging)'}")
|
||||
broadcast_func(f"Comando: {' '.join(cmd)}")
|
||||
broadcast_func(f"Directorio: {working_dir}")
|
||||
broadcast_func(f"Directorio: {exec_working_dir}")
|
||||
broadcast_func(f"Encoding: UTF-8 (PYTHONUTF8=1, PYTHONIOENCODING=utf-8)")
|
||||
if '-X' in cmd and 'utf8' in cmd:
|
||||
broadcast_func("Usando flag -X utf8 para forzar UTF-8")
|
||||
broadcast_func("=" * 50)
|
||||
|
||||
# Ejecutar script
|
||||
start_time = datetime.now()
|
||||
|
||||
if use_pythonw:
|
||||
# Con pythonw.exe: No capturar salida, solo ejecutar
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=working_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=exec_working_dir,
|
||||
env=env,
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
|
||||
)
|
||||
|
||||
broadcast_func("Script GUI ejecutado sin logging (pythonw.exe)")
|
||||
broadcast_func(f"PID: {process.pid}")
|
||||
broadcast_func("=" * 50)
|
||||
status = "running"
|
||||
|
||||
except subprocess.SubprocessError as e:
|
||||
error_msg = f"Error ejecutando con pythonw.exe: {str(e)}"
|
||||
broadcast_func(error_msg)
|
||||
return {"status": "error", "message": error_msg}
|
||||
|
||||
else:
|
||||
# Con python.exe: Capturar salida para logging
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=exec_working_dir,
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, # Combinar stderr con stdout
|
||||
text=True,
|
||||
encoding='utf-8', # Forzar UTF-8
|
||||
universal_newlines=True,
|
||||
bufsize=1 # Line buffered
|
||||
)
|
||||
|
||||
# Leer salida en tiempo real
|
||||
def read_output():
|
||||
try:
|
||||
for line in iter(process.stdout.readline, ''):
|
||||
if line.strip(): # Solo enviar líneas no vacías
|
||||
broadcast_func(line.rstrip())
|
||||
process.stdout.close()
|
||||
except Exception as e:
|
||||
broadcast_func(f"Error leyendo salida: {str(e)}")
|
||||
|
||||
# Iniciar thread para leer salida
|
||||
output_thread = threading.Thread(target=read_output, daemon=True)
|
||||
output_thread.start()
|
||||
|
||||
# Esperar más tiempo para capturar salida inicial y detectar finalización
|
||||
time.sleep(2)
|
||||
|
||||
# Verificar si el proceso sigue corriendo
|
||||
poll_result = process.poll()
|
||||
if poll_result is not None:
|
||||
# El proceso terminó
|
||||
execution_time = (datetime.now() - start_time).total_seconds()
|
||||
# Esperar a que termine el thread de lectura
|
||||
output_thread.join(timeout=1)
|
||||
|
||||
if poll_result == 0:
|
||||
broadcast_func("=" * 50)
|
||||
broadcast_func(f"Script completado exitosamente en {execution_time:.2f}s")
|
||||
status = "success"
|
||||
else:
|
||||
broadcast_func("=" * 50)
|
||||
broadcast_func(f"Script terminó con código de error: {poll_result}")
|
||||
status = "error"
|
||||
else:
|
||||
# El proceso sigue corriendo (típico para GUIs)
|
||||
broadcast_func("=" * 50)
|
||||
broadcast_func(f"Script GUI iniciado correctamente (PID: {process.pid})")
|
||||
broadcast_func("Nota: El script sigue ejecutándose en segundo plano")
|
||||
status = "running"
|
||||
|
||||
# Iniciar un thread separado para monitorear la finalización
|
||||
def monitor_completion():
|
||||
try:
|
||||
final_code = process.wait() # Esperar hasta que termine
|
||||
end_time = datetime.now()
|
||||
final_execution_time = (end_time - start_time).total_seconds()
|
||||
|
||||
# Actualizar el historial cuando termine
|
||||
self._update_history_status(execution_id, final_code, final_execution_time)
|
||||
|
||||
if final_code == 0:
|
||||
broadcast_func(f"[{end_time.strftime('%H:%M:%S')}] Script {script_name} completado exitosamente ({final_execution_time:.2f}s)")
|
||||
else:
|
||||
broadcast_func(f"[{end_time.strftime('%H:%M:%S')}] Script {script_name} terminó con error (código: {final_code})")
|
||||
except Exception as e:
|
||||
broadcast_func(f"Error monitoreando finalización: {str(e)}")
|
||||
|
||||
monitor_thread = threading.Thread(target=monitor_completion, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
except subprocess.SubprocessError as e:
|
||||
error_msg = f"Error ejecutando con python.exe: {str(e)}"
|
||||
broadcast_func(error_msg)
|
||||
return {"status": "error", "message": error_msg}
|
||||
|
||||
# Registrar en historial
|
||||
execution_id = str(uuid.uuid4())[:8]
|
||||
self._add_to_history({
|
||||
|
@ -271,12 +557,14 @@ class LauncherManager:
|
|||
"script_name": script_name,
|
||||
"executed_date": start_time.isoformat() + "Z",
|
||||
"arguments": script_args,
|
||||
"working_directory": working_dir,
|
||||
"status": "running",
|
||||
"pid": process.pid
|
||||
"working_directory": exec_working_dir,
|
||||
"python_env": python_env,
|
||||
"executable_type": "pythonw.exe" if use_pythonw else "python.exe",
|
||||
"status": status,
|
||||
"pid": process.pid,
|
||||
"execution_time": (datetime.now() - start_time).total_seconds() if status != "running" else None
|
||||
})
|
||||
|
||||
broadcast_func(f"Script GUI ejecutado con PID: {process.pid}")
|
||||
broadcast_func(f"ID de ejecución: {execution_id}")
|
||||
|
||||
return {
|
||||
|
@ -291,6 +579,87 @@ class LauncherManager:
|
|||
broadcast_func(error_msg)
|
||||
return {"status": "error", "message": error_msg}
|
||||
|
||||
def _get_python_executable(self, env_name: str, use_pythonw: bool = False) -> str:
|
||||
"""Obtener el ejecutable de Python para un entorno específico
|
||||
|
||||
Args:
|
||||
env_name: Nombre del entorno Python
|
||||
use_pythonw: Si True, usa pythonw.exe (sin consola), si False usa python.exe (con logging)
|
||||
"""
|
||||
if env_name == "base":
|
||||
# Para el sistema base
|
||||
base_dir = os.path.dirname(sys.executable)
|
||||
if use_pythonw:
|
||||
pythonw_path = os.path.join(base_dir, "pythonw.exe")
|
||||
if os.path.exists(pythonw_path):
|
||||
return pythonw_path
|
||||
python_path = os.path.join(base_dir, "python.exe")
|
||||
if os.path.exists(python_path):
|
||||
return python_path
|
||||
return sys.executable
|
||||
|
||||
# Buscar en entornos de Miniconda
|
||||
miniconda_paths = [
|
||||
r"C:\Users\migue\miniconda3",
|
||||
r"C:\ProgramData\miniconda3",
|
||||
r"C:\miniconda3",
|
||||
os.path.expanduser("~/miniconda3"),
|
||||
os.path.expanduser("~/anaconda3")
|
||||
]
|
||||
|
||||
for base_path in miniconda_paths:
|
||||
if use_pythonw:
|
||||
# Intentar pythonw.exe para GUI sin consola
|
||||
env_pythonw_path = os.path.join(base_path, "envs", env_name, "pythonw.exe")
|
||||
if os.path.exists(env_pythonw_path):
|
||||
return env_pythonw_path
|
||||
|
||||
# Intentar python.exe para logging
|
||||
env_python_path = os.path.join(base_path, "envs", env_name, "python.exe")
|
||||
if os.path.exists(env_python_path):
|
||||
return env_python_path
|
||||
|
||||
# Fallback final al sistema
|
||||
base_dir = os.path.dirname(sys.executable)
|
||||
if use_pythonw:
|
||||
pythonw_path = os.path.join(base_dir, "pythonw.exe")
|
||||
if os.path.exists(pythonw_path):
|
||||
return pythonw_path
|
||||
return sys.executable
|
||||
|
||||
def _load_script_metadata(self) -> Dict[str, Any]:
|
||||
"""Cargar metadatos de scripts"""
|
||||
try:
|
||||
with open(self.script_metadata_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get("script_metadata", {})
|
||||
except Exception as e:
|
||||
print(f"Error loading script metadata: {e}")
|
||||
return {}
|
||||
|
||||
def _save_script_metadata(self, metadata: Dict[str, Any]):
|
||||
"""Guardar metadatos de scripts"""
|
||||
try:
|
||||
data = {
|
||||
"version": "1.0",
|
||||
"script_metadata": metadata,
|
||||
"updated_date": datetime.now().isoformat() + "Z"
|
||||
}
|
||||
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving script metadata: {e}")
|
||||
|
||||
def _cleanup_script_metadata_for_group(self, group_id: str):
|
||||
"""Limpiar metadatos de scripts de un grupo eliminado"""
|
||||
try:
|
||||
script_metadata = self._load_script_metadata()
|
||||
# Filtrar metadatos del grupo eliminado
|
||||
filtered_metadata = {k: v for k, v in script_metadata.items() if not k.startswith(f"{group_id}_")}
|
||||
self._save_script_metadata(filtered_metadata)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up script metadata for group {group_id}: {e}")
|
||||
|
||||
def get_favorites(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener lista de favoritos"""
|
||||
try:
|
||||
|
@ -414,3 +783,236 @@ class LauncherManager:
|
|||
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up favorites for group {group_id}: {e}")
|
||||
|
||||
def _update_history_status(self, execution_id: str, final_code: int, final_execution_time: float):
|
||||
"""Actualizar el estado del historial de ejecución"""
|
||||
try:
|
||||
with open(self.history_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
history = data.get("history", [])
|
||||
for i, entry in enumerate(history):
|
||||
if entry["id"] == execution_id:
|
||||
history[i]["status"] = "success" if final_code == 0 else "error"
|
||||
history[i]["execution_time"] = final_execution_time
|
||||
break
|
||||
|
||||
data["history"] = history
|
||||
|
||||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error updating history status: {e}")
|
||||
|
||||
def focus_process(self, pid: int) -> Dict[str, str]:
|
||||
"""Activar el foco de un proceso por su PID"""
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
# Funciones de Windows API
|
||||
user32 = ctypes.windll.user32
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
|
||||
# Encontrar ventana por PID
|
||||
def enum_windows_proc(hwnd, pid):
|
||||
window_pid = wintypes.DWORD()
|
||||
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(window_pid))
|
||||
if window_pid.value == pid:
|
||||
# Traer ventana al frente
|
||||
user32.SetForegroundWindow(hwnd)
|
||||
user32.ShowWindow(hwnd, 9) # SW_RESTORE
|
||||
return False # Detener enumeración
|
||||
return True
|
||||
|
||||
# Enumeración de ventanas
|
||||
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
|
||||
enum_proc = WNDENUMPROC(enum_windows_proc)
|
||||
user32.EnumWindows(enum_proc, pid)
|
||||
|
||||
return {"status": "success", "message": f"Proceso {pid} activado"}
|
||||
else:
|
||||
return {"status": "error", "message": "Función solo disponible en Windows"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error activando proceso: {str(e)}"}
|
||||
|
||||
def terminate_process(self, pid: int) -> Dict[str, str]:
|
||||
"""Cerrar un proceso por su PID"""
|
||||
try:
|
||||
import psutil
|
||||
|
||||
process = psutil.Process(pid)
|
||||
process_name = process.name()
|
||||
|
||||
# Intentar cerrar suavemente primero
|
||||
process.terminate()
|
||||
|
||||
# Esperar un momento para que cierre
|
||||
time.sleep(1)
|
||||
|
||||
# Si sigue corriendo, forzar cierre
|
||||
if process.is_running():
|
||||
process.kill()
|
||||
|
||||
return {"status": "success", "message": f"Proceso {process_name} (PID: {pid}) cerrado exitosamente"}
|
||||
|
||||
except ImportError:
|
||||
# Fallback sin psutil
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=True)
|
||||
else:
|
||||
subprocess.run(["kill", "-9", str(pid)], check=True)
|
||||
return {"status": "success", "message": f"Proceso {pid} cerrado"}
|
||||
except subprocess.CalledProcessError as e:
|
||||
return {"status": "error", "message": f"Error cerrando proceso: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error: {str(e)}"}
|
||||
|
||||
def get_running_processes(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener lista de procesos en ejecución del historial"""
|
||||
try:
|
||||
history = self.get_history()
|
||||
running_processes = []
|
||||
|
||||
for entry in history:
|
||||
if entry.get("status") == "running" and entry.get("pid"):
|
||||
try:
|
||||
# Verificar si el proceso sigue corriendo
|
||||
if sys.platform == "win32":
|
||||
result = subprocess.run(
|
||||
["tasklist", "/FI", f"PID eq {entry['pid']}", "/FO", "CSV"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if str(entry['pid']) in result.stdout:
|
||||
running_processes.append(entry)
|
||||
else:
|
||||
# Linux/Mac
|
||||
result = subprocess.run(
|
||||
["ps", "-p", str(entry['pid'])],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
running_processes.append(entry)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return running_processes
|
||||
except Exception as e:
|
||||
print(f"Error getting running processes: {e}")
|
||||
return []
|
||||
|
||||
def get_markdown_files(self, group_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener archivos Markdown de un grupo (root y subdirectorios hasta 10 archivos)"""
|
||||
try:
|
||||
print(f"[DEBUG] Looking for markdown files in group: {group_id}")
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
print(f"[DEBUG] Group {group_id} not found for markdown search")
|
||||
return []
|
||||
|
||||
directory = group["directory"]
|
||||
print(f"[DEBUG] Searching markdown files in directory: {directory}")
|
||||
if not os.path.isdir(directory):
|
||||
print(f"[DEBUG] Directory {directory} does not exist for markdown search")
|
||||
return []
|
||||
|
||||
markdown_files = []
|
||||
|
||||
# Buscar en directorio root
|
||||
all_files = os.listdir(directory)
|
||||
print(f"[DEBUG] All files in directory: {all_files}")
|
||||
|
||||
for file in all_files:
|
||||
if file.lower().endswith('.md'):
|
||||
file_path = os.path.join(directory, file)
|
||||
if os.path.isfile(file_path):
|
||||
print(f"[DEBUG] Found markdown file: {file}")
|
||||
markdown_files.append({
|
||||
"name": file,
|
||||
"display_name": file[:-3], # Sin extensión .md
|
||||
"path": file_path,
|
||||
"relative_path": file,
|
||||
"level": 0, # Root level
|
||||
"size": os.path.getsize(file_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
|
||||
})
|
||||
else:
|
||||
print(f"[DEBUG] {file} is not a file, skipping")
|
||||
|
||||
# Buscar en subdirectorios (máximo 1 nivel)
|
||||
try:
|
||||
for subdir in all_files:
|
||||
subdir_path = os.path.join(directory, subdir)
|
||||
if os.path.isdir(subdir_path) and (not subdir.startswith('.') or subdir.startswith('.doc')):
|
||||
print(f"[DEBUG] Searching in subdirectory: {subdir}")
|
||||
subdir_files = os.listdir(subdir_path)
|
||||
for file in subdir_files:
|
||||
if file.lower().endswith('.md'):
|
||||
file_path = os.path.join(subdir_path, file)
|
||||
if os.path.isfile(file_path):
|
||||
print(f"[DEBUG] Found markdown file in subdir: {subdir}/{file}")
|
||||
markdown_files.append({
|
||||
"name": file,
|
||||
"display_name": f"{subdir}/{file[:-3]}",
|
||||
"path": file_path,
|
||||
"relative_path": f"{subdir}/{file}",
|
||||
"level": 1, # Subdirectory level
|
||||
"size": os.path.getsize(file_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
|
||||
})
|
||||
|
||||
# Limitar a 10 archivos total
|
||||
if len(markdown_files) >= 10:
|
||||
break
|
||||
|
||||
if len(markdown_files) >= 10:
|
||||
break
|
||||
except PermissionError as e:
|
||||
print(f"[DEBUG] Permission error accessing subdirectories: {e}")
|
||||
# Ignorar subdirectorios sin permisos
|
||||
pass
|
||||
|
||||
# Ordenar por modificación (más recientes primero) y limitar a 10
|
||||
markdown_files.sort(key=lambda x: x["modified"], reverse=True)
|
||||
print(f"[DEBUG] Found {len(markdown_files)} total markdown files")
|
||||
return markdown_files[:10]
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting markdown files for group {group_id}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return []
|
||||
|
||||
def read_markdown_file(self, group_id: str, relative_path: str) -> Dict[str, Any]:
|
||||
"""Leer contenido de un archivo Markdown"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return {"status": "error", "message": "Grupo no encontrado"}
|
||||
|
||||
file_path = os.path.join(group["directory"], relative_path)
|
||||
|
||||
# Verificar que el archivo existe y está dentro del directorio del grupo (seguridad)
|
||||
if not os.path.isfile(file_path):
|
||||
return {"status": "error", "message": "Archivo no encontrado"}
|
||||
|
||||
if not file_path.startswith(group["directory"]):
|
||||
return {"status": "error", "message": "Acceso denegado"}
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"content": content,
|
||||
"file_name": os.path.basename(file_path),
|
||||
"file_path": relative_path,
|
||||
"size": len(content),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error leyendo archivo: {str(e)}"}
|
|
@ -14,3 +14,11 @@ pypandoc==1.15
|
|||
Requests==2.32.3
|
||||
siemens_tia_scripting==1.0.7
|
||||
sympy==1.13.3
|
||||
pillow
|
||||
pystray
|
||||
|
||||
# Nota: Para evitar problemas de encoding, configura estas variables de entorno:
|
||||
# PYTHONIOENCODING=utf-8
|
||||
# PYTHONLEGACYWINDOWSSTDIO=0
|
||||
#
|
||||
# O ejecuta Python con: python -X utf8 app.py
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
@echo off
|
||||
echo Configurando encoding UTF-8 para ParamManager...
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set PYTHONLEGACYWINDOWSSTDIO=0
|
||||
echo.
|
||||
echo Variables de entorno configuradas:
|
||||
echo PYTHONIOENCODING=%PYTHONIOENCODING%
|
||||
echo PYTHONLEGACYWINDOWSSTDIO=%PYTHONLEGACYWINDOWSSTDIO%
|
||||
echo.
|
||||
echo Iniciando ParamManager...
|
||||
python app.py
|
||||
pause
|
|
@ -221,8 +221,8 @@
|
|||
}
|
||||
|
||||
.group-icon-small {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
border: 1px solid #E5E7EB;
|
||||
|
@ -234,7 +234,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Category Styles */
|
||||
|
@ -291,12 +291,10 @@
|
|||
.favorite-star {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
.favorite-star:hover {
|
||||
transform: scale(1.1);
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
.favorite-star.active {
|
||||
|
@ -305,16 +303,11 @@
|
|||
|
||||
/* History Item */
|
||||
.history-item {
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
background: #F9FAFB;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
background: #F3F4F6;
|
||||
border-color: #D1D5DB;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.history-item.success {
|
||||
|
@ -327,6 +320,19 @@
|
|||
|
||||
.history-item.running {
|
||||
border-left: 4px solid #3B82F6;
|
||||
background: #F0F9FF;
|
||||
}
|
||||
|
||||
/* Process control buttons */
|
||||
.history-item button {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.history-item button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Favorites Panel */
|
||||
|
@ -338,6 +344,19 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Favorite Cards - Diseño más compacto */
|
||||
.favorites-card {
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid #FCD34D;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.favorites-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: #F59E0B;
|
||||
}
|
||||
|
||||
/* Group List Item */
|
||||
.group-list-item {
|
||||
display: flex;
|
||||
|
@ -534,3 +553,172 @@
|
|||
outline: 2px solid #3B82F6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Description Modal Styles */
|
||||
.prose {
|
||||
color: #374151;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.prose h1,
|
||||
.prose h2,
|
||||
.prose h3,
|
||||
.prose h4,
|
||||
.prose h5,
|
||||
.prose h6 {
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
font-size: 1.875rem;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.prose ul,
|
||||
.prose ol {
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.prose li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
background-color: #F3F4F6;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
.prose pre {
|
||||
background-color: #F9FAFB;
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.prose pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
border-left: 4px solid #E5E7EB;
|
||||
padding-left: 1rem;
|
||||
font-style: italic;
|
||||
color: #6B7280;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.prose table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.prose th,
|
||||
.prose td {
|
||||
border: 1px solid #E5E7EB;
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.prose th {
|
||||
background-color: #F9FAFB;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
color: #3B82F6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.prose a:hover {
|
||||
color: #1D4ED8;
|
||||
}
|
||||
|
||||
/* Script Card Button Improvements */
|
||||
.script-card .flex.gap-1 button {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.script-card .flex.gap-1 button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Markdown Files */
|
||||
.markdown-file-card {
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.markdown-file-card:hover {
|
||||
border-color: #3B82F6;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Responsive adjustments for favorites grid */
|
||||
@media (max-width: 640px) {
|
||||
.favorites-list.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) and (max-width: 1023px) {
|
||||
.favorites-list.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.favorites-list.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Button styles for favorite cards */
|
||||
.favorites-card button {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.favorites-card button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Truncate text utilities */
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -876,10 +876,14 @@ function toggleConfig(sectionId) {
|
|||
}
|
||||
|
||||
async function clearLogs() {
|
||||
// Eliminada la confirmación - directamente procede a limpiar
|
||||
const response = await fetch('/api/logs', { method: 'DELETE' });
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
document.getElementById('log-area').innerHTML = '';
|
||||
showToast ? showToast('Logs borrados correctamente', 'success') : console.log('Logs cleared');
|
||||
} else {
|
||||
showToast ? showToast('Error al borrar los logs', 'error') : alert('Error al borrar los logs');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1188,26 +1192,6 @@ function fetchLogs() {
|
|||
.catch(error => console.error('Error fetching logs:', error));
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
if (confirm("¿Estás seguro de que quieres borrar los logs?")) {
|
||||
fetch('/api/logs', { method: 'DELETE' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// Limpiar el área de log visualmente AHORA
|
||||
document.getElementById('log-area').innerHTML = '';
|
||||
showToast('Logs borrados correctamente.');
|
||||
} else {
|
||||
showToast('Error al borrar los logs.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error clearing logs:', error);
|
||||
showToast('Error de red al borrar los logs.', 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Necesitarás una función showToast o similar si la usas
|
||||
function showToast(message, type = 'success') {
|
||||
// Implementa tu lógica de Toast aquí
|
||||
|
@ -1276,28 +1260,37 @@ async function saveConfig(level) {
|
|||
}
|
||||
}
|
||||
|
||||
async function openGroupInVsCode() {
|
||||
if (!currentGroup) {
|
||||
async function openGroupInEditor(editorCode, groupSystem, groupId) {
|
||||
// groupId is already the currentGroup string from the select
|
||||
if (!groupId) {
|
||||
alert('Por favor, seleccione un grupo de scripts primero');
|
||||
return;
|
||||
}
|
||||
|
||||
const editorName = editorCode.toUpperCase();
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/open-vscode/${currentGroup}`, {
|
||||
const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
console.log('VS Code opened successfully');
|
||||
if (!response.ok) {
|
||||
// If response is not OK, it might not be JSON (e.g., Flask error page)
|
||||
const errorText = await response.text();
|
||||
console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText);
|
||||
alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text
|
||||
} else {
|
||||
console.error('Error opening VS Code:', result.message);
|
||||
alert(`Error al abrir VS Code: ${result.message}`);
|
||||
const result = await response.json(); // Now it's safer to parse JSON
|
||||
if (result.status === 'success') {
|
||||
console.log(`${editorName} opened successfully`);
|
||||
} else {
|
||||
console.error(`Error al abrir ${editorName}:`, result.message);
|
||||
alert(`Error al abrir ${editorName}: ${result.message}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error calling open-vscode API:', error);
|
||||
alert('Error al intentar abrir VS Code');
|
||||
console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error);
|
||||
alert(`Error de red o del cliente al intentar abrir ${editorName}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1357,3 +1350,68 @@ async function openCurrentWorkingDirectoryInExplorer() {
|
|||
showToast("Error de red al intentar abrir el explorador.", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function openGroupFolder(groupSystem, groupId) {
|
||||
if (!groupId) {
|
||||
alert('Por favor, seleccione un grupo de scripts primero');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/open-group-folder/${groupSystem}/${groupId}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
console.log(`Carpeta abierta: ${result.path}`);
|
||||
} else {
|
||||
alert(`Error al abrir carpeta: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error opening folder:', error);
|
||||
alert('Error al comunicarse con el servidor');
|
||||
}
|
||||
}
|
||||
|
||||
async function copyGroupPath(groupSystem, groupId) {
|
||||
if (!groupId) {
|
||||
alert('Por favor, seleccione un grupo de scripts primero');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/get-group-path/${groupSystem}/${groupId}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
// Copiar al portapapeles usando la API moderna
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(result.path);
|
||||
showToast ? showToast('Path copiado al portapapeles', 'success') : alert('Path copiado al portapapeles');
|
||||
} else {
|
||||
// Fallback para navegadores más antiguos
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = result.path;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showToast ? showToast('Path copiado al portapapeles', 'success') : alert('Path copiado al portapapeles');
|
||||
} catch (err) {
|
||||
console.error('Error copying to clipboard:', err);
|
||||
alert(`Error al copiar. Path: ${result.path}`);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
} else {
|
||||
alert(`Error al obtener path: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting path:', error);
|
||||
alert('Error al comunicarse con el servidor');
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Script Parameter Manager</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}?v=1.1">
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100">
|
||||
|
@ -140,14 +140,33 @@
|
|||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="openGroupInVsCode()" class="bg-blue-500 text-white p-2 rounded mb-2"
|
||||
<button onclick="openGroupInEditor('vscode', 'config', currentGroup)"
|
||||
class="bg-blue-500 text-white p-2 rounded mb-2" id="vscode-config-btn"
|
||||
title="Abrir grupo en VS Code">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
<img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
|
||||
alt="VS Code Icon">
|
||||
</button>
|
||||
<!-- Nuevo botón para Cursor -->
|
||||
<button onclick="openGroupInEditor('cursor', 'config', currentGroup)"
|
||||
class="bg-blue-500 text-white p-2 rounded mb-2" id="cursor-config-btn"
|
||||
title="Abrir grupo en Cursor">
|
||||
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
|
||||
alt="Cursor Icon">
|
||||
</button>
|
||||
<!-- Botón para abrir carpeta del grupo -->
|
||||
<button onclick="openGroupFolder('config', currentGroup)"
|
||||
class="bg-green-500 text-white p-2 rounded mb-2" id="folder-config-btn"
|
||||
title="Abrir carpeta del grupo">
|
||||
📁
|
||||
</button>
|
||||
<!-- Botón para copiar path del grupo -->
|
||||
<button onclick="copyGroupPath('config', currentGroup)"
|
||||
class="bg-gray-500 text-white p-2 rounded mb-2" id="copy-path-config-btn"
|
||||
title="Copiar path del grupo">
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p id="group-description" class="text-gray-600 text-sm italic"></p>
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
<span id="group-version"></span>
|
||||
|
@ -223,17 +242,47 @@
|
|||
<!-- Group Selector -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label>
|
||||
<div class="relative">
|
||||
<div class="flex gap-2">
|
||||
<div class="relative flex-1">
|
||||
<select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12"
|
||||
onchange="loadLauncherScripts()">
|
||||
<option value="">-- Seleccionar Grupo --</option>
|
||||
</select>
|
||||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
||||
<div id="selected-group-icon"
|
||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁</div>
|
||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick="launcherManager.openGroupInEditor('vscode', 'launcher', launcherManager.currentGroup.id)"
|
||||
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
|
||||
id="vscode-launcher-btn" style="display: none;" title="Abrir grupo en VS Code">
|
||||
<img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
|
||||
alt="VS Code Icon">
|
||||
</button>
|
||||
<!-- Nuevo botón para Cursor -->
|
||||
<button
|
||||
onclick="launcherManager.openGroupInEditor('cursor', 'launcher', launcherManager.currentGroup.id)"
|
||||
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
|
||||
id="cursor-launcher-btn" style="display: none;" title="Abrir grupo en Cursor">
|
||||
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
|
||||
alt="Cursor Icon">
|
||||
</button>
|
||||
<!-- Botón para abrir carpeta del grupo launcher -->
|
||||
<button onclick="launcherManager.openGroupFolder()"
|
||||
class="bg-green-500 text-white px-4 py-3 rounded-lg hover:bg-green-600"
|
||||
id="folder-launcher-btn" style="display: none;" title="Abrir carpeta del grupo">
|
||||
📁
|
||||
</button>
|
||||
<!-- Botón para copiar path del grupo launcher -->
|
||||
<button onclick="launcherManager.copyGroupPath()"
|
||||
class="bg-gray-500 text-white px-4 py-3 rounded-lg hover:bg-gray-600"
|
||||
id="copy-path-launcher-btn" style="display: none;" title="Copiar path del grupo">
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="mb-4">
|
||||
|
@ -276,21 +325,43 @@
|
|||
|
||||
<!-- Scripts Grid -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Scripts Disponibles</h2>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Scripts Disponibles</h2>
|
||||
<button onclick="openScriptManager()"
|
||||
class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600" id="manage-scripts-btn"
|
||||
style="display: none;">
|
||||
Gestionar Scripts
|
||||
</button>
|
||||
</div>
|
||||
<div id="launcher-scripts-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Scripts cards dinámicos -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Files Section -->
|
||||
<div id="markdown-files-section" class="mb-6 bg-white p-6 rounded-lg shadow" style="display: none;">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">📄 Documentación (Markdown)</h2>
|
||||
<span class="text-sm text-gray-500">Archivos .md en el directorio del grupo</span>
|
||||
</div>
|
||||
<div id="markdown-files-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<!-- Markdown files cards dinámicos -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History Panel -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex justify-between items-center mb-4 cursor-pointer" onclick="toggleHistoryPanel()">
|
||||
<div class="flex items-center">
|
||||
<span id="history-toggle-icon" class="mr-2">▶</span>
|
||||
<h3 class="text-lg font-semibold">📝 Historial Reciente</h3>
|
||||
<button onclick="clearLauncherHistory()" class="text-red-500 hover:text-red-700 text-sm">
|
||||
</div>
|
||||
<button onclick="event.stopPropagation(); clearLauncherHistory()"
|
||||
class="text-red-500 hover:text-red-700 text-sm">
|
||||
Limpiar Historial
|
||||
</button>
|
||||
</div>
|
||||
<div id="history-list" class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<div id="history-list" class="space-y-2 max-h-64 overflow-y-auto hidden">
|
||||
<!-- Lista dinámica de historial -->
|
||||
</div>
|
||||
</div>
|
||||
|
@ -441,7 +512,7 @@
|
|||
<textarea id="group-description" class="w-full p-2 border rounded h-20"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Categoría</label>
|
||||
<select id="group-category" class="w-full p-2 border rounded">
|
||||
|
@ -457,6 +528,12 @@
|
|||
<label class="block text-sm font-medium mb-1">Versión</label>
|
||||
<input type="text" id="group-version" class="w-full p-2 border rounded" value="1.0">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Entorno Python</label>
|
||||
<select id="group-python-env" class="w-full p-2 border rounded">
|
||||
<option value="base">Cargando...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -490,6 +567,90 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Manager Modal (NUEVO) -->
|
||||
<div id="script-manager-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-screen overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold mb-6">Gestionar Scripts del Grupo</h3>
|
||||
<p class="text-gray-600 mb-4" id="script-manager-group-info">Selecciona un grupo para gestionar sus
|
||||
scripts</p>
|
||||
|
||||
<!-- Lista de scripts -->
|
||||
<div class="space-y-3" id="script-manager-list">
|
||||
<!-- Lista dinámica de scripts -->
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 mt-6 border-t">
|
||||
<button type="button" onclick="closeScriptManager()"
|
||||
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Metadata Editor Modal (NUEVO) -->
|
||||
<div id="script-metadata-editor-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-lg w-full max-h-screen overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Editar Metadatos del Script</h3>
|
||||
|
||||
<form id="script-metadata-form" class="space-y-4">
|
||||
<input type="hidden" id="edit-meta-group-id">
|
||||
<input type="hidden" id="edit-meta-script-name">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold mb-1">Nombre del Archivo</label>
|
||||
<p id="edit-meta-filename-display"
|
||||
class="text-sm text-gray-600 bg-gray-100 p-2 rounded border"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-meta-display-name" class="block text-sm font-bold mb-2">Nombre a
|
||||
Mostrar</label>
|
||||
<input type="text" id="edit-meta-display-name" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-meta-description" class="block text-sm font-bold mb-2">Descripción
|
||||
Corta</label>
|
||||
<input type="text" id="edit-meta-description" class="w-full p-2 border rounded">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-meta-long-description" class="block text-sm font-bold mb-2">Descripción
|
||||
Larga / Ayuda</label>
|
||||
<textarea id="edit-meta-long-description" class="w-full p-2 border rounded"
|
||||
rows="5"></textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">Usa Markdown. Doble Enter para párrafo nuevo, dos
|
||||
espacios + Enter para salto de línea simple.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="edit-meta-hidden" class="form-checkbox h-5 w-5 mr-2">
|
||||
<label for="edit-meta-hidden" class="text-sm font-bold">Ocultar script (no aparecerá en la
|
||||
lista de ejecución)</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4">
|
||||
<button type="button" onclick="closeScriptMetadataEditor()"
|
||||
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||
Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Arguments Modal (NUEVO) -->
|
||||
<div id="script-args-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
|
@ -512,6 +673,39 @@
|
|||
Separar argumentos con espacios. Usar comillas para valores con espacios.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">
|
||||
Directorio de Ejecución
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="script-working-dir" class="flex-1 p-2 border rounded"
|
||||
placeholder="Por defecto: directorio del script">
|
||||
<button type="button" onclick="browseWorkingDirectory()"
|
||||
class="bg-gray-500 text-white px-3 py-2 rounded text-sm">
|
||||
📁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">
|
||||
Tipo de Ejecución
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="execution-type" value="false" checked class="mr-2">
|
||||
<span class="text-sm">🖥️ Con Log (python.exe)</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="execution-type" value="true" class="mr-2">
|
||||
<span class="text-sm">🚀 Sin Log (pythonw.exe)</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Con Log: Muestra la salida del script. Sin Log: No muestra ventana de consola.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
|
@ -528,10 +722,71 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Description Modal (NUEVO) -->
|
||||
<div id="script-description-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-hidden">
|
||||
<div class="p-6 border-b">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold" id="desc-modal-script-name">Descripción del Script</h3>
|
||||
<p class="text-sm text-gray-600" id="desc-modal-script-file"></p>
|
||||
</div>
|
||||
<button onclick="closeDescriptionModal()"
|
||||
class="text-gray-500 hover:text-gray-700 text-2xl">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto max-h-[60vh]">
|
||||
<div id="script-description-content" class="prose prose-sm max-w-none">
|
||||
<!-- Contenido markdown renderizado -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t bg-gray-50 flex justify-end">
|
||||
<button onclick="closeDescriptionModal()"
|
||||
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Viewer Modal (NUEVO) -->
|
||||
<div id="markdown-viewer-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
|
||||
<div class="p-6 border-b">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold" id="markdown-viewer-title">Documento Markdown</h3>
|
||||
<p class="text-sm text-gray-600" id="markdown-viewer-path"></p>
|
||||
</div>
|
||||
<button onclick="closeMarkdownViewer()"
|
||||
class="text-gray-500 hover:text-gray-700 text-2xl">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto max-h-[75vh]">
|
||||
<div id="markdown-viewer-content" class="prose prose-lg max-w-none">
|
||||
<!-- Contenido markdown renderizado -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t bg-gray-50 flex justify-end">
|
||||
<button onclick="closeMarkdownViewer()"
|
||||
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
|
||||
<script>
|
||||
// Inicializar markdown-it globalmente
|
||||
window.markdownit = window.markdownit || markdownit;
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
console.log('Window loaded, initializing app...');
|
||||
if (typeof initializeApp === 'function') {
|
||||
|
|
Loading…
Reference in New Issue