Segunda Version con FC, FB y OB

This commit is contained in:
Miguel 2025-03-24 16:52:18 +01:00
parent 8356c6d2f5
commit ea96ba2bb9
8 changed files with 903 additions and 52 deletions

69
LinqExtensions.cs Normal file
View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace S7Explorer
{
/// <summary>
/// Extensiones LINQ para funcionalidades específicas de la aplicación
/// </summary>
public static class LinqExtensions
{
/// <summary>
/// Cuenta elementos en una colección que cumplen un predicado
/// </summary>
public static int Count<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null)
return 0;
return source.Where(predicate).Count();
}
/// <summary>
/// Verifica si una colección contiene al menos un elemento
/// </summary>
public static bool Any<T>(this IEnumerable<T> source)
{
if (source == null)
return false;
return source.Any(item => true);
}
/// <summary>
/// Verifica si una colección contiene al menos un elemento que cumpla un predicado
/// </summary>
public static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null)
return false;
using (var enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
if (predicate(enumerator.Current))
return true;
}
}
return false;
}
/// <summary>
/// Selecciona elementos de una colección que cumplen un predicado
/// </summary>
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null)
yield break;
foreach (var item in source)
{
if (predicate(item))
yield return item;
}
}
}
}

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using Newtonsoft.Json;
namespace S7Explorer.Models namespace S7Explorer.Models
{ {
@ -10,6 +13,7 @@ namespace S7Explorer.Models
private string _version; private string _version;
private DateTime? _modified; private DateTime? _modified;
private int _size; private int _size;
private string _language;
[DisplayName("Autor")] [DisplayName("Autor")]
public string AuthorName 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() public S7Block()
{ {
// Establece valores predeterminados específicos para bloques // Establece valores predeterminados específicos para bloques
@ -113,49 +131,20 @@ namespace S7Explorer.Models
public class S7FunctionBlock : S7Block 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() public S7FunctionBlock()
{ {
ObjectType = S7ObjectType.FunctionBlock; ObjectType = S7ObjectType.FunctionBlock;
} }
} }
public class S7Function : S7Block /// <summary>
/// Clase para representar un parámetro de función
/// </summary>
public class FunctionParameter
{ {
private string _returnType; public string Name { get; set; }
public string DataType { get; set; }
[DisplayName("Tipo de Retorno")] public string Direction { get; set; } // IN, OUT, IN/OUT
public string ReturnType public string Description { get; set; }
{
get => _returnType;
set
{
if (_returnType != value)
{
_returnType = value;
OnPropertyChanged(nameof(ReturnType));
}
}
}
public S7Function()
{
ObjectType = S7ObjectType.Function;
}
} }
} }

169
Models/S7Function.cs Normal file
View File

@ -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<FunctionParameter> _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<FunctionParameter> 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<FunctionParameter>();
}
/// <summary>
/// Actualiza la representación formateada de la interfaz basada en los parámetros
/// </summary>
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();
}
}
}

View File

@ -18,7 +18,7 @@ namespace S7Explorer.Parsers
try 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 stream = File.OpenRead(filePath);
using var table = Table.Open(stream); using var table = Table.Open(stream);
@ -33,14 +33,23 @@ namespace S7Explorer.Parsers
{ {
// Obtener valor y convertir a string si no es null // Obtener valor y convertir a string si no es null
var value = reader.GetValue(fieldName); var value = reader.GetValue(fieldName);
record[fieldName] = value?.ToString() ?? string.Empty;
// Manejamos específicamente bytes para campos como "MC5CODE" // Manejar los diferentes tipos de datos
// que pueden contener datos binarios codificados como CP1252
if (value is byte[] byteValue) if (value is byte[] byteValue)
{ {
// Para campos de tipo binario como MC5CODE
record[fieldName] = Encoding.GetEncoding(1252).GetString(byteValue); 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); result.Add(record);
@ -60,7 +69,21 @@ namespace S7Explorer.Parsers
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
return null; 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 result;
return null; return null;
@ -72,8 +95,45 @@ namespace S7Explorer.Parsers
if (string.IsNullOrEmpty(input)) if (string.IsNullOrEmpty(input))
return string.Empty; return string.Empty;
byte[] bytes = Encoding.GetEncoding(1252).GetBytes(input); try
return Encoding.UTF8.GetString(bytes); {
// 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;
} }
} }
} }

439
Parsers/FCParser.cs Normal file
View File

