272 lines
12 KiB
Python
272 lines
12 KiB
Python
# -*- 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) |