Post

Replies

Boosts

Views

Activity

Reply to Issues with TCP Socket Management and Ghost Data on ESP32 (Swift)
Hello, To assist with the investigation, I have created a simplified test project that isolates the issue I’m encountering. This specific implementation is where I am seeing the most consistent problems. Could you please take a look at this example and let me know if you see anything that might be causing the failure? The test code is provided below: // TCPClient.swift import Foundation import Network import Combine class TCPClient: ObservableObject { private var connection: NWConnection? private let queue = DispatchQueue(label: "TelnetQueue") @Published var currentMessage: String = "---" @Published var isConnected: Bool = false // Contador de soquetes solicitado para monitoramento private var socketAttemptCount: Int = 0 private let host: NWEndpoint.Host = "192.168.1.50" private let port: NWEndpoint.Port = 23 private let allowedCommands = ["HRS", "BTz", "BTd"] private var buffer = Data() func toggleConnection() { if isConnected { disconnect() } else { connect() } } func connect() { // Incrementa e imprime a contagem toda vez que tenta abrir uma nova conexão socketAttemptCount += 1 print("TENTATIVA DE ABERTURA DE SOQUETE Nº: \(socketAttemptCount)") guard connection == nil else { print("AVISO: Já existe uma instância de conexão. Abortando duplicata.") return } let tcpOptions = NWProtocolTCP.Options() tcpOptions.noDelay = true // Configuração de Keep-Alive de baixo nível (estilo Android) tcpOptions.enableKeepalive = true tcpOptions.keepaliveIdle = 5 // Tempo (segundos) de espera antes de testar a conexão tcpOptions.keepaliveInterval = 1 // Intervalo entre testes se não houver resposta tcpOptions.keepaliveCount = 3 // Tentativas antes de derrubar let parameters = NWParameters(tls: nil, tcp: tcpOptions) parameters.prohibitedInterfaceTypes = [.cellular] // Garante uso do Wi-Fi local connection = NWConnection(host: host, port: port, using: parameters) connection?.stateUpdateHandler = { [weak self] state in guard let self = self else { return } print("STATE UPDATE:", state) switch state { case .ready: print("ESTADO: Pronto (Conectado à balança)") DispatchQueue.main.async { self.isConnected = true self.currentMessage = "CONECTADO" self.receive() // Envia o comando inicial uma única vez DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if self.isConnected { self.sendHRS() } } } case .failed(let error): print("ESTADO: Falha crítica: \(error)") self.disconnect() case .cancelled: print("ESTADO: Soquete cancelado e memória liberada.") DispatchQueue.main.async { self.isConnected = false } case .waiting(let error): print("ESTADO: Aguardando rede (Pode ser erro de porta no ESP32): \(error)") default: break } } connection?.start(queue: queue) } func disconnect() { print("DISCONNECT CHAMADO - Limpando recursos...") // Zera o contador e o estado ao desconectar socketAttemptCount = 0 DispatchQueue.main.async { self.isConnected = false self.connection?.stateUpdateHandler = nil // Remove o handler antes de cancelar para evitar loops self.connection?.cancel() self.connection = nil } DispatchQueue.main.async { self.currentMessage = "DESCONECTADO" self.buffer.removeAll() print("Contador de soquetes zerado.") } } private func receive() { // Verificação de segurança: soquete deve existir e o app deve querer estar conectado guard let connection = connection, isConnected else { return } connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, isComplete, error in guard let self = self else { return } if let data = data, !data.isEmpty { self.buffer.append(data) // Processa apenas mensagens completas (terminadas em CRLF) while let range = self.buffer.range(of: Data([0x0d, 0x0a])) { let lineData = self.buffer.subdata(in: 0..<range.lowerBound) self.buffer.removeSubrange(0..<range.upperBound) if let text = String(data: lineData, encoding: .ascii) { DispatchQueue.main.async { // Limpa espaços e exibe o valor da balança self.currentMessage = text.trimmingCharacters(in: .whitespacesAndNewlines) } } } } // Se o ESP32 enviar um pacote de fechamento (FIN) if isComplete { print("AVISO: O servidor (ESP32) encerrou a conexão.") self.disconnect() return } if error != nil { self.disconnect() return } // Mantém a escuta ativa enquanto estiver conectado if self.isConnected { self.receive() } } } func send(_ command: String) { guard isConnected, let connection = connection, connection.state == .ready else { return } guard allowedCommands.contains(where: { command.starts(with: $0) }) else { return } guard let data = (command + "\r\n").data(using: .ascii) else { return } // Envio com proteção de processamento connection.send(content: data, completion: .contentProcessed { [weak self] error in if let error = error { print("Erro ao enviar comando: \(error)") self?.disconnect() } }) } // Funções auxiliares mantidas conforme sua estrutura original func buildHRSCommand() -> String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "HH:mm dd/MM/yy" return "HRS" + formatter.string(from: Date()) } func sendHRS() { send(buildHRSCommand()) } func sendBTz() { send("BTz") } func sendBTd() { send("BTd") } }
12h
Reply to Issues with TCP Socket Management and Ghost Data on ESP32 (Swift)
Hello, To assist with the investigation, I have created a simplified test project that isolates the issue I’m encountering. This specific implementation is where I am seeing the most consistent problems. Could you please take a look at this example and let me know if you see anything that might be causing the failure? The test code is provided below: // TCPClient.swift import Foundation import Network import Combine class TCPClient: ObservableObject { private var connection: NWConnection? private let queue = DispatchQueue(label: "TelnetQueue") @Published var currentMessage: String = "---" @Published var isConnected: Bool = false // Contador de soquetes solicitado para monitoramento private var socketAttemptCount: Int = 0 private let host: NWEndpoint.Host = "192.168.1.50" private let port: NWEndpoint.Port = 23 private let allowedCommands = ["HRS", "BTz", "BTd"] private var buffer = Data() func toggleConnection() { if isConnected { disconnect() } else { connect() } } func connect() { // Incrementa e imprime a contagem toda vez que tenta abrir uma nova conexão socketAttemptCount += 1 print("TENTATIVA DE ABERTURA DE SOQUETE Nº: \(socketAttemptCount)") guard connection == nil else { print("AVISO: Já existe uma instância de conexão. Abortando duplicata.") return } let tcpOptions = NWProtocolTCP.Options() tcpOptions.noDelay = true // Configuração de Keep-Alive de baixo nível (estilo Android) tcpOptions.enableKeepalive = true tcpOptions.keepaliveIdle = 5 // Tempo (segundos) de espera antes de testar a conexão tcpOptions.keepaliveInterval = 1 // Intervalo entre testes se não houver resposta tcpOptions.keepaliveCount = 3 // Tentativas antes de derrubar let parameters = NWParameters(tls: nil, tcp: tcpOptions) parameters.prohibitedInterfaceTypes = [.cellular] // Garante uso do Wi-Fi local connection = NWConnection(host: host, port: port, using: parameters) connection?.stateUpdateHandler = { [weak self] state in guard let self = self else { return } print("STATE UPDATE:", state) switch state { case .ready: print("ESTADO: Pronto (Conectado à balança)") DispatchQueue.main.async { self.isConnected = true self.currentMessage = "CONECTADO" self.receive() // Envia o comando inicial uma única vez DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if self.isConnected { self.sendHRS() } } } case .failed(let error): print("ESTADO: Falha crítica: \(error)") self.disconnect() case .cancelled: print("ESTADO: Soquete cancelado e memória liberada.") DispatchQueue.main.async { self.isConnected = false } case .waiting(let error): print("ESTADO: Aguardando rede (Pode ser erro de porta no ESP32): \(error)") default: break } } connection?.start(queue: queue) } func disconnect() { print("DISCONNECT CHAMADO - Limpando recursos...") // Zera o contador e o estado ao desconectar socketAttemptCount = 0 DispatchQueue.main.async { self.isConnected = false self.connection?.stateUpdateHandler = nil // Remove o handler antes de cancelar para evitar loops self.connection?.cancel() self.connection = nil } DispatchQueue.main.async { self.currentMessage = "DESCONECTADO" self.buffer.removeAll() print("Contador de soquetes zerado.") } } private func receive() { // Verificação de segurança: soquete deve existir e o app deve querer estar conectado guard let connection = connection, isConnected else { return } connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, isComplete, error in guard let self = self else { return } if let data = data, !data.isEmpty { self.buffer.append(data) // Processa apenas mensagens completas (terminadas em CRLF) while let range = self.buffer.range(of: Data([0x0d, 0x0a])) { let lineData = self.buffer.subdata(in: 0..<range.lowerBound) self.buffer.removeSubrange(0..<range.upperBound) if let text = String(data: lineData, encoding: .ascii) { DispatchQueue.main.async { // Limpa espaços e exibe o valor da balança self.currentMessage = text.trimmingCharacters(in: .whitespacesAndNewlines) } } } } // Se o ESP32 enviar um pacote de fechamento (FIN) if isComplete { print("AVISO: O servidor (ESP32) encerrou a conexão.") self.disconnect() return } if error != nil { self.disconnect() return } // Mantém a escuta ativa enquanto estiver conectado if self.isConnected { self.receive() } } } func send(_ command: String) { guard isConnected, let connection = connection, connection.state == .ready else { return } guard allowedCommands.contains(where: { command.starts(with: $0) }) else { return } guard let data = (command + "\r\n").data(using: .ascii) else { return } // Envio com proteção de processamento connection.send(content: data, completion: .contentProcessed { [weak self] error in if let error = error { print("Erro ao enviar comando: \(error)") self?.disconnect() } }) } // Funções auxiliares mantidas conforme sua estrutura original func buildHRSCommand() -> String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "HH:mm dd/MM/yy" return "HRS" + formatter.string(from: Date()) } func sendHRS() { send(buildHRSCommand()) } func sendBTz() { send("BTz") } func sendBTd() { send("BTd") } }
Replies
Boosts
Views
Activity
12h