559 lines
19 KiB
C#
559 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Sharp7;
|
|
|
|
namespace LibS7Adv
|
|
{
|
|
/// <summary>
|
|
/// Implementación de conexión PLC usando la librería Sharp7 (C#)
|
|
/// </summary>
|
|
public class Sharp7Connection : IPlcConnection
|
|
{
|
|
private S7Client _client;
|
|
private bool _isConnected;
|
|
private string _ipAddress = "";
|
|
private int _rack;
|
|
private int _slot;
|
|
private string _lastError = "";
|
|
private readonly Dictionary<EArea, int> _areaMapping;
|
|
|
|
public bool IsConnected => _isConnected && _client != null && _client.Connected;
|
|
public string Status => IsConnected ? "Conectado" : "Desconectado";
|
|
public string LastError => _lastError;
|
|
public ConnectionType ConnectionType => LibS7Adv.ConnectionType.Sharp7;
|
|
|
|
public Sharp7Connection()
|
|
{
|
|
_client = new S7Client();
|
|
_isConnected = false;
|
|
_lastError = "";
|
|
|
|
// Mapeo de áreas de memoria entre nuestro enum y Sharp7
|
|
_areaMapping = new Dictionary<EArea, int>
|
|
{
|
|
{ EArea.Input, (int)S7Area.PE },
|
|
{ EArea.Output, (int)S7Area.PA },
|
|
{ EArea.Marker, (int)S7Area.MK },
|
|
{ EArea.DataBlock, (int)S7Area.DB }
|
|
};
|
|
}
|
|
|
|
public bool Connect(string ipAddress, string instanceName)
|
|
{
|
|
try
|
|
{
|
|
_ipAddress = ipAddress;
|
|
// Para Sharp7, usamos valores por defecto para rack y slot
|
|
_rack = 0;
|
|
_slot = 2; // Valor típico para S7-300
|
|
|
|
int result = _client.ConnectTo(ipAddress, _rack, _slot);
|
|
_isConnected = result == 0;
|
|
|
|
if (!_isConnected)
|
|
{
|
|
_lastError = $"Error conectando: {result}";
|
|
}
|
|
else
|
|
{
|
|
_lastError = "";
|
|
}
|
|
|
|
return _isConnected;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_isConnected = false;
|
|
_lastError = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
try
|
|
{
|
|
_client?.Disconnect();
|
|
_lastError = "";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
}
|
|
finally
|
|
{
|
|
_isConnected = false;
|
|
}
|
|
}
|
|
|
|
public bool? ReadBool(string address)
|
|
{
|
|
if (!IsConnected) return null;
|
|
|
|
try
|
|
{
|
|
var parsed = ParseAddress(address);
|
|
if (parsed == null) return null;
|
|
|
|
// Para bits, necesitamos calcular el offset en bits
|
|
int bitOffset = (parsed.Offset * 8) + parsed.Bit;
|
|
byte[] buffer = new byte[1];
|
|
|
|
int result = _client.ReadArea(parsed.Area, parsed.DbNumber, bitOffset, 1, (int)S7WordLength.Bit, buffer);
|
|
|
|
if (result == 0)
|
|
{
|
|
return S7.GetBitAt(buffer, 0, 0);
|
|
}
|
|
|
|
_lastError = $"Error leyendo bool: {result}";
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public bool WriteBool(string address, bool value)
|
|
{
|
|
if (!IsConnected) return false;
|
|
|
|
try
|
|
{
|
|
var parsed = ParseAddress(address);
|
|
if (parsed == null) return false;
|
|
|
|
// Para bits, necesitamos calcular el offset en bits
|
|
int bitOffset = (parsed.Offset * 8) + parsed.Bit;
|
|
byte[] buffer = new byte[1];
|
|
|
|
S7.SetBitAt(buffer, 0, 0, value);
|
|
|
|
int result = _client.WriteArea(parsed.Area, parsed.DbNumber, bitOffset, 1, (int)S7WordLength.Bit, buffer);
|
|
|
|
if (result != 0)
|
|
{
|
|
_lastError = $"Error escribiendo bool: {result}";
|
|
return false;
|
|
}
|
|
|
|
_lastError = "";
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool ReadOutputBool(byte pByte, int pBit)
|
|
{
|
|
if (!IsConnected) return false;
|
|
|
|
try
|
|
{
|
|
int bitOffset = (pByte * 8) + pBit;
|
|
byte[] buffer = new byte[1];
|
|
|
|
int result = _client.ReadArea((int)S7Area.PA, 0, bitOffset, 1, (int)S7WordLength.Bit, buffer);
|
|
|
|
if (result == 0)
|
|
{
|
|
return S7.GetBitAt(buffer, 0, 0);
|
|
}
|
|
|
|
_lastError = $"Error leyendo output: {result}";
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void WriteInputBool(byte pByte, int pBit, bool value)
|
|
{
|
|
if (!IsConnected) return;
|
|
|
|
try
|
|
{
|
|
int bitOffset = (pByte * 8) + pBit;
|
|
byte[] buffer = new byte[1];
|
|
|
|
S7.SetBitAt(buffer, 0, 0, value);
|
|
|
|
int result = _client.WriteArea((int)S7Area.PE, 0, bitOffset, 1, (int)S7WordLength.Bit, buffer);
|
|
|
|
if (result != 0)
|
|
{
|
|
_lastError = $"Error escribiendo input: {result}";
|
|
}
|
|
else
|
|
{
|
|
_lastError = "";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
}
|
|
}
|
|
|
|
public string ReadNumber(string address, bool signed = false)
|
|
{
|
|
if (!IsConnected) return "";
|
|
|
|
try
|
|
{
|
|
var parsed = ParseNumberAddress(address);
|
|
if (parsed == null) return "";
|
|
|
|
byte[] buffer;
|
|
int wordLen;
|
|
int amount = 1;
|
|
|
|
// Determinar el tamaño del buffer y WordLen según el tipo de dato
|
|
switch (parsed.DataType.ToUpper())
|
|
{
|
|
case "B":
|
|
case "BYTE":
|
|
buffer = new byte[1];
|
|
wordLen = (int)S7WordLength.Byte;
|
|
break;
|
|
case "W":
|
|
case "WORD":
|
|
buffer = new byte[2];
|
|
wordLen = (int)S7WordLength.Word;
|
|
break;
|
|
case "DW":
|
|
case "DWORD":
|
|
buffer = new byte[4];
|
|
wordLen = (int)S7WordLength.DWord;
|
|
break;
|
|
case "R":
|
|
case "REAL":
|
|
buffer = new byte[4];
|
|
wordLen = (int)S7WordLength.Real;
|
|
break;
|
|
default:
|
|
return "";
|
|
}
|
|
|
|
int result = _client.ReadArea(parsed.Area, parsed.DbNumber, parsed.Offset, amount, wordLen, buffer);
|
|
|
|
if (result == 0)
|
|
{
|
|
return parsed.DataType.ToUpper() switch
|
|
{
|
|
"B" or "BYTE" => S7.GetByteAt(buffer, 0).ToString(),
|
|
"W" or "WORD" => signed ? S7.GetIntAt(buffer, 0).ToString() : S7.GetWordAt(buffer, 0).ToString(),
|
|
"DW" or "DWORD" => signed ? S7.GetDIntAt(buffer, 0).ToString() : S7.GetDWordAt(buffer, 0).ToString(),
|
|
"R" or "REAL" => S7.GetRealAt(buffer, 0).ToString(),
|
|
_ => ""
|
|
};
|
|
}
|
|
|
|
_lastError = $"Error leyendo número: {result}";
|
|
return "";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public bool WriteNumber(string address, object value)
|
|
{
|
|
if (!IsConnected) return false;
|
|
|
|
try
|
|
{
|
|
var parsed = ParseNumberAddress(address);
|
|
if (parsed == null) return false;
|
|
|
|
byte[] buffer;
|
|
int wordLen;
|
|
int amount = 1;
|
|
|
|
// Convertir el valor
|
|
double numValue = Convert.ToDouble(value);
|
|
|
|
// Determinar el tamaño del buffer y WordLen según el tipo de dato
|
|
switch (parsed.DataType.ToUpper())
|
|
{
|
|
case "B":
|
|
case "BYTE":
|
|
buffer = new byte[1];
|
|
wordLen = (int)S7WordLength.Byte;
|
|
S7.SetByteAt(buffer, 0, (byte)numValue);
|
|
break;
|
|
case "W":
|
|
case "WORD":
|
|
buffer = new byte[2];
|
|
wordLen = (int)S7WordLength.Word;
|
|
S7.SetWordAt(buffer, 0, (ushort)numValue);
|
|
break;
|
|
case "DW":
|
|
case "DWORD":
|
|
buffer = new byte[4];
|
|
wordLen = (int)S7WordLength.DWord;
|
|
S7.SetDWordAt(buffer, 0, (uint)numValue);
|
|
break;
|
|
case "R":
|
|
case "REAL":
|
|
buffer = new byte[4];
|
|
wordLen = (int)S7WordLength.Real;
|
|
S7.SetRealAt(buffer, 0, (float)numValue);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
int result = _client.WriteArea(parsed.Area, parsed.DbNumber, parsed.Offset, amount, wordLen, buffer);
|
|
|
|
if (result != 0)
|
|
{
|
|
_lastError = $"Error escribiendo número: {result}";
|
|
return false;
|
|
}
|
|
|
|
_lastError = "";
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Tags no son soportados en Sharp7 (solo direccionamiento directo)
|
|
public object? ReadTag(string tagName)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBX0.0'");
|
|
}
|
|
|
|
public bool WriteTag(string tagName, object value)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBX0.0'");
|
|
}
|
|
|
|
public bool? ReadTagBool(string tagName)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBX0.0'");
|
|
}
|
|
|
|
public bool WriteTagBool(string tagName, bool value)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBX0.0'");
|
|
}
|
|
|
|
public int? ReadTagInt16(string tagName)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBW0'");
|
|
}
|
|
|
|
public bool WriteTagInt16(string tagName, int value)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBW0'");
|
|
}
|
|
|
|
public int? ReadTagDInt(string tagName)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBD0'");
|
|
}
|
|
|
|
public bool WriteTagDInt(string tagName, int value)
|
|
{
|
|
throw new NotSupportedException("Sharp7 no soporta tags simbólicos. Use direccionamiento directo como 'DB1.DBD0'");
|
|
}
|
|
|
|
public byte[]? ReadBytes(EArea area, uint offset, int length)
|
|
{
|
|
if (!IsConnected) return null;
|
|
|
|
try
|
|
{
|
|
if (!_areaMapping.TryGetValue(area, out int s7Area))
|
|
return null;
|
|
|
|
byte[] buffer = new byte[length];
|
|
int result = _client.ReadArea(s7Area, 0, (int)offset, length, (int)S7WordLength.Byte, buffer);
|
|
|
|
if (result == 0)
|
|
{
|
|
return buffer;
|
|
}
|
|
|
|
_lastError = $"Error leyendo bytes: {result}";
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public bool WriteBytes(EArea area, uint offset, byte[] data)
|
|
{
|
|
if (!IsConnected) return false;
|
|
|
|
try
|
|
{
|
|
if (!_areaMapping.TryGetValue(area, out int s7Area))
|
|
return false;
|
|
|
|
int result = _client.WriteArea(s7Area, 0, (int)offset, data.Length, (int)S7WordLength.Byte, data);
|
|
|
|
if (result != 0)
|
|
{
|
|
_lastError = $"Error escribiendo bytes: {result}";
|
|
return false;
|
|
}
|
|
|
|
_lastError = "";
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lastError = ex.Message;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private AddressParsed? ParseAddress(string address)
|
|
{
|
|
try
|
|
{
|
|
// Ejemplos: "DB1.DBX0.0", "I0.0", "Q0.1", "M0.0"
|
|
if (address.StartsWith("DB"))
|
|
{
|
|
// DB1.DBX0.0
|
|
var parts = address.Split('.');
|
|
if (parts.Length >= 3)
|
|
{
|
|
int dbNum = int.Parse(parts[0].Substring(2));
|
|
int offset = int.Parse(parts[1].Substring(3));
|
|
int bit = int.Parse(parts[2]);
|
|
return new AddressParsed { Area = (int)S7Area.DB, DbNumber = dbNum, Offset = offset, Bit = bit };
|
|
}
|
|
}
|
|
else if (address.StartsWith("I"))
|
|
{
|
|
// I0.0
|
|
var parts = address.Split('.');
|
|
if (parts.Length >= 2)
|
|
{
|
|
int offset = int.Parse(parts[0].Substring(1));
|
|
int bit = int.Parse(parts[1]);
|
|
return new AddressParsed { Area = (int)S7Area.PE, DbNumber = 0, Offset = offset, Bit = bit };
|
|
}
|
|
}
|
|
else if (address.StartsWith("Q"))
|
|
{
|
|
// Q0.0
|
|
var parts = address.Split('.');
|
|
if (parts.Length >= 2)
|
|
{
|
|
int offset = int.Parse(parts[0].Substring(1));
|
|
int bit = int.Parse(parts[1]);
|
|
return new AddressParsed { Area = (int)S7Area.PA, DbNumber = 0, Offset = offset, Bit = bit };
|
|
}
|
|
}
|
|
else if (address.StartsWith("M"))
|
|
{
|
|
// M0.0
|
|
var parts = address.Split('.');
|
|
if (parts.Length >= 2)
|
|
{
|
|
int offset = int.Parse(parts[0].Substring(1));
|
|
int bit = int.Parse(parts[1]);
|
|
return new AddressParsed { Area = (int)S7Area.MK, DbNumber = 0, Offset = offset, Bit = bit };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private NumberAddressParsed? ParseNumberAddress(string address)
|
|
{
|
|
try
|
|
{
|
|
// Ejemplos: "DB1.DBW0", "DB1.DBD0", "DB1.DBB0", "IW0", "QW0", "MW0"
|
|
if (address.StartsWith("DB"))
|
|
{
|
|
// DB1.DBW0
|
|
var parts = address.Split('.');
|
|
if (parts.Length >= 2)
|
|
{
|
|
int dbNum = int.Parse(parts[0].Substring(2));
|
|
string dataTypePart = parts[1].Substring(2); // W0, D0, B0, etc.
|
|
string dataType = dataTypePart.Substring(0, 1); // W, D, B
|
|
int offset = int.Parse(dataTypePart.Substring(1));
|
|
return new NumberAddressParsed { Area = (int)S7Area.DB, DbNumber = dbNum, Offset = offset, DataType = dataType };
|
|
}
|
|
}
|
|
else if (address.StartsWith("IW") || address.StartsWith("IB") || address.StartsWith("ID"))
|
|
{
|
|
string dataType = address.Substring(1, 1);
|
|
int offset = int.Parse(address.Substring(2));
|
|
return new NumberAddressParsed { Area = (int)S7Area.PE, DbNumber = 0, Offset = offset, DataType = dataType };
|
|
}
|
|
else if (address.StartsWith("QW") || address.StartsWith("QB") || address.StartsWith("QD"))
|
|
{
|
|
string dataType = address.Substring(1, 1);
|
|
int offset = int.Parse(address.Substring(2));
|
|
return new NumberAddressParsed { Area = (int)S7Area.PA, DbNumber = 0, Offset = offset, DataType = dataType };
|
|
}
|
|
else if (address.StartsWith("MW") || address.StartsWith("MB") || address.StartsWith("MD"))
|
|
{
|
|
string dataType = address.Substring(1, 1);
|
|
int offset = int.Parse(address.Substring(2));
|
|
return new NumberAddressParsed { Area = (int)S7Area.MK, DbNumber = 0, Offset = offset, DataType = dataType };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Disconnect();
|
|
_client = null!;
|
|
}
|
|
|
|
private class AddressParsed
|
|
{
|
|
public int Area { get; set; }
|
|
public int DbNumber { get; set; }
|
|
public int Offset { get; set; }
|
|
public int Bit { get; set; }
|
|
}
|
|
|
|
private class NumberAddressParsed
|
|
{
|
|
public int Area { get; set; }
|
|
public int DbNumber { get; set; }
|
|
public int Offset { get; set; }
|
|
public required string DataType { get; set; }
|
|
}
|
|
}
|
|
}
|