using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Sharp7; namespace LibS7Adv { /// /// Implementación de conexión PLC usando la librería Sharp7 (C#) /// 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 _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.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; } } } }