287 lines
14 KiB
Python
287 lines
14 KiB
Python
import json
|
|
from typing import List, Dict, Any, Union
|
|
|
|
def format_data_type_for_source(var_info: Dict[str, Any]) -> str:
|
|
"""Formatea la declaración de tipo completa para la variable en S7 source."""
|
|
base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"]
|
|
|
|
type_str = ""
|
|
if var_info.get("array_dimensions"):
|
|
dims_str = ",".join([f"{d['lower_bound']}..{d['upper_bound']}" for d in var_info["array_dimensions"]])
|
|
type_str += f"ARRAY [{dims_str}] OF "
|
|
|
|
type_str += base_type
|
|
|
|
if var_info["data_type"].upper() == "STRING" and var_info.get("string_length") is not None:
|
|
type_str += f"[{var_info['string_length']}]"
|
|
|
|
return type_str
|
|
|
|
def generate_variable_declaration_for_source(var_info: Dict[str, Any], indent_level: int) -> str:
|
|
"""Genera una línea de declaración de variable S7."""
|
|
indent_str = " " * indent_level
|
|
type_declaration_str = format_data_type_for_source(var_info)
|
|
|
|
line = f'{indent_str}{var_info["name"]} : {type_declaration_str}'
|
|
|
|
if var_info.get("initial_value") is not None:
|
|
initial_val = var_info["initial_value"]
|
|
if isinstance(initial_val, bool):
|
|
initial_val_str = "TRUE" if initial_val else "FALSE"
|
|
else:
|
|
initial_val_str = str(initial_val)
|
|
line += f' := {initial_val_str}'
|
|
|
|
# No añadir ; si es una declaración de STRUCT que va a tener un bloque de miembros.
|
|
# El ; irá después de su END_STRUCT.
|
|
if not (var_info["data_type"].upper() == "STRUCT" and var_info.get("children") and not var_info.get("udt_source_name")):
|
|
line += ';'
|
|
|
|
if var_info.get("comment"):
|
|
line += f'\t// {var_info["comment"]}'
|
|
|
|
return line
|
|
|
|
def generate_struct_members_for_source(members: List[Dict[str, Any]], indent_level: int) -> List[str]:
|
|
"""Genera recursivamente las declaraciones de miembros para STRUCTs/UDTs."""
|
|
lines = []
|
|
for var_info in members:
|
|
if var_info.get("is_udt_expanded_member"): # No declarar individualmente miembros expandidos de UDT en el padre
|
|
continue
|
|
|
|
# Si es una definición de STRUCT anidada (no una instancia de UDT)
|
|
if var_info["data_type"].upper() == "STRUCT" and not var_info.get("udt_source_name") and var_info.get("children"):
|
|
current_indent_str = " " * indent_level
|
|
# La declaración "Miembro : STRUCT" no lleva ; si es multilínea.
|
|
lines.append(f'{current_indent_str}{var_info["name"]} : STRUCT')
|
|
lines.extend(generate_struct_members_for_source(var_info["children"], indent_level + 1))
|
|
lines.append(f'{current_indent_str}END_STRUCT;') # ; después de END_STRUCT
|
|
else: # Variable primitiva, String, Array, o instancia de UDT
|
|
lines.append(generate_variable_declaration_for_source(var_info, indent_level))
|
|
return lines
|
|
|
|
def _generate_assignments_recursive_from_current_values(members: List[Dict[str, Any]], path_prefix: str, indent_str: str) -> List[str]:
|
|
"""
|
|
Fallback recursivo para generar asignaciones del bloque BEGIN usando current_value y current_element_values.
|
|
Este se usa si _initial_values_from_begin_block no está en el JSON.
|
|
"""
|
|
assignment_lines = []
|
|
for var_info in members:
|
|
current_member_name = var_info['name']
|
|
current_full_path = f"{path_prefix}{current_member_name}"
|
|
|
|
if var_info.get("current_element_values") and isinstance(var_info["current_element_values"], dict):
|
|
# Ordenar por índice para consistencia si los índices son numéricos simples
|
|
# La clave 'index_str' podría ser "1" o "1,0" etc.
|
|
try:
|
|
# Intenta convertir la clave a una tupla de enteros para un ordenamiento numérico robusto
|
|
sorted_indices = sorted(
|
|
var_info["current_element_values"].keys(),
|
|
key=lambda k: tuple(map(int, k.split(',')))
|
|
)
|
|
except ValueError: # Si las claves no son puramente numéricas/separadas por comas
|
|
sorted_indices = sorted(var_info["current_element_values"].keys())
|
|
|
|
for index_str in sorted_indices:
|
|
val_str_el = var_info["current_element_values"][index_str]
|
|
el_path = f"{current_full_path}[{index_str}]"
|
|
|
|
f_val_str_el = str(val_str_el)
|
|
if f_val_str_el.lower() == "true": f_val_str_el = "TRUE"
|
|
elif f_val_str_el.lower() == "false": f_val_str_el = "FALSE"
|
|
assignment_lines.append(f"{indent_str}{el_path} := {f_val_str_el};")
|
|
|
|
elif var_info.get("udt_source_name") and var_info.get("children"):
|
|
assignment_lines.extend(_generate_assignments_recursive_from_current_values(var_info["children"], f"{current_full_path}.", indent_str))
|
|
elif var_info.get("data_type", "").upper() == "STRUCT" and not var_info.get("udt_source_name") and var_info.get("children"):
|
|
assignment_lines.extend(_generate_assignments_recursive_from_current_values(var_info["children"], f"{current_full_path}.", indent_str))
|
|
elif var_info.get("current_value") is not None:
|
|
val_str_cv = var_info["current_value"]
|
|
f_val_str_cv = str(val_str_cv)
|
|
if f_val_str_cv.lower() == "true": f_val_str_cv = "TRUE"
|
|
elif f_val_str_cv.lower() == "false": f_val_str_cv = "FALSE"
|
|
assignment_lines.append(f"{indent_str}{current_full_path} := {f_val_str_cv};")
|
|
return assignment_lines
|
|
|
|
|
|
def generate_begin_block_assignments(db_info: Dict[str, Any], indent_level: int) -> List[str]:
|
|
"""Genera las líneas de asignación para el bloque BEGIN de un DB."""
|
|
indent_str = " " * indent_level
|
|
lines = []
|
|
|
|
# Prioridad: Usar _initial_values_from_begin_block si está presente en el JSON
|
|
# ¡ASEGÚRATE DE QUE x3.py INCLUYA ESTE CAMPO EN EL JSON (modificando custom_json_serializer en x3.py)!
|
|
begin_values_map = db_info.get("_initial_values_from_begin_block")
|
|
|
|
if begin_values_map and isinstance(begin_values_map, dict):
|
|
print(f"INFO: Usando _initial_values_from_begin_block para DB '{db_info['name']}' en el bloque BEGIN.")
|
|
# Ordenar por clave para una salida más consistente.
|
|
# El orden original del bloque BEGIN en el archivo fuente es difícil de replicar sin información adicional.
|
|
for path, value_obj in sorted(begin_values_map.items()):
|
|
value_str = str(value_obj) # Asegurar que el valor es una cadena
|
|
# S7 convierte true/false de JSON a TRUE/FALSE en el fuente.
|
|
if value_str.lower() == "true":
|
|
value_str = "TRUE"
|
|
elif value_str.lower() == "false":
|
|
value_str = "FALSE"
|
|
# Otros tipos como B#16#XX, W#16#YYYY, L#... etc., deberían estar ya como strings.
|
|
lines.append(f"{indent_str}{path} := {value_str};")
|
|
else:
|
|
# Fallback: si _initial_values_from_begin_block no está, intentar reconstruir desde current_value/current_element_values
|
|
# Esto depende de que x3.py haya poblado bien estos campos.
|
|
print(f"ADVERTENCIA: _initial_values_from_begin_block no encontrado o vacío para DB '{db_info['name']}'. "
|
|
"Intentando reconstruir el bloque BEGIN desde current_value/current_element_values.")
|
|
lines.extend(_generate_assignments_recursive_from_current_values(db_info.get("members", []), "", indent_str))
|
|
|
|
return lines
|
|
|
|
def generate_s7_source_code_lines(data: Dict[str, Any]) -> List[str]:
|
|
"""Genera el código fuente S7 completo (UDTs y DBs) a partir de los datos JSON."""
|
|
lines = []
|
|
|
|
parsed_json_udts_lookup = {udt['name']: udt for udt in data.get("udts", [])} # Necesario para algunas lógicas internas
|
|
|
|
# Generar UDTs
|
|
for udt in data.get("udts", []):
|
|
lines.append(f'TYPE "{udt["name"]}"')
|
|
if udt.get("family"): lines.append(f' FAMILY : {udt["family"]};')
|
|
if udt.get("version"): lines.append(f' VERSION : {udt["version"]};')
|
|
lines.append("")
|
|
lines.append(" STRUCT") # El keyword STRUCT en sí no lleva ;
|
|
lines.extend(generate_struct_members_for_source(udt["members"], 2))
|
|
lines.append(" END_STRUCT;")
|
|
lines.append(f'END_TYPE;')
|
|
lines.append("")
|
|
|
|
# Generar DBs
|
|
for db in data.get("dbs", []):
|
|
lines.append(f'DATA_BLOCK "{db["name"]}"')
|
|
|
|
# Reconstruir TITLE si existe en el JSON
|
|
if db.get("title"):
|
|
title_str = db["title"]
|
|
# El formato original es TITLE = y luego el valor, usualmente una estructura { S7_language... }
|
|
# No se añade punto y coma a esta línea específica.
|
|
lines.append(f' TITLE = {title_str}')
|
|
|
|
if db.get("family"): lines.append(f' FAMILY : {db["family"]};')
|
|
if db.get("version"): lines.append(f' VERSION : {db["version"]};')
|
|
lines.append("")
|
|
lines.append(" STRUCT") # El keyword STRUCT en sí no lleva ;
|
|
lines.extend(generate_struct_members_for_source(db["members"], 2))
|
|
lines.append(" END_STRUCT;")
|
|
|
|
begin_assignments = generate_begin_block_assignments(db, 1) # Pasar parsed_json_udts_lookup si es necesario en el futuro
|
|
if begin_assignments:
|
|
lines.append("BEGIN")
|
|
lines.extend(begin_assignments)
|
|
|
|
lines.append(f'END_DATA_BLOCK;')
|
|
lines.append("")
|
|
|
|
return lines
|
|
|
|
# --- generate_markdown_table (sin cambios significativos respecto a la v3, pero puedes revisarla) ---
|
|
def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]:
|
|
lines = []
|
|
lines.append(f"# Documentación para DB: {db_info['name']}")
|
|
lines.append("")
|
|
lines.append("| Address | Name | Type | Initial Value | Actual Value | Comment |")
|
|
lines.append("|---|---|---|---|---|---|")
|
|
|
|
processed_expanded_members = set()
|
|
|
|
def flatten_members_for_markdown(members: List[Dict[str, Any]], prefix: str = "", base_offset: float = 0.0, is_expansion: bool = False):
|
|
md_lines = []
|
|
for var_idx, var in enumerate(members):
|
|
member_id = f"{prefix}{var['name']}_{var_idx}"
|
|
|
|
if is_expansion and member_id in processed_expanded_members:
|
|
continue
|
|
if is_expansion:
|
|
processed_expanded_members.add(member_id)
|
|
|
|
name_for_display = f"{prefix}{var['name']}"
|
|
|
|
address = f"{var['byte_offset']:.1f}" if isinstance(var['byte_offset'], float) else str(var['byte_offset'])
|
|
if var.get("bit_size", 0) > 0 and isinstance(var['byte_offset'], float) and var['byte_offset'] != int(var['byte_offset']):
|
|
pass
|
|
elif var.get("bit_size", 0) > 0 :
|
|
address = f"{int(var['byte_offset'])}.0"
|
|
|
|
data_type_str = format_data_type_for_source(var)
|
|
initial_value = str(var.get("initial_value", ""))
|
|
actual_value = str(var.get("current_value", ""))
|
|
comment = str(var.get("comment", ""))
|
|
|
|
initial_value = initial_value.replace("|", "\\|")
|
|
actual_value = actual_value.replace("|", "\\|")
|
|
comment = comment.replace("|", "\\|").replace("\n", " ")
|
|
|
|
is_struct_container = var["data_type"].upper() == "STRUCT" and not var.get("udt_source_name") and var.get("children")
|
|
is_udt_instance_container = bool(var.get("udt_source_name")) and var.get("children")
|
|
|
|
if not is_struct_container and not is_udt_instance_container or var.get("is_udt_expanded_member"):
|
|
md_lines.append(f"| {address} | {name_for_display} | {data_type_str} | {initial_value} | {actual_value} | {comment} |")
|
|
|
|
if var.get("children"):
|
|
md_lines.extend(flatten_members_for_markdown(var["children"],
|
|
f"{name_for_display}.",
|
|
var['byte_offset'],
|
|
is_expansion=bool(var.get("udt_source_name"))))
|
|
return md_lines
|
|
|
|
lines.extend(flatten_members_for_markdown(db_info.get("members", [])))
|
|
return lines
|
|
|
|
|
|
def main():
|
|
# Asegúrate que este es el JSON generado por la última versión de x3.py
|
|
# (la que incluye _initial_values_from_begin_block y current_element_values)
|
|
json_input_filename = "parsed_s7_data.json"
|
|
s7_output_filename = "reconstructed_s7_source.txt" # Nueva versión de salida
|
|
|
|
try:
|
|
with open(json_input_filename, 'r', encoding='utf-8') as f:
|
|
data_from_json = json.load(f)
|
|
except FileNotFoundError:
|
|
print(f"Error: No se encontró el archivo JSON de entrada: {json_input_filename}")
|
|
return
|
|
except json.JSONDecodeError:
|
|
print(f"Error: El archivo JSON de entrada no es válido: {json_input_filename}")
|
|
return
|
|
except Exception as e:
|
|
print(f"Error al leer el archivo JSON {json_input_filename}: {e}")
|
|
return
|
|
|
|
print(f"Archivo JSON '{json_input_filename}' cargado correctamente.")
|
|
|
|
# CORRECCIÓN DEL ERROR: Pasar el argumento faltante
|
|
s7_code_lines = generate_s7_source_code_lines(data_from_json) # parsed_json_udts se crea dentro ahora
|
|
try:
|
|
with open(s7_output_filename, 'w', encoding='utf-8') as f:
|
|
for line in s7_code_lines:
|
|
f.write(line + "\n")
|
|
print(f"Archivo S7 reconstruido generado: {s7_output_filename}")
|
|
except Exception as e:
|
|
print(f"Error al escribir el archivo S7 {s7_output_filename}: {e}")
|
|
|
|
if data_from_json.get("dbs"):
|
|
for db_to_document in data_from_json["dbs"]:
|
|
db_name_safe = db_to_document['name'].replace('"', '').replace(' ', '_').replace('/','_')
|
|
md_filename_specific = f"documentation_db_{db_name_safe}.md" # Nuevo nombre
|
|
|
|
print(f"\nGenerando documentación Markdown para DB: {db_to_document['name']}...")
|
|
markdown_lines = generate_markdown_table(db_to_document)
|
|
try:
|
|
with open(md_filename_specific, 'w', encoding='utf-8') as f:
|
|
for line in markdown_lines:
|
|
f.write(line + "\n")
|
|
print(f"Archivo Markdown de documentación generado: {md_filename_specific}")
|
|
except Exception as e:
|
|
print(f"Error al escribir el archivo Markdown {md_filename_specific}: {e}")
|
|
else:
|
|
print("No se encontraron DBs en el archivo JSON para generar documentación.")
|
|
|
|
if __name__ == "__main__":
|
|
main() |