# -*- coding: utf-8 -*- import json import argparse import os import copy import traceback import re # --- Constantes y Configuración --- SCL_SUFFIX = "_scl" GROUPED_COMMENT = "// Logic included in grouped IF" # Global data variable data = {} # --- Helper Functions --- # (get_scl_representation, format_variable_name, generate_temp_var_name, get_target_scl_name - sin cambios) def get_scl_representation(source_info, network_id, scl_map, access_map): if not source_info: return None if isinstance(source_info, list): scl_parts = [] all_resolved = True for sub_source in source_info: sub_scl = get_scl_representation( sub_source, network_id, scl_map, access_map ) if sub_scl is None: all_resolved = False break if ( sub_scl in ["TRUE", "FALSE"] or (sub_scl.startswith('"') and sub_scl.endswith('"')) or sub_scl.isdigit() or (sub_scl.startswith("(") and sub_scl.endswith(")")) ): scl_parts.append(sub_scl) else: scl_parts.append(f"({sub_scl})") return ( " OR ".join(scl_parts) if len(scl_parts) > 1 else (scl_parts[0] if scl_parts else "FALSE") if all_resolved else None ) source_type = source_info.get("type") if source_type == "powerrail": return "TRUE" elif source_type == "variable": name = source_info.get("name") # Asegurar que los nombres de variables se formatean correctamente aquí también return ( format_variable_name(name) if name else f"_ERR_VAR_NO_NAME_{source_info.get('uid')}_" ) elif source_type == "constant": dtype = str(source_info.get("datatype", "")).upper() value = source_info.get("value") try: if dtype == "BOOL": return str(value).upper() elif dtype in [ "INT", "DINT", "SINT", "USINT", "UINT", "UDINT", "LINT", "ULINT", "WORD", "DWORD", "LWORD", "BYTE", ]: return str(value) elif dtype in ["REAL", "LREAL"]: s_val = str(value) return s_val if "." in s_val or "e" in s_val.lower() else s_val + ".0" elif dtype == "STRING": # Escapar comillas simples dentro del string si es necesario str_val = str(value).replace("'", "''") return f"'{str_val}'" elif dtype == "TYPEDCONSTANT": # Podría necesitar formateo específico basado en el tipo real return str(value) else: # Otros tipos (TIME, DATE, etc.) - devolver como string por ahora str_val = str(value).replace("'", "''") return f"'{str_val}'" except Exception as e: print(f"Advertencia: Error formateando constante {source_info}: {e}") return f"_ERR_CONST_FORMAT_{source_info.get('uid')}_" elif source_type == "connection": map_key = ( network_id, source_info.get("source_instruction_uid"), source_info.get("source_pin"), ) return scl_map.get(map_key) elif source_type == "unknown_source": print( f"Advertencia: Refiriendo a fuente desconocida UID: {source_info.get('uid')}" ) return f"_ERR_UNKNOWN_SRC_{source_info.get('uid')}_" else: print(f"Advertencia: Tipo de fuente desconocido: {source_info}") return f"_ERR_INVALID_SRC_TYPE_" def format_variable_name(name): """Limpia el nombre de la variable para SCL.""" if not name: return "_INVALID_NAME_" # Si ya está entre comillas dobles, asumimos que es un nombre complejo (ej. "DB"."Variable") # y lo devolvemos tal cual para SCL. if name.startswith('"') and name.endswith('"'): # Podríamos añadir validación extra aquí si fuera necesario return name # Si no tiene comillas, es un nombre simple (ej. Tag_1, #tempVar) # Reemplazar caracteres no válidos (excepto '_') por '_' # Permitir '#' al inicio para variables temporales prefix = "" if name.startswith("#"): prefix = "#" name = name[1:] # Permitir letras, números y guiones bajos. Reemplazar el resto. # Asegurarse de que no empiece con número (después del # si existe) if name and name[0].isdigit(): name = "_" + name # Reemplazar caracteres no válidos name = re.sub(r"[^a-zA-Z0-9_]", "_", name) return prefix + name def generate_temp_var_name(network_id, instr_uid, pin_name): net_id_clean = str(network_id).replace("-", "_") instr_uid_clean = str(instr_uid).replace("-", "_") pin_name_clean = str(pin_name).replace("-", "_").lower() # Usar # para variables temporales SCL estándar return f"#_temp_{net_id_clean}_{instr_uid_clean}_{pin_name_clean}" def get_target_scl_name(instruction, output_pin_name, network_id, default_to_temp=True): instr_uid = instruction["instruction_uid"] output_pin_data = instruction["outputs"].get(output_pin_name) target_scl = None if ( output_pin_data and isinstance(output_pin_data, list) and len(output_pin_data) == 1 ): dest_access = output_pin_data[0] if dest_access.get("type") == "variable": target_scl = dest_access.get("name") if target_scl: target_scl = format_variable_name(target_scl) # Formatear nombre else: print( f"Error: Var destino {instr_uid}.{output_pin_name} sin nombre (UID: {dest_access.get('uid')}). {'Usando temp.' if default_to_temp else 'Ignorando.'}" ) target_scl = ( generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None ) elif dest_access.get("type") == "constant": print( f"Advertencia: Instr {instr_uid} escribe en const UID {dest_access.get('uid')}. {'Usando temp.' if default_to_temp else 'Ignorando.'}" ) target_scl = ( generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None ) else: print( f"Advertencia: Destino {instr_uid}.{output_pin_name} no es var/const: {dest_access.get('type')}. {'Usando temp.' if default_to_temp else 'Ignorando.'}" ) target_scl = ( generate_temp_var_name(network_id, instr_uid, output_pin_name) if default_to_temp else None ) elif default_to_temp: target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) # Si target_scl sigue siendo None y no se debe usar temp, devolver None if target_scl is None and not default_to_temp: return None # Si target_scl es None pero sí se permite temp, generar uno ahora if target_scl is None and default_to_temp: target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name) return target_scl # --- Procesadores de Instrucciones --- # (process_contact, process_eq, process_coil, process_convert, process_mod, # process_add, process_move, process_pbox, process_o, process_call - sin cambios significativos, # solo asegurar que usan format_variable_name donde sea necesario para operandos/destinos) # ... (código de los procesadores aquí, asumiendo que ya usan format_variable_name) ... def process_contact(instruction, network_id, scl_map, access_map): """Traduce Contact (normal o negado) a una expresión booleana SCL.""" instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # --- INICIO LEER NEGACIÓN --- # Verificar si el pin 'operand' está marcado como negado en el JSON is_negated = instruction.get("negated_pins", {}).get("operand", False) # --- FIN LEER NEGACIÓN --- # print(f"DEBUG: Intentando procesar CONTACT{' (N)' if is_negated else ''} - UID: {instr_uid} en Red: {network_id}") in_input = instruction["inputs"].get("in") in_rlo_scl = ( "TRUE" if in_input is None else get_scl_representation(in_input, network_id, scl_map, access_map) ) operand_scl = get_scl_representation( instruction["inputs"].get("operand"), network_id, scl_map, access_map ) if in_rlo_scl is None or operand_scl is None: return False # Usar is_negated para aplicar NOT term = f"NOT {operand_scl}" if is_negated else operand_scl if not (term.startswith('"') and term.endswith('"')): # Añadir paréntesis si es NOT o si contiene espacios/operadores if is_negated or ( " " in term and not (term.startswith("(") and term.endswith(")")) ): term = f"({term})" new_rlo_scl = ( term if in_rlo_scl == "TRUE" else ( f"({in_rlo_scl}) AND {term}" if ("AND" in in_rlo_scl or "OR" in in_rlo_scl) and not (in_rlo_scl.startswith("(") and in_rlo_scl.endswith(")")) else f"{in_rlo_scl} AND {term}" ) ) map_key = (network_id, instr_uid, "out") scl_map[map_key] = new_rlo_scl instruction["scl"] = f"// RLO: {new_rlo_scl}" instruction["type"] = instr_type + SCL_SUFFIX return True # --- process_edge_detector MODIFICADA --- def process_edge_detector(instruction, network_id, scl_map, access_map): """Genera SCL para PBox (P_TRIG) o NBox (N_TRIG). Guarda la expresión del pulso en scl_map['out'] y la actualización de memoria en un campo temporal '_edge_mem_update_scl'. El campo 'scl' principal se deja casi vacío/comentario. Usa el nombre de memoria original sin renombrar. """ instr_uid = instruction["instruction_uid"] instr_type_original = instruction["type"] # PBox o NBox if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: return False # Ya procesado o error # 1. Obtener CLK y MemBit original clk_input = instruction["inputs"].get("in") mem_bit_input = instruction["inputs"].get("bit") clk_scl = get_scl_representation(clk_input, network_id, scl_map, access_map) mem_bit_scl_original = get_scl_representation(mem_bit_input, network_id, scl_map, access_map) # Ej: "M19001" # 2. Verificar dependencias y tipo de MemBit if clk_scl is None: return False if mem_bit_scl_original is None: instruction["scl"] = f"// ERROR: {instr_type_original} {instr_uid} MemBit no resuelto." instruction["type"] = instr_type_original + "_error" return True if not (mem_bit_input and mem_bit_input.get("type") == "variable"): instruction["scl"] = f"// ERROR: {instr_type_original} {instr_uid} 'bit' no es variable." instruction["type"] = instr_type_original + "_error" return True # 3. Formatear CLK (usa memoria original) clk_scl_formatted = clk_scl if clk_scl not in ["TRUE", "FALSE"] and \ (' ' in clk_scl or 'AND' in clk_scl or 'OR' in clk_scl or ':=' in clk_scl) and \ not (clk_scl.startswith('(') and clk_scl.endswith(')')): clk_scl_formatted = f"({clk_scl})" # 4. Generar Lógica SCL del *pulso* result_pulse_expression = "FALSE" scl_comment = "" if instr_type_original == "PBox": # P_TRIG result_pulse_expression = f"{clk_scl_formatted} AND NOT {mem_bit_scl_original}" scl_comment = f"// P_TRIG({clk_scl_formatted})" elif instr_type_original == "NBox": # N_TRIG result_pulse_expression = f"NOT {clk_scl_formatted} AND {mem_bit_scl_original}" scl_comment = f"// N_TRIG({clk_scl_formatted})" else: # Error instruction["scl"] = f"// ERROR: Tipo de flanco inesperado {instr_type_original}" instruction["type"] = instr_type_original + "_error" return True # 5. Generar la actualización del bit de memoria scl_mem_update = f"{mem_bit_scl_original} := {clk_scl_formatted};" # 6. Almacenar Resultados map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = result_pulse_expression # Guardar EXPRESIÓN del pulso instruction['_edge_mem_update_scl'] = f"{scl_mem_update} {scl_comment}" # Guardar UPDATE + Comentario en campo temporal instruction['scl'] = f"// {instr_type_original} Logic moved to consumer Coil" # Dejar SCL principal vacío/comentario instruction["type"] = instr_type_original + SCL_SUFFIX # 7. Propagar ENO map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = clk_scl return True # --- process_coil MODIFICADA (con \n correcto) --- def process_coil(instruction, network_id, scl_map, access_map): """Genera la asignación para Coil. Si la entrada viene de PBox/NBox, añade la actualización de memoria del flanco DESPUÉS de la asignación.""" instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False coil_input_info = instruction["inputs"].get("in") operand_info = instruction["inputs"].get("operand") in_rlo_scl = get_scl_representation(coil_input_info, network_id, scl_map, access_map) operand_scl = get_scl_representation(operand_info, network_id, scl_map, access_map) if in_rlo_scl is None or operand_scl is None: return False if not (operand_info and operand_info.get("type") == "variable"): instruction["scl"] = f"// ERROR: Coil {instr_uid} operando no es variable o falta info" instruction["type"] = instr_type + "_error" return True operand_scl_formatted = format_variable_name(operand_scl) if in_rlo_scl == "(TRUE)": in_rlo_scl = "TRUE" elif in_rlo_scl == "(FALSE)": in_rlo_scl = "FALSE" # Generar la asignación SCL principal de la bobina scl_assignment = f"{operand_scl_formatted} := {in_rlo_scl};" scl_final = scl_assignment # Inicializar SCL final # --- Lógica para añadir actualización de memoria de flancos --- mem_update_scl_combined = None if isinstance(coil_input_info, dict) and coil_input_info.get("type") == "connection": source_uid = coil_input_info.get("source_instruction_uid") source_pin = coil_input_info.get("source_pin") # Buscar la instrucción fuente PBox/NBox source_instruction = None network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), []) for instr in network_logic: if instr.get("instruction_uid") == source_uid: source_instruction = instr break if source_instruction: source_type = source_instruction.get("type","").replace('_scl','').replace('_error','') # Si la fuente es PBox o NBox y tiene el campo temporal con la actualización if source_type in ["PBox", "NBox"] and '_edge_mem_update_scl' in source_instruction: mem_update_scl_combined = source_instruction.get('_edge_mem_update_scl') # Obtener update+comment if mem_update_scl_combined: # Añadir la actualización DESPUÉS de la asignación de la bobina, USANDO \n scl_final = f"{scl_assignment}\n{mem_update_scl_combined}" # Marcar la instrucción PBox/NBox para que x3 no escriba su SCL (que ahora está vacío/comentario) source_instruction['scl'] = f"// Logic moved to Coil {instr_uid}" # Actualizar PBox/NBox SCL instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX return True # EN x2_process.py, junto a otras funciones process_xxx def process_scoil(instruction, network_id, scl_map, access_map): """Genera SCL para Set Coil (SCoil): IF condition THEN variable := TRUE; END_IF;""" instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # Ya procesado o con error # Obtener condición de entrada (RLO) in_info = instruction["inputs"].get("in") condition_scl = get_scl_representation(in_info, network_id, scl_map, access_map) # Obtener operando (variable a poner a TRUE) operand_info = instruction["inputs"].get("operand") variable_scl = get_scl_representation(operand_info, network_id, scl_map, access_map) # Verificar dependencias if condition_scl is None or variable_scl is None: return False # Dependencias no listas # Verificar que el operando sea una variable if not (operand_info and operand_info.get("type") == "variable"): print(f"Error: SCoil {instr_uid} operando no es variable o falta info (Tipo: {operand_info.get('type')}).") instruction["scl"] = f"// ERROR: SCoil {instr_uid} operando no es variable." instruction["type"] += "_error" return True # Procesado con error # Formatear nombre de variable variable_name_formatted = format_variable_name(variable_scl) # Generar SCL scl_core = f"{variable_name_formatted} := TRUE;" scl_final = ( f"IF {condition_scl} THEN\n {scl_core}\nEND_IF;" if condition_scl != "TRUE" else scl_core ) # Actualizar instrucción instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX # SCoil no genera salida 'out' ni 'eno' significativas para propagar return True def process_rcoil(instruction, network_id, scl_map, access_map): """Genera SCL para Reset Coil (RCoil): IF condition THEN variable := FALSE; END_IF;""" instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # Ya procesado o con error # Obtener condición de entrada (RLO) in_info = instruction["inputs"].get("in") condition_scl = get_scl_representation(in_info, network_id, scl_map, access_map) # Obtener operando (variable a poner a FALSE) operand_info = instruction["inputs"].get("operand") variable_scl = get_scl_representation(operand_info, network_id, scl_map, access_map) # Verificar dependencias if condition_scl is None or variable_scl is None: return False # Dependencias no listas # Verificar que el operando sea una variable if not (operand_info and operand_info.get("type") == "variable"): print(f"Error: RCoil {instr_uid} operando no es variable o falta info (Tipo: {operand_info.get('type')}).") instruction["scl"] = f"// ERROR: RCoil {instr_uid} operando no es variable." instruction["type"] += "_error" return True # Procesado con error # Formatear nombre de variable variable_name_formatted = format_variable_name(variable_scl) # Generar SCL scl_core = f"{variable_name_formatted} := FALSE;" scl_final = ( f"IF {condition_scl} THEN\n {scl_core}\nEND_IF;" if condition_scl != "TRUE" else scl_core ) # Actualizar instrucción instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX # RCoil no genera salida 'out' ni 'eno' significativas para propagar return True def process_eq(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False in1_info = instruction["inputs"].get("in1") in2_info = instruction["inputs"].get("in2") in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map) in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map) if in1_scl is None or in2_scl is None: return False # Dependencias no listas # Formatear operandos si son variables op1 = ( format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl ) op2 = ( format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl ) # Añadir paréntesis si los operandos contienen espacios (poco probable después de formatear) op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1 op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2 comparison_scl = f"{op1} = {op2}" # Guardar el resultado booleano de la comparación en el mapa SCL para la salida 'out' map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = comparison_scl # Procesar la entrada 'pre' (RLO anterior) para determinar ENO pre_input = instruction["inputs"].get("pre") pre_scl = ( "TRUE" if pre_input is None else get_scl_representation(pre_input, network_id, scl_map, access_map) ) if pre_scl is None: return False # Dependencia 'pre' no lista # Guardar el estado de 'pre' como ENO map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = pre_scl # El SCL generado es solo un comentario indicando la condición, # ya que la lógica se propaga a través de scl_map['out'] instruction["scl"] = f"// Comparison Eq {instr_uid}: {comparison_scl}" instruction["type"] = instr_type + SCL_SUFFIX return True def process_convert(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False en_input = instruction["inputs"].get("en") en_scl = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) in_info = instruction["inputs"].get("in") in_scl = get_scl_representation(in_info, network_id, scl_map, access_map) if en_scl is None or in_scl is None: return False # Esperar si dependencias no listas target_scl = get_target_scl_name( instruction, "out", network_id, default_to_temp=True ) if target_scl is None: print(f"Error: Sin destino claro para CONVERT {instr_uid}") instruction["scl"] = f"// ERROR: Convert {instr_uid} sin destino" instruction["type"] += "_error" return True # Procesado con error # Formatear entrada si es variable in_scl_formatted = ( format_variable_name(in_scl) if in_info and in_info.get("type") == "variable" else in_scl ) # Determinar el tipo de destino (simplificado, necesitaría info del XML original) # Asumimos que el tipo está en TemplateValues o inferirlo del nombre/contexto target_type = instruction.get("template_values", {}).get( "destType", "VARIANT" ) # Ejemplo, ajustar según XML real conversion_func = f"{target_type}_TO_" # Necesita el tipo de origen también # Esta parte es compleja sin saber los tipos exactos. Usaremos una conversión genérica o MOVE. # Para una conversión real, necesitaríamos algo como: # conversion_expr = f"CONVERT(IN := {in_scl_formatted}, OUT => {target_scl})" # Sintaxis inventada # O usar funciones específicas: INT_TO_REAL, etc. # Simplificación: Usar asignación directa (MOVE implícito) o función genérica si existe # Asumiremos asignación directa por ahora. conversion_expr = in_scl_formatted scl_core = f"{target_scl} := {conversion_expr};" # Añadir IF EN si es necesario scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = target_scl # El valor de salida es el contenido del destino map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl # ENO sigue a EN return True def process_mod(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False en_input = instruction["inputs"].get("en") en_scl = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) in1_info = instruction["inputs"].get("in1") in2_info = instruction["inputs"].get("in2") in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map) in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map) if en_scl is None or in1_scl is None or in2_scl is None: return False target_scl = get_target_scl_name( instruction, "out", network_id, default_to_temp=True ) if target_scl is None: print(f"Error: Sin destino MOD {instr_uid}") instruction["scl"] = f"// ERROR: Mod {instr_uid} sin destino" instruction["type"] += "_error" return True # Formatear operandos si son variables op1 = ( format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl ) op2 = ( format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl ) # Añadir paréntesis si es necesario (poco probable tras formatear) op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1 op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2 scl_core = f"{target_scl} := {op1} MOD {op2};" scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = target_scl map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl return True def process_add(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False en_input = instruction["inputs"].get("en") en_scl = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) in1_info = instruction["inputs"].get("in1") in2_info = instruction["inputs"].get("in2") in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map) in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map) if en_scl is None or in1_scl is None or in2_scl is None: return False target_scl = get_target_scl_name( instruction, "out", network_id, default_to_temp=True ) if target_scl is None: print(f"Error: Sin destino ADD {instr_uid}") instruction["scl"] = f"// ERROR: Add {instr_uid} sin destino" instruction["type"] += "_error" return True # Formatear operandos si son variables op1 = ( format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl ) op2 = ( format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl ) # Añadir paréntesis si es necesario op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1 op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2 scl_core = f"{target_scl} := {op1} + {op2};" scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = target_scl map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl return True def process_move(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False en_input = instruction["inputs"].get("en") en_scl = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) in_info = instruction["inputs"].get("in") in_scl = get_scl_representation(in_info, network_id, scl_map, access_map) if en_scl is None or in_scl is None: return False # Intentar obtener el destino de 'out1' (o 'out' si es el estándar) target_scl = get_target_scl_name( instruction, "out1", network_id, default_to_temp=False ) if target_scl is None: target_scl = get_target_scl_name( instruction, "out", network_id, default_to_temp=False ) if target_scl is None: # Si no hay destino explícito, podríamos usar una temp si la lógica lo requiere, # pero MOVE generalmente necesita un destino claro. Marcar como advertencia/error. print( f"Advertencia/Error: MOVE {instr_uid} sin destino claro en 'out' o 'out1'. Se requiere destino explícito." ) # Decidir si generar error o simplemente no hacer nada. No hacer nada es más seguro. # instruction["scl"] = f"// ERROR: MOVE {instr_uid} sin destino" # instruction["type"] += "_error" # return True return False # No procesar si no hay destino claro # Formatear entrada si es variable in_scl_formatted = ( format_variable_name(in_scl) if in_info and in_info.get("type") == "variable" else in_scl ) scl_core = f"{target_scl} := {in_scl_formatted};" scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX # Propagar el valor movido a través de scl_map si se usa 'out' o 'out1' como fuente map_key_out = (network_id, instr_uid, "out") # Asumir 'out' como estándar scl_map[map_key_out] = target_scl # El valor es lo que está en el destino map_key_out1 = (network_id, instr_uid, "out1") # Si existe out1 scl_map[map_key_out1] = target_scl map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl return True # --- FUNCIÓN UNIFICADA CORREGIDA para PBox y NBox --- """Genera SCL para PBox (P_TRIG) o NBox (N_TRIG).""" instr_uid = instruction["instruction_uid"] instr_type_original = instruction["type"] # PBox o NBox # print(f"DEBUG Edge: Intentando procesar {instr_type_original} UID {instr_uid} en Red {network_id}") # Debug if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: return False # Ya procesado o error # 1. Obtener representaciones SCL de las entradas CLK y MemBit clk_input = instruction["inputs"].get("in") mem_bit_input = instruction["inputs"].get("bit") clk_scl = get_scl_representation(clk_input, network_id, scl_map, access_map) mem_bit_scl_original = get_scl_representation(mem_bit_input, network_id, scl_map, access_map) # 2. Verificar si las dependencias están listas if clk_scl is None: # print(f"DEBUG Edge: CLK no resuelto para {instr_type_original} UID {instr_uid}. Esperando.") return False # Dependencia CLK no lista if mem_bit_scl_original is None: # Esto es menos probable, pero por seguridad print(f"Error: No se pudo resolver MemBit para {instr_type_original} UID {instr_uid}.") instruction["scl"] = f"// ERROR: {instr_type_original} {instr_uid} MemBit no resuelto." instruction["type"] = instr_type_original + "_error" return True # Marcar como error # 3. Validar que el bit de memoria sea una variable if not (mem_bit_input and mem_bit_input.get("type") == "variable"): print(f"Error: {instr_type_original} {instr_uid} 'bit' no es variable o falta información.") instruction["scl"] = f"// ERROR: {instr_type_original} {instr_uid} 'bit' no es variable." instruction["type"] = instr_type_original + "_error" return True # Procesado con error # 4. Renombrar bit de memoria para VAR_STAT y formatear CLK mem_bit_name_clean = mem_bit_scl_original.strip('"') stat_mem_bit_scl = f'"stat_{mem_bit_name_clean}"' if not mem_bit_name_clean.startswith("stat_") else mem_bit_scl_original clk_scl_formatted = clk_scl # Añadir paréntesis si es necesario (expresión compleja) - No a TRUE/FALSE if clk_scl not in ["TRUE", "FALSE"] and \ (' ' in clk_scl or 'AND' in clk_scl or 'OR' in clk_scl or ':=' in clk_scl) and \ not (clk_scl.startswith('(') and clk_scl.endswith(')')): clk_scl_formatted = f"({clk_scl})" # 5. Generar Lógica SCL específica para PBox o NBox result_pulse_scl = "FALSE" # SCL para la salida del flanco (pin 'out') scl_comment = "" if instr_type_original == "PBox": # Flanco Positivo (P_TRIG) # Pulso = CLK actual Y NO Memoria anterior result_pulse_scl = f"{clk_scl_formatted} AND NOT {stat_mem_bit_scl}" scl_comment = f"// P_TRIG({clk_scl_formatted})" elif instr_type_original == "NBox": # Flanco Negativo (N_TRIG) # Pulso = NO CLK actual Y Memoria anterior result_pulse_scl = f"NOT {clk_scl_formatted} AND {stat_mem_bit_scl}" scl_comment = f"// N_TRIG({clk_scl_formatted})" else: print(f"Error interno: process_edge_detector llamado para tipo inesperado {instr_type_original}") instruction["scl"] = f"// ERROR: Tipo de flanco inesperado {instr_type_original}" instruction["type"] = instr_type_original + "_error" return True # 6. Generar la actualización del bit de memoria (siempre se actualiza con el estado actual de CLK) scl_mem_update = f"{stat_mem_bit_scl} := {clk_scl_formatted};" # 7. Almacenar Resultados # - El *pulso* resultante se almacena en el mapa para la salida 'out'. # - La *actualización de memoria* se almacena en el campo 'scl' de la instrucción. map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = result_pulse_scl # print(f"DEBUG Edge: {instr_type_original} UID {instr_uid} -> map['out'] = {result_pulse_scl}") # Debug instruction["scl"] = f"{scl_mem_update} {scl_comment}" # Incluye la acción principal y un comentario instruction["type"] = instr_type_original + SCL_SUFFIX # print(f"DEBUG Edge: {instr_type_original} UID {instr_uid} -> instruction['scl'] = {instruction['scl']}") # Debug # 8. Propagar ENO (generalmente sigue al CLK) map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = clk_scl # Usar el clk_scl original sin formato extra # print(f"DEBUG Edge: {instr_type_original} UID {instr_uid} procesado exitosamente.") # Debug return True # Indicar que se procesó # EN x2_process.py def process_blkmov(instruction, network_id, scl_map, access_map): """ Genera SCL usando BLKMOV directamente como nombre de función, sin COUNT y con formato específico, según solicitud del usuario. ADVERTENCIA: Es MUY PROBABLE que esto NO compile en TIA Portal estándar, ya que BLKMOV no es una función SCL y MOVE_BLK requiere COUNT. """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # Ya procesado o con error # --- Obtener Entradas --- en_input = instruction["inputs"].get("en") en_scl = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) srcblk_info = instruction["inputs"].get("SRCBLK") # ¡IMPORTANTE! Obtenemos el nombre RAW antes de formatearlo para usarlo como pide el usuario raw_srcblk_name = srcblk_info.get("name") if srcblk_info else None # Verificar dependencias de entrada (solo necesitamos que EN esté resuelto) if en_scl is None: return False # Dependencia EN no lista if raw_srcblk_name is None: print(f"Error: BLKMOV {instr_uid} sin información válida para SRCBLK.") instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin SRCBLK válido." instruction["type"] += "_error" return True # --- Obtener Destinos (Salidas) --- # RET_VAL (Usamos get_target_scl_name para manejar variables temporales si es necesario) retval_target_scl = get_target_scl_name( instruction, "RET_VAL", network_id, default_to_temp=True ) if retval_target_scl is None: print(f"Error: BLKMOV {instr_uid} sin destino claro para RET_VAL.") instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin destino RET_VAL" instruction["type"] += "_error" return True # DSTBLK (Obtenemos el nombre RAW para usarlo como pide el usuario) raw_dstblk_name = None dstblk_output_list = instruction.get("outputs", {}).get("DSTBLK", []) if dstblk_output_list and isinstance(dstblk_output_list, list) and len(dstblk_output_list) == 1: dest_access = dstblk_output_list[0] if dest_access.get("type") == "variable": raw_dstblk_name = dest_access.get("name") # Nombre raw del JSON else: print(f"Advertencia: Destino DSTBLK de BLKMOV {instr_uid} no es una variable (Tipo: {dest_access.get('type')}).") else: print(f"Error: No se encontró un destino único y válido para DSTBLK en BLKMOV {instr_uid}.") if raw_dstblk_name is None: instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin destino DSTBLK válido." instruction["type"] += "_error" return True # --- Formateo especial para SRCBLK/DSTBLK como pidió el usuario --- # Asume formato "DB".Variable o "Struct".Variable del JSON y lo mantiene # (Esto anula la limpieza normal de format_variable_name para estos parámetros) srcblk_final_str = raw_srcblk_name if raw_srcblk_name else "_ERROR_SRC_" dstblk_final_str = raw_dstblk_name if raw_dstblk_name else "_ERROR_DST_" # --- Generar SCL Exacto Solicitado --- scl_core = ( f"{retval_target_scl} := BLKMOV(SRCBLK := {srcblk_final_str}, " f"DSTBLK => {dstblk_final_str}); " f"// ADVERTENCIA: BLKMOV usado directamente, probablemente no compile!" ) # Añadir condición EN (usando la representación SCL obtenida para EN) scl_final = ( f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core ) # --- Actualizar Instrucción y Mapa SCL --- instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX # Propagar ENO (igual que EN) map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl # Propagar el valor de retorno (el contenido de la variable asignada a RET_VAL) map_key_ret_val = (network_id, instr_uid, "RET_VAL") scl_map[map_key_ret_val] = retval_target_scl # El valor es lo que sea que se asigne return True # ... (Asegúrate de que esta función está registrada en processor_map como antes) ... def process_o(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # Buscar todas las entradas 'in', 'in1', 'in2', ... input_pins = sorted([pin for pin in instruction["inputs"] if pin.startswith("in")]) if not input_pins: print(f"Error: O {instr_uid} sin pines de entrada (inX).") instruction["scl"] = f"// ERROR: O {instr_uid} sin pines inX" instruction["type"] += "_error" return True # Procesado con error scl_parts = [] all_resolved = True for pin in input_pins: in_scl = get_scl_representation( instruction["inputs"][pin], network_id, scl_map, access_map ) if in_scl is None: all_resolved = False # print(f"DEBUG: O {instr_uid} esperando pin {pin}") break # Salir del bucle for si una entrada no está lista # Formatear término (añadir paréntesis si es necesario) term = in_scl if (" " in term or "AND" in term) and not ( term.startswith("(") and term.endswith(")") ): term = f"({term})" scl_parts.append(term) if not all_resolved: return False # Esperar a que todas las entradas estén resueltas # Construir la expresión OR result_scl = "FALSE" # Valor por defecto si no hay entradas válidas (raro) if scl_parts: result_scl = " OR ".join(scl_parts) # Simplificar si solo hay un término if len(scl_parts) == 1: result_scl = scl_parts[0] # Quitar paréntesis redundantes si solo hay un término y está entre paréntesis if result_scl.startswith("(") and result_scl.endswith(")"): # Comprobar si los paréntesis son necesarios (contienen operadores de menor precedencia) # Simplificación: quitar siempre si solo hay un término. Podría ser incorrecto en casos complejos. # result_scl = result_scl[1:-1] # Comentado por seguridad pass # Actualizar mapa SCL y la instrucción map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = result_scl instruction["scl"] = ( f"// Logic O {instr_uid}: {result_scl}" # Comentario informativo ) instruction["type"] = instr_type + SCL_SUFFIX # La instrucción 'O' no tiene ENO propio, propaga el resultado por 'out' return True def process_call(instruction, network_id, scl_map, access_map): instr_uid = instruction["instruction_uid"] instr_type = instruction.get("type", "") # Usar get con default if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False block_name = instruction.get("block_name", f"UnknownCall_{instr_uid}") block_type = instruction.get("block_type") # FC, FB instance_db = instruction.get("instance_db") # Nombre del DB de instancia (para FB) # Formatear nombres block_name_scl = format_variable_name(block_name) instance_db_scl = format_variable_name(instance_db) if instance_db else None # --- Manejo de EN --- en_input = instruction["inputs"].get("en") en_scl = ( get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" ) if en_scl is None: return False # Dependencia EN no resuelta # --- Procesar Parámetros de Entrada/Salida --- # Necesitamos iterar sobre los pines definidos en la interfaz del bloque llamado. # Esta información no está directamente en la instrucción 'Call' del JSON simplificado. # ¡Limitación! Sin la interfaz del bloque llamado, solo podemos manejar EN/ENO # y asumir una llamada sin parámetros o con parámetros conectados implícitamente. # Solución temporal: Buscar conexiones en 'inputs' y 'outputs' que NO sean 'en'/'eno' # y construir la llamada basándose en eso. Esto es muy heurístico. scl_call_params = [] processed_inputs = {"en"} # Marcar 'en' como ya procesado for pin_name, source_info in instruction.get("inputs", {}).items(): if pin_name not in processed_inputs: param_scl = get_scl_representation( source_info, network_id, scl_map, access_map ) if param_scl is None: # print(f"DEBUG: Call {instr_uid} esperando parámetro de entrada {pin_name}") return False # Dependencia de parámetro no resuelta # Formatear si es variable param_scl_formatted = ( format_variable_name(param_scl) if source_info.get("type") == "variable" else param_scl ) scl_call_params.append( f"{format_variable_name(pin_name)} := {param_scl_formatted}" ) processed_inputs.add(pin_name) # Procesar parámetros de salida (asignaciones después de la llamada o pasados como VAR_IN_OUT/VAR_OUTPUT) # Esto es aún más complejo. SCL normalmente asigna salidas después o usa punteros/referencias. # Simplificación: Asumir que las salidas se manejan por asignación posterior si es necesario, # o que son VAR_OUTPUT y se acceden como instancia.salida. # Por ahora, no generamos asignaciones explícitas para las salidas aquí. # --- Construcción de la Llamada SCL --- scl_call_body = "" param_string = ", ".join(scl_call_params) if block_type == "FB": if not instance_db_scl: print( f"Error: Llamada a FB '{block_name_scl}' (UID {instr_uid}) sin DB de instancia especificado." ) instruction["scl"] = f"// ERROR: FB Call {block_name_scl} sin instancia" instruction["type"] = "Call_FB_error" return True # Procesado con error # Llamada a FB con DB de instancia scl_call_body = f"{instance_db_scl}({param_string});" elif block_type == "FC": # Llamada a FC scl_call_body = f"{block_name_scl}({param_string});" else: print( f"Advertencia: Tipo de bloque no soportado para Call UID {instr_uid}: {block_type}" ) scl_call_body = f"// ERROR: Call a bloque tipo '{block_type}' no soportado: {block_name_scl}" instruction["type"] = f"Call_{block_type}_error" # Marcar como error parcial # --- Aplicar Condición EN --- scl_final = "" if en_scl != "TRUE": # Indentar la llamada dentro del IF indented_call = "\n".join([f" {line}" for line in scl_call_body.splitlines()]) scl_final = f"IF {en_scl} THEN\n{indented_call}\nEND_IF;" else: scl_final = scl_call_body # --- Actualizar JSON y Mapa SCL --- instruction["scl"] = scl_final instruction["type"] = ( f"Call_{block_type}_scl" if "_error" not in instruction["type"] else instruction["type"] ) # Actualizar scl_map con el estado ENO (igual a EN para llamadas simples sin manejo explícito de ENO) map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl # Propagar valores de salida (si pudiéramos determinarlos) # Ejemplo: Si supiéramos que hay una salida 'Out1' de tipo INT # map_key_out1 = (network_id, instr_uid, "Out1") # if block_type == "FB" and instance_db_scl: # scl_map[map_key_out1] = f"{instance_db_scl}.Out1" # Acceso a salida de instancia # else: # # Para FCs, necesitaríamos una variable temporal o asignación explícita # temp_out1 = generate_temp_var_name(network_id, instr_uid, "Out1") # # Modificar scl_call_body para incluir la asignación: Out1 => temp_out1 # scl_map[map_key_out1] = temp_out1 return True # --- Procesador de Temporizadores (TON, TOF) --- def process_timer(instruction, network_id, scl_map, access_map): """ Genera SCL para Temporizadores (TON, TOF). Requiere datos de instancia (DB o STAT). """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # Será "TON" o "TOF" if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # 1. Obtener Inputs in_info = instruction["inputs"].get("IN") # Entrada booleana pt_info = instruction["inputs"].get("PT") # Preset Time (Tipo TIME) scl_in = get_scl_representation(in_info, network_id, scl_map, access_map) scl_pt = get_scl_representation(pt_info, network_id, scl_map, access_map) if scl_in is None or scl_pt is None: return False # Dependencias no listas # 2. Obtener Nombre de Instancia (NECESITA MEJORA EN x1.py) instance_name = instruction.get("instance_db") # Reutilizar campo si x1 lo llena if not instance_name: # Generar placeholder si x1 no extrajo la instancia para Part instance_name = f"#TIMER_INSTANCE_{instr_uid}" # Placeholder para VAR_TEMP o VAR_STAT print(f"Advertencia: No se encontró instancia para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. Ajustar x1.py y declarar en x3.py.") else: instance_name = format_variable_name(instance_name) # Limpiar si viene de x1 # 3. Formatear entradas si son variables scl_in_formatted = format_variable_name(scl_in) if in_info and in_info.get("type") == "variable" else scl_in scl_pt_formatted = format_variable_name(scl_pt) if pt_info and pt_info.get("type") == "variable" else scl_pt # 4. Generar la llamada SCL # Nota: Las salidas Q y ET se acceden directamente desde la instancia. scl_call = f"{instance_name}(IN := {scl_in_formatted}, PT := {scl_pt_formatted}); // TODO: Declarar {instance_name} : {instr_type}; en VAR_STAT o VAR" instruction["scl"] = scl_call instruction["type"] = instr_type + SCL_SUFFIX # 5. Actualizar scl_map para las salidas Q y ET map_key_q = (network_id, instr_uid, "Q") scl_map[map_key_q] = f"{instance_name}.Q" map_key_et = (network_id, instr_uid, "ET") scl_map[map_key_et] = f"{instance_name}.ET" # TON/TOF no tienen un pin ENO estándar en LAD/FBD que se mapee directamente return True # --- Procesador de Contadores (CTU, CTD, CTUD) --- def process_counter(instruction, network_id, scl_map, access_map): """ Genera SCL para Contadores (CTU, CTD, CTUD). Requiere datos de instancia (DB o STAT). """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # CTU, CTD, CTUD if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # 1. Obtener Inputs (varía según tipo) params = [] resolved = True input_pins = [] if instr_type == "CTU": input_pins = ["CU", "R", "PV"] elif instr_type == "CTD": input_pins = ["CD", "LD", "PV"] elif instr_type == "CTUD": input_pins = ["CU", "CD", "R", "LD", "PV"] else: instruction["scl"] = f"// ERROR: Tipo de contador no soportado: {instr_type}" instruction["type"] += "_error" return True # Procesado con error for pin in input_pins: pin_info = instruction["inputs"].get(pin) if pin_info is None and pin not in ["R", "LD"]: # R y LD pueden no estar conectados print(f"Error: Falta entrada requerida '{pin}' para {instr_type} UID {instr_uid}.") # Permitir continuar si solo faltan R o LD opcionales? Por ahora no. instruction["scl"] = f"// ERROR: Falta entrada requerida '{pin}' para {instr_type} UID {instr_uid}." instruction["type"] += "_error" return True # Error elif pin_info: # Si el pin existe en el JSON scl_pin = get_scl_representation(pin_info, network_id, scl_map, access_map) if scl_pin is None: resolved = False break # Salir si una dependencia no está lista scl_pin_formatted = format_variable_name(scl_pin) if pin_info.get("type") == "variable" else scl_pin params.append(f"{pin} := {scl_pin_formatted}") if not resolved: return False # 2. Obtener Nombre de Instancia (NECESITA MEJORA EN x1.py) instance_name = instruction.get("instance_db") if not instance_name: instance_name = f"#COUNTER_INSTANCE_{instr_uid}" # Placeholder print(f"Advertencia: No se encontró instancia para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. Ajustar x1.py y declarar en x3.py.") else: instance_name = format_variable_name(instance_name) # 3. Generar la llamada SCL param_string = ", ".join(params) scl_call = f"{instance_name}({param_string}); // TODO: Declarar {instance_name} : {instr_type}; en VAR_STAT o VAR" instruction["scl"] = scl_call instruction["type"] = instr_type + SCL_SUFFIX # 4. Actualizar scl_map para las salidas (QU, QD, CV) output_pins = [] if instr_type == "CTU": output_pins = ["QU", "CV"] elif instr_type == "CTD": output_pins = ["QD", "CV"] elif instr_type == "CTUD": output_pins = ["QU", "QD", "CV"] for pin in output_pins: map_key = (network_id, instr_uid, pin) scl_map[map_key] = f"{instance_name}.{pin}" # Contadores no tienen ENO estándar en LAD/FBD return True # --- Procesador de Comparadores (EQ ya existe, añadir otros) --- def process_comparison(instruction, network_id, scl_map, access_map): """ Genera la expresión SCL para Comparadores (GT, LT, GE, LE, NE). El resultado se propaga por scl_map['out']. """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # GT, LT, GE, LE, NE if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # Mapa de tipos a operadores SCL op_map = {"GT": ">", "LT": "<", "GE": ">=", "LE": "<=", "NE": "<>"} scl_operator = op_map.get(instr_type) if not scl_operator: instruction["scl"] = f"// ERROR: Tipo de comparación no soportado: {instr_type}" instruction["type"] += "_error" return True # Obtener operandos in1_info = instruction["inputs"].get("in1") in2_info = instruction["inputs"].get("in2") in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map) in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map) if in1_scl is None or in2_scl is None: return False # Dependencias no listas # Formatear operandos si son variables op1 = format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl op2 = format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl # Añadir paréntesis si contienen espacios (poco probable tras formatear) op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1 op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2 comparison_scl = f"{op1} {scl_operator} {op2}" # Guardar resultado en el mapa para 'out' map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = f"({comparison_scl})" # Poner paréntesis por seguridad # Manejar entrada 'pre'/RLO -> ENO (como en EQ) pre_input = instruction["inputs"].get("pre") # Asumir 'pre' como en EQ en_scl = get_scl_representation(pre_input, network_id, scl_map, access_map) if pre_input else "TRUE" if en_scl is None: return False # Dependencia 'pre'/'en' no lista map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl instruction["scl"] = f"// Comparison {instr_type} {instr_uid}: {comparison_scl}" instruction["type"] = instr_type + SCL_SUFFIX return True # --- Procesador de Matemáticas (ADD ya existe, añadir otros) --- def process_math(instruction, network_id, scl_map, access_map): """ Genera SCL para operaciones matemáticas (SUB, MUL, DIV). """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # SUB, MUL, DIV if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False # Mapa de tipos a operadores SCL op_map = {"SUB": "-", "MUL": "*", "DIV": "/"} scl_operator = op_map.get(instr_type) if not scl_operator: instruction["scl"] = f"// ERROR: Operación matemática no soportada: {instr_type}" instruction["type"] += "_error" return True # Obtener EN, IN1, IN2 en_input = instruction["inputs"].get("en") in1_info = instruction["inputs"].get("in1") in2_info = instruction["inputs"].get("in2") en_scl = get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE" in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map) in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map) if en_scl is None or in1_scl is None or in2_scl is None: return False # Dependencias no listas # Obtener destino 'out' target_scl = get_target_scl_name(instruction, "out", network_id, default_to_temp=True) if target_scl is None: instruction["scl"] = f"// ERROR: {instr_type} {instr_uid} sin destino 'out'." instruction["type"] += "_error" return True # Formatear operandos si son variables op1 = format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl op2 = format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl # Añadir paréntesis si es necesario (especialmente para expresiones) op1 = f"({op1})" if (" " in op1 or "+" in op1 or "-" in op1 or "*" in op1 or "/" in op1) and not op1.startswith("(") else op1 op2 = f"({op2})" if (" " in op2 or "+" in op2 or "-" in op2 or "*" in op2 or "/" in op2) and not op2.startswith("(") else op2 # Generar SCL scl_core = f"{target_scl} := {op1} {scl_operator} {op2};" scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core instruction["scl"] = scl_final instruction["type"] = instr_type + SCL_SUFFIX # Actualizar mapa SCL map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = target_scl map_key_eno = (network_id, instr_uid, "eno") scl_map[map_key_eno] = en_scl return True # --- Procesador NOT --- def process_not(instruction, network_id, scl_map, access_map): """Genera la expresión SCL para la inversión lógica NOT.""" instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] # Not if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: return False in_info = instruction["inputs"].get("in") in_scl = get_scl_representation(in_info, network_id, scl_map, access_map) if in_scl is None: return False # Dependencia no lista # Formatear entrada (añadir paréntesis si es complejo) in_scl_formatted = in_scl if (" " in in_scl or "AND" in in_scl or "OR" in in_scl) and not (in_scl.startswith("(") and in_scl.endswith(")")): in_scl_formatted = f"({in_scl})" result_scl = f"NOT {in_scl_formatted}" # Guardar resultado en mapa para 'out' map_key_out = (network_id, instr_uid, "out") scl_map[map_key_out] = result_scl instruction["scl"] = f"// Logic NOT {instr_uid}: {result_scl}" instruction["type"] = instr_type + SCL_SUFFIX # NOT no tiene EN/ENO explícito en LAD, modifica el RLO return True # EN x2_process.py, junto a otros procesadores # --- Procesador para Se (Timer Pulse -> TP SCL) --- def process_se(instruction, network_id, scl_map, access_map): """ Genera SCL para Temporizador de Pulso (Se -> TP). Requiere datos de instancia (DB o STAT/TEMP). """ instr_uid = instruction["instruction_uid"] instr_type = "Se" # Tipo original LAD if instruction["type"].endswith(SCL_SUFFIX) or "_error" in instruction["type"]: return False # 1. Obtener Inputs: s (start), tv (time value) # El pin 'r' (reset) no tiene equivalente directo en TP, se ignora aquí. s_info = instruction["inputs"].get("s") tv_info = instruction["inputs"].get("tv") timer_instance_info = instruction["inputs"].get("timer") # Esperando que x1 lo extraiga scl_s = get_scl_representation(s_info, network_id, scl_map, access_map) scl_tv = get_scl_representation(tv_info, network_id, scl_map, access_map) # Obtenemos el nombre de la variable instancia, crucial! scl_instance_name = get_scl_representation(timer_instance_info, network_id, scl_map, access_map) if scl_s is None or scl_tv is None: return False # Dependencias no listas # 2. Validar y obtener Nombre de Instancia instance_name = None if timer_instance_info and timer_instance_info.get("type") == "variable": instance_name = scl_instance_name # Ya debería estar formateado por get_scl_repr elif timer_instance_info: # Si está conectado pero no es variable directa? Raro. print(f"Advertencia: Pin 'timer' de {instr_type} UID {instr_uid} conectado a algo inesperado: {timer_instance_info.get('type')}") instance_name = f"#TP_INSTANCE_{instr_uid}" # Usar placeholder else: # Si no hay pin 'timer' conectado (no debería pasar si x1 funciona) instance_name = f"#TP_INSTANCE_{instr_uid}" # Usar placeholder print(f"Advertencia: No se encontró conexión al pin 'timer' para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. ¡Revisar x1.py y XML!") # 3. Formatear entradas si son variables (aunque get_scl_representation ya debería hacerlo) scl_s_formatted = format_variable_name(scl_s) if s_info and s_info.get("type") == "variable" else scl_s scl_tv_formatted = format_variable_name(scl_tv) if tv_info and tv_info.get("type") == "variable" else scl_tv # 4. Generar la llamada SCL (TP usa IN, PT, Q, ET) scl_call = f"{instance_name}(IN := {scl_s_formatted}, PT := {scl_tv_formatted}); // TODO: Declarar {instance_name} : TP; en VAR_STAT o VAR" instruction["scl"] = scl_call instruction["type"] = instr_type + SCL_SUFFIX # 5. Actualizar scl_map usando los nombres de pin ORIGINALES mapeados si existen output_pin_mapping_reverse = {v: k for k, v in instruction.get("_output_pin_mapping", {}).items()} # Necesitaríamos guardar el mapeo en x1 q_original_pin = "q" # Default rt_original_pin = "rt" # Default # Intentar encontrar los pines originales si x1 guardó el mapeo (MEJORA NECESARIA en x1) # Por ahora, para SdCoil que mapeaba out->q, usaremos 'out' directamente if instruction.get("type") == "Se_scl" and instruction.get("original_type") == "SdCoil": # Necesitamos guardar original_type en x1 q_original_pin = "out" # rt no existe en SdCoil map_key_q = (network_id, instr_uid, q_original_pin) scl_map[map_key_q] = f"{instance_name}.Q" if rt_original_pin: # Solo añadir rt si corresponde map_key_rt = (network_id, instr_uid, rt_original_pin) scl_map[map_key_rt] = f"{instance_name}.ET" return True # --- Procesador para Sd (On-Delay Timer -> TON SCL) --- def process_sd(instruction, network_id, scl_map, access_map): """ Genera SCL para Temporizador On-Delay (Sd -> TON). Requiere datos de instancia (DB o STAT/TEMP). """ instr_uid = instruction["instruction_uid"] instr_type = "Sd" # Tipo original LAD if instruction["type"].endswith(SCL_SUFFIX) or "_error" in instruction["type"]: return False # 1. Obtener Inputs: s (start), tv (time value) # El pin 'r' (reset) no tiene equivalente directo en TON, se ignora aquí. s_info = instruction["inputs"].get("s") tv_info = instruction["inputs"].get("tv") timer_instance_info = instruction["inputs"].get("timer") # Esperando que x1 lo extraiga scl_s = get_scl_representation(s_info, network_id, scl_map, access_map) scl_tv = get_scl_representation(tv_info, network_id, scl_map, access_map) scl_instance_name = get_scl_representation(timer_instance_info, network_id, scl_map, access_map) if scl_s is None or scl_tv is None: return False # Dependencias no listas # 2. Validar y obtener Nombre de Instancia instance_name = None if timer_instance_info and timer_instance_info.get("type") == "variable": instance_name = scl_instance_name elif timer_instance_info: print(f"Advertencia: Pin 'timer' de {instr_type} UID {instr_uid} conectado a algo inesperado: {timer_instance_info.get('type')}") instance_name = f"#TON_INSTANCE_{instr_uid}" else: instance_name = f"#TON_INSTANCE_{instr_uid}" print(f"Advertencia: No se encontró conexión al pin 'timer' para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. ¡Revisar x1.py y XML!") # 3. Formatear entradas si son variables scl_s_formatted = format_variable_name(scl_s) if s_info and s_info.get("type") == "variable" else scl_s scl_tv_formatted = format_variable_name(scl_tv) if tv_info and tv_info.get("type") == "variable" else scl_tv # 4. Generar la llamada SCL (TON usa IN, PT, Q, ET) scl_call = f"{instance_name}(IN := {scl_s_formatted}, PT := {scl_tv_formatted}); // TODO: Declarar {instance_name} : TON; en VAR_STAT o VAR" instruction["scl"] = scl_call instruction["type"] = instr_type + SCL_SUFFIX # 5. Actualizar scl_map para las salidas Q y RT (mapeado a ET de TON) map_key_q = (network_id, instr_uid, "q") scl_map[map_key_q] = f"{instance_name}.Q" map_key_rt = (network_id, instr_uid, "rt") scl_map[map_key_rt] = f"{instance_name}.ET" return True # --- NUEVO: Procesador de Agrupación (Refinado) --- def process_group_ifs(instruction, network_id, scl_map, access_map): """ Busca instrucciones que generan condiciones (Contact, O, Eq, PBox, etc.) ya procesadas y, si habilitan un grupo (>1) de bloques funcionales (Move, Add, Call, etc.), construye el bloque IF agrupado. Modifica el campo 'scl' de la instrucción generadora de condición. """ instr_uid = instruction["instruction_uid"] instr_type = instruction["type"] instr_type_original = instr_type.replace("_scl", "").replace("_error", "") made_change = False # Solo actuar sobre generadores de condición ya procesados (_scl) # y que no sean ellos mismos errores o ya agrupados por otro IF if ( not instr_type.endswith("_scl") or "_error" in instr_type or instruction.get("grouped", False) or instr_type_original not in [ "Contact", "O", "Eq", "Ne", "Gt", "Lt", "Ge", "Le", "PBox", "And", "Xor", ] ): # Añadir más si es necesario return False # Evitar reagrupar si ya se hizo (comprobando si SCL ya es un IF complejo) current_scl = instruction.get("scl", "") if current_scl.strip().startswith("IF") and "END_IF;" in current_scl: # print(f"DEBUG Group: {instr_uid} ya tiene IF complejo, saltando agrupación.") return False # Ignorar comentarios simples que empiezan por IF if current_scl.strip().startswith("//") and "IF" in current_scl: return False # Obtener la condición generada por esta instrucción (debería estar en scl_map['out']) map_key_out = (network_id, instr_uid, "out") condition_scl = scl_map.get(map_key_out) # No agrupar para condiciones triviales, no encontradas o ya agrupadas if condition_scl is None or condition_scl in ["TRUE", "FALSE"]: return False # Encontrar todos los bloques funcionales habilitados DIRECTAMENTE por esta condición grouped_instructions_cores = [] # Lista de SCL 'core' de los consumidores consumer_instr_list = [] # Lista de instrucciones consumidoras network_logic = next( (net["logic"] for net in data["networks"] if net["id"] == network_id), [] ) if not network_logic: return False # Identificar los tipos de instrucciones que queremos agrupar (bloques funcionales) groupable_types = [ "Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert", "Call_FC", "Call_FB", ] # Añadir más si es necesario for consumer_instr in network_logic: consumer_uid = consumer_instr["instruction_uid"] # Saltar si ya está agrupado por otra condición o es él mismo if consumer_instr.get("grouped", False) or consumer_uid == instr_uid: continue consumer_en = consumer_instr.get("inputs", {}).get("en") consumer_type = consumer_instr.get("type", "") # Tipo actual (_scl o no) consumer_type_original = consumer_type.replace("_scl", "").replace("_error", "") # ¿Está la entrada 'en' del consumidor conectada a nuestra salida 'out'? is_enabled_by_us = False if ( isinstance(consumer_en, dict) and consumer_en.get("type") == "connection" and consumer_en.get("source_instruction_uid") == instr_uid and consumer_en.get("source_pin") == "out" # Condición viene de 'out' del generador ): is_enabled_by_us = True # ¿Es un tipo de instrucción agrupable y ya procesado (tiene SCL)? if ( is_enabled_by_us and consumer_type.endswith("_scl") and consumer_type_original in groupable_types ): consumer_scl = consumer_instr.get("scl", "") # Extraer el SCL core (la parte DENTRO del IF o la línea única si no había IF) core_scl = None if consumer_scl.strip().startswith("IF"): # Extraer todo entre THEN y END_IF; match = re.search( r"IF\s+.*\s+THEN\s*(.*?)\s*END_IF;", consumer_scl, re.DOTALL | re.IGNORECASE, ) if match: core_scl = match.group(1).strip() elif consumer_scl and not consumer_scl.strip().startswith("//"): # Si no es IF y no es solo comentario, es el core core_scl = consumer_scl.strip() if core_scl: grouped_instructions_cores.append(core_scl) consumer_instr_list.append(consumer_instr) # Guardar referencia # else: # Debug # print(f"DEBUG Group: Consumidor {consumer_uid} ({consumer_type}) habilitado por {instr_uid} no tenía SCL core extraíble. SCL: '{consumer_scl}'") # Si encontramos más de un consumidor agrupable if len(grouped_instructions_cores) > 1: print( f"INFO: Agrupando {len(grouped_instructions_cores)} instrucciones bajo condición de {instr_type_original} UID {instr_uid} (Cond: {condition_scl})" ) # Construir el bloque IF agrupado scl_grouped = [f"IF {condition_scl} THEN"] for core_line in grouped_instructions_cores: # Añadir indentación adecuada (2 espacios) indented_core = "\n".join( [f" {line.strip()}" for line in core_line.splitlines()] ) scl_grouped.append(indented_core) scl_grouped.append("END_IF;") final_grouped_scl = "\n".join(scl_grouped) # Sobrescribir 'scl' de la instrucción generadora de condición instruction["scl"] = final_grouped_scl # Marcar los consumidores como agrupados y limpiar su SCL original for consumer_instr in consumer_instr_list: consumer_instr["scl"] = f"{GROUPED_COMMENT} (by UID {instr_uid})" consumer_instr["grouped"] = True # Marcar como agrupado made_change = True # else: # Debug # if len(grouped_instructions_cores) == 1: # print(f"DEBUG Group: Solo 1 consumidor ({consumer_instr_list[0]['instruction_uid']}) para {instr_uid}. No se agrupa.") # elif len(grouped_instructions_cores) == 0 and condition_scl not in ['TRUE', 'FALSE']: # # Solo mostrar si la condición no era trivial # pass # print(f"DEBUG Group: Ningún consumidor agrupable encontrado para {instr_uid} (Cond: {condition_scl}).") return made_change # --- Bucle Principal de Procesamiento --- def process_json_to_scl(json_filepath): """Lee el JSON, aplica los procesadores iterativamente y guarda el resultado.""" if not os.path.exists(json_filepath): print(f"Error: JSON no encontrado: {json_filepath}") return print(f"Cargando JSON desde: {json_filepath}") try: with open(json_filepath, "r", encoding="utf-8") as f: global data # Modificar la variable global data = json.load(f) except Exception as e: print(f"Error al cargar JSON: {e}") traceback.print_exc() return # Crear mapas de acceso por red (para resolver UIDs de variables/constantes rápidamente) network_access_maps = {} # print("Creando mapas de acceso por red...") # Comentado para brevedad for network in data.get("networks", []): net_id = network["id"] current_access_map = {} # Extraer todos los 'Access' (variables/constantes) usados en esta red # Esta información ya está dentro de inputs/outputs en el JSON simplificado for instr in network.get("logic", []): # Revisar Inputs for _, source in instr.get("inputs", {}).items(): sources_to_check = ( source if isinstance(source, list) else ([source] if isinstance(source, dict) else []) ) for src in sources_to_check: if ( isinstance(src, dict) and src.get("uid") and src.get("type") in ["variable", "constant"] ): current_access_map[src["uid"]] = ( src # Guardar info del Access por UID ) # Revisar Outputs for _, dest_list in instr.get("outputs", {}).items(): if isinstance(dest_list, list): for dest in dest_list: if ( isinstance(dest, dict) and dest.get("uid") and dest.get("type") in ["variable", "constant"] ): current_access_map[dest["uid"]] = ( dest # Guardar info del Access por UID ) network_access_maps[net_id] = current_access_map # Mapa global para almacenar el SCL generado para cada salida de instrucción (pin) # Clave: (network_id, instruction_uid, pin_name) -> Valor: SCL string scl_map = {} # --- Bucle Iterativo --- max_passes = 30 # Aumentar por si hay dependencias largas o agrupaciones complejas passes = 0 processing_complete = False # Lista y mapa de procesadores base base_processors = [ process_convert, process_mod, process_eq, process_contact, process_o, process_not, # <-- Nuevo process_edge_detector, process_comparison, # <-- Nuevo (para GT, LT, etc.) process_add, process_math, # <-- Nuevo (para SUB, MUL, DIV) process_move, process_timer, # <-- Nuevo process_se, # <-- Añadido process_sd, # <-- Añadido process_counter, # <-- Nuevo process_call, process_coil, process_scoil, process_rcoil, process_blkmov, ] # Crear mapa por nombre de tipo original (en minúsculas) processor_map = {} for func in base_processors: match = re.match(r"process_(\w+)", func.__name__) if match: type_name = match.group(1).lower() if type_name == "call": processor_map["call_fc"] = func processor_map["call_fb"] = func processor_map["call"] = func elif type_name == "edge_detector": # Mapear PBox y NBox a la nueva función processor_map["pbox"] = func processor_map["nbox"] = func elif type_name == "blkmov": processor_map[type_name] = process_blkmov # Usar la nueva función BLKMOV elif type_name == "scoil": processor_map[type_name] = func elif type_name == "rcoil": processor_map[type_name] = func elif type_name == "se": processor_map["se"] = func processor_map["sdcoil"] = func # Mapear SdCoil a process_se basado en análisis elif type_name == "sd": processor_map["sd"] = func elif type_name == "timer": processor_map["ton"] = func # Mapear TON al procesador de timer processor_map["tof"] = func # Mapear TOF al procesador de timer elif type_name == "counter": processor_map["ctu"] = func processor_map["ctd"] = func processor_map["ctud"] = func elif type_name == "comparison": processor_map["gt"] = func processor_map["lt"] = func processor_map["ge"] = func processor_map["le"] = func processor_map["ne"] = func # EQ ya tiene su propio procesador (process_eq), así que no lo añadimos aquí. elif type_name == "math": processor_map["sub"] = func processor_map["mul"] = func processor_map["div"] = func # ADD ya tiene su propio procesador (process_add) elif type_name == "not": processor_map["not"] = func # Mapear 'not' elif type_name not in processor_map: processor_map[type_name] = func print("\n--- Iniciando Bucle de Procesamiento Iterativo ---") while passes < max_passes and not processing_complete: passes += 1 made_change_in_base_pass = False made_change_in_group_pass = False print(f"\n--- Pase {passes} ---") num_processed_this_pass = 0 num_grouped_this_pass = 0 # --- FASE 1: Procesadores Base --- for network in data.get("networks", []): network_id = network["id"] access_map = network_access_maps.get(network_id, {}) network_logic = network.get( "logic", [] ) # Obtener referencia a la lista de lógica # Crear una copia de la lista de instrucciones para iterar, # ya que modificaremos la original (al añadir _scl al tipo) # logic_to_process = list(network_logic) # No necesario si solo modificamos in-place for instruction in network_logic: # Iterar sobre la lista original instr_uid = instruction.get("instruction_uid") instr_type_original = instruction.get("type", "Unknown") # Saltar si ya procesado, es un error, o está agrupado if ( instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original or instruction.get("grouped", False) ): continue # Buscar el procesador adecuado # Para 'Call', necesitamos distinguir FC/FB si es posible lookup_key = instr_type_original.lower() if instr_type_original == "Call": block_type = instruction.get("block_type", "").upper() if block_type == "FC": lookup_key = "call_fc" elif block_type == "FB": lookup_key = "call_fb" # else: se usará 'call' genérico si existe func_to_call = processor_map.get(lookup_key) if func_to_call: try: # Pasar la lista de lógica completa solo si es necesario (PBox) changed = func_to_call( instruction, network_id, scl_map, access_map ) if changed: made_change_in_base_pass = True num_processed_this_pass += 1 # print(f"DEBUG: Procesado {instr_type_original} UID {instr_uid}") # Debug detallado except Exception as e: print( f"ERROR(Base) al procesar {instr_type_original} UID {instr_uid} con {func_to_call.__name__}: {e}" ) traceback.print_exc() # Marcar como error para no reintentar instruction["scl"] = f"// ERROR en procesador base: {e}" instruction["type"] = instr_type_original + "_error" made_change_in_base_pass = True # Considerar error como cambio para evitar bucles infinitos # else: # Debug para tipos no encontrados # if lookup_key not in ['unknown', 'unknown_structure', 'error_parsing_symbol', 'error_parsing_constant', 'error_no_name']: # print(f"DEBUG: No se encontró procesador base para el tipo '{instr_type_original}' (lookup: '{lookup_key}') UID {instr_uid}") # pass # --- FASE 2: Procesador de Agrupación --- # Ejecutar solo si hubo cambios en la fase base (o en el primer pase) para optimizar if made_change_in_base_pass or passes == 1: # print(f"DEBUG: Iniciando Fase 2 (Agrupación IF) - Pase {passes}") # Debug for network in data.get("networks", []): network_id = network["id"] access_map = network_access_maps.get(network_id, {}) network_logic = network.get("logic", []) # Iterar sobre generadores de condición ya procesados for instruction in network_logic: # Solo intentar agrupar si es un generador de condición procesado y no agrupado if instruction["type"].endswith("_scl") and not instruction.get( "grouped", False ): try: group_changed = process_group_ifs( instruction, network_id, scl_map, access_map ) if group_changed: made_change_in_group_pass = True num_grouped_this_pass += 1 except Exception as e: print( f"ERROR(Group) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}" ) traceback.print_exc() # No marcar la instrucción generadora como error, solo falló la agrupación # --- Comprobar si se completó el procesamiento --- if not made_change_in_base_pass and not made_change_in_group_pass: print( f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---" ) processing_complete = True else: print( f"--- Fin Pase {passes}: {num_processed_this_pass} procesados, {num_grouped_this_pass} agrupados. Continuando..." ) if passes == max_passes and not processing_complete: print( f"\n--- ADVERTENCIA: Límite de {max_passes} pases alcanzado. Puede haber dependencias no resueltas. ---" ) # --- FIN BUCLE ITERATIVO --- # --- NUEVO: Verificación de Instrucciones No Procesadas --- print("\n--- Verificación Final de Instrucciones No Procesadas ---") unprocessed_count = 0 unprocessed_details = [] # Lista para guardar detalles for network in data.get("networks", []): network_id = network.get("id", "Unknown ID") network_title = network.get("title", f"Network {network_id}") for instruction in network.get("logic", []): instr_uid = instruction.get("instruction_uid", "Unknown UID") instr_type = instruction.get("type", "Unknown Type") is_grouped = instruction.get("grouped", False) # Comprobar si NO está procesada (sin _scl), NO es error, y NO está agrupada if ( not instr_type.endswith(SCL_SUFFIX) and "_error" not in instr_type and not is_grouped ): unprocessed_count += 1 # Añadir detalle formateado a la lista unprocessed_details.append( f" - Red '{network_title}' (ID: {network_id}), " f"Instrucción UID: {instr_uid}, Tipo Original: '{instr_type}'" ) # Imprimir el resumen de no procesadas if unprocessed_count > 0: print( f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones que no pudieron ser procesadas a SCL:" ) # Imprimir cada detalle de la lista for detail in unprocessed_details: print(detail) print( ">>> Estos tipos de instrucción podrían necesitar un procesador específico en 'x2_process.py'." ) else: print( "INFO: Todas las instrucciones fueron procesadas a SCL, marcadas como error o agrupadas exitosamente." ) # --- FIN Verificación --- # --- Guardar JSON Final --- output_filename = json_filepath.replace( "_simplified.json", "_simplified_processed.json" ) print(f"\nGuardando JSON procesado en: {output_filename}") try: with open(output_filename, "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) print("Guardado completado.") except Exception as e: print(f"Error Crítico al guardar JSON procesado: {e}") traceback.print_exc() # --- Ejecución --- if __name__ == "__main__": # Imports necesarios solo para la ejecución como script principal import argparse import os import sys parser = argparse.ArgumentParser(description="Process simplified JSON to embed SCL logic.") # Acepta el nombre del XML original como referencia para derivar nombres parser.add_argument( "source_xml_filepath", nargs="?", default="TestLAD.xml", help="Path to the original source XML file (used to derive JSON input name, default: TestLAD.xml)" ) args = parser.parse_args() # Derivar el nombre del archivo JSON de entrada esperado xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0] input_dir = os.path.dirname(args.source_xml_filepath) # Directorio del XML original input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json") if not os.path.exists(input_json_file): print( f"Error Fatal: El archivo de entrada JSON simplificado no existe: '{input_json_file}'" ) print( f"Asegúrate de haber ejecutado 'x1_to_json.py' primero sobre '{args.source_xml_filepath}'." ) sys.exit(1) # Salir si el archivo de entrada no existe else: # Llamar a la función principal con el nombre del JSON de entrada # La función process_json_to_scl ya deriva el nombre de salida (_processed.json) process_json_to_scl(input_json_file)