# -*- coding: utf-8 -*- import json import os import re import argparse # --- Helper Functions --- def format_variable_name(name): """Limpia el nombre de la variable quitando comillas y espacios.""" if not name: return "_INVALID_NAME_" # Quita comillas dobles iniciales/finales name = name.strip('"') # Reemplaza comillas dobles internas y puntos por guión bajo name = name.replace('"."', '_').replace('.', '_') # Quita comillas restantes (si las hubiera) name = name.replace('"', '') # Asegurarse de que no empiece con número (aunque raro con comillas iniciales) if name and name[0].isdigit(): name = "_" + name return name def generate_scl(processed_json_filepath, output_scl_filepath): """Genera un archivo SCL a partir del JSON procesado.""" if not os.path.exists(processed_json_filepath): print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'") return print(f"Cargando JSON procesado desde: {processed_json_filepath}") try: with open(processed_json_filepath, 'r', encoding='utf-8') as f: data = json.load(f) except Exception as e: print(f"Error al cargar o parsear JSON: {e}") return # --- Extracción de Información del Bloque --- block_name = data.get('block_name', 'UnknownBlock') block_number = data.get('block_number') block_lang = data.get('language', 'LAD') # Lenguaje original block_comment = data.get('block_comment', '') # Limpiar nombre del bloque para usarlo en SCL scl_block_name = format_variable_name(block_name) print(f"Generando SCL para el bloque: {scl_block_name} (Original: {block_name})") # --- Identificación de Variables Temporales y Estáticas --- temp_vars = set() stat_vars = set() # Para flancos, si se implementan completamente # Usar regex para encontrar variables _temp_... y stat_... temp_pattern = re.compile(r'"?(_temp_[a-zA-Z0-9_]+)"?') stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?') for network in data.get('networks', []): for instruction in network.get('logic', []): scl_code = instruction.get('scl', '') if scl_code: # Buscar temporales en el código SCL generado found_temps = temp_pattern.findall(scl_code) for temp_name in found_temps: temp_vars.add(temp_name) # Añadir al set (evita duplicados) # Buscar estáticas (para flancos) found_stats = stat_pattern.findall(scl_code) for stat_name in found_stats: stat_vars.add(stat_name) print(f"Variables temporales detectadas: {len(temp_vars)}") # print(f"Variables estáticas detectadas (para flancos): {len(stat_vars)}") # --- Construcción del String SCL --- scl_output = [] # Cabecera del Bloque scl_output.append(f"// Block Name (Original): {block_name}") if block_number: scl_output.append(f"// Block Number: {block_number}") scl_output.append(f"// Original Language: {block_lang}") if block_comment: scl_output.append(f"// Block Comment: {block_comment}") scl_output.append("") scl_output.append(f"FUNCTION_BLOCK \"{scl_block_name}\"") # Asumir FB por variables Temp/Stat scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Opcional, común scl_output.append("VERSION : 0.1") # Opcional scl_output.append("") # Declaraciones de Interfaz scl_output.append("VAR_INPUT") # Iterar sobre data['interface']['Input'] si existe # for var in data.get('interface', {}).get('Input', []): # scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};") scl_output.append("END_VAR") scl_output.append("") scl_output.append("VAR_OUTPUT") # Iterar sobre data['interface']['Output'] si existe # for var in data.get('interface', {}).get('Output', []): # scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};") scl_output.append("END_VAR") scl_output.append("") scl_output.append("VAR_IN_OUT") # Iterar sobre data['interface']['InOut'] si existe # for var in data.get('interface', {}).get('InOut', []): # scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};") scl_output.append("END_VAR") scl_output.append("") # Declaraciones Estáticas (para flancos) if stat_vars: scl_output.append("VAR_STAT") # Asumir Bool para flancos, se podría inferir mejor si PBox lo indicara for var_name in sorted(list(stat_vars)): scl_output.append(f" \"{var_name}\" : Bool; // Memory for edge detection") scl_output.append("END_VAR") scl_output.append("") # Declaraciones Temporales # Incluir las variables de la sección Temp del JSON original # y las generadas automáticamente (_temp_...) scl_output.append("VAR_TEMP") declared_temps = set() interface_temps = data.get('interface', {}).get('Temp', []) if interface_temps: for var in interface_temps: formatted_name = format_variable_name(var['name']) # Añadir comillas si el nombre original las tenía o si contiene caracteres especiales scl_name = f'"{formatted_name}"' if '"' in var['name'] or '.' in var['name'] else formatted_name scl_output.append(f" {scl_name} : {var['datatype']};") declared_temps.add(scl_name) # Marcar como declarada # Declarar las _temp_ generadas si no estaban ya en la interfaz Temp if temp_vars: # Intentar inferir tipo (difícil sin más info), por ahora usar Variant o Bool/DInt for var_name in sorted(list(temp_vars)): scl_name = f'"{var_name}"' # Asegurar comillas para _temp_ if scl_name not in declared_temps: # Inferencia básica de tipo por nombre de pin (muy heurístico) inferred_type = "Variant" # Tipo seguro por defecto if var_name.endswith("_out"): # Salida de bloque lógico/comparación? inferred_type = "Bool" elif var_name.endswith("_out_num"): # Salida de bloque numérico? inferred_type = "DInt" # O Real? O Int? Difícil saber # Se podría mejorar si los procesadores añadieran info de tipo scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary") declared_temps.add(scl_name) # Marcar como declarada scl_output.append("END_VAR") scl_output.append("") # Cuerpo del Bloque scl_output.append("BEGIN") scl_output.append("") # Iterar por redes y lógica for i, network in enumerate(data.get('networks', [])): network_title = network.get('title', f'Network {network.get("id")}') network_comment = network.get('comment', '') # Obtener lenguaje original de la red, default a LAD si no está network_lang = network.get('language', 'LAD') scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})") if network_comment: for line in network_comment.splitlines(): scl_output.append(f" // {line}") scl_output.append("") # Línea en blanco antes del código de la red network_has_code = False # Iterar sobre la 'logica' de la red for instruction in network.get('logic', []): instruction_type = instruction.get("type", "") scl_code = instruction.get('scl') # --- INICIO: Manejar RAW_SCL_CHUNK y otros tipos --- if instruction_type == "RAW_SCL_CHUNK" and scl_code: network_has_code = True # Imprimir el SCL reconstruido directamente for line in scl_code.splitlines(): scl_output.append(f" {line}") # Añadir indentación elif instruction_type == "UNSUPPORTED_LANG" and scl_code: network_has_code = True # Imprimir comentario para lenguajes no soportados for line in scl_code.splitlines(): scl_output.append(f" {line}") elif instruction_type.endswith("_scl") and scl_code: # Código SCL generado por x2_process.py para LAD/FBD if instruction.get("grouped", False): # Ignorar si fue agrupado continue # Lógica mejorada para omitir comentarios internos si no aportan lines = scl_code.splitlines() is_internal_comment_only = (len(lines) == 1 and (lines[0].strip().startswith("// RLO") or lines[0].strip().startswith("// Comparison") or lines[0].strip().startswith("// Logic O") or lines[0].strip().startswith("// Logic included in grouped IF") or # Usar GROUPED_COMMENT si lo importas lines[0].strip().startswith("// P_TRIG") or lines[0].strip().startswith("// N_TRIG") )) if not is_internal_comment_only: network_has_code = True for line in lines: clean_line = line.strip() # Omitir comentarios autogenerados específicos if not (clean_line.startswith("// RLO:") or \ clean_line.startswith("// Comparison") or \ clean_line.startswith("// Logic O") or \ clean_line == "// Logic included in grouped IF" or \ clean_line.startswith("// P_TRIG(") or \ clean_line.startswith("// N_TRIG(")): scl_output.append(f" {line}") # Indentación # Ignorar instrucciones no procesadas (sin _scl) o con error # elif "_error" in instruction_type: # scl_output.append(f" // Error processing instruction {instruction.get('instruction_uid')}: {scl_code}") # network_has_code = True # --- FIN: Manejar RAW_SCL_CHUNK y otros tipos --- if network_has_code: scl_output.append("") # Añadir línea en blanco después del código de la red si hubo algo else: # Añadir comentario si la red estaba vacía o no generó código imprimible scl_output.append(f" // Network did not produce printable SCL code.") scl_output.append("") scl_output.append("END_FUNCTION_BLOCK") # --- Escritura del Archivo SCL --- print(f"Escribiendo archivo SCL en: {output_scl_filepath}") try: with open(output_scl_filepath, 'w', encoding='utf-8') as f: for line in scl_output: f.write(line + '\n') print("Generación de SCL completada.") except Exception as e: print(f"Error al escribir el archivo SCL: {e}") # --- 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="Generate final SCL file from processed JSON.") # 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 input/output names, default: TestLAD.xml)" ) args = parser.parse_args() # Derivar nombres de archivos de entrada y salida 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 # Nombre del JSON procesado (entrada para este script) input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified_processed.json") # Nombre del SCL final (salida de este script) output_scl_file = os.path.join(input_dir, f"{xml_filename_base}_simplified_processed.scl") # Verificar si el archivo JSON procesado de entrada existe if not os.path.exists(input_json_file): print(f"Error: Processed JSON file not found: '{input_json_file}'") print(f"Ensure 'x2_process.py' ran successfully for '{args.source_xml_filepath}'.") sys.exit(1) # Salir si el archivo de entrada no existe else: # Llamar a la función principal con los nombres derivados generate_scl(input_json_file, output_scl_file)