- Diseño de la arquitectura de comunicación
1.1 Arquitectura en capas del protocolo
public class ClienteComunicacionOmronFins
{
// Capa de transporte
private TcpClient _socketTcp;
private NetworkStream _flujoRed;
// Capa de protocolo
private byte[] _cabecera = { 0x46, 0x49, 0x4E, 0x53 }; // Cabecera FINS
private byte[] _paqueteMano = new byte[20]; // Paquete de handshake
// Gestión de estado
private bool _conectado = false;
private byte _nodoCliente; // Nodo del cliente
private byte _nodoServidor; // Nodo del PLC
}
- Implementación central
2.1 Conexión TCP y establecimiento de handshake
public bool Conectar(string direccionIp, int puerto = 9600)
{
try
{
_socketTcp = new TcpClient();
_socketTcp.Connect(IPAddress.Parse(direccionIp), puerto);
_flujoRed = _socketTcp.GetStream();
// Construir paquete de handshake
PrepararPaqueteMano();
_flujoRed.Write(_paqueteMano, 0, _paqueteMano.Length);
// Recibir respuesta
byte[] buffer = new byte[24];
int bytesLeidos = _flujoRed.Read(buffer, 0, buffer.Length);
if (buffer[0] == 0x46 && buffer[1] == 0x49 && buffer[2] == 0x4E && buffer[3] == 0x53)
{
_nodoServidor = buffer[23]; // Obtener nodo del PLC
_conectado = true;
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error de conexión: {ex.Message}");
}
return false;
}
private void PrepararPaqueteMano()
{
Array.Clear(_paqueteMano, 0, _paqueteMano.Length);
_paqueteMano[0] = 0x46; // F
_paqueteMano[1] = 0x49; // I
_paqueteMano[2] = 0x4E; // N
_paqueteMano[3] = 0x53; // S
_paqueteMano[7] = 0x0C; // Longitud de datos
_paqueteMano[16] = 0x00; // Nodo del cliente (bajo)
_paqueteMano[17] = 0x00; // Nodo del cliente (alto)
}
2.2 Operaciones de lectura y escritura de datos
/// <summary>
/// Leer registros de datos del PLC (área D)
/// </summary>
public ushort[] LeerRegistrosD(string direccion, int cantidad)
{
ushort inicio = Convert.ToUInt16(direccion.Substring(1));
byte area = ObtenerCodigoArea(direccion);
// Construir trama de comando
byte[] comando = ConstruirComando(
ComandoFins.LecturaAreaMemoria,
area,
inicio,
cantidad
);
// Enviar y recibir datos
EnviarComando(comando);
return ProcesarRespuesta(cantidad * 2);
}
/// <summary>
/// Escribir en registros de retención del PLC (área HR)
/// </summary>
public bool EscribirRegistrosHR(string direccion, ushort[] valores)
{
ushort inicio = Convert.ToUInt16(direccion.Substring(1));
byte area = ObtenerCodigoArea(direccion);
// Construir trama de comando
byte[] comando = ConstruirComando(
ComandoFins.EscrituraAreaMemoria,
area,
inicio,
valores
);
// Enviar y verificar
EnviarComando(comando);
return VerificarConfirmacion();
}
private byte[] ConstruirComando(byte codigoPrincipal, byte codigoSecundario, ushort direccion, ushort cantidad)
{
byte[] cmd = new byte[26];
cmd[6] = 0x1A; // Longitud de datos
cmd[7] = 0x02; // Tipo de comando
cmd[20] = codigoPrincipal; // Código de solicitud principal
cmd[21] = codigoSecundario; // Código de solicitud secundario
cmd[22] = area; // Código de área
// Codificación de dirección y longitud...
return cmd;
}
- Implementación de funcionalidades clave
3.1 Mapeo de códigos de área
private byte ObtenerCodigoArea(string area)
{
return area switch
{
"DM" => 0x82,
"HR" => 0xB2,
"WR" => 0x31,
"CIO" => 0x30,
_ => throw new ArgumentException("Código de área no válido")
};
}
3.2 Análisis de mensajes
private ushort[] ProcesarRespuesta(int longitudEsperada)
{
byte[] buffer = new byte[longitudEsperada];
int bytesLeidos = _flujoRed.Read(buffer, 0, buffer.Length);
if (buffer[12] != 0x00 || buffer[13] != 0x00) // Verificar código de error
throw new Exception("El PLC devolvió un código de error");
// Convertir a arreglo de ushort
ushort[] resultado = new ushort[bytesLeidos / 2];
for (int i = 0; i < resultado.Length; i++)
{
resultado[i] = (ushort)(buffer[2*i] << 8 | buffer[2*i+1]);
}
return resultado;
}
- Manejo de excepciones y optimización
4.1 Mecanismo de reconexión automática
public void Reconectar()
{
Desconectar();
int intentos = 0;
while (!_conectado && intentos < 3)
{
try
{
Conectar(DireccionIp, Puerto);
intentos++;
}
catch { Thread.Sleep(500); }
}
}
4.2 Optimización de rendimiento
// Optimización de operaciones por lotes
public void EscrituraPorLotes(Dictionary<string, ushort[]> datos)
{
using (var flujoMemoria = new MemoryStream())
{
foreach (var elemento in datos)
{
byte[] cmd = ConstruirComando(
ComandoFins.EscrituraAreaMemoria,
ObtenerCodigoArea(elemento.Key),
Convert.ToUInt16(elemento.Key.Substring(1)),
elemento.Value
);
flujoMemoria.Write(cmd, 0, cmd.Length);
}
_flujoRed.Write(flujoMemoria.ToArray(), 0, flujoMemoria.Length);
}
}
- Ejemplo completo de uso
var plc = new ClienteComunicacionOmronFins();
if(plc.Conectar("192.168.1.100"))
{
try
{
// Leer D100-D105
ushort[] datos = plc.LeerRegistrosD("D100", 6);
plc.EscribirRegistrosHR("HR10", new ushort[] { 1234, 5678 });
// Monitoreo en tiempo real
Timer temporizador = new Timer(1000);
temporizador.Elapsed += (s,e) =>
{
float temperatura = plc.LeerFlotante("DM100");
Console.WriteLine($"Temperatura: {temperatura}℃");
};
temporizador.Start();
}
finally
{
plc.Desconectar();
}
}
- Herramientas de depuración y verificación
1. Análisis de paquetes con Wireshark:
Filtrar tcp.port==9600 para inspeccionar la estructura de mensajes FINS y validar el handshake y tramas de datos.
2. Herramienta de monitoreo de PLC:
Utilizar Omron FinsTool para verificar la comunicación básica, comparando resultados manuales con la ejecución del programa.
3. Registro de logs:
_flujoRed.Write(Encoding.ASCII.GetBytes($"[{DateTime.Now}] Escrito en HR10: 1234\r\n"));
- Solución a problemas comunes
| Fenómeno del problema | Solución |
|---|---|
| Tiempo de conexión agotado | Veriifcar configuración de firewall y estado de conexión del puerto del PLC |
| Error de validación de datos | Asegurar la conversión correcta del orden de bytes (modo big-endian) |
| Fallo en escritura por lotes | Dividir operaciones (≤2000 bytes por vez) y añadir mecanismo de reintentos |
| Rendimiento insuficiente en tiempo real | Habilitar comunicación asíncrona y establecer _socketTcp.ReceiveTimeout = 1000; |
- Implementación de funciones extendidas
8.1 Mecanismo de caché de datos
private Dictionary<string, ushort[]> _almacenCache = new();
public ushort[] LeerConCache(string direccion, int cantidad)
{
if (!_almacenCache.ContainsKey(direccion) || _almacenCache[direccion] == null)
{
_almacenCache[direccion] = LeerRegistrosD(direccion, cantidad);
}
return _almacenCache[direccion];
}
8.2 Manejo de alarmas
public void VerificarAlarmaTemperatura(string direccion, float umbral)
{
float temp = LeerFlotante(direccion);
if (temp > umbral)
{
EnviarAlertaEmail($"¡Alarma de temperatura! {direccion}: {temp}℃");
plc.EscribirBit("CIO.0.0", true); // Activar relé de alarma
}
}
- Recomendaciones de despliegue de ingeniería
1. Configuración de hardware:
- PC industrial: Computadora industrial Advantech (soporte para operación en amplio rango de temperatura)
- Equipos de red: PLC OMRON CP1E + Switch compatible con FINS
2. Configuración de seguridad:
// Habilitar comunicación cifrada
_flujoRed = new SslStream(_socketTcp.GetStream(), false);
_flujoRed.AuthenticateAsClient("PLC001");