using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using S7Explorer.Models; namespace S7Explorer.Parsers { public class S7ProjectParser { private readonly string _projectFilePath; private readonly string _projectDirectory; public S7ProjectParser(string projectFilePath) { _projectFilePath = projectFilePath; _projectDirectory = Path.GetDirectoryName(projectFilePath); if (string.IsNullOrEmpty(_projectDirectory)) throw new ArgumentException("No se pudo determinar el directorio del proyecto"); } public async Task ParseProjectAsync() { return await Task.Run(() => { // Crear objeto de proyecto var project = new S7Project(_projectFilePath); try { // Estructura básica del proyecto var devicesFolder = new S7Object { Name = "Dispositivos", ObjectType = S7ObjectType.Folder, Parent = project }; project.Children.Add(devicesFolder); // Parsear dispositivos ParseDevices(devicesFolder); // Para uso en el futuro: más carpetas de alto nivel var sharedFolder = new S7Object { Name = "Datos Globales", ObjectType = S7ObjectType.Folder, Parent = project }; project.Children.Add(sharedFolder); return project; } catch (Exception ex) { // En caso de error, al menos retornamos un proyecto básico con el error var errorObject = new S7Object { Name = "Error al parsear proyecto", Description = ex.Message, ObjectType = S7ObjectType.Folder, Parent = project }; project.Children.Add(errorObject); return project; } }); } private void ParseDevices(S7Object devicesFolder) { try { // Obtener lista de dispositivos a partir del archivo de información var deviceIdInfos = ParseDeviceIdInfos(); foreach (var deviceInfo in deviceIdInfos) { var device = new S7Object { Name = deviceInfo.Name, ObjectType = S7ObjectType.Device, Parent = devicesFolder }; devicesFolder.Children.Add(device); // Crear carpetas para cada tipo de bloque var dbFolder = CreateBlockFolder(device, "Bloques de datos (DB)"); var fbFolder = CreateBlockFolder(device, "Bloques de función (FB)"); var fcFolder = CreateBlockFolder(device, "Funciones (FC)"); var obFolder = CreateBlockFolder(device, "Bloques de organización (OB)"); var symbolsFolder = CreateBlockFolder(device, "Tabla de símbolos"); // Parsear bloques y símbolos si se dispone de IDs if (deviceInfo.SymbolListId.HasValue) { ParseSymbols(symbolsFolder, deviceInfo.SymbolListId.Value); } if (deviceInfo.SubblockListId.HasValue) { ParseBlocks(dbFolder, fbFolder, fcFolder, obFolder, deviceInfo.SubblockListId.Value); } } } catch (Exception ex) { // Si falla el parseo, añadimos un nodo de error pero seguimos con el resto var errorNode = new S7Object { Name = "Error al parsear dispositivos", Description = ex.Message, ObjectType = S7ObjectType.Folder, Parent = devicesFolder }; devicesFolder.Children.Add(errorNode); } } private S7Object CreateBlockFolder(S7Object parent, string name) { var folder = new S7Object { Name = name, ObjectType = S7ObjectType.Folder, Parent = parent }; parent.Children.Add(folder); return folder; } private List ParseDeviceIdInfos() { var result = new List(); // Esto es una simplificación - en una implementación real // necesitarías parsear los archivos reales como en el proyecto C++ var s7resoffPath = Path.Combine(_projectDirectory, "hrs", "S7RESOFF.DBF"); if (File.Exists(s7resoffPath)) { try { // Leer la tabla de dispositivos var records = DbfParser.ReadDbfFile(s7resoffPath, new[] { "ID", "NAME", "RSRVD4_L" }); foreach (var record in records) { var device = new DeviceIdInfo { Name = DbfParser.ConvertCP1252ToUtf8(record["NAME"]), }; // Procesamiento simplificado para IDs de Subblock y Symbol List device.SubblockListId = DbfParser.StringToInt(record["RSRVD4_L"]); // NOTA: En una implementación completa, tendrías que leer linkhrs.lnk // para obtener SubblockListId y SymbolListId reales result.Add(device); } } catch (Exception) { // Error de parseo - ignorar y continuar } } // Si no se encuentran dispositivos, crear uno de ejemplo if (result.Count == 0) { result.Add(new DeviceIdInfo { Name = "Dispositivo de ejemplo" }); } return result; } private void ParseSymbols(S7Object symbolsFolder, int symbolListId) { // Esta es una implementación de muestra // En una versión completa, leerías los archivos YDBs reales // Crear algunos símbolos de ejemplo var symbols = new List { new S7Symbol { Name = "Motor_Start", Address = "I0.0", DataType = "BOOL", Comment = "Pulsador de inicio del motor" }, new S7Symbol { Name = "Motor_Stop", Address = "I0.1", DataType = "BOOL", Comment = "Pulsador de parada del motor" }, new S7Symbol { Name = "Motor_Running", Address = "Q0.0", DataType = "BOOL", Comment = "Motor en marcha" }, new S7Symbol { Name = "Temperature", Address = "IW64", DataType = "INT", Comment = "Temperatura del proceso" }, }; foreach (var symbol in symbols) { symbol.Parent = symbolsFolder; symbolsFolder.Children.Add(symbol); } } private void ParseBlocks(S7Object dbFolder, S7Object fbFolder, S7Object fcFolder, S7Object obFolder, int subblockListId) { try { // Usar parser específico para las funciones (FC) var fcParser = new FCParser(_projectDirectory); var functions = fcParser.ParseFunctionBlocks(subblockListId); // Añadir bloques FC al árbol de proyecto foreach (var fc in functions) { fc.Parent = fcFolder; fcFolder.Children.Add(fc); } // Para los otros tipos de bloques, seguimos usando los ejemplos por ahora // En una implementación completa, crearíamos parsers específicos para cada tipo AddSampleDataBlocks(dbFolder); AddSampleFunctionBlocks(fbFolder); AddSampleOrgBlocks(obFolder); } catch (Exception ex) { // En caso de error, añadimos un objeto de error informativo var errorObject = new S7Object { Name = "Error en parseo de bloques", Description = ex.Message, ObjectType = S7ObjectType.Folder }; // Lo añadimos a cada carpeta para que sea visible dbFolder.Children.Add(errorObject); fbFolder.Children.Add(new S7Object { Name = errorObject.Name, Description = errorObject.Description, ObjectType = errorObject.ObjectType }); fcFolder.Children.Add(new S7Object { Name = errorObject.Name, Description = errorObject.Description, ObjectType = errorObject.ObjectType }); obFolder.Children.Add(new S7Object { Name = errorObject.Name, Description = errorObject.Description, ObjectType = errorObject.ObjectType }); } } private void AddSampleDataBlocks(S7Object dbFolder) { var dbs = new List { new S7DataBlock { Name = "Datos_proceso", Number = "DB1", Size = 124, IsInstanceDb = false, Description = "Datos de proceso", Modified = DateTime.Now.AddDays(-5) }, new S7DataBlock { Name = "Parámetros", Number = "DB2", Size = 234, IsInstanceDb = false, Description = "Parámetros de configuración", Modified = DateTime.Now.AddDays(-10) }, new S7DataBlock { Name = "Motor_Inst", Number = "DB10", Size = 86, IsInstanceDb = true, Description = "Instancia de FB1", Modified = DateTime.Now.AddDays(-2) } }; foreach (var db in dbs) { db.Parent = dbFolder; dbFolder.Children.Add(db); } } private void AddSampleFunctionBlocks(S7Object fbFolder) { var fbs = new List { new S7FunctionBlock { Name = "Motor_Control", Number = "FB1", Size = 328, Language = "SCL", Description = "Control de motor", Modified = DateTime.Now.AddDays(-15) }, new S7FunctionBlock { Name = "PID_Control", Number = "FB2", Size = 512, Language = "SCL", Description = "Controlador PID", Modified = DateTime.Now.AddDays(-20) } }; foreach (var fb in fbs) { fb.Parent = fbFolder; fbFolder.Children.Add(fb); } } private void AddSampleFunctions(S7Object fcFolder) { var fcs = new List { new S7Function { Name = "Calc_Setpoint", Number = "FC1", Size = 124, ReturnType = "REAL", Description = "Cálculo de punto de consigna", Modified = DateTime.Now.AddDays(-8) }, new S7Function { Name = "Scale_Analog", Number = "FC2", Size = 68, ReturnType = "REAL", Description = "Escalado de valor analógico", Modified = DateTime.Now.AddDays(-12) } }; foreach (var fc in fcs) { fc.Parent = fcFolder; fcFolder.Children.Add(fc); } } private void AddSampleOrgBlocks(S7Object obFolder) { var obs = new List { new S7Block { Name = "Main", Number = "OB1", Size = 256, ObjectType = S7ObjectType.Organization, Description = "Ciclo principal", Modified = DateTime.Now.AddDays(-1) }, new S7Block { Name = "Clock_100ms", Number = "OB35", Size = 124, ObjectType = S7ObjectType.Organization, Description = "Interrupción cíclica 100ms", Modified = DateTime.Now.AddDays(-7) } }; foreach (var ob in obs) { ob.Parent = obFolder; obFolder.Children.Add(ob); } } // Clase interna para la información de los dispositivos private class DeviceIdInfo { public string Name { get; set; } public int? SubblockListId { get; set; } public int? SymbolListId { get; set; } } } }