861 lines
31 KiB
C#
861 lines
31 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Siemens.Simatic.Simulation.Runtime;
|
|
using Newtonsoft.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.ComponentModel;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.Diagnostics;
|
|
|
|
namespace LibS7Adv
|
|
{
|
|
[DisplayName("PLC Advanced Setup:")]
|
|
public partial class PlcData : ObservableObject
|
|
{
|
|
[ObservableProperty]
|
|
[property: Description("Type of connection driver")]
|
|
[property: Category("Connection:")]
|
|
ConnectionType connectionType = ConnectionType.AdvCoSimulator;
|
|
|
|
[ObservableProperty]
|
|
[property: Description("IP of the PLC")]
|
|
[property: Category("Connection:")]
|
|
string iP = "10.1.30.11";
|
|
|
|
[ObservableProperty]
|
|
[property: Description("Name of the instance at the PLC Advanced")]
|
|
[property: Category("Connection:")]
|
|
string name = "PLC Name";
|
|
|
|
[ObservableProperty]
|
|
[property: Description("CPU Info")]
|
|
[property: Category("Status:")]
|
|
[property: ReadOnly(true)]
|
|
string cpuTime = "";
|
|
|
|
[ObservableProperty]
|
|
[property: Display(Name = "Status")]
|
|
[property: Description("CPU Status")]
|
|
[property: Category("Status:")]
|
|
[property: ReadOnly(true)]
|
|
string connectionStatus = "";
|
|
|
|
[ObservableProperty]
|
|
[property: Description("API Error")]
|
|
[property: Category("Status:")]
|
|
[property: ReadOnly(true)]
|
|
string lastError = "";
|
|
}
|
|
|
|
|
|
|
|
public partial class PLCViewModel : ObservableObject
|
|
{
|
|
[JsonIgnore]
|
|
private IPlcConnection? _connection;
|
|
|
|
[ObservableProperty]
|
|
PlcData plcData = new PlcData();
|
|
|
|
public PLCViewModel()
|
|
{
|
|
// Configurar la conexión inicial
|
|
_connection = CreateConnection(PlcData.ConnectionType);
|
|
}
|
|
|
|
private IPlcConnection CreateConnection(ConnectionType connectionType)
|
|
{
|
|
try
|
|
{
|
|
return connectionType switch
|
|
{
|
|
ConnectionType.AdvCoSimulator => new AdvCoSimulatorConnection(),
|
|
ConnectionType.Sharp7 => new Sharp7Connection(),
|
|
_ => throw new ArgumentException("Invalid connection type")
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"Error creating {connectionType} connection: {ex.Message}";
|
|
// Fallback a AdvCoSimulator si hay problemas con Snap7
|
|
if (connectionType == ConnectionType.Sharp7)
|
|
{
|
|
PlcData.LastError += " Falling back to AdvCoSimulator.";
|
|
PlcData.ConnectionType = ConnectionType.AdvCoSimulator;
|
|
return new AdvCoSimulatorConnection();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
partial void OnPlcDataChanged(PlcData value)
|
|
{
|
|
// Si cambió el tipo de conexión, recreamos la conexión
|
|
if (_connection?.ConnectionType != value.ConnectionType)
|
|
{
|
|
if (_connection?.IsConnected == true)
|
|
{
|
|
Disconnect();
|
|
}
|
|
_connection = CreateConnection(value.ConnectionType);
|
|
}
|
|
}
|
|
|
|
public void ucLoaded()
|
|
{
|
|
IsConnected = false;
|
|
PlcData.LastError = "";
|
|
PlcData.ConnectionStatus = "offline";
|
|
}
|
|
|
|
[ObservableProperty]
|
|
[property: JsonIgnore]
|
|
bool isConnected;
|
|
|
|
[RelayCommand]
|
|
[property: JsonIgnore]
|
|
public void ResetAlarmButton()
|
|
{
|
|
PlcData.LastError = "";
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
[property: JsonIgnore]
|
|
public void ConnectButton()
|
|
{
|
|
if (IsConnected)
|
|
Disconnect();
|
|
else
|
|
Connect();
|
|
}
|
|
|
|
public void Connect()
|
|
{
|
|
if (IsConnected)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (_connection == null)
|
|
{
|
|
_connection = CreateConnection(PlcData.ConnectionType);
|
|
}
|
|
|
|
// Verificación especial para Snap7
|
|
// Para Sharp7 no necesitamos validar DLL externa ya que es una librería C# administrada
|
|
|
|
bool success = _connection.Connect(PlcData.IP, PlcData.Name);
|
|
|
|
if (success)
|
|
{
|
|
IsConnected = true;
|
|
PlcData.ConnectionStatus = _connection.Status;
|
|
PlcData.LastError = "";
|
|
}
|
|
else
|
|
{
|
|
IsConnected = false;
|
|
PlcData.ConnectionStatus = "offline";
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = ex.Message;
|
|
PlcData.ConnectionStatus = "offline";
|
|
IsConnected = false;
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
_connection?.Disconnect();
|
|
IsConnected = false;
|
|
PlcData.ConnectionStatus = "offline";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obtiene información de diagnóstico sobre los drivers disponibles
|
|
/// </summary>
|
|
/// <returns>Información de estado de los drivers</returns>
|
|
public string GetDriverDiagnostics()
|
|
{
|
|
var diagnostics = new System.Text.StringBuilder();
|
|
|
|
diagnostics.AppendLine("=== Driver Diagnostics ===");
|
|
diagnostics.AppendLine($"Current Driver: {PlcData.ConnectionType}");
|
|
diagnostics.AppendLine($"Connection Status: {PlcData.ConnectionStatus}");
|
|
diagnostics.AppendLine($"Last Error: {PlcData.LastError}");
|
|
diagnostics.AppendLine();
|
|
|
|
// Diagnóstico AdvCoSimulator
|
|
diagnostics.AppendLine("AdvCoSimulator Status:");
|
|
try
|
|
{
|
|
var advConnection = new AdvCoSimulatorConnection();
|
|
diagnostics.AppendLine("✓ AdvCoSimulator driver available");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
diagnostics.AppendLine($"✗ AdvCoSimulator error: {ex.Message}");
|
|
}
|
|
|
|
diagnostics.AppendLine();
|
|
|
|
// Diagnóstico Sharp7
|
|
diagnostics.AppendLine("Sharp7 Status:");
|
|
diagnostics.AppendLine(" Status: ✓ C# Managed Library (no external DLL required)");
|
|
diagnostics.AppendLine(" Available: ✓ Yes");
|
|
|
|
return diagnostics.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mapea un tag a su dirección correspondiente para Sharp7.
|
|
/// IMPORTANTE: Esta función solo se debe usar con Sharp7, NO con AdvCoSimulator.
|
|
/// - Para Sharp7: Normaliza diferentes notaciones de direcciones PLC y retorna formato compatible.
|
|
/// - Para AdvCoSimulator: No usar esta función, usar los tags directamente.
|
|
///
|
|
/// Soporta múltiples notaciones:
|
|
/// - Alemán vs Americano: E/A vs I/O
|
|
/// - Con/Sin P: PEW vs EW, PIW vs IW
|
|
/// - Diferentes formatos: IW0, PIW0, EW0, PEW0
|
|
///
|
|
/// En el futuro se puede implementar una tabla de mapeo para convertir tags simbólicos a direcciones absolutas.
|
|
/// </summary>
|
|
/// <param name="tag">El tag o dirección a mapear (solo para Sharp7)</param>
|
|
/// <returns>La dirección absoluta correspondiente normalizada para Sharp7</returns>
|
|
/// <example>
|
|
/// Ejemplos de normalización:
|
|
/// <code>
|
|
/// "PEW0" → "IW0" (Alemán con P → Americano)
|
|
/// "EW0" → "IW0" (Alemán → Americano)
|
|
/// "PIW0" → "IW0" (Con P → Sin P)
|
|
/// "PAW0" → "QW0" (Alemán con P → Americano)
|
|
/// "AW0" → "QW0" (Alemán → Americano)
|
|
/// "POW0" → "QW0" (Con P → Sin P)
|
|
/// </code>
|
|
///
|
|
/// Ejemplo de uso futuro con tabla de mapeo:
|
|
/// <code>
|
|
/// // Implementación futura:
|
|
/// private Dictionary<string, string> _tagMappingTable = new Dictionary<string, string>
|
|
/// {
|
|
/// { "MotorStart", "DB1.DBX0.0" },
|
|
/// { "MotorSpeed", "DB1.DBW2" },
|
|
/// { "Temperature", "DB2.DBD4" }
|
|
/// };
|
|
/// </code>
|
|
/// </example>
|
|
private string MapTagToAddress(string tag)
|
|
{
|
|
// TODO: En el futuro, implementar búsqueda en tabla de mapeo
|
|
// if (_tagMappingTable?.ContainsKey(tag) == true)
|
|
// {
|
|
// return _tagMappingTable[tag];
|
|
// }
|
|
|
|
// Normalizar direcciones PLC para compatibilidad con Sharp7
|
|
string normalizedAddress = NormalizePlcAddress(tag);
|
|
|
|
return normalizedAddress;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normaliza direcciones PLC de diferentes notaciones a formato compatible con Sharp7
|
|
/// </summary>
|
|
/// <param name="address">Dirección original en cualquier formato</param>
|
|
/// <returns>Dirección normalizada para Sharp7</returns>
|
|
private string NormalizePlcAddress(string address)
|
|
{
|
|
if (string.IsNullOrEmpty(address))
|
|
return address;
|
|
|
|
// Convertir a mayúsculas para normalización
|
|
string upperAddress = address.ToUpper();
|
|
|
|
// === NORMALIZACIÓN DE NOTACIONES INPUTS/OUTPUTS ===
|
|
|
|
// PEW (Alemán con P) → IW (Americano)
|
|
if (upperAddress.StartsWith("PEW"))
|
|
return upperAddress.Replace("PEW", "IW");
|
|
|
|
// PIW (Americano con P) → IW (Americano sin P)
|
|
if (upperAddress.StartsWith("PIW"))
|
|
return upperAddress.Replace("PIW", "IW");
|
|
|
|
// EW (Alemán) → IW (Americano)
|
|
if (upperAddress.StartsWith("EW"))
|
|
return upperAddress.Replace("EW", "IW");
|
|
|
|
// PEB (Alemán con P) → IB (Americano)
|
|
if (upperAddress.StartsWith("PEB"))
|
|
return upperAddress.Replace("PEB", "IB");
|
|
|
|
// PIB (Americano con P) → IB (Americano sin P)
|
|
if (upperAddress.StartsWith("PIB"))
|
|
return upperAddress.Replace("PIB", "IB");
|
|
|
|
// EB (Alemán) → IB (Americano)
|
|
if (upperAddress.StartsWith("EB"))
|
|
return upperAddress.Replace("EB", "IB");
|
|
|
|
// PED (Alemán con P) → ID (Americano)
|
|
if (upperAddress.StartsWith("PED"))
|
|
return upperAddress.Replace("PED", "ID");
|
|
|
|
// PID (Americano con P) → ID (Americano sin P)
|
|
if (upperAddress.StartsWith("PID"))
|
|
return upperAddress.Replace("PID", "ID");
|
|
|
|
// ED (Alemán) → ID (Americano)
|
|
if (upperAddress.StartsWith("ED"))
|
|
return upperAddress.Replace("ED", "ID");
|
|
|
|
// === OUTPUTS ===
|
|
|
|
// PAW (Alemán con P) → QW (Americano)
|
|
if (upperAddress.StartsWith("PAW"))
|
|
return upperAddress.Replace("PAW", "QW");
|
|
|
|
// POW (Americano con P) → QW (Americano sin P)
|
|
if (upperAddress.StartsWith("POW"))
|
|
return upperAddress.Replace("POW", "QW");
|
|
|
|
// AW (Alemán) → QW (Americano)
|
|
if (upperAddress.StartsWith("AW"))
|
|
return upperAddress.Replace("AW", "QW");
|
|
|
|
// PAB (Alemán con P) → QB (Americano)
|
|
if (upperAddress.StartsWith("PAB"))
|
|
return upperAddress.Replace("PAB", "QB");
|
|
|
|
// POB (Americano con P) → QB (Americano sin P)
|
|
if (upperAddress.StartsWith("POB"))
|
|
return upperAddress.Replace("POB", "QB");
|
|
|
|
// AB (Alemán) → QB (Americano)
|
|
if (upperAddress.StartsWith("AB"))
|
|
return upperAddress.Replace("AB", "QB");
|
|
|
|
// PAD (Alemán con P) → QD (Americano)
|
|
if (upperAddress.StartsWith("PAD"))
|
|
return upperAddress.Replace("PAD", "QD");
|
|
|
|
// POD (Americano con P) → QD (Americano sin P)
|
|
if (upperAddress.StartsWith("POD"))
|
|
return upperAddress.Replace("POD", "QD");
|
|
|
|
// AD (Alemán) → QD (Americano)
|
|
if (upperAddress.StartsWith("AD"))
|
|
return upperAddress.Replace("AD", "QD");
|
|
|
|
// === BITS (con punto) ===
|
|
|
|
// PE0.0 (Alemán con P) → I0.0 (Americano)
|
|
if (upperAddress.StartsWith("PE") && upperAddress.Contains("."))
|
|
return upperAddress.Replace("PE", "I");
|
|
|
|
// PI0.0 (Americano con P) → I0.0 (Americano sin P)
|
|
if (upperAddress.StartsWith("PI") && upperAddress.Contains("."))
|
|
return upperAddress.Replace("PI", "I");
|
|
|
|
// E0.0 (Alemán) → I0.0 (Americano)
|
|
if (upperAddress.StartsWith("E") && upperAddress.Contains(".") && !upperAddress.StartsWith("EB") && !upperAddress.StartsWith("EW") && !upperAddress.StartsWith("ED"))
|
|
return upperAddress.Replace("E", "I");
|
|
|
|
// PA0.0 (Alemán con P) → Q0.0 (Americano)
|
|
if (upperAddress.StartsWith("PA") && upperAddress.Contains("."))
|
|
return upperAddress.Replace("PA", "Q");
|
|
|
|
// PO0.0 (Americano con P) → Q0.0 (Americano sin P)
|
|
if (upperAddress.StartsWith("PO") && upperAddress.Contains("."))
|
|
return upperAddress.Replace("PO", "Q");
|
|
|
|
// A0.0 (Alemán) → Q0.0 (Americano)
|
|
if (upperAddress.StartsWith("A") && upperAddress.Contains(".") && !upperAddress.StartsWith("AB") && !upperAddress.StartsWith("AW") && !upperAddress.StartsWith("AD"))
|
|
return upperAddress.Replace("A", "Q");
|
|
|
|
// Si no necesita normalización, devolver tal como está
|
|
return upperAddress;
|
|
}
|
|
|
|
|
|
public bool? LeerBool(string sTag)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(sTag)
|
|
: sTag;
|
|
|
|
var result = _connection?.ReadBool(address);
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{sTag}: {ex.Message}";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool? EscribirBool(string sTag, bool Value)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(sTag)
|
|
: sTag;
|
|
|
|
var result = _connection?.WriteBool(address, Value);
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{sTag}: {ex.Message}";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public string? LeerNumber(string sTag, bool Signed = false)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(sTag)
|
|
: sTag;
|
|
|
|
var result = _connection?.ReadNumber(address, Signed);
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{sTag}: {ex.Message}";
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public bool EscribirNumber(string sTag, object value)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(sTag)
|
|
: sTag;
|
|
|
|
var result = _connection?.WriteNumber(address, value) ?? false;
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{sTag}: {ex.Message}";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool LeerSalidaBool(byte pByte, int pBit)
|
|
{
|
|
try
|
|
{
|
|
var result = _connection?.ReadOutputBool(pByte, pBit) ?? false;
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"Byte {pByte}, Bit {pBit}: {ex.Message}";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void EscribirInputBool(byte pByte, int pBit, bool pValue)
|
|
{
|
|
try
|
|
{
|
|
_connection?.WriteInputBool(pByte, pBit, pValue);
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"Byte {pByte}, Bit {pBit}: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public void EscribirTag(string pTag, SDataValue Value)
|
|
{
|
|
try
|
|
{
|
|
if (_connection?.ConnectionType == ConnectionType.AdvCoSimulator)
|
|
{
|
|
// AdvCoSimulator: usar tag directamente con SDataValue
|
|
_connection?.WriteTag(pTag, Value);
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
}
|
|
else if (_connection?.ConnectionType == ConnectionType.Sharp7)
|
|
{
|
|
// Sharp7: mapear tag y convertir SDataValue a función específica
|
|
string address = MapTagToAddress(pTag);
|
|
|
|
// Detectar el tipo del SDataValue y usar la función correspondiente
|
|
switch (Value.Type)
|
|
{
|
|
case EPrimitiveDataType.Bool:
|
|
EscribirBool(address, Value.Bool);
|
|
break;
|
|
case EPrimitiveDataType.Int8:
|
|
case EPrimitiveDataType.UInt8:
|
|
EscribirNumber(address, Value.UInt8);
|
|
break;
|
|
case EPrimitiveDataType.Int16:
|
|
EscribirNumber(address, Value.Int16);
|
|
break;
|
|
case EPrimitiveDataType.UInt16:
|
|
EscribirNumber(address, Value.UInt16);
|
|
break;
|
|
case EPrimitiveDataType.Int32:
|
|
EscribirNumber(address, Value.Int32);
|
|
break;
|
|
case EPrimitiveDataType.UInt32:
|
|
EscribirNumber(address, Value.UInt32);
|
|
break;
|
|
case EPrimitiveDataType.Float:
|
|
EscribirNumber(address, Value.Float);
|
|
break;
|
|
case EPrimitiveDataType.Double:
|
|
EscribirNumber(address, Value.Double);
|
|
break;
|
|
default:
|
|
PlcData.LastError = $"Unsupported SDataValue type: {Value.Type} for Sharp7";
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PlcData.LastError = "No connection available";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public SDataValue LeerTag(string pTag)
|
|
{
|
|
try
|
|
{
|
|
if (_connection?.ConnectionType == ConnectionType.AdvCoSimulator)
|
|
{
|
|
// AdvCoSimulator: usar tag directamente con SDataValue
|
|
var result = _connection?.ReadTag(pTag);
|
|
if (_connection != null)
|
|
{
|
|
PlcData.LastError = _connection.LastError;
|
|
}
|
|
return result as SDataValue? ?? new SDataValue();
|
|
}
|
|
else if (_connection?.ConnectionType == ConnectionType.Sharp7)
|
|
{
|
|
// Sharp7: no soporta SDataValue directamente
|
|
// El usuario debería usar funciones específicas como LeerBool, LeerNumber
|
|
PlcData.LastError = "LeerTag with SDataValue is not supported with Sharp7. Use specific type methods like LeerBool, LeerNumber.";
|
|
return new SDataValue();
|
|
}
|
|
else
|
|
{
|
|
PlcData.LastError = "No connection available";
|
|
return new SDataValue();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
return new SDataValue();
|
|
}
|
|
}
|
|
|
|
public void EscribirTagBool(string pTag, bool pValue)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(pTag)
|
|
: pTag;
|
|
|
|
// Usar directamente la función unificada EscribirBool
|
|
EscribirBool(address, pValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public void EscribirTagInt16(string pTag, int pValue)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(pTag)
|
|
: pTag;
|
|
|
|
// Usar directamente la función unificada EscribirNumber
|
|
EscribirNumber(address, pValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public bool LeerTagBool(string pTag)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(pTag)
|
|
: pTag;
|
|
|
|
// Usar directamente la función unificada LeerBool
|
|
return LeerBool(address) ?? false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int? LeerTagInt16(string pTag)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(pTag)
|
|
: pTag;
|
|
|
|
// Usar directamente la función unificada LeerNumber
|
|
string? result = LeerNumber(address);
|
|
if (int.TryParse(result, out int value))
|
|
{
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public int? LeerTagDInt(string pTag)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(pTag)
|
|
: pTag;
|
|
|
|
// Usar directamente la función unificada LeerNumber
|
|
string? result = LeerNumber(address);
|
|
if (int.TryParse(result, out int value))
|
|
{
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void EscribirTagDInt(string pTag, int pValue)
|
|
{
|
|
try
|
|
{
|
|
// Para Sharp7: mapear tag a dirección absoluta
|
|
// Para AdvCoSimulator: usar tag directamente
|
|
string address = _connection?.ConnectionType == ConnectionType.Sharp7
|
|
? MapTagToAddress(pTag)
|
|
: pTag;
|
|
|
|
// Usar directamente la función unificada EscribirNumber
|
|
EscribirNumber(address, pValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PlcData.LastError = $"{pTag}: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public static TagAddress? ParseTagAddress(string tag)
|
|
{
|
|
TagAddress tagAddress = new TagAddress();
|
|
if (tag == null)
|
|
return null;
|
|
if (tag.StartsWith("%"))
|
|
{
|
|
if (tag.StartsWith("%DB"))
|
|
{
|
|
tagAddress.areaType = EArea.DataBlock;
|
|
var match = Regex.Match(tag, @"^%DB(\d+)\.DB([X|B|W|D])(\d+)\.(\d+)$");
|
|
|
|
if (match.Success)
|
|
{
|
|
tagAddress.DB = int.Parse(match.Groups[1].Value);
|
|
char dataTypeChar = match.Groups[2].Value[0];
|
|
tagAddress.word_offset = uint.Parse(match.Groups[3].Value);
|
|
tagAddress.bit = byte.Parse(match.Groups[4].Value);
|
|
|
|
switch (dataTypeChar)
|
|
{
|
|
case 'X':
|
|
tagAddress.tagType = EDataType.Bool;
|
|
break;
|
|
case 'B':
|
|
tagAddress.tagType = EDataType.Byte;
|
|
break;
|
|
case 'W':
|
|
tagAddress.tagType = EDataType.Word;
|
|
break;
|
|
case 'D':
|
|
tagAddress.tagType = EDataType.DWord;
|
|
break;
|
|
default:
|
|
tagAddress.tagType = EDataType.Unknown;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (tag.StartsWith("%M"))
|
|
{
|
|
tagAddress.areaType = EArea.Marker;
|
|
ParseNonDBAddress(tag, tagAddress);
|
|
}
|
|
else if (tag.StartsWith("%E"))
|
|
{
|
|
tagAddress.areaType = EArea.Input;
|
|
ParseNonDBAddress(tag, tagAddress);
|
|
}
|
|
else if (tag.StartsWith("%A"))
|
|
{
|
|
tagAddress.areaType = EArea.Output;
|
|
ParseNonDBAddress(tag, tagAddress);
|
|
}
|
|
else
|
|
{
|
|
tagAddress.areaType = EArea.InvalidArea;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If it's not a direct address, treat it as a tag name
|
|
tagAddress.areaType = EArea.InvalidArea;
|
|
tagAddress.tagType = EDataType.Unknown;
|
|
}
|
|
|
|
return tagAddress;
|
|
}
|
|
|
|
private static void ParseNonDBAddress(string tag, TagAddress tagAddress)
|
|
{
|
|
var match = Regex.Match(tag, @"^%[EMA](\d+)\.(\d+)$");
|
|
if (match.Success)
|
|
{
|
|
tagAddress.word_offset = uint.Parse(match.Groups[1].Value);
|
|
tagAddress.bit = byte.Parse(match.Groups[2].Value);
|
|
tagAddress.tagType = EDataType.Bool;
|
|
}
|
|
else
|
|
{
|
|
match = Regex.Match(tag, @"^%[EMA]([XBWDF])(\d+)$");
|
|
if (match.Success)
|
|
{
|
|
char dataTypeChar = match.Groups[1].Value[0];
|
|
tagAddress.word_offset = uint.Parse(match.Groups[2].Value);
|
|
|
|
switch (dataTypeChar)
|
|
{
|
|
case 'X':
|
|
tagAddress.tagType = EDataType.Bool;
|
|
break;
|
|
case 'B':
|
|
tagAddress.tagType = EDataType.Byte;
|
|
break;
|
|
case 'W':
|
|
tagAddress.tagType = EDataType.Word;
|
|
break;
|
|
case 'D':
|
|
tagAddress.tagType = EDataType.DWord;
|
|
break;
|
|
default:
|
|
tagAddress.tagType = EDataType.Unknown;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class TagAddress
|
|
{
|
|
public EArea areaType;
|
|
public EDataType tagType;
|
|
public int DB;
|
|
public uint word_offset;
|
|
public byte bit;
|
|
}
|
|
|
|
}
|