diff --git a/LinqExtensions.cs b/LinqExtensions.cs
new file mode 100644
index 0000000..44f8906
--- /dev/null
+++ b/LinqExtensions.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace S7Explorer
+{
+ ///
+ /// Extensiones LINQ para funcionalidades específicas de la aplicación
+ ///
+ public static class LinqExtensions
+ {
+ ///
+ /// Cuenta elementos en una colección que cumplen un predicado
+ ///
+ public static int Count(this IEnumerable source, Func predicate)
+ {
+ if (source == null)
+ return 0;
+
+ return source.Where(predicate).Count();
+ }
+
+ ///
+ /// Verifica si una colección contiene al menos un elemento
+ ///
+ public static bool Any(this IEnumerable source)
+ {
+ if (source == null)
+ return false;
+
+ return source.Any(item => true);
+ }
+
+ ///
+ /// Verifica si una colección contiene al menos un elemento que cumpla un predicado
+ ///
+ public static bool Any(this IEnumerable source, Func predicate)
+ {
+ if (source == null)
+ return false;
+
+ using (var enumerator = source.GetEnumerator())
+ {
+ while (enumerator.MoveNext())
+ {
+ if (predicate(enumerator.Current))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Selecciona elementos de una colección que cumplen un predicado
+ ///
+ public static IEnumerable Where(this IEnumerable source, Func predicate)
+ {
+ if (source == null)
+ yield break;
+
+ foreach (var item in source)
+ {
+ if (predicate(item))
+ yield return item;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Models/S7Block.cs b/Models/S7Block.cs
index 0cda6a1..727fb3c 100644
--- a/Models/S7Block.cs
+++ b/Models/S7Block.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Linq;
+using Newtonsoft.Json;
namespace S7Explorer.Models
{
@@ -10,6 +13,7 @@ namespace S7Explorer.Models
private string _version;
private DateTime? _modified;
private int _size;
+ private string _language;
[DisplayName("Autor")]
public string AuthorName
@@ -81,6 +85,20 @@ namespace S7Explorer.Models
}
}
+ [DisplayName("Lenguaje")]
+ public string Language
+ {
+ get => _language;
+ set
+ {
+ if (_language != value)
+ {
+ _language = value;
+ OnPropertyChanged(nameof(Language));
+ }
+ }
+ }
+
public S7Block()
{
// Establece valores predeterminados específicos para bloques
@@ -113,49 +131,20 @@ namespace S7Explorer.Models
public class S7FunctionBlock : S7Block
{
- private string _language;
-
- [DisplayName("Lenguaje")]
- public string Language
- {
- get => _language;
- set
- {
- if (_language != value)
- {
- _language = value;
- OnPropertyChanged(nameof(Language));
- }
- }
- }
-
public S7FunctionBlock()
{
ObjectType = S7ObjectType.FunctionBlock;
}
}
- public class S7Function : S7Block
+ ///
+ /// Clase para representar un parámetro de función
+ ///
+ public class FunctionParameter
{
- private string _returnType;
-
- [DisplayName("Tipo de Retorno")]
- public string ReturnType
- {
- get => _returnType;
- set
- {
- if (_returnType != value)
- {
- _returnType = value;
- OnPropertyChanged(nameof(ReturnType));
- }
- }
- }
-
- public S7Function()
- {
- ObjectType = S7ObjectType.Function;
- }
+ public string Name { get; set; }
+ public string DataType { get; set; }
+ public string Direction { get; set; } // IN, OUT, IN/OUT
+ public string Description { get; set; }
}
}
\ No newline at end of file
diff --git a/Models/S7Function.cs b/Models/S7Function.cs
new file mode 100644
index 0000000..32f4ee2
--- /dev/null
+++ b/Models/S7Function.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Newtonsoft.Json;
+using S7Explorer.Parsers;
+
+namespace S7Explorer.Models
+{
+ public class S7Function : S7Block
+ {
+ private string _returnType;
+ private List _parameters;
+ private string _interface;
+
+ [DisplayName("Tipo de Retorno")]
+ [Description("Tipo de dato que retorna la función")]
+ [Category("Interfaz")]
+ public string ReturnType
+ {
+ get => _returnType;
+ set
+ {
+ if (_returnType != value)
+ {
+ _returnType = value;
+ OnPropertyChanged(nameof(ReturnType));
+ }
+ }
+ }
+
+ [Browsable(false)]
+ [JsonIgnore]
+ public List Parameters
+ {
+ get => _parameters;
+ set
+ {
+ if (_parameters != value)
+ {
+ _parameters = value;
+ OnPropertyChanged(nameof(Parameters));
+
+ // Actualizar la interfaz formateada cuando se cambian los parámetros
+ UpdateFormattedInterface();
+ }
+ }
+ }
+
+ [DisplayName("Interfaz")]
+ [Description("Interfaz de la función con sus parámetros")]
+ [Category("Interfaz")]
+ [EditorAttribute(typeof(System.ComponentModel.Design.MultilineStringEditor),
+ typeof(System.Drawing.Design.UITypeEditor))]
+ public string Interface
+ {
+ get => _interface;
+ set
+ {
+ if (_interface != value)
+ {
+ _interface = value;
+ OnPropertyChanged(nameof(Interface));
+ }
+ }
+ }
+
+ // Propiedades adicionales específicas de FC
+
+ [DisplayName("Parámetros de Entrada")]
+ [Description("Número de parámetros de entrada")]
+ [Category("Estadísticas")]
+ public int InputParameterCount =>
+ Parameters?.Count(p => p.Direction == "IN") ?? 0;
+
+ [DisplayName("Parámetros de Salida")]
+ [Description("Número de parámetros de salida")]
+ [Category("Estadísticas")]
+ public int OutputParameterCount =>
+ Parameters?.Count(p => p.Direction == "OUT") ?? 0;
+
+ [DisplayName("Parámetros IN/OUT")]
+ [Description("Número de parámetros de entrada/salida")]
+ [Category("Estadísticas")]
+ public int InOutParameterCount =>
+ Parameters?.Count(p => p.Direction == "IN/OUT") ?? 0;
+
+ [DisplayName("Total Parámetros")]
+ [Description("Número total de parámetros")]
+ [Category("Estadísticas")]
+ public int TotalParameterCount =>
+ Parameters?.Count ?? 0;
+
+ public S7Function()
+ {
+ ObjectType = S7ObjectType.Function;
+ Parameters = new List();
+ }
+
+ ///
+ /// Actualiza la representación formateada de la interfaz basada en los parámetros
+ ///
+ private void UpdateFormattedInterface()
+ {
+ if (Parameters == null || Parameters.Count == 0)
+ {
+ Interface = "// No hay parámetros definidos";
+ return;
+ }
+
+ var builder = new System.Text.StringBuilder();
+
+ // Agrupar por dirección
+ var inputParams = Parameters.Where(p => p.Direction == "IN").ToList();
+ var outputParams = Parameters.Where(p => p.Direction == "OUT").ToList();
+ var inOutParams = Parameters.Where(p => p.Direction == "IN/OUT").ToList();
+
+ // Añadir tipo de retorno
+ builder.AppendLine($"FUNCTION {Name} : {ReturnType ?? "VOID"}");
+ builder.AppendLine();
+
+ // Añadir parámetros de entrada
+ if (inputParams.Any())
+ {
+ builder.AppendLine("VAR_INPUT");
+ foreach (var param in inputParams)
+ {
+ builder.Append($" {param.Name} : {param.DataType}");
+ if (!string.IsNullOrEmpty(param.Description))
+ builder.Append($"; // {param.Description}");
+ builder.AppendLine();
+ }
+ builder.AppendLine("END_VAR");
+ builder.AppendLine();
+ }
+
+ // Añadir parámetros de salida
+ if (outputParams.Any())
+ {
+ builder.AppendLine("VAR_OUTPUT");
+ foreach (var param in outputParams)
+ {
+ builder.Append($" {param.Name} : {param.DataType}");
+ if (!string.IsNullOrEmpty(param.Description))
+ builder.Append($"; // {param.Description}");
+ builder.AppendLine();
+ }
+ builder.AppendLine("END_VAR");
+ builder.AppendLine();
+ }
+
+ // Añadir parámetros de entrada/salida
+ if (inOutParams.Any())
+ {
+ builder.AppendLine("VAR_IN_OUT");
+ foreach (var param in inOutParams)
+ {
+ builder.Append($" {param.Name} : {param.DataType}");
+ if (!string.IsNullOrEmpty(param.Description))
+ builder.Append($"; // {param.Description}");
+ builder.AppendLine();
+ }
+ builder.AppendLine("END_VAR");
+ }
+
+ Interface = builder.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Parsers/DbfParser.cs b/Parsers/DbfParser.cs
index 3472b08..cf87de1 100644
--- a/Parsers/DbfParser.cs
+++ b/Parsers/DbfParser.cs
@@ -18,7 +18,7 @@ namespace S7Explorer.Parsers
try
{
- // Abrir tabla DBF con codificación específica
+ // Abrir tabla DBF con codificación específica (importante para caracteres especiales en STEP7)
using var stream = File.OpenRead(filePath);
using var table = Table.Open(stream);
@@ -33,14 +33,23 @@ namespace S7Explorer.Parsers
{
// Obtener valor y convertir a string si no es null
var value = reader.GetValue(fieldName);
- record[fieldName] = value?.ToString() ?? string.Empty;
- // Manejamos específicamente bytes para campos como "MC5CODE"
- // que pueden contener datos binarios codificados como CP1252
+ // Manejar los diferentes tipos de datos
if (value is byte[] byteValue)
{
+ // Para campos de tipo binario como MC5CODE
record[fieldName] = Encoding.GetEncoding(1252).GetString(byteValue);
}
+ else if (value is DateTime dateValue)
+ {
+ // Mantener formato consistente para fechas
+ record[fieldName] = dateValue.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+ else
+ {
+ // Para todos los demás tipos
+ record[fieldName] = value?.ToString() ?? string.Empty;
+ }
}
result.Add(record);
@@ -60,7 +69,21 @@ namespace S7Explorer.Parsers
if (string.IsNullOrWhiteSpace(value))
return null;
- if (int.TryParse(value, out int result))
+ // Eliminar espacios y caracteres no numéricos iniciales
+ string trimmedValue = value.Trim();
+ int startIndex = 0;
+ while (startIndex < trimmedValue.Length && !char.IsDigit(trimmedValue[startIndex]))
+ startIndex++;
+
+ if (startIndex >= trimmedValue.Length)
+ return null;
+
+ // Extraer solo los dígitos
+ string numericPart = new string(trimmedValue.Substring(startIndex)
+ .TakeWhile(char.IsDigit)
+ .ToArray());
+
+ if (int.TryParse(numericPart, out int result))
return result;
return null;
@@ -72,8 +95,45 @@ namespace S7Explorer.Parsers
if (string.IsNullOrEmpty(input))
return string.Empty;
- byte[] bytes = Encoding.GetEncoding(1252).GetBytes(input);
- return Encoding.UTF8.GetString(bytes);
+ try
+ {
+ // Primero decodificar como Windows-1252 y luego encodear como UTF-8
+ byte[] bytes = Encoding.GetEncoding(1252).GetBytes(input);
+ return Encoding.UTF8.GetString(bytes);
+ }
+ catch
+ {
+ // En caso de error, devolver el string original
+ return input;
+ }
+ }
+
+ // Busca un archivo DBF en varias ubicaciones posibles basadas en el patrón de archivos STEP7
+ public static string FindDbfFile(string basePath, string relativePath)
+ {
+ string path = Path.Combine(basePath, relativePath);
+ if (File.Exists(path))
+ return path;
+
+ // Comprobar si se puede encontrar en un directorio padre
+ string parentPath = Directory.GetParent(basePath)?.FullName;
+ if (!string.IsNullOrEmpty(parentPath))
+ {
+ path = Path.Combine(parentPath, relativePath);
+ if (File.Exists(path))
+ return path;
+ }
+
+ // Intentar buscar por nombre de archivo en subdirectorios
+ string fileName = Path.GetFileName(relativePath);
+ foreach (var subdir in Directory.GetDirectories(basePath, "*", SearchOption.AllDirectories))
+ {
+ path = Path.Combine(subdir, fileName);
+ if (File.Exists(path))
+ return path;
+ }
+
+ return null;
}
}
}
\ No newline at end of file
diff --git a/Parsers/FCParser.cs b/Parsers/FCParser.cs
new file mode 100644
index 0000000..2f97ae4
--- /dev/null
+++ b/Parsers/FCParser.cs
@@ -0,0 +1,439 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using S7Explorer.Models;
+
+namespace S7Explorer.Parsers
+{
+ ///
+ /// Parser específico para los bloques de función (FC) de STEP7
+ ///
+ public class FCParser
+ {
+ private readonly string _projectDirectory;
+
+ public FCParser(string projectDirectory)
+ {
+ _projectDirectory = projectDirectory;
+ }
+
+ ///
+ /// Parsea los bloques FC desde los archivos SUBBLK.DBF del proyecto
+ ///
+ /// ID de la lista de subbloques del dispositivo
+ /// Lista de objetos S7Function representando los bloques FC
+ public List ParseFunctionBlocks(int subblockListId)
+ {
+ var functions = new List();
+
+ try
+ {
+ // Construir la ruta al archivo SUBBLK.DBF que contiene información de los bloques
+ string subblockFolder = $"{_projectDirectory}\\ombstx\\offline\\{subblockListId:X8}";
+ string subblkPath = Path.Combine(subblockFolder, "SUBBLK.DBF");
+
+ if (!File.Exists(subblkPath))
+ {
+ return CreateSampleFunctions(); // En caso de que no exista, usar datos de muestra
+ }
+
+ // Leer datos del archivo DBF
+ var records = DbfParser.ReadDbfFile(subblkPath, new[]
+ {
+ "SUBBLKTYP", "BLKNUMBER", "BLKNAME", "AUTHOR", "FAMILY",
+ "VERSION", "CREATEDATE", "MODDATE", "INTERFLEN", "MC5LEN", "MC5CODE"
+ });
+
+ // Filtrar solo los registros que correspondan a FCs (tipo 00003)
+ var fcRecords = records.Where(r => r["SUBBLKTYP"] == "00003").ToList();
+
+ foreach (var record in fcRecords)
+ {
+ // Convertir número de bloque a entero
+ if (!int.TryParse(record["BLKNUMBER"], out int blockNumber))
+ continue;
+
+ // Crear objeto FC con los datos del registro
+ var fc = new S7Function
+ {
+ Number = $"FC{blockNumber}",
+ Name = DbfParser.ConvertCP1252ToUtf8(record["BLKNAME"]).Trim(),
+ AuthorName = DbfParser.ConvertCP1252ToUtf8(record["AUTHOR"]).Trim(),
+ Family = DbfParser.ConvertCP1252ToUtf8(record["FAMILY"]).Trim(),
+ Version = record["VERSION"].Trim(),
+ Size = ParseBlockSize(record["MC5LEN"]),
+ ObjectType = S7ObjectType.Function,
+ Language = DetermineLanguageFromMC5Code(record["MC5CODE"]),
+ ReturnType = DetermineReturnTypeFromMC5Code(record["MC5CODE"])
+ };
+
+ // Intentar extraer fecha de modificación
+ if (DateTime.TryParse(record["MODDATE"], out DateTime modDate))
+ {
+ fc.Modified = modDate;
+ }
+
+ // Extraer más información del código MC5
+ ExtractAdditionalInfoFromMC5Code(fc, record["MC5CODE"]);
+
+ // Analizar variables de entrada/salida del interfaz
+ AnalyzeInterfaceData(fc, record["MC5CODE"]);
+
+ functions.Add(fc);
+ }
+
+ // Ordenar los bloques por número
+ functions = functions.OrderBy(f => ExtractNumber(f.Number)).ToList();
+ }
+ catch (Exception ex)
+ {
+ // En caso de error, añadir un FC de error para informar al usuario
+ var errorFc = new S7Function
+ {
+ Name = "Error_Parsing_FC",
+ Number = "FC999",
+ Description = $"Error al parsear FCs: {ex.Message}",
+ ObjectType = S7ObjectType.Function
+ };
+ functions.Add(errorFc);
+ }
+
+ // Si no se encontraron FCs, usar datos de muestra
+ if (functions.Count == 0)
+ {
+ return CreateSampleFunctions();
+ }
+
+ return functions;
+ }
+
+ ///
+ /// Extrae el número de un bloque a partir de su identificador (ej: "FC5" -> 5)
+ ///
+ private int ExtractNumber(string blockId)
+ {
+ if (string.IsNullOrEmpty(blockId) || blockId.Length < 3)
+ return 9999; // Valor alto para ordenar al final
+
+ if (int.TryParse(blockId.Substring(2), out int number))
+ return number;
+
+ return 9999;
+ }
+
+ ///
+ /// Parsea el tamaño del bloque a partir del valor MC5LEN
+ ///
+ private int ParseBlockSize(string mc5Len)
+ {
+ if (int.TryParse(mc5Len, out int size))
+ return size;
+
+ return 0;
+ }
+
+ ///
+ /// Determina el lenguaje de programación a partir del código MC5
+ ///
+ private string DetermineLanguageFromMC5Code(string mc5Code)
+ {
+ if (string.IsNullOrEmpty(mc5Code))
+ return "Desconocido";
+
+ // Esta es una lógica simplificada. En una implementación real,
+ // necesitarías analizar patrones específicos en el código MC5
+ if (mc5Code.Contains("STL") || mc5Code.Contains("AWL"))
+ return "AWL";
+ else if (mc5Code.Contains("SCL"))
+ return "SCL";
+ else if (mc5Code.Contains("GRAPH"))
+ return "GRAPH";
+ else if (mc5Code.Contains("KOP") || mc5Code.Contains("LAD"))
+ return "KOP";
+ else if (mc5Code.Contains("FUP") || mc5Code.Contains("FBD"))
+ return "FUP";
+
+ // Por defecto asumimos AWL (lenguaje más común)
+ return "AWL";
+ }
+
+ ///
+ /// Determina el tipo de retorno a partir del código MC5
+ ///
+ private string DetermineReturnTypeFromMC5Code(string mc5Code)
+ {
+ if (string.IsNullOrEmpty(mc5Code))
+ return "VOID";
+
+ // Esta es una lógica simplificada. En una implementación real,
+ // necesitarías analizar patrones específicos en el código MC5
+ if (mc5Code.Contains("BOOL") && mc5Code.Contains("RET_VAL"))
+ return "BOOL";
+ else if (mc5Code.Contains("INT") && mc5Code.Contains("RET_VAL"))
+ return "INT";
+ else if (mc5Code.Contains("REAL") && mc5Code.Contains("RET_VAL"))
+ return "REAL";
+ else if (mc5Code.Contains("WORD") && mc5Code.Contains("RET_VAL"))
+ return "WORD";
+ else if (mc5Code.Contains("DWORD") && mc5Code.Contains("RET_VAL"))
+ return "DWORD";
+ else if (mc5Code.Contains("TIME") && mc5Code.Contains("RET_VAL"))
+ return "TIME";
+
+ // Buscar cualquier tipo de dato asociado a RET_VAL
+ int retValPos = mc5Code.IndexOf("RET_VAL");
+ if (retValPos > 0)
+ {
+ string[] dataTypes = { "BYTE", "DINT", "CHAR", "STRING", "DATE", "TIME_OF_DAY", "DATE_AND_TIME" };
+ foreach (string type in dataTypes)
+ {
+ if (mc5Code.IndexOf(type, retValPos - 50, 100) > 0)
+ return type;
+ }
+ }
+
+ return "VOID";
+ }
+
+ ///
+ /// Extrae información adicional del código MC5
+ ///
+ private void ExtractAdditionalInfoFromMC5Code(S7Function fc, string mc5Code)
+ {
+ if (string.IsNullOrEmpty(mc5Code))
+ return;
+
+ try
+ {
+ // Extraer descripción (comentario del bloque)
+ int descStart = mc5Code.IndexOf("//");
+ if (descStart >= 0)
+ {
+ int descEnd = mc5Code.IndexOf('\n', descStart);
+ if (descEnd > descStart)
+ {
+ string comment = mc5Code.Substring(descStart + 2, descEnd - descStart - 2).Trim();
+ if (!string.IsNullOrEmpty(comment))
+ fc.Description = DbfParser.ConvertCP1252ToUtf8(comment);
+ }
+ }
+
+ // Añadir otros metadatos que se puedan extraer
+ }
+ catch
+ {
+ // Ignorar errores en la extracción para no detener el proceso
+ }
+ }
+
+ ///
+ /// Analiza los datos de interfaz para extraer parámetros de entrada/salida
+ ///
+ private void AnalyzeInterfaceData(S7Function fc, string mc5Code)
+ {
+ if (string.IsNullOrEmpty(mc5Code))
+ return;
+
+ try
+ {
+ // Crear lista de parámetros
+ fc.Parameters = new List();
+
+ // Buscar sección de interfaz
+ int varInputPos = mc5Code.IndexOf("VAR_INPUT");
+ int varOutputPos = mc5Code.IndexOf("VAR_OUTPUT");
+ int varInOutPos = mc5Code.IndexOf("VAR_IN_OUT");
+
+ // Procesar parámetros de entrada
+ if (varInputPos >= 0)
+ {
+ int endPos = DetermineNextSectionPos(mc5Code, varInputPos);
+ string inputSection = mc5Code.Substring(varInputPos, endPos - varInputPos);
+ ExtractParameters(inputSection, "IN", fc.Parameters);
+ }
+
+ // Procesar parámetros de salida
+ if (varOutputPos >= 0)
+ {
+ int endPos = DetermineNextSectionPos(mc5Code, varOutputPos);
+ string outputSection = mc5Code.Substring(varOutputPos, endPos - varOutputPos);
+ ExtractParameters(outputSection, "OUT", fc.Parameters);
+ }
+
+ // Procesar parámetros de entrada/salida
+ if (varInOutPos >= 0)
+ {
+ int endPos = DetermineNextSectionPos(mc5Code, varInOutPos);
+ string inOutSection = mc5Code.Substring(varInOutPos, endPos - varInOutPos);
+ ExtractParameters(inOutSection, "IN/OUT", fc.Parameters);
+ }
+ }
+ catch
+ {
+ // Ignorar errores en la extracción para no detener el proceso
+ }
+ }
+
+ ///
+ /// Determina la posición de la siguiente sección en el código
+ ///
+ private int DetermineNextSectionPos(string code, int startPos)
+ {
+ string[] sections = { "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR", "BEGIN" };
+
+ int minPos = code.Length;
+ foreach (string section in sections)
+ {
+ int pos = code.IndexOf(section, startPos + 1);
+ if (pos > 0 && pos < minPos)
+ minPos = pos;
+ }
+
+ return minPos;
+ }
+
+ ///
+ /// Extrae parámetros individuales de una sección de interfaz
+ ///
+ private void ExtractParameters(string section, string direction, List parameters)
+ {
+ // Dividir por líneas
+ string[] lines = section.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (string line in lines)
+ {
+ string trimmedLine = line.Trim();
+
+ // Ignorar líneas de declaración de sección, comentarios o END_VAR
+ if (trimmedLine.StartsWith("VAR_") || trimmedLine.StartsWith("//") ||
+ trimmedLine.StartsWith("END_VAR") || string.IsNullOrWhiteSpace(trimmedLine))
+ continue;
+
+ // Procesar línea con declaración de parámetro
+ int colonPos = trimmedLine.IndexOf(':');
+ if (colonPos > 0)
+ {
+ string paramName = trimmedLine.Substring(0, colonPos).Trim();
+
+ // Extraer tipo y comentario
+ string remainder = trimmedLine.Substring(colonPos + 1).Trim();
+ string paramType = remainder;
+ string comment = string.Empty;
+
+ // Verificar si hay comentario en la línea
+ int commentPos = remainder.IndexOf("//");
+ if (commentPos > 0)
+ {
+ paramType = remainder.Substring(0, commentPos).Trim();
+ comment = remainder.Substring(commentPos + 2).Trim();
+ }
+
+ // Limpiar cualquier punto y coma al final del tipo
+ if (paramType.EndsWith(";"))
+ paramType = paramType.Substring(0, paramType.Length - 1).Trim();
+
+ // Añadir parámetro a la lista
+ parameters.Add(new FunctionParameter
+ {
+ Name = paramName,
+ DataType = paramType,
+ Direction = direction,
+ Description = comment
+ });
+ }
+ }
+ }
+
+ ///
+ /// Crea bloques FC de ejemplo cuando no se pueden parsear los reales
+ ///
+ private List CreateSampleFunctions()
+ {
+ return 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),
+ Language = "SCL",
+ Parameters = new List
+ {
+ new FunctionParameter { Name = "Target", DataType = "REAL", Direction = "IN", Description = "Valor objetivo" },
+ new FunctionParameter { Name = "Actual", DataType = "REAL", Direction = "IN", Description = "Valor actual" },
+ new FunctionParameter { Name = "Gain", DataType = "REAL", Direction = "IN", Description = "Ganancia" }
+ }
+ },
+ new S7Function {
+ Name = "Scale_Analog",
+ Number = "FC2",
+ Size = 68,
+ ReturnType = "REAL",
+ Description = "Escalado de valor analógico",
+ Modified = DateTime.Now.AddDays(-12),
+ Language = "AWL",
+ Parameters = new List
+ {
+ new FunctionParameter { Name = "Raw", DataType = "INT", Direction = "IN", Description = "Valor bruto" },
+ new FunctionParameter { Name = "RawLow", DataType = "INT", Direction = "IN", Description = "Valor mínimo bruto" },
+ new FunctionParameter { Name = "RawHigh", DataType = "INT", Direction = "IN", Description = "Valor máximo bruto" },
+ new FunctionParameter { Name = "ScaleLow", DataType = "REAL", Direction = "IN", Description = "Valor mínimo escalado" },
+ new FunctionParameter { Name = "ScaleHigh", DataType = "REAL", Direction = "IN", Description = "Valor máximo escalado" }
+ }
+ },
+ new S7Function {
+ Name = "CheckAlarms",
+ Number = "FC3",
+ Size = 154,
+ ReturnType = "BOOL",
+ Description = "Verificación de alarmas",
+ Modified = DateTime.Now.AddDays(-5),
+ Language = "KOP",
+ Parameters = new List
+ {
+ new FunctionParameter { Name = "Value", DataType = "REAL", Direction = "IN", Description = "Valor a comprobar" },
+ new FunctionParameter { Name = "LowLimit", DataType = "REAL", Direction = "IN", Description = "Límite inferior" },
+ new FunctionParameter { Name = "HighLimit", DataType = "REAL", Direction = "IN", Description = "Límite superior" },
+ new FunctionParameter { Name = "AlarmStatus", DataType = "WORD", Direction = "OUT", Description = "Estado de alarmas" }
+ }
+ },
+ new S7Function {
+ Name = "Timer_Control",
+ Number = "FC4",
+ Size = 86,
+ ReturnType = "VOID",
+ Description = "Control de temporizadores",
+ Modified = DateTime.Now.AddDays(-3),
+ Language = "FUP",
+ Parameters = new List
+ {
+ new FunctionParameter { Name = "Start", DataType = "BOOL", Direction = "IN", Description = "Iniciar temporizador" },
+ new FunctionParameter { Name = "Duration", DataType = "TIME", Direction = "IN", Description = "Duración" },
+ new FunctionParameter { Name = "TimerNo", DataType = "INT", Direction = "IN", Description = "Número de temporizador" },
+ new FunctionParameter { Name = "Status", DataType = "BOOL", Direction = "OUT", Description = "Estado (activo/inactivo)" },
+ new FunctionParameter { Name = "ElapsedTime", DataType = "TIME", Direction = "OUT", Description = "Tiempo transcurrido" }
+ }
+ },
+ new S7Function {
+ Name = "String_Process",
+ Number = "FC5",
+ Size = 210,
+ ReturnType = "INT",
+ Description = "Procesamiento de cadenas",
+ Modified = DateTime.Now.AddDays(-1),
+ Language = "SCL",
+ Parameters = new List
+ {
+ new FunctionParameter { Name = "InStr", DataType = "STRING", Direction = "IN", Description = "Cadena de entrada" },
+ new FunctionParameter { Name = "Operation", DataType = "INT", Direction = "IN", Description = "Operación a realizar" },
+ new FunctionParameter { Name = "OutStr", DataType = "STRING", Direction = "OUT", Description = "Cadena de salida" }
+ }
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Parsers/S7ProjectParser.cs b/Parsers/S7ProjectParser.cs
index ef598b5..062af74 100644
--- a/Parsers/S7ProjectParser.cs
+++ b/Parsers/S7ProjectParser.cs
@@ -225,14 +225,56 @@ namespace S7Explorer.Parsers
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
+ try
+ {
+ // Usar parser específico para las funciones (FC)
+ var fcParser = new FCParser(_projectDirectory);
+ var functions = fcParser.ParseFunctionBlocks(subblockListId);
- // Crear algunos bloques de ejemplo
- AddSampleDataBlocks(dbFolder);
- AddSampleFunctionBlocks(fbFolder);
- AddSampleFunctions(fcFolder);
- AddSampleOrgBlocks(obFolder);
+ // 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)
diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs
index aeb6e00..f01a2f4 100644
--- a/ViewModels/MainViewModel.cs
+++ b/ViewModels/MainViewModel.cs
@@ -46,10 +46,38 @@ namespace S7Explorer.ViewModels
{
// Actualizar el objeto seleccionado para PropertyGrid
SelectedObject = _selectedTreeItem;
+
+ // Acciones específicas según el tipo de objeto seleccionado
+ if (_selectedTreeItem is S7Function fc)
+ {
+ // Si el objeto seleccionado es una función, actualizar su interfaz
+ UpdateFunctionInterface(fc);
+ }
}
}
}
+ ///
+ /// Actualiza la información de interfaz de una función seleccionada
+ ///
+ private void UpdateFunctionInterface(S7Function fc)
+ {
+ try
+ {
+ // Aquí podríamos cargar información adicional específica de la función
+ // Por ejemplo, cargar el código de la función, detalles de parámetros, etc.
+
+ // Actualizar estadísticas o análisis de la función
+ // Por ejemplo, mostrar uso de la función en otros bloques
+
+ // Este método se ejecutará cuando el usuario seleccione una FC en el árbol
+ }
+ catch (Exception)
+ {
+ // Ignorar errores para no interrumpir la experiencia del usuario
+ }
+ }
+
public ObservableCollection ProjectStructure
{
get => _projectStructure;
@@ -138,8 +166,61 @@ namespace S7Explorer.ViewModels
// Recopilar todos los objetos en el proyecto
var allObjects = GetAllObjects(_currentProject);
+ // Lista para almacenar coincidencias
+ var matchingObjects = new List();
+
+ // Buscar en función del texto de búsqueda
+ string searchText = SearchText.Trim().ToLowerInvariant();
+
// Buscar objetos que coincidan con el texto
- var matchingObjects = allObjects.Where(o => o.ContainsText(SearchText)).ToList();
+ foreach (var obj in allObjects)
+ {
+ // Comprobar si el texto de búsqueda parece ser una referencia a FC específica
+ if (searchText.StartsWith("fc") && searchText.Length >= 3)
+ {
+ // Si es una función específica (por ejemplo "fc5")
+ if (obj is S7Function func &&
+ func.Number.ToLowerInvariant() == searchText)
+ {
+ matchingObjects.Add(func);
+ }
+ }
+ // Comprobar si queremos encontrar todas las FCs
+ else if (searchText == "fc" || searchText == "función" || searchText == "funcion")
+ {
+ if (obj is S7Function)
+ {
+ matchingObjects.Add(obj);
+ }
+ }
+ // Búsqueda en parámetros de función
+ else if (obj is S7Function func)
+ {
+ if (func.ContainsText(searchText))
+ {
+ matchingObjects.Add(func);
+ }
+ else if (func.Parameters != null)
+ {
+ // Buscar en los parámetros de la función
+ foreach (var param in func.Parameters)
+ {
+ if (param.Name.ToLowerInvariant().Contains(searchText) ||
+ param.DataType.ToLowerInvariant().Contains(searchText) ||
+ (param.Description?.ToLowerInvariant().Contains(searchText) ?? false))
+ {
+ matchingObjects.Add(func);
+ break;
+ }
+ }
+ }
+ }
+ // Búsqueda general para otros objetos
+ else if (obj.ContainsText(searchText))
+ {
+ matchingObjects.Add(obj);
+ }
+ }
if (matchingObjects.Count == 0)
{
diff --git a/Views/MainWindow.xaml.cs b/Views/MainWindow.xaml.cs
index fbc3f4d..271a412 100644
--- a/Views/MainWindow.xaml.cs
+++ b/Views/MainWindow.xaml.cs
@@ -23,6 +23,8 @@ namespace S7Explorer.Views
}
}
+
+
private void SearchBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && ViewModel != null)