using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ScaBox30.Controller
{
///
/// Reemplazo del ReaderAccessor del SDK de Keyence
/// Implementa conexión TCP/IP directa para SR-X300W
///
public class TcpReaderAccessor : IDisposable
{
private TcpClient client;
private NetworkStream stream;
private bool isConnected = false;
private CancellableBackgroundWorker receiverWorker;
private Action 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();
}
///
/// Conecta al sensor SR-X300W y registra callback para datos recibidos
/// Compatible con: m_reader.Connect((data) => { ... })
///
public bool Connect(Action 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;
}
}
///
/// Ejecuta un comando y espera respuesta COMPLETA usando async/await
/// BASADO EN LA APP DE PRUEBA QUE FUNCIONA CORRECTAMENTE
///
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 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;
}
}
///
/// Inicia un worker en background para recibir datos asíncronos
/// Esto simula el comportamiento del SDK cuando hay lecturas continuas
///
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();
}
///
/// Cierra la conexión TCP
///
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();
}
}
///
/// Enumeración de códigos de error compatible con el SDK
///
public enum ErrorCode
{
None = 0,
AlreadyOpen = 1,
Closed = 2,
OpenFailed = 3,
Timeout = 4,
SendFailed = 5,
BeginReceiveFailed = 6,
InvalidParameter = 7
}
///
/// BackgroundWorker con soporte para cancelación
///
public class CancellableBackgroundWorker : System.ComponentModel.BackgroundWorker
{
public CancellableBackgroundWorker()
{
WorkerSupportsCancellation = true;
}
}
}