@ -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
{
/// <summary>
/// Parser específico para los bloques de función (FC) de STEP7
/// </summary>
public class FCParser
{
private readonly string _projectDirectory;
public FCParser(string projectDirectory)
{
_projectDirectory = projectDirectory;
}
/// <summary>
/// Parsea los bloques FC desde los archivos SUBBLK.DBF del proyecto
/// </summary>
/// <param name="subblockListId">ID de la lista de subbloques del dispositivo</param>
/// <returns>Lista de objetos S7Function representando los bloques FC</returns>
public List<S7Function> ParseFunctionBlocks(int subblockListId)
{
var functions = new List<S7Function>();
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;
}
/// <summary>
/// Extrae el número de un bloque a partir de su identificador (ej: "FC5" -> 5)
/// </summary>
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;
}
/// <summary>
/// Parsea el tamaño del bloque a partir del valor MC5LEN
/// </summary>
private int ParseBlockSize(string mc5Len)
{
if (int.TryParse(mc5Len, out int size))
return size;
return 0;
}
/// <summary>
/// Determina el lenguaje de programación a partir del código MC5
/// </summary>
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";
}
/// <summary>
/// Determina el tipo de retorno a partir del código MC5
/// </summary>
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";
}
/// <summary>
/// Extrae información adicional del código MC5
/// </summary>
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
}
}
/// <summary>
/// Analiza los datos de interfaz para extraer parámetros de entrada/salida
/// </summary>
private void AnalyzeInterfaceData(S7Function fc, string mc5Code)
{
if (string.IsNullOrEmpty(mc5Code))
return;
try
{
// Crear lista de parámetros
fc.Parameters = new List<FunctionParameter>();
// 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
}
}
/// <summary>
/// Determina la posición de la siguiente sección en el código
/// </summary>
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;
}
/// <summary>
/// Extrae parámetros individuales de una sección de interfaz
/// </summary>
private void ExtractParameters(string section, string direction, List<FunctionParameter> 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
});
}
}
}
/// <summary>
/// Crea bloques FC de ejemplo cuando no se pueden parsear los reales
/// </summary>
private List<S7Function> CreateSampleFunctions()
{
return 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),
Language = "SCL",
Parameters = new List<FunctionParameter>
{
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<FunctionParameter>
{
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<FunctionParameter>
{
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<FunctionParameter>
{
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<FunctionParameter>
{
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" }
}
}
};
}
}
}

View File

@ -225,14 +225,56 @@ namespace S7Explorer.Parsers
private void ParseBlocks(S7Object dbFolder, S7Object fbFolder, S7Object fcFolder, S7Object obFolder, int subblockListId) private void ParseBlocks(S7Object dbFolder, S7Object fbFolder, S7Object fcFolder, S7Object obFolder, int subblockListId)
{ {
// Esta es una implementación de muestra try
// En una versión completa, leerías los archivos SUBBLK.DBF reales {
// Usar parser específico para las funciones (FC)
var fcParser = new FCParser(_projectDirectory);
var functions = fcParser.ParseFunctionBlocks(subblockListId);
// Crear algunos bloques de ejemplo // Añadir bloques FC al árbol de proyecto
AddSampleDataBlocks(dbFolder); foreach (var fc in functions)
AddSampleFunctionBlocks(fbFolder); {
AddSampleFunctions(fcFolder); fc.Parent = fcFolder;
AddSampleOrgBlocks(obFolder); 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) private void AddSampleDataBlocks(S7Object dbFolder)

View File

@ -46,10 +46,38 @@ namespace S7Explorer.ViewModels
{ {
// Actualizar el objeto seleccionado para PropertyGrid // Actualizar el objeto seleccionado para PropertyGrid
SelectedObject = _selectedTreeItem; 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);
}
} }
} }
} }
/// <summary>
/// Actualiza la información de interfaz de una función seleccionada
/// </summary>
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<S7Object> ProjectStructure public ObservableCollection<S7Object> ProjectStructure
{ {
get => _projectStructure; get => _projectStructure;
@ -138,8 +166,61 @@ namespace S7Explorer.ViewModels
// Recopilar todos los objetos en el proyecto // Recopilar todos los objetos en el proyecto
var allObjects = GetAllObjects(_currentProject); var allObjects = GetAllObjects(_currentProject);
// Lista para almacenar coincidencias
var matchingObjects = new List<S7Object>();
// Buscar en función del texto de búsqueda
string searchText = SearchText.Trim().ToLowerInvariant();
// Buscar objetos que coincidan con el texto // 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) if (matchingObjects.Count == 0)
{ {

View File

@ -23,6 +23,8 @@ namespace S7Explorer.Views
} }
} }
private void SearchBox_KeyDown(object sender, KeyEventArgs e) private void SearchBox_KeyDown(object sender, KeyEventArgs e)
{ {
if (e.Key == Key.Enter && ViewModel != null) if (e.Key == Key.Enter && ViewModel != null)