319 lines
12 KiB
C#
319 lines
12 KiB
C#
using System;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ScaBox30.Controller
|
|
{
|
|
/// <summary>
|
|
/// Reemplazo del ReaderAccessor del SDK de Keyence
|
|
/// Implementa conexión TCP/IP directa para SR-X300W
|
|
/// </summary>
|
|
public class TcpReaderAccessor : IDisposable
|
|
{
|
|
private TcpClient client;
|
|
private NetworkStream stream;
|
|
private bool isConnected = false;
|
|
private CancellableBackgroundWorker receiverWorker;
|
|
private Action<byte[]> onDataReceived;
|
|
|
|
// Propiedades compatibles con el SDK original
|
|
public string IpAddress { get; set; }
|
|
public int Port { get; set; } = 9004; // Puerto TCP de comandos SR-X300W
|
|
public ErrorCode LastErrorInfo { get; private set; } = ErrorCode.None;
|
|
public int ReceiveTimeout { get; set; } = 30000; // 5 segundos
|
|
|
|
public TcpReaderAccessor()
|
|
{
|
|
receiverWorker = new CancellableBackgroundWorker();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Conecta al sensor SR-X300W y registra callback para datos recibidos
|
|
/// Compatible con: m_reader.Connect((data) => { ... })
|
|
/// </summary>
|
|
public bool Connect(Action<byte[]> dataReceivedCallback)
|
|
{
|
|
if (string.IsNullOrEmpty(IpAddress))
|
|
{
|
|
LastErrorInfo = ErrorCode.InvalidParameter;
|
|
Console.WriteLine("ERROR: IpAddress no configurada");
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
Console.WriteLine($"[TCP] Conectando a {IpAddress}:{Port}...");
|
|
|
|
// Crear y conectar TcpClient
|
|
client = new TcpClient();
|
|
client.Connect(IpAddress, Port);
|
|
stream = client.GetStream();
|
|
stream.ReadTimeout = ReceiveTimeout;
|
|
|
|
isConnected = true;
|
|
onDataReceived = dataReceivedCallback;
|
|
LastErrorInfo = ErrorCode.None;
|
|
|
|
Console.WriteLine($"[TCP] Conectado exitosamente a {IpAddress}:{Port}");
|
|
|
|
// Iniciar listener asíncrono (similar al SDK)
|
|
//StartAsyncReceiver();
|
|
|
|
return true;
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
Console.WriteLine($"[TCP] Error de conexión: {ex.Message}");
|
|
LastErrorInfo = ErrorCode.OpenFailed;
|
|
isConnected = false;
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[TCP] Error inesperado: {ex.Message}");
|
|
LastErrorInfo = ErrorCode.OpenFailed;
|
|
isConnected = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ejecuta un comando y espera respuesta COMPLETA usando async/await
|
|
/// BASADO EN LA APP DE PRUEBA QUE FUNCIONA CORRECTAMENTE
|
|
/// </summary>
|
|
public string ExecCommand(string command)
|
|
{
|
|
if (!isConnected || stream == null)
|
|
{
|
|
LastErrorInfo = ErrorCode.Closed;
|
|
Console.WriteLine($"[TCP] ERROR: No conectado al ejecutar '{command}'");
|
|
return string.Empty;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Agregar terminador de línea si no lo tiene
|
|
string fullCommand = command.EndsWith("\r") ? command : command + "\r";
|
|
byte[] cmdBytes = Encoding.ASCII.GetBytes(fullCommand);
|
|
|
|
Console.WriteLine($"[TCP] Enviando comando: '{command}' ({cmdBytes.Length} bytes)");
|
|
Console.WriteLine($"[TCP] Bytes a enviar: [{BitConverter.ToString(cmdBytes)}]");
|
|
|
|
// Enviar comando (SÍNCRONO como en tu app original)
|
|
stream.Write(cmdBytes, 0, cmdBytes.Length);
|
|
stream.Flush();
|
|
Console.WriteLine("[TCP] Bytes escritos ✓");
|
|
|
|
// **CAMBIO CRÍTICO**: Usar el patrón de la app de prueba
|
|
Console.WriteLine($"[TCP] Leyendo respuesta (timeout {ReceiveTimeout}ms)...");
|
|
|
|
byte[] buffer = new byte[8192];
|
|
StringBuilder responseBuilder = new StringBuilder();
|
|
|
|
// Crear task de lectura asíncrona
|
|
Task<int> readTask = stream.ReadAsync(buffer, 0, buffer.Length);
|
|
|
|
// Esperar con timeout (igual que la app de prueba)
|
|
if (readTask.Wait(ReceiveTimeout))
|
|
{
|
|
int bytesRead = readTask.Result;
|
|
Console.WriteLine($"[TCP] Primera lectura: {bytesRead} bytes");
|
|
|
|
if (bytesRead > 0)
|
|
{
|
|
string chunk = Encoding.ASCII.GetString(buffer, 0, bytesRead);
|
|
responseBuilder.Append(chunk);
|
|
|
|
// Si contiene \r, ya terminó
|
|
if (chunk.Contains("\r"))
|
|
{
|
|
string response = responseBuilder.ToString();
|
|
Console.WriteLine($"[TCP] Respuesta completa en primer paquete: {response.Length} bytes");
|
|
Console.WriteLine($"[TCP] Respuesta HEX: [{BitConverter.ToString(buffer, 0, bytesRead)}]");
|
|
|
|
string preview = response.Length > 100
|
|
? response.Substring(0, 50) + "..." + response.Substring(response.Length - 50)
|
|
: response.TrimEnd('\r', '\n');
|
|
Console.WriteLine($"[TCP] Preview: '{preview}'");
|
|
|
|
LastErrorInfo = ErrorCode.None;
|
|
return response;
|
|
}
|
|
|
|
// Si NO terminó, leer más paquetes
|
|
Console.WriteLine("[TCP] Respuesta incompleta, leyendo más paquetes...");
|
|
DateTime startTime = DateTime.Now;
|
|
int packetsRead = 1;
|
|
|
|
while ((DateTime.Now - startTime).TotalMilliseconds < ReceiveTimeout)
|
|
{
|
|
if (stream.DataAvailable)
|
|
{
|
|
readTask = stream.ReadAsync(buffer, 0, buffer.Length);
|
|
if (readTask.Wait(5000)) // 5s para paquetes adicionales
|
|
{
|
|
bytesRead = readTask.Result;
|
|
if (bytesRead > 0)
|
|
{
|
|
packetsRead++;
|
|
chunk = Encoding.ASCII.GetString(buffer, 0, bytesRead);
|
|
responseBuilder.Append(chunk);
|
|
Console.WriteLine($"[TCP] Paquete {packetsRead}: {bytesRead} bytes");
|
|
|
|
if (chunk.Contains("\r"))
|
|
{
|
|
break; // Terminador encontrado
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
System.Threading.Thread.Sleep(50); // Esperar más datos
|
|
}
|
|
}
|
|
|
|
string finalResponse = responseBuilder.ToString();
|
|
Console.WriteLine($"[TCP] Respuesta COMPLETA: {finalResponse.Length} bytes en {packetsRead} paquetes");
|
|
|
|
string finalPreview = finalResponse.Length > 100
|
|
? finalResponse.Substring(0, 50) + "..." + finalResponse.Substring(finalResponse.Length - 50)
|
|
: finalResponse.TrimEnd('\r', '\n');
|
|
Console.WriteLine($"[TCP] Preview final: '{finalPreview}'");
|
|
|
|
LastErrorInfo = ErrorCode.None;
|
|
return finalResponse;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("[TCP] Primera lectura devolvió 0 bytes");
|
|
LastErrorInfo = ErrorCode.None;
|
|
return string.Empty;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[TCP] TIMEOUT esperando respuesta de '{command}'");
|
|
LastErrorInfo = ErrorCode.Timeout;
|
|
return string.Empty;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[TCP] Error en ExecCommand '{command}': {ex.Message}");
|
|
Console.WriteLine($"[TCP] StackTrace: {ex.StackTrace}");
|
|
LastErrorInfo = ErrorCode.SendFailed;
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inicia un worker en background para recibir datos asíncronos
|
|
/// Esto simula el comportamiento del SDK cuando hay lecturas continuas
|
|
/// </summary>
|
|
private void StartAsyncReceiver()
|
|
{
|
|
receiverWorker.DoWork += (sender, e) =>
|
|
{
|
|
byte[] buffer = new byte[8192];
|
|
|
|
while (!receiverWorker.CancellationPending && isConnected)
|
|
{
|
|
try
|
|
{
|
|
if (stream != null && stream.DataAvailable)
|
|
{
|
|
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
if (bytesRead > 0)
|
|
{
|
|
byte[] data = new byte[bytesRead];
|
|
Array.Copy(buffer, data, bytesRead);
|
|
|
|
// Llamar al callback registrado
|
|
onDataReceived?.Invoke(data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Thread.Sleep(50); // Pequeña pausa para no saturar CPU
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[TCP] Error en receiver: {ex.Message}");
|
|
LastErrorInfo = ErrorCode.BeginReceiveFailed;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
receiverWorker.RunWorkerAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cierra la conexión TCP
|
|
/// </summary>
|
|
public void Disconnect()
|
|
{
|
|
try
|
|
{
|
|
Console.WriteLine("[TCP] Desconectando...");
|
|
|
|
isConnected = false;
|
|
|
|
// Detener receiver
|
|
if (receiverWorker != null && receiverWorker.IsBusy)
|
|
{
|
|
receiverWorker.CancelAsync();
|
|
Thread.Sleep(100); // Dar tiempo para que termine
|
|
}
|
|
|
|
stream?.Close();
|
|
client?.Close();
|
|
|
|
LastErrorInfo = ErrorCode.None;
|
|
Console.WriteLine("[TCP] Desconectado correctamente");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[TCP] Error al desconectar: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Disconnect();
|
|
stream?.Dispose();
|
|
client?.Dispose();
|
|
receiverWorker?.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumeración de códigos de error compatible con el SDK
|
|
/// </summary>
|
|
public enum ErrorCode
|
|
{
|
|
None = 0,
|
|
AlreadyOpen = 1,
|
|
Closed = 2,
|
|
OpenFailed = 3,
|
|
Timeout = 4,
|
|
SendFailed = 5,
|
|
BeginReceiveFailed = 6,
|
|
InvalidParameter = 7
|
|
}
|
|
|
|
/// <summary>
|
|
/// BackgroundWorker con soporte para cancelación
|
|
/// </summary>
|
|
public class CancellableBackgroundWorker : System.ComponentModel.BackgroundWorker
|
|
{
|
|
public CancellableBackgroundWorker()
|
|
{
|
|
WorkerSupportsCancellation = true;
|
|
}
|
|
}
|
|
} |