Post

Replies

Boosts

Views

Activity

Reply to Issues with TCP Socket Management and Ghost Data on ESP32 (Swift)
"Hello, thank you for the insights and the links; I am already reviewing the documentation to implement the necessary fixes. To provide more context on my scenario: the app communicates with a scale via TCP protocol. The scale creates its own dedicated Wi-Fi network exclusively for local network data transmission. The workflow is as follows: as soon as the app connects, the scale starts continuously sending data strings in the format: "C, 050.1,c,N,000.0,". The app is only supposed to receive and process these values. Regarding the disconnection issue: the scale is dropping the TCP connection. The hardware developer (scale side) analyzed their logs and identified that the scale is receiving strange character sequences, such as "ggggggggggfdhj". According to them, these strings are originating from the app. The critical point is that, in my iOS logs, I cannot find any record of the app sending this data, whether voluntarily or involuntarily. Since my code does not use unsafe pointers, as you observed, I am still trying to understand how NWConnection or the system could be triggering this content to the accessory."
May ’26
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") } }
May ’26
Reply to Issues with TCP Socket Management and Ghost Data on ESP32 (Swift)
"Hello, thank you for the insights and the links; I am already reviewing the documentation to implement the necessary fixes. To provide more context on my scenario: the app communicates with a scale via TCP protocol. The scale creates its own dedicated Wi-Fi network exclusively for local network data transmission. The workflow is as follows: as soon as the app connects, the scale starts continuously sending data strings in the format: "C, 050.1,c,N,000.0,". The app is only supposed to receive and process these values. Regarding the disconnection issue: the scale is dropping the TCP connection. The hardware developer (scale side) analyzed their logs and identified that the scale is receiving strange character sequences, such as "ggggggggggfdhj". According to them, these strings are originating from the app. The critical point is that, in my iOS logs, I cannot find any record of the app sending this data, whether voluntarily or involuntarily. Since my code does not use unsafe pointers, as you observed, I am still trying to understand how NWConnection or the system could be triggering this content to the accessory."
Replies
Boosts
Views
Activity
May ’26
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
May ’26