#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Script para generar documentación de adaptación de IOs entre TwinCAT y TIA Portal - Proyecto SIDEL Autor: Generado automáticamente Proyecto: E5.007560 - Modifica O&U - SAE235 """ import re import os import sys import pandas as pd import json from pathlib import Path from typing import Dict, List, Tuple, Optional import argparse from collections import defaultdict # Configurar el path al directorio raíz del proyecto script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ) sys.path.append(script_root) # Importar la función de configuración from backend.script_utils import load_configuration def load_tiaportal_adaptations(working_directory, file_path='IO Adapted.md'): """Carga las adaptaciones de TIA Portal desde el archivo markdown""" full_file_path = os.path.join(working_directory, file_path) print(f"Cargando adaptaciones de TIA Portal desde: {full_file_path}") adaptations = {} if not os.path.exists(full_file_path): print(f"⚠️ Archivo {full_file_path} no encontrado") return adaptations with open(full_file_path, 'r', encoding='utf-8') as f: content = f.read() # Patrones mejorados para diferentes tipos de IOs patterns = [ # Digitales: E0.0, A0.0 r'\|\s*([EA]\d+\.\d+)\s*\|\s*([^|]+?)\s*\|', # Analógicos: PEW100, PAW100 r'\|\s*(P[EA]W\d+)\s*\|\s*([^|]+?)\s*\|', # Profibus: EW 1640, AW 1640 r'\|\s*([EA]W\s+\d+)\s*\|\s*([^|]+?)\s*\|' ] for pattern in patterns: matches = re.findall(pattern, content, re.MULTILINE) for io_addr, master_tag in matches: io_addr = io_addr.strip() master_tag = master_tag.strip() if io_addr and master_tag and not master_tag.startswith('-'): adaptations[io_addr] = master_tag print(f" 📍 {io_addr} → {master_tag}") print(f"✅ Cargadas {len(adaptations)} adaptaciones de TIA Portal") return adaptations def scan_twincat_definitions(working_directory, directory='TwinCat'): """Escanea archivos TwinCAT para encontrar definiciones de variables AT %""" full_directory = os.path.join(working_directory, directory) print(f"\n🔍 Escaneando definiciones TwinCAT en: {full_directory}") definitions = {} if not os.path.exists(full_directory): print(f"⚠️ Directorio {full_directory} no encontrado") return definitions # Patrones para definiciones AT % definition_patterns = [ r'(\w+)\s+AT\s+%([IQ][XWB]\d+(?:\.\d+)?)\s*:\s*(\w+);', # Activas r'(\w+)\s+\(\*\s*AT\s+%([IQ][XWB]\d+(?:\.\d+)?)\s*\*\)\s*:\s*(\w+);', # Comentadas ] for file_path in Path(full_directory).glob('*.scl'): print(f" 📄 Procesando: {file_path.name}") with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() for pattern in definition_patterns: matches = re.findall(pattern, content, re.MULTILINE | re.IGNORECASE) for var_name, io_addr, data_type in matches: var_name = var_name.strip() io_addr = io_addr.strip() data_type = data_type.strip() definitions[var_name] = { 'address': io_addr, 'type': data_type, 'file': file_path.name, 'definition_line': content[:content.find(var_name)].count('\n') + 1 } print(f" 🔗 {var_name} AT %{io_addr} : {data_type}") print(f"✅ Encontradas {len(definitions)} definiciones TwinCAT") return definitions def scan_twincat_usage(working_directory, directory='TwinCat'): """Escanea archivos TwinCAT para encontrar uso de variables""" full_directory = os.path.join(working_directory, directory) print(f"\n🔍 Escaneando uso de variables TwinCAT en: {full_directory}") usage_data = defaultdict(list) if not os.path.exists(full_directory): print(f"⚠️ Directorio {full_directory} no encontrado") return usage_data for file_path in Path(full_directory).glob('*.scl'): print(f" 📄 Analizando uso en: {file_path.name}") with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines() for line_num, line in enumerate(lines, 1): # Buscar variables que empiecen con DI_, DO_, AI_, AO_ var_matches = re.findall(r'\b([DA][IO]_\w+)\b', line) for var_name in var_matches: usage_data[var_name].append({ 'file': file_path.name, 'line': line_num, 'context': line.strip()[:100] + ('...' if len(line.strip()) > 100 else '') }) print(f"✅ Encontrado uso de {len(usage_data)} variables diferentes") return usage_data def convert_tia_to_twincat(tia_addr): """Convierte direcciones TIA Portal a formato TwinCAT""" conversions = [] # Digitales if re.match(r'^E\d+\.\d+$', tia_addr): # E0.0 → IX0.0 twincat_addr = tia_addr.replace('E', 'IX') conversions.append(twincat_addr) elif re.match(r'^A\d+\.\d+$', tia_addr): # A0.0 → QX0.0 twincat_addr = tia_addr.replace('A', 'QX') conversions.append(twincat_addr) # Analógicos elif re.match(r'^PEW\d+$', tia_addr): # PEW100 → IW100 twincat_addr = tia_addr.replace('PEW', 'IW') conversions.append(twincat_addr) elif re.match(r'^PAW\d+$', tia_addr): # PAW100 → QW100 twincat_addr = tia_addr.replace('PAW', 'QW') conversions.append(twincat_addr) # Profibus elif re.match(r'^EW\s+\d+$', tia_addr): # EW 1234 → IB1234 addr_num = re.search(r'\d+', tia_addr).group() conversions.append(f'IB{addr_num}') elif re.match(r'^AW\s+\d+$', tia_addr): # AW 1234 → QB1234 addr_num = re.search(r'\d+', tia_addr).group() conversions.append(f'QB{addr_num}') return conversions def find_variable_by_address(definitions, target_address): """Busca variable por dirección exacta""" for var_name, info in definitions.items(): if info['address'] == target_address: return var_name, info return None, None def find_variable_by_name_similarity(definitions, usage_data, master_tag): """Busca variables por similitud de nombre""" candidates = [] # Limpiar el master tag para comparación clean_master = re.sub(r'^[DA][IO]_', '', master_tag).lower() # Buscar en definiciones for var_name, info in definitions.items(): clean_var = re.sub(r'^[DA][IO]_', '', var_name).lower() if clean_master in clean_var or clean_var in clean_master: candidates.append((var_name, info, 'definition')) # Buscar en uso for var_name in usage_data.keys(): clean_var = re.sub(r'^[DA][IO]_', '', var_name).lower() if clean_master in clean_var or clean_var in clean_master: # Intentar encontrar la definición de esta variable var_info = definitions.get(var_name) if not var_info: var_info = {'address': 'Unknown', 'type': 'Unknown', 'file': 'Not found'} candidates.append((var_name, var_info, 'usage')) return candidates def analyze_adaptations(tia_adaptations, twincat_definitions, twincat_usage): """Analiza las correlaciones entre TIA Portal y TwinCAT""" print(f"\n📊 Analizando correlaciones...") results = [] matches_found = 0 for tia_addr, master_tag in tia_adaptations.items(): result = { 'tia_address': tia_addr, 'master_tag': master_tag, 'twincat_variable': None, 'twincat_address': None, 'twincat_type': None, 'match_type': None, 'definition_file': None, 'usage_files': [], 'usage_count': 0, 'confidence': 'Low' } # 1. Buscar por conversión directa de dirección twincat_addresses = convert_tia_to_twincat(tia_addr) var_found = False for twincat_addr in twincat_addresses: var_name, var_info = find_variable_by_address(twincat_definitions, twincat_addr) if var_name: result.update({ 'twincat_variable': var_name, 'twincat_address': var_info['address'], 'twincat_type': var_info['type'], 'match_type': 'Address Match', 'definition_file': var_info['file'], 'confidence': 'High' }) var_found = True matches_found += 1 break # 2. Si no se encontró por dirección, buscar por nombre if not var_found: candidates = find_variable_by_name_similarity(twincat_definitions, twincat_usage, master_tag) if candidates: # Tomar el mejor candidato best_candidate = candidates[0] var_name, var_info, source = best_candidate result.update({ 'twincat_variable': var_name, 'twincat_address': var_info.get('address', 'Unknown'), 'twincat_type': var_info.get('type', 'Unknown'), 'match_type': f'Name Similarity ({source})', 'definition_file': var_info.get('file', 'Unknown'), 'confidence': 'Medium' }) matches_found += 1 # 3. Buscar información de uso if result['twincat_variable']: var_name = result['twincat_variable'] if var_name in twincat_usage: usage_info = twincat_usage[var_name] result['usage_files'] = list(set([u['file'] for u in usage_info])) result['usage_count'] = len(usage_info) results.append(result) # Log del progreso status = "✅" if result['twincat_variable'] else "❌" print(f" {status} {tia_addr} → {master_tag}") if result['twincat_variable']: print(f" 🔗 {result['twincat_variable']} AT %{result['twincat_address']}") if result['usage_count'] > 0: print(f" 📝 Usado en {result['usage_count']} lugares: {', '.join(result['usage_files'])}") print(f"\n🎯 Resumen: {matches_found}/{len(tia_adaptations)} variables correlacionadas ({matches_found/len(tia_adaptations)*100:.1f}%)") return results def create_results_directory(working_directory): """Crea el directorio de resultados si no existe""" results_dir = Path(working_directory) / 'resultados' results_dir.mkdir(exist_ok=True) print(f"📁 Directorio de resultados: {results_dir.absolute()}") return results_dir def generate_json_output(results, working_directory, output_file='io_adaptation_data.json'): """Genera archivo JSON con datos estructurados para análisis posterior""" full_output_file = os.path.join(working_directory, 'resultados', output_file) print(f"\n📄 Generando archivo JSON: {full_output_file}") json_data = { "metadata": { "generated_at": pd.Timestamp.now().isoformat(), "project": "E5.007560 - Modifica O&U - SAE235", "total_adaptations": len(results), "matched_variables": len([r for r in results if r['twincat_variable']]), "high_confidence": len([r for r in results if r['confidence'] == 'High']), "medium_confidence": len([r for r in results if r['confidence'] == 'Medium']) }, "adaptations": [] } for result in results: adaptation = { "tia_portal": { "address": result['tia_address'], "tag": result['master_tag'] }, "twincat": { "variable": result['twincat_variable'], "address": result['twincat_address'], "data_type": result['twincat_type'], "definition_file": result['definition_file'] }, "correlation": { "match_type": result['match_type'], "confidence": result['confidence'], "found": result['twincat_variable'] is not None }, "usage": { "usage_count": result['usage_count'], "usage_files": result['usage_files'] } } json_data["adaptations"].append(adaptation) with open(full_output_file, 'w', encoding='utf-8') as f: json.dump(json_data, f, indent=2, ensure_ascii=False) print(f"✅ Archivo JSON generado: {full_output_file}") def generate_detailed_report(results, working_directory, output_file='IO_Detailed_Analysis_Report.md'): """Genera un reporte detallado con tabla markdown""" full_output_file = os.path.join(working_directory, 'resultados', output_file) print(f"\n📄 Generando reporte detallado: {full_output_file}") with open(full_output_file, 'w', encoding='utf-8') as f: f.write("# Reporte Detallado de Análisis de Adaptación IO\n\n") f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") # Estadísticas total = len(results) matched = len([r for r in results if r['twincat_variable']]) high_conf = len([r for r in results if r['confidence'] == 'High']) medium_conf = len([r for r in results if r['confidence'] == 'Medium']) f.write("## 📊 Estadísticas Generales\n\n") f.write(f"- **Total adaptaciones procesadas:** {total}\n") f.write(f"- **Variables encontradas:** {matched} ({matched/total*100:.1f}%)\n") f.write(f"- **Coincidencias de alta confianza:** {high_conf}\n") f.write(f"- **Coincidencias de media confianza:** {medium_conf}\n\n") # Tabla de variables correlacionadas exitosamente f.write("## ✅ Variables Correlacionadas Exitosamente\n\n") matched_results = [r for r in results if r['twincat_variable']] if matched_results: # Encabezado de la tabla f.write("| TIA Address | TIA Tag | TwinCAT Variable | TwinCAT Address | Tipo | Método | Confianza | Archivo Def. | Uso | Archivos Uso |\n") f.write("|-------------|---------|------------------|-----------------|------|--------|-----------|--------------|-----|---------------|\n") # Filas de datos for result in matched_results: usage_files_str = ', '.join(result['usage_files'][:3]) # Limitar a 3 archivos if len(result['usage_files']) > 3: usage_files_str += "..." f.write(f"| {result['tia_address']} | " f"`{result['master_tag']}` | " f"`{result['twincat_variable']}` | " f"`%{result['twincat_address']}` | " f"`{result['twincat_type']}` | " f"{result['match_type']} | " f"{result['confidence']} | " f"{result['definition_file']} | " f"{result['usage_count']} | " f"{usage_files_str} |\n") f.write("\n") # Tabla de variables no encontradas f.write("## ❌ Variables No Encontradas\n\n") unmatched_results = [r for r in results if not r['twincat_variable']] if unmatched_results: f.write("| TIA Address | TIA Tag |\n") f.write("|-------------|----------|\n") for result in unmatched_results: f.write(f"| {result['tia_address']} | `{result['master_tag']}` |\n") f.write(f"\n**Total no encontradas:** {len(unmatched_results)}\n\n") # Recomendaciones f.write("## 💡 Recomendaciones\n\n") f.write("1. **Variables de alta confianza** pueden migrarse directamente\n") f.write("2. **Variables de media confianza** requieren verificación manual\n") f.write("3. **Variables no encontradas** requieren mapeo manual o pueden ser obsoletas\n") f.write("4. Variables con uso extensivo son prioritarias para la migración\n\n") # Resumen por confianza f.write("## 📈 Distribución por Confianza\n\n") f.write("| Nivel de Confianza | Cantidad | Porcentaje |\n") f.write("|--------------------|----------|------------|\n") f.write(f"| Alta | {high_conf} | {high_conf/total*100:.1f}% |\n") f.write(f"| Media | {medium_conf} | {medium_conf/total*100:.1f}% |\n") f.write(f"| No encontradas | {total-matched} | {(total-matched)/total*100:.1f}% |\n") print(f"✅ Reporte detallado generado: {full_output_file}") def main(): print("🚀 Iniciando análisis detallado de adaptación de IOs TwinCAT ↔ TIA Portal") print("=" * 80) # Cargar configuración configs = load_configuration() # Verificar que se cargó correctamente if not configs: print("Advertencia: No se pudo cargar la configuración, usando valores por defecto") working_directory = "./" else: working_directory = configs.get("working_directory", "./") # Verificar directorio de trabajo if not os.path.exists(working_directory): print(f"Error: El directorio de trabajo no existe: {working_directory}") return print(f"📁 Directorio de trabajo: {working_directory}") # Crear directorio de resultados results_dir = create_results_directory(working_directory) # Cargar datos tia_adaptations = load_tiaportal_adaptations(working_directory) twincat_definitions = scan_twincat_definitions(working_directory) twincat_usage = scan_twincat_usage(working_directory) # Analizar correlaciones results = analyze_adaptations(tia_adaptations, twincat_definitions, twincat_usage) # Generar reportes en el directorio de resultados generate_detailed_report(results, working_directory) generate_json_output(results, working_directory) # Generar CSV para análisis adicional df = pd.DataFrame(results) csv_file = results_dir / 'io_detailed_analysis.csv' df.to_csv(csv_file, index=False, encoding='utf-8') print(f"✅ Datos exportados a CSV: {csv_file}") print(f"\n🎉 Análisis completado exitosamente!") print(f"📁 Archivos generados en: {results_dir.absolute()}") print(f" 📄 {results_dir / 'IO_Detailed_Analysis_Report.md'}") print(f" 📄 {results_dir / 'io_adaptation_data.json'}") print(f" 📄 {results_dir / 'io_detailed_analysis.csv'}") return results if __name__ == "__main__": results = main()