370 lines
13 KiB
C#
370 lines
13 KiB
C#
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<S7Project> 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<DeviceIdInfo> ParseDeviceIdInfos()
|
|
{
|
|
var result = new List<DeviceIdInfo>();
|
|
|
|
// 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<S7Symbol>
|
|
{
|
|
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)
|
|
{
|
|
// Esta es una implementación de muestra
|
|
// En una versión completa, leerías los archivos SUBBLK.DBF reales
|
|
|
|
// Crear algunos bloques de ejemplo
|
|
AddSampleDataBlocks(dbFolder);
|
|
AddSampleFunctionBlocks(fbFolder);
|
|
AddSampleFunctions(fcFolder);
|
|
AddSampleOrgBlocks(obFolder);
|
|
}
|
|
|
|
private void AddSampleDataBlocks(S7Object dbFolder)
|
|
{
|
|
var dbs = new List<S7DataBlock>
|
|
{
|
|
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<S7FunctionBlock>
|
|
{
|
|
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<S7Function>
|
|
{
|
|
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<S7Block>
|
|
{
|
|
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; }
|
|
}
|
|
}
|
|
} |