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; } } }