diff --git a/App/tcp_client.c b/App/tcp_client.c index 5bcdbb2..87bc6c5 100644 --- a/App/tcp_client.c +++ b/App/tcp_client.c @@ -7,6 +7,8 @@ #include "main.h" +#include "SEGGER_RTT.h" + #include "lwip/ip_addr.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" @@ -85,11 +87,15 @@ static err_t tcp_client_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len) static void tcp_client_on_err(void *arg, err_t err) { tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg; - (void)err; + if (ctx == NULL) { + return; + } + ctx->pcb = NULL; ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED; ctx->status.errors++; ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms; + SEGGER_RTT_printf(0, "TCP client error=%d, reconnect scheduled\r\n", (int)err); } static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err) @@ -101,11 +107,17 @@ static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err) ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED; ctx->status.errors++; ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms; + SEGGER_RTT_printf(0, "TCP client connect callback failed err=%d\r\n", (int)err); return err; } ctx->pcb = pcb; ctx->status.state = TCP_CLIENT_STATE_CONNECTED; + SEGGER_RTT_printf(0, + "TCP client connected to %u.%u.%u.%u:%u\r\n", + ctx->config.server_ip[0], ctx->config.server_ip[1], + ctx->config.server_ip[2], ctx->config.server_ip[3], + ctx->config.server_port); tcp_nagle_disable(pcb); tcp_arg(pcb, ctx); tcp_recv(pcb, tcp_client_on_recv); @@ -147,6 +159,8 @@ int tcp_client_connect(void) pcb = tcp_new_ip_type(IPADDR_TYPE_V4); if (pcb == NULL) { g_client.status.errors++; + g_client.status.state = TCP_CLIENT_STATE_ERROR; + SEGGER_RTT_WriteString(0, "TCP client connect failed: no PCB\r\n"); return -1; } @@ -157,6 +171,7 @@ int tcp_client_connect(void) g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; g_client.status.errors++; g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms; + SEGGER_RTT_printf(0, "TCP client bind failed err=%d\r\n", (int)err); return -1; } } @@ -169,16 +184,24 @@ int tcp_client_connect(void) g_client.status.state = TCP_CLIENT_STATE_CONNECTING; tcp_arg(pcb, &g_client); + tcp_err(pcb, tcp_client_on_err); err = tcp_connect(pcb, &remote_addr, g_client.config.server_port, tcp_client_on_connected); if (err != ERR_OK) { + tcp_err(pcb, NULL); tcp_abort(pcb); g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; g_client.status.errors++; g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms; + SEGGER_RTT_printf(0, "TCP client connect start failed err=%d\r\n", (int)err); return -1; } g_client.pcb = pcb; + SEGGER_RTT_printf(0, + "TCP client connecting to %u.%u.%u.%u:%u\r\n", + g_client.config.server_ip[0], g_client.config.server_ip[1], + g_client.config.server_ip[2], g_client.config.server_ip[3], + g_client.config.server_port); return 0; } @@ -194,6 +217,7 @@ int tcp_client_disconnect(void) } g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; + SEGGER_RTT_WriteString(0, "TCP client disconnected\r\n"); return 0; } @@ -288,7 +312,11 @@ void tcp_client_poll(void) { uint32_t now; - if (!g_client.config.auto_reconnect || g_client.pcb != NULL) { + if (!g_client.config.auto_reconnect || tcp_client_is_connected()) { + return; + } + + if ((g_client.pcb != NULL) && (g_client.status.state == TCP_CLIENT_STATE_CONNECTING)) { return; } @@ -296,6 +324,9 @@ void tcp_client_poll(void) if (now >= g_client.next_retry_ms) { g_client.status.reconnect_count++; g_client.next_retry_ms = now + g_client.config.reconnect_interval_ms; + SEGGER_RTT_printf(0, + "TCP client reconnect attempt %lu\r\n", + g_client.status.reconnect_count); (void)tcp_client_connect(); } } diff --git a/App/tcp_server.c b/App/tcp_server.c index 524b2ed..8499a04 100644 --- a/App/tcp_server.c +++ b/App/tcp_server.c @@ -8,6 +8,8 @@ #include "lwip/pbuf.h" #include "lwip/tcp.h" +#include "SEGGER_RTT.h" + #include typedef struct { @@ -49,6 +51,7 @@ static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, } ctx->client_pcb = NULL; ctx->status.state = TCP_SERVER_STATE_LISTENING; + SEGGER_RTT_WriteString(0, "TCP server peer disconnected\r\n"); return ERR_OK; } @@ -85,6 +88,7 @@ static void tcp_server_on_err(void *arg, err_t err) ctx->client_pcb = NULL; ctx->status.state = TCP_SERVER_STATE_LISTENING; ctx->status.errors++; + SEGGER_RTT_printf(0, "TCP server connection error=%d\r\n", (int)err); } static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err) @@ -103,6 +107,7 @@ static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err) ctx->client_pcb = newpcb; ctx->status.state = TCP_SERVER_STATE_CONNECTED; ctx->status.connections++; + SEGGER_RTT_WriteString(0, "TCP server client connected\r\n"); tcp_nagle_disable(newpcb); @@ -158,6 +163,7 @@ int tcp_server_start(void) tcp_arg(g_server.listen_pcb, &g_server); tcp_accept(g_server.listen_pcb, tcp_server_on_accept); g_server.status.state = TCP_SERVER_STATE_LISTENING; + SEGGER_RTT_printf(0, "TCP server listening on %u\r\n", g_server.config.port); return 0; } diff --git a/Core/Src/main.c b/Core/Src/main.c index cda4d2e..9df4983 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -68,19 +68,44 @@ static volatile uint16_t g_led_blink_ticks = 0; static uint8_t g_clock_fallback_to_hsi = 0u; volatile uint8_t g_uart1_rx_probe_byte = 0u; +typedef struct { + uint8_t data[256]; + uint16_t len; +} tcp_bridge_buffer_t; + +static tcp_bridge_buffer_t g_server_to_client; +static tcp_bridge_buffer_t g_client_to_server; + static void App_ForwardTcpPair(void) { - uint8_t buffer[256]; - int len; + int rc; - len = tcp_server_recv(buffer, sizeof(buffer), 0u); - if (len > 0) { - (void)tcp_client_send(buffer, (uint16_t)len); + if ((g_server_to_client.len == 0u) && tcp_server_is_connected()) { + rc = tcp_server_recv(g_server_to_client.data, sizeof(g_server_to_client.data), 0u); + if (rc > 0) { + g_server_to_client.len = (uint16_t)rc; + } } - len = tcp_client_recv(buffer, sizeof(buffer), 0u); - if (len > 0) { - (void)tcp_server_send(buffer, (uint16_t)len); + if ((g_server_to_client.len != 0u) && tcp_client_is_connected()) { + rc = tcp_client_send(g_server_to_client.data, g_server_to_client.len); + if (rc == (int)g_server_to_client.len) { + g_server_to_client.len = 0u; + } + } + + if ((g_client_to_server.len == 0u) && tcp_client_is_connected()) { + rc = tcp_client_recv(g_client_to_server.data, sizeof(g_client_to_server.data), 0u); + if (rc > 0) { + g_client_to_server.len = (uint16_t)rc; + } + } + + if ((g_client_to_server.len != 0u) && tcp_server_is_connected()) { + rc = tcp_server_send(g_client_to_server.data, g_client_to_server.len); + if (rc == (int)g_client_to_server.len) { + g_client_to_server.len = 0u; + } } } diff --git a/tools/start_tcp_debug_server.ps1 b/tools/start_tcp_debug_server.ps1 new file mode 100644 index 0000000..7d63d2b --- /dev/null +++ b/tools/start_tcp_debug_server.ps1 @@ -0,0 +1,32 @@ +param( + [string]$BindHost = "0.0.0.0", + [int]$Port = 8081, + [switch]$Echo, + [switch]$NoStdin +) + +$listeners = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue +if ($listeners) { + $pids = $listeners | Select-Object -ExpandProperty OwningProcess -Unique + Write-Host "Stopping existing listeners on TCP ${Port}: $($pids -join ', ')" + foreach ($procId in $pids) { + try { + Stop-Process -Id $procId -Force -ErrorAction Stop + } + catch { + Write-Warning "Failed to stop process $procId : $_" + } + } + Start-Sleep -Milliseconds 300 +} + +$args = @("tools/tcp_debug_server.py", "--host", $BindHost, "--port", "$Port") +if ($Echo) { + $args += "--echo" +} +if ($NoStdin) { + $args += "--no-stdin" +} + +Write-Host "Starting TCP debug server on ${BindHost}:${Port}" +python @args diff --git a/tools/tcp_debug_server.py b/tools/tcp_debug_server.py new file mode 100644 index 0000000..a9778a7 --- /dev/null +++ b/tools/tcp_debug_server.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Simple TCP debug server for validating raw payload forwarding. + +Usage examples: + python tools/tcp_debug_server.py --host 0.0.0.0 --port 8081 + python tools/tcp_debug_server.py --port 8081 --echo + +Features: + - Listen as a plain TCP server + - Print connect/disconnect events + - Print received payload as text and hex + - Optional echo mode + - Optional stdin -> socket sender for manual testing +""" + +from __future__ import annotations + +import argparse +import select +import socket +import sys +import threading +from datetime import datetime + + +def ts() -> str: + return datetime.now().strftime("%H:%M:%S.%f")[:-3] + + +def hex_bytes(data: bytes) -> str: + return " ".join(f"{b:02X}" for b in data) + + +def text_view(data: bytes) -> str: + return "".join(chr(b) if 32 <= b < 127 else "." for b in data) + + +def sender_loop(conn: socket.socket, stop_event: threading.Event) -> None: + print(f"[{ts()}] stdin sender ready. Type text and press Enter to send.") + while not stop_event.is_set(): + line = sys.stdin.readline() + if line == "": + stop_event.set() + break + payload = line.encode("utf-8", errors="replace") + try: + conn.sendall(payload) + print( + f"[{ts()}] TX {len(payload)} bytes | text={payload!r} | hex={hex_bytes(payload)}" + ) + except OSError as exc: + print(f"[{ts()}] TX failed: {exc}") + stop_event.set() + break + + +def serve(host: str, port: int, echo: bool, no_stdin: bool) -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((host, port)) + server.listen(1) + print(f"[{ts()}] Listening on {host}:{port}") + + conn, addr = server.accept() + with conn: + print(f"[{ts()}] Client connected from {addr[0]}:{addr[1]}") + stop_event = threading.Event() + sender_thread = None + + if not no_stdin: + sender_thread = threading.Thread( + target=sender_loop, args=(conn, stop_event), daemon=True + ) + sender_thread.start() + + conn.setblocking(False) + + while not stop_event.is_set(): + ready, _, _ = select.select([conn], [], [], 0.2) + if not ready: + continue + + try: + data = conn.recv(4096) + except BlockingIOError: + continue + except OSError as exc: + print(f"[{ts()}] RX failed: {exc}") + break + + if not data: + print(f"[{ts()}] Client disconnected") + break + + print( + f"[{ts()}] RX {len(data)} bytes | text={text_view(data)} | hex={hex_bytes(data)}" + ) + + if echo: + try: + conn.sendall(data) + print(f"[{ts()}] ECHO {len(data)} bytes") + except OSError as exc: + print(f"[{ts()}] Echo failed: {exc}") + break + + stop_event.set() + if sender_thread is not None: + sender_thread.join(timeout=0.5) + + return 0 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Minimal raw TCP debug server") + parser.add_argument( + "--host", default="0.0.0.0", help="Listen host, default: 0.0.0.0" + ) + parser.add_argument( + "--port", type=int, default=8081, help="Listen port, default: 8081" + ) + parser.add_argument( + "--echo", action="store_true", help="Echo received payload back to client" + ) + parser.add_argument( + "--no-stdin", + action="store_true", + help="Disable stdin sender thread (receive-only mode)", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + try: + return serve(args.host, args.port, args.echo, args.no_stdin) + except KeyboardInterrupt: + print(f"\n[{ts()}] Stopped by user") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